/ Check-in [fb4a3558]
Login
SQLite training in Houston TX on 2019-11-05 (details)
Part of the 2019 Tcl Conference

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Extra tests for fts3. And fixes for conflict-handling related problems in fts3.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | vtab-conflict
Files: files | file ages | folders
SHA1: fb4a355871d9482ccb28b6ba03b842b3cc87b696
User & Date: dan 2011-04-26 19:21:34
Context
2011-04-27
12:08
Fix problems related to savepoint rollback and fts3. check-in: ff69f823 user: dan tags: vtab-conflict
2011-04-26
19:21
Extra tests for fts3. And fixes for conflict-handling related problems in fts3. check-in: fb4a3558 user: dan tags: vtab-conflict
2011-04-25
18:49
Add support for on conflict clauses to fts3/fts4. check-in: 6d2633a6 user: dan tags: vtab-conflict
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/fts3/fts3.c.

  3539   3539     );
  3540   3540     fts3DbExec(&rc, db,
  3541   3541       "ALTER TABLE %Q.'%q_segdir'   RENAME TO '%q_segdir';",
  3542   3542       p->zDb, p->zName, zName
  3543   3543     );
  3544   3544     return rc;
  3545   3545   }
         3546  +
         3547  +static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
         3548  +  return sqlite3Fts3PendingTermsFlush((Fts3Table *)pVtab);
         3549  +}
         3550  +static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
         3551  +  return SQLITE_OK;
         3552  +}
         3553  +static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
         3554  +  sqlite3Fts3PendingTermsClear((Fts3Table *)pVtab);
         3555  +  return SQLITE_OK;
         3556  +}
  3546   3557   
  3547   3558   static const sqlite3_module fts3Module = {
  3548         -  /* iVersion      */ 0,
         3559  +  /* iVersion      */ 1,
  3549   3560     /* xCreate       */ fts3CreateMethod,
  3550   3561     /* xConnect      */ fts3ConnectMethod,
  3551   3562     /* xBestIndex    */ fts3BestIndexMethod,
  3552   3563     /* xDisconnect   */ fts3DisconnectMethod,
  3553   3564     /* xDestroy      */ fts3DestroyMethod,
  3554   3565     /* xOpen         */ fts3OpenMethod,
  3555   3566     /* xClose        */ fts3CloseMethod,
................................................................................
  3561   3572     /* xUpdate       */ fts3UpdateMethod,
  3562   3573     /* xBegin        */ fts3BeginMethod,
  3563   3574     /* xSync         */ fts3SyncMethod,
  3564   3575     /* xCommit       */ fts3CommitMethod,
  3565   3576     /* xRollback     */ fts3RollbackMethod,
  3566   3577     /* xFindFunction */ fts3FindFunctionMethod,
  3567   3578     /* xRename */       fts3RenameMethod,
         3579  +  /* xSavepoint    */ fts3SavepointMethod,
         3580  +  /* xRelease      */ fts3ReleaseMethod,
         3581  +  /* xRollbackTo   */ fts3RollbackToMethod,
  3568   3582   };
  3569   3583   
  3570   3584   /*
  3571   3585   ** This function is registered as the module destructor (called when an
  3572   3586   ** FTS3 enabled database connection is closed). It frees the memory
  3573   3587   ** allocated for the tokenizer hash table.
  3574   3588   */
................................................................................
  3606   3620     const sqlite3_tokenizer_module *pSimple = 0;
  3607   3621     const sqlite3_tokenizer_module *pPorter = 0;
  3608   3622   
  3609   3623   #ifdef SQLITE_ENABLE_ICU
  3610   3624     const sqlite3_tokenizer_module *pIcu = 0;
  3611   3625     sqlite3Fts3IcuTokenizerModule(&pIcu);
  3612   3626   #endif
         3627  +
         3628  +#ifdef SQLITE_TEST
         3629  +  rc = sqlite3Fts3InitTerm(db);
         3630  +  if( rc!=SQLITE_OK ) return rc;
         3631  +#endif
  3613   3632   
  3614   3633     rc = sqlite3Fts3InitAux(db);
  3615   3634     if( rc!=SQLITE_OK ) return rc;
  3616   3635   
  3617   3636     sqlite3Fts3SimpleTokenizerModule(&pSimple);
  3618   3637     sqlite3Fts3PorterTokenizerModule(&pPorter);
  3619   3638   

Added ext/fts3/fts3_term.c.

            1  +/*
            2  +** 2011 Jan 27
            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  +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
           16  +#ifdef SQLITE_TEST
           17  +
           18  +#include "fts3Int.h"
           19  +#include <string.h>
           20  +#include <assert.h>
           21  +
           22  +typedef struct Fts3termTable Fts3termTable;
           23  +typedef struct Fts3termCursor Fts3termCursor;
           24  +
           25  +struct Fts3termTable {
           26  +  sqlite3_vtab base;              /* Base class used by SQLite core */
           27  +  Fts3Table *pFts3Tab;
           28  +};
           29  +
           30  +struct Fts3termCursor {
           31  +  sqlite3_vtab_cursor base;       /* Base class used by SQLite core */
           32  +  Fts3SegReaderCursor csr;        /* Must be right after "base" */
           33  +  Fts3SegFilter filter;
           34  +
           35  +  int isEof;                      /* True if cursor is at EOF */
           36  +  char *pNext;
           37  +
           38  +  sqlite3_int64 iRowid;           /* Current 'rowid' value */
           39  +  sqlite3_int64 iDocid;           /* Current 'docid' value */
           40  +  int iCol;                       /* Current 'col' value */
           41  +  int iPos;                       /* Current 'pos' value */
           42  +};
           43  +
           44  +/*
           45  +** Schema of the terms table.
           46  +*/
           47  +#define FTS3_TERMS_SCHEMA "CREATE TABLE x(term, docid, col, pos)"
           48  +
           49  +/*
           50  +** This function does all the work for both the xConnect and xCreate methods.
           51  +** These tables have no persistent representation of their own, so xConnect
           52  +** and xCreate are identical operations.
           53  +*/
           54  +static int fts3termConnectMethod(
           55  +  sqlite3 *db,                    /* Database connection */
           56  +  void *pUnused,                  /* Unused */
           57  +  int argc,                       /* Number of elements in argv array */
           58  +  const char * const *argv,       /* xCreate/xConnect argument array */
           59  +  sqlite3_vtab **ppVtab,          /* OUT: New sqlite3_vtab object */
           60  +  char **pzErr                    /* OUT: sqlite3_malloc'd error message */
           61  +){
           62  +  char const *zDb;                /* Name of database (e.g. "main") */
           63  +  char const *zFts3;              /* Name of fts3 table */
           64  +  int nDb;                        /* Result of strlen(zDb) */
           65  +  int nFts3;                      /* Result of strlen(zFts3) */
           66  +  int nByte;                      /* Bytes of space to allocate here */
           67  +  int rc;                         /* value returned by declare_vtab() */
           68  +  Fts3termTable *p;                /* Virtual table object to return */
           69  +
           70  +  UNUSED_PARAMETER(pUnused);
           71  +
           72  +  /* The user should specify a single argument - the name of an fts3 table. */
           73  +  if( argc!=4 ){
           74  +    *pzErr = sqlite3_mprintf(
           75  +        "wrong number of arguments to fts4term constructor"
           76  +    );
           77  +    return SQLITE_ERROR;
           78  +  }
           79  +
           80  +  zDb = argv[1]; 
           81  +  nDb = strlen(zDb);
           82  +  zFts3 = argv[3];
           83  +  nFts3 = strlen(zFts3);
           84  +
           85  +  rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA);
           86  +  if( rc!=SQLITE_OK ) return rc;
           87  +
           88  +  nByte = sizeof(Fts3termTable) + sizeof(Fts3Table) + nDb + nFts3 + 2;
           89  +  p = (Fts3termTable *)sqlite3_malloc(nByte);
           90  +  if( !p ) return SQLITE_NOMEM;
           91  +  memset(p, 0, nByte);
           92  +
           93  +  p->pFts3Tab = (Fts3Table *)&p[1];
           94  +  p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
           95  +  p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
           96  +  p->pFts3Tab->db = db;
           97  +
           98  +  memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
           99  +  memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
          100  +  sqlite3Fts3Dequote((char *)p->pFts3Tab->zName);
          101  +
          102  +  *ppVtab = (sqlite3_vtab *)p;
          103  +  return SQLITE_OK;
          104  +}
          105  +
          106  +/*
          107  +** This function does the work for both the xDisconnect and xDestroy methods.
          108  +** These tables have no persistent representation of their own, so xDisconnect
          109  +** and xDestroy are identical operations.
          110  +*/
          111  +static int fts3termDisconnectMethod(sqlite3_vtab *pVtab){
          112  +  Fts3termTable *p = (Fts3termTable *)pVtab;
          113  +  Fts3Table *pFts3 = p->pFts3Tab;
          114  +  int i;
          115  +
          116  +  /* Free any prepared statements held */
          117  +  for(i=0; i<SizeofArray(pFts3->aStmt); i++){
          118  +    sqlite3_finalize(pFts3->aStmt[i]);
          119  +  }
          120  +  sqlite3_free(pFts3->zSegmentsTbl);
          121  +  sqlite3_free(p);
          122  +  return SQLITE_OK;
          123  +}
          124  +
          125  +#define FTS4AUX_EQ_CONSTRAINT 1
          126  +#define FTS4AUX_GE_CONSTRAINT 2
          127  +#define FTS4AUX_LE_CONSTRAINT 4
          128  +
          129  +/*
          130  +** xBestIndex - Analyze a WHERE and ORDER BY clause.
          131  +*/
          132  +static int fts3termBestIndexMethod(
          133  +  sqlite3_vtab *pVTab, 
          134  +  sqlite3_index_info *pInfo
          135  +){
          136  +  UNUSED_PARAMETER(pVTab);
          137  +  UNUSED_PARAMETER(pInfo);
          138  +  return SQLITE_OK;
          139  +}
          140  +
          141  +/*
          142  +** xOpen - Open a cursor.
          143  +*/
          144  +static int fts3termOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
          145  +  Fts3termCursor *pCsr;            /* Pointer to cursor object to return */
          146  +
          147  +  UNUSED_PARAMETER(pVTab);
          148  +
          149  +  pCsr = (Fts3termCursor *)sqlite3_malloc(sizeof(Fts3termCursor));
          150  +  if( !pCsr ) return SQLITE_NOMEM;
          151  +  memset(pCsr, 0, sizeof(Fts3termCursor));
          152  +
          153  +  *ppCsr = (sqlite3_vtab_cursor *)pCsr;
          154  +  return SQLITE_OK;
          155  +}
          156  +
          157  +/*
          158  +** xClose - Close a cursor.
          159  +*/
          160  +static int fts3termCloseMethod(sqlite3_vtab_cursor *pCursor){
          161  +  Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
          162  +  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
          163  +
          164  +  sqlite3Fts3SegmentsClose(pFts3);
          165  +  sqlite3Fts3SegReaderFinish(&pCsr->csr);
          166  +  sqlite3_free(pCsr);
          167  +  return SQLITE_OK;
          168  +}
          169  +
          170  +/*
          171  +** xNext - Advance the cursor to the next row, if any.
          172  +*/
          173  +static int fts3termNextMethod(sqlite3_vtab_cursor *pCursor){
          174  +  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
          175  +  Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
          176  +  int rc;
          177  +  sqlite3_int64 v;
          178  +
          179  +  /* Increment our pretend rowid value. */
          180  +  pCsr->iRowid++;
          181  +
          182  +  /* Advance to the next term in the full-text index. */
          183  +  if( pCsr->csr.aDoclist==0 
          184  +   || pCsr->pNext>=&pCsr->csr.aDoclist[pCsr->csr.nDoclist-1]
          185  +  ){
          186  +    rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
          187  +    if( rc!=SQLITE_ROW ){
          188  +      pCsr->isEof = 1;
          189  +      return rc;
          190  +    }
          191  +
          192  +    pCsr->iCol = 0;
          193  +    pCsr->iPos = 0;
          194  +    pCsr->iDocid = 0;
          195  +    pCsr->pNext = pCsr->csr.aDoclist;
          196  +
          197  +    /* Read docid */
          198  +    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &pCsr->iDocid);
          199  +  }
          200  +
          201  +  pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
          202  +  if( v==0 ){
          203  +    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
          204  +    pCsr->iDocid += v;
          205  +    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
          206  +    pCsr->iCol = 0;
          207  +    pCsr->iPos = 0;
          208  +  }
          209  +
          210  +  if( v==1 ){
          211  +    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
          212  +    pCsr->iCol += v;
          213  +    pCsr->iPos = 0;
          214  +    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
          215  +  }
          216  +
          217  +  pCsr->iPos += (v - 2);
          218  +
          219  +  return SQLITE_OK;
          220  +}
          221  +
          222  +/*
          223  +** xFilter - Initialize a cursor to point at the start of its data.
          224  +*/
          225  +static int fts3termFilterMethod(
          226  +  sqlite3_vtab_cursor *pCursor,   /* The cursor used for this query */
          227  +  int idxNum,                     /* Strategy index */
          228  +  const char *idxStr,             /* Unused */
          229  +  int nVal,                       /* Number of elements in apVal */
          230  +  sqlite3_value **apVal           /* Arguments for the indexing scheme */
          231  +){
          232  +  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
          233  +  Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
          234  +  int rc;
          235  +
          236  +  UNUSED_PARAMETER(nVal);
          237  +  UNUSED_PARAMETER(idxNum);
          238  +  UNUSED_PARAMETER(idxStr);
          239  +  UNUSED_PARAMETER(apVal);
          240  +
          241  +  assert( idxStr==0 && idxNum==0 );
          242  +
          243  +  /* In case this cursor is being reused, close and zero it. */
          244  +  testcase(pCsr->filter.zTerm);
          245  +  sqlite3Fts3SegReaderFinish(&pCsr->csr);
          246  +  memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);
          247  +
          248  +  pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
          249  +  pCsr->filter.flags |= FTS3_SEGMENT_SCAN;
          250  +
          251  +  rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
          252  +      pCsr->filter.zTerm, pCsr->filter.nTerm, 0, 1, &pCsr->csr
          253  +  );
          254  +  if( rc==SQLITE_OK ){
          255  +    rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
          256  +  }
          257  +  if( rc==SQLITE_OK ){
          258  +    rc = fts3termNextMethod(pCursor);
          259  +  }
          260  +  return rc;
          261  +}
          262  +
          263  +/*
          264  +** xEof - Return true if the cursor is at EOF, or false otherwise.
          265  +*/
          266  +static int fts3termEofMethod(sqlite3_vtab_cursor *pCursor){
          267  +  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
          268  +  return pCsr->isEof;
          269  +}
          270  +
          271  +/*
          272  +** xColumn - Return a column value.
          273  +*/
          274  +static int fts3termColumnMethod(
          275  +  sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
          276  +  sqlite3_context *pCtx,          /* Context for sqlite3_result_xxx() calls */
          277  +  int iCol                        /* Index of column to read value from */
          278  +){
          279  +  Fts3termCursor *p = (Fts3termCursor *)pCursor;
          280  +
          281  +  assert( iCol>=0 && iCol<=3 );
          282  +  switch( iCol ){
          283  +    case 0:
          284  +      sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
          285  +      break;
          286  +    case 1:
          287  +      sqlite3_result_int64(pCtx, p->iDocid);
          288  +      break;
          289  +    case 2:
          290  +      sqlite3_result_int64(pCtx, p->iCol);
          291  +      break;
          292  +    default:
          293  +      sqlite3_result_int64(pCtx, p->iPos);
          294  +      break;
          295  +  }
          296  +
          297  +  return SQLITE_OK;
          298  +}
          299  +
          300  +/*
          301  +** xRowid - Return the current rowid for the cursor.
          302  +*/
          303  +static int fts3termRowidMethod(
          304  +  sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
          305  +  sqlite_int64 *pRowid            /* OUT: Rowid value */
          306  +){
          307  +  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
          308  +  *pRowid = pCsr->iRowid;
          309  +  return SQLITE_OK;
          310  +}
          311  +
          312  +/*
          313  +** Register the fts3term module with database connection db. Return SQLITE_OK
          314  +** if successful or an error code if sqlite3_create_module() fails.
          315  +*/
          316  +int sqlite3Fts3InitTerm(sqlite3 *db){
          317  +  static const sqlite3_module fts3term_module = {
          318  +     0,                           /* iVersion      */
          319  +     fts3termConnectMethod,       /* xCreate       */
          320  +     fts3termConnectMethod,       /* xConnect      */
          321  +     fts3termBestIndexMethod,     /* xBestIndex    */
          322  +     fts3termDisconnectMethod,    /* xDisconnect   */
          323  +     fts3termDisconnectMethod,    /* xDestroy      */
          324  +     fts3termOpenMethod,          /* xOpen         */
          325  +     fts3termCloseMethod,         /* xClose        */
          326  +     fts3termFilterMethod,        /* xFilter       */
          327  +     fts3termNextMethod,          /* xNext         */
          328  +     fts3termEofMethod,           /* xEof          */
          329  +     fts3termColumnMethod,        /* xColumn       */
          330  +     fts3termRowidMethod,         /* xRowid        */
          331  +     0,                           /* xUpdate       */
          332  +     0,                           /* xBegin        */
          333  +     0,                           /* xSync         */
          334  +     0,                           /* xCommit       */
          335  +     0,                           /* xRollback     */
          336  +     0,                           /* xFindFunction */
          337  +     0                            /* xRename       */
          338  +  };
          339  +  int rc;                         /* Return code */
          340  +
          341  +  rc = sqlite3_create_module(db, "fts4term", &fts3term_module, 0);
          342  +  return rc;
          343  +}
          344  +
          345  +#endif
          346  +#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */

Changes to ext/fts3/fts3_write.c.

  2707   2707     ** If the on-conflict mode is REPLACE, this means that the existing row
  2708   2708     ** should be deleted from the database before inserting the new row. Or,
  2709   2709     ** if the on-conflict mode is other than REPLACE, then this method must
  2710   2710     ** detect the conflict and return SQLITE_CONSTRAINT before beginning to
  2711   2711     ** modify the database file.
  2712   2712     */
  2713   2713     if( nArg>1 ){
  2714         -    sqlite3_int64 iNewRowid;
         2714  +    /* Find the value object that holds the new rowid value. */
  2715   2715       sqlite3_value *pNewRowid = apVal[3+p->nColumn];
  2716   2716       if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
  2717   2717         pNewRowid = apVal[1];
  2718   2718       }
         2719  +
  2719   2720       if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && ( 
  2720   2721           sqlite3_value_type(apVal[0])==SQLITE_NULL
  2721   2722        || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid)
  2722   2723       )){
         2724  +      /* The new rowid is not NULL (in this case the rowid will be
         2725  +      ** automatically assigned and there is no chance of a conflict), and 
         2726  +      ** the statement is either an INSERT or an UPDATE that modifies the
         2727  +      ** rowid column. So if the conflict mode is REPLACE, then delete any
         2728  +      ** existing row with rowid=pNewRowid. 
         2729  +      **
         2730  +      ** Or, if the conflict mode is not REPLACE, insert the new record into 
         2731  +      ** the %_content table. If we hit the duplicate rowid constraint (or any
         2732  +      ** other error) while doing so, return immediately.
         2733  +      **
         2734  +      ** This branch may also run if pNewRowid contains a value that cannot
         2735  +      ** be losslessly converted to an integer. In this case, the eventual 
         2736  +      ** call to fts3InsertData() (either just below or further on in this
         2737  +      ** function) will return SQLITE_MISMATCH.
         2738  +      */
  2723   2739         if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
  2724   2740           rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel);
  2725   2741         }else{
  2726   2742           rc = fts3InsertData(p, apVal, pRowid);
  2727   2743           bInsertDone = 1;
  2728   2744         }
  2729   2745       }

Changes to main.mk.

   295    295     $(TOP)/src/vdbe.c \
   296    296     $(TOP)/src/vdbemem.c \
   297    297     $(TOP)/src/where.c \
   298    298     parse.c \
   299    299     $(TOP)/ext/fts3/fts3.c \
   300    300     $(TOP)/ext/fts3/fts3_aux.c \
   301    301     $(TOP)/ext/fts3/fts3_expr.c \
          302  +  $(TOP)/ext/fts3/fts3_term.c \
   302    303     $(TOP)/ext/fts3/fts3_tokenizer.c \
   303    304     $(TOP)/ext/fts3/fts3_write.c \
   304    305     $(TOP)/ext/async/sqlite3async.c
   305    306   
   306    307   # Header files used by all library source files.
   307    308   #
   308    309   HDR = \

Changes to src/sqlite.h.in.

  4603   4603     int (*xSync)(sqlite3_vtab *pVTab);
  4604   4604     int (*xCommit)(sqlite3_vtab *pVTab);
  4605   4605     int (*xRollback)(sqlite3_vtab *pVTab);
  4606   4606     int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
  4607   4607                          void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
  4608   4608                          void **ppArg);
  4609   4609     int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
         4610  +  /* The methods above are in version 0 of the sqlite_module object. Those 
         4611  +  ** below are for version 1 and greater. */
         4612  +  int (*xSavepoint)(sqlite3_vtab *pVTab, int);
         4613  +  int (*xRelease)(sqlite3_vtab *pVTab, int);
         4614  +  int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
  4610   4615   };
  4611   4616   
  4612   4617   /*
  4613   4618   ** CAPI3REF: Virtual Table Indexing Information
  4614   4619   ** KEYWORDS: sqlite3_index_info
  4615   4620   **
  4616   4621   ** The sqlite3_index_info structure and its substructures is used as part

Changes to src/sqliteInt.h.

  3045   3045   #  define sqlite3VtabSync(X,Y) SQLITE_OK
  3046   3046   #  define sqlite3VtabRollback(X)
  3047   3047   #  define sqlite3VtabCommit(X)
  3048   3048   #  define sqlite3VtabInSync(db) 0
  3049   3049   #  define sqlite3VtabLock(X) 
  3050   3050   #  define sqlite3VtabUnlock(X)
  3051   3051   #  define sqlite3VtabUnlockList(X)
         3052  +#  define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK
  3052   3053   #else
  3053   3054      void sqlite3VtabClear(sqlite3 *db, Table*);
  3054   3055      int sqlite3VtabSync(sqlite3 *db, char **);
  3055   3056      int sqlite3VtabRollback(sqlite3 *db);
  3056   3057      int sqlite3VtabCommit(sqlite3 *db);
  3057   3058      void sqlite3VtabLock(VTable *);
  3058   3059      void sqlite3VtabUnlock(VTable *);
  3059   3060      void sqlite3VtabUnlockList(sqlite3*);
         3061  +   int sqlite3VtabSavepoint(sqlite3 *, int, int);
  3060   3062   #  define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0)
  3061   3063   #endif
  3062   3064   void sqlite3VtabMakeWritable(Parse*,Table*);
  3063   3065   void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*);
  3064   3066   void sqlite3VtabFinishParse(Parse*, Token*);
  3065   3067   void sqlite3VtabArgInit(Parse*);
  3066   3068   void sqlite3VtabArgExtend(Parse*, Token*);

Changes to src/vdbe.c.

  2817   2817       ){
  2818   2818         assert( sqlite3BtreeIsInTrans(pBt) );
  2819   2819         if( p->iStatement==0 ){
  2820   2820           assert( db->nStatement>=0 && db->nSavepoint>=0 );
  2821   2821           db->nStatement++; 
  2822   2822           p->iStatement = db->nSavepoint + db->nStatement;
  2823   2823         }
  2824         -      rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
         2824  +
         2825  +      rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement);
         2826  +      if( rc==SQLITE_OK ){
         2827  +        rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
         2828  +      }
  2825   2829   
  2826   2830         /* Store the current value of the database handles deferred constraint
  2827   2831         ** counter. If the statement transaction needs to be rolled back,
  2828   2832         ** the value of this counter needs to be restored too.  */
  2829   2833         p->nStmtDefCons = db->nDeferredCons;
  2830   2834       }
  2831   2835     }

Changes to src/vdbeaux.c.

  2008   2008           if( rc==SQLITE_OK ){
  2009   2009             rc = rc2;
  2010   2010           }
  2011   2011         }
  2012   2012       }
  2013   2013       db->nStatement--;
  2014   2014       p->iStatement = 0;
         2015  +
         2016  +    if( rc==SQLITE_OK ){
         2017  +      if( eOp==SAVEPOINT_ROLLBACK ){
         2018  +        rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint);
         2019  +      }
         2020  +      if( rc==SQLITE_OK ){
         2021  +        rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint);
         2022  +      }
         2023  +    }
  2015   2024   
  2016   2025       /* If the statement transaction is being rolled back, also restore the 
  2017   2026       ** database handles deferred constraint counter to the value it had when 
  2018   2027       ** the statement transaction was opened.  */
  2019   2028       if( eOp==SAVEPOINT_ROLLBACK ){
  2020   2029         db->nDeferredCons = p->nStmtDefCons;
  2021   2030       }

Changes to src/vtab.c.

   832    832       return SQLITE_OK;
   833    833     } 
   834    834     pModule = pVTab->pVtab->pModule;
   835    835   
   836    836     if( pModule->xBegin ){
   837    837       int i;
   838    838   
   839         -
   840    839       /* If pVtab is already in the aVTrans array, return early */
   841    840       for(i=0; i<db->nVTrans; i++){
   842    841         if( db->aVTrans[i]==pVTab ){
   843    842           return SQLITE_OK;
   844    843         }
   845    844       }
   846    845   
   847    846       /* Invoke the xBegin method */
   848    847       rc = pModule->xBegin(pVTab->pVtab);
   849    848       if( rc==SQLITE_OK ){
   850    849         rc = addToVTrans(db, pVTab);
   851    850       }
          851  +  }
          852  +  return rc;
          853  +}
          854  +
          855  +int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
          856  +  int i;
          857  +  int rc = SQLITE_OK;
          858  +
          859  +  assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN );
          860  +
          861  +  for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){
          862  +    sqlite3_vtab *pVtab = db->aVTrans[i]->pVtab;
          863  +    sqlite3_module *pMod = db->aVTrans[i]->pMod->pModule;
          864  +    if( pMod->iVersion>=1 ){
          865  +      switch( op ){
          866  +        case SAVEPOINT_BEGIN:
          867  +          rc = pMod->xSavepoint(pVtab, iSavepoint);
          868  +          break;
          869  +        case SAVEPOINT_ROLLBACK:
          870  +          rc = pMod->xRollbackTo(pVtab, iSavepoint);
          871  +          break;
          872  +        default:
          873  +          rc = pMod->xRelease(pVtab, iSavepoint);
          874  +          break;
          875  +      }
          876  +    }
   852    877     }
   853    878     return rc;
   854    879   }
   855    880   
   856    881   /*
   857    882   ** The first parameter (pDef) is a function implementation.  The
   858    883   ** second parameter (pExpr) is the first argument to this function.

Changes to test/fts3conf.test.

    18     18   
    19     19   # If SQLITE_ENABLE_FTS3 is defined, omit this file.
    20     20   ifcapable !fts3 {
    21     21     finish_test
    22     22     return
    23     23   }
    24     24   
    25         -do_execsql_test 1.0 {
           25  +
           26  +proc fts3_integrity {tn db tbl} {
           27  +
           28  +  if {[sqlite3_get_autocommit $db]==0} {
           29  +    error "fts3_integrity does not work with an open transaction"
           30  +  }
           31  +
           32  +  set sql [db one {SELECT sql FROM sqlite_master WHERE name = $tbl}]
           33  +  regexp -nocase {[^(]* using (.*)} $sql -> tail
           34  +  set cols [list]
           35  +  $db eval "PRAGMA table_info($tbl)" {
           36  +    lappend cols $name
           37  +  }
           38  +  set cols [join [concat docid $cols] ,]
           39  +
           40  +  $db eval [subst {
           41  +    CREATE VIRTUAL TABLE fts3check USING fts4term($tbl);
           42  +    CREATE VIRTUAL TABLE temp.fts3check2 USING $tail;
           43  +    INSERT INTO temp.fts3check2($cols) SELECT docid, * FROM $tbl;
           44  +    CREATE VIRTUAL TABLE temp.fts3check3 USING fts4term(fts3check2);
           45  +  }]
           46  +
           47  +  set m1 [$db one {SELECT md5sum(term, docid, col, pos) FROM fts3check}]
           48  +  set m2 [$db one {SELECT md5sum(term, docid, col, pos) FROM fts3check3}]
           49  +
           50  +  $db eval {
           51  +    DROP TABLE fts3check;
           52  +    DROP TABLE temp.fts3check2;
           53  +    DROP TABLE temp.fts3check3;
           54  +  }
           55  +  
           56  +  uplevel [list do_test $tn [list set {} $m1] $m2]
           57  +}
           58  +
           59  +
           60  +
           61  +do_execsql_test 1.0.1 {
    26     62     CREATE VIRTUAL TABLE t1 USING fts3(x);
    27     63     INSERT INTO t1(rowid, x) VALUES(1, 'a b c d');
    28     64     INSERT INTO t1(rowid, x) VALUES(2, 'e f g h');
    29     65   
    30     66     CREATE TABLE source(a, b);
    31     67     INSERT INTO source VALUES(4, 'z');
    32     68     INSERT INTO source VALUES(2, 'y');
    33     69   }
    34     70   db_save_and_close
    35     71   
    36     72   set T1 "INTO t1(rowid, x) VALUES(1, 'x')"
    37     73   set T2 "INTO t1(rowid, x) SELECT * FROM source"
    38     74   
           75  +set T3 "t1 SET docid = 2 WHERE docid = 1"
           76  +set T4 "t1 SET docid = CASE WHEN docid = 1 THEN 4 ELSE 3 END WHERE docid <=2"
           77  +
    39     78   foreach {tn sql constraint data} [subst {
    40     79     1    "INSERT OR ROLLBACK $T1"   1 {{a b c d} {e f g h}}
    41     80     2    "INSERT OR ABORT    $T1"   1 {{a b c d} {e f g h} {i j k l}}
    42     81     3    "INSERT OR FAIL     $T1"   1 {{a b c d} {e f g h} {i j k l}}
    43     82     4    "INSERT OR IGNORE   $T1"   0 {{a b c d} {e f g h} {i j k l}}
    44     83     5    "INSERT OR REPLACE  $T1"   0 {x {e f g h} {i j k l}}
    45     84   
    46     85     6    "INSERT OR ROLLBACK $T2"   1 {{a b c d} {e f g h}}
    47     86     7    "INSERT OR ABORT    $T2"   1 {{a b c d} {e f g h} {i j k l}}
    48     87     8    "INSERT OR FAIL     $T2"   1 {{a b c d} {e f g h} {i j k l} z}
    49     88     9    "INSERT OR IGNORE   $T2"   0 {{a b c d} {e f g h} {i j k l} z}
    50     89     10   "INSERT OR REPLACE  $T2"   0 {{a b c d} y {i j k l} z}
           90  +
           91  +  11   "UPDATE OR ROLLBACK $T3"   1 {{a b c d} {e f g h}}
           92  +  12   "UPDATE OR ABORT    $T3"   1 {{a b c d} {e f g h} {i j k l}}
           93  +  13   "UPDATE OR FAIL     $T3"   1 {{a b c d} {e f g h} {i j k l}}
           94  +  14   "UPDATE OR IGNORE   $T3"   0 {{a b c d} {e f g h} {i j k l}}
           95  +  15   "UPDATE OR REPLACE  $T3"   0 {{a b c d} {i j k l}}
           96  +
           97  +  16   "UPDATE OR ROLLBACK $T4"   1 {{a b c d} {e f g h}}
           98  +  17   "UPDATE OR ABORT    $T4"   1 {{a b c d} {e f g h} {i j k l}}
           99  +  18   "UPDATE OR FAIL     $T4"   1 {{e f g h} {i j k l} {a b c d}}
          100  +  19   "UPDATE OR IGNORE   $T4"   0 {{e f g h} {i j k l} {a b c d}}
          101  +  20   "UPDATE OR REPLACE  $T4"   0 {{e f g h} {a b c d}}
    51    102   }] {
    52    103     db_restore_and_reopen
    53    104     execsql { 
    54    105       BEGIN;
    55    106         INSERT INTO t1(rowid, x) VALUES(3, 'i j k l');
    56    107     }
    57    108     set R(0) {0 {}}
    58    109     set R(1) {1 {constraint failed}}
    59    110     do_catchsql_test 1.$tn.1 $sql $R($constraint)
    60    111     do_catchsql_test 1.$tn.2 { SELECT * FROM t1 } [list 0 $data]
          112  +  catchsql COMMIT
          113  +
          114  +  fts3_integrity 1.$tn.3 db t1
    61    115   }
          116  +
    62    117   
    63    118   finish_test