From: dan Date: Fri, 2 Oct 2015 20:04:30 +0000 (+0000) Subject: Update fts5 to avoid using a statement journal for UPDATE and DELETE operations that... X-Git-Tag: version-3.9.0~42 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=bca189d5d86fe18c54d42c35a263a9db66267d24;p=thirdparty%2Fsqlite.git Update fts5 to avoid using a statement journal for UPDATE and DELETE operations that affect at most a single row. FossilOrigin-Name: 5c83b9db46d61cfa76a1abed50467e2f02db0eb0 --- diff --git a/ext/fts5/fts5Int.h b/ext/fts5/fts5Int.h index 6f6f4ed784..99b07bfb2f 100644 --- a/ext/fts5/fts5Int.h +++ b/ext/fts5/fts5Int.h @@ -370,6 +370,7 @@ int sqlite3Fts5IndexWrite( */ int sqlite3Fts5IndexBeginWrite( Fts5Index *p, /* Index to write to */ + int bDelete, /* True if current operation is a delete */ i64 iDocid /* Docid to add or remove data from */ ); @@ -526,7 +527,8 @@ int sqlite3Fts5DropAll(Fts5Config*); int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **); int sqlite3Fts5StorageDelete(Fts5Storage *p, i64); -int sqlite3Fts5StorageInsert(Fts5Storage *p, sqlite3_value **apVal, int, i64*); +int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*); +int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64); int sqlite3Fts5StorageIntegrity(Fts5Storage *p); diff --git a/ext/fts5/fts5_index.c b/ext/fts5/fts5_index.c index 9910c59b0a..418127a51c 100644 --- a/ext/fts5/fts5_index.c +++ b/ext/fts5/fts5_index.c @@ -291,7 +291,7 @@ struct Fts5Index { int nMaxPendingData; /* Max pending data before flush to disk */ int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ - Fts5Buffer scratch; + int bDelete; /* Current write is a delete */ /* Error state. */ int rc; /* Current error code */ @@ -1780,6 +1780,7 @@ static void fts5SegIterNext( pIter->iEndofDoclist = nList+1; sqlite3Fts5BufferSet(&p->rc, &pIter->term, strlen(zTerm), (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); + if( pbNewTerm ) *pbNewTerm = 1; } }else{ iOff = 0; @@ -4195,7 +4196,7 @@ static void fts5SetupPrefixIter( ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain ** to the document with rowid iRowid. */ -int sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){ +int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ assert( p->rc==SQLITE_OK ); /* Allocate the hash table if it has not already been allocated */ @@ -4204,10 +4205,15 @@ int sqlite3Fts5IndexBeginWrite(Fts5Index *p, i64 iRowid){ } /* Flush the hash table to disk if required */ - if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){ + if( iRowidiWriteRowid + || (iRowid==p->iWriteRowid && p->bDelete==0) + || (p->nPendingData > p->nMaxPendingData) + ){ fts5IndexFlush(p); } + p->iWriteRowid = iRowid; + p->bDelete = bDelete; return fts5IndexReturn(p); } @@ -4306,7 +4312,6 @@ int sqlite3Fts5IndexClose(Fts5Index *p){ sqlite3_finalize(p->pIdxDeleter); sqlite3_finalize(p->pIdxSelect); sqlite3Fts5HashFree(p->pHash); - sqlite3Fts5BufferFree(&p->scratch); sqlite3_free(p->zDataTbl); sqlite3_free(p); } @@ -4367,6 +4372,7 @@ int sqlite3Fts5IndexWrite( Fts5Config *pConfig = p->pConfig; assert( p->rc==SQLITE_OK ); + assert( (iCol<0)==p->bDelete ); /* Add the entry to the main terms index. */ rc = sqlite3Fts5HashWrite( @@ -5243,6 +5249,29 @@ static void fts5DecodeStructure( fts5StructureRelease(p); } +/* +** This is part of the fts5_decode() debugging aid. +** +** Arguments pBlob/nBlob contain an "averages" record. This function +** appends a human-readable representation of record to the buffer passed +** as the second argument. +*/ +static void fts5DecodeAverages( + int *pRc, /* IN/OUT: error code */ + Fts5Buffer *pBuf, + const u8 *pBlob, int nBlob +){ + int i = 0; + const char *zSpace = ""; + + while( iflags. 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 fts5SetUniqueFlag(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 FTS5 tables. Within the ** WHERE constraint, it searches for the following: @@ -546,6 +559,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ bHasMatch = BitFlagTest(idxFlags, FTS5_BI_MATCH); if( BitFlagTest(idxFlags, FTS5_BI_ROWID_EQ) ){ pInfo->estimatedCost = bHasMatch ? 100.0 : 10.0; + if( bHasMatch==0 ) fts5SetUniqueFlag(pInfo); }else if( BitFlagAllTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){ pInfo->estimatedCost = bHasMatch ? 500.0 : 250000.0; }else if( BitFlagTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){ @@ -1284,15 +1298,14 @@ static void fts5SetVtabError(Fts5Table *p, const char *zFormat, ...){ */ static int fts5SpecialInsert( Fts5Table *pTab, /* Fts5 table object */ - sqlite3_value *pCmd, /* Value inserted into special column */ + const char *zCmd, /* Text inserted into table-name column */ sqlite3_value *pVal /* Value inserted into rank column */ ){ Fts5Config *pConfig = pTab->pConfig; - const char *z = (const char*)sqlite3_value_text(pCmd); int rc = SQLITE_OK; int bError = 0; - if( 0==sqlite3_stricmp("delete-all", z) ){ + if( 0==sqlite3_stricmp("delete-all", zCmd) ){ if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ fts5SetVtabError(pTab, "'delete-all' may only be used with a " @@ -1302,7 +1315,7 @@ static int fts5SpecialInsert( }else{ rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage); } - }else if( 0==sqlite3_stricmp("rebuild", z) ){ + }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){ if( pConfig->eContent==FTS5_CONTENT_NONE ){ fts5SetVtabError(pTab, "'rebuild' may not be used with a contentless fts5 table" @@ -1311,27 +1324,27 @@ static int fts5SpecialInsert( }else{ rc = sqlite3Fts5StorageRebuild(pTab->pStorage); } - }else if( 0==sqlite3_stricmp("optimize", z) ){ + }else if( 0==sqlite3_stricmp("optimize", zCmd) ){ rc = sqlite3Fts5StorageOptimize(pTab->pStorage); - }else if( 0==sqlite3_stricmp("merge", z) ){ + }else if( 0==sqlite3_stricmp("merge", zCmd) ){ int nMerge = sqlite3_value_int(pVal); rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge); - }else if( 0==sqlite3_stricmp("integrity-check", z) ){ + }else if( 0==sqlite3_stricmp("integrity-check", zCmd) ){ rc = sqlite3Fts5StorageIntegrity(pTab->pStorage); #ifdef SQLITE_DEBUG - }else if( 0==sqlite3_stricmp("prefix-index", z) ){ + }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){ pConfig->bPrefixIndex = sqlite3_value_int(pVal); #endif }else{ rc = sqlite3Fts5IndexLoadConfig(pTab->pIndex); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5ConfigSetValue(pTab->pConfig, z, pVal, &bError); + rc = sqlite3Fts5ConfigSetValue(pTab->pConfig, zCmd, pVal, &bError); } if( rc==SQLITE_OK ){ if( bError ){ rc = SQLITE_ERROR; }else{ - rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, z, pVal, 0); + rc = sqlite3Fts5StorageConfigValue(pTab->pStorage, zCmd, pVal, 0); } } } @@ -1352,10 +1365,35 @@ static int fts5SpecialDelete( return rc; } +static void fts5StorageInsert( + int *pRc, + Fts5Table *pTab, + sqlite3_value **apVal, + i64 *piRowid +){ + int rc = *pRc; + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid); + } + *pRc = rc; +} + /* ** This function is the implementation of the xUpdate callback used by ** FTS3 virtual tables. It is invoked by SQLite each time a row is to be ** inserted, updated or deleted. +** +** A delete specifies a single argument - the rowid of the row to remove. +** +** Update and insert operations pass: +** +** 1. The "old" rowid, or NULL. +** 2. The "new" rowid. +** 3. Values for each of the nCol matchable columns. +** 4. Values for the two hidden columns ( and "rank"). */ static int fts5UpdateMethod( sqlite3_vtab *pVtab, /* Virtual table handle */ @@ -1366,62 +1404,106 @@ static int fts5UpdateMethod( Fts5Table *pTab = (Fts5Table*)pVtab; Fts5Config *pConfig = pTab->pConfig; int eType0; /* value_type() of apVal[0] */ - int eConflict; /* ON CONFLICT for this DML */ int rc = SQLITE_OK; /* Return code */ /* A transaction must be open when this is called. */ assert( pTab->ts.eState==1 ); + assert( pVtab->zErrMsg==0 ); + assert( nArg==1 || nArg==(2+pConfig->nCol+2) ); + assert( nArg==1 + || sqlite3_value_type(apVal[1])==SQLITE_INTEGER + || sqlite3_value_type(apVal[1])==SQLITE_NULL + ); assert( pTab->pConfig->pzErrmsg==0 ); pTab->pConfig->pzErrmsg = &pTab->base.zErrMsg; - /* A delete specifies a single argument - the rowid of the row to remove. - ** Update and insert operations pass: - ** - ** 1. The "old" rowid, or NULL. - ** 2. The "new" rowid. - ** 3. Values for each of the nCol matchable columns. - ** 4. Values for the two hidden columns ( and "rank"). - */ + /* Put any active cursors into REQUIRE_SEEK state. */ + fts5TripCursors(pTab); eType0 = sqlite3_value_type(apVal[0]); - eConflict = sqlite3_vtab_on_conflict(pConfig->db); - - assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); - assert( pVtab->zErrMsg==0 ); - assert( (nArg==1 && eType0==SQLITE_INTEGER) || nArg==(2+pConfig->nCol+2) ); - - fts5TripCursors(pTab); - if( eType0==SQLITE_INTEGER ){ - if( fts5IsContentless(pTab) ){ + if( eType0==SQLITE_NULL + && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL + ){ + /* A "special" INSERT op. These are handled separately. */ + const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]); + if( pConfig->eContent!=FTS5_CONTENT_NORMAL + && 0==sqlite3_stricmp("delete", z) + ){ + rc = fts5SpecialDelete(pTab, apVal, pRowid); + }else{ + rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); + } + }else{ + /* A regular INSERT, UPDATE or DELETE statement. The trick here is that + ** any conflict on the rowid value must be detected before any + ** modifications are made to the database file. There are 4 cases: + ** + ** 1) DELETE + ** 2) UPDATE (rowid not modified) + ** 3) UPDATE (rowid modified) + ** 4) INSERT + ** + ** Cases 3 and 4 may violate the rowid constraint. + */ + int eConflict = sqlite3_vtab_on_conflict(pConfig->db); + + assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); + assert( nArg!=1 || eType0==SQLITE_INTEGER ); + + /* Filter out attempts to run UPDATE or DELETE on contentless tables. + ** This is not suported. */ + if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){ pTab->base.zErrMsg = sqlite3_mprintf( "cannot %s contentless fts5 table: %s", (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName ); rc = SQLITE_ERROR; - }else{ + } + + /* Case 1: DELETE */ + else if( nArg==1 ){ i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel); } - }else{ - sqlite3_value *pCmd = apVal[2 + pConfig->nCol]; - assert( nArg>1 ); - if( SQLITE_NULL!=sqlite3_value_type(pCmd) ){ - const char *z = (const char*)sqlite3_value_text(pCmd); - if( pConfig->eContent!=FTS5_CONTENT_NORMAL - && 0==sqlite3_stricmp("delete", z) + + /* Case 2: INSERT */ + else if( eType0!=SQLITE_INTEGER ){ + /* If this is a REPLACE, first remove the current entry (if any) */ + if( eConflict==SQLITE_REPLACE + && sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ - rc = fts5SpecialDelete(pTab, apVal, pRowid); - }else{ - rc = fts5SpecialInsert(pTab, pCmd, apVal[2 + pConfig->nCol + 1]); + i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew); } - goto update_method_out; + fts5StorageInsert(&rc, pTab, apVal, pRowid); } - } - - if( rc==SQLITE_OK && nArg>1 ){ - rc = sqlite3Fts5StorageInsert(pTab->pStorage, apVal, eConflict, pRowid); + /* Case 2: UPDATE */ + else{ + i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */ + i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */ + if( iOld!=iNew ){ + if( eConflict==SQLITE_REPLACE ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew); + } + fts5StorageInsert(&rc, pTab, apVal, pRowid); + }else{ + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *pRowid); + } + } + }else{ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld); + fts5StorageInsert(&rc, pTab, apVal, pRowid); + } + } } update_method_out: diff --git a/ext/fts5/fts5_storage.c b/ext/fts5/fts5_storage.c index 65c6d48a36..9f19e561b4 100644 --- a/ext/fts5/fts5_storage.c +++ b/ext/fts5/fts5_storage.c @@ -392,7 +392,7 @@ static int fts5StorageDeleteFromIndex(Fts5Storage *p, i64 iDel){ Fts5InsertCtx ctx; ctx.pStorage = p; ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iDel); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1] ) continue; ctx.szCol = 0; @@ -549,7 +549,7 @@ int sqlite3Fts5StorageSpecialDelete( ctx.pStorage = p; ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iDel); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); for(iCol=0; rc==SQLITE_OK && iColnCol; iCol++){ if( pConfig->abUnindexed[iCol] ) continue; ctx.szCol = 0; @@ -639,7 +639,7 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){ i64 iRowid = sqlite3_column_int64(pScan, 0); sqlite3Fts5BufferZero(&buf); - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, iRowid); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){ ctx.szCol = 0; if( pConfig->abUnindexed[ctx.iCol]==0 ){ @@ -705,58 +705,69 @@ static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){ } /* -** Insert a new row into the FTS table. +** Insert a new row into the FTS content table. */ -int sqlite3Fts5StorageInsert( - Fts5Storage *p, /* Storage module to write to */ - sqlite3_value **apVal, /* Array of values passed to xUpdate() */ - int eConflict, /* on conflict clause */ - i64 *piRowid /* OUT: rowid of new record */ +int sqlite3Fts5StorageContentInsert( + Fts5Storage *p, + sqlite3_value **apVal, + i64 *piRowid +){ + Fts5Config *pConfig = p->pConfig; + int rc = SQLITE_OK; + + /* Insert the new row into the %_content table. */ + if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ + if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ + *piRowid = sqlite3_value_int64(apVal[1]); + }else{ + rc = fts5StorageNewRowid(p, piRowid); + } + }else{ + sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */ + int i; /* Counter variable */ +#if 0 + if( eConflict==SQLITE_REPLACE ){ + eStmt = FTS5_STMT_REPLACE_CONTENT; + rc = fts5StorageDeleteFromIndex(p, sqlite3_value_int64(apVal[1])); + }else{ + eStmt = FTS5_STMT_INSERT_CONTENT; + } +#endif + if( rc==SQLITE_OK ){ + rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0); + } + for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ + rc = sqlite3_bind_value(pInsert, i, apVal[i]); + } + if( rc==SQLITE_OK ){ + sqlite3_step(pInsert); + rc = sqlite3_reset(pInsert); + } + *piRowid = sqlite3_last_insert_rowid(pConfig->db); + } + + return rc; +} + +/* +** Insert new entries into the FTS index and %_docsize table. +*/ +int sqlite3Fts5StorageIndexInsert( + Fts5Storage *p, + sqlite3_value **apVal, + i64 iRowid ){ Fts5Config *pConfig = p->pConfig; int rc = SQLITE_OK; /* Return code */ - sqlite3_stmt *pInsert = 0; /* Statement used to write %_content table */ - int eStmt = 0; /* Type of statement used on %_content */ - int i; /* Counter variable */ Fts5InsertCtx ctx; /* Tokenization callback context object */ Fts5Buffer buf; /* Buffer used to build up %_docsize blob */ memset(&buf, 0, sizeof(Fts5Buffer)); + ctx.pStorage = p; rc = fts5StorageLoadTotals(p, 1); - /* Insert the new row into the %_content table. */ - if( rc==SQLITE_OK ){ - if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ - if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ - *piRowid = sqlite3_value_int64(apVal[1]); - }else{ - rc = fts5StorageNewRowid(p, piRowid); - } - }else{ - if( eConflict==SQLITE_REPLACE ){ - eStmt = FTS5_STMT_REPLACE_CONTENT; - rc = fts5StorageDeleteFromIndex(p, sqlite3_value_int64(apVal[1])); - }else{ - eStmt = FTS5_STMT_INSERT_CONTENT; - } - if( rc==SQLITE_OK ){ - rc = fts5StorageGetStmt(p, eStmt, &pInsert, 0); - } - for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ - rc = sqlite3_bind_value(pInsert, i, apVal[i]); - } - if( rc==SQLITE_OK ){ - sqlite3_step(pInsert); - rc = sqlite3_reset(pInsert); - } - *piRowid = sqlite3_last_insert_rowid(pConfig->db); - } - } - - /* Add new entries to the FTS index */ if( rc==SQLITE_OK ){ - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, *piRowid); - ctx.pStorage = p; + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid); } for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){ ctx.szCol = 0; @@ -776,7 +787,7 @@ int sqlite3Fts5StorageInsert( /* Write the %_docsize record */ if( rc==SQLITE_OK ){ - rc = fts5StorageInsertDocsize(p, *piRowid, &buf); + rc = fts5StorageInsertDocsize(p, iRowid, &buf); } sqlite3_free(buf.p); diff --git a/ext/fts5/test/fts5onepass.test b/ext/fts5/test/fts5onepass.test new file mode 100644 index 0000000000..a614b780a2 --- /dev/null +++ b/ext/fts5/test/fts5onepass.test @@ -0,0 +1,181 @@ +# 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. +# +#************************************************************************* +# + +source [file join [file dirname [info script]] fts5_common.tcl] +set testprefix fts5onepass + +# If SQLITE_ENABLE_FTS3 is defined, omit this file. +ifcapable !fts5 { + finish_test + return +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE ft USING fts5(content); + 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 rowid=?" clauses do not use statement journals. But that other +# DELETE and UPDATE statements do. +# +# Note: "MATCH ? AND rowid=?" does use a statement journal. +# +foreach {tn sql uses} { + 1.1 { DELETE FROM ft } 1 + 1.2 { DELETE FROM ft WHERE rowid=? } 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 rowid=? } 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 rowid=? } 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 rowid=? } 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 fts5(content); + 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 rowid=2 WHERE rowid=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 rowid=5 WHERE rowid=4; + UPDATE ft2 SET rowid=3 WHERE rowid=5; + } { 1 {a b c} 2 {a b d} 3 {a b e} 5 {a b f} } + + 3 { + UPDATE ft2 SET rowid=3 WHERE rowid=4; -- matches 0 rows + UPDATE ft2 SET rowid=2 WHERE rowid=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 rowid=-1 WHERE rowid=4; + UPDATE ft2 SET rowid=3 WHERE rowid=-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 rowid = 2 WHERE rowid = 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 +} + +#------------------------------------------------------------------------- +# Check that DELETE and UPDATE operations can be done without flushing +# the in-memory hash table to disk. +# +reset_db +do_execsql_test 4.1.1 { + CREATE VIRTUAL TABLE ttt USING fts5(x); + BEGIN; + INSERT INTO ttt(rowid, x) VALUES(1, 'a b c'); + INSERT INTO ttt(rowid, x) VALUES(2, 'a b c'); + INSERT INTO ttt(rowid, x) VALUES(3, 'a b c'); + COMMIT +} +do_test 4.1.2 { fts5_level_segs ttt } {1} + +do_execsql_test 4.2.1 { + BEGIN; + DELETE FROM ttt WHERE rowid=1; + DELETE FROM ttt WHERE rowid=3; + INSERT INTO ttt(rowid, x) VALUES(4, 'd e f'); + INSERT INTO ttt(rowid, x) VALUES(5, 'd e f'); + COMMIT; +} {} +do_test 4.2.2 { fts5_level_segs ttt } {2} + + +do_execsql_test 4.3.1 { + BEGIN; + UPDATE ttt SET x = 'd e f' WHERE rowid = 2; + UPDATE ttt SET x = 'A B C' WHERE rowid = 4; + INSERT INTO ttt(rowid, x) VALUES(6, 'd e f'); + COMMIT; +} {} +do_test 4.2.2 { fts5_level_segs ttt } {3} + +finish_test + diff --git a/ext/fts5/test/fts5simple.test b/ext/fts5/test/fts5simple.test index 6a980c1b19..2fa72740dd 100644 --- a/ext/fts5/test/fts5simple.test +++ b/ext/fts5/test/fts5simple.test @@ -19,7 +19,6 @@ ifcapable !fts5 { return } -if 1 { #------------------------------------------------------------------------- # set doc "x x [string repeat {y } 50]z z" @@ -137,8 +136,6 @@ do_execsql_test 5.4 { SELECT rowid FROM tt WHERE tt MATCH 'a*'; } {1 2} -} - do_execsql_test 5.5 { DELETE FROM tt; BEGIN; @@ -184,6 +181,44 @@ do_catchsql_test 6.3 { SELECT * FROM xyz WHERE xyz MATCH NULL } {1 {fts5: syntax error near ""}} +#------------------------------------------------------------------------- + +do_execsql_test 7.1 { + CREATE VIRTUAL TABLE ft2 USING fts5(content); + INSERT INTO ft2(rowid, content) VALUES(1, 'a b c'); + INSERT INTO ft2(rowid, content) VALUES(2, 'a b d'); +} + +do_catchsql_test 7.2 { + BEGIN; + UPDATE ft2 SET rowid=2 WHERE rowid=1; +} {1 {constraint failed}} + +do_execsql_test 7.3 { + COMMIT; + INSERT INTO ft2(ft2) VALUES('integrity-check'); +} {} + +do_execsql_test 7.4 { + SELECT * FROM ft2; +} {{a b c} {a b d}} + +#------------------------------------------------------------------------- +# +reset_db +do_execsql_test 8.1 { + CREATE VIRTUAL TABLE ft2 USING fts5(content); + INSERT INTO ft2(rowid, content) VALUES(1, 'a b'); +} + +do_execsql_test 8.2 { + BEGIN; + INSERT INTO ft2(rowid, content) VALUES(4, 'a x'); +} + +do_execsql_test 8.3 { + INSERT INTO ft2(ft2) VALUES('integrity-check'); +} finish_test diff --git a/manifest b/manifest index 8cd4cfd3fd..5bb7286a31 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\san\sfts3\sbug\scausing\sNEAR\squeries\son\suncommitted\sdata\sto\smalfunction. -D 2015-10-01T18:31:29.602 +C Update\sfts5\sto\savoid\susing\sa\sstatement\sjournal\sfor\sUPDATE\sand\sDELETE\soperations\sthat\saffect\sat\smost\sa\ssingle\srow. +D 2015-10-02T20:04:30.384 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 2143eeef6d0cc26006ae5fc4bb242a4a8b973412 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -106,15 +106,15 @@ F ext/fts3/unicode/mkunicode.tcl 95cf7ec186e48d4985e433ff8a1c89090a774252 F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95 F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0 F ext/fts5/fts5.h 98f802fe41481f9d797fce496f0fefcad72c7782 -F ext/fts5/fts5Int.h 666aba8432940a8449a3bd4636e898fe906ed95d +F ext/fts5/fts5Int.h ff78a77d819a7fc04a7f8b08b0e1ce361a3395e4 F ext/fts5/fts5_aux.c 7a307760a9c57c750d043188ec0bad59f5b5ec7e 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 c77882ab38d698d5147cef96fa67a2121d77c0b3 -F ext/fts5/fts5_main.c 53116cffeb26898832ff7700cc5ebac5fe085d32 -F ext/fts5/fts5_storage.c 120f7b143688b5b7710dacbd48cff211609b8059 +F ext/fts5/fts5_index.c 00d2593f94ede440ea274f8db21864cf41632aa3 +F ext/fts5/fts5_main.c fd9ab880963ea536cd6b043efffa4a322ccfb2b3 +F ext/fts5/fts5_storage.c df061a5caf9e50fbbd43113009b5b248362f4995 F ext/fts5/fts5_tcl.c 6da58d6e8f42a93c4486b5ba9b187a7f995dee37 F ext/fts5/fts5_test_mi.c e96be827aa8f571031e65e481251dc1981d608bf F ext/fts5/fts5_tokenize.c f380f46f341af9c9a9908e1aade685ba1eaa157a @@ -164,6 +164,7 @@ F ext/fts5/test/fts5integrity.test 29f41d2c7126c6122fbb5d54e556506456876145 F ext/fts5/test/fts5matchinfo.test 2163b0013e824bba65499da9e34ea4da41349cc2 F ext/fts5/test/fts5merge.test 8f3cdba2ec9c5e7e568246e81b700ad37f764367 F ext/fts5/test/fts5near.test b214cddb1c1f1bddf45c75af768f20145f7e71cc +F ext/fts5/test/fts5onepass.test 7ed9608e258132cb8d55e7c479b08676ad68810c F ext/fts5/test/fts5optimize.test 42741e7c085ee0a1276140a752d4407d97c2c9f5 F ext/fts5/test/fts5plan.test 6a55ecbac9890765b0e16f8c421c7e0888cfe436 F ext/fts5/test/fts5porter.test 7cdc07bef301d70eebbfa75dcaf45c3680e1d0e1 @@ -173,7 +174,7 @@ F ext/fts5/test/fts5rank.test 11dcebba31d822f7e99685b4ea2c2ae3ec0b16f1 F ext/fts5/test/fts5rebuild.test 03935f617ace91ed23a6099c7c74d905227ff29b F ext/fts5/test/fts5restart.test c17728fdea26e7d0f617d22ad5b4b2862b994c17 F ext/fts5/test/fts5rowid.test 400384798349d658eaf06aefa1e364957d5d4821 -F ext/fts5/test/fts5simple.test 967b7144644ad4b40b2526160a5adfa896864c55 +F ext/fts5/test/fts5simple.test 426c960e51e5839c8655bca723c34a05d44dcb6d F ext/fts5/test/fts5synonym.test cf88c0a56d5ea9591e3939ef1f6e294f7f2d0671 F ext/fts5/test/fts5tokenizer.test ea4df698b35cc427ebf2ba22829d0e28386d8c89 F ext/fts5/test/fts5unicode.test fbef8d8a3b4b88470536cc57604a82ca52e51841 @@ -1389,7 +1390,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 e796c0efb6cf17444b53af75046daf7d8fa82f78 -R 0cafe6c8cdead8895dc88237ddef8389 +P 6f90839e91024e2006042f5eb7f21ca5b47a9b4a +R c4ed86613c7c7ba1f8647ee3a7ddd45f U dan -Z 654a1b4baa598b306106df1a114a779c +Z c935b0cc17e8eaea980f1eab0035d787 diff --git a/manifest.uuid b/manifest.uuid index becb519cfa..7529743fd4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -6f90839e91024e2006042f5eb7f21ca5b47a9b4a \ No newline at end of file +5c83b9db46d61cfa76a1abed50467e2f02db0eb0 \ No newline at end of file