]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Alternative implementation of fts5 locale=1 feature that allows blobs to be stored...
authordan <Dan Kennedy>
Tue, 10 Sep 2024 16:19:31 +0000 (16:19 +0000)
committerdan <Dan Kennedy>
Tue, 10 Sep 2024 16:19:31 +0000 (16:19 +0000)
FossilOrigin-Name: 55c5c119a0a77fac2c9f46d718ef78c0f33ed3520e10c240cf5bf1801e0586ee

ext/fts5/fts5Int.h
ext/fts5/fts5_config.c
ext/fts5/fts5_main.c
ext/fts5/fts5_storage.c
ext/fts5/test/fts5blob.test
ext/fts5/test/fts5faultI.test
ext/fts5/test/fts5locale.test
ext/fts5/test/fts5simple.test
manifest
manifest.uuid

index 0b8851d227745d2ed26e686c14bed0d94ffd5e6d..b15521f163129d27b8f7cad32b54f04b2e4af2f7 100644 (file)
@@ -636,18 +636,13 @@ Fts5Table *sqlite3Fts5TableFromCsrid(Fts5Global*, i64);
 
 int sqlite3Fts5FlushToDisk(Fts5Table*);
 
-int sqlite3Fts5ExtractText(
-  Fts5Config *pConfig,
-  sqlite3_value *pVal,            /* Value to extract text from */
-  int bContent,                   /* Loaded from content table */
-  int *pbResetTokenizer,          /* OUT: True if ClearLocale() required */
-  const char **ppText,            /* OUT: Pointer to text buffer */
-  int *pnText                     /* OUT: Size of (*ppText) in bytes */
-);
-
 void sqlite3Fts5ClearLocale(Fts5Config *pConfig);
+void sqlite3Fts5SetLocale(Fts5Config *pConfig, const char *pLoc, int nLoc);
 
 int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal);
+int sqlite3Fts5DecodeLocaleValue(sqlite3_value *pVal, 
+    const char **ppText, int *pnText, const char **ppLoc, int *pnLoc
+);
 
 /*
 ** End of interface to code in fts5.c.
index 3cb1bd3bea89a87839c08b594e6d8b62532513df..1fade5f7a821c00e67da8a41be90a5a5575e9807 100644 (file)
@@ -516,6 +516,15 @@ static int fts5ConfigMakeExprlist(Fts5Config *p){
       }
     }
   }
+  if( p->eContent==FTS5_CONTENT_NORMAL && p->bLocale ){
+    for(i=0; i<p->nCol; i++){
+      if( p->abUnindexed[i]==0 ){
+        sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.l%d", i);
+      }else{
+        sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", NULL");
+      }
+    }
+  }
 
   assert( p->zContentExprlist==0 );
   p->zContentExprlist = (char*)buf.p;
index 6ccca8a3ee31d95b7a994f1f554c33accb96d7a7..b4922a8b6fd92a2218f17a5c7169becbf6170c22 100644 (file)
@@ -1259,7 +1259,7 @@ static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){
 ** valid until after the final call to sqlite3Fts5Tokenize() that will use
 ** the locale.
 */
-static void fts5SetLocale(
+static void sqlite3Fts5SetLocale(
   Fts5Config *pConfig, 
   const char *zLocale, 
   int nLocale
@@ -1270,11 +1270,10 @@ static void fts5SetLocale(
 }
 
 /*
-** Clear any locale configured by an earlier call to fts5SetLocale() or
-** sqlite3Fts5ExtractText().
+** Clear any locale configured by an earlier call to sqlite3Fts5SetLocale().
 */
 void sqlite3Fts5ClearLocale(Fts5Config *pConfig){
-  fts5SetLocale(pConfig, 0, 0);
+  sqlite3Fts5SetLocale(pConfig, 0, 0);
 }
 
 /*
@@ -1302,118 +1301,33 @@ int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal){
 }
 
 /*
-** This function is used to extract utf-8 text from an sqlite3_value. This
-** is usually done in order to tokenize it. For example, when:
-**
-**     * a value is written to an fts5 table,
-**     * a value is deleted from an FTS5_CONTENT_NORMAL table,
-**     * a value containing a query expression is passed to xFilter()
-**
-** and so on.
-**
-** This function handles 2 cases:
-**
-**   1) Ordinary values. The text can be extracted from these using
-**      sqlite3_value_text().
-**
-**   2) Combination text/locale blobs created by fts5_locale(). There
-**      are several cases for these:
-**
-**        * Blobs that have the 16-byte header, and
-**        * Blobs read from the content table of a locale=1 regular 
-**          content table.
-**
-**      The first case above has the 16 byte FTS5_LOCALE_HDR(pConfig)
-**      header. It is an error if a blob read from the content table of 
-**      an external content table does not have the required header. A blob 
-**      read from the content table of a regular locale=1 table does not 
-**      have the header. This is to save space.
-**
-** If successful, SQLITE_OK is returned and output parameters (*ppText)
-** and (*pnText) are set to point to a buffer containing the extracted utf-8 
-** text and its length in bytes, respectively. The buffer is not 
-** nul-terminated. It has the same lifetime as the sqlite3_value object
-** from which it is extracted.
-**
-** Parameter bContent must be true if the value was read from an indexed
-** column (i.e. not UNINDEXED) of the on disk content. 
-**
-** If pbResetTokenizer is not NULL and if case (2) is used, then 
-** fts5SetLocale() is called to ensure subsequent sqlite3Fts5Tokenize() calls
-** use the locale. In this case (*pbResetTokenizer) is set to true before
-** returning, to indicate that the caller must call sqlite3Fts5ClearLocale() 
-** to clear the locale after tokenizing the text.
+** Value pVal is guaranteed to be an fts5_locale() value.
 */
-int sqlite3Fts5ExtractText(
-  Fts5Config *pConfig,
-  sqlite3_value *pVal,            /* Value to extract text from */
-  int bContent,                   /* True if indexed table content */
-  int *pbResetTokenizer,          /* OUT: True if xSetLocale(NULL) required */
-  const char **ppText,            /* OUT: Pointer to text buffer */
-  int *pnText                     /* OUT: Size of (*ppText) in bytes */
+int sqlite3Fts5DecodeLocaleValue(
+  sqlite3_value *pVal, 
+  const char **ppText,
+  int *pnText, 
+  const char **ppLoc, 
+  int *pnLoc
 ){
-  const char *pText = 0;
-  int nText = 0;
-  int rc = SQLITE_OK;
-
-  /* 0: Do not decode blob 
-  ** 1: Decode blob, expect fts5_locale() header
-  ** 2: Decode blob, expect no fts5_locale() header
-  */
-  int eDecodeBlob = 0;
-
-  assert( pbResetTokenizer==0 || *pbResetTokenizer==0 );
-  assert( bContent==0 || pConfig->eContent!=FTS5_CONTENT_NONE );
-
-  if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
-    if( bContent 
-     && pConfig->bLocale 
-     && pConfig->eContent==FTS5_CONTENT_NORMAL 
-    ){
-      eDecodeBlob = 2;
-    }else if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
-      eDecodeBlob = 1;
-    }else if( bContent && pConfig->bLocale ){
-      return SQLITE_ERROR;
-    }
-  }
-
-  if( eDecodeBlob ){
-    const u8 *pBlob = sqlite3_value_blob(pVal);
-    int nBlob = sqlite3_value_bytes(pVal);
-    int nLocale = 0;
-
-    /* Unless this blob was read from the %_content table of an 
-    ** FTS5_CONTENT_NORMAL table, it should have the 4 byte fts5_locale() 
-    ** header. Check for this. If it is not found, return an error.  */
-    if( eDecodeBlob==1 ){
-      pBlob += FTS5_LOCALE_HDR_SIZE;
-      nBlob -= FTS5_LOCALE_HDR_SIZE;
-    }
+  const char *p = sqlite3_value_blob(pVal);
+  int n = sqlite3_value_bytes(pVal);
+  int nLoc = 0;
 
-    for(nLocale=0; nLocale<nBlob; nLocale++){
-      if( pBlob[nLocale]==0x00 ) break;
-    }
-    if( nLocale==nBlob || nLocale==0 ){
-      rc = SQLITE_ERROR;
-    }else{
-      pText = (const char*)&pBlob[nLocale+1];
-      nText = nBlob-nLocale-1;
+  assert( sqlite3_value_type(pVal)==SQLITE_BLOB );
+  assert( n>FTS5_LOCALE_HDR_SIZE );
 
-      if( pbResetTokenizer ){
-        fts5SetLocale(pConfig, (const char*)pBlob, nLocale);
-        *pbResetTokenizer = 1;
-      }
+  for(nLoc=FTS5_LOCALE_HDR_SIZE; p[nLoc]; nLoc++){
+    if( nLoc==(n-1) ){
+      return SQLITE_MISMATCH;
     }
-
-  }else{
-    pText = (const char*)sqlite3_value_text(pVal);
-    nText = sqlite3_value_bytes(pVal);
   }
+  *ppLoc = &p[FTS5_LOCALE_HDR_SIZE];
+  *pnLoc = nLoc - FTS5_LOCALE_HDR_SIZE;
 
-  *ppText = pText;
-  *pnText = nText;
-  return rc;
+  *ppText = &p[nLoc+1];
+  *pnText = n - nLoc - 1;
+  return SQLITE_OK;
 }
 
 /*
@@ -1422,8 +1336,8 @@ int sqlite3Fts5ExtractText(
 ** the text of the expression, and sets output variable (*pzText) to 
 ** point to a nul-terminated buffer containing the expression.
 **
-** If pVal was an fts5_locale() value, then fts5SetLocale() is called to
-** set the tokenizer to use the specified locale.
+** If pVal was an fts5_locale() value, then sqlite3Fts5SetLocale() is called 
+** to set the tokenizer to use the specified locale.
 **
 ** If output variable (*pbFreeAndReset) is set to true, then the caller
 ** is required to (a) call sqlite3Fts5ClearLocale() to reset the tokenizer 
@@ -1435,24 +1349,22 @@ static int fts5ExtractExprText(
   char **pzText,                  /* OUT: nul-terminated buffer of text */
   int *pbFreeAndReset             /* OUT: Free (*pzText) and clear locale */
 ){
-  const char *zText = 0;
-  int nText = 0;
   int rc = SQLITE_OK;
-  int bReset = 0;
 
-  *pbFreeAndReset = 0;
-  rc = sqlite3Fts5ExtractText(pConfig, pVal, 0, &bReset, &zText, &nText);
-  if( rc==SQLITE_OK ){
-    if( bReset ){
-      *pzText = sqlite3Fts5Mprintf(&rc, "%.*s", nText, zText);
-      if( rc!=SQLITE_OK ){
-        sqlite3Fts5ClearLocale(pConfig);
-      }else{
-        *pbFreeAndReset = 1;
-      }
-    }else{
-      *pzText = (char*)zText;
+  if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
+    const char *pText = 0;
+    int nText = 0;
+    const char *pLoc = 0;
+    int nLoc = 0;
+    rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc);
+    *pzText = sqlite3Fts5Mprintf(&rc, "%.*s", nText, pText);
+    if( rc==SQLITE_OK ){
+      sqlite3Fts5SetLocale(pConfig, pLoc, nLoc);
     }
+    *pbFreeAndReset = 1;
+  }else{
+    *pzText = (char*)sqlite3_value_text(pVal);
+    *pbFreeAndReset = 0;
   }
 
   return rc;
@@ -1992,22 +1904,16 @@ static int fts5UpdateMethod(
     else{
       int eType1 = sqlite3_value_numeric_type(apVal[1]);
 
-      int ii;
-      for(ii=0; ii<pConfig->nCol; ii++){
-        sqlite3_value *pVal = apVal[ii+2];
-        if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
-          int isLocale = sqlite3Fts5IsLocaleValue(pConfig, pVal);
-          if( pConfig->bLocale ){
-            if( isLocale==0 && pConfig->abUnindexed[ii]==0 ){
-              rc = SQLITE_MISMATCH;
-              goto update_out;
-            }
-          }else{
-            if( isLocale ){
-              fts5SetVtabError(pTab, "fts5_locale() requires locale=1");
-              rc = SQLITE_MISMATCH;
-              goto update_out;
-            }
+      /* It is an error to write an fts5_locale() value to a table without
+      ** the locale=1 option. */
+      if( pConfig->bLocale==0 ){
+        int ii;
+        for(ii=0; ii<pConfig->nCol; ii++){
+          sqlite3_value *pVal = apVal[ii+2];
+          if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
+            fts5SetVtabError(pTab, "fts5_locale() requires locale=1");
+            rc = SQLITE_MISMATCH;
+            goto update_out;
           }
         }
       }
@@ -2166,11 +2072,11 @@ static int fts5ApiTokenize_v2(
   Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab);
   int rc = SQLITE_OK;
 
-  fts5SetLocale(pTab->pConfig, pLoc, nLoc);
+  sqlite3Fts5SetLocale(pTab->pConfig, pLoc, nLoc);
   rc = sqlite3Fts5Tokenize(pTab->pConfig, 
       FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken
   );
-  fts5SetLocale(pTab->pConfig, 0, 0);
+  sqlite3Fts5SetLocale(pTab->pConfig, 0, 0);
 
   return rc;
 }
@@ -2198,6 +2104,38 @@ static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){
   return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase);
 }
 
+/*
+** Argument pStmt is an SQL statement of the type used by Fts5Cursor.
+*/
+static int fts5TextFromStmt(
+  Fts5Config *pConfig,
+  sqlite3_stmt *pStmt,
+  int iCol,
+  const char **ppText,
+  int *pnText
+){
+  sqlite3_value *pVal = sqlite3_column_value(pStmt, iCol+1);
+  const char *pLoc = 0;
+  int nLoc = 0;
+  int rc = SQLITE_OK;
+
+  if( pConfig->bLocale 
+   && pConfig->eContent==FTS5_CONTENT_EXTERNAL
+   && sqlite3Fts5IsLocaleValue(pConfig, pVal) 
+  ){
+    rc = sqlite3Fts5DecodeLocaleValue(pVal, ppText, pnText, &pLoc, &nLoc);
+  }else{
+    *ppText = (const char*)sqlite3_value_text(pVal);
+    *pnText = sqlite3_value_bytes(pVal);
+    if( pConfig->bLocale && pConfig->eContent==FTS5_CONTENT_NORMAL ){
+      pLoc = (const char*)sqlite3_column_text(pStmt, iCol+1+pConfig->nCol);
+      nLoc = sqlite3_column_bytes(pStmt, iCol+1+pConfig->nCol);
+    }
+  }
+  sqlite3Fts5SetLocale(pConfig, pLoc, nLoc);
+  return rc;
+}
+
 static int fts5ApiColumnText(
   Fts5Context *pCtx, 
   int iCol, 
@@ -2217,10 +2155,8 @@ static int fts5ApiColumnText(
   }else{
     rc = fts5SeekCursor(pCsr, 0);
     if( rc==SQLITE_OK ){
-      Fts5Config *pConfig = pTab->pConfig;
-      int bContent = (pConfig->abUnindexed[iCol]==0);
-      sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
-      sqlite3Fts5ExtractText(pConfig, pVal, bContent, 0, pz, pn);
+      rc = fts5TextFromStmt(pTab->pConfig, pCsr->pStmt, iCol, pz, pn);
+      sqlite3Fts5ClearLocale(pTab->pConfig);
     }
   }
   return rc;
@@ -2262,17 +2198,15 @@ static int fts5CsrPoslist(
         rc = fts5SeekCursor(pCsr, 0);
       }
       for(i=0; i<pConfig->nCol && rc==SQLITE_OK; i++){
-        sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, i+1);
         const char *z = 0;
         int n = 0; 
-        int bReset = 0;
-        rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &z, &n);
+        rc = fts5TextFromStmt(pConfig, pCsr->pStmt, i, &z, &n);
         if( rc==SQLITE_OK ){
           rc = sqlite3Fts5ExprPopulatePoslists(
               pConfig, pCsr->pExpr, aPopulator, i, z, n
           );
         }
-        if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+        sqlite3Fts5ClearLocale(pConfig);
       }
       sqlite3_free(aPopulator);
 
@@ -2458,17 +2392,14 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){
         if( pConfig->abUnindexed[i]==0 ){
           const char *z = 0; 
           int n = 0;
-          int bReset = 0;
-          sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, i+1);
-
           pCsr->aColumnSize[i] = 0;
-          rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &z, &n);
+          rc = fts5TextFromStmt(pConfig, pCsr->pStmt, i, &z, &n);
           if( rc==SQLITE_OK ){
             rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_AUX, 
                 z, n, (void*)&pCsr->aColumnSize[i], fts5ColumnSizeCb
             );
-            if( bReset ) sqlite3Fts5ClearLocale(pConfig);
           }
+          sqlite3Fts5ClearLocale(pConfig);
         }
       }
     }
@@ -2740,37 +2671,14 @@ static int fts5ApiColumnLocale(
   ){
     rc = fts5SeekCursor(pCsr, 0);
     if( rc==SQLITE_OK ){
-      /* Load the value into pVal. pVal is a locale/text pair iff:
-      **
-      **   1) It is an SQLITE_BLOB, and
-      **   2) Either the FTS5_LOCALE_HDR header is present, or else the
-      **      value was loaded from an FTS5_CONTENT_NORMAL table.
-      **
-      ** If condition (1) is met but condition (2) is not, it is an error.
-      */ 
-      sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
-      if( sqlite3_value_type(pVal)==SQLITE_BLOB ){
-        const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
-        int nBlob = sqlite3_value_bytes(pVal);
-        if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){
-          if( sqlite3Fts5IsLocaleValue(pConfig, pVal)==0 ){
-            rc = SQLITE_ERROR;
-          }
-          pBlob += FTS5_LOCALE_HDR_SIZE;
-          nBlob -= FTS5_LOCALE_HDR_SIZE;
-        }
-        if( rc==SQLITE_OK ){
-          int nLocale = 0;
-          for(nLocale=0; nLocale<nBlob && pBlob[nLocale]!=0x00; nLocale++);
-          if( nLocale==nBlob || nLocale==0 ){
-            rc = SQLITE_ERROR;
-          }else{
-            /* A locale/text pair */
-            *pzLocale = (const char*)pBlob;
-            *pnLocale = nLocale;
-          }
-        }
+      const char *zDummy = 0;
+      int nDummy = 0;
+      rc = fts5TextFromStmt(pConfig, pCsr->pStmt, iCol, &zDummy, &nDummy);
+      if( rc==SQLITE_OK ){
+        *pzLocale = pConfig->t.pLocale;
+        *pnLocale = pConfig->t.nLocale;
       }
+      sqlite3Fts5ClearLocale(pConfig);
     }
   }
 
@@ -2991,58 +2899,6 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){
   return rc;
 }
 
-/*
-** Value pVal was read from column iCol of the FTS5 table. This function
-** returns it to the owner of pCtx via a call to an sqlite3_result_xxx()
-** function. This function deals with the same cases as 
-** sqlite3Fts5ExtractText():
-**
-**   1) Ordinary values. These can be returned using sqlite3_result_value().
-**
-**   2) Blobs from fts5_locale(). The text is extracted from these and 
-**      returned via sqlite3_result_text(). The locale is discarded.
-*/
-static void fts5ExtractValueFromColumn(
-  sqlite3_context *pCtx,
-  Fts5Config *pConfig, 
-  int iCol,
-  sqlite3_value *pVal
-){
-  assert( pConfig->eContent!=FTS5_CONTENT_NONE );
-
-  if( pConfig->bLocale 
-   && sqlite3_value_type(pVal)==SQLITE_BLOB 
-   && pConfig->abUnindexed[iCol]==0
-  ){
-    const u8 *pBlob = sqlite3_value_blob(pVal);
-    int nBlob = sqlite3_value_bytes(pVal);
-    int ii;
-
-    if( pConfig->eContent==FTS5_CONTENT_EXTERNAL ){
-      if( nBlob<FTS5_LOCALE_HDR_SIZE 
-       || memcmp(pBlob, FTS5_LOCALE_HDR(pConfig), FTS5_LOCALE_HDR_SIZE) 
-      ){
-        sqlite3_result_error_code(pCtx, SQLITE_ERROR);
-        return;
-      }else{
-        pBlob += FTS5_LOCALE_HDR_SIZE;
-        nBlob -= FTS5_LOCALE_HDR_SIZE;
-      }
-    }
-
-    for(ii=0; ii<nBlob && pBlob[ii]; ii++);
-    if( ii==0 || ii==nBlob ){
-      sqlite3_result_error_code(pCtx, SQLITE_ERROR);
-    }else{
-      const char *pText = (const char*)&pBlob[ii+1];
-      sqlite3_result_text(pCtx, pText, nBlob-ii-1, SQLITE_TRANSIENT);
-    }
-    return;
-  }
-
-  sqlite3_result_value(pCtx, pVal);
-}
-
 /* 
 ** This is the xColumn method, called by SQLite to request a value from
 ** the row that the supplied cursor currently points to.
@@ -3099,8 +2955,21 @@ static int fts5ColumnMethod(
       rc = fts5SeekCursor(pCsr, 1);
       if( rc==SQLITE_OK ){
         sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1);
-        fts5ExtractValueFromColumn(pCtx, pConfig, iCol, pVal);
+        if( pConfig->bLocale 
+         && pConfig->eContent==FTS5_CONTENT_EXTERNAL 
+         && sqlite3Fts5IsLocaleValue(pConfig, pVal)
+        ){
+          const char *z = 0;
+          int n = 0;
+          rc = fts5TextFromStmt(pConfig, pCsr->pStmt, iCol, &z, &n);
+          if( rc==SQLITE_OK ){
+            sqlite3_result_text(pCtx, z, n, SQLITE_TRANSIENT);
+          }
+        }else{
+          sqlite3_result_value(pCtx, pVal);
+        }
       }
+
       pConfig->pzErrmsg = 0;
     }
   }
index 3bf20b339087f42666c90203095178e0c8415c4e..e8649c703f581bc13f2d7baa09f5df14bcfd0d18 100644 (file)
@@ -73,6 +73,30 @@ struct Fts5Storage {
 #define FTS5_STMT_REPLACE_CONFIG 10
 #define FTS5_STMT_SCAN           11
 
+/*
+** Return a pointer to a buffer obtained from sqlite3_malloc() that contains
+** nBind comma-separated question marks. e.g. if nBind is passed 5, this 
+** function returns "?,?,?,?,?".
+**
+** If *pRc is not SQLITE_OK when this function is called, it is a no-op and
+** NULL is returned immediately. Or, if the attempt to malloc a buffer
+** fails, then *pRc is set to SQLITE_NOMEM and NULL is returned. Otherwise,
+** if it is SQLITE_OK when this function is called and the malloc() succeeds,
+** *pRc is left unchanged.
+*/
+static char *fts5BindingsList(int *pRc, int nBind){
+  char *zBind = sqlite3Fts5MallocZero(pRc, 1 + nBind*2);
+  if( zBind ){
+    int ii;
+    for(ii=0; ii<nBind; ii++){
+      zBind[ii*2] = '?';
+      zBind[ii*2 + 1] = ',';
+    }
+    zBind[ii*2-1] = '\0';
+  }
+  return zBind;
+}
+
 /*
 ** Prepare the two insert statements - Fts5Storage.pInsertContent and
 ** Fts5Storage.pInsertDocsize - if they have not already been prepared.
@@ -141,19 +165,20 @@ static int fts5StorageGetStmt(
         );
         break;
 
-      case FTS5_STMT_INSERT_CONTENT: 
-      case FTS5_STMT_REPLACE_CONTENT: {
-        int nCol = pC->nCol + 1;
+      case FTS5_STMT_INSERT_CONTENT: {
+        int nCol = 0;
         char *zBind;
         int i;
 
-        zBind = sqlite3_malloc64(1 + nCol*2);
-        if( zBind ){
-          for(i=0; i<nCol; i++){
-            zBind[i*2] = '?';
-            zBind[i*2 + 1] = ',';
+        nCol = 1 + pC->nCol;
+        if( pC->bLocale ){
+          for(i=0; i<pC->nCol; i++){
+            if( pC->abUnindexed[i]==0 ) nCol++;
           }
-          zBind[i*2-1] = '\0';
+        }
+
+        zBind = fts5BindingsList(&rc, nCol);
+        if( zBind ){
           zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind);
           sqlite3_free(zBind);
         }
@@ -344,7 +369,7 @@ int sqlite3Fts5StorageOpen(
   if( bCreate ){
     if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
       int nDefn = 32 + pConfig->nCol*10;
-      char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 10);
+      char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 20);
       if( zDefn==0 ){
         rc = SQLITE_NOMEM;
       }else{
@@ -356,6 +381,14 @@ int sqlite3Fts5StorageOpen(
           sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i);
           iOff += (int)strlen(&zDefn[iOff]);
         }
+        if( pConfig->bLocale ){
+          for(i=0; i<pConfig->nCol; i++){
+            if( pConfig->abUnindexed[i]==0 ){
+              sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", l%d", i);
+              iOff += (int)strlen(&zDefn[iOff]);
+            }
+          }
+        }
         rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
       }
       sqlite3_free(zDefn);
@@ -507,7 +540,8 @@ static int fts5StorageDeleteFromIndex(
       sqlite3_value *pVal = 0;
       const char *pText = 0;
       int nText = 0;
-      int bReset = 0;
+      const char *pLoc = 0;
+      int nLoc = 0;
 
       assert( pSeek==0 || apVal==0 );
       assert( pSeek!=0 || apVal!=0 );
@@ -517,10 +551,19 @@ static int fts5StorageDeleteFromIndex(
         pVal = apVal[iCol-1];
       }
 
-      rc = sqlite3Fts5ExtractText(
-          pConfig, pVal, pSeek!=0, &bReset, &pText, &nText
-      );
+      if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
+        rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc);
+      }else{
+        pText = (const char*)sqlite3_value_text(pVal);
+        nText = sqlite3_value_bytes(pVal);
+        if( pConfig->bLocale && pSeek ){
+          pLoc = (const char*)sqlite3_column_text(pSeek, iCol + pConfig->nCol);
+          nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol);
+        }
+      }
+
       if( rc==SQLITE_OK ){
+        sqlite3Fts5SetLocale(pConfig, pLoc, nLoc);
         ctx.szCol = 0;
         rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, 
             pText, nText, (void*)&ctx, fts5StorageInsertCallback
@@ -529,7 +572,7 @@ static int fts5StorageDeleteFromIndex(
         if( rc==SQLITE_OK && p->aTotalSize[iCol-1]<0 ){
           rc = FTS5_CORRUPT;
         }
-        if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+        sqlite3Fts5ClearLocale(pConfig);
       }
     }
   }
@@ -788,20 +831,35 @@ int sqlite3Fts5StorageRebuild(Fts5Storage *p){
     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;           /* 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 */
+        int nLoc = 0;             /* Size of pLoc in bytes */
+        const char *pLoc = 0;     /* Pointer to buffer containing text value */
+
         sqlite3_value *pVal = sqlite3_column_value(pScan, ctx.iCol+1);
+        if( pConfig->eContent==FTS5_CONTENT_EXTERNAL
+         && sqlite3Fts5IsLocaleValue(pConfig, pVal)
+        ){
+          rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc);
+        }else{
+          pText = (const char*)sqlite3_value_text(pVal);
+          nText = sqlite3_value_bytes(pVal);
+          if( pConfig->bLocale ){
+            int iCol = ctx.iCol + 1 + pConfig->nCol;
+            pLoc = (const char*)sqlite3_column_text(pScan, iCol);
+            nLoc = sqlite3_column_bytes(pScan, iCol);
+          }
+        }
 
-        rc = sqlite3Fts5ExtractText(pConfig, pVal, 1, &bReset, &pText, &nText);
         if( rc==SQLITE_OK ){
+          sqlite3Fts5SetLocale(pConfig, pLoc, nLoc);
           rc = sqlite3Fts5Tokenize(pConfig, 
               FTS5_TOKENIZE_DOCUMENT,
               pText, nText,
               (void*)&ctx,
               fts5StorageInsertCallback
           );
-          if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+          sqlite3Fts5ClearLocale(pConfig);
         }
       }
       sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
@@ -884,33 +942,46 @@ int sqlite3Fts5StorageContentInsert(
   }else{
     sqlite3_stmt *pInsert = 0;    /* Statement to write %_content table */
     int i;                        /* Counter variable */
+    int nIndexed = 0;             /* Number indexed columns seen */
     rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
-    for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
+    if( pInsert ) sqlite3_clear_bindings(pInsert);
+
+    /* Bind the rowid value */
+    sqlite3_bind_value(pInsert, 1, apVal[1]);
+
+    /* Loop through values for user-defined columns. i=2 is the leftmost
+    ** user-defined column. As is column 1 of pSavedRow.  */
+    for(i=2; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
+      int bUnindexed = pConfig->abUnindexed[i-2];
       sqlite3_value *pVal = apVal[i];
+
+      nIndexed += !bUnindexed;
       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_type(pVal)==SQLITE_BLOB && pConfig->bLocale ){
+        if( pConfig->bLocale && bUnindexed==0 ){
+          sqlite3_bind_value(pInsert, pConfig->nCol + 1 + nIndexed,
+              sqlite3_column_value(p->pSavedRow, pConfig->nCol + i - 1)
+          );
+        }
+      }else if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
+        const char *pText = 0;
+        const char *pLoc = 0;
+        int nText = 0;
+        int nLoc = 0;
         assert( pConfig->bLocale );
-        assert( i>1 );
-        if( pConfig->abUnindexed[i-2] ){
-          if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
-            /* At attempt to insert an fts5_locale() value into an UNINDEXED
-            ** column. Strip the locale away and just bind the text.  */
-            const char *pText = 0;
-            int nText = 0;
-            rc = sqlite3Fts5ExtractText(pConfig, pVal, 0, 0, &pText, &nText);
-            sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT);
-            continue;
+
+        rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc);
+        if( rc==SQLITE_OK ){
+          sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT);
+          if( bUnindexed==0 ){
+            int iLoc = pConfig->nCol + 1 + nIndexed;
+            sqlite3_bind_text(pInsert, iLoc, pLoc, nLoc, SQLITE_TRANSIENT);
           }
-        }else{
-          const u8 *pBlob = (const u8*)sqlite3_value_blob(pVal);
-          int nBlob = sqlite3_value_bytes(pVal);
-          assert( nBlob>4 );
-          sqlite3_bind_blob(pInsert, i, pBlob+16, nBlob-16, SQLITE_TRANSIENT);
-          continue;
         }
+
+        continue;
       }
 
       rc = sqlite3_bind_value(pInsert, i, pVal);
@@ -948,23 +1019,37 @@ int sqlite3Fts5StorageIndexInsert(
   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;             /* 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 */
+      int nLoc = 0;               /* Size of pText in bytes */
+      const char *pLoc = 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;
+        if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){
+          int iCol = ctx.iCol + 1 + pConfig->nCol;
+          pLoc = (const char*)sqlite3_column_text(p->pSavedRow, iCol);
+          nLoc = sqlite3_column_bytes(p->pSavedRow, iCol);
+        }
+      }else{
+        pVal = apVal[ctx.iCol+2];
       }
-      rc = sqlite3Fts5ExtractText(pConfig, pVal, bDisk, &bReset, &pText,&nText);
+
+      if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){
+        rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc);
+      }else{
+        pText = (const char*)sqlite3_value_text(pVal);
+        nText = sqlite3_value_bytes(pVal);
+      }
+
       if( rc==SQLITE_OK ){
-        assert( bReset==0 || pConfig->bLocale );
+        sqlite3Fts5SetLocale(pConfig, pLoc, nLoc);
         rc = sqlite3Fts5Tokenize(pConfig, 
             FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx,
             fts5StorageInsertCallback
         );
-        if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+        sqlite3Fts5ClearLocale(pConfig);
       }
     }
     sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
@@ -1129,37 +1214,61 @@ int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){
           rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
         }
         for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
-          if( pConfig->abUnindexed[i] ) continue;
-          ctx.iCol = i;
-          ctx.szCol = 0;
-          if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
-            rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
-          }
-          if( rc==SQLITE_OK ){
-            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( pConfig->abUnindexed[i]==0 ){
+            const char *pText = 0;
+            int nText = 0;
+            const char *pLoc = 0;
+            int nLoc = 0;
+            sqlite3_value *pVal = sqlite3_column_value(pScan, i+1);
+
+            if( pConfig->eContent==FTS5_CONTENT_EXTERNAL 
+             && sqlite3Fts5IsLocaleValue(pConfig, pVal)
+            ){
+              rc = sqlite3Fts5DecodeLocaleValue(
+                  pVal, &pText, &nText, &pLoc, &nLoc
+              );
+            }else{
+              if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){
+                int iCol = i + 1 + pConfig->nCol;
+                pLoc = (const char*)sqlite3_column_text(pScan, iCol);
+                nLoc = sqlite3_column_bytes(pScan, iCol);
+              }
+              pText = (const char*)sqlite3_value_text(pVal);
+              nText = sqlite3_value_bytes(pVal);
+            }
+
+            ctx.iCol = i;
+            ctx.szCol = 0;
+
+            if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
+              rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
+            }
+
             if( rc==SQLITE_OK ){
+              sqlite3Fts5SetLocale(pConfig, pLoc, nLoc);
               rc = sqlite3Fts5Tokenize(pConfig, 
                   FTS5_TOKENIZE_DOCUMENT,
                   pText, nText,
                   (void*)&ctx,
                   fts5StorageIntegrityCallback
               );
-              if( bReset ) sqlite3Fts5ClearLocale(pConfig);
+              sqlite3Fts5ClearLocale(pConfig);
+            }
+
+            /* If this is not a columnsize=0 database, check that the number
+            ** of tokens in the value matches the aColSize[] value read from
+            ** the %_docsize table.  */
+            if( rc==SQLITE_OK 
+             && pConfig->bColumnsize 
+             && ctx.szCol!=aColSize[i] 
+            ){
+              rc = FTS5_CORRUPT;
+            }
+            aTotalSize[i] += ctx.szCol;
+            if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
+              sqlite3Fts5TermsetFree(ctx.pTermset);
+              ctx.pTermset = 0;
             }
-          }
-          if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){
-            rc = FTS5_CORRUPT;
-          }
-          aTotalSize[i] += ctx.szCol;
-          if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
-            sqlite3Fts5TermsetFree(ctx.pTermset);
-            ctx.pTermset = 0;
           }
         }
         sqlite3Fts5TermsetFree(ctx.pTermset);
index 4233719fbc1c74bb49cf47a4a4b1aff8448128f3..93485541042ee6eeab5fb752ffd6e81ba92d2302 100644 (file)
@@ -103,13 +103,13 @@ do_execsql_test 3.0 {
 
 do_catchsql_test 3.1 {
   INSERT INTO x1(rowid, a, b) VALUES(113, 'hello world', X'123456');
-} {1 {datatype mismatch}}
+} {0 {}}
 do_catchsql_test 3.2 {
   INSERT INTO x2(rowid, a, b) VALUES(113, 'hello world', X'123456');
-} {1 {datatype mismatch}}
+} {0 {}}
 do_catchsql_test 3.3 {
   INSERT INTO x3(rowid, a, b) VALUES(113, 'hello world', X'123456');
-} {1 {datatype mismatch}}
+} {0 {}}
 
 
 #--------------------------------------------------------------------------
index fe7c0aad50d1b59ce839776d64a8d661f3a31b76..72f25caee1b01d560e1c4f693db2a0d5633d4291 100644 (file)
@@ -256,15 +256,16 @@ do_faultsim_test 10.1 -faults oom* -prep {
   faultsim_test_result {0 hello}
 }
 
+breakpoint
 faultsim_save_and_close
-do_faultsim_test 10.2 -faults oom* -prep {
+do_faultsim_test 10.2 -faults oom-t* -prep {
   faultsim_restore_and_reopen
 } -body {
   execsql {
     INSERT INTO ft VALUES(zeroblob(10000));
   }
 } -test {
-  faultsim_test_result {1 {datatype mismatch}}
+  faultsim_test_result {0 {}}
 }
 
 #-------------------------------------------------------------------------
index f0df4969dcd446c7d32c1d6de888dcbc76825e43..e5799fb7fd78b354f2279be3d0a74cd600078169 100644 (file)
@@ -73,6 +73,7 @@ do_execsql_test 1.2 {
   SELECT rowid, a FROM t1( fts5_locale('reverse', 'abc') );
 } {2 cba}
 
+
 #-------------------------------------------------------------------------
 # Test that the locale= option exists and seems to accept values. And
 # that fts5_locale() values may only be inserted into an internal-content
@@ -99,8 +100,11 @@ do_catchsql_test 2.3 {
   INSERT INTO b1(b1, rank) VALUES('locale', 0);
 } {1 {SQL logic error}}
 
-do_execsql_test 2.4 {
+do_execsql_test 2.4.1 {
   INSERT INTO b1 VALUES('abc', 'one two three');
+}
+
+do_execsql_test 2.4.2 {
   INSERT INTO b1 VALUES('def', fts5_locale('reverse', 'four five six'));
 }
 
@@ -131,6 +135,7 @@ do_execsql_test 2.12  { SELECT quote(y) FROM b1('ruof') } {
 do_execsql_test 2.13 {
   INSERT INTO b1(b1) VALUES('integrity-check');
 }
+
 do_execsql_test 2.14 {
   INSERT INTO b1(b1) VALUES('rebuild');
 }
@@ -149,7 +154,6 @@ do_execsql_test 2.18 {
   INSERT INTO b1(rowid, x, y) VALUES(
       test_setsubtype(45, 76), 'abc def', 'def abc'
   );
-  INSERT INTO b1(b1) VALUES('integrity-check');
 }
 
 #-------------------------------------------------------------------------
@@ -278,9 +282,9 @@ do_execsql_test 5.2 {
 }
 
 do_execsql_test 5.3 {
-  SELECT typeof(c0), typeof(c1) FROM t1_content
+  SELECT typeof(c0), typeof(c1), typeof(l0) FROM t1_content
 } {
-  blob text
+  text text text
 }
 
 #-------------------------------------------------------------------------
@@ -305,37 +309,37 @@ foreach {tn opt} {
 
   fts5_aux_test_functions db
 
-  do_execsql_test 5.$tn.3 {
+  do_execsql_test 6.$tn.3 {
     SELECT fts5_test_columnsize(y1) FROM y1
   } {
     2 3 2 4
   }
 
-  do_execsql_test 5.$tn.4 {
+  do_execsql_test 6.$tn.4 {
     SELECT rowid, fts5_test_columnsize(y1) FROM y1('shall');
   } {
     2 3
   }
 
-  do_execsql_test 5.$tn.5 {
+  do_execsql_test 6.$tn.5 {
     SELECT rowid, fts5_test_columnsize(y1) FROM y1('shall');
   } {
     2 3
   }
 
-  do_execsql_test 5.$tn.6 {
+  do_execsql_test 6.$tn.6 {
     SELECT rowid, fts5_test_columnsize(y1) FROM y1('have');
   } {
     4 4
   }
 
-  do_execsql_test 5.$tn.7 {
+  do_execsql_test 6.$tn.7 {
     SELECT rowid, highlight(y1, 0, '[', ']') FROM y1('have');
   } {
     4 {which it hath been used to [have]}
   }
 
-  do_execsql_test 5.$tn.8 {
+  do_execsql_test 6.$tn.8 {
     SELECT rowid, 
            highlight(y1, 0, '[', ']'),
            snippet(y1, 0, '[', ']', '...', 10)
@@ -473,7 +477,7 @@ foreach_detail_mode $::testprefix {
   }
   
   foreach {tn v} {
-    1   X'001122'
+    1   X'001152'
     2   X'0011223344'
     3   X'00E0B2EB68656c6c6f'
     4   X'00E0B2EB0068656c6c6f'
@@ -484,7 +488,7 @@ foreach_detail_mode $::testprefix {
   
     do_catchsql_test 10.2.$tn.3 {
       INSERT INTO ft(ft) VALUES('rebuild');
-    } {1 {SQL logic error}}
+    } {0 {}}
   
     do_catchsql_test 10.2.$tn.4 "
       SELECT * FROM ft( test_setsubtype($v, 76) );
@@ -494,23 +498,23 @@ foreach_detail_mode $::testprefix {
       INSERT INTO ft(rowid, x) VALUES(1, 'hello world');
     }
   
-    if {"%DETAIL%"!="full"} {
-      do_catchsql_test 10.2.$tn.6 {
+    if {"%DETAIL%"=="full"} {
+      do_execsql_test 10.2.$tn.6 {
         SELECT fts5_test_poslist(ft) FROM ft('world');
-      } {1 SQLITE_ERROR}
+      } {0.0.1}
   
-      do_catchsql_test 10.2.$tn.7 {
+      do_execsql_test 10.2.$tn.7.1 {
         SELECT fts5_test_columnsize(ft) FROM ft('world');
-      } {1 SQLITE_ERROR}
+      } {1}
 
-      do_catchsql_test 10.2.$tn.7 {
+      do_execsql_test 10.2.$tn.7.2 {
         SELECT fts5_test_columnlocale(ft) FROM ft('world');
-      } {1 SQLITE_ERROR}
+      } {{{}}}
     }
 
     do_catchsql_test 10.2.$tn.8 {
-      SELECT * FROM ft('hello')
-    } {1 {SQL logic error}}
+      SELECT count(*) FROM ft('hello')
+    } {0 1}
 
     do_catchsql_test 10.2.$tn.9 {
       PRAGMA integrity_check;
@@ -527,7 +531,7 @@ foreach_detail_mode $::testprefix {
 
     do_catchsql_test 10.2.$tn.12 "
       INSERT INTO ft(rowid, x) VALUES(2, test_setsubtype($v,76) )
-    " {1 {datatype mismatch}}
+    " {0 {}}
 
     do_execsql_test 10.2.$tn.13 {
       INSERT INTO ft2(rowid, x) VALUES(1, 'hello world');
@@ -536,7 +540,7 @@ foreach_detail_mode $::testprefix {
 
     do_catchsql_test 10.2.$tn.15 {
       PRAGMA integrity_check;
-    } {1 {SQL logic error}}
+    } {0 {{malformed inverted index for FTS5 table main.ft2}}}
 
     do_execsql_test 10.2.$tn.16 {
       DELETE FROM ft2_content;
@@ -679,13 +683,66 @@ do_execsql_test 14.3 {
   UPDATE ft SET b = fts5_locale('en_AU', 'world');
 }
 
-do_catchsql_test 14.4 {
+do_execsql_test 14.4 {
   INSERT INTO ft VALUES(X'abcd', X'1234');
-} {1 {datatype mismatch}}
+} {}
+
+do_execsql_test 14.5 {
+  SELECT quote(a), quote(b) FROM ft
+} {'hello' 'world' X'ABCD' X'1234'}
+
+do_execsql_test 14.6 {
+  DELETE FROM ft;
+  INSERT INTO ft VALUES(NULL, 'null');
+  INSERT INTO ft VALUES(123, 'int');
+  INSERT INTO ft VALUES(345.0, 'real');
+  INSERT INTO ft VALUES('abc', 'text');
+  INSERT INTO ft VALUES(fts5_locale('abc', 'def'), 'text');
+
+  SELECT a, typeof(a), b FROM ft
+} {
+  {} null null
+  123 integer int
+  345.0 real real
+  abc text text
+  def text text
+}
+
+do_execsql_test 14.7 {
+  SELECT quote(c0), typeof(c0) FROM ft_content
+} {
+  NULL null
+  123 integer
+  345.0 real
+  'abc' text
+  'def' text
+}
+
+#-------------------------------------------------------------------------
+# Check that inserting UNINDEXED columns between indexed columns of a
+# locale=1 table does not cause a problem.
+#
+reset_db
+sqlite3_fts5_create_tokenizer -v2 db tcl tcl_create
+fts5_aux_test_functions db
+
+do_execsql_test 15.1 {
+  CREATE VIRTUAL TABLE ft USING fts5(a, b UNINDEXED, c, locale=1, tokenize=tcl);
+}
+
+do_execsql_test 15.2 {
+  INSERT INTO ft VALUES('one', 'two', 'three');
+  INSERT INTO ft VALUES('one', 'two', fts5_locale('loc', 'three'));
+}
+
+do_execsql_test 15.3 {
+  SELECT c2, l2 FROM ft_content
+} {three {} three loc}
+
+do_execsql_test 15.4 {
+  SELECT c, fts5_columnlocale(ft, 2) FROM ft
+} {three {} three loc}
 
-do_execsql_test 14.4 {
-  SELECT * FROM ft
-} {hello world}
 
 finish_test
 
index ad59bf0d9e660c4e13663ef35a47010eda21eae7..8ca5d334feeab8be7a6e70466d90ec66a7e55a9b 100644 (file)
@@ -371,6 +371,7 @@ foreach_detail_mode $testprefix {
     CREATE VIRTUAL TABLE x3 USING fts5(x, detail=%DETAIL%);
     INSERT INTO x3 VALUES('a b c d e f');
   }
+  breakpoint
   do_execsql_test 16.1 {
     SELECT fts5_test_poslist(x3) FROM x3('(a NOT b) OR c');
   } {2.0.2}
index df046ec751e36e34995021fad8588ad55c8d35c7..86c0ee142c2d95d2a2a203710bcae017559b4bb7 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\san\sOOM-handling\sproblem\saffecting\slocale=1\sfts5\stables.
-D 2024-09-09T19:12:57.172
+C Alternative\simplementation\sof\sfts5\slocale=1\sfeature\sthat\sallows\sblobs\sto\sbe\sstored\sin\sindexed\scolumns\sof\sfts5\slocale=1\stables.
+D 2024-09-10T16:19:31.078
 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 7ab1d838adc4f22fdad5e1ba19182d6899ebded1d3ecadbe995322b0f0de7b9f
+F ext/fts5/fts5Int.h 93aba03ca417f403b07b2ab6f50aa0e0c1b8b031917a9026b81520e7047a168e
 F ext/fts5/fts5_aux.c 65a0468dd177d6093aa9ae1622e6d86b0136b8d267c62c0ad6493ad1e9a3d759
 F ext/fts5/fts5_buffer.c 0eec58bff585f1a44ea9147eae5da2447292080ea435957f7488c70673cb6f09
-F ext/fts5/fts5_config.c 353d2a0d12678cae6ab5b9ce54aed8dac0825667b69248b5a4ed81cbefc109ea
+F ext/fts5/fts5_config.c da21548ddbc1a457cb42545f527065221ede8ada6a734891b8c34317a7a9506b
 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 9124eba418eb0c608c1454c4ad08a5f1ac21a4748c36a44828a0a7a1b32ef896
-F ext/fts5/fts5_storage.c 42cde97eb7d8506a8d2c7ea80b292fc3017b1f5469e1acb0035a69c345e6cf71
+F ext/fts5/fts5_main.c 68b8fd96b0798b4822c081049bb37598f9f3fc34c6447c74cc162f86b18d2ded
+F ext/fts5/fts5_storage.c 3332497823c3d171cf56379f2bd8c971ce15a19aadacff961106462022c92470
 F ext/fts5/fts5_tcl.c 4db9258a7882c5eac0da4433042132aaf15b87dd1e1636c7a6ca203abd2c8bfe
 F ext/fts5/fts5_test_mi.c 08c11ec968148d4cb4119d96d819f8c1f329812c568bac3684f5464be177d3ee
 F ext/fts5/fts5_test_tok.c 3cb0a9b508b30d17ef025ccddd26ae3dc8ddffbe76c057616e59a9aa85d36f3b
@@ -132,7 +132,7 @@ F ext/fts5/test/fts5auxdata.test 372549088ff792655f73e62b9dfaf4863ce74f5e604c06c
 F ext/fts5/test/fts5bigid.test 2860854c2561a57594192b00c33a29f91cb85e25f3d6c03b5c2b8f62708f39dd
 F ext/fts5/test/fts5bigpl.test 8f09858aab866c33593560e6480b2b6975ae7ff29ca32ad7b77e2da61402f8ef
 F ext/fts5/test/fts5bigtok.test 541119e616c637caea925a8c028c37c2c29e94383e00aa2f9198d530724b6e36
-F ext/fts5/test/fts5blob.test caa33369e93e99ff494cd1103506ae34c5afbc0bcc369ed5e58e135144e33689
+F ext/fts5/test/fts5blob.test 9644a5f917306690e08c5f89a470a3f2489376eaa52026eeca3209d149d6af74
 F ext/fts5/test/fts5cat.test bf67dd335f964482ee658287521b81e2b88697b45eb7f73933e15f198ed447cb
 F ext/fts5/test/fts5circref.test f880dfd0d99f6fb73b88ccacb0927d18e833672fd906cc47d6b4e529419eaa62
 F ext/fts5/test/fts5colset.test 544f4998cdbfe06a3123887fc0221612e8aa8192cdaff152872f1aadb10e6897
@@ -178,7 +178,7 @@ F ext/fts5/test/fts5faultE.test 844586ce71dab4be85bb86880e87b624d089f851654cd22e
 F ext/fts5/test/fts5faultF.test 4abef99f86e99d9f0c6460dd68c586a766b6b9f1f660ada55bf2e8266bd1bbc1
 F ext/fts5/test/fts5faultG.test 0544411ffcb3e19b42866f757a8a5e0fb8fef3a62c06f61d14deebc571bb7ea9
 F ext/fts5/test/fts5faultH.test 2b2b5b8cb1b3fd7679f488c06e22af44107fbc6137eaf45b3e771dc7b149312d
-F ext/fts5/test/fts5faultI.test a1496d6d72b864102f95f9a616a0f583320310a6fb7a463a37c88dfb40d68ae5
+F ext/fts5/test/fts5faultI.test 0706b307b208638554c9e65b4091e1c0dd8c92941535089a301df454ff2c56f4
 F ext/fts5/test/fts5first.test bfd685b96905bf541d99d8644e0a7219d1d833455a08ab64e344071a613b6ba9
 F ext/fts5/test/fts5full.test 97d263c1072f4a560929cca31e70f65d2ae232610e17e6affcf7e979df59547b
 F ext/fts5/test/fts5fuzz1.test 238d8c45f3b81342aa384de3e581ff2fa330bf922a7b69e484bbc06051a1080e
@@ -189,7 +189,7 @@ F ext/fts5/test/fts5interrupt.test 20d04204d3e341b104c0c24a41596b6393a3a81eba104
 F ext/fts5/test/fts5lastrowid.test f36298a1fb9f988bde060a274a7ce638faa9c38a31400f8d2d27ea9373e0c4a1
 F ext/fts5/test/fts5leftjoin.test c0b4cafb9661379e576dc4405c0891d8fcc2782680740513c4d1fc114b43d4ad
 F ext/fts5/test/fts5limits.test 8ab67cf5d311c124b6ceb0062d0297767176df4572d955fce79fa43004dff01c
-F ext/fts5/test/fts5locale.test 58ce0515c4f49cbb9905e3711168050d58fc184daf885c9ef7483e20aab63e5a
+F ext/fts5/test/fts5locale.test 83ba7ee12628b540d3098f39c39c1de0c0440eddff8f7512c8c698d0c4a3ae3c
 F ext/fts5/test/fts5matchinfo.test 877520582feb86bbfd95ab780099bcba4526f18ac75ee34979144cf86ba3a5a3
 F ext/fts5/test/fts5merge.test 2654df0bcdb2d117c2d38b6aeb0168061be01c643f9e9194b36c43a2970e8082
 F ext/fts5/test/fts5merge2.test 3ebad1a59d6ad3fb66eff6523a09e95dc6367cbefb3cd73196801dea0425c8e2
@@ -227,7 +227,7 @@ F ext/fts5/test/fts5secure6.test 74bf04733cc523bccca519bb03d3b4e2ed6f6e3db7c59bf
 F ext/fts5/test/fts5secure7.test fd03d0868d64340a1db8615b02e5508fea409de13910114e4f19eaefc120777a
 F ext/fts5/test/fts5secure8.test 808ade9d172ed07b24b85c57dd53b6d2b1aba018b4e634d267ce572221de80e0
 F ext/fts5/test/fts5securefault.test c34a28c7cd2f31a8b8907563889e1329a97da975c08df2d951422bcef8e2ebc5
-F ext/fts5/test/fts5simple.test 302cdb4f8a3350b091f4f1bccd82d05610428657f6f9e81c17703ba48267ec40
+F ext/fts5/test/fts5simple.test 5a81d494f269baa176a0acefeae1e4a239d4933700d8f415eff16da214fd11d3
 F ext/fts5/test/fts5simple2.test d10d963a357b8ec77b99032e4c816459b4dbdb1f6eee25eada7ef3ed245cb2dc
 F ext/fts5/test/fts5simple3.test 146ec3dc8f5763d6212641c9f0a2f1cba41679353d2add7b963beceb115dc7f4
 F ext/fts5/test/fts5synonym.test becc8cea6cfc958a50b30c572c68cbfdf7455971d0fe988202ce67638d2c6cf6
@@ -2212,8 +2212,11 @@ F vsixtest/vsixtest.tcl 6195aba1f12a5e10efc2b8c0009532167be5e301abe5b31385638080
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P fe0d67e72d4228661c021f227bfc0d5ddb1b726db0f36c7221ead8dd8bd1dc73
-R d215b5e5c87263682db579b6ad049bae
+P d8103684f660ff9b3186d0f89afb113ca580bd16f0bf413ed8a9434236b54426
+R 4bff6e3adb2a3e18f21f459a8e5e307a
+T *branch * fts5-locale-alternate
+T *sym-fts5-locale-alternate *
+T -sym-trunk *
 U dan
-Z 3d429b85e0f7dde1fd63e360d413140d
+Z 708e8bcf278c2f81c8bdb896ae9792a2
 # Remove this line to create a well-formed Fossil manifest.
index 739aa6819cb48f85ea6f617eec68a3c2bc2de104..b9473faf2929ed27ca4126f2208e27dc174d799a 100644 (file)
@@ -1 +1 @@
-d8103684f660ff9b3186d0f89afb113ca580bd16f0bf413ed8a9434236b54426
+55c5c119a0a77fac2c9f46d718ef78c0f33ed3520e10c240cf5bf1801e0586ee