diff options
37 files changed, 771 insertions, 234 deletions
diff --git a/Makefile.in b/Makefile.in index a10705e7f..d34ee84fb 100644 --- a/Makefile.in +++ b/Makefile.in @@ -551,7 +551,7 @@ FUZZDATA = \ # Extra arguments for including json1 in the build of tools # JSON1_DEP = $(TOP)/ext/misc/json1.c sqlite3ext.h -JSON1_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_CORE +JSON1_OPT = -DSQLITE_SHELL_JSON1 -DSQLITE_CORE JSON1_SRC = $(TOP)/ext/misc/json1.c # Standard options to testfixture @@ -633,6 +633,9 @@ sqlite3.c: .target_source $(TOP)/tool/mksqlite3c.tcl cp tsrc/shell.c tsrc/sqlite3ext.h . cp $(TOP)/ext/session/sqlite3session.h . +sqlite3ext.h: .target_source + cp tsrc/sqlite3ext.h . + tclsqlite3.c: sqlite3.c echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c cat sqlite3.c >>tclsqlite3.c diff --git a/Makefile.msc b/Makefile.msc index 7f4ef98ab..b818ff160 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -390,9 +390,9 @@ CORE_LINK_OPTS = /DEF:sqlite3.def # !IFNDEF SHELL_COMPILE_OPTS !IF $(DYNAMIC_SHELL)!=0 -SHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 $(SHELL_CCONV_OPTS) -DSQLITE_API=__declspec(dllimport) +SHELL_COMPILE_OPTS = -DSQLITE_SHELL_JSON1 $(SHELL_CCONV_OPTS) -DSQLITE_API=__declspec(dllimport) !ELSE -SHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 $(SHELL_CCONV_OPTS) +SHELL_COMPILE_OPTS = -DSQLITE_SHELL_JSON1 $(SHELL_CCONV_OPTS) !ENDIF !ENDIF diff --git a/ext/fts3/fts3.c b/ext/fts3/fts3.c index 6a9b507fc..748faefec 100644 --- a/ext/fts3/fts3.c +++ b/ext/fts3/fts3.c @@ -1517,6 +1517,19 @@ static void fts3SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ #endif } +/* +** Set the SQLITE_INDEX_SCAN_UNIQUE flag in pIdxInfo->flags. Unless this +** extension is currently being used by a version of SQLite too old to +** support index-info flags. In that case this function is a no-op. +*/ +static void fts3SetUniqueFlag(sqlite3_index_info *pIdxInfo){ +#if SQLITE_VERSION_NUMBER>=3008012 + if( sqlite3_libversion_number()>=3008012 ){ + pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_UNIQUE; + } +#endif +} + /* ** Implementation of the xBestIndex method for FTS3 tables. There ** are three possible strategies, in order of preference: @@ -1607,6 +1620,9 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ } } + /* If using a docid=? or rowid=? strategy, set the UNIQUE flag. */ + if( pInfo->idxNum==FTS3_DOCID_SEARCH ) fts3SetUniqueFlag(pInfo); + iIdx = 1; if( iCons>=0 ){ pInfo->aConstraintUsage[iCons].argvIndex = iIdx++; diff --git a/ext/fts3/fts3Int.h b/ext/fts3/fts3Int.h index 981c37dee..06bcc7202 100644 --- a/ext/fts3/fts3Int.h +++ b/ext/fts3/fts3Int.h @@ -264,6 +264,7 @@ struct Fts3Table { int nPendingData; /* Current bytes of pending data */ sqlite_int64 iPrevDocid; /* Docid of most recently inserted document */ int iPrevLangid; /* Langid of recently inserted document */ + int bPrevDelete; /* True if last operation was a delete */ #if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) /* State variables used for validating that the transaction control diff --git a/ext/fts3/fts3_write.c b/ext/fts3/fts3_write.c index 4cd2aebf6..6b7d5ef2a 100644 --- a/ext/fts3/fts3_write.c +++ b/ext/fts3/fts3_write.c @@ -860,10 +860,12 @@ static int fts3PendingTermsAdd( */ static int fts3PendingTermsDocid( Fts3Table *p, /* Full-text table handle */ + int bDelete, /* True if this op is a delete */ int iLangid, /* Language id of row being written */ sqlite_int64 iDocid /* Docid of row being written */ ){ assert( iLangid>=0 ); + assert( bDelete==1 || bDelete==0 ); /* TODO(shess) Explore whether partially flushing the buffer on ** forced-flush would provide better performance. I suspect that if @@ -871,7 +873,8 @@ static int fts3PendingTermsDocid( ** buffer was half empty, that would let the less frequent terms ** generate longer doclists. */ - if( iDocid<=p->iPrevDocid + if( iDocid<p->iPrevDocid + || (iDocid==p->iPrevDocid && p->bPrevDelete==0) || p->iPrevLangid!=iLangid || p->nPendingData>p->nMaxPendingData ){ @@ -880,6 +883,7 @@ static int fts3PendingTermsDocid( } p->iPrevDocid = iDocid; p->iPrevLangid = iLangid; + p->bPrevDelete = bDelete; return SQLITE_OK; } @@ -1069,7 +1073,8 @@ static void fts3DeleteTerms( if( SQLITE_ROW==sqlite3_step(pSelect) ){ int i; int iLangid = langidFromSelect(p, pSelect); - rc = fts3PendingTermsDocid(p, iLangid, sqlite3_column_int64(pSelect, 0)); + i64 iDocid = sqlite3_column_int64(pSelect, 0); + rc = fts3PendingTermsDocid(p, 1, iLangid, iDocid); for(i=1; rc==SQLITE_OK && i<=p->nColumn; i++){ int iCol = i-1; if( p->abNotindexed[iCol]==0 ){ @@ -3512,7 +3517,7 @@ static int fts3DoRebuild(Fts3Table *p){ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ int iCol; int iLangid = langidFromSelect(p, pStmt); - rc = fts3PendingTermsDocid(p, iLangid, sqlite3_column_int64(pStmt, 0)); + rc = fts3PendingTermsDocid(p, 0, iLangid, sqlite3_column_int64(pStmt, 0)); memset(aSz, 0, sizeof(aSz[0]) * (p->nColumn+1)); for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){ if( p->abNotindexed[iCol]==0 ){ @@ -5617,7 +5622,7 @@ int sqlite3Fts3UpdateMethod( } } if( rc==SQLITE_OK && (!isRemove || *pRowid!=p->iPrevDocid ) ){ - rc = fts3PendingTermsDocid(p, iLangid, *pRowid); + rc = fts3PendingTermsDocid(p, 0, iLangid, *pRowid); } if( rc==SQLITE_OK ){ assert( p->iPrevDocid==*pRowid ); diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index b8aa52c2f..9910c59b0 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -5361,7 +5361,7 @@ static void fts5DecodeFunction( memset(&term, 0, sizeof(Fts5Buffer)); if( n<4 ){ - sqlite3Fts5BufferSet(&rc, &s, 8, (const u8*)"corrupt"); + sqlite3Fts5BufferSet(&rc, &s, 7, (const u8*)"corrupt"); goto decode_out; }else{ iRowidOff = fts5GetU16(&a[0]); diff --git a/ext/misc/json1.c b/ext/misc/json1.c index 89b70f083..77857f544 100644 --- a/ext/misc/json1.c +++ b/ext/misc/json1.c @@ -1937,19 +1937,12 @@ static sqlite3_module jsonTreeModule = { #endif /* SQLITE_OMIT_VIRTUALTABLE */ /**************************************************************************** -** The following routine is the only publically visible identifier in this -** file. Call the following routine in order to register the various SQL +** The following routines are the only publically visible identifiers in this +** file. Call the following routines in order to register the various SQL ** functions and the virtual table implemented by this file. ****************************************************************************/ -#ifdef _WIN32 -__declspec(dllexport) -#endif -int sqlite3_json_init( - sqlite3 *db, - char **pzErrMsg, - const sqlite3_api_routines *pApi -){ +int sqlite3Json1Init(sqlite3 *db){ int rc = SQLITE_OK; unsigned int i; static const struct { @@ -1987,8 +1980,6 @@ int sqlite3_json_init( { "json_tree", &jsonTreeModule }, }; #endif - SQLITE_EXTENSION_INIT2(pApi); - (void)pzErrMsg; /* Unused parameter */ for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){ rc = sqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg, SQLITE_UTF8 | SQLITE_DETERMINISTIC, @@ -2002,3 +1993,17 @@ int sqlite3_json_init( #endif return rc; } + + +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_json_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + return sqlite3Json1Init(db); +} @@ -461,7 +461,7 @@ FUZZDATA = \ # Extra arguments for including json1 in the build of tools # JSON1_DEP = $(TOP)/ext/misc/json1.c sqlite3ext.h -JSON1_OPT = -DSQLITE_ENABLE_JSON1 -DSQLITE_CORE +JSON1_OPT = -DSQLITE_SHELL_JSON1 -DSQLITE_CORE JSON1_SRC = $(TOP)/ext/misc/json1.c # Standard options to testfixture @@ -540,6 +540,9 @@ sqlite3.c: target_source $(TOP)/tool/mksqlite3c.tcl echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c +sqlite3ext.h: target_source + cp tsrc/sqlite3ext.h . + sqlite3.c-debug: target_source $(TOP)/tool/mksqlite3c.tcl tclsh $(TOP)/tool/mksqlite3c.tcl --linemacros echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c @@ -1,9 +1,9 @@ -C Merge\sall\sthe\slatest\strunk\senhancements\sinto\sthe\ssessions\sbranch. -D 2015-09-24T14:26:51.287 +C Merge\srecent\senhancements\sfrom\strunk,\sand\sespecially\sthe\sfix\sfor\nticket\s[1b266395d6bc10]. +D 2015-09-30T14:50:39.955 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f -F Makefile.in 5bda8cc6bbd2be407f4ef679f4f235ba47c7a3c0 +F Makefile.in fdcfdc361f0a3723da9b48b967f259f7aaff3ad5 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 -F Makefile.msc fd760727d07ba21b5f779c6a056a0c6ea7f40979 +F Makefile.msc 2f913bc2229596a31ec1025ce34aef9634164a6a F Makefile.vxworks e1b65dea203f054e71653415bd8f96dcaed47858 F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7 F VERSION ccfc4d1576dbfdeece0a4372a2e6a2e37d3e7975 @@ -78,9 +78,9 @@ F ext/fts3/README.content fdc666a70d5257a64fee209f97cf89e0e6e32b51 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d -F ext/fts3/fts3.c b04b0c57761fdba2ae562d9d9ba50c7c4a95d9ea +F ext/fts3/fts3.c e028eb13432f108d2e22cded019fc980700e4e00 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe -F ext/fts3/fts3Int.h 601743955ac43a0e82e6828a931c07bb3b0c95ff +F ext/fts3/fts3Int.h c84125c666ee54cef6efce6ff64abb0d0e2f4535 F ext/fts3/fts3_aux.c 9edc3655fcb287f0467d0a4b886a01c6185fe9f1 F ext/fts3/fts3_expr.c 71c063da9c2a4167fb54aec089dd5ef33a58c9cb F ext/fts3/fts3_hash.c 29b986e43f4e9dd40110eafa377dc0d63c422c60 @@ -96,7 +96,7 @@ F ext/fts3/fts3_tokenizer.h 64c6ef6c5272c51ebe60fc607a896e84288fcbc3 F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004 F ext/fts3/fts3_unicode.c a93f5edc0aff44ef8b06d7cb55b52026541ca145 F ext/fts3/fts3_unicode2.c c3d01968d497bd7001e7dc774ba75b372738c057 -F ext/fts3/fts3_write.c 4f005f78592a1447ca96c8475ef5342ab7dbe02a +F ext/fts3/fts3_write.c 6f7233a06df17084d5cd968899053731bf953f94 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100 F ext/fts3/tool/fts3view.c 8e53d0190a7b3443764bbd32ad47be2bd852026d @@ -112,7 +112,7 @@ F ext/fts5/fts5_buffer.c 64dcaf36a3ebda9e84b7c3b8788887ec325e12a4 F ext/fts5/fts5_config.c 57ee5fe71578cb494574fc0e6e51acb9a22a8695 F ext/fts5/fts5_expr.c 667faaf14a69a5683ac383acdc8d942cf32c3f93 F ext/fts5/fts5_hash.c 4bf4b99708848357b8a2b5819e509eb6d3df9246 -F ext/fts5/fts5_index.c 4fdbc0a321e3a1d73741a623d7aea4db78d6a86d +F ext/fts5/fts5_index.c c77882ab38d698d5147cef96fa67a2121d77c0b3 F ext/fts5/fts5_main.c 53116cffeb26898832ff7700cc5ebac5fe085d32 F ext/fts5/fts5_storage.c 120f7b143688b5b7710dacbd48cff211609b8059 F ext/fts5/fts5_tcl.c 6da58d6e8f42a93c4486b5ba9b187a7f995dee37 @@ -195,7 +195,7 @@ F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2 F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f F ext/misc/fuzzer.c 4c84635c71c26cfa7c2e5848cf49fe2d2cfcd767 F ext/misc/ieee754.c b0362167289170627659e84173f5d2e8fee8566e -F ext/misc/json1.c 557d6b2d0c3d26625e686a4b4ef8d4a50b8cec94 +F ext/misc/json1.c 263cac0292302b7cf7ecb2e8bd698a50d1aedecc F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63 F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc @@ -280,7 +280,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 284e224af00642c0e5e9454c4b07ec5c2d7860d5 +F main.mk f0a074da8e55de2646bc3aca50f13989d4558c1e F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea F mkopcodeh.awk 0e7f04a8eb90f92259e47d80110e4e98d7ce337a F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 @@ -301,30 +301,30 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240 F src/backup.c c3a9c4209439b806c44cf30daf466955727bf46c F src/bitvec.c d1f21d7d91690747881f03940584f4cc548c9d3d F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79 -F src/btree.c 164583151135a3764672c2c25aa8e4fa06bdb12b +F src/btree.c a5a653087ae98dd743d12ae0920d5b64c5335960 F src/btree.h 40189aefdc2b830d25c8b58fd7d56538481bfdd7 F src/btreeInt.h 8177c9ab90d772d6d2c6c517e05bed774b7c92c0 -F src/build.c 8a86f4203ac8a9ac0734f242a96f043edffb6018 +F src/build.c 0549b56722f15c146ca21f82a33838365c2031f0 F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0 F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f F src/ctime.c 5a0b735dc95604766f5dac73973658eef782ee8b F src/date.c fb1c99172017dcc8e237339132c91a21a0788584 F src/dbstat.c e637e7a7ff40ef32132a418c6fdf1cfb63aa27c7 -F src/delete.c d14f86935c7ff120f04532af8332c2ca21a7af0c -F src/expr.c 3a76afcdac925294c39903b7002ddb9e5fd29863 +F src/delete.c 4545c9f793f27d14a32195f6a0b121913a80f692 +F src/expr.c 36381822e617cb3586d4be1af2cbc3dd5f2b84eb F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb -F src/fkey.c 83e1baba999bed3144ea5a2143fc922edf51135f +F src/fkey.c 31900763094a3736a5fc887469202eb579fef2d0 F src/func.c ecdd69ec6a1e406f04cc73324be2ebbf6354197f F src/global.c 508e4087f7b41d688e4762dcf4d4fe28cfbc87f9 F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5 F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094 F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08 -F src/insert.c 7708c63195ba6397325fe64b8442085e73da6851 +F src/insert.c a1e265bbe11d179cb5dc61541e927c6d4f0d04bd F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e F src/lempar.c d344a95d60c24e2f490ee59db9784b1b17439012 F src/loadext.c f0b66d28e377fd6c6d36cc9d92df1ff251ebee44 -F src/main.c 9e2e596c97401a7b99f2f500f5e2c725d53eabe2 +F src/main.c 34206b735954c44defba8f324b613e5d5de89ce3 F src/malloc.c 3a37ce6979a40f499d8cea9e9ab4e8517854d35d F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c abe6ee469b6c5a35c7f22bfeb9c9bac664a1c987 @@ -336,8 +336,8 @@ F src/msvc.h d9ba56c6851227ab44b3f228a35f3f5772296495 F src/mutex.c 8e45800ee78e0cd1f1f3fe8e398853307f4a085c F src/mutex.h 779d588e3b7756ec3ecf7d78cde1d84aba414f85 F src/mutex_noop.c 9d4309c075ba9cc7249e19412d3d62f7f94839c4 -F src/mutex_unix.c 8cfa6e83c618d2fcae0fe63f4d2b5bb16b11a97a -F src/mutex_w32.c 2e025e6642eaf27597403690980f560d1a91f62c +F src/mutex_unix.c a94b46f3f7beba307dde1b298b0f99f9c3677a93 +F src/mutex_w32.c b483d3e5914b84c82516a6a9919582f12ef3b838 F src/notify.c 9711a7575036f0d3040ba61bc6e217f13a9888e7 F src/os.c 8fd25588eeba74068d41102d26810e216999b6c8 F src/os.h 3e57a24e2794a94d3cf2342c6d9a884888cd96bf @@ -352,19 +352,19 @@ F src/parse.y f599aa5e871a493330d567ced93de696f61f48f7 F src/pcache.c 24be750c79272e0ca7b6e007bc94999700f3e5ef F src/pcache.h 9968603796240cdf83da7e7bef76edf90619cea9 F src/pcache1.c e822007159d53a7ea7aa040d6e28964ddb6de083 -F src/pragma.c d71b813e67bf03f3116b9dd5164fbfd81ec673a2 +F src/pragma.c 234814978bcd35bce6e2874dfb2f5b5e28e7fb38 F src/pragma.h 631a91c8b0e6ca8f051a1d8a4a0da4150e04620a F src/prepare.c 82e5db1013846a819f198336fed72c44c974e7b1 F src/printf.c 0c4bcdd1c2e2521024f0a69cb5eb334f86b3652a F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 F src/resolve.c 1954a0f01bf65d78d7d559aea3d5c67f33376d91 F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e -F src/select.c 36ee14d729e182cd8b6796f980b7ab3fc9bcee72 -F src/shell.c 2b29a6f5c1b431eb0d25196e249d7e69b68d5ef0 -F src/sqlite.h.in 627e991195047aebc49f3c895d458f9ac2805e4a +F src/select.c e49f4af9748c9e0cc1bf864b4190aa94841c8409 +F src/shell.c f38cfe6a0b971d50158e71880852119bdca89ce9 +F src/sqlite.h.in 8f4deb5874227c7635300fb75105ff6e92131fb5 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h 64350bf36833a56ad675e27392a913f417c5c308 -F src/sqliteInt.h 211b8080f46e7cda9f6df9ab590a7aec1b51d999 +F src/sqliteInt.h a6edcbabcd78cfd888f74aaedb2160241a38294f F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46 F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179 F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e @@ -414,11 +414,11 @@ F src/test_thread.c af391ec03d23486dffbcc250b7e58e073f172af9 F src/test_vfs.c 3b65d42e18b262805716bd96178c81da8f2d9283 F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9 -F src/threads.c 6bbcc9fe50c917864d48287b4792d46d6e873481 +F src/threads.c bbfb74450643cb5372a43ad4f6cffd7e9dfcecb0 F src/tokenize.c 83c6ed569423a3af83a83973b444cf7123be33a6 F src/treeview.c 154f0acc622fa3514de8777dcedf4c8a8802b4ce F src/trigger.c 322f23aad694e8f31d384dcfa386d52a48d3c52f -F src/update.c 04a06489008ad1d6007e6de3fbe4caed7c6c620b +F src/update.c 9e102cc3d8732aeb6b977569c8ce1f73fb0031bd F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c F src/util.c fc612367108b74573c5fd13a85d0a23027f438bd F src/vacuum.c 2ddd5cad2a7b9cef7f9e431b8c7771634c6b1701 @@ -427,18 +427,18 @@ F src/vdbe.h 67151895e779b35475c6c11b16be2ceb839066c8 F src/vdbeInt.h 42fa34502937071aabd3c0596575ba9776547353 F src/vdbeapi.c f5eda36a5c85ef578957ab4311e8d9b1f51a3552 F src/vdbeaux.c 4cbd4cc79dad0e2c2b9996ae018d79a7330110f4 -F src/vdbeblob.c 53ed7f38ab93922038bcfa17aae514dc47752f1e +F src/vdbeblob.c b400c25ac822af3c507ef84b5cd93c1583a70321 F src/vdbemem.c 28ab8455ac490373798cf2c21def2c1287942551 F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 -F src/vtab.c 2ecfe020c10e0a0c7b078203fdba2fae844744bc +F src/vtab.c 9a6d8818c8a2477ce547f064701b5e955b25d894 F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb F src/wal.c 18b0ed49830cf04fe2d68224b41838a73ac6cd24 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba -F src/where.c d5eed2584542e7f3bc78ddef7809a9d76d14d811 +F src/where.c 4c4646675e794ac71e701289edefd7cd81bac844 F src/whereInt.h 7892bb54cf9ca0ae5c7e6094491b94c9286dc647 -F src/wherecode.c 7660e1ad16817a921b099af553f3e1349352d16f +F src/wherecode.c a32bf1f304f6328e3eefcb82e70bd86836cff343 F src/whereexpr.c 2473e4350e30f9b55d1c6a8f66ca23c689f23f1d F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd @@ -591,7 +591,7 @@ F test/default.test 0cb49b1c315a0d81c81d775e407f66906a2a604d F test/delete.test e1bcdf8926234e27aac24b346ad83d3329ec8b6f F test/delete2.test 3a03f2cca1f9a67ec469915cb8babd6485db43fa F test/delete3.test 555e84a00a99230b7d049d477a324a631126a6ab -F test/delete4.test d9e7d553a939597b27d205b022d769469f361c1f +F test/delete4.test 3ac0b8d23689ba764c2e8b78c1b56b8f1b942fa2 F test/descidx1.test 6d03b44c8538fe0eb4924e19fba10cdd8f3c9240 F test/descidx2.test 9f1a0c83fd57f8667c82310ca21b30a350888b5d F test/descidx3.test 09ddbe3f5295f482d2f8b687cf6db8bad7acd9a2 @@ -640,7 +640,7 @@ F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 F test/fallocate.test 3e979af17dfa7e5e9dda5eba1a696c04fa9d47f7 F test/filectrl.test 7c13f96457435238da99aff7343ad6a3a4885787 F test/filefmt.test cb34663f126cbc2d358af552dcaf5c72769b0146 -F test/fkey1.test de5b287f6a480b36bd51e8debcf48168e26e4ed2 +F test/fkey1.test 13e3d48236a2b9f5c5ebd232eef9b3ab682a8a2c F test/fkey2.test f3d27ecba480a348c328965d154214719bb158a9 F test/fkey3.test 76d475c80b84ee7a5d062e56ccb6ea68882e2b49 F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d @@ -709,7 +709,7 @@ F test/fts3aux2.test 7ae2b2c13aefdf4169279a27a5f51780ce57f6ba F test/fts3b.test e93bbb653e52afde110ad53bbd793f14fe7a8984 F test/fts3c.test fc723a9cf10b397fdfc2b32e73c53c8b1ec02958 F test/fts3comp1.test a0f5b16a2df44dd0b15751787130af2183167c0c -F test/fts3conf.test ee8500c86dd58ec075e8831a1e216a79989436de +F test/fts3conf.test ff90127b2462c788348c0dd7f6f8c573ef9cb5d9 F test/fts3corrupt.test 2710b77983cc7789295ddbffea52c1d3b7506dbb F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7 @@ -755,6 +755,7 @@ F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891 F test/fts4merge3.test aab02a09f50fe6baaddc2e159c3eabc116d45fc7 F test/fts4merge4.test d895b1057a7798b67e03455d0fa50e9ea836c47b F test/fts4noti.test 524807f0c36d49deea7920cdd4cd687408b58849 +F test/fts4onepass.test bfca61f69c6ca74cd71e6dca12a0cdd47192fc24 F test/fts4unicode.test 27378af76394542cf490cf001d8d1505fe55f6a9 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d F test/func.test ae97561957aba6ca9e3a7b8a13aac41830d701ef @@ -771,7 +772,7 @@ F test/fuzz_malloc.test 328f70aaca63adf29b4c6f06505ed0cf57ca7c26 F test/fuzzcheck.c b8eb7ee40f6d28548a0b028e0676293522f3427f F test/fuzzdata1.db 7ee3227bad0e7ccdeb08a9e6822916777073c664 F test/fuzzdata2.db f03a420d3b822cc82e4f894ca957618fbe9c4973 -F test/fuzzdata3.db 1d6044c33a114007f02b6e6846f1fa232f607bfd +F test/fuzzdata3.db c6586d3e3cef0fbc18108f9bb649aa77bfc38aba F test/fuzzdata4.db 1882f0055fb63214d8407ddc7aca9b0b1c59af21 F test/fuzzer1.test d4c52aaf3ef923da293a2653cfab33d02f718a36 F test/fuzzerfault.test 8792cd77fd5bce765b05d0c8e01b9edcf8af8536 @@ -804,7 +805,7 @@ F test/index5.test 8621491915800ec274609e42e02a97d67e9b13e7 F test/index6.test 7102ec371414c42dfb1d5ca37eb4519aa9edc23a F test/index7.test 9c6765a74fc3fcde7aebc5b3bd40d98df14a527c F test/indexedby.test 9c4cd331224e57f79fbf411ae245e6272d415985 -F test/indexexpr1.test 203c83a05accf6f2b748834192f3564321b8c0d8 +F test/indexexpr1.test bbb52b5d5717d9f23853826963b0af5110009366 F test/indexfault.test 31d4ab9a7d2f6e9616933eb079722362a883eb1d F test/init.test 15c823093fdabbf7b531fe22cf037134d09587a7 F test/insert.test 38742b5e9601c8f8d76e9b7555f7270288c2d371 @@ -1268,7 +1269,7 @@ F test/vacuum3.test 77ecdd54592b45a0bcb133339f99f1ae0ae94d0d F test/vacuum4.test d3f8ecff345f166911568f397d2432c16d2867d9 F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102 F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661 -F test/view.test 3930ae94042d702ab15a6a0ef692cfa5c9f9b68b +F test/view.test 0d8935e64867ae01492347884f5cbb47f056f4c8 F test/vtab1.test 6210e076997f176bedc300a87ad6404651b601dd F test/vtab2.test f8cd1bb9aba7143eba97812d9617880a36d247ad F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e @@ -1408,7 +1409,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P b7469c44be77358e02892a3abc696f7caa0dcd3b c6ab807b72ddfc1462f61aa91442b6fac04ace8a -R 1b640a6d419521daa5b2a632ced07132 +P c91065f8edb1e54076791716fc20d3fcfe3070dc e796c0efb6cf17444b53af75046daf7d8fa82f78 +R 0237333bf6dc63bf0c4f4c66d7918366 U drh -Z b4a9ac9211cc2ca2704bd7cc4c9b5b17 +Z 5f6cedfd7517312f688a69652091b608 diff --git a/manifest.uuid b/manifest.uuid index 5cc71bdd0..75c74a20e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c91065f8edb1e54076791716fc20d3fcfe3070dc
\ No newline at end of file +b2face9aa95ade96a5666c70b6b31064c1ad0977
\ No newline at end of file diff --git a/src/btree.c b/src/btree.c index c7d6fabba..1eae0ac5d 100644 --- a/src/btree.c +++ b/src/btree.c @@ -661,7 +661,7 @@ static int saveCursorPosition(BtCursor *pCur){ pCur->eState = CURSOR_REQUIRESEEK; } - invalidateOverflowCache(pCur); + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl|BTCF_AtLast); return rc; } @@ -7624,7 +7624,7 @@ static int balance_nonroot( ** by smaller than the child due to the database header, and so all the ** free space needs to be up front. */ - assert( nNew==1 ); + assert( nNew==1 || CORRUPT_DB ); rc = defragmentPage(apNew[0]); testcase( rc!=SQLITE_OK ); assert( apNew[0]->nFree == diff --git a/src/build.c b/src/build.c index eee62ee15..ea89e501d 100644 --- a/src/build.c +++ b/src/build.c @@ -192,6 +192,8 @@ void sqlite3FinishCoding(Parse *pParse){ db->aDb[iDb].pSchema->iGeneration /* P4 */ ); if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); + VdbeComment((v, + "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); } #ifndef SQLITE_OMIT_VIRTUALTABLE for(i=0; i<pParse->nVtabLock; i++){ @@ -2082,8 +2084,7 @@ void sqlite3CreateView( if( pParse->nVar>0 ){ sqlite3ErrorMsg(pParse, "parameters are not allowed in views"); - sqlite3SelectDelete(db, pSelect); - return; + goto create_view_fail; } sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr); p = pParse->pNewTable; @@ -3135,7 +3136,7 @@ Index *sqlite3CreateIndex( /* Analyze the list of expressions that form the terms of the index and ** report any errors. In the common case where the expression is exactly ** a table column, store that column in aiColumn[]. For general expressions, - ** populate pIndex->aColExpr and store -2 in aiColumn[]. + ** populate pIndex->aColExpr and store XN_EXPR (-2) in aiColumn[]. ** ** TODO: Issue a warning if two or more columns of the index are identical. ** TODO: Issue a warning if the table primary key is used as part of the @@ -3164,8 +3165,8 @@ Index *sqlite3CreateIndex( pListItem = &pCopy->a[i]; } } - j = -2; - pIndex->aiColumn[i] = -2; + j = XN_EXPR; + pIndex->aiColumn[i] = XN_EXPR; pIndex->uniqNotNull = 0; }else{ j = pCExpr->iColumn; @@ -3218,7 +3219,7 @@ Index *sqlite3CreateIndex( } assert( i==pIndex->nColumn ); }else{ - pIndex->aiColumn[i] = -1; + pIndex->aiColumn[i] = XN_ROWID; pIndex->azColl[i] = "BINARY"; } sqlite3DefaultRowEst(pIndex); diff --git a/src/delete.c b/src/delete.c index 914b3dfae..9abefda1a 100644 --- a/src/delete.c +++ b/src/delete.c @@ -414,7 +414,7 @@ void sqlite3DeleteFrom( pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1); if( pWInfo==0 ) goto delete_from_cleanup; eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); - assert( IsVirtual(pTab)==0 || eOnePass==ONEPASS_OFF ); + assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI ); assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF ); /* Keep track of the number of rows to be deleted */ @@ -425,7 +425,7 @@ void sqlite3DeleteFrom( /* Extract the rowid or primary key for the current row */ if( pPk ){ for(i=0; i<nPk; i++){ - assert( pPk->aiColumn[i]>=(-1) ); + assert( pPk->aiColumn[i]>=0 ); sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, pPk->aiColumn[i], iPk+i); } @@ -497,7 +497,7 @@ void sqlite3DeleteFrom( */ if( eOnePass!=ONEPASS_OFF ){ assert( nKey==nPk ); /* OP_Found will use an unpacked key */ - if( aToOpen[iDataCur-iTabCur] ){ + if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){ assert( pPk!=0 || pTab->pSelect!=0 ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey); VdbeCoverage(v); @@ -519,7 +519,11 @@ void sqlite3DeleteFrom( sqlite3VtabMakeWritable(pParse, pTab); sqlite3VdbeAddOp4(v, OP_VUpdate, 0, 1, iKey, pVTab, P4_VTAB); sqlite3VdbeChangeP5(v, OE_Abort); + assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE ); sqlite3MayAbort(pParse); + if( eOnePass==ONEPASS_SINGLE && sqlite3IsToplevel(pParse) ){ + pParse->isMultiWrite = 0; + } }else #endif { @@ -862,7 +866,7 @@ int sqlite3GenerateIndexKey( for(j=0; j<nCol; j++){ if( pPrior && pPrior->aiColumn[j]==pIdx->aiColumn[j] - && pPrior->aiColumn[j]>=(-1) + && pPrior->aiColumn[j]!=XN_EXPR ){ /* This column was already computed by the previous index */ continue; diff --git a/src/expr.c b/src/expr.c index 3141cd9db..b52e814f3 100644 --- a/src/expr.c +++ b/src/expr.c @@ -2443,15 +2443,15 @@ void sqlite3ExprCodeLoadIndexColumn( int regOut /* Store the index column value in this register */ ){ i16 iTabCol = pIdx->aiColumn[iIdxCol]; - if( iTabCol>=(-1) ){ + if( iTabCol==XN_EXPR ){ + assert( pIdx->aColExpr ); + assert( pIdx->aColExpr->nExpr>iIdxCol ); + pParse->iSelfTab = iTabCur; + sqlite3ExprCode(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut); + }else{ sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pIdx->pTable, iTabCur, iTabCol, regOut); - return; } - assert( pIdx->aColExpr ); - assert( pIdx->aColExpr->nExpr>iIdxCol ); - pParse->iSelfTab = iTabCur; - sqlite3ExprCode(pParse, pIdx->aColExpr->a[iIdxCol].pExpr, regOut); } /* diff --git a/src/fkey.c b/src/fkey.c index a087889f4..b55e2a981 100644 --- a/src/fkey.c +++ b/src/fkey.c @@ -252,6 +252,8 @@ int sqlite3FkLocateIndex( char *zDfltColl; /* Def. collation for column */ char *zIdxCol; /* Name of indexed column */ + if( iCol<0 ) break; /* No foreign keys against expression indexes */ + /* 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. */ @@ -404,6 +406,7 @@ static void fkLookupParent( for(i=0; i<nCol; i++){ int iChild = aiCol[i]+1+regData; int iParent = pIdx->aiColumn[i]+1+regData; + assert( pIdx->aiColumn[i]>=0 ); assert( aiCol[i]!=pTab->iPKey ); if( pIdx->aiColumn[i]==pTab->iPKey ){ /* The parent key is a composite key that includes the IPK column */ @@ -612,6 +615,7 @@ static void fkScanChildren( assert( pIdx!=0 ); for(i=0; i<pPk->nKeyCol; i++){ i16 iCol = pIdx->aiColumn[i]; + assert( iCol>=0 ); pLeft = exprTableRegister(pParse, pTab, regData, iCol); pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol); pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); @@ -931,6 +935,7 @@ void sqlite3FkCheck( if( aiCol[i]==pTab->iPKey ){ aiCol[i] = -1; } + assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); #ifndef SQLITE_OMIT_AUTHORIZATION /* Request permission to read the parent key columns. If the ** authorization callback returns SQLITE_IGNORE, behave as if any @@ -1062,7 +1067,10 @@ u32 sqlite3FkOldmask( Index *pIdx = 0; sqlite3FkLocateIndex(pParse, pTab, p, &pIdx, 0); if( pIdx ){ - for(i=0; i<pIdx->nKeyCol; i++) mask |= COLUMN_MASK(pIdx->aiColumn[i]); + for(i=0; i<pIdx->nKeyCol; i++){ + assert( pIdx->aiColumn[i]>=0 ); + mask |= COLUMN_MASK(pIdx->aiColumn[i]); + } } } } @@ -1185,6 +1193,7 @@ static Trigger *fkActionTrigger( iFromCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; assert( iFromCol>=0 ); assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKey<pTab->nCol) ); + assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); tToCol.z = pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName; tFromCol.z = pFKey->pFrom->aCol[iFromCol].zName; diff --git a/src/insert.c b/src/insert.c index 746f58db3..4108c8879 100644 --- a/src/insert.c +++ b/src/insert.c @@ -90,11 +90,11 @@ const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ i16 x = pIdx->aiColumn[n]; if( x>=0 ){ pIdx->zColAff[n] = pTab->aCol[x].affinity; - }else if( x==(-1) ){ + }else if( x==XN_ROWID ){ pIdx->zColAff[n] = SQLITE_AFF_INTEGER; }else{ char aff; - assert( x==(-2) ); + assert( x==XN_EXPR ); assert( pIdx->aColExpr!=0 ); aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr); if( aff==0 ) aff = SQLITE_AFF_BLOB; @@ -260,7 +260,7 @@ void sqlite3AutoincrementBegin(Parse *pParse){ /* This routine is never called during trigger-generation. It is ** only called from the top-level */ assert( pParse->pTriggerTab==0 ); - assert( pParse==sqlite3ParseToplevel(pParse) ); + assert( sqlite3IsToplevel(pParse) ); assert( v ); /* We failed long ago if this is not so */ for(p = pParse->pAinc; p; p = p->pNext){ @@ -1417,13 +1417,13 @@ void sqlite3GenerateConstraintChecks( for(i=0; i<pIdx->nColumn; i++){ int iField = pIdx->aiColumn[i]; int x; - if( iField==(-2) ){ + if( iField==XN_EXPR ){ pParse->ckBase = regNewData+1; sqlite3ExprCode(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i); pParse->ckBase = 0; VdbeComment((v, "%s column %d", pIdx->zName, i)); }else{ - if( iField==(-1) || iField==pTab->iPKey ){ + if( iField==XN_ROWID || iField==pTab->iPKey ){ if( regRowid==regIdx+i ) continue; /* ROWID already in regIdx+i */ x = regNewData; regRowid = pIdx->pPartIdxWhere ? -1 : regIdx+i; @@ -1482,6 +1482,7 @@ void sqlite3GenerateConstraintChecks( ** store it in registers regR..regR+nPk-1 */ if( pIdx!=pPk ){ for(i=0; i<pPk->nKeyCol; i++){ + assert( pPk->aiColumn[i]>=0 ); x = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]); sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i); VdbeComment((v, "%s.%s", pTab->zName, @@ -1503,6 +1504,7 @@ void sqlite3GenerateConstraintChecks( for(i=0; i<pPk->nKeyCol; i++){ char *p4 = (char*)sqlite3LocateCollSeq(pParse, pPk->azColl[i]); x = pPk->aiColumn[i]; + assert( x>=0 ); if( i==(pPk->nKeyCol-1) ){ addrJump = addrUniqueOk; op = OP_Eq; @@ -1754,7 +1756,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ if( pSrc->aiColumn[i]!=pDest->aiColumn[i] ){ return 0; /* Different columns indexed */ } - if( pSrc->aiColumn[i]==(-2) ){ + if( pSrc->aiColumn[i]==XN_EXPR ){ assert( pSrc->aColExpr!=0 && pDest->aColExpr!=0 ); if( sqlite3ExprCompare(pSrc->aColExpr->a[i].pExpr, pDest->aColExpr->a[i].pExpr, -1)!=0 ){ diff --git a/src/main.c b/src/main.c index fbb7da437..2aca573b0 100644 --- a/src/main.c +++ b/src/main.c @@ -2917,6 +2917,13 @@ static int openDatabase( } #endif +#ifdef SQLITE_ENABLE_JSON1 + if( !db->mallocFailed && rc==SQLITE_OK){ + extern int sqlite3Json1Init(sqlite3*); + rc = sqlite3Json1Init(db); + } +#endif + /* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking ** mode. -DSQLITE_DEFAULT_LOCKING_MODE=0 make NORMAL the default locking ** mode. Doing nothing at all also makes NORMAL the default. diff --git a/src/mutex_unix.c b/src/mutex_unix.c index cebb96c90..dbdaa225b 100644 --- a/src/mutex_unix.c +++ b/src/mutex_unix.c @@ -81,7 +81,9 @@ static int pthreadMutexNotheld(sqlite3_mutex *p){ #endif /* -** Try to provide a memory barrier operation, needed for initialization only. +** Try to provide a memory barrier operation, needed for initialization +** and also for the implementation of xShmBarrier in the VFS in cases +** where SQLite is compiled without mutexes. */ void sqlite3MemoryBarrier(void){ #if defined(SQLITE_MEMORY_BARRIER) diff --git a/src/mutex_w32.c b/src/mutex_w32.c index 90be07db2..7d28e4def 100644 --- a/src/mutex_w32.c +++ b/src/mutex_w32.c @@ -78,7 +78,9 @@ static int winMutexNotheld(sqlite3_mutex *p){ #endif /* -** Try to provide a memory barrier operation, needed for initialization only. +** Try to provide a memory barrier operation, needed for initialization +** and also for the xShmBarrier method of the VFS in cases when SQLite is +** compiled without mutexes (SQLITE_THREADSAFE=0). */ void sqlite3MemoryBarrier(void){ #if defined(SQLITE_MEMORY_BARRIER) diff --git a/src/pragma.c b/src/pragma.c index 2dcad614f..e5e7e54a2 100644 --- a/src/pragma.c +++ b/src/pragma.c @@ -1524,8 +1524,8 @@ void sqlite3Pragma( int kk; for(kk=0; kk<pIdx->nKeyCol; kk++){ int iCol = pIdx->aiColumn[kk]; - assert( iCol>=0 && iCol<pTab->nCol ); - if( pTab->aCol[iCol].notNull ) continue; + assert( iCol!=XN_ROWID && iCol<pTab->nCol ); + if( iCol>=0 && pTab->aCol[iCol].notNull ) continue; sqlite3VdbeAddOp2(v, OP_IsNull, r1+kk, uniqOk); VdbeCoverage(v); } diff --git a/src/select.c b/src/select.c index a4aaa0eca..8430a00f3 100644 --- a/src/select.c +++ b/src/select.c @@ -4221,17 +4221,9 @@ static int selectExpander(Walker *pWalker, Select *p){ */ for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ Table *pTab; - assert( pFrom->fg.isRecursive==0 || pFrom->pTab ); + assert( pFrom->fg.isRecursive==0 || pFrom->pTab!=0 ); if( pFrom->fg.isRecursive ) continue; - if( pFrom->pTab!=0 ){ - /* This statement has already been prepared. There is no need - ** to go further. */ - assert( i==0 ); -#ifndef SQLITE_OMIT_CTE - selectPopWith(pWalker, p); -#endif - return WRC_Prune; - } + assert( pFrom->pTab==0 ); #ifndef SQLITE_OMIT_CTE if( withExpand(pWalker, pFrom) ) return WRC_Abort; if( pFrom->pTab ) {} else @@ -4523,19 +4515,19 @@ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ struct SrcList_item *pFrom; assert( p->selFlags & SF_Resolved ); - if( (p->selFlags & SF_HasTypeInfo)==0 ){ - p->selFlags |= SF_HasTypeInfo; - pParse = pWalker->pParse; - pTabList = p->pSrc; - for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ - Table *pTab = pFrom->pTab; - if( ALWAYS(pTab!=0) && (pTab->tabFlags & TF_Ephemeral)!=0 ){ - /* A sub-query in the FROM clause of a SELECT */ - Select *pSel = pFrom->pSelect; - if( pSel ){ - while( pSel->pPrior ) pSel = pSel->pPrior; - selectAddColumnTypeAndCollation(pParse, pTab, pSel); - } + assert( (p->selFlags & SF_HasTypeInfo)==0 ); + p->selFlags |= SF_HasTypeInfo; + pParse = pWalker->pParse; + pTabList = p->pSrc; + for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){ + Table *pTab = pFrom->pTab; + assert( pTab!=0 ); + if( (pTab->tabFlags & TF_Ephemeral)!=0 ){ + /* A sub-query in the FROM clause of a SELECT */ + Select *pSel = pFrom->pSelect; + if( pSel ){ + while( pSel->pPrior ) pSel = pSel->pPrior; + selectAddColumnTypeAndCollation(pParse, pTab, pSel); } } } @@ -4861,7 +4853,17 @@ int sqlite3Select( struct SrcList_item *pItem = &pTabList->a[i]; Select *pSub = pItem->pSelect; int isAggSub; + Table *pTab = pItem->pTab; if( pSub==0 ) continue; + + /* Catch mismatch in the declared columns of a view and the number of + ** columns in the SELECT on the RHS */ + if( pTab->nCol!=pSub->pEList->nExpr ){ + sqlite3ErrorMsg(pParse, "expected %d columns for '%s' but got %d", + pTab->nCol, pTab->zName, pSub->pEList->nExpr); + goto select_end; + } + isAggSub = (pSub->selFlags & SF_Aggregate)!=0; if( flattenSubquery(pParse, p, i, isAgg, isAggSub) ){ /* This subquery can be absorbed into its parent. */ diff --git a/src/shell.c b/src/shell.c index 277473099..ca8e37186 100644 --- a/src/shell.c +++ b/src/shell.c @@ -2701,6 +2701,22 @@ static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){ return 0; } +/* +** Print the current sqlite3_errmsg() value to stderr and return 1. +*/ +static int shellDatabaseError(sqlite3 *db){ + const char *zErr = sqlite3_errmsg(db); + fprintf(stderr, "Error: %s\n", zErr); + return 1; +} + +/* +** Print an out-of-memory message to stderr and return 1. +*/ +static int shellNomemError(void){ + fprintf(stderr, "Error: out of memory\n"); + return 1; +} /* ** If an input line begins with "." then invoke this routine to @@ -3994,13 +4010,17 @@ static int do_meta_command(char *zLine, ShellState *p){ int ii; open_db(p, 0); rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); - if( rc ) return rc; + if( rc ) return shellDatabaseError(p->db); + + /* Create an SQL statement to query for the list of tables in the + ** main and all attached databases where the table name matches the + ** LIKE pattern bound to variable "?1". */ zSql = sqlite3_mprintf( "SELECT name FROM sqlite_master" " WHERE type IN ('table','view')" " AND name NOT LIKE 'sqlite_%%'" " AND name LIKE ?1"); - while( sqlite3_step(pStmt)==SQLITE_ROW ){ + while( zSql && sqlite3_step(pStmt)==SQLITE_ROW ){ const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1); if( zDbName==0 || strcmp(zDbName,"main")==0 ) continue; if( strcmp(zDbName,"temp")==0 ){ @@ -4019,11 +4039,17 @@ static int do_meta_command(char *zLine, ShellState *p){ " AND name LIKE ?1", zSql, zDbName, zDbName); } } - sqlite3_finalize(pStmt); - zSql = sqlite3_mprintf("%z ORDER BY 1", zSql); - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3_finalize(pStmt); + if( zSql && rc==SQLITE_OK ){ + zSql = sqlite3_mprintf("%z ORDER BY 1", zSql); + if( zSql ) rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + } sqlite3_free(zSql); - if( rc ) return rc; + if( !zSql ) return shellNomemError(); + if( rc ) return shellDatabaseError(p->db); + + /* Run the SQL statement prepared by the above block. Store the results + ** as an array of nul-terminated strings in azResult[]. */ nRow = nAlloc = 0; azResult = 0; if( nArg>1 ){ @@ -4037,17 +4063,25 @@ static int do_meta_command(char *zLine, ShellState *p){ int n2 = nAlloc*2 + 10; azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2); if( azNew==0 ){ - fprintf(stderr, "Error: out of memory\n"); + rc = shellNomemError(); break; } nAlloc = n2; azResult = azNew; } azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0)); - if( azResult[nRow] ) nRow++; + if( 0==azResult[nRow] ){ + rc = shellNomemError(); + break; + } + nRow++; + } + if( sqlite3_finalize(pStmt)!=SQLITE_OK ){ + rc = shellDatabaseError(p->db); } - sqlite3_finalize(pStmt); - if( nRow>0 ){ + + /* Pretty-print the contents of array azResult[] to the output */ + if( rc==0 && nRow>0 ){ int len, maxlen = 0; int i, j; int nPrintCol, nPrintRow; @@ -4066,6 +4100,7 @@ static int do_meta_command(char *zLine, ShellState *p){ fprintf(p->out, "\n"); } } + for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]); sqlite3_free(azResult); }else @@ -4900,7 +4935,7 @@ int SQLITE_CDECL main(int argc, char **argv){ } data.out = stdout; -#ifdef SQLITE_ENABLE_JSON1 +#ifdef SQLITE_SHELL_JSON1 { extern int sqlite3_json_init(sqlite3*); sqlite3_auto_extension((void(*)(void))sqlite3_json_init); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 471b7f526..8934d729e 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -4360,7 +4360,7 @@ int sqlite3_value_type(sqlite3_value*); int sqlite3_value_numeric_type(sqlite3_value*); /* -** CAPI3REF: Obtaining SQL Values +** CAPI3REF: Finding The Subtype Of SQL Values ** METHOD: sqlite3_value ** ** The sqlite3_value_subtype(V) function returns the subtype for @@ -5634,13 +5634,31 @@ struct sqlite3_module { ** ^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 +** 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. +** +** 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 +** part of the same statement to delete or update a virtual table row and the +** implementation returns SQLITE_CONSTRAINT, then there is no need to rollback +** any database changes. In other words, if the xUpdate() returns +** SQLITE_CONSTRAINT, the database contents must be exactly as they were +** before xUpdate was called. By contrast, if SQLITE_INDEX_SCAN_UNIQUE is not +** set and xUpdate returns SQLITE_CONSTRAINT, any database changes made by +** 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. If a virtual table extension is ** used with an SQLite version earlier than 3.8.2, the results of attempting ** to read or write the estimatedRows field are undefined (but are likely ** to included crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a -** value greater than or equal to 3008002. +** value greater than or equal to 3008002. Similarly, the idxFlags field +** was added for version 3.8.12. It may therefore only be used if +** sqlite3_libversion_number() returns a value greater than or equal to +** 3008012. */ struct sqlite3_index_info { /* Inputs */ @@ -5668,9 +5686,16 @@ struct sqlite3_index_info { double estimatedCost; /* Estimated cost of using this index */ /* Fields below are only available in SQLite 3.8.2 and later */ sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ + /* Fields below are only available in SQLite 3.8.12 and later */ + int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ }; /* +** CAPI3REF: Virtual Table Scan Flags +*/ +#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ + +/* ** CAPI3REF: Virtual Table Constraint Operator Codes ** ** These macros defined the allowed values for the diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 6b999374b..450eb7cc5 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -1925,6 +1925,12 @@ struct Index { /* Return true if index X is a UNIQUE index */ #define IsUniqueIndex(X) ((X)->onError!=OE_None) +/* The Index.aiColumn[] values are normally positive integer. But +** there are some negative values that have special meaning: +*/ +#define XN_ROWID (-1) /* Indexed column is the rowid */ +#define XN_EXPR (-2) /* Indexed column is an expression */ + /* ** Each sample stored in the sqlite_stat3 table is represented in memory ** using a structure of this type. See documentation at the top of the @@ -3516,6 +3522,7 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, int); void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int); # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p)) +# define sqlite3IsToplevel(p) ((p)->pToplevel==0) #else # define sqlite3TriggersExist(B,C,D,E,F) 0 # define sqlite3DeleteTrigger(A,B) @@ -3525,6 +3532,7 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, int); # define sqlite3CodeRowTriggerDirect(A,B,C,D,E,F) # define sqlite3TriggerList(X, Y) 0 # define sqlite3ParseToplevel(p) p +# define sqlite3IsToplevel(p) 1 # define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0 #endif diff --git a/src/threads.c b/src/threads.c index 4ce612227..251b9b763 100644 --- a/src/threads.c +++ b/src/threads.c @@ -67,6 +67,10 @@ int sqlite3ThreadCreate( memset(p, 0, sizeof(*p)); p->xTask = xTask; p->pIn = pIn; + /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a + ** function that returns SQLITE_ERROR when passed the argument 200, that + ** forces worker threads to run sequentially and deterministically + ** for testing purposes. */ if( sqlite3FaultSim(200) ){ rc = 1; }else{ @@ -151,7 +155,12 @@ int sqlite3ThreadCreate( *ppThread = 0; p = sqlite3Malloc(sizeof(*p)); if( p==0 ) return SQLITE_NOMEM; - if( sqlite3GlobalConfig.bCoreMutex==0 ){ + /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a + ** function that returns SQLITE_ERROR when passed the argument 200, that + ** forces worker threads to run sequentially and deterministically + ** (via the sqlite3FaultSim() term of the conditional) for testing + ** purposes. */ + if( sqlite3GlobalConfig.bCoreMutex==0 || sqlite3FaultSim(200) ){ memset(p, 0, sizeof(*p)); }else{ p->xTask = xTask; @@ -179,7 +188,7 @@ int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ assert( ppOut!=0 ); if( NEVER(p==0) ) return SQLITE_NOMEM; if( p->xTask==0 ){ - assert( p->id==GetCurrentThreadId() ); + /* assert( p->id==GetCurrentThreadId() ); */ rc = WAIT_OBJECT_0; assert( p->tid==0 ); }else{ diff --git a/src/update.c b/src/update.c index aa13e7f7d..fba7762aa 100644 --- a/src/update.c +++ b/src/update.c @@ -134,9 +134,9 @@ void sqlite3Update( /* Register Allocations */ int regRowCount = 0; /* A count of rows changed */ - int regOldRowid; /* The old rowid */ - int regNewRowid; /* The new rowid */ - int regNew; /* Content of the NEW.* table in triggers */ + int regOldRowid = 0; /* The old rowid */ + int regNewRowid = 0; /* The new rowid */ + int regNew = 0; /* Content of the NEW.* table in triggers */ int regOld = 0; /* Content of OLD.* table in triggers */ int regRowSet = 0; /* Rowset of rows to be updated */ int regKey = 0; /* composite PRIMARY KEY value */ @@ -300,29 +300,20 @@ void sqlite3Update( if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, 1, iDb); -#ifndef SQLITE_OMIT_VIRTUALTABLE - /* Virtual tables must be handled separately */ - if( IsVirtual(pTab) ){ - updateVirtualTable(pParse, pTabList, pTab, pChanges, pRowidExpr, aXRef, - pWhere, onError); - pWhere = 0; - pTabList = 0; - goto update_cleanup; - } -#endif - /* Allocate required registers. */ - regRowSet = ++pParse->nMem; - regOldRowid = regNewRowid = ++pParse->nMem; - if( chngPk || pTrigger || hasFK ){ - regOld = pParse->nMem + 1; + if( !IsVirtual(pTab) ){ + regRowSet = ++pParse->nMem; + regOldRowid = regNewRowid = ++pParse->nMem; + if( chngPk || pTrigger || hasFK ){ + regOld = pParse->nMem + 1; + pParse->nMem += pTab->nCol; + } + if( chngKey || pTrigger || hasFK ){ + regNewRowid = ++pParse->nMem; + } + regNew = pParse->nMem + 1; pParse->nMem += pTab->nCol; } - if( chngKey || pTrigger || hasFK ){ - regNewRowid = ++pParse->nMem; - } - regNew = pParse->nMem + 1; - pParse->nMem += pTab->nCol; /* Start the view context. */ if( isView ){ @@ -345,6 +336,15 @@ void sqlite3Update( goto update_cleanup; } +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Virtual tables must be handled separately */ + if( IsVirtual(pTab) ){ + updateVirtualTable(pParse, pTabList, pTab, pChanges, pRowidExpr, aXRef, + pWhere, onError); + goto update_cleanup; + } +#endif + /* Begin the database scan */ if( HasRowid(pTab) ){ @@ -384,7 +384,7 @@ void sqlite3Update( if( pWInfo==0 ) goto update_cleanup; okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); for(i=0; i<nPk; i++){ - assert( pPk->aiColumn[i]>=(-1) ); + assert( pPk->aiColumn[i]>=0 ); sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pPk->aiColumn[i], iPk+i); } @@ -507,7 +507,6 @@ void sqlite3Update( newmask = sqlite3TriggerColmask( pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError ); - /*sqlite3VdbeAddOp3(v, OP_Null, 0, regNew, regNew+pTab->nCol-1);*/ for(i=0; i<pTab->nCol; i++){ if( i==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); @@ -698,21 +697,23 @@ update_cleanup: /* ** Generate code for an UPDATE of a virtual table. ** -** The strategy is that we create an ephemeral table that contains +** 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 ** for each row to be changed: ** ** (A) The original rowid of that row. -** (B) The revised rowid for the row. (note1) +** (B) The revised rowid for the row. ** (C) The content of every column in the row. ** -** Then we loop over this ephemeral table and for each row in -** the ephemeral table call VUpdate. -** -** When finished, drop the ephemeral table. +** Then loop through the contents of this ephemeral table executing a +** VUpdate for each row. When finished, drop the ephemeral table. ** -** (note1) Actually, if we know in advance that (A) is always the same -** as (B) we only store (A), then duplicate (A) when pulling -** it out of the ephemeral table before calling VUpdate. +** The "onepass" strategy does not use an ephemeral table. Instead, it +** stores the same values (A, B and C above) in a register array and +** makes a single invocation of VUpdate. */ static void updateVirtualTable( Parse *pParse, /* The parsing context */ @@ -725,65 +726,95 @@ static void updateVirtualTable( int onError /* ON CONFLICT strategy */ ){ Vdbe *v = pParse->pVdbe; /* Virtual machine under construction */ - ExprList *pEList = 0; /* The result set of the SELECT statement */ - Select *pSelect = 0; /* The SELECT statement */ - Expr *pExpr; /* Temporary expression */ int ephemTab; /* Table holding the result of the SELECT */ int i; /* Loop counter */ - int addr; /* Address of top of loop */ - int iReg; /* First register in set passed to OP_VUpdate */ sqlite3 *db = pParse->db; /* Database connection */ const char *pVTab = (const char*)sqlite3GetVTable(db, pTab); - SelectDest dest; - - /* Construct the SELECT statement that will find the new values for - ** all updated rows. - */ - pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, "_rowid_")); + WhereInfo *pWInfo; + 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 iCsr = pSrc->a[0].iCursor; /* Cursor used for virtual table scan */ + int aDummy[2]; /* Unused arg for sqlite3WhereOkOnePass() */ + int bOnePass; /* True to use onepass strategy */ + int addr; /* Address of OP_OpenEphemeral */ + + /* Allocate nArg registers to martial the arguments to VUpdate. Then + ** create and open the ephemeral table in which the records created from + ** these arguments will be temporarily stored. */ + assert( v ); + ephemTab = pParse->nTab++; + addr= sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg); + regArg = pParse->nMem + 1; + pParse->nMem += nArg; + regRec = ++pParse->nMem; + regRowid = ++pParse->nMem; + + /* Start scanning the virtual table */ + pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0); + if( pWInfo==0 ) return; + + /* Populate the argument registers. */ + sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg); if( pRowid ){ - pEList = sqlite3ExprListAppend(pParse, pEList, - sqlite3ExprDup(db, pRowid, 0)); + sqlite3ExprCode(pParse, pRowid, regArg+1); + }else{ + sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1); } - assert( pTab->iPKey<0 ); for(i=0; i<pTab->nCol; i++){ if( aXRef[i]>=0 ){ - pExpr = sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0); + sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i); }else{ - pExpr = sqlite3Expr(db, TK_ID, pTab->aCol[i].zName); + sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i); } - pEList = sqlite3ExprListAppend(pParse, pEList, pExpr); } - pSelect = sqlite3SelectNew(pParse, pEList, pSrc, pWhere, 0, 0, 0, 0, 0, 0); - - /* Create the ephemeral table into which the update results will - ** be stored. - */ - assert( v ); - ephemTab = pParse->nTab++; - /* fill the ephemeral table - */ - sqlite3SelectDestInit(&dest, SRT_EphemTab, ephemTab); - sqlite3Select(pParse, pSelect, &dest); - - /* Generate code to scan the ephemeral table and call VUpdate. */ - iReg = ++pParse->nMem; - pParse->nMem += pTab->nCol+1; - addr = sqlite3VdbeAddOp2(v, OP_Rewind, ephemTab, 0); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_Column, ephemTab, 0, iReg); - sqlite3VdbeAddOp3(v, OP_Column, ephemTab, (pRowid?1:0), iReg+1); - for(i=0; i<pTab->nCol; i++){ - sqlite3VdbeAddOp3(v, OP_Column, ephemTab, i+1+(pRowid!=0), iReg+2+i); + bOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy); + + if( bOnePass ){ + /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded + ** above. Also, if this is a top-level parse (not a trigger), clear the + ** multi-write flag so that the VM does not open a statement journal */ + sqlite3VdbeChangeToNoop(v, addr); + if( sqlite3IsToplevel(pParse) ){ + pParse->isMultiWrite = 0; + } + }else{ + /* Create a record from the argument register contents and insert it into + ** the ephemeral table. */ + sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec); + sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid); + } + + + if( bOnePass==0 ){ + /* End the virtual table scan */ + sqlite3WhereEnd(pWInfo); + + /* Begin scannning through the ephemeral table. */ + addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v); + + /* Extract arguments from the current row of the ephemeral table and + ** invoke the VUpdate method. */ + for(i=0; i<nArg; i++){ + sqlite3VdbeAddOp3(v, OP_Column, ephemTab, i, regArg+i); + } } sqlite3VtabMakeWritable(pParse, pTab); - sqlite3VdbeAddOp4(v, OP_VUpdate, 0, pTab->nCol+2, iReg, pVTab, P4_VTAB); + sqlite3VdbeAddOp4(v, OP_VUpdate, 0, nArg, regArg, pVTab, P4_VTAB); sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError); sqlite3MayAbort(pParse); - sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1); VdbeCoverage(v); - sqlite3VdbeJumpHere(v, addr); - sqlite3VdbeAddOp2(v, OP_Close, ephemTab, 0); - /* Cleanup */ - sqlite3SelectDelete(db, pSelect); + /* End of the ephemeral table scan. Or, if using the onepass strategy, + ** jump to here if the scan visited zero rows. */ + if( bOnePass==0 ){ + sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeAddOp2(v, OP_Close, ephemTab, 0); + }else{ + sqlite3WhereEnd(pWInfo); + } } #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/src/vdbeblob.c b/src/vdbeblob.c index ae40a9c09..80608ecec 100644 --- a/src/vdbeblob.c +++ b/src/vdbeblob.c @@ -252,7 +252,7 @@ int sqlite3_blob_open( int j; for(j=0; j<pIdx->nKeyCol; j++){ /* FIXME: Be smarter about indexes that use expressions */ - if( pIdx->aiColumn[j]==iCol || pIdx->aiColumn[j]==(-2) ){ + if( pIdx->aiColumn[j]==iCol || pIdx->aiColumn[j]==XN_EXPR ){ zFault = "indexed"; } } diff --git a/src/vtab.c b/src/vtab.c index 041805ca4..cc293e806 100644 --- a/src/vtab.c +++ b/src/vtab.c @@ -937,7 +937,9 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ if( rc==SQLITE_OK ){ rc = pModule->xBegin(pVTab->pVtab); if( rc==SQLITE_OK ){ + int iSvpt = db->nStatement + db->nSavepoint; addToVTrans(db, pVTab); + if( iSvpt ) rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, iSvpt-1); } } } diff --git a/src/where.c b/src/where.c index 1e13cf0ed..af8e2f35f 100644 --- a/src/where.c +++ b/src/where.c @@ -87,6 +87,13 @@ int sqlite3WhereBreakLabel(WhereInfo *pWInfo){ */ int sqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){ memcpy(aiCur, pWInfo->aiCurOnePass, sizeof(int)*2); +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace && pWInfo->eOnePass!=ONEPASS_OFF ){ + sqlite3DebugPrintf("%s cursors: %d %d\n", + pWInfo->eOnePass==ONEPASS_SINGLE ? "ONEPASS_SINGLE" : "ONEPASS_MULTI", + aiCur[0], aiCur[1]); + } +#endif return pWInfo->eOnePass; } @@ -182,13 +189,13 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ while( pScan->iEquiv<=pScan->nEquiv ){ iCur = pScan->aiCur[pScan->iEquiv-1]; iColumn = pScan->aiColumn[pScan->iEquiv-1]; - if( iColumn==(-2) && pScan->pIdxExpr==0 ) return 0; + if( iColumn==XN_EXPR && pScan->pIdxExpr==0 ) return 0; while( (pWC = pScan->pWC)!=0 ){ for(pTerm=pWC->a+k; k<pWC->nTerm; k++, pTerm++){ if( pTerm->leftCursor==iCur && pTerm->u.leftColumn==iColumn - && (iColumn!=(-2) - || sqlite3ExprCompare(pTerm->pExpr->pLeft,pScan->pIdxExpr,iCur)==0) + && (iColumn!=XN_EXPR + || sqlite3ExprCompare(pTerm->pExpr->pLeft,pScan->pIdxExpr,iCur)==0) && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin)) ){ if( (pTerm->eOperator & WO_EQUIV)!=0 @@ -281,7 +288,7 @@ static WhereTerm *whereScanInit( if( pIdx ){ j = iColumn; iColumn = pIdx->aiColumn[j]; - if( iColumn==(-2) ) pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; + if( iColumn==XN_EXPR ) pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; } if( pIdx && iColumn>=0 ){ pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; @@ -720,7 +727,7 @@ static void constructAutomaticIndex( } } assert( n==nKeyCol ); - pIdx->aiColumn[n] = -1; + pIdx->aiColumn[n] = XN_ROWID; pIdx->azColl[n] = "BINARY"; /* Create the automatic index */ @@ -1159,6 +1166,7 @@ static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){ ** Return the affinity for a single column of an index. */ static char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ + assert( iCol>=0 && iCol<pIdx->nColumn ); if( !pIdx->zColAff ){ if( sqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB; } @@ -1216,8 +1224,7 @@ static int whereRangeSkipScanEst( int nLower = -1; int nUpper = p->nSample+1; int rc = SQLITE_OK; - int iCol = p->aiColumn[nEq]; - u8 aff = sqlite3IndexColumnAffinity(db, p, iCol); + u8 aff = sqlite3IndexColumnAffinity(db, p, nEq); CollSeq *pColl; sqlite3_value *p1 = 0; /* Value extracted from pLower */ @@ -2235,7 +2242,9 @@ static int whereLoopAddBtreeIndex( int iCol = pProbe->aiColumn[saved_nEq]; pNew->wsFlags |= WHERE_COLUMN_EQ; assert( saved_nEq==pNew->u.btree.nEq ); - if( iCol==(-1) || (iCol>0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) ){ + if( iCol==XN_ROWID + || (iCol>0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) + ){ if( iCol>=0 && pProbe->uniqNotNull==0 ){ pNew->wsFlags |= WHERE_UNQ_WANTED; }else{ @@ -2421,18 +2430,25 @@ static int indexMightHelpWithOrderBy( int iCursor ){ ExprList *pOB; + ExprList *aColExpr; int ii, jj; if( pIndex->bUnordered ) return 0; if( (pOB = pBuilder->pWInfo->pOrderBy)==0 ) return 0; for(ii=0; ii<pOB->nExpr; ii++){ Expr *pExpr = sqlite3ExprSkipCollate(pOB->a[ii].pExpr); - if( pExpr->op!=TK_COLUMN ) return 0; - if( pExpr->iTable==iCursor ){ + if( pExpr->op==TK_COLUMN && pExpr->iTable==iCursor ){ if( pExpr->iColumn<0 ) return 1; for(jj=0; jj<pIndex->nKeyCol; jj++){ if( pExpr->iColumn==pIndex->aiColumn[jj] ) return 1; } + }else if( (aColExpr = pIndex->aColExpr)!=0 ){ + for(jj=0; jj<pIndex->nKeyCol; jj++){ + if( pIndex->aiColumn[jj]!=XN_EXPR ) continue; + if( sqlite3ExprCompare(pExpr,aColExpr->a[jj].pExpr,iCursor)==0 ){ + return 1; + } + } } } return 0; @@ -2580,7 +2596,7 @@ static int whereLoopAddBtree( && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 && pSrc->pIBIndex==0 /* Has no INDEXED BY clause */ && !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */ - && HasRowid(pTab) /* Is not a WITHOUT ROWID table. (FIXME: Why not?) */ + && 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. */ ){ @@ -2825,6 +2841,7 @@ static int whereLoopAddVirtual( pIdxInfo->orderByConsumed = 0; pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2; pIdxInfo->estimatedRows = 25; + pIdxInfo->idxFlags = 0; rc = vtabBestIndex(pParse, pTab, pIdxInfo); if( rc ) goto whereLoopAddVtab_exit; pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; @@ -2870,6 +2887,7 @@ static int whereLoopAddVirtual( ** (2) Multiple outputs from a single IN value will not merge ** together. */ pIdxInfo->orderByConsumed = 0; + pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; } } } @@ -2885,6 +2903,14 @@ static int whereLoopAddVirtual( pNew->rSetup = 0; pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost); pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows); + + /* Set the WHERE_ONEROW flag if the xBestIndex() method indicated + ** that the scan will visit at most one row. Clear it otherwise. */ + if( pIdxInfo->idxFlags & SQLITE_INDEX_SCAN_UNIQUE ){ + pNew->wsFlags |= WHERE_ONEROW; + }else{ + pNew->wsFlags &= ~WHERE_ONEROW; + } whereLoopInsert(pBuilder, pNew); if( pNew->u.vtab.needFree ){ sqlite3_free(pNew->u.vtab.idxStr); @@ -3206,7 +3232,8 @@ static i8 wherePathSatisfiesOrderBy( nKeyCol = pIndex->nKeyCol; nColumn = pIndex->nColumn; assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) ); - assert( pIndex->aiColumn[nColumn-1]==(-1) || !HasRowid(pIndex->pTable)); + assert( pIndex->aiColumn[nColumn-1]==XN_ROWID + || !HasRowid(pIndex->pTable)); isOrderDistinct = IsUniqueIndex(pIndex); } @@ -3238,7 +3265,7 @@ static i8 wherePathSatisfiesOrderBy( revIdx = pIndex->aSortOrder[j]; if( iColumn==pIndex->pTable->iPKey ) iColumn = -1; }else{ - iColumn = -1; + iColumn = XN_ROWID; revIdx = 0; } @@ -3264,9 +3291,15 @@ static i8 wherePathSatisfiesOrderBy( testcase( wctrlFlags & WHERE_GROUPBY ); testcase( wctrlFlags & WHERE_DISTINCTBY ); if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0; - if( pOBExpr->op!=TK_COLUMN ) continue; - if( pOBExpr->iTable!=iCur ) continue; - if( pOBExpr->iColumn!=iColumn ) continue; + if( iColumn>=(-1) ){ + if( pOBExpr->op!=TK_COLUMN ) continue; + if( pOBExpr->iTable!=iCur ) continue; + if( pOBExpr->iColumn!=iColumn ) continue; + }else{ + if( sqlite3ExprCompare(pOBExpr,pIndex->aColExpr->a[j].pExpr,iCur) ){ + continue; + } + } if( iColumn>=0 ){ pColl = sqlite3ExprCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr); if( !pColl ) pColl = db->pDfltColl; @@ -4097,7 +4130,8 @@ WhereInfo *sqlite3WhereBegin( } /* Construct the WhereLoop objects */ - WHERETRACE(0xffff,("*** Optimizer Start ***\n")); + WHERETRACE(0xffff,("*** Optimizer Start *** (wctrlFlags: 0x%x)\n", + wctrlFlags)); #if defined(WHERETRACE_ENABLED) if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */ int i; @@ -4516,7 +4550,10 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ }else if( pLoop->wsFlags & WHERE_MULTI_OR ){ pIdx = pLevel->u.pCovidx; } - if( pIdx && !db->mallocFailed ){ + if( pIdx + && (pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable)) + && !db->mallocFailed + ){ last = sqlite3VdbeCurrentAddr(v); k = pLevel->addrBody; pOp = sqlite3VdbeGetOp(v, k); @@ -4528,6 +4565,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){ if( !HasRowid(pTab) ){ Index *pPk = sqlite3PrimaryKeyIndex(pTab); x = pPk->aiColumn[x]; + assert( x>=0 ); } x = sqlite3ColumnOfIndex(pIdx, x); if( x>=0 ){ diff --git a/src/wherecode.c b/src/wherecode.c index 77b8be4be..a6c45f032 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -46,8 +46,8 @@ static void explainAppendTerm( */ static const char *explainIndexColumnName(Index *pIdx, int i){ i = pIdx->aiColumn[i]; - if( i==(-2) ) return "<expr>"; - if( i==(-1) ) return "rowid"; + if( i==XN_EXPR ) return "<expr>"; + if( i==XN_ROWID ) return "rowid"; return pIdx->pTable->aCol[i].zName; } @@ -514,7 +514,7 @@ static int codeAllEqualityTerms( sqlite3VdbeJumpHere(v, j); for(j=0; j<nSkip; j++){ sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, j, regBase+j); - testcase( pIdx->aiColumn[j]==(-2) ); + testcase( pIdx->aiColumn[j]==XN_EXPR ); VdbeComment((v, "%s", explainIndexColumnName(pIdx, j))); } } @@ -700,8 +700,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( disableTerm(pLevel, pLoop->aLTerm[j]); } } - pLevel->op = OP_VNext; pLevel->p1 = iCur; + pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext; pLevel->p2 = sqlite3VdbeCurrentAddr(v); sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2); sqlite3ExprCachePop(pParse); diff --git a/test/delete4.test b/test/delete4.test index 7334bf02e..f3598a949 100644 --- a/test/delete4.test +++ b/test/delete4.test @@ -98,5 +98,48 @@ do_execsql_test 3.4 { PRAGMA integrity_check; } {ok} +# Between 2015-09-14 and 2015-09-28, the following test cases would result +# in corruption (wrong # of entries in index) due to a bug in the ONEPASS +# optimization. +# +do_execsql_test 4.1 { + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(col0, col1); + INSERT INTO "t4" VALUES(14, 'abcde'); + CREATE INDEX idx_t4_0 ON t4 (col1, col0); + CREATE INDEX idx_t4_3 ON t4 (col0); + DELETE FROM t4 WHERE col0=69 OR col0>7; + PRAGMA integrity_check; +} {ok} +do_execsql_test 4.2 { + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(col0, col1); + INSERT INTO "t4" VALUES(14, 'abcde'); + CREATE INDEX idx_t4_3 ON t4 (col0); + CREATE INDEX idx_t4_0 ON t4 (col1, col0); + DELETE FROM t4 WHERE col0=69 OR col0>7; + PRAGMA integrity_check; +} {ok} +do_execsql_test 4.11 { + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(col0, col1, pk PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t4 VALUES(14, 'abcde','xyzzy'); + CREATE INDEX idx_t4_0 ON t4 (col1, col0); + CREATE INDEX idx_t4_3 ON t4 (col0); + DELETE FROM t4 WHERE col0=69 OR col0>7; + PRAGMA integrity_check; +} {ok} +do_execsql_test 4.12 { + DROP TABLE IF EXISTS t4; + CREATE TABLE t4(col0, col1, pk PRIMARY KEY) WITHOUT ROWID; + INSERT INTO t4 VALUES(14, 'abcde','xyzzy'); + CREATE INDEX idx_t4_3 ON t4 (col0); + CREATE INDEX idx_t4_0 ON t4 (col1, col0); + DELETE FROM t4 WHERE col0=69 OR col0>7; + PRAGMA integrity_check; +} {ok} + + + finish_test diff --git a/test/fkey1.test b/test/fkey1.test index 0bd4939eb..e10781ac5 100644 --- a/test/fkey1.test +++ b/test/fkey1.test @@ -151,4 +151,38 @@ do_execsql_test fkey1-4.2 { PRAGMA table_info="""1"; } {0 {"2} TEXT 0 {} 1 1 {"3} TEXT 0 {} 0} +#------------------------------------------------------------------------- +# +do_execsql_test fkey1-5.1 { + CREATE TABLE t11( + x INTEGER PRIMARY KEY, + parent REFERENCES t11 ON DELETE CASCADE + ); + INSERT INTO t11 VALUES (1, NULL), (2, 1), (3, 2); +} {} + +# The REPLACE part of this statement deletes the row (2, 1). Then the +# DELETE CASCADE caused by deleting that row removes the (3, 2) row. Which +# would have been the parent of the new row being inserted. Causing an +# FK violation. +# +do_catchsql_test fkey1-5.2 { + INSERT OR REPLACE INTO t11 VALUES (2, 3); +} {1 {FOREIGN KEY constraint failed}} + +# A similar test to the above. +do_execsql_test fkey1-5.3 { + CREATE TABLE Foo ( + Id INTEGER PRIMARY KEY, + ParentId INTEGER REFERENCES Foo(Id) ON DELETE CASCADE, C1 + ); + INSERT OR REPLACE INTO Foo(Id, ParentId, C1) VALUES (1, null, 'A'); + INSERT OR REPLACE INTO Foo(Id, ParentId, C1) VALUES (2, 1, 'A-2-1'); + INSERT OR REPLACE INTO Foo(Id, ParentId, C1) VALUES (3, 2, 'A-3-2'); + INSERT OR REPLACE INTO Foo(Id, ParentId, C1) VALUES (4, 3, 'A-4-3'); +} +do_catchsql_test fkey1-5.4 { + INSERT OR REPLACE INTO Foo(Id, ParentId, C1) VALUES (2, 3, 'A-2-3'); +} {1 {FOREIGN KEY constraint failed}} + finish_test diff --git a/test/fts3conf.test b/test/fts3conf.test index e91efbefb..e157c239e 100644 --- a/test/fts3conf.test +++ b/test/fts3conf.test @@ -86,11 +86,11 @@ foreach {tn sql uses constraint data} [subst { 9 "INSERT OR IGNORE $T2" 1 0 {{a b c d} {e f g h} {i j k l} z} 10 "INSERT OR REPLACE $T2" 1 0 {{a b c d} y {i j k l} z} - 11 "UPDATE OR ROLLBACK $T3" 1 1 {{a b c d} {e f g h}} - 12 "UPDATE OR ABORT $T3" 1 1 {{a b c d} {e f g h} {i j k l}} - 13 "UPDATE OR FAIL $T3" 1 1 {{a b c d} {e f g h} {i j k l}} - 14 "UPDATE OR IGNORE $T3" 1 0 {{a b c d} {e f g h} {i j k l}} - 15 "UPDATE OR REPLACE $T3" 1 0 {{a b c d} {i j k l}} + 11 "UPDATE OR ROLLBACK $T3" 0 1 {{a b c d} {e f g h}} + 12 "UPDATE OR ABORT $T3" 0 1 {{a b c d} {e f g h} {i j k l}} + 13 "UPDATE OR FAIL $T3" 0 1 {{a b c d} {e f g h} {i j k l}} + 14 "UPDATE OR IGNORE $T3" 0 0 {{a b c d} {e f g h} {i j k l}} + 15 "UPDATE OR REPLACE $T3" 0 0 {{a b c d} {i j k l}} 16 "UPDATE OR ROLLBACK $T4" 1 1 {{a b c d} {e f g h}} 17 "UPDATE OR ABORT $T4" 1 1 {{a b c d} {e f g h} {i j k l}} @@ -178,4 +178,38 @@ do_execsql_test 3.8 { SELECT quote(matchinfo(t3, 'na')) FROM t3 WHERE t3 MATCH 'one' } {X'0200000002000000'} +#------------------------------------------------------------------------- +# Test that the xSavepoint is invoked correctly if the first write +# operation within a transaction is to a virtual table. +# +do_catchsql_test 4.1.1 { + CREATE VIRTUAL TABLE t0 USING fts4; + BEGIN; + INSERT INTO t0(rowid, content) SELECT + 1, 'abc' UNION ALL SELECT + 2, 'def' UNION ALL SELECT + 1, 'ghi'; +} {1 {constraint failed}} +do_execsql_test 4.1.2 { + COMMIT; +} +do_execsql_test 4.1.3 { + SELECT * FROM t0 WHERE t0 MATCH 'abc'; + INSERT INTO t0(t0) VALUES('integrity-check'); +} {} + +do_execsql_test 4.2.1 { + CREATE VIRTUAL TABLE t01 USING fts4; + BEGIN; + SAVEPOINT abc; + INSERT INTO t01 VALUES('a b c'); + ROLLBACK TO abc; + COMMIT; +} +do_execsql_test 4.2.2 { + SELECT * FROM t01 WHERE t01 MATCH 'b'; + INSERT INTO t01(t01) VALUES('integrity-check'); +} {} + finish_test + diff --git a/test/fts4onepass.test b/test/fts4onepass.test new file mode 100644 index 000000000..d290980dc --- /dev/null +++ b/test/fts4onepass.test @@ -0,0 +1,147 @@ +# 2015 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. +# +#************************************************************************* +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +source $testdir/fts3_common.tcl +set ::testprefix fts4onepass + +# 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; + INSERT INTO ft(rowid, content) VALUES(1, '1 2 3'); + INSERT INTO ft(rowid, content) VALUES(2, '4 5 6'); + INSERT INTO ft(rowid, content) VALUES(3, '7 8 9'); +} + +#------------------------------------------------------------------------- +# Check that UPDATE and DELETE statements that feature "WHERE rowid=?" or +# or "WHERE docid=?" clauses do not use statement journals. But that other +# DELETE and UPDATE statements do. +# +# Note: "MATCH ? AND docid=?" does use a statement journal. +# +foreach {tn sql uses} { + 1.1 { DELETE FROM ft } 1 + 1.2 { DELETE FROM ft WHERE docid=? } 0 + 1.3 { DELETE FROM ft WHERE rowid=? } 0 + 1.4 { DELETE FROM ft WHERE ft MATCH '1' } 1 + 1.5 { DELETE FROM ft WHERE ft MATCH '1' AND docid=? } 1 + 1.6 { DELETE FROM ft WHERE ft MATCH '1' AND rowid=? } 1 + + 2.1 { UPDATE ft SET content='a b c' } 1 + 2.2 { UPDATE ft SET content='a b c' WHERE docid=? } 0 + 2.3 { UPDATE ft SET content='a b c' WHERE rowid=? } 0 + 2.4 { UPDATE ft SET content='a b c' WHERE ft MATCH '1' } 1 + 2.5 { UPDATE ft SET content='a b c' WHERE ft MATCH '1' AND docid=? } 1 + 2.6 { UPDATE ft SET content='a b c' WHERE ft MATCH '1' AND rowid=? } 1 +} { + do_test 1.$tn { sql_uses_stmt db $sql } $uses +} + +#------------------------------------------------------------------------- +# Check that putting a "DELETE/UPDATE ... WHERE rowid=?" statement in a +# trigger program does not prevent the VM from using a statement +# transaction. Even if the calling statement cannot hit a constraint. +# +do_execsql_test 2.0 { + CREATE TABLE t1(x); + + CREATE TRIGGER t1_ai AFTER INSERT ON t1 BEGIN + DELETE FROM ft WHERE rowid=new.x; + END; + + CREATE TRIGGER t1_ad AFTER DELETE ON t1 BEGIN + UPDATE ft SET content = 'a b c' WHERE rowid=old.x; + END; + + CREATE TRIGGER t1_bu BEFORE UPDATE ON t1 BEGIN + DELETE FROM ft WHERE rowid=old.x; + END; +} + +foreach {tn sql uses} { + 1 { INSERT INTO t1 VALUES(1) } 1 + 2 { DELETE FROM t1 WHERE x=4 } 1 + 3 { UPDATE t1 SET x=10 WHERE x=11 } 1 +} { + do_test 2.$tn { sql_uses_stmt db $sql } $uses +} + +#------------------------------------------------------------------------- +# Test that an "UPDATE ... WHERE rowid=?" works and does not corrupt the +# index when it strikes a constraint. Both inside and outside a +# transaction. +# +foreach {tn tcl1 tcl2} { + 1 {} {} + + 2 { + execsql BEGIN + } { + if {[sqlite3_get_autocommit db]==1} { error "transaction rolled back!" } + execsql COMMIT + } +} { + + do_execsql_test 3.$tn.0 { + DROP TABLE IF EXISTS ft2; + CREATE VIRTUAL TABLE ft2 USING fts4; + INSERT INTO ft2(rowid, content) VALUES(1, 'a b c'); + INSERT INTO ft2(rowid, content) VALUES(2, 'a b d'); + INSERT INTO ft2(rowid, content) VALUES(3, 'a b e'); + } + + eval $tcl1 + foreach {tn2 sql content} { + 1 { UPDATE ft2 SET docid=2 WHERE docid=1 } + { 1 {a b c} 2 {a b d} 3 {a b e} } + + 2 { + INSERT INTO ft2(rowid, content) VALUES(4, 'a b f'); + UPDATE ft2 SET docid=5 WHERE docid=4; + UPDATE ft2 SET docid=3 WHERE docid=5; + } { 1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + + 3 { + UPDATE ft2 SET docid=3 WHERE docid=4; -- matches 0 rows + UPDATE ft2 SET docid=2 WHERE docid=3; + } { 1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + + 4 { + INSERT INTO ft2(rowid, content) VALUES(4, 'a b g'); + UPDATE ft2 SET docid=-1 WHERE docid=4; + UPDATE ft2 SET docid=3 WHERE docid=-1; + } {-1 {a b g} 1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + + 5 { + DELETE FROM ft2 WHERE rowid=451; + DELETE FROM ft2 WHERE rowid=-1; + UPDATE ft2 SET docid = 2 WHERE docid = 1; + } {1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + } { + do_catchsql_test 3.$tn.$tn2.a $sql {1 {constraint failed}} + do_execsql_test 3.$tn.$tn2.b { SELECT rowid, content FROM ft2 } $content + do_execsql_test 3.$tn.$tn2.c { + INSERT INTO ft2(ft2) VALUES('integrity-check'); + } + } + eval $tcl2 +} + +finish_test + diff --git a/test/fuzzdata3.db b/test/fuzzdata3.db Binary files differindex 4ca7a2357..0a41fd6e3 100644 --- a/test/fuzzdata3.db +++ b/test/fuzzdata3.db diff --git a/test/indexexpr1.test b/test/indexexpr1.test index 1945059f2..89bea1877 100644 --- a/test/indexexpr1.test +++ b/test/indexexpr1.test @@ -88,6 +88,23 @@ do_execsql_test indexexpr1-160eqp { WHERE substr(a,27,3)=='ord' AND d>=29; } {/USING INDEX t1a2/} +# ORDER BY using an indexed expression +# +do_execsql_test indexexpr1-170 { + CREATE INDEX t1alen ON t1(length(a)); + SELECT length(a) FROM t1 ORDER BY length(a); +} {20 25 27 29 38 52} +do_execsql_test indexexpr1-170eqp { + EXPLAIN QUERY PLAN + SELECT length(a) FROM t1 ORDER BY length(a); +} {/SCAN TABLE 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/} do_execsql_test indexexpr1-200 { DROP TABLE t1; @@ -193,7 +210,8 @@ do_execsql_test indexexpr1-400 { SELECT x, printf('ab%04xyz',x), random() FROM c; CREATE UNIQUE INDEX t3abc ON t3(CAST(a AS text), b, substr(c,1,3)); SELECT a FROM t3 WHERE CAST(a AS text)<='10' ORDER BY +a; -} {1 10} + PRAGMA integrity_check; +} {1 10 ok} do_catchsql_test indexexpr1-410 { INSERT INTO t3 SELECT * FROM t3 WHERE rowid=10; } {1 {UNIQUE constraint failed: index 't3abc'}} @@ -255,4 +273,39 @@ do_execsql_test indexexpr1-710 { ORDER BY +a, +x; } {1 1 | 2 2 |} +# Collating sequences on indexes of expressions +# +do_execsql_test indexexpr1-800 { + DROP TABLE IF EXISTS t8; + CREATE TABLE t8(a INTEGER PRIMARY KEY, b TEXT); + CREATE UNIQUE INDEX t8bx ON t8(substr(b,2,4) COLLATE nocase); + INSERT INTO t8(a,b) VALUES(1,'Alice'),(2,'Bartholemew'),(3,'Cynthia'); + SELECT * FROM t8 WHERE substr(b,2,4)='ARTH' COLLATE nocase; +} {2 Bartholemew} +do_catchsql_test indexexpr1-810 { + INSERT INTO t8(a,b) VALUES(4,'BARTHMERE'); +} {1 {UNIQUE constraint failed: index 't8bx'}} +do_catchsql_test indexexpr1-820 { + DROP INDEX t8bx; + CREATE UNIQUE INDEX t8bx ON t8(substr(b,2,4) COLLATE rtrim); + INSERT INTO t8(a,b) VALUES(4,'BARTHMERE'); +} {0 {}} + +# Check that PRAGMA integrity_check works correctly on a +# UNIQUE index that includes rowid and expression terms. +# +do_execsql_test indexexpr1-900 { + CREATE TABLE t9(a,b,c,d); + CREATE UNIQUE INDEX t9x1 ON t9(c,abs(d),b); + INSERT INTO t9(rowid,a,b,c,d) VALUES(1,2,3,4,5); + INSERT INTO t9(rowid,a,b,c,d) VALUES(2,NULL,NULL,NULL,NULL); + INSERT INTO t9(rowid,a,b,c,d) VALUES(3,NULL,NULL,NULL,NULL); + INSERT INTO t9(rowid,a,b,c,d) VALUES(4,5,6,7,8); + PRAGMA integrity_check; +} {ok} +do_catchsql_test indexexpr1-910 { + INSERT INTO t9(a,b,c,d) VALUES(5,6,7,-8); +} {1 {UNIQUE constraint failed: index 't9x1'}} + + finish_test diff --git a/test/view.test b/test/view.test index b39487689..0b8354a38 100644 --- a/test/view.test +++ b/test/view.test @@ -161,6 +161,16 @@ do_test view-3.3.3 { do_catchsql_test view-3.3.4 { CREATE VIEW v1err(x,y DESC,z) AS SELECT a, b+c, c-b FROM t1; } {1 {syntax error after column name "y"}} +do_catchsql_test view-3.3.5 { + DROP VIEW IF EXISTS v1err; + CREATE VIEW v1err(x,y) AS SELECT a, b+c, c-b FROM t1; + SELECT * FROM v1err; +} {1 {expected 2 columns for 'v1err' but got 3}} +do_catchsql_test view-3.3.6 { + DROP VIEW IF EXISTS v1err; + CREATE VIEW v1err(w,x,y,z) AS SELECT a, b+c, c-b FROM t1; + SELECT * FROM v1err; +} {1 {expected 4 columns for 'v1err' but got 3}} ifcapable compound { do_test view-3.4 { @@ -464,6 +474,11 @@ do_test view-12.1 { CREATE VIEW v12 AS SELECT a FROM t1 WHERE b=? } } {1 {parameters are not allowed in views}} +do_test view-12.2 { + catchsql { + CREATE VIEW v12(x) AS SELECT a FROM t1 WHERE b=? + } +} {1 {parameters are not allowed in views}} ifcapable attach { do_test view-13.1 { |