]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow UPDATEs of unindexed columns in fts5 contentless_unindexed=1 tables. Testing...
authordan <Dan Kennedy>
Fri, 27 Sep 2024 10:57:41 +0000 (10:57 +0000)
committerdan <Dan Kennedy>
Fri, 27 Sep 2024 10:57:41 +0000 (10:57 +0000)
FossilOrigin-Name: cd36d66c88d7282eb0a3ccde5713253f72f5843e451b2693b71adfdae28b41fb

ext/fts5/fts5Int.h
ext/fts5/fts5_main.c
ext/fts5/fts5_storage.c
ext/fts5/test/fts5unindexed2.test
manifest
manifest.uuid

index f189edf312c95a47879a32a52e7816f3d6f14a99..51d808177408d600755989bc0549edb5cc16f7e9 100644 (file)
@@ -726,7 +726,7 @@ int sqlite3Fts5DropAll(Fts5Config*);
 int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **);
 
 int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**, int);
-int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*);
+int sqlite3Fts5StorageContentInsert(Fts5Storage *p, int, sqlite3_value**, i64*);
 int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64);
 
 int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg);
index 781a8a8719f8321408e904c15eaeb3cdb242eb64..ff8b76694bb6578451f98cce7115543b1be45f17 100644 (file)
@@ -1801,7 +1801,7 @@ static void fts5StorageInsert(
 ){
   int rc = *pRc;
   if( rc==SQLITE_OK ){
-    rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid);
+    rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, 0, apVal, piRowid);
   }
   if( rc==SQLITE_OK ){
     rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid);
@@ -1809,6 +1809,45 @@ static void fts5StorageInsert(
   *pRc = rc;
 }
 
+static int fts5ContentlessUpdateOk(
+  Fts5Config *pConfig,
+  sqlite3_value **apVal,
+  i64 iOld,
+  i64 iNew,
+  int *pbContent
+){
+  int ii;
+  int bSeenIndex = 0;
+  int bSeenIndexNC = 0;
+  int rc = SQLITE_OK;
+
+  for(ii=0; ii<pConfig->nCol; ii++){
+    if( pConfig->abUnindexed[ii]==0 ){
+      if( sqlite3_value_nochange(apVal[ii]) ){
+        bSeenIndexNC++;
+      }else{
+        bSeenIndex++;
+      }
+    }
+  }
+
+  if( bSeenIndex==0 && iOld==iNew ){
+    *pbContent = 1;
+  }else{
+    if( bSeenIndexNC || pConfig->bContentlessDelete==0 ){
+      rc = SQLITE_ERROR;
+      sqlite3Fts5ConfigErrmsg(pConfig, 
+          (pConfig->bContentlessDelete ?
+          "%s a subset of columns on fts5 contentless-delete table: %s" :
+          "%s contentless fts5 table: %s")
+          , "cannot UPDATE", pConfig->zName
+      );
+    }
+  }
+
+  return 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
@@ -1895,25 +1934,35 @@ static int fts5UpdateMethod(
     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. Except - they are both supported if the CREATE
-    ** VIRTUAL TABLE statement contained "contentless_delete=1". */
-    if( eType0==SQLITE_INTEGER 
-     && fts5IsContentless(pTab, 1)
-     && pConfig->bContentlessDelete==0
-    ){
-      pTab->p.base.zErrMsg = sqlite3_mprintf(
-          "cannot %s contentless fts5 table: %s", 
-          (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName
-      );
-      rc = SQLITE_ERROR;
-    }
+    /*
+    ** Extra rule for contentless tables:
+    **
+    ** DELETE:
+    **   It is only possible to DELETE if the contentless_delete=1 flag
+    **   is set.
+    **
+    ** UPDATE:
+    **   A "content-only" UPDATE is one that only affects UNINDEXED
+    **   columns. And that does not modify the rowid value.
+    **
+    **   If the contentless_delete flag is clear, then content-only UPDATEs
+    **   are the only ones supported. Or, if contentless_delete=1 is set,
+    **   then updates that modify all indexed columns are also supported.
+    **   If all indexed columns are updated, then rowid updates are allowed.
+    */
 
     /* DELETE */
-    else if( nArg==1 ){
-      i64 iDel = sqlite3_value_int64(apVal[0]);  /* Rowid to delete */
-      rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0, 0);
-      bUpdateOrDelete = 1;
+    if( nArg==1 ){
+      if( fts5IsContentless(pTab, 1) && pConfig->bContentlessDelete==0 ){
+        fts5SetVtabError(pTab, 
+            "cannot DELETE from contentless fts5 table: %s", pConfig->zName
+        );
+        rc = SQLITE_ERROR;
+      }else{
+        i64 iDel = sqlite3_value_int64(apVal[0]);  /* Rowid to delete */
+        rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0, 0);
+        bUpdateOrDelete = 1;
+      }
     }
 
     /* INSERT or UPDATE */
@@ -1947,35 +1996,49 @@ static int fts5UpdateMethod(
 
       /* UPDATE */
       else{
+        Fts5Storage *pStorage = pTab->pStorage;
         i64 iOld = sqlite3_value_int64(apVal[0]);  /* Old rowid */
         i64 iNew = sqlite3_value_int64(apVal[1]);  /* New rowid */
+        int bContent = 0;         /* Content only update */
+
+        if( fts5IsContentless(pTab, 1) ){
+          rc = fts5ContentlessUpdateOk(pConfig,&apVal[2],iOld,iNew,&bContent);
+          if( rc!=SQLITE_OK ) goto update_out;
+        }
+
         if( eType1!=SQLITE_INTEGER ){
           rc = SQLITE_MISMATCH;
         }else if( iOld!=iNew ){
+          assert( bContent==0 );
           if( eConflict==SQLITE_REPLACE ){
-            rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0, 1);
+            rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 1);
             if( rc==SQLITE_OK ){
-              rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0, 0);
+              rc = sqlite3Fts5StorageDelete(pStorage, iNew, 0, 0);
             }
             fts5StorageInsert(&rc, pTab, apVal, pRowid);
           }else{
-            rc = sqlite3Fts5StorageFindDeleteRow(pTab->pStorage, iOld);
+            rc = sqlite3Fts5StorageFindDeleteRow(pStorage, iOld);
             if( rc==SQLITE_OK ){
-              rc = sqlite3Fts5StorageContentInsert(pTab->pStorage,apVal,pRowid);
+              rc = sqlite3Fts5StorageContentInsert(pStorage, 0, apVal, pRowid);
             }
             if( rc==SQLITE_OK ){
-              rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0, 1);
+              rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 1);
             }
             if( rc==SQLITE_OK ){
-              rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid);
+              rc = sqlite3Fts5StorageIndexInsert(pStorage, apVal,*pRowid);
             }
           }
+        }else if( bContent ){
+          rc = sqlite3Fts5StorageFindDeleteRow(pStorage, iOld);
+          if( rc==SQLITE_OK ){
+            rc = sqlite3Fts5StorageContentInsert(pStorage, 1, apVal, pRowid);
+          }
         }else{
-          rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0, 1);
+          rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 1);
           fts5StorageInsert(&rc, pTab, apVal, pRowid);
         }
         bUpdateOrDelete = 1;
-        sqlite3Fts5StorageReleaseDeleteRow(pTab->pStorage);
+        sqlite3Fts5StorageReleaseDeleteRow(pStorage);
       }
 
     }
@@ -2968,18 +3031,7 @@ static int fts5ColumnMethod(
       }
     }
   }else{
-    /* A column created by the user containing values. */
-    int bNochange = sqlite3_vtab_nochange(pCtx);
-
-    if( bNochange ){
-      if( pConfig->bContentlessDelete 
-       && (pConfig->eContent==FTS5_CONTENT_NONE || !pConfig->abUnindexed[iCol])
-      ){
-        fts5ResultError(pCtx, "cannot UPDATE a subset of "
-            "columns on fts5 contentless-delete table: %s", pConfig->zName
-        );
-      }
-    }else if( pConfig->eContent!=FTS5_CONTENT_NONE ){
+    if( !sqlite3_vtab_nochange(pCtx) && pConfig->eContent!=FTS5_CONTENT_NONE ){
       pConfig->pzErrmsg = &pTab->p.base.zErrMsg;
       rc = fts5SeekCursor(pCsr, 1);
       if( rc==SQLITE_OK ){
index ee65b0f7ee47fa48efa48e9af68000f3c3722e29..7f149eee8a62309f449eb9083124b3036de23758 100644 (file)
@@ -141,7 +141,8 @@ static int fts5StorageGetStmt(
         );
         break;
 
-      case FTS5_STMT_INSERT_CONTENT: {
+      case FTS5_STMT_INSERT_CONTENT: 
+      case FTS5_STMT_REPLACE_CONTENT: {
         char *zBind = 0;
         int i;
 
@@ -930,6 +931,7 @@ static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){
 */
 int sqlite3Fts5StorageContentInsert(
   Fts5Storage *p, 
+  int bReplace,                   /* True to use REPLACE instead of INSERT */
   sqlite3_value **apVal, 
   i64 *piRowid
 ){
@@ -948,7 +950,10 @@ int sqlite3Fts5StorageContentInsert(
   }else{
     sqlite3_stmt *pInsert = 0;    /* Statement to write %_content table */
     int i;                        /* Counter variable */
-    rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
+
+    assert( FTS5_STMT_INSERT_CONTENT+1==FTS5_STMT_REPLACE_CONTENT );
+    assert( bReplace==0 || bReplace==1 );
+    rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT+bReplace, &pInsert, 0);
     if( pInsert ) sqlite3_clear_bindings(pInsert);
 
     /* Bind the rowid value */
index f2703ac374841dd73cede3787d4124669e83d781..c0abfc39801e3690067b178b3ebee37ed2184445 100644 (file)
@@ -255,5 +255,43 @@ do_execsql_test 5.6 {
   {{} two {}}
 }
 
+#-------------------------------------------------------------------------
+# Check that it is possible to UPDATE a contentless_unindexed=1 table
+# if the only columns being modified are UNINDEXED.
+#
+# If the contentless_unindexed=1 table is also contentless_delete=1, then
+# it is also possible to update indexed columns - but only if *all* indexed
+# columns are updated.
+#
+reset_db
+do_execsql_test 6.0 {
+  CREATE VIRTUAL TABLE ft1 USING fts5(a, b UNINDEXED, c UNINDEXED, d,
+      contentless_unindexed=1, content=''
+  );
+
+  INSERT INTO ft1(rowid, a, b, c, d) VALUES
+      (100, 'x y', 'b1', 'c1', 'a b'),
+      (200, 'c d', 'b2', 'c2', 'a b'),
+      (300, 'e f', 'b3', 'c3', 'a b');
+}
+
+do_execsql_test 6.1 {
+  UPDATE ft1 SET b='b1.1', c='c1.1' WHERE rowid=100;
+}
+do_execsql_test 6.2 {
+  UPDATE ft1 SET b='b2.1' WHERE rowid=200;
+}
+do_execsql_test 6.3 {
+  UPDATE ft1 SET c='c3.1' WHERE rowid=300;
+}
+
+do_execsql_test 6.4 {
+  SELECT rowid, a, b, c, d FROM ft1
+} {
+  100 {} b1.1 c1.1 {}
+  200 {} b2.1 c2 {}
+  300 {} b3 c3.1 {}
+}
+
 finish_test
 
index c9871be60a0e3921e5917c7d709a4941300d2224..0daa473be97ac2a3b1f9214e6add1d96e79c66bc 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Prevent\sregular\sDELETE\sand\sUPDATE\sstatements\sfrom\srunning\sagainst\scontentless_unindexed=1\stables\sthat\sare\snot\salso\scontentless_delete=1.
-D 2024-09-25T12:03:08.340
+C Allow\sUPDATEs\sof\sunindexed\scolumns\sin\sfts5\scontentless_unindexed=1\stables.\sTesting\sto\scome.
+D 2024-09-27T10:57:41.777
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -93,15 +93,15 @@ F ext/fts3/unicode/mkunicode.tcl 63db9624ccf70d4887836c320eda93ab552f21008f3be7e
 F ext/fts3/unicode/parseunicode.tcl a981bd6466d12dd17967515801c3ff23f74a281be1a03cf1e6f52a6959fc77eb
 F ext/fts5/extract_api_docs.tcl 009cf59c77afa86d137b0cca3e3b1a5efbe2264faa2df233f9a7aa8563926d15
 F ext/fts5/fts5.h efaaac0df3d3bc740383044c144b582f47921aafa21d7b10eb98f42c24c740b0
-F ext/fts5/fts5Int.h 927772e795bc897a210630296531c6a397b247f0b6f65ef9e9dc8e03baa77d1d
+F ext/fts5/fts5Int.h bf0d3efa144f36e00f9b5206626aec2f436f58186a0835092394f2202e9828e3
 F ext/fts5/fts5_aux.c 65a0468dd177d6093aa9ae1622e6d86b0136b8d267c62c0ad6493ad1e9a3d759
 F ext/fts5/fts5_buffer.c 0eec58bff585f1a44ea9147eae5da2447292080ea435957f7488c70673cb6f09
 F ext/fts5/fts5_config.c a6633d88596758941c625b526075b85d3d9fd1089d8d9eab5db6e8a71fd347ad
 F ext/fts5/fts5_expr.c 9a56f53700d1860f0ee2f373c2b9074eaf2a7aa0637d0e27a6476de26a3fee33
 F ext/fts5/fts5_hash.c adda4272be401566a6e0ba1acbe70ee5cb97fce944bc2e04dc707152a0ec91b1
 F ext/fts5/fts5_index.c 571483823193f09439356741669aa8c81da838ae6f5e1bfa7517f7ee2fb3addd
-F ext/fts5/fts5_main.c 0d2013bd03285f4bbcea0f4594f2cd33b68337da709da79cd46379be189d11da
-F ext/fts5/fts5_storage.c 1fbaf212042bdb363d74a48d3293b2c453a46880e86656df3ee19ade63472681
+F ext/fts5/fts5_main.c 853d44c804b6d842f0197002a03ecf482b721834b0bf3d2f1ab3a21e4fd13df5
+F ext/fts5/fts5_storage.c 3b5d743e97502263961e706aedd1000c394ee9df7942ba6671ce4bc41fcf9993
 F ext/fts5/fts5_tcl.c 4db9258a7882c5eac0da4433042132aaf15b87dd1e1636c7a6ca203abd2c8bfe
 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
 F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b
@@ -246,7 +246,7 @@ F ext/fts5/test/fts5unicode2.test 3bbd30152f9f760bf13886e5b1e5ec23ff62f56758ddda
 F ext/fts5/test/fts5unicode3.test f4891a3dac3b49c3d7c0fdb29566e9eb0ecff35263370c89f9661b1952b20818
 F ext/fts5/test/fts5unicode4.test 728c8f0caafb05567f524ad313d9f8b780fa45987b8a8df04eff87923c74b4d0
 F ext/fts5/test/fts5unindexed.test 168838d2c385e131120bbf5b516d2432a5fabc4caa2259c932e1d49ae209a4ae
-F ext/fts5/test/fts5unindexed2.test 54a924b8acc6270350898e09d56c1b942b6e3cae789b9e5f31ec7b9a3dc7953e
+F ext/fts5/test/fts5unindexed2.test 516236eceaac05ace322290a0d3705b4c4ffe4760d8eb9d014d9d27d56dfcc02
 F ext/fts5/test/fts5update.test b8affd796e45c94a4d19ad5c26606ea06065a0f162a9562d9f005b5a80ccf0bc
 F ext/fts5/test/fts5version.test c22d163c17e60a99f022cbc52de5a48bb7f84deaa00fe15e9bc4c3aa1996204e
 F ext/fts5/test/fts5vocab.test 2a2bdb60d0998fa3124d541b6d30b019504918dc43a6584645b63a24be72f992
@@ -2213,8 +2213,8 @@ F vsixtest/vsixtest.tcl 6195aba1f12a5e10efc2b8c0009532167be5e301abe5b31385638080
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P c51dc2a5e75baacbd905cf314e7b1a58a81993ff05ca656739e028d7db25d5b2
-R 0573b4e17b15d974d21966942a753c69
+P 21539e9d0d57fdc762affbce9220d1bb1ca009d9dc751b4ccfe63eecbbe2f575
+R de377aee83cef1249306ac8b9c4a8126
 U dan
-Z 19e56c0d30986f9b0323b85fbd6aa264
+Z cfbbba283553b2d6b64e253eb6c401a0
 # Remove this line to create a well-formed Fossil manifest.
index 957a7db2aa30a722ae0ba9dd06d924c91eda6301..28116b337f366aedf71f0e9e23b825b05f9e5e6c 100644 (file)
@@ -1 +1 @@
-21539e9d0d57fdc762affbce9220d1bb1ca009d9dc751b4ccfe63eecbbe2f575
+cd36d66c88d7282eb0a3ccde5713253f72f5843e451b2693b71adfdae28b41fb