modernc.org/cc@v1.0.1/v2/testdata/_sqlite/ext/fts5/fts5_storage.c (about)

     1  /*
     2  ** 2014 May 31
     3  **
     4  ** The author disclaims copyright to this source code.  In place of
     5  ** a legal notice, here is a blessing:
     6  **
     7  **    May you do good and not evil.
     8  **    May you find forgiveness for yourself and forgive others.
     9  **    May you share freely, never taking more than you give.
    10  **
    11  ******************************************************************************
    12  **
    13  */
    14  
    15  
    16  
    17  #include "fts5Int.h"
    18  
    19  struct Fts5Storage {
    20    Fts5Config *pConfig;
    21    Fts5Index *pIndex;
    22    int bTotalsValid;               /* True if nTotalRow/aTotalSize[] are valid */
    23    i64 nTotalRow;                  /* Total number of rows in FTS table */
    24    i64 *aTotalSize;                /* Total sizes of each column */ 
    25    sqlite3_stmt *aStmt[11];
    26  };
    27  
    28  
    29  #if FTS5_STMT_SCAN_ASC!=0 
    30  # error "FTS5_STMT_SCAN_ASC mismatch" 
    31  #endif
    32  #if FTS5_STMT_SCAN_DESC!=1 
    33  # error "FTS5_STMT_SCAN_DESC mismatch" 
    34  #endif
    35  #if FTS5_STMT_LOOKUP!=2
    36  # error "FTS5_STMT_LOOKUP mismatch" 
    37  #endif
    38  
    39  #define FTS5_STMT_INSERT_CONTENT  3
    40  #define FTS5_STMT_REPLACE_CONTENT 4
    41  #define FTS5_STMT_DELETE_CONTENT  5
    42  #define FTS5_STMT_REPLACE_DOCSIZE  6
    43  #define FTS5_STMT_DELETE_DOCSIZE  7
    44  #define FTS5_STMT_LOOKUP_DOCSIZE  8
    45  #define FTS5_STMT_REPLACE_CONFIG 9
    46  #define FTS5_STMT_SCAN 10
    47  
    48  /*
    49  ** Prepare the two insert statements - Fts5Storage.pInsertContent and
    50  ** Fts5Storage.pInsertDocsize - if they have not already been prepared.
    51  ** Return SQLITE_OK if successful, or an SQLite error code if an error
    52  ** occurs.
    53  */
    54  static int fts5StorageGetStmt(
    55    Fts5Storage *p,                 /* Storage handle */
    56    int eStmt,                      /* FTS5_STMT_XXX constant */
    57    sqlite3_stmt **ppStmt,          /* OUT: Prepared statement handle */
    58    char **pzErrMsg                 /* OUT: Error message (if any) */
    59  ){
    60    int rc = SQLITE_OK;
    61  
    62    /* If there is no %_docsize table, there should be no requests for 
    63    ** statements to operate on it.  */
    64    assert( p->pConfig->bColumnsize || (
    65          eStmt!=FTS5_STMT_REPLACE_DOCSIZE 
    66       && eStmt!=FTS5_STMT_DELETE_DOCSIZE 
    67       && eStmt!=FTS5_STMT_LOOKUP_DOCSIZE 
    68    ));
    69  
    70    assert( eStmt>=0 && eStmt<ArraySize(p->aStmt) );
    71    if( p->aStmt[eStmt]==0 ){
    72      const char *azStmt[] = {
    73        "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC",
    74        "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC",
    75        "SELECT %s FROM %s T WHERE T.%Q=?",               /* LOOKUP  */
    76  
    77        "INSERT INTO %Q.'%q_content' VALUES(%s)",         /* INSERT_CONTENT  */
    78        "REPLACE INTO %Q.'%q_content' VALUES(%s)",        /* REPLACE_CONTENT */
    79        "DELETE FROM %Q.'%q_content' WHERE id=?",         /* DELETE_CONTENT  */
    80        "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)",       /* REPLACE_DOCSIZE  */
    81        "DELETE FROM %Q.'%q_docsize' WHERE id=?",         /* DELETE_DOCSIZE  */
    82  
    83        "SELECT sz FROM %Q.'%q_docsize' WHERE id=?",      /* LOOKUP_DOCSIZE  */
    84  
    85        "REPLACE INTO %Q.'%q_config' VALUES(?,?)",        /* REPLACE_CONFIG */
    86        "SELECT %s FROM %s AS T",                         /* SCAN */
    87      };
    88      Fts5Config *pC = p->pConfig;
    89      char *zSql = 0;
    90  
    91      switch( eStmt ){
    92        case FTS5_STMT_SCAN:
    93          zSql = sqlite3_mprintf(azStmt[eStmt], 
    94              pC->zContentExprlist, pC->zContent
    95          );
    96          break;
    97  
    98        case FTS5_STMT_SCAN_ASC:
    99        case FTS5_STMT_SCAN_DESC:
   100          zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, 
   101              pC->zContent, pC->zContentRowid, pC->zContentRowid,
   102              pC->zContentRowid
   103          );
   104          break;
   105  
   106        case FTS5_STMT_LOOKUP:
   107          zSql = sqlite3_mprintf(azStmt[eStmt], 
   108              pC->zContentExprlist, pC->zContent, pC->zContentRowid
   109          );
   110          break;
   111  
   112        case FTS5_STMT_INSERT_CONTENT: 
   113        case FTS5_STMT_REPLACE_CONTENT: {
   114          int nCol = pC->nCol + 1;
   115          char *zBind;
   116          int i;
   117  
   118          zBind = sqlite3_malloc(1 + nCol*2);
   119          if( zBind ){
   120            for(i=0; i<nCol; i++){
   121              zBind[i*2] = '?';
   122              zBind[i*2 + 1] = ',';
   123            }
   124            zBind[i*2-1] = '\0';
   125            zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind);
   126            sqlite3_free(zBind);
   127          }
   128          break;
   129        }
   130  
   131        default:
   132          zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName);
   133          break;
   134      }
   135  
   136      if( zSql==0 ){
   137        rc = SQLITE_NOMEM;
   138      }else{
   139        rc = sqlite3_prepare_v3(pC->db, zSql, -1,
   140                                SQLITE_PREPARE_PERSISTENT, &p->aStmt[eStmt], 0);
   141        sqlite3_free(zSql);
   142        if( rc!=SQLITE_OK && pzErrMsg ){
   143          *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db));
   144        }
   145      }
   146    }
   147  
   148    *ppStmt = p->aStmt[eStmt];
   149    sqlite3_reset(*ppStmt);
   150    return rc;
   151  }
   152  
   153  
   154  static int fts5ExecPrintf(
   155    sqlite3 *db,
   156    char **pzErr,
   157    const char *zFormat,
   158    ...
   159  ){
   160    int rc;
   161    va_list ap;                     /* ... printf arguments */
   162    char *zSql;
   163  
   164    va_start(ap, zFormat);
   165    zSql = sqlite3_vmprintf(zFormat, ap);
   166  
   167    if( zSql==0 ){
   168      rc = SQLITE_NOMEM;
   169    }else{
   170      rc = sqlite3_exec(db, zSql, 0, 0, pzErr);
   171      sqlite3_free(zSql);
   172    }
   173  
   174    va_end(ap);
   175    return rc;
   176  }
   177  
   178  /*
   179  ** Drop all shadow tables. Return SQLITE_OK if successful or an SQLite error
   180  ** code otherwise.
   181  */
   182  int sqlite3Fts5DropAll(Fts5Config *pConfig){
   183    int rc = fts5ExecPrintf(pConfig->db, 0, 
   184        "DROP TABLE IF EXISTS %Q.'%q_data';"
   185        "DROP TABLE IF EXISTS %Q.'%q_idx';"
   186        "DROP TABLE IF EXISTS %Q.'%q_config';",
   187        pConfig->zDb, pConfig->zName,
   188        pConfig->zDb, pConfig->zName,
   189        pConfig->zDb, pConfig->zName
   190    );
   191    if( rc==SQLITE_OK && pConfig->bColumnsize ){
   192      rc = fts5ExecPrintf(pConfig->db, 0, 
   193          "DROP TABLE IF EXISTS %Q.'%q_docsize';",
   194          pConfig->zDb, pConfig->zName
   195      );
   196    }
   197    if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
   198      rc = fts5ExecPrintf(pConfig->db, 0, 
   199          "DROP TABLE IF EXISTS %Q.'%q_content';",
   200          pConfig->zDb, pConfig->zName
   201      );
   202    }
   203    return rc;
   204  }
   205  
   206  static void fts5StorageRenameOne(
   207    Fts5Config *pConfig,            /* Current FTS5 configuration */
   208    int *pRc,                       /* IN/OUT: Error code */
   209    const char *zTail,              /* Tail of table name e.g. "data", "config" */
   210    const char *zName               /* New name of FTS5 table */
   211  ){
   212    if( *pRc==SQLITE_OK ){
   213      *pRc = fts5ExecPrintf(pConfig->db, 0, 
   214          "ALTER TABLE %Q.'%q_%s' RENAME TO '%q_%s';",
   215          pConfig->zDb, pConfig->zName, zTail, zName, zTail
   216      );
   217    }
   218  }
   219  
   220  int sqlite3Fts5StorageRename(Fts5Storage *pStorage, const char *zName){
   221    Fts5Config *pConfig = pStorage->pConfig;
   222    int rc = sqlite3Fts5StorageSync(pStorage);
   223  
   224    fts5StorageRenameOne(pConfig, &rc, "data", zName);
   225    fts5StorageRenameOne(pConfig, &rc, "idx", zName);
   226    fts5StorageRenameOne(pConfig, &rc, "config", zName);
   227    if( pConfig->bColumnsize ){
   228      fts5StorageRenameOne(pConfig, &rc, "docsize", zName);
   229    }
   230    if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
   231      fts5StorageRenameOne(pConfig, &rc, "content", zName);
   232    }
   233    return rc;
   234  }
   235  
   236  /*
   237  ** Create the shadow table named zPost, with definition zDefn. Return
   238  ** SQLITE_OK if successful, or an SQLite error code otherwise.
   239  */
   240  int sqlite3Fts5CreateTable(
   241    Fts5Config *pConfig,            /* FTS5 configuration */
   242    const char *zPost,              /* Shadow table to create (e.g. "content") */
   243    const char *zDefn,              /* Columns etc. for shadow table */
   244    int bWithout,                   /* True for without rowid */
   245    char **pzErr                    /* OUT: Error message */
   246  ){
   247    int rc;
   248    char *zErr = 0;
   249  
   250    rc = fts5ExecPrintf(pConfig->db, &zErr, "CREATE TABLE %Q.'%q_%q'(%s)%s",
   251        pConfig->zDb, pConfig->zName, zPost, zDefn, 
   252  #ifndef SQLITE_FTS5_NO_WITHOUT_ROWID
   253        bWithout?" WITHOUT ROWID":
   254  #endif
   255        ""
   256    );
   257    if( zErr ){
   258      *pzErr = sqlite3_mprintf(
   259          "fts5: error creating shadow table %q_%s: %s", 
   260          pConfig->zName, zPost, zErr
   261      );
   262      sqlite3_free(zErr);
   263    }
   264  
   265    return rc;
   266  }
   267  
   268  /*
   269  ** Open a new Fts5Index handle. If the bCreate argument is true, create
   270  ** and initialize the underlying tables 
   271  **
   272  ** If successful, set *pp to point to the new object and return SQLITE_OK.
   273  ** Otherwise, set *pp to NULL and return an SQLite error code.
   274  */
   275  int sqlite3Fts5StorageOpen(
   276    Fts5Config *pConfig, 
   277    Fts5Index *pIndex, 
   278    int bCreate, 
   279    Fts5Storage **pp,
   280    char **pzErr                    /* OUT: Error message */
   281  ){
   282    int rc = SQLITE_OK;
   283    Fts5Storage *p;                 /* New object */
   284    int nByte;                      /* Bytes of space to allocate */
   285  
   286    nByte = sizeof(Fts5Storage)               /* Fts5Storage object */
   287          + pConfig->nCol * sizeof(i64);      /* Fts5Storage.aTotalSize[] */
   288    *pp = p = (Fts5Storage*)sqlite3_malloc(nByte);
   289    if( !p ) return SQLITE_NOMEM;
   290  
   291    memset(p, 0, nByte);
   292    p->aTotalSize = (i64*)&p[1];
   293    p->pConfig = pConfig;
   294    p->pIndex = pIndex;
   295  
   296    if( bCreate ){
   297      if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
   298        int nDefn = 32 + pConfig->nCol*10;
   299        char *zDefn = sqlite3_malloc(32 + pConfig->nCol * 10);
   300        if( zDefn==0 ){
   301          rc = SQLITE_NOMEM;
   302        }else{
   303          int i;
   304          int iOff;
   305          sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY");
   306          iOff = (int)strlen(zDefn);
   307          for(i=0; i<pConfig->nCol; i++){
   308            sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i);
   309            iOff += (int)strlen(&zDefn[iOff]);
   310          }
   311          rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr);
   312        }
   313        sqlite3_free(zDefn);
   314      }
   315  
   316      if( rc==SQLITE_OK && pConfig->bColumnsize ){
   317        rc = sqlite3Fts5CreateTable(
   318            pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr
   319        );
   320      }
   321      if( rc==SQLITE_OK ){
   322        rc = sqlite3Fts5CreateTable(
   323            pConfig, "config", "k PRIMARY KEY, v", 1, pzErr
   324        );
   325      }
   326      if( rc==SQLITE_OK ){
   327        rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
   328      }
   329    }
   330  
   331    if( rc ){
   332      sqlite3Fts5StorageClose(p);
   333      *pp = 0;
   334    }
   335    return rc;
   336  }
   337  
   338  /*
   339  ** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen().
   340  */
   341  int sqlite3Fts5StorageClose(Fts5Storage *p){
   342    int rc = SQLITE_OK;
   343    if( p ){
   344      int i;
   345  
   346      /* Finalize all SQL statements */
   347      for(i=0; i<ArraySize(p->aStmt); i++){
   348        sqlite3_finalize(p->aStmt[i]);
   349      }
   350  
   351      sqlite3_free(p);
   352    }
   353    return rc;
   354  }
   355  
   356  typedef struct Fts5InsertCtx Fts5InsertCtx;
   357  struct Fts5InsertCtx {
   358    Fts5Storage *pStorage;
   359    int iCol;
   360    int szCol;                      /* Size of column value in tokens */
   361  };
   362  
   363  /*
   364  ** Tokenization callback used when inserting tokens into the FTS index.
   365  */
   366  static int fts5StorageInsertCallback(
   367    void *pContext,                 /* Pointer to Fts5InsertCtx object */
   368    int tflags,
   369    const char *pToken,             /* Buffer containing token */
   370    int nToken,                     /* Size of token in bytes */
   371    int iUnused1,                   /* Start offset of token */
   372    int iUnused2                    /* End offset of token */
   373  ){
   374    Fts5InsertCtx *pCtx = (Fts5InsertCtx*)pContext;
   375    Fts5Index *pIdx = pCtx->pStorage->pIndex;
   376    UNUSED_PARAM2(iUnused1, iUnused2);
   377    if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
   378    if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
   379      pCtx->szCol++;
   380    }
   381    return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken);
   382  }
   383  
   384  /*
   385  ** If a row with rowid iDel is present in the %_content table, add the
   386  ** delete-markers to the FTS index necessary to delete it. Do not actually
   387  ** remove the %_content row at this time though.
   388  */
   389  static int fts5StorageDeleteFromIndex(
   390    Fts5Storage *p, 
   391    i64 iDel, 
   392    sqlite3_value **apVal
   393  ){
   394    Fts5Config *pConfig = p->pConfig;
   395    sqlite3_stmt *pSeek = 0;        /* SELECT to read row iDel from %_data */
   396    int rc;                         /* Return code */
   397    int rc2;                        /* sqlite3_reset() return code */
   398    int iCol;
   399    Fts5InsertCtx ctx;
   400  
   401    if( apVal==0 ){
   402      rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0);
   403      if( rc!=SQLITE_OK ) return rc;
   404      sqlite3_bind_int64(pSeek, 1, iDel);
   405      if( sqlite3_step(pSeek)!=SQLITE_ROW ){
   406        return sqlite3_reset(pSeek);
   407      }
   408    }
   409  
   410    ctx.pStorage = p;
   411    ctx.iCol = -1;
   412    rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel);
   413    for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){
   414      if( pConfig->abUnindexed[iCol-1]==0 ){
   415        const char *zText;
   416        int nText;
   417        if( pSeek ){
   418          zText = (const char*)sqlite3_column_text(pSeek, iCol);
   419          nText = sqlite3_column_bytes(pSeek, iCol);
   420        }else{
   421          zText = (const char*)sqlite3_value_text(apVal[iCol-1]);
   422          nText = sqlite3_value_bytes(apVal[iCol-1]);
   423        }
   424        ctx.szCol = 0;
   425        rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, 
   426            zText, nText, (void*)&ctx, fts5StorageInsertCallback
   427        );
   428        p->aTotalSize[iCol-1] -= (i64)ctx.szCol;
   429      }
   430    }
   431    p->nTotalRow--;
   432  
   433    rc2 = sqlite3_reset(pSeek);
   434    if( rc==SQLITE_OK ) rc = rc2;
   435    return rc;
   436  }
   437  
   438  
   439  /*
   440  ** Insert a record into the %_docsize table. Specifically, do:
   441  **
   442  **   INSERT OR REPLACE INTO %_docsize(id, sz) VALUES(iRowid, pBuf);
   443  **
   444  ** If there is no %_docsize table (as happens if the columnsize=0 option
   445  ** is specified when the FTS5 table is created), this function is a no-op.
   446  */
   447  static int fts5StorageInsertDocsize(
   448    Fts5Storage *p,                 /* Storage module to write to */
   449    i64 iRowid,                     /* id value */
   450    Fts5Buffer *pBuf                /* sz value */
   451  ){
   452    int rc = SQLITE_OK;
   453    if( p->pConfig->bColumnsize ){
   454      sqlite3_stmt *pReplace = 0;
   455      rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
   456      if( rc==SQLITE_OK ){
   457        sqlite3_bind_int64(pReplace, 1, iRowid);
   458        sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC);
   459        sqlite3_step(pReplace);
   460        rc = sqlite3_reset(pReplace);
   461      }
   462    }
   463    return rc;
   464  }
   465  
   466  /*
   467  ** Load the contents of the "averages" record from disk into the 
   468  ** p->nTotalRow and p->aTotalSize[] variables. If successful, and if
   469  ** argument bCache is true, set the p->bTotalsValid flag to indicate
   470  ** that the contents of aTotalSize[] and nTotalRow are valid until
   471  ** further notice.
   472  **
   473  ** Return SQLITE_OK if successful, or an SQLite error code if an error
   474  ** occurs.
   475  */
   476  static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){
   477    int rc = SQLITE_OK;
   478    if( p->bTotalsValid==0 ){
   479      rc = sqlite3Fts5IndexGetAverages(p->pIndex, &p->nTotalRow, p->aTotalSize);
   480      p->bTotalsValid = bCache;
   481    }
   482    return rc;
   483  }
   484  
   485  /*
   486  ** Store the current contents of the p->nTotalRow and p->aTotalSize[] 
   487  ** variables in the "averages" record on disk.
   488  **
   489  ** Return SQLITE_OK if successful, or an SQLite error code if an error
   490  ** occurs.
   491  */
   492  static int fts5StorageSaveTotals(Fts5Storage *p){
   493    int nCol = p->pConfig->nCol;
   494    int i;
   495    Fts5Buffer buf;
   496    int rc = SQLITE_OK;
   497    memset(&buf, 0, sizeof(buf));
   498  
   499    sqlite3Fts5BufferAppendVarint(&rc, &buf, p->nTotalRow);
   500    for(i=0; i<nCol; i++){
   501      sqlite3Fts5BufferAppendVarint(&rc, &buf, p->aTotalSize[i]);
   502    }
   503    if( rc==SQLITE_OK ){
   504      rc = sqlite3Fts5IndexSetAverages(p->pIndex, buf.p, buf.n);
   505    }
   506    sqlite3_free(buf.p);
   507  
   508    return rc;
   509  }
   510  
   511  /*
   512  ** Remove a row from the FTS table.
   513  */
   514  int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){
   515    Fts5Config *pConfig = p->pConfig;
   516    int rc;
   517    sqlite3_stmt *pDel = 0;
   518  
   519    assert( pConfig->eContent!=FTS5_CONTENT_NORMAL || apVal==0 );
   520    rc = fts5StorageLoadTotals(p, 1);
   521  
   522    /* Delete the index records */
   523    if( rc==SQLITE_OK ){
   524      rc = fts5StorageDeleteFromIndex(p, iDel, apVal);
   525    }
   526  
   527    /* Delete the %_docsize record */
   528    if( rc==SQLITE_OK && pConfig->bColumnsize ){
   529      rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_DOCSIZE, &pDel, 0);
   530      if( rc==SQLITE_OK ){
   531        sqlite3_bind_int64(pDel, 1, iDel);
   532        sqlite3_step(pDel);
   533        rc = sqlite3_reset(pDel);
   534      }
   535    }
   536  
   537    /* Delete the %_content record */
   538    if( pConfig->eContent==FTS5_CONTENT_NORMAL ){
   539      if( rc==SQLITE_OK ){
   540        rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0);
   541      }
   542      if( rc==SQLITE_OK ){
   543        sqlite3_bind_int64(pDel, 1, iDel);
   544        sqlite3_step(pDel);
   545        rc = sqlite3_reset(pDel);
   546      }
   547    }
   548  
   549    return rc;
   550  }
   551  
   552  /*
   553  ** Delete all entries in the FTS5 index.
   554  */
   555  int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){
   556    Fts5Config *pConfig = p->pConfig;
   557    int rc;
   558  
   559    /* Delete the contents of the %_data and %_docsize tables. */
   560    rc = fts5ExecPrintf(pConfig->db, 0,
   561        "DELETE FROM %Q.'%q_data';" 
   562        "DELETE FROM %Q.'%q_idx';",
   563        pConfig->zDb, pConfig->zName,
   564        pConfig->zDb, pConfig->zName
   565    );
   566    if( rc==SQLITE_OK && pConfig->bColumnsize ){
   567      rc = fts5ExecPrintf(pConfig->db, 0,
   568          "DELETE FROM %Q.'%q_docsize';",
   569          pConfig->zDb, pConfig->zName
   570      );
   571    }
   572  
   573    /* Reinitialize the %_data table. This call creates the initial structure
   574    ** and averages records.  */
   575    if( rc==SQLITE_OK ){
   576      rc = sqlite3Fts5IndexReinit(p->pIndex);
   577    }
   578    if( rc==SQLITE_OK ){
   579      rc = sqlite3Fts5StorageConfigValue(p, "version", 0, FTS5_CURRENT_VERSION);
   580    }
   581    return rc;
   582  }
   583  
   584  int sqlite3Fts5StorageRebuild(Fts5Storage *p){
   585    Fts5Buffer buf = {0,0,0};
   586    Fts5Config *pConfig = p->pConfig;
   587    sqlite3_stmt *pScan = 0;
   588    Fts5InsertCtx ctx;
   589    int rc;
   590  
   591    memset(&ctx, 0, sizeof(Fts5InsertCtx));
   592    ctx.pStorage = p;
   593    rc = sqlite3Fts5StorageDeleteAll(p);
   594    if( rc==SQLITE_OK ){
   595      rc = fts5StorageLoadTotals(p, 1);
   596    }
   597  
   598    if( rc==SQLITE_OK ){
   599      rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0);
   600    }
   601  
   602    while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){
   603      i64 iRowid = sqlite3_column_int64(pScan, 0);
   604  
   605      sqlite3Fts5BufferZero(&buf);
   606      rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
   607      for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
   608        ctx.szCol = 0;
   609        if( pConfig->abUnindexed[ctx.iCol]==0 ){
   610          rc = sqlite3Fts5Tokenize(pConfig, 
   611              FTS5_TOKENIZE_DOCUMENT,
   612              (const char*)sqlite3_column_text(pScan, ctx.iCol+1),
   613              sqlite3_column_bytes(pScan, ctx.iCol+1),
   614              (void*)&ctx,
   615              fts5StorageInsertCallback
   616          );
   617        }
   618        sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
   619        p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
   620      }
   621      p->nTotalRow++;
   622  
   623      if( rc==SQLITE_OK ){
   624        rc = fts5StorageInsertDocsize(p, iRowid, &buf);
   625      }
   626    }
   627    sqlite3_free(buf.p);
   628  
   629    /* Write the averages record */
   630    if( rc==SQLITE_OK ){
   631      rc = fts5StorageSaveTotals(p);
   632    }
   633    return rc;
   634  }
   635  
   636  int sqlite3Fts5StorageOptimize(Fts5Storage *p){
   637    return sqlite3Fts5IndexOptimize(p->pIndex);
   638  }
   639  
   640  int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){
   641    return sqlite3Fts5IndexMerge(p->pIndex, nMerge);
   642  }
   643  
   644  int sqlite3Fts5StorageReset(Fts5Storage *p){
   645    return sqlite3Fts5IndexReset(p->pIndex);
   646  }
   647  
   648  /*
   649  ** Allocate a new rowid. This is used for "external content" tables when
   650  ** a NULL value is inserted into the rowid column. The new rowid is allocated
   651  ** by inserting a dummy row into the %_docsize table. The dummy will be
   652  ** overwritten later.
   653  **
   654  ** If the %_docsize table does not exist, SQLITE_MISMATCH is returned. In
   655  ** this case the user is required to provide a rowid explicitly.
   656  */
   657  static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){
   658    int rc = SQLITE_MISMATCH;
   659    if( p->pConfig->bColumnsize ){
   660      sqlite3_stmt *pReplace = 0;
   661      rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0);
   662      if( rc==SQLITE_OK ){
   663        sqlite3_bind_null(pReplace, 1);
   664        sqlite3_bind_null(pReplace, 2);
   665        sqlite3_step(pReplace);
   666        rc = sqlite3_reset(pReplace);
   667      }
   668      if( rc==SQLITE_OK ){
   669        *piRowid = sqlite3_last_insert_rowid(p->pConfig->db);
   670      }
   671    }
   672    return rc;
   673  }
   674  
   675  /*
   676  ** Insert a new row into the FTS content table.
   677  */
   678  int sqlite3Fts5StorageContentInsert(
   679    Fts5Storage *p, 
   680    sqlite3_value **apVal, 
   681    i64 *piRowid
   682  ){
   683    Fts5Config *pConfig = p->pConfig;
   684    int rc = SQLITE_OK;
   685  
   686    /* Insert the new row into the %_content table. */
   687    if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){
   688      if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){
   689        *piRowid = sqlite3_value_int64(apVal[1]);
   690      }else{
   691        rc = fts5StorageNewRowid(p, piRowid);
   692      }
   693    }else{
   694      sqlite3_stmt *pInsert = 0;    /* Statement to write %_content table */
   695      int i;                        /* Counter variable */
   696      rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0);
   697      for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){
   698        rc = sqlite3_bind_value(pInsert, i, apVal[i]);
   699      }
   700      if( rc==SQLITE_OK ){
   701        sqlite3_step(pInsert);
   702        rc = sqlite3_reset(pInsert);
   703      }
   704      *piRowid = sqlite3_last_insert_rowid(pConfig->db);
   705    }
   706  
   707    return rc;
   708  }
   709  
   710  /*
   711  ** Insert new entries into the FTS index and %_docsize table.
   712  */
   713  int sqlite3Fts5StorageIndexInsert(
   714    Fts5Storage *p, 
   715    sqlite3_value **apVal, 
   716    i64 iRowid
   717  ){
   718    Fts5Config *pConfig = p->pConfig;
   719    int rc = SQLITE_OK;             /* Return code */
   720    Fts5InsertCtx ctx;              /* Tokenization callback context object */
   721    Fts5Buffer buf;                 /* Buffer used to build up %_docsize blob */
   722  
   723    memset(&buf, 0, sizeof(Fts5Buffer));
   724    ctx.pStorage = p;
   725    rc = fts5StorageLoadTotals(p, 1);
   726  
   727    if( rc==SQLITE_OK ){
   728      rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 0, iRowid);
   729    }
   730    for(ctx.iCol=0; rc==SQLITE_OK && ctx.iCol<pConfig->nCol; ctx.iCol++){
   731      ctx.szCol = 0;
   732      if( pConfig->abUnindexed[ctx.iCol]==0 ){
   733        rc = sqlite3Fts5Tokenize(pConfig, 
   734            FTS5_TOKENIZE_DOCUMENT,
   735            (const char*)sqlite3_value_text(apVal[ctx.iCol+2]),
   736            sqlite3_value_bytes(apVal[ctx.iCol+2]),
   737            (void*)&ctx,
   738            fts5StorageInsertCallback
   739        );
   740      }
   741      sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol);
   742      p->aTotalSize[ctx.iCol] += (i64)ctx.szCol;
   743    }
   744    p->nTotalRow++;
   745  
   746    /* Write the %_docsize record */
   747    if( rc==SQLITE_OK ){
   748      rc = fts5StorageInsertDocsize(p, iRowid, &buf);
   749    }
   750    sqlite3_free(buf.p);
   751  
   752    return rc;
   753  }
   754  
   755  static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){
   756    Fts5Config *pConfig = p->pConfig;
   757    char *zSql;
   758    int rc;
   759  
   760    zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'", 
   761        pConfig->zDb, pConfig->zName, zSuffix
   762    );
   763    if( zSql==0 ){
   764      rc = SQLITE_NOMEM;
   765    }else{
   766      sqlite3_stmt *pCnt = 0;
   767      rc = sqlite3_prepare_v2(pConfig->db, zSql, -1, &pCnt, 0);
   768      if( rc==SQLITE_OK ){
   769        if( SQLITE_ROW==sqlite3_step(pCnt) ){
   770          *pnRow = sqlite3_column_int64(pCnt, 0);
   771        }
   772        rc = sqlite3_finalize(pCnt);
   773      }
   774    }
   775  
   776    sqlite3_free(zSql);
   777    return rc;
   778  }
   779  
   780  /*
   781  ** Context object used by sqlite3Fts5StorageIntegrity().
   782  */
   783  typedef struct Fts5IntegrityCtx Fts5IntegrityCtx;
   784  struct Fts5IntegrityCtx {
   785    i64 iRowid;
   786    int iCol;
   787    int szCol;
   788    u64 cksum;
   789    Fts5Termset *pTermset;
   790    Fts5Config *pConfig;
   791  };
   792  
   793  
   794  /*
   795  ** Tokenization callback used by integrity check.
   796  */
   797  static int fts5StorageIntegrityCallback(
   798    void *pContext,                 /* Pointer to Fts5IntegrityCtx object */
   799    int tflags,
   800    const char *pToken,             /* Buffer containing token */
   801    int nToken,                     /* Size of token in bytes */
   802    int iUnused1,                   /* Start offset of token */
   803    int iUnused2                    /* End offset of token */
   804  ){
   805    Fts5IntegrityCtx *pCtx = (Fts5IntegrityCtx*)pContext;
   806    Fts5Termset *pTermset = pCtx->pTermset;
   807    int bPresent;
   808    int ii;
   809    int rc = SQLITE_OK;
   810    int iPos;
   811    int iCol;
   812  
   813    UNUSED_PARAM2(iUnused1, iUnused2);
   814    if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE;
   815  
   816    if( (tflags & FTS5_TOKEN_COLOCATED)==0 || pCtx->szCol==0 ){
   817      pCtx->szCol++;
   818    }
   819  
   820    switch( pCtx->pConfig->eDetail ){
   821      case FTS5_DETAIL_FULL:
   822        iPos = pCtx->szCol-1;
   823        iCol = pCtx->iCol;
   824        break;
   825  
   826      case FTS5_DETAIL_COLUMNS:
   827        iPos = pCtx->iCol;
   828        iCol = 0;
   829        break;
   830  
   831      default:
   832        assert( pCtx->pConfig->eDetail==FTS5_DETAIL_NONE );
   833        iPos = 0;
   834        iCol = 0;
   835        break;
   836    }
   837  
   838    rc = sqlite3Fts5TermsetAdd(pTermset, 0, pToken, nToken, &bPresent);
   839    if( rc==SQLITE_OK && bPresent==0 ){
   840      pCtx->cksum ^= sqlite3Fts5IndexEntryCksum(
   841          pCtx->iRowid, iCol, iPos, 0, pToken, nToken
   842      );
   843    }
   844  
   845    for(ii=0; rc==SQLITE_OK && ii<pCtx->pConfig->nPrefix; ii++){
   846      const int nChar = pCtx->pConfig->aPrefix[ii];
   847      int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar);
   848      if( nByte ){
   849        rc = sqlite3Fts5TermsetAdd(pTermset, ii+1, pToken, nByte, &bPresent);
   850        if( bPresent==0 ){
   851          pCtx->cksum ^= sqlite3Fts5IndexEntryCksum(
   852              pCtx->iRowid, iCol, iPos, ii+1, pToken, nByte
   853          );
   854        }
   855      }
   856    }
   857  
   858    return rc;
   859  }
   860  
   861  /*
   862  ** Check that the contents of the FTS index match that of the %_content
   863  ** table. Return SQLITE_OK if they do, or SQLITE_CORRUPT if not. Return
   864  ** some other SQLite error code if an error occurs while attempting to
   865  ** determine this.
   866  */
   867  int sqlite3Fts5StorageIntegrity(Fts5Storage *p){
   868    Fts5Config *pConfig = p->pConfig;
   869    int rc;                         /* Return code */
   870    int *aColSize;                  /* Array of size pConfig->nCol */
   871    i64 *aTotalSize;                /* Array of size pConfig->nCol */
   872    Fts5IntegrityCtx ctx;
   873    sqlite3_stmt *pScan;
   874  
   875    memset(&ctx, 0, sizeof(Fts5IntegrityCtx));
   876    ctx.pConfig = p->pConfig;
   877    aTotalSize = (i64*)sqlite3_malloc(pConfig->nCol * (sizeof(int)+sizeof(i64)));
   878    if( !aTotalSize ) return SQLITE_NOMEM;
   879    aColSize = (int*)&aTotalSize[pConfig->nCol];
   880    memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol);
   881  
   882    /* Generate the expected index checksum based on the contents of the
   883    ** %_content table. This block stores the checksum in ctx.cksum. */
   884    rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0);
   885    if( rc==SQLITE_OK ){
   886      int rc2;
   887      while( SQLITE_ROW==sqlite3_step(pScan) ){
   888        int i;
   889        ctx.iRowid = sqlite3_column_int64(pScan, 0);
   890        ctx.szCol = 0;
   891        if( pConfig->bColumnsize ){
   892          rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize);
   893        }
   894        if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){
   895          rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
   896        }
   897        for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
   898          if( pConfig->abUnindexed[i] ) continue;
   899          ctx.iCol = i;
   900          ctx.szCol = 0;
   901          if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
   902            rc = sqlite3Fts5TermsetNew(&ctx.pTermset);
   903          }
   904          if( rc==SQLITE_OK ){
   905            rc = sqlite3Fts5Tokenize(pConfig, 
   906                FTS5_TOKENIZE_DOCUMENT,
   907                (const char*)sqlite3_column_text(pScan, i+1),
   908                sqlite3_column_bytes(pScan, i+1),
   909                (void*)&ctx,
   910                fts5StorageIntegrityCallback
   911            );
   912          }
   913          if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){
   914            rc = FTS5_CORRUPT;
   915          }
   916          aTotalSize[i] += ctx.szCol;
   917          if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){
   918            sqlite3Fts5TermsetFree(ctx.pTermset);
   919            ctx.pTermset = 0;
   920          }
   921        }
   922        sqlite3Fts5TermsetFree(ctx.pTermset);
   923        ctx.pTermset = 0;
   924  
   925        if( rc!=SQLITE_OK ) break;
   926      }
   927      rc2 = sqlite3_reset(pScan);
   928      if( rc==SQLITE_OK ) rc = rc2;
   929    }
   930  
   931    /* Test that the "totals" (sometimes called "averages") record looks Ok */
   932    if( rc==SQLITE_OK ){
   933      int i;
   934      rc = fts5StorageLoadTotals(p, 0);
   935      for(i=0; rc==SQLITE_OK && i<pConfig->nCol; i++){
   936        if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT;
   937      }
   938    }
   939  
   940    /* Check that the %_docsize and %_content tables contain the expected
   941    ** number of rows.  */
   942    if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
   943      i64 nRow = 0;
   944      rc = fts5StorageCount(p, "content", &nRow);
   945      if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
   946    }
   947    if( rc==SQLITE_OK && pConfig->bColumnsize ){
   948      i64 nRow = 0;
   949      rc = fts5StorageCount(p, "docsize", &nRow);
   950      if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT;
   951    }
   952  
   953    /* Pass the expected checksum down to the FTS index module. It will
   954    ** verify, amongst other things, that it matches the checksum generated by
   955    ** inspecting the index itself.  */
   956    if( rc==SQLITE_OK ){
   957      rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum);
   958    }
   959  
   960    sqlite3_free(aTotalSize);
   961    return rc;
   962  }
   963  
   964  /*
   965  ** Obtain an SQLite statement handle that may be used to read data from the
   966  ** %_content table.
   967  */
   968  int sqlite3Fts5StorageStmt(
   969    Fts5Storage *p, 
   970    int eStmt, 
   971    sqlite3_stmt **pp, 
   972    char **pzErrMsg
   973  ){
   974    int rc;
   975    assert( eStmt==FTS5_STMT_SCAN_ASC 
   976         || eStmt==FTS5_STMT_SCAN_DESC
   977         || eStmt==FTS5_STMT_LOOKUP
   978    );
   979    rc = fts5StorageGetStmt(p, eStmt, pp, pzErrMsg);
   980    if( rc==SQLITE_OK ){
   981      assert( p->aStmt[eStmt]==*pp );
   982      p->aStmt[eStmt] = 0;
   983    }
   984    return rc;
   985  }
   986  
   987  /*
   988  ** Release an SQLite statement handle obtained via an earlier call to
   989  ** sqlite3Fts5StorageStmt(). The eStmt parameter passed to this function
   990  ** must match that passed to the sqlite3Fts5StorageStmt() call.
   991  */
   992  void sqlite3Fts5StorageStmtRelease(
   993    Fts5Storage *p, 
   994    int eStmt, 
   995    sqlite3_stmt *pStmt
   996  ){
   997    assert( eStmt==FTS5_STMT_SCAN_ASC
   998         || eStmt==FTS5_STMT_SCAN_DESC
   999         || eStmt==FTS5_STMT_LOOKUP
  1000    );
  1001    if( p->aStmt[eStmt]==0 ){
  1002      sqlite3_reset(pStmt);
  1003      p->aStmt[eStmt] = pStmt;
  1004    }else{
  1005      sqlite3_finalize(pStmt);
  1006    }
  1007  }
  1008  
  1009  static int fts5StorageDecodeSizeArray(
  1010    int *aCol, int nCol,            /* Array to populate */
  1011    const u8 *aBlob, int nBlob      /* Record to read varints from */
  1012  ){
  1013    int i;
  1014    int iOff = 0;
  1015    for(i=0; i<nCol; i++){
  1016      if( iOff>=nBlob ) return 1;
  1017      iOff += fts5GetVarint32(&aBlob[iOff], aCol[i]);
  1018    }
  1019    return (iOff!=nBlob);
  1020  }
  1021  
  1022  /*
  1023  ** Argument aCol points to an array of integers containing one entry for
  1024  ** each table column. This function reads the %_docsize record for the
  1025  ** specified rowid and populates aCol[] with the results.
  1026  **
  1027  ** An SQLite error code is returned if an error occurs, or SQLITE_OK
  1028  ** otherwise.
  1029  */
  1030  int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){
  1031    int nCol = p->pConfig->nCol;    /* Number of user columns in table */
  1032    sqlite3_stmt *pLookup = 0;      /* Statement to query %_docsize */
  1033    int rc;                         /* Return Code */
  1034  
  1035    assert( p->pConfig->bColumnsize );
  1036    rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0);
  1037    if( rc==SQLITE_OK ){
  1038      int bCorrupt = 1;
  1039      sqlite3_bind_int64(pLookup, 1, iRowid);
  1040      if( SQLITE_ROW==sqlite3_step(pLookup) ){
  1041        const u8 *aBlob = sqlite3_column_blob(pLookup, 0);
  1042        int nBlob = sqlite3_column_bytes(pLookup, 0);
  1043        if( 0==fts5StorageDecodeSizeArray(aCol, nCol, aBlob, nBlob) ){
  1044          bCorrupt = 0;
  1045        }
  1046      }
  1047      rc = sqlite3_reset(pLookup);
  1048      if( bCorrupt && rc==SQLITE_OK ){
  1049        rc = FTS5_CORRUPT;
  1050      }
  1051    }
  1052  
  1053    return rc;
  1054  }
  1055  
  1056  int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){
  1057    int rc = fts5StorageLoadTotals(p, 0);
  1058    if( rc==SQLITE_OK ){
  1059      *pnToken = 0;
  1060      if( iCol<0 ){
  1061        int i;
  1062        for(i=0; i<p->pConfig->nCol; i++){
  1063          *pnToken += p->aTotalSize[i];
  1064        }
  1065      }else if( iCol<p->pConfig->nCol ){
  1066        *pnToken = p->aTotalSize[iCol];
  1067      }else{
  1068        rc = SQLITE_RANGE;
  1069      }
  1070    }
  1071    return rc;
  1072  }
  1073  
  1074  int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){
  1075    int rc = fts5StorageLoadTotals(p, 0);
  1076    if( rc==SQLITE_OK ){
  1077      *pnRow = p->nTotalRow;
  1078    }
  1079    return rc;
  1080  }
  1081  
  1082  /*
  1083  ** Flush any data currently held in-memory to disk.
  1084  */
  1085  int sqlite3Fts5StorageSync(Fts5Storage *p){
  1086    int rc = SQLITE_OK;
  1087    i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db);
  1088    if( p->bTotalsValid ){
  1089      rc = fts5StorageSaveTotals(p);
  1090      p->bTotalsValid = 0;
  1091    }
  1092    if( rc==SQLITE_OK ){
  1093      rc = sqlite3Fts5IndexSync(p->pIndex);
  1094    }
  1095    sqlite3_set_last_insert_rowid(p->pConfig->db, iLastRowid);
  1096    return rc;
  1097  }
  1098  
  1099  int sqlite3Fts5StorageRollback(Fts5Storage *p){
  1100    p->bTotalsValid = 0;
  1101    return sqlite3Fts5IndexRollback(p->pIndex);
  1102  }
  1103  
  1104  int sqlite3Fts5StorageConfigValue(
  1105    Fts5Storage *p, 
  1106    const char *z,
  1107    sqlite3_value *pVal,
  1108    int iVal
  1109  ){
  1110    sqlite3_stmt *pReplace = 0;
  1111    int rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_CONFIG, &pReplace, 0);
  1112    if( rc==SQLITE_OK ){
  1113      sqlite3_bind_text(pReplace, 1, z, -1, SQLITE_STATIC);
  1114      if( pVal ){
  1115        sqlite3_bind_value(pReplace, 2, pVal);
  1116      }else{
  1117        sqlite3_bind_int(pReplace, 2, iVal);
  1118      }
  1119      sqlite3_step(pReplace);
  1120      rc = sqlite3_reset(pReplace);
  1121    }
  1122    if( rc==SQLITE_OK && pVal ){
  1123      int iNew = p->pConfig->iCookie + 1;
  1124      rc = sqlite3Fts5IndexSetCookie(p->pIndex, iNew);
  1125      if( rc==SQLITE_OK ){
  1126        p->pConfig->iCookie = iNew;
  1127      }
  1128    }
  1129    return rc;
  1130  }