]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add experimental 'content' option to FTS4.
authordan <dan@noemail.net>
Tue, 4 Oct 2011 11:22:59 +0000 (11:22 +0000)
committerdan <dan@noemail.net>
Tue, 4 Oct 2011 11:22:59 +0000 (11:22 +0000)
FossilOrigin-Name: 1d27ea741f61c624e18bdc6a3b1c2d8574a64ddc

ext/fts3/fts3.c
ext/fts3/fts3Int.h
ext/fts3/fts3_snippet.c
ext/fts3/fts3_write.c
manifest
manifest.uuid
test/fts4content.test [new file with mode: 0644]
test/permutations.test

index 29e071a50f15d8e11ad744fa70522e22f8e1e786..c188945b28c2756546c673da6942dc4425d31738 100644 (file)
@@ -468,6 +468,7 @@ static int fts3DisconnectMethod(sqlite3_vtab *pVtab){
   sqlite3_free(p->zSegmentsTbl);
   sqlite3_free(p->zReadExprlist);
   sqlite3_free(p->zWriteExprlist);
+  sqlite3_free(p->zContentTbl);
 
   /* Invoke the tokenizer destructor to free the tokenizer. */
   p->pTokenizer->pModule->xDestroy(p->pTokenizer);
@@ -507,16 +508,19 @@ static void fts3DbExec(
 ** The xDestroy() virtual table method.
 */
 static int fts3DestroyMethod(sqlite3_vtab *pVtab){
-  int rc = SQLITE_OK;              /* Return code */
   Fts3Table *p = (Fts3Table *)pVtab;
-  sqlite3 *db = p->db;
+  int rc = SQLITE_OK;              /* Return code */
+  const char *zDb = p->zDb;        /* Name of database (e.g. "main", "temp") */
+  sqlite3 *db = p->db;             /* Database handle */
 
   /* Drop the shadow tables */
-  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", p->zDb, p->zName);
-  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", p->zDb,p->zName);
-  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", p->zDb, p->zName);
-  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", p->zDb, p->zName);
-  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", p->zDb, p->zName);
+  if( p->zContentTbl==0 ){
+    fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_content'", zDb, p->zName);
+  }
+  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments'", zDb,p->zName);
+  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segdir'", zDb, p->zName);
+  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_docsize'", zDb, p->zName);
+  fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_stat'", zDb, p->zName);
 
   /* If everything has worked, invoke fts3DisconnectMethod() to free the
   ** memory associated with the Fts3Table structure and return SQLITE_OK.
@@ -578,23 +582,27 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){
 static int fts3CreateTables(Fts3Table *p){
   int rc = SQLITE_OK;             /* Return code */
   int i;                          /* Iterator variable */
-  char *zContentCols;             /* Columns of %_content table */
   sqlite3 *db = p->db;            /* The database connection */
 
-  /* Create a list of user columns for the content table */
-  zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY");
-  for(i=0; zContentCols && i<p->nColumn; i++){
-    char *z = p->azColumn[i];
-    zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
+  if( p->zContentTbl==0 ){
+    char *zContentCols;           /* Columns of %_content table */
+
+    /* Create a list of user columns for the content table */
+    zContentCols = sqlite3_mprintf("docid INTEGER PRIMARY KEY");
+    for(i=0; zContentCols && i<p->nColumn; i++){
+      char *z = p->azColumn[i];
+      zContentCols = sqlite3_mprintf("%z, 'c%d%q'", zContentCols, i, z);
+    }
+    if( zContentCols==0 ) rc = SQLITE_NOMEM;
+  
+    /* Create the content table */
+    fts3DbExec(&rc, db, 
+       "CREATE TABLE %Q.'%q_content'(%s)",
+       p->zDb, p->zName, zContentCols
+    );
+    sqlite3_free(zContentCols);
   }
-  if( zContentCols==0 ) rc = SQLITE_NOMEM;
 
-  /* Create the content table */
-  fts3DbExec(&rc, db, 
-     "CREATE TABLE %Q.'%q_content'(%s)",
-     p->zDb, p->zName, zContentCols
-  );
-  sqlite3_free(zContentCols);
   /* Create other tables */
   fts3DbExec(&rc, db, 
       "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);",
@@ -745,8 +753,8 @@ static char *fts3QuoteId(char const *zInput){
 }
 
 /*
-** Return a list of comma separated SQL expressions that could be used
-** in a SELECT statement such as the following:
+** Return a list of comma separated SQL expressions and a FROM clause that 
+** could be used in a SELECT statement such as the following:
 **
 **     SELECT <list of expressions> FROM %_content AS x ...
 **
@@ -757,7 +765,7 @@ static char *fts3QuoteId(char const *zInput){
 ** table has the three user-defined columns "a", "b", and "c", the following
 ** string is returned:
 **
-**     "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c')"
+**     "docid, unzip(x.'a'), unzip(x.'b'), unzip(x.'c') FROM %_content AS x"
 **
 ** The pointer returned points to a buffer allocated by sqlite3_malloc(). It
 ** is the responsibility of the caller to eventually free it.
@@ -773,16 +781,28 @@ static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){
   char *zFunction;
   int i;
 
-  if( !zFunc ){
-    zFunction = "";
+  if( p->zContentTbl==0 ){
+    if( !zFunc ){
+      zFunction = "";
+    }else{
+      zFree = zFunction = fts3QuoteId(zFunc);
+    }
+    fts3Appendf(pRc, &zRet, "docid");
+    for(i=0; i<p->nColumn; i++){
+      fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]);
+    }
+    sqlite3_free(zFree);
   }else{
-    zFree = zFunction = fts3QuoteId(zFunc);
-  }
-  fts3Appendf(pRc, &zRet, "docid");
-  for(i=0; i<p->nColumn; i++){
-    fts3Appendf(pRc, &zRet, ",%s(x.'c%d%q')", zFunction, i, p->azColumn[i]);
+    fts3Appendf(pRc, &zRet, "rowid");
+    for(i=0; i<p->nColumn; i++){
+      fts3Appendf(pRc, &zRet, ", x.'%q'", p->azColumn[i]);
+    }
   }
-  sqlite3_free(zFree);
+  fts3Appendf(pRc, &zRet, "FROM '%q'.'%q%s' AS x", 
+      p->zDb,
+      (p->zContentTbl ? p->zContentTbl : p->zName),
+      (p->zContentTbl ? "" : "_content")
+  );
   return zRet;
 }
 
@@ -906,6 +926,91 @@ static int fts3PrefixParameter(
   return SQLITE_OK;
 }
 
+/*
+** This function is called when initializing an FTS4 table that uses the
+** content=xxx option. It determines the number of and names of the columns
+** of the new FTS4 table.
+**
+** The third argument passed to this function is the value passed to the
+** config=xxx option (i.e. "xxx"). This function queries the database for
+** a table of that name. If found, the output variables are populated
+** as follows:
+**
+**   *pnCol:   Set to the number of columns table xxx has,
+**
+**   *pnStr:   Set to the total amount of space required to store a copy
+**             of each columns name, including the nul-terminator.
+**
+**   *pazCol:  Set to point to an array of *pnCol strings. Each string is
+**             the name of the corresponding column in table xxx. The array
+**             and its contents are allocated using a single allocation. It
+**             is the responsibility of the caller to free this allocation
+**             by eventually passing the *pazCol value to sqlite3_free().
+**
+** If the table cannot be found, an error code is returned and the output
+** variables are undefined. Or, if an OOM is encountered, SQLITE_NOMEM is
+** returned (and the output variables are undefined).
+*/
+static int fts3ContentColumns(
+  sqlite3 *db,                    /* Database handle */
+  const char *zDb,                /* Name of db (i.e. "main", "temp" etc.) */
+  const char *zTbl,               /* Name of content table */
+  const char ***pazCol,           /* OUT: Malloc'd array of column names */
+  int *pnCol,                     /* OUT: Size of array *pazCol */
+  int *pnStr                      /* OUT: Bytes of string content */
+){
+  int rc = SQLITE_OK;             /* Return code */
+  char *zSql;                     /* "SELECT *" statement on zTbl */  
+  sqlite3_stmt *pStmt = 0;        /* Compiled version of zSql */
+
+  zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl);
+  if( !zSql ){
+    rc = SQLITE_NOMEM;
+  }else{
+    rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
+  }
+  sqlite3_free(zSql);
+
+  if( rc==SQLITE_OK ){
+    const char **azCol;           /* Output array */
+    int nStr = 0;                 /* Size of all column names (incl. 0x00) */
+    int nCol;                     /* Number of table columns */
+    int i;                        /* Used to iterate through columns */
+
+    /* Loop through the returned columns. Set nStr to the number of bytes of
+    ** space required to store a copy of each column name, including the
+    ** nul-terminator byte.  */
+    nCol = sqlite3_column_count(pStmt);
+    for(i=0; i<nCol; i++){
+      const char *zCol = sqlite3_column_name(pStmt, i);
+      nStr += strlen(zCol) + 1;
+    }
+
+    /* Allocate and populate the array to return. */
+    azCol = (const char **)sqlite3_malloc(sizeof(char *) * nCol + nStr);
+    if( azCol==0 ){
+      rc = SQLITE_NOMEM;
+    }else{
+      char *p = (char *)&azCol[nCol];
+      for(i=0; i<nCol; i++){
+        const char *zCol = sqlite3_column_name(pStmt, i);
+        int n = strlen(zCol)+1;
+        memcpy(p, zCol, n);
+        azCol[i] = p;
+        p += n;
+      }
+    }
+    sqlite3_finalize(pStmt);
+
+    /* Set the output variables. */
+    *pnCol = nCol;
+    *pnStr = nStr;
+    *pazCol = azCol;
+  }
+
+  return rc;
+}
+
 /*
 ** This function is the implementation of both the xConnect and xCreate
 ** methods of the FTS3 virtual table.
@@ -950,6 +1055,7 @@ static int fts3InitVtab(
   char *zPrefix = 0;              /* Prefix parameter value (or NULL) */
   char *zCompress = 0;            /* compress=? parameter (or NULL) */
   char *zUncompress = 0;          /* uncompress=? parameter (or NULL) */
+  char *zContent = 0;             /* content=? parameter (or NULL) */
 
   assert( strlen(argv[0])==4 );
   assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
@@ -993,13 +1099,13 @@ static int fts3InitVtab(
       struct Fts4Option {
         const char *zOpt;
         int nOpt;
-        char **pzVar;
       } aFts4Opt[] = {
-        { "matchinfo",   9, 0 },            /* 0 -> MATCHINFO */
-        { "prefix",      6, 0 },            /* 1 -> PREFIX */
-        { "compress",    8, 0 },            /* 2 -> COMPRESS */
-        { "uncompress", 10, 0 },            /* 3 -> UNCOMPRESS */
-        { "order",       5, 0 }             /* 4 -> ORDER */
+        { "matchinfo",   9 },     /* 0 -> MATCHINFO */
+        { "prefix",      6 },     /* 1 -> PREFIX */
+        { "compress",    8 },     /* 2 -> COMPRESS */
+        { "uncompress", 10 },     /* 3 -> UNCOMPRESS */
+        { "order",       5 },     /* 4 -> ORDER */
+        { "content",     7 }      /* 5 -> CONTENT */
       };
 
       int iOpt;
@@ -1052,6 +1158,12 @@ static int fts3InitVtab(
               }
               bDescIdx = (zVal[0]=='d' || zVal[0]=='D');
               break;
+
+            case 5:               /* CONTENT */
+              sqlite3_free(zUncompress);
+              zContent = zVal;
+              zVal = 0;
+              break;
           }
         }
         sqlite3_free(zVal);
@@ -1064,6 +1176,27 @@ static int fts3InitVtab(
       aCol[nCol++] = z;
     }
   }
+
+  /* If a content=xxx option was specified, the following:
+  **
+  **   1. Ignore any compress= and uncompress= options.
+  **
+  **   2. Ignore any column names that were specified as part of the 
+  **      the CREATE VIRTUAL TABLE statement.
+  **
+  **   3. Determine the actual column names to use for the FTS table 
+  **      based on the columns of the content= table.
+  */
+  if( rc==SQLITE_OK && zContent ){
+    sqlite3_free(aCol); 
+    sqlite3_free(zCompress); 
+    sqlite3_free(zUncompress); 
+    zCompress = 0;
+    zUncompress = 0;
+    aCol = 0;
+    rc = fts3ContentColumns(db, argv[1], zContent, &aCol, &nCol, &nString);
+    assert( rc!=SQLITE_OK || nCol>0 );
+  }
   if( rc!=SQLITE_OK ) goto fts3_init_out;
 
   if( nCol==0 ){
@@ -1108,6 +1241,8 @@ static int fts3InitVtab(
   p->bHasDocsize = (isFts4 && bNoDocsize==0);
   p->bHasStat = isFts4;
   p->bDescIdx = bDescIdx;
+  p->zContentTbl = zContent;
+  zContent = 0;
   TESTONLY( p->inTransaction = -1 );
   TESTONLY( p->mxSavepoint = -1 );
 
@@ -1169,6 +1304,7 @@ fts3_init_out:
   sqlite3_free(aIndex);
   sqlite3_free(zCompress);
   sqlite3_free(zUncompress);
+  sqlite3_free(zContent);
   sqlite3_free((void *)aCol);
   if( rc!=SQLITE_OK ){
     if( p ){
@@ -1334,11 +1470,16 @@ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){
     }else{
       int rc = sqlite3_reset(pCsr->pStmt);
       if( rc==SQLITE_OK ){
-        /* If no row was found and no error has occured, then the %_content
-        ** table is missing a row that is present in the full-text index.
-        ** The data structures are corrupt.
-        */
-        rc = SQLITE_CORRUPT_VTAB;
+        Fts3Table *p = (Fts3Table *)pCsr->base.pVtab;
+        if( p->zContentTbl==0 ){
+          /* If no row was found and no error has occured, then the %_content
+          ** table is missing a row that is present in the full-text index.
+          ** The data structures are corrupt.
+          */
+          rc = SQLITE_CORRUPT_VTAB;
+        }else{
+          return SQLITE_OK;
+        }
       }
       pCsr->isEof = 1;
       if( pContext ){
@@ -2713,12 +2854,13 @@ static int fts3FilterMethod(
   ** row by docid.
   */
   if( idxNum==FTS3_FULLSCAN_SEARCH ){
-    const char *zSort = (pCsr->bDesc ? "DESC" : "ASC");
-    const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x ORDER BY docid %s";
-    zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName, zSort);
+    const char *zTmpl = "SELECT %s ORDER BY rowid %s";
+    zSql = sqlite3_mprintf(zTmpl, 
+        p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC")
+    );
   }else{
-    const char *zTmpl = "SELECT %s FROM %Q.'%q_content' AS x WHERE docid = ?";
-    zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist, p->zDb, p->zName);
+    const char *zTmpl = "SELECT %s WHERE rowid = ?";
+    zSql = sqlite3_mprintf(zTmpl, p->zReadExprlist);
   }
   if( !zSql ) return SQLITE_NOMEM;
   rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0);
@@ -2781,7 +2923,7 @@ static int fts3ColumnMethod(
     sqlite3_result_blob(pContext, &pCsr, sizeof(pCsr), SQLITE_TRANSIENT);
   }else{
     rc = fts3CursorSeek(0, pCsr);
-    if( rc==SQLITE_OK ){
+    if( rc==SQLITE_OK && sqlite3_data_count(pCsr->pStmt)>(iCol+1) ){
       sqlite3_result_value(pContext, sqlite3_column_value(pCsr->pStmt, iCol+1));
     }
   }
@@ -3079,10 +3221,13 @@ static int fts3RenameMethod(
     return rc;
   }
 
-  fts3DbExec(&rc, db,
-    "ALTER TABLE %Q.'%q_content'  RENAME TO '%q_content';",
-    p->zDb, p->zName, zName
-  );
+  if( p->zContentTbl==0 ){
+    fts3DbExec(&rc, db,
+      "ALTER TABLE %Q.'%q_content'  RENAME TO '%q_content';",
+      p->zDb, p->zName, zName
+    );
+  }
+
   if( p->bHasDocsize ){
     fts3DbExec(&rc, db,
       "ALTER TABLE %Q.'%q_docsize'  RENAME TO '%q_docsize';",
index ed8043adf61079613324679276332c66b38acd9f..b4f26fc7a5de4098550f2b5965609fd6de02ce1a 100644 (file)
@@ -184,6 +184,7 @@ struct Fts3Table {
   int nColumn;                    /* number of named columns in virtual table */
   char **azColumn;                /* column names.  malloced */
   sqlite3_tokenizer *pTokenizer;  /* tokenizer for inserts and queries */
+  char *zContentTbl;              /* content=xxx option, or NULL */
 
   /* Precompiled statements used by the implementation. Each of these 
   ** statements is run and reset within a single virtual table API call. 
@@ -245,6 +246,7 @@ struct Fts3Cursor {
   i16 eSearch;                    /* Search strategy (see below) */
   u8 isEof;                       /* True if at End Of Results */
   u8 isRequireSeek;               /* True if must seek pStmt to %_content row */
+  u8 isNullRow;                   /* True for a row of NULLs */
   sqlite3_stmt *pStmt;            /* Prepared statement in use by the cursor */
   Fts3Expr *pExpr;                /* Parsed MATCH query string */
   int nPhrase;                    /* Number of matchable phrases in query */
index b569eb131b48b144f73274c1d5ddbc669c54c9bb..13d0ca355126de04a79528c763a72dec6ccf04aa 100644 (file)
@@ -1409,7 +1409,7 @@ void sqlite3Fts3Offsets(
 
       if( !pTerm ){
         /* All offsets for this column have been gathered. */
-        break;
+        rc = SQLITE_DONE;
       }else{
         assert( iCurrent<=iMinPos );
         if( 0==(0xFE&*pTerm->pList) ){
@@ -1426,7 +1426,7 @@ void sqlite3Fts3Offsets(
               "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart
           );
           rc = fts3StringAppend(&res, aBuffer, -1);
-        }else if( rc==SQLITE_DONE ){
+        }else if( rc==SQLITE_DONE && pTab->zContentTbl==0 ){
           rc = SQLITE_CORRUPT_VTAB;
         }
       }
index 36f2249e12b06f4e2c480c57ce1a452059d18e3a..46a1ead2d892ada52f7264e8f1fb75c4dd89c628 100644 (file)
@@ -256,7 +256,7 @@ static int fts3SqlStmt(
 /* 4  */  "DELETE FROM %Q.'%q_segdir'",
 /* 5  */  "DELETE FROM %Q.'%q_docsize'",
 /* 6  */  "DELETE FROM %Q.'%q_stat'",
-/* 7  */  "SELECT %s FROM %Q.'%q_content' AS x WHERE rowid=?",
+/* 7  */  "SELECT %s WHERE rowid=?",
 /* 8  */  "SELECT (SELECT max(idx) FROM %Q.'%q_segdir' WHERE level = ?) + 1",
 /* 9  */  "INSERT INTO %Q.'%q_segments'(blockid, block) VALUES(?, ?)",
 /* 10 */  "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)",
@@ -298,7 +298,7 @@ static int fts3SqlStmt(
     if( eStmt==SQL_CONTENT_INSERT ){
       zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist);
     }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){
-      zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist, p->zDb, p->zName);
+      zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist);
     }else{
       zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName);
     }
@@ -780,6 +780,18 @@ static int fts3InsertData(
   int rc;                         /* Return code */
   sqlite3_stmt *pContentInsert;   /* INSERT INTO %_content VALUES(...) */
 
+  if( p->zContentTbl ){
+    sqlite3_value *pRowid = apVal[p->nColumn+3];
+    if( sqlite3_value_type(pRowid)==SQLITE_NULL ){
+      pRowid = apVal[1];
+    }
+    if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){
+      return SQLITE_CONSTRAINT;
+    }
+    *piDocid = sqlite3_value_int64(pRowid);
+    return SQLITE_OK;
+  }
+
   /* Locate the statement handle used to insert data into the %_content
   ** table. The SQL for this statement is:
   **
@@ -830,14 +842,16 @@ static int fts3InsertData(
 ** Remove all data from the FTS3 table. Clear the hash table containing
 ** pending terms.
 */
-static int fts3DeleteAll(Fts3Table *p){
+static int fts3DeleteAll(Fts3Table *p, int bContent){
   int rc = SQLITE_OK;             /* Return code */
 
   /* Discard the contents of the pending-terms hash table. */
   sqlite3Fts3PendingTermsClear(p);
 
-  /* Delete everything from the %_content, %_segments and %_segdir tables. */
-  fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0);
+  /* Delete everything from the shadow tables. Except, leave %_content as
+  ** is if bContent is false.  */
+  assert( p->zContentTbl==0 || bContent==0 );
+  if( bContent ) fts3SqlExec(&rc, p, SQL_DELETE_ALL_CONTENT, 0);
   fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGMENTS, 0);
   fts3SqlExec(&rc, p, SQL_DELETE_ALL_SEGDIR, 0);
   if( p->bHasDocsize ){
@@ -2125,12 +2139,18 @@ static void fts3SegWriterFree(SegmentWriter *pWriter){
 static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
   sqlite3_stmt *pStmt;
   int rc;
-  rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
-  if( rc==SQLITE_OK ){
-    if( SQLITE_ROW==sqlite3_step(pStmt) ){
-      *pisEmpty = sqlite3_column_int(pStmt, 0);
+  if( p->zContentTbl ){
+    /* If using the content=xxx option, assume the table is never empty */
+    *pisEmpty = 0;
+    rc = SQLITE_OK;
+  }else{
+    rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
+    if( rc==SQLITE_OK ){
+      if( SQLITE_ROW==sqlite3_step(pStmt) ){
+        *pisEmpty = sqlite3_column_int(pStmt, 0);
+      }
+      rc = sqlite3_reset(pStmt);
     }
-    rc = sqlite3_reset(pStmt);
   }
   return rc;
 }
@@ -2787,9 +2807,9 @@ static void fts3DecodeIntArray(
 ** a blob of varints.
 */
 static void fts3InsertDocsize(
-  int *pRC,         /* Result code */
-  Fts3Table *p,     /* Table into which to insert */
-  u32 *aSz          /* Sizes of each column */
+  int *pRC,                       /* Result code */
+  Fts3Table *p,                   /* Table into which to insert */
+  u32 *aSz                        /* Sizes of each column, in tokens */
 ){
   char *pBlob;             /* The BLOB encoding of the document size */
   int nBlob;               /* Number of bytes in the BLOB */
@@ -2911,6 +2931,84 @@ static int fts3DoOptimize(Fts3Table *p, int bReturnDone){
   return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc;
 }
 
+/*
+** This function is called when the user executes the following statement:
+**
+**     INSERT INTO <tbl>(<tbl>) VALUES('rebuild');
+**
+** The entire FTS index is discarded and rebuilt. If the table is one 
+** created using the content=xxx option, then the new index is based on
+** the current contents of the xxx table. Otherwise, it is rebuilt based
+** on the contents of the %_content table.
+*/
+static int fts3DoRebuild(Fts3Table *p){
+  int rc;                         /* Return Code */
+
+  rc = fts3DeleteAll(p, 0);
+  if( rc==SQLITE_OK ){
+    u32 *aSz = 0;
+    u32 *aSzIns;
+    u32 *aSzDel;
+    sqlite3_stmt *pStmt = 0;
+    int nEntry = 0;
+
+    /* Compose and prepare an SQL statement to loop through the content table */
+    char *zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist);
+    if( !zSql ){
+      rc = SQLITE_NOMEM;
+    }else{
+      rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+      sqlite3_free(zSql);
+    }
+
+    if( rc==SQLITE_OK ){
+      int nByte = sizeof(u32) * (p->nColumn+1)*3;
+      aSz = (u32 *)sqlite3_malloc(nByte);
+      if( aSz==0 ){
+        rc = SQLITE_NOMEM;
+      }else{
+        memset(aSz, 0, nByte);
+        aSzIns = &aSz[p->nColumn+1];
+        aSzDel = &aSzIns[p->nColumn+1];
+      }
+    }
+
+    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+      int iCol;
+      rc = fts3PendingTermsDocid(p, sqlite3_column_int64(pStmt, 0));
+      aSz[p->nColumn] = 0;
+      for(iCol=0; rc==SQLITE_OK && iCol<p->nColumn; iCol++){
+        const char *z = (const char *) sqlite3_column_text(pStmt, iCol+1);
+        rc = fts3PendingTermsAdd(p, z, iCol, &aSz[iCol]);
+        aSz[p->nColumn] += sqlite3_column_bytes(pStmt, iCol+1);
+      }
+      if( p->bHasDocsize ){
+        fts3InsertDocsize(&rc, p, aSz);
+      }
+      if( rc!=SQLITE_OK ){
+        sqlite3_finalize(pStmt);
+        pStmt = 0;
+      }else{
+        nEntry++;
+        for(iCol=0; iCol<=p->nColumn; iCol++){
+          aSzIns[iCol] += aSz[iCol];
+        }
+      }
+    }
+    if( p->bHasStat ){
+      fts3UpdateDocTotals(&rc, p, aSzIns, aSzDel, nEntry);
+    }
+    sqlite3_free(aSz);
+
+    if( pStmt ){
+      assert( rc==SQLITE_OK );
+      rc = sqlite3_finalize(pStmt);
+    }
+  }
+
+  return rc;
+}
+
 /*
 ** Handle a 'special' INSERT of the form:
 **
@@ -2928,6 +3026,8 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){
     return SQLITE_NOMEM;
   }else if( nVal==8 && 0==sqlite3_strnicmp(zVal, "optimize", 8) ){
     rc = fts3DoOptimize(p, 0);
+  }else if( nVal==7 && 0==sqlite3_strnicmp(zVal, "rebuild", 7) ){
+    rc = fts3DoRebuild(p);
 #ifdef SQLITE_TEST
   }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){
     p->nNodeSize = atoi(&zVal[9]);
@@ -3099,14 +3199,18 @@ static int fts3DeleteByRowid(
       /* Deleting this row means the whole table is empty. In this case
       ** delete the contents of all three tables and throw away any
       ** data in the pendingTerms hash table.  */
-      rc = fts3DeleteAll(p);
+      rc = fts3DeleteAll(p, 1);
       *pnDoc = *pnDoc - 1;
     }else{
       sqlite3_int64 iRemove = sqlite3_value_int64(pRowid);
       rc = fts3PendingTermsDocid(p, iRemove);
       fts3DeleteTerms(&rc, p, pRowid, aSzDel);
-      fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
-      if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1;
+      if( p->zContentTbl==0 ){
+        fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
+        if( sqlite3_changes(p->db) ) *pnDoc = *pnDoc - 1;
+      }else{
+        *pnDoc = *pnDoc - 1;
+      }
       if( p->bHasDocsize ){
         fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid);
       }
@@ -3167,7 +3271,7 @@ int sqlite3Fts3UpdateMethod(
   ** detect the conflict and return SQLITE_CONSTRAINT before beginning to
   ** modify the database file.
   */
-  if( nArg>1 ){
+  if( nArg>1 && p->zContentTbl==0 ){
     /* Find the value object that holds the new rowid value. */
     sqlite3_value *pNewRowid = apVal[3+p->nColumn];
     if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
@@ -3219,7 +3323,9 @@ int sqlite3Fts3UpdateMethod(
   if( nArg>1 && rc==SQLITE_OK ){
     if( bInsertDone==0 ){
       rc = fts3InsertData(p, apVal, pRowid);
-      if( rc==SQLITE_CONSTRAINT ) rc = SQLITE_CORRUPT_VTAB;
+      if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){
+        rc = SQLITE_CORRUPT_VTAB;
+      }
     }
     if( rc==SQLITE_OK && (!isRemove || *pRowid!=iRemove) ){
       rc = fts3PendingTermsDocid(p, *pRowid);
index 916f02c3419e98788b0044eb4cd68a4f0573f31c..6ca44ed83de1658f21bb7b6267f588bb895b7f65 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Update\sMSVC\smakefile\sto\sallow\stargets\sto\sbe\sbuilt\swith\ssupport\sfor\sICU.
-D 2011-10-02T05:23:16.470
+C Add\sexperimental\s'content'\soption\sto\sFTS4.
+D 2011-10-04T11:22:59.677
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in a162fe39e249b8ed4a65ee947c30152786cfe897
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -62,22 +62,22 @@ F ext/fts2/mkfts2amal.tcl 974d5d438cb3f7c4a652639262f82418c1e4cff0
 F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
 F ext/fts3/README.tokenizers 998756696647400de63d5ba60e9655036cb966e9
 F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c 195e4da669741c1f097434ec48c0ba5739193af9
+F ext/fts3/fts3.c 626fcf477e531b470f06aae3d427abcda2d5d31c
 F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h 30063fdd0bc433b5db1532e3a363cb0f2f7e8eb3
+F ext/fts3/fts3Int.h a335d671d42ca07528a93c5b830e48e0205b26bc
 F ext/fts3/fts3_aux.c 0ebfa7b86cf8ff6a0861605fcc63b83ec1b70691
 F ext/fts3/fts3_expr.c 23791de01b3a5d313d76e02befd2601d4096bc2b
 F ext/fts3/fts3_hash.c 8dd2d06b66c72c628c2732555a32bc0943114914
 F ext/fts3/fts3_hash.h 8331fb2206c609f9fc4c4735b9ab5ad6137c88ec
 F ext/fts3/fts3_icu.c 6c8f395cdf9e1e3afa7fadb7e523dbbf381c6dfa
 F ext/fts3/fts3_porter.c 8d946908f4812c005d3d33fcbe78418b1f4eb70c
-F ext/fts3/fts3_snippet.c 58b2ba2b934c1e2a2f6ac857d7f3c7e1a14b4532
+F ext/fts3/fts3_snippet.c 8838a1de5f7df3a559596870caaa4a9895248998
 F ext/fts3/fts3_term.c a5457992723455a58804cb75c8cbd8978db5c2ef
 F ext/fts3/fts3_test.c 24fa13f330db011500acb95590da9eee24951894
 F ext/fts3/fts3_tokenizer.c 9ff7ec66ae3c5c0340fa081958e64f395c71a106
 F ext/fts3/fts3_tokenizer.h 13ffd9fcb397fec32a05ef5cd9e0fa659bf3dbd3
 F ext/fts3/fts3_tokenizer1.c 0dde8f307b8045565cf63797ba9acfaff1c50c68
-F ext/fts3/fts3_write.c 194829c8fd024a448fc899e5ff02a8ed06595529
+F ext/fts3/fts3_write.c 9e14eb54310345b441d7a8c83bc3ae474230bea6
 F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
 F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
 F ext/icu/README.txt bf8461d8cdc6b8f514c080e4e10dc3b2bbdfefa9
@@ -486,6 +486,7 @@ F test/fts3shared.test 8bb266521d7c5495c0ae522bb4d376ad5387d4a2
 F test/fts3snippet.test 8e956051221a34c7daeb504f023cb54d5fa5a8b2
 F test/fts3sort.test 9a5176c9317bb545ec5f144d62e6fedb4da6c66e
 F test/fts4aa.test 6e7f90420b837b2c685f3bcbe84c868492d40a68
+F test/fts4content.test fbafb7160eef1560b6a254b1ad5631acccd388a1
 F test/func.test 6c5ce11e3a0021ca3c0649234e2d4454c89110ca
 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
 F test/func3.test 001021e5b88bd02a3b365a5c5fd8f6f49d39744a
@@ -620,7 +621,7 @@ F test/pageropt.test 8146bf448cf09e87bb1867c2217b921fb5857806
 F test/pagesize.test 1dd51367e752e742f58e861e65ed7390603827a0
 F test/pcache.test 065aa286e722ab24f2e51792c1f093bf60656b16
 F test/pcache2.test a83efe2dec0d392f814bfc998def1d1833942025
-F test/permutations.test ad17319066a90e2db71823c3ff104795ffc71b31
+F test/permutations.test 7b1c4cb40af3712e42364c82390ace0df207b5e0
 F test/pragma.test c8108e01da04f16e67e5754e610bc62c1b993f6c
 F test/pragma2.test 3a55f82b954242c642f8342b17dffc8b47472947
 F test/printf.test 05970cde31b1a9f54bd75af60597be75a5c54fea
@@ -965,7 +966,10 @@ F tool/symbols.sh caaf6ccc7300fd43353318b44524853e222557d5
 F tool/tostr.awk e75472c2f98dd76e06b8c9c1367f4ab07e122d06
 F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
 F tool/warnings.sh b7fdb2cc525f5ef4fa43c80e771636dd3690f9d2
-P 9ddfe1e41300413bc9af7e5ce0ec9d1daf9136b1
-R 03863d4dd01ba5995289085115b33375
-U mistachkin
-Z 7dfdbd4e337132033acb3bc17ce78d60
+P eb5da5e1dbe9c198095036827318fb381441cbd0
+R 7991a3071fb34636c7b5ed4b010ad95a
+T *branch * fts4-content
+T *sym-fts4-content *
+T -sym-trunk *
+U dan
+Z 48ff718a3615a321d5bc0764cb014ed4
index c4bc7f0c61dd2afa9832e3fd615c3a2b41861cfe..be6a47c58b925298c8c761d1c168f7fd10e8826e 100644 (file)
@@ -1 +1 @@
-eb5da5e1dbe9c198095036827318fb381441cbd0
\ No newline at end of file
+1d27ea741f61c624e18bdc6a3b1c2d8574a64ddc
\ No newline at end of file
diff --git a/test/fts4content.test b/test/fts4content.test
new file mode 100644 (file)
index 0000000..6ac5a68
--- /dev/null
@@ -0,0 +1,366 @@
+# 2011 October 03
+#
+# 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.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is testing the content=xxx FTS4 option.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set ::testprefix fts4content
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts3 {
+  finish_test
+  return
+}
+
+do_execsql_test 1.1 {
+  CREATE TABLE t1(a, b, c);
+  INSERT INTO t1 VALUES('w x', 'x y', 'y z');
+  CREATE VIRTUAL TABLE ft1 USING fts4(content=t1);
+}
+
+do_execsql_test 1.2 {
+  PRAGMA table_info(ft1);
+} {
+  0 a {} 0 {} 0 
+  1 b {} 0 {} 0 
+  2 c {} 0 {} 0
+}
+
+do_execsql_test 1.3 { SELECT *, rowid FROM ft1 } {{w x} {x y} {y z} 1}
+do_execsql_test 1.4 { SELECT a, c FROM ft1 WHERE rowid=1 } {{w x} {y z}}
+
+do_execsql_test 1.5 { INSERT INTO ft1(ft1) VALUES('rebuild') } {}
+do_execsql_test 1.6 { SELECT rowid FROM ft1 WHERE ft1 MATCH 'x' } {1}
+do_execsql_test 1.7 { SELECT rowid FROM ft1 WHERE ft1 MATCH 'a' } {}
+
+#-------------------------------------------------------------------------
+# The following block of tests - 2.* - test that a content=xxx FTS table
+# can be queried. Also tested are cases where rows identified in the FTS
+# are missing from the content table, and cases where the index is 
+# inconsistent with the content table.
+# 
+do_execsql_test 2.0 {
+  CREATE TABLE t2(x);
+  INSERT INTO t2 VALUES('O S W W F U C R Q I C N P Z Y Y E Y Y E');  -- 1
+  INSERT INTO t2 VALUES('Y X U V L B E H Y J C Y A I A P V F V K');  -- 2
+  INSERT INTO t2 VALUES('P W I N J H I I N I F B K D U Q B Z S F');  -- 3
+  INSERT INTO t2 VALUES('N R O R H J R H G M D I U U B O M P A U');  -- 4
+  INSERT INTO t2 VALUES('Y O V O G T P N G T N F I V B U M J M G');  -- 5
+  INSERT INTO t2 VALUES('J O B N K N E C H Z R K J O U G M K L S');  -- 6
+  INSERT INTO t2 VALUES('S Z S R I Q U A P W R X H K C Z U L S P');  -- 7
+  INSERT INTO t2 VALUES('J C H N R C K R V N M O F Z M Z A I H W');  -- 8
+  INSERT INTO t2 VALUES('O Y G I S J U U W O D Z F J K N R P R L');  -- 9
+  INSERT INTO t2 VALUES('B G L K U R U P V X Z I H V R W C Q A S');  -- 10
+  INSERT INTO t2 VALUES('T F T J F F Y V F W N X K Q A Y L X W G');  -- 11
+  INSERT INTO t2 VALUES('C J U H B Q X L C M M Y E G V F W V Z C');  -- 12
+  INSERT INTO t2 VALUES('B W L T F S G X D P H N G M R I O A X I');  -- 13
+  INSERT INTO t2 VALUES('N G Y O K Q K Z N M H U J E D H U W R K');  -- 14
+  INSERT INTO t2 VALUES('U D T R U Y F J D S J X E H Q G V A S Z');  -- 15
+  INSERT INTO t2 VALUES('M I W P J S H R J D Q I C G P C T P H R');  -- 16
+  INSERT INTO t2 VALUES('J M N I S L X Q C A B F C B Y D H V R J');  -- 17
+  INSERT INTO t2 VALUES('F V Z W J Q L P X Y E W B U Q N H X K T');  -- 18
+  INSERT INTO t2 VALUES('R F S R Y O F Q E I E G H C B H R X Y N');  -- 19
+  INSERT INTO t2 VALUES('U Q Q Q T E P D M F X P J G H X C Q D L');  -- 20
+}
+
+do_execsql_test 2.1 {
+  CREATE VIRTUAL TABLE ft2 USING fts4(content=t2);
+  INSERT INTO ft2(ft2) VALUES('rebuild');
+
+  -- Modify the backing table a bit: Row 17 is missing and the contents 
+  -- of row 20 do not match the FTS index contents. 
+  DELETE FROM t2 WHERE rowid = 17;
+  UPDATE t2 SET x = 'a b c d e f g h i j' WHERE rowid = 20;
+}
+
+foreach {tn match rowidlist} {
+  1   {S}        {1 3 6 7 9 10 13 15 16 17 19}
+  2   {"S R"}    {7 19}
+  3   {"N K N"}  {6}
+  4   {"Q Q"}    {20}
+  5   {"B Y D"}  {17}
+} {
+  do_execsql_test 2.2.1.$tn {
+    SELECT rowid FROM ft2 WHERE ft2 MATCH $match
+  } $rowidlist
+
+  do_execsql_test 2.2.2.$tn {
+    SELECT docid FROM ft2 WHERE ft2 MATCH $match
+  } $rowidlist
+}
+
+foreach {tn match result} {
+  1   {"N K N"}  {{J O B N K N E C H Z R K J O U G M K L S}}
+  2   {"Q Q"}    {{a b c d e f g h i j}}
+  3   {"B Y D"}  {{}}
+} {
+  do_execsql_test 2.3.$tn {
+    SELECT * FROM ft2 WHERE ft2 MATCH $match
+  } $result
+}
+
+foreach {tn match result} {
+  1   {"N K N"}  {{..O B [N] [K] [N] E..}}
+  2   {"B Y D"}  {{}}
+  3   {"Q Q"}    {{a [b] [c] [d] e f..}}
+} {
+  do_execsql_test 2.4.$tn {
+    SELECT snippet(ft2, '[', ']', '..', -1, 6) FROM ft2 WHERE ft2 MATCH $match
+  } $result
+}
+
+foreach {tn match result} {
+  1   {"N K N"}  {{0 0 6 1 0 1 8 1 0 2 10 1}}
+  2   {"B Y D"}  {{}}
+  3   {"Q Q"}    {{0 0 2 1 0 0 4 1 0 1 4 1 0 1 6 1}}
+  4   {"Q D L"}  {{}}
+} {
+  do_execsql_test 2.5.$tn {
+    SELECT offsets(ft2) FROM ft2 WHERE ft2 MATCH $match
+  } $result
+}
+
+#-------------------------------------------------------------------------
+# The following block of tests - 3.* - test that the FTS index can be
+# modified by writing to the table. But that this has no effect on the 
+# content table.
+# 
+
+do_execsql_test 3.1 {
+  CREATE TABLE t3(x, y);
+  CREATE VIRTUAL TABLE ft3 USING fts4(content=t3);
+}
+
+do_catchsql_test 3.1.1 {
+  INSERT INTO ft3 VALUES('a b c', 'd e f');
+} {1 {constraint failed}}
+do_execsql_test 3.1.2 {
+  INSERT INTO ft3(docid, x, y) VALUES(21, 'a b c', 'd e f');
+  SELECT rowid FROM ft3 WHERE ft3 MATCH '"a b c"';
+} {21}
+do_execsql_test 3.1.3 { SELECT * FROM t3 } {}
+
+# This DELETE does not work, since there is no row in [t3] to base the
+# DELETE on. So the SELECT on [ft3] still returns rowid 21.
+do_execsql_test 3.1.4 { 
+  DELETE FROM ft3;
+  SELECT rowid FROM ft3 WHERE ft3 MATCH '"a b c"';
+} {21}
+
+# If the row is added to [t3] before the DELETE on [ft3], it works.
+do_execsql_test 3.1.5 {
+  INSERT INTO t3(rowid, x, y) VALUES(21, 'a b c', 'd e f');
+  DELETE FROM ft3;
+  SELECT rowid FROM ft3 WHERE ft3 MATCH '"a b c"';
+} {}
+do_execsql_test 3.1.6 { SELECT rowid FROM t3 } {21}
+
+do_execsql_test 3.2.1 {
+  INSERT INTO ft3(rowid, x, y) VALUES(0, 'R T M S M', 'A F O K H');
+  INSERT INTO ft3(rowid, x, y) VALUES(1, 'C Z J O X', 'U S Q D K');
+  INSERT INTO ft3(rowid, x, y) VALUES(2, 'N G H P O', 'N O P O C');
+  INSERT INTO ft3(rowid, x, y) VALUES(3, 'V H S D R', 'K N G E C');
+  INSERT INTO ft3(rowid, x, y) VALUES(4, 'J T R V U', 'U X S L C');
+  INSERT INTO ft3(rowid, x, y) VALUES(5, 'N A Y N G', 'X D G P Y');
+  INSERT INTO ft3(rowid, x, y) VALUES(6, 'I Q I S P', 'D R O Q B');
+  INSERT INTO ft3(rowid, x, y) VALUES(7, 'T K T Z J', 'B W D G O');
+  INSERT INTO ft3(rowid, x, y) VALUES(8, 'Y K F X T', 'D F G V G');
+  INSERT INTO ft3(rowid, x, y) VALUES(9, 'E L E T L', 'P W N F Z');
+  INSERT INTO ft3(rowid, x, y) VALUES(10, 'O G J G X', 'G J F E P');
+  INSERT INTO ft3(rowid, x, y) VALUES(11, 'O L N N Z', 'K E Z F D');
+  INSERT INTO ft3(rowid, x, y) VALUES(12, 'R Z M R J', 'X G I M Z');
+  INSERT INTO ft3(rowid, x, y) VALUES(13, 'L X N N X', 'R R N S T');
+  INSERT INTO ft3(rowid, x, y) VALUES(14, 'F L B J H', 'K W F L C');
+  INSERT INTO ft3(rowid, x, y) VALUES(15, 'P E B M V', 'E A A B U');
+  INSERT INTO ft3(rowid, x, y) VALUES(16, 'V E C F P', 'L U T V K');
+  INSERT INTO ft3(rowid, x, y) VALUES(17, 'T N O Z N', 'T P Q X N');
+  INSERT INTO ft3(rowid, x, y) VALUES(18, 'V W U W R', 'H O A A V');
+  INSERT INTO ft3(rowid, x, y) VALUES(19, 'A H N L F', 'I G H B O');
+}
+
+foreach {tn match rowidlist} {
+  1   "N A"    {5 19}
+  2   "x:O"    {1 2 10 11 17}
+  3   "y:O"    {0 2 6 7 18 19}
+} {
+  set res [list]
+  foreach rowid $rowidlist { lappend res $rowid {} {} }
+
+  do_execsql_test 3.2.2.$tn {
+    SELECT rowid, * FROM ft3 WHERE ft3 MATCH $match
+  } $res
+  do_execsql_test 3.2.3.$tn {
+    SELECT docid, * FROM ft3 WHERE ft3 MATCH $match
+  } $res
+}
+
+do_execsql_test 3.3.1 {
+  INSERT INTO t3(rowid, x, y) VALUES(0, 'R T M S M', 'A F O K H');
+  INSERT INTO t3(rowid, x, y) VALUES(1, 'C Z J O X', 'U S Q D K');
+  INSERT INTO t3(rowid, x, y) VALUES(2, 'N G H P O', 'N O P O C');
+  INSERT INTO t3(rowid, x, y) VALUES(3, 'V H S D R', 'K N G E C');
+  INSERT INTO t3(rowid, x, y) VALUES(4, 'J T R V U', 'U X S L C');
+  INSERT INTO t3(rowid, x, y) VALUES(5, 'N A Y N G', 'X D G P Y');
+  UPDATE ft3 SET x = y, y = x;
+  DELETE FROM t3;
+}
+
+foreach {tn match rowidlist} {
+  1   "N A"    {5 19}
+  2   "x:O"    {0 2 10 11 17}
+  3   "y:O"    {1 2 6 7 18 19}
+} {
+  set res [list]
+  foreach rowid $rowidlist { lappend res $rowid {} {} }
+
+  do_execsql_test 3.3.2.$tn {
+    SELECT rowid, * FROM ft3 WHERE ft3 MATCH $match
+  } $res
+  do_execsql_test 3.3.3.$tn {
+    SELECT docid, * FROM ft3 WHERE ft3 MATCH $match
+  } $res
+}
+
+do_execsql_test 3.3.1 {
+  INSERT INTO t3(rowid, x, y) VALUES(15, 'P E B M V', 'E A A B U');
+  INSERT INTO t3(rowid, x, y) VALUES(16, 'V E C F P', 'L U T V K');
+  INSERT INTO t3(rowid, x, y) VALUES(17, 'T N O Z N', 'T P Q X N');
+  INSERT INTO t3(rowid, x, y) VALUES(18, 'V W U W R', 'H O A A V');
+  INSERT INTO t3(rowid, x, y) VALUES(19, 'A H N L F', 'I G H B O');
+  DELETE FROM ft3;
+}
+
+foreach {tn match rowidlist} {
+  1   "N A"    {5}
+  2   "x:O"    {0 2 10 11}
+  3   "y:O"    {1 2 6 7}
+} {
+  set res [list]
+  foreach rowid $rowidlist { lappend res $rowid {} {} }
+
+  do_execsql_test 3.3.2.$tn {
+    SELECT rowid, * FROM ft3 WHERE ft3 MATCH $match
+  } $res
+  do_execsql_test 3.3.3.$tn {
+    SELECT docid, * FROM ft3 WHERE ft3 MATCH $match
+  } $res
+}
+
+
+#-------------------------------------------------------------------------
+# Test cases 4.* test the 'rebuild' command. On content=xxx and regular
+# FTS tables.
+# 
+do_execsql_test 4.0 {
+  CREATE TABLE t4(x);
+  CREATE VIRTUAL TABLE ft4 USING fts4(content=t4);
+  CREATE VIRTUAL TABLE ft4x USING fts4(x);
+}
+
+do_execsql_test 4.1.1 {
+  INSERT INTO ft4x(ft4x) VALUES('rebuild');
+  INSERT INTO ft4(ft4) VALUES('rebuild');
+} {}
+do_execsql_test 4.1.2 {
+  SELECT id, quote(value) FROM ft4_stat
+} {0 X'000000'}
+do_execsql_test 4.1.3 {
+  SELECT id, quote(value) FROM ft4x_stat
+} {0 X'000000'}
+
+do_execsql_test 4.2.1 {
+  INSERT INTO ft4x VALUES('M G M F T');
+  INSERT INTO ft4x VALUES('Z Q C A U');
+  INSERT INTO ft4x VALUES('N L L V');
+  INSERT INTO ft4x VALUES('T F D X D');
+  INSERT INTO ft4x VALUES('Z H I S D');
+
+  SELECT id, quote(value) FROM ft4x_stat
+} {0 X'05182B'}
+
+do_execsql_test 4.2.2 {
+  INSERT INTO ft4(rowid, x) SELECT rowid, * FROM ft4x;
+  SELECT id, quote(value) FROM ft4_stat
+} {0 X'05182B'}
+
+do_execsql_test 4.2.3 {
+  SELECT docid, quote(size) FROM ft4_docsize
+} {1 X'05' 2 X'05' 3 X'04' 4 X'05' 5 X'05'}
+
+do_execsql_test 4.2.4 {
+  INSERT INTO ft4x(ft4x) VALUES('rebuild');
+  SELECT id, quote(value) FROM ft4x_stat;
+  SELECT docid, quote(size) FROM ft4x_docsize
+} {0 X'05182B' 1 X'05' 2 X'05' 3 X'04' 4 X'05' 5 X'05'}
+
+do_execsql_test 4.2.5 {
+  INSERT INTO ft4(ft4) VALUES('rebuild');
+  SELECT id, quote(value) FROM ft4_stat;
+  SELECT docid, quote(size) FROM ft4_docsize
+} {0 X'000000'}
+
+do_execsql_test 4.2.6 {
+  INSERT INTO t4(rowid, x) SELECT rowid, x FROM ft4x;
+  INSERT INTO ft4(ft4) VALUES('rebuild');
+  SELECT id, quote(value) FROM ft4_stat;
+  SELECT docid, quote(size) FROM ft4_docsize
+} {0 X'05182B' 1 X'05' 2 X'05' 3 X'04' 4 X'05' 5 X'05'}
+
+
+#-------------------------------------------------------------------------
+# Test cases 5.* test that the following commands do not create/move or
+# delete a %_content table when used with a content=xxx FTS table.
+# 
+do_execsql_test 5.1.1 {
+  CREATE TABLE t5(a, b, c, d);
+  CREATE VIRTUAL TABLE ft5 USING fts4(content=t5);
+  SELECT name FROM sqlite_master WHERE name LIKE '%t5%';
+} {
+  t5 ft5 ft5_segments ft5_segdir 
+  sqlite_autoindex_ft5_segdir_1 ft5_docsize ft5_stat
+}
+do_execsql_test 5.1.2 {
+  ALTER TABLE ft5 RENAME TO ft6;
+  SELECT name FROM sqlite_master WHERE name LIKE '%t5%';
+} {
+  t5
+}
+do_execsql_test 5.1.3 {
+  SELECT name FROM sqlite_master WHERE name LIKE '%t6%';
+} {
+  ft6 ft6_segments ft6_segdir 
+  sqlite_autoindex_ft6_segdir_1 ft6_docsize ft6_stat
+}
+do_execsql_test 5.1.4 {
+  INSERT INTO t5 VALUES('a', 'b', 'c', 'd');
+  INSERT INTO ft6(ft6) VALUES('rebuild');
+  SELECT rowid FROM ft6 WHERE ft6 MATCH 'b';
+} {1}
+do_execsql_test 5.1.5 {
+  DROP TABLE ft6;
+  SELECT * FROM t5;
+} {a b c d}
+do_execsql_test 5.1.6 {
+  SELECT name FROM sqlite_master WHERE name LIKE '%t6%';
+} {
+}
+do_execsql_test 5.1.7 {
+  CREATE VIRTUAL TABLE ft5 USING fts4(content=t5);
+  CREATE TABLE t5_content(a, b);
+  DROP TABLE ft5;
+  SELECT name FROM sqlite_master WHERE name LIKE '%t5%';
+} {
+  t5 t5_content
+}
+
+finish_test
index c911e6f06213c254c735d76593712d931025e50f..9ae881ba23edaab50e0459e2ed449490c5952dfc 100644 (file)
@@ -180,10 +180,9 @@ test_suite "fts3" -prefix "" -description {
   fts3defer.test fts3defer2.test fts3e.test fts3expr.test fts3expr2.test 
   fts3near.test fts3query.test fts3shared.test fts3snippet.test 
   fts3sort.test
-
   fts3fault.test fts3malloc.test fts3matchinfo.test
-
   fts3aux1.test fts3comp1.test fts3auto.test
+  fts4aa.test fts4content.test
 }