1
0
mirror of https://github.com/sqlite/sqlite.git synced 2026-01-06 08:01:16 +03:00

Add the sqlite3_index_info.idxFlags field, allowing xBestIndex() implementations to specify to SQLite that a strategy may visit at most one row. Add support for this to fts3/4. Omit the statement journal from virtual table UPDATE and DELETE operations that are guaranteed not to affect more than one row.

FossilOrigin-Name: a1d08fd3d0419da8c22355d48c6d83eed6fd7e07
This commit is contained in:
dan
2015-09-29 16:47:53 +00:00
16 changed files with 370 additions and 134 deletions

View File

@@ -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++;

View File

@@ -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

View File

@@ -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 );

View File

@@ -1,5 +1,5 @@
C Ensure\sthat\sthe\sxSavepoint()\svirtual\stable\smethod\sis\scorrectly\sinvoked\sif\sthere\sare\salready\sopen\ssavepoints\s(or\sstatement\stransactions)\sthe\sfirst\stime\sa\svirtual\stable\sis\swritten\swithin\sa\stransaction.
D 2015-09-29T16:41:23.689
C Add\sthe\ssqlite3_index_info.idxFlags\sfield,\sallowing\sxBestIndex()\simplementations\sto\sspecify\sto\sSQLite\sthat\sa\sstrategy\smay\svisit\sat\smost\sone\srow.\sAdd\ssupport\sfor\sthis\sto\sfts3/4.\sOmit\sthe\sstatement\sjournal\sfrom\svirtual\stable\sUPDATE\sand\sDELETE\soperations\sthat\sare\sguaranteed\snot\sto\saffect\smore\sthan\sone\srow.
D 2015-09-29T16:47:53.132
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 2143eeef6d0cc26006ae5fc4bb242a4a8b973412
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -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
@@ -285,13 +285,13 @@ F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79
F src/btree.c 164583151135a3764672c2c25aa8e4fa06bdb12b
F src/btree.h 40189aefdc2b830d25c8b58fd7d56538481bfdd7
F src/btreeInt.h 8177c9ab90d772d6d2c6c517e05bed774b7c92c0
F src/build.c edc5a29cd55257b05be837c3613e2cade02b3e03
F src/build.c 361f58b73aad7804f5706bf62d210bd9cd608041
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 371df4fc86e96efeaed3d37565aef77f956be109
F src/delete.c 46bb5e217f83af68574ccb613421485578c41ba8
F src/expr.c 3a76afcdac925294c39903b7002ddb9e5fd29863
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c 83e1baba999bed3144ea5a2143fc922edf51135f
@@ -300,7 +300,7 @@ F src/global.c 508e4087f7b41d688e4762dcf4d4fe28cfbc87f9
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
F src/insert.c 9748a37e058256eb2ead69f028ab85ebf203ad15
F src/insert.c 81d6bf397c05b4e8ddc4af2ff6637c113ee7e37a
F src/journal.c b4124532212b6952f42eb2c12fa3c25701d8ba8d
F src/legacy.c ba1863ea58c4c840335a84ec276fc2b25e22bc4e
F src/lempar.c d344a95d60c24e2f490ee59db9784b1b17439012
@@ -340,12 +340,12 @@ F src/printf.c 0c4bcdd1c2e2521024f0a69cb5eb334f86b3652a
F src/random.c ba2679f80ec82c4190062d756f22d0c358180696
F src/resolve.c 1954a0f01bf65d78d7d559aea3d5c67f33376d91
F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e
F src/select.c 33230303f5f32430ee971a6fcc6a370e4a93ae1a
F src/select.c e49f4af9748c9e0cc1bf864b4190aa94841c8409
F src/shell.c a11b20da4c6630e0e8f83c47ce36f717dd0422f0
F src/sqlite.h.in 02f6ed7de3a96d10bd1e6e5803e4e4b786dff014
F src/sqlite.h.in 4b76d74d69af48c534c58fb723137dc6944bdedc
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
F src/sqlite3ext.h 64350bf36833a56ad675e27392a913f417c5c308
F src/sqliteInt.h 5afc6e50402be1e0a870f28e1cd8b32eb9db590f
F src/sqliteInt.h b6516bb2727876348b4e05aadd12237797626270
F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179
F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e
@@ -399,7 +399,7 @@ F src/threads.c bbfb74450643cb5372a43ad4f6cffd7e9dfcecb0
F src/tokenize.c 83c6ed569423a3af83a83973b444cf7123be33a6
F src/treeview.c 154f0acc622fa3514de8777dcedf4c8a8802b4ce
F src/trigger.c 322f23aad694e8f31d384dcfa386d52a48d3c52f
F src/update.c eb7ab3ff2928628692a4f14be397c95f4a681d97
F src/update.c d8f0afe1a1c66b97cbd87e5236f4e71d35ab4840
F src/utf.c fc6b889ba0779b7722634cdeaa25f1930d93820c
F src/util.c fc612367108b74573c5fd13a85d0a23027f438bd
F src/vacuum.c 2ddd5cad2a7b9cef7f9e431b8c7771634c6b1701
@@ -417,9 +417,9 @@ F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
F src/wal.c 18b0ed49830cf04fe2d68224b41838a73ac6cd24
F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba
F src/where.c 25d7527226d63176b3cd4880a85119cda9f83844
F src/where.c b7f928b6b8ce091f385ba5ac2394928608431550
F src/whereInt.h 7892bb54cf9ca0ae5c7e6094491b94c9286dc647
F src/wherecode.c 7660e1ad16817a921b099af553f3e1349352d16f
F src/wherecode.c a87238178f8c300fd27afec54326ea8d0a18baa2
F src/whereexpr.c 2473e4350e30f9b55d1c6a8f66ca23c689f23f1d
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
@@ -690,7 +690,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 6c7faa66bfb0e90c2c073c029c08b58ea5023922
F test/fts3conf.test ff90127b2462c788348c0dd7f6f8c573ef9cb5d9
F test/fts3corrupt.test 2710b77983cc7789295ddbffea52c1d3b7506dbb
F test/fts3corrupt2.test 6d96efae2f8a6af3eeaf283aba437e6d0e5447ba
F test/fts3cov.test e0fb00d8b715ddae4a94c305992dfc3ef70353d7
@@ -736,6 +736,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
@@ -1388,7 +1389,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 3a9f076250d9559d8ea94ba44095ba3ddbc5542d
R 756fa6b717967f5524da70e2a3635809
P 77948b5eceab92a77c39d0864ac15ad453a76fd7 0e317dda5dfd35b742afd70dacb5bfb39aa3fc4a
R d39a034a4821b5c42018f04d74a8aeb2
U dan
Z 7ea154fd75a363e5d81a988ffd47237f
Z 28a594c5d9670d5cb6f4428303834152

View File

@@ -1 +1 @@
77948b5eceab92a77c39d0864ac15ad453a76fd7
a1d08fd3d0419da8c22355d48c6d83eed6fd7e07

View File

@@ -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++){

View File

@@ -411,7 +411,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 */
@@ -494,7 +494,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);
@@ -516,7 +516,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
{

View File

@@ -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){

View File

@@ -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);
}
}
}

View File

@@ -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,8 +5686,15 @@ 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
**

View File

@@ -3504,6 +3504,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)
@@ -3513,6 +3514,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

View File

@@ -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) ){
@@ -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);
@@ -685,21 +684,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.
** Then loop through the contents of this ephemeral table executing a
** VUpdate for each row. When finished, drop the ephemeral table.
**
** 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 */
@@ -712,65 +713,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;
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 */
/* Construct the SELECT statement that will find the new values for
** all updated rows.
*/
pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, "_rowid_"));
if( pRowid ){
pEList = sqlite3ExprListAppend(pParse, pEList,
sqlite3ExprDup(db, pRowid, 0));
}
assert( pTab->iPKey<0 );
for(i=0; i<pTab->nCol; i++){
if( aXRef[i]>=0 ){
pExpr = sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0);
}else{
pExpr = sqlite3Expr(db, TK_ID, pTab->aCol[i].zName);
}
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.
*/
/* 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;
/* fill the ephemeral table
*/
sqlite3SelectDestInit(&dest, SRT_EphemTab, ephemTab);
sqlite3Select(pParse, pSelect, &dest);
/* Start scanning the virtual table */
pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0);
if( pWInfo==0 ) return;
/* 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);
/* Populate the argument registers. */
sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg);
if( pRowid ){
sqlite3ExprCode(pParse, pRowid, regArg+1);
}else{
sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1);
}
for(i=0; i<pTab->nCol; i++){
sqlite3VdbeAddOp3(v, OP_Column, ephemTab, i+1+(pRowid!=0), iReg+2+i);
if( aXRef[i]>=0 ){
sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
}else{
sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+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 */

View File

@@ -2839,6 +2839,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;
@@ -2884,6 +2885,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;
}
}
}
@@ -2899,6 +2901,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);

View File

@@ -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);

View File

@@ -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}}

147
test/fts4onepass.test Normal file
View File

@@ -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