mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Update fts5 to avoid using a statement journal for UPDATE and DELETE operations that affect at most a single row.
FossilOrigin-Name: 5c83b9db46d61cfa76a1abed50467e2f02db0eb0
This commit is contained in:
@ -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);
|
||||
|
||||
|
@ -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( iRowid<p->iWriteRowid
|
||||
|| (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( i<nBlob ){
|
||||
u64 iVal;
|
||||
i += sqlite3Fts5GetVarint(&pBlob[i], &iVal);
|
||||
sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "%s%d", zSpace, (int)iVal);
|
||||
zSpace = " ";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Buffer (a/n) is assumed to contain a list of serialized varints. Read
|
||||
** each varint and append its string representation to buffer pBuf. Return
|
||||
@ -5344,7 +5373,7 @@ static void fts5DecodeFunction(
|
||||
}
|
||||
}else if( iSegid==0 ){
|
||||
if( iRowid==FTS5_AVERAGES_ROWID ){
|
||||
/* todo */
|
||||
fts5DecodeAverages(&rc, &s, a, n);
|
||||
}else{
|
||||
fts5DecodeStructure(&rc, &s, a, n);
|
||||
}
|
||||
|
@ -440,6 +440,19 @@ static int fts5CreateMethod(
|
||||
#define FTS5_PLAN_SCAN 5 /* No usable constraint */
|
||||
#define FTS5_PLAN_ROWID 6 /* (rowid = ?) */
|
||||
|
||||
/*
|
||||
** 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 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 (<tablename> 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 (<tablename> 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);
|
||||
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( pVtab->zErrMsg==0 );
|
||||
assert( (nArg==1 && eType0==SQLITE_INTEGER) || nArg==(2+pConfig->nCol+2) );
|
||||
assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL );
|
||||
assert( nArg!=1 || eType0==SQLITE_INTEGER );
|
||||
|
||||
fts5TripCursors(pTab);
|
||||
if( eType0==SQLITE_INTEGER ){
|
||||
if( fts5IsContentless(pTab) ){
|
||||
/* 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:
|
||||
|
@ -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 && iCol<pConfig->nCol; 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.iCol<pConfig->nCol; 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.iCol<pConfig->nCol; 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);
|
||||
|
||||
|
181
ext/fts5/test/fts5onepass.test
Normal file
181
ext/fts5/test/fts5onepass.test
Normal file
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
21
manifest
21
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
|
||||
|
@ -1 +1 @@
|
||||
6f90839e91024e2006042f5eb7f21ca5b47a9b4a
|
||||
5c83b9db46d61cfa76a1abed50467e2f02db0eb0
|
Reference in New Issue
Block a user