#include "fts5Int.h"
+/*
+** pSavedRow:
+** SQL statement FTS5_STMT_LOOKUP2 is a copy of FTS5_STMT_LOOKUP, it
+** does a by-rowid lookup to retrieve a single row from the %_content
+** table or equivalent external-content table/view.
+**
+** However, FTS5_STMT_LOOKUP2 is only used when retrieving the original
+** values for a row being UPDATEd. In that case, the SQL statement is
+** not reset and pSavedRow is set to point at it. This is so that the
+** insert operation that follows the delete may access the original
+** row values for any new values for which sqlite3_value_nochange() returns
+** true. i.e. if the user executes:
+**
+** CREATE VIRTUAL TABLE ft USING fts5(a, b, c, locale=1);
+** ...
+** UPDATE fts SET a=?, b=? WHERE rowid=?;
+**
+** then the value passed to the xUpdate() method of this table as the
+** new.c value is an sqlite3_value_nochange() value. So in this case it
+** must be read from the saved row stored in Fts5Storage.pSavedRow.
+**
+** This is necessary - using sqlite3_value_nochange() instead of just having
+** SQLite pass the original value back via xUpdate() - so as not to discard
+** any locale information associated with such values.
+**
+*/
struct Fts5Storage {
Fts5Config *pConfig;
Fts5Index *pIndex;
# error "FTS5_STMT_LOOKUP mismatch"
#endif
-#define FTS5_STMT_LOOKUP2 3
+#define FTS5_STMT_LOOKUP2 3
#define FTS5_STMT_INSERT_CONTENT 4
#define FTS5_STMT_REPLACE_CONTENT 5
#define FTS5_STMT_DELETE_CONTENT 6
-#define FTS5_STMT_REPLACE_DOCSIZE 7
+#define FTS5_STMT_REPLACE_DOCSIZE 7
#define FTS5_STMT_DELETE_DOCSIZE 8
#define FTS5_STMT_LOOKUP_DOCSIZE 9
#define FTS5_STMT_REPLACE_CONFIG 10
-#define FTS5_STMT_SCAN 11
+#define FTS5_STMT_SCAN 11
/*
** Prepare the two insert statements - Fts5Storage.pInsertContent and
return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
}
+/*
+** This function is used as part of an UPDATE statement that modifies the
+** rowid of a row. In that case, this function is called first to set
+** Fts5Storage.pSavedRow to point to a statement that may be used to
+** access the original values of the row being deleted - iDel.
+**
+** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
+** It is not considered an error if row iDel does not exist. In this case
+** pSavedRow is not set and SQLITE_OK returned.
+*/
int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel){
int rc = SQLITE_OK;
sqlite3_stmt *pSeek = 0;
** If a row with rowid iDel is present in the %_content table, add the
** delete-markers to the FTS index necessary to delete it. Do not actually
** remove the %_content row at this time though.
+**
+** If parameter bSaveRow is true, then Fts5Storage.pSavedRow is left
+** pointing to a statement (FTS5_STMT_LOOKUP2) that may be used to access
+** the original values of the row being deleted. This is used by UPDATE
+** statements.
*/
static int fts5StorageDeleteFromIndex(
Fts5Storage *p,
i64 iDel,
sqlite3_value **apVal,
- int bSaveRow
+ int bSaveRow /* True to set pSavedRow */
){
Fts5Config *pConfig = p->pConfig;
sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */
return rc;
}
+/*
+** Reset any saved statement pSavedRow. Zero pSavedRow as well. This
+** should be called by the xUpdate() method of the fts5 table before
+** returning from any operation that may have set Fts5Storage.pSavedRow.
+*/
void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){
+ assert( pStorage->pSavedRow==0
+ || pStorage->pSavedRow==pStorage->aStmt[FTS5_STMT_LOOKUP2]
+ );
sqlite3_reset(pStorage->pSavedRow);
pStorage->pSavedRow = 0;
}
Fts5Storage *p, /* Storage object */
i64 iDel, /* Rowid to delete from table */
sqlite3_value **apVal, /* Optional - values to remove from index */
- int bSaveRow
+ int bSaveRow /* If true, set pSavedRow for deleted row */
){
Fts5Config *pConfig = p->pConfig;
int rc;
for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
ctx.szCol = 0;
if( pConfig->abUnindexed[ctx.iCol]==0 ){
- int bReset = 0;
- int nText = 0;
- const char *pText = 0;
- rc = sqlite3Fts5ExtractText(pConfig,
- sqlite3_column_value(pScan, ctx.iCol+1), 1, &bReset, &pText, &nText
- );
+ int bReset = 0; /* True if tokenizer locale must be reset */
+ int nText = 0; /* Size of pText in bytes */
+ const char *pText = 0; /* Pointer to buffer containing text value */
+ sqlite3_value *pVal = sqlite3_column_value(pScan, ctx.iCol+1);
+ rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &pText, &nText);
if( rc==SQLITE_OK ){
rc = sqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
sqlite3_value *pVal = apVal[i];
if( sqlite3_value_nochange(pVal) && p->pSavedRow ){
+ /* This is an UPDATE statement, and column (i-2) was not modified.
+ ** Retrieve the value from Fts5Storage.pSavedRow instead. */
pVal = sqlite3_column_value(p->pSavedRow, i-1);
}else if( sqlite3_value_subtype(pVal)==FTS5_LOCALE_SUBTYPE ){
if( pConfig->bLocale==0 ){
&& sqlite3_value_type(pVal)==SQLITE_BLOB
&& i>=2 && pConfig->abUnindexed[i-2]==0
){
- /* Inserting a blob into a normal content table with locale=1. */
+ /* Inserting a blob into a normal content table with locale=1.
+ ** Add the 4 0x00 byte header. */
int n = sqlite3_value_bytes(pVal);
u8 *pBlob = sqlite3Fts5MallocZero(&rc, n+4);
if( pBlob ){
for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
ctx.szCol = 0;
if( pConfig->abUnindexed[ctx.iCol]==0 ){
- int bReset = 0;
- int nText = 0;
- const char *pText = 0;
+ int bReset = 0; /* True if tokenizer locale must be reset */
+ int nText = 0; /* Size of pText in bytes */
+ const char *pText = 0; /* Pointer to buffer containing text value */
sqlite3_value *pVal = apVal[ctx.iCol+2];
int bDisk = 0;
if( p->pSavedRow && sqlite3_value_nochange(pVal) ){
pVal = sqlite3_column_value(p->pSavedRow, ctx.iCol+1);
bDisk = 1;
}
- rc = sqlite3Fts5ExtractText(pConfig, pVal, bDisk, &bReset,&pText,&nText);
+ rc = sqlite3Fts5ExtractText(pConfig, pVal, bDisk, &bReset, &pText,&nText);
if( rc==SQLITE_OK ){
- if( bReset && pConfig->bLocale==0 ){
- rc = SQLITE_ERROR;
- sqlite3Fts5ConfigErrmsg(pConfig,
- "fts5_locale() may not be used without locale=1"
- );
- }else{
- rc = sqlite3Fts5Tokenize(pConfig,
- FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx,
- fts5StorageInsertCallback
- );
- }
+ assert( bReset==0 || pConfig->bLocale );
+ rc = sqlite3Fts5Tokenize(pConfig,
+ FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx,
+ fts5StorageInsertCallback
+ );
if( bReset ) sqlite3Fts5ClearLocale(pConfig);
}
}
rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
}
if( rc==SQLITE_OK ){
- const char *pText = 0;
- int nText = 0;
- int bReset = 0;
+ int bReset = 0; /* True if tokenizer locale must be reset */
+ int nText = 0; /* Size of pText in bytes */
+ const char *pText = 0; /* Pointer to buffer containing text value */
rc = sqlite3Fts5ExtractText(pConfig,
sqlite3_column_value(pScan, i+1), 1, &bReset, &pText, &nText
);
-
if( rc==SQLITE_OK ){
rc = sqlite3Fts5Tokenize(pConfig,
FTS5_TOKENIZE_DOCUMENT,
-C Fix\svarious\sproblems\swith\sthe\scode\son\sthis\sbranch.
-D 2024-07-31T20:49:00.656
+C Clarify\sthe\srole\sof\sFts5Storage.pSavedRow\sin\sthe\snew\sfeature\son\sthis\sbranch.
+D 2024-08-01T17:15:17.507
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1
F ext/fts5/fts5_index.c eb9a0dda3bc6ef969a6be8d2746af56856e67251810ddba08622b45be8477abe
F ext/fts5/fts5_main.c bda8d421024191376343a571370a7b2f92fdf5303e32c6dc0d7456bef9ffedd8
-F ext/fts5/fts5_storage.c f94b924db1bc164af3feadcc3f08f0d8f5da5cd45e4909313637aeee85d0d13c
+F ext/fts5/fts5_storage.c 784c6c4edf676c831ffbaef7b0484dcafe925d3d39049b8ee6b6eeaadae1b042
F ext/fts5/fts5_tcl.c 93b705cb87633574983161edc5234f9b91ba03f9fecfbd2c5d401a1da6f93aa5
F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P fa0da3b28e411affd45a918d6b7faba49f744ca8b4adf3cd5ce6609bb630499e
-R 4281c2d6e2e80158745c4320fdfed09d
+P 8bd4ae7e95c7b6ce34db5ea705dc136e742a22f333d0e7370b485ebd736b5ec2
+R 65a2f69590c259e395cd85b8f6d4acad
U dan
-Z 7ae6a6ddbc4056daf1c0903e2b7934cf
+Z cbe99cb860ae582ece42e3b6610aa855
# Remove this line to create a well-formed Fossil manifest.