/ Check-in [fb4a3558]
Login

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 Unified Diffs Ignore Whitespace Patch

Changes to ext/fts3/fts3.c.

3539
3540
3541
3542
3543
3544
3545
3546











3547
3548
3549
3550
3551
3552
3553
3554
3555
....
3561
3562
3563
3564
3565
3566
3567



3568
3569
3570
3571
3572
3573
3574
....
3606
3607
3608
3609
3610
3611
3612





3613
3614
3615
3616
3617
3618
3619
  );
  fts3DbExec(&rc, db,
    "ALTER TABLE %Q.'%q_segdir'   RENAME TO '%q_segdir';",
    p->zDb, p->zName, zName
  );
  return rc;
}












static const sqlite3_module fts3Module = {
  /* iVersion      */ 0,
  /* xCreate       */ fts3CreateMethod,
  /* xConnect      */ fts3ConnectMethod,
  /* xBestIndex    */ fts3BestIndexMethod,
  /* xDisconnect   */ fts3DisconnectMethod,
  /* xDestroy      */ fts3DestroyMethod,
  /* xOpen         */ fts3OpenMethod,
  /* xClose        */ fts3CloseMethod,
................................................................................
  /* xUpdate       */ fts3UpdateMethod,
  /* xBegin        */ fts3BeginMethod,
  /* xSync         */ fts3SyncMethod,
  /* xCommit       */ fts3CommitMethod,
  /* xRollback     */ fts3RollbackMethod,
  /* xFindFunction */ fts3FindFunctionMethod,
  /* xRename */       fts3RenameMethod,



};

/*
** This function is registered as the module destructor (called when an
** FTS3 enabled database connection is closed). It frees the memory
** allocated for the tokenizer hash table.
*/
................................................................................
  const sqlite3_tokenizer_module *pSimple = 0;
  const sqlite3_tokenizer_module *pPorter = 0;

#ifdef SQLITE_ENABLE_ICU
  const sqlite3_tokenizer_module *pIcu = 0;
  sqlite3Fts3IcuTokenizerModule(&pIcu);
#endif






  rc = sqlite3Fts3InitAux(db);
  if( rc!=SQLITE_OK ) return rc;

  sqlite3Fts3SimpleTokenizerModule(&pSimple);
  sqlite3Fts3PorterTokenizerModule(&pPorter);









>
>
>
>
>
>
>
>
>
>
>

|







 







>
>
>







 







>
>
>
>
>







3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
....
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
....
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
  );
  fts3DbExec(&rc, db,
    "ALTER TABLE %Q.'%q_segdir'   RENAME TO '%q_segdir';",
    p->zDb, p->zName, zName
  );
  return rc;
}

static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){
  return sqlite3Fts3PendingTermsFlush((Fts3Table *)pVtab);
}
static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){
  return SQLITE_OK;
}
static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){
  sqlite3Fts3PendingTermsClear((Fts3Table *)pVtab);
  return SQLITE_OK;
}

static const sqlite3_module fts3Module = {
  /* iVersion      */ 1,
  /* xCreate       */ fts3CreateMethod,
  /* xConnect      */ fts3ConnectMethod,
  /* xBestIndex    */ fts3BestIndexMethod,
  /* xDisconnect   */ fts3DisconnectMethod,
  /* xDestroy      */ fts3DestroyMethod,
  /* xOpen         */ fts3OpenMethod,
  /* xClose        */ fts3CloseMethod,
................................................................................
  /* xUpdate       */ fts3UpdateMethod,
  /* xBegin        */ fts3BeginMethod,
  /* xSync         */ fts3SyncMethod,
  /* xCommit       */ fts3CommitMethod,
  /* xRollback     */ fts3RollbackMethod,
  /* xFindFunction */ fts3FindFunctionMethod,
  /* xRename */       fts3RenameMethod,
  /* xSavepoint    */ fts3SavepointMethod,
  /* xRelease      */ fts3ReleaseMethod,
  /* xRollbackTo   */ fts3RollbackToMethod,
};

/*
** This function is registered as the module destructor (called when an
** FTS3 enabled database connection is closed). It frees the memory
** allocated for the tokenizer hash table.
*/
................................................................................
  const sqlite3_tokenizer_module *pSimple = 0;
  const sqlite3_tokenizer_module *pPorter = 0;

#ifdef SQLITE_ENABLE_ICU
  const sqlite3_tokenizer_module *pIcu = 0;
  sqlite3Fts3IcuTokenizerModule(&pIcu);
#endif

#ifdef SQLITE_TEST
  rc = sqlite3Fts3InitTerm(db);
  if( rc!=SQLITE_OK ) return rc;
#endif

  rc = sqlite3Fts3InitAux(db);
  if( rc!=SQLITE_OK ) return rc;

  sqlite3Fts3SimpleTokenizerModule(&pSimple);
  sqlite3Fts3PorterTokenizerModule(&pPorter);

Added ext/fts3/fts3_term.c.





















































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
/*
** 2011 Jan 27
**
** 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.
**
******************************************************************************
**
*/

#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3)
#ifdef SQLITE_TEST

#include "fts3Int.h"
#include <string.h>
#include <assert.h>

typedef struct Fts3termTable Fts3termTable;
typedef struct Fts3termCursor Fts3termCursor;

struct Fts3termTable {
  sqlite3_vtab base;              /* Base class used by SQLite core */
  Fts3Table *pFts3Tab;
};

struct Fts3termCursor {
  sqlite3_vtab_cursor base;       /* Base class used by SQLite core */
  Fts3SegReaderCursor csr;        /* Must be right after "base" */
  Fts3SegFilter filter;

  int isEof;                      /* True if cursor is at EOF */
  char *pNext;

  sqlite3_int64 iRowid;           /* Current 'rowid' value */
  sqlite3_int64 iDocid;           /* Current 'docid' value */
  int iCol;                       /* Current 'col' value */
  int iPos;                       /* Current 'pos' value */
};

/*
** Schema of the terms table.
*/
#define FTS3_TERMS_SCHEMA "CREATE TABLE x(term, docid, col, pos)"

/*
** This function does all the work for both the xConnect and xCreate methods.
** These tables have no persistent representation of their own, so xConnect
** and xCreate are identical operations.
*/
static int fts3termConnectMethod(
  sqlite3 *db,                    /* Database connection */
  void *pUnused,                  /* Unused */
  int argc,                       /* Number of elements in argv array */
  const char * const *argv,       /* xCreate/xConnect argument array */
  sqlite3_vtab **ppVtab,          /* OUT: New sqlite3_vtab object */
  char **pzErr                    /* OUT: sqlite3_malloc'd error message */
){
  char const *zDb;                /* Name of database (e.g. "main") */
  char const *zFts3;              /* Name of fts3 table */
  int nDb;                        /* Result of strlen(zDb) */
  int nFts3;                      /* Result of strlen(zFts3) */
  int nByte;                      /* Bytes of space to allocate here */
  int rc;                         /* value returned by declare_vtab() */
  Fts3termTable *p;                /* Virtual table object to return */

  UNUSED_PARAMETER(pUnused);

  /* The user should specify a single argument - the name of an fts3 table. */
  if( argc!=4 ){
    *pzErr = sqlite3_mprintf(
        "wrong number of arguments to fts4term constructor"
    );
    return SQLITE_ERROR;
  }

  zDb = argv[1]; 
  nDb = strlen(zDb);
  zFts3 = argv[3];
  nFts3 = strlen(zFts3);

  rc = sqlite3_declare_vtab(db, FTS3_TERMS_SCHEMA);
  if( rc!=SQLITE_OK ) return rc;

  nByte = sizeof(Fts3termTable) + sizeof(Fts3Table) + nDb + nFts3 + 2;
  p = (Fts3termTable *)sqlite3_malloc(nByte);
  if( !p ) return SQLITE_NOMEM;
  memset(p, 0, nByte);

  p->pFts3Tab = (Fts3Table *)&p[1];
  p->pFts3Tab->zDb = (char *)&p->pFts3Tab[1];
  p->pFts3Tab->zName = &p->pFts3Tab->zDb[nDb+1];
  p->pFts3Tab->db = db;

  memcpy((char *)p->pFts3Tab->zDb, zDb, nDb);
  memcpy((char *)p->pFts3Tab->zName, zFts3, nFts3);
  sqlite3Fts3Dequote((char *)p->pFts3Tab->zName);

  *ppVtab = (sqlite3_vtab *)p;
  return SQLITE_OK;
}

/*
** This function does the work for both the xDisconnect and xDestroy methods.
** These tables have no persistent representation of their own, so xDisconnect
** and xDestroy are identical operations.
*/
static int fts3termDisconnectMethod(sqlite3_vtab *pVtab){
  Fts3termTable *p = (Fts3termTable *)pVtab;
  Fts3Table *pFts3 = p->pFts3Tab;
  int i;

  /* Free any prepared statements held */
  for(i=0; i<SizeofArray(pFts3->aStmt); i++){
    sqlite3_finalize(pFts3->aStmt[i]);
  }
  sqlite3_free(pFts3->zSegmentsTbl);
  sqlite3_free(p);
  return SQLITE_OK;
}

#define FTS4AUX_EQ_CONSTRAINT 1
#define FTS4AUX_GE_CONSTRAINT 2
#define FTS4AUX_LE_CONSTRAINT 4

/*
** xBestIndex - Analyze a WHERE and ORDER BY clause.
*/
static int fts3termBestIndexMethod(
  sqlite3_vtab *pVTab, 
  sqlite3_index_info *pInfo
){
  UNUSED_PARAMETER(pVTab);
  UNUSED_PARAMETER(pInfo);
  return SQLITE_OK;
}

/*
** xOpen - Open a cursor.
*/
static int fts3termOpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){
  Fts3termCursor *pCsr;            /* Pointer to cursor object to return */

  UNUSED_PARAMETER(pVTab);

  pCsr = (Fts3termCursor *)sqlite3_malloc(sizeof(Fts3termCursor));
  if( !pCsr ) return SQLITE_NOMEM;
  memset(pCsr, 0, sizeof(Fts3termCursor));

  *ppCsr = (sqlite3_vtab_cursor *)pCsr;
  return SQLITE_OK;
}

/*
** xClose - Close a cursor.
*/
static int fts3termCloseMethod(sqlite3_vtab_cursor *pCursor){
  Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;

  sqlite3Fts3SegmentsClose(pFts3);
  sqlite3Fts3SegReaderFinish(&pCsr->csr);
  sqlite3_free(pCsr);
  return SQLITE_OK;
}

/*
** xNext - Advance the cursor to the next row, if any.
*/
static int fts3termNextMethod(sqlite3_vtab_cursor *pCursor){
  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
  Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
  int rc;
  sqlite3_int64 v;

  /* Increment our pretend rowid value. */
  pCsr->iRowid++;

  /* Advance to the next term in the full-text index. */
  if( pCsr->csr.aDoclist==0 
   || pCsr->pNext>=&pCsr->csr.aDoclist[pCsr->csr.nDoclist-1]
  ){
    rc = sqlite3Fts3SegReaderStep(pFts3, &pCsr->csr);
    if( rc!=SQLITE_ROW ){
      pCsr->isEof = 1;
      return rc;
    }

    pCsr->iCol = 0;
    pCsr->iPos = 0;
    pCsr->iDocid = 0;
    pCsr->pNext = pCsr->csr.aDoclist;

    /* Read docid */
    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &pCsr->iDocid);
  }

  pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
  if( v==0 ){
    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
    pCsr->iDocid += v;
    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
    pCsr->iCol = 0;
    pCsr->iPos = 0;
  }

  if( v==1 ){
    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
    pCsr->iCol += v;
    pCsr->iPos = 0;
    pCsr->pNext += sqlite3Fts3GetVarint(pCsr->pNext, &v);
  }

  pCsr->iPos += (v - 2);

  return SQLITE_OK;
}

/*
** xFilter - Initialize a cursor to point at the start of its data.
*/
static int fts3termFilterMethod(
  sqlite3_vtab_cursor *pCursor,   /* The cursor used for this query */
  int idxNum,                     /* Strategy index */
  const char *idxStr,             /* Unused */
  int nVal,                       /* Number of elements in apVal */
  sqlite3_value **apVal           /* Arguments for the indexing scheme */
){
  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
  Fts3Table *pFts3 = ((Fts3termTable *)pCursor->pVtab)->pFts3Tab;
  int rc;

  UNUSED_PARAMETER(nVal);
  UNUSED_PARAMETER(idxNum);
  UNUSED_PARAMETER(idxStr);
  UNUSED_PARAMETER(apVal);

  assert( idxStr==0 && idxNum==0 );

  /* In case this cursor is being reused, close and zero it. */
  testcase(pCsr->filter.zTerm);
  sqlite3Fts3SegReaderFinish(&pCsr->csr);
  memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr);

  pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY;
  pCsr->filter.flags |= FTS3_SEGMENT_SCAN;

  rc = sqlite3Fts3SegReaderCursor(pFts3, FTS3_SEGCURSOR_ALL,
      pCsr->filter.zTerm, pCsr->filter.nTerm, 0, 1, &pCsr->csr
  );
  if( rc==SQLITE_OK ){
    rc = sqlite3Fts3SegReaderStart(pFts3, &pCsr->csr, &pCsr->filter);
  }
  if( rc==SQLITE_OK ){
    rc = fts3termNextMethod(pCursor);
  }
  return rc;
}

/*
** xEof - Return true if the cursor is at EOF, or false otherwise.
*/
static int fts3termEofMethod(sqlite3_vtab_cursor *pCursor){
  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
  return pCsr->isEof;
}

/*
** xColumn - Return a column value.
*/
static int fts3termColumnMethod(
  sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
  sqlite3_context *pCtx,          /* Context for sqlite3_result_xxx() calls */
  int iCol                        /* Index of column to read value from */
){
  Fts3termCursor *p = (Fts3termCursor *)pCursor;

  assert( iCol>=0 && iCol<=3 );
  switch( iCol ){
    case 0:
      sqlite3_result_text(pCtx, p->csr.zTerm, p->csr.nTerm, SQLITE_TRANSIENT);
      break;
    case 1:
      sqlite3_result_int64(pCtx, p->iDocid);
      break;
    case 2:
      sqlite3_result_int64(pCtx, p->iCol);
      break;
    default:
      sqlite3_result_int64(pCtx, p->iPos);
      break;
  }

  return SQLITE_OK;
}

/*
** xRowid - Return the current rowid for the cursor.
*/
static int fts3termRowidMethod(
  sqlite3_vtab_cursor *pCursor,   /* Cursor to retrieve value from */
  sqlite_int64 *pRowid            /* OUT: Rowid value */
){
  Fts3termCursor *pCsr = (Fts3termCursor *)pCursor;
  *pRowid = pCsr->iRowid;
  return SQLITE_OK;
}

/*
** Register the fts3term module with database connection db. Return SQLITE_OK
** if successful or an error code if sqlite3_create_module() fails.
*/
int sqlite3Fts3InitTerm(sqlite3 *db){
  static const sqlite3_module fts3term_module = {
     0,                           /* iVersion      */
     fts3termConnectMethod,       /* xCreate       */
     fts3termConnectMethod,       /* xConnect      */
     fts3termBestIndexMethod,     /* xBestIndex    */
     fts3termDisconnectMethod,    /* xDisconnect   */
     fts3termDisconnectMethod,    /* xDestroy      */
     fts3termOpenMethod,          /* xOpen         */
     fts3termCloseMethod,         /* xClose        */
     fts3termFilterMethod,        /* xFilter       */
     fts3termNextMethod,          /* xNext         */
     fts3termEofMethod,           /* xEof          */
     fts3termColumnMethod,        /* xColumn       */
     fts3termRowidMethod,         /* xRowid        */
     0,                           /* xUpdate       */
     0,                           /* xBegin        */
     0,                           /* xSync         */
     0,                           /* xCommit       */
     0,                           /* xRollback     */
     0,                           /* xFindFunction */
     0                            /* xRename       */
  };
  int rc;                         /* Return code */

  rc = sqlite3_create_module(db, "fts4term", &fts3term_module, 0);
  return rc;
}

#endif
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) */

Changes to ext/fts3/fts3_write.c.

2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718

2719
2720
2721
2722















2723
2724
2725
2726
2727
2728
2729
  ** If the on-conflict mode is REPLACE, this means that the existing row
  ** should be deleted from the database before inserting the new row. Or,
  ** if the on-conflict mode is other than REPLACE, then this method must
  ** detect the conflict and return SQLITE_CONSTRAINT before beginning to
  ** modify the database file.
  */
  if( nArg>1 ){
    sqlite3_int64 iNewRowid;
    sqlite3_value *pNewRowid = apVal[3+p->nColumn];
    if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
      pNewRowid = apVal[1];
    }

    if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && ( 
        sqlite3_value_type(apVal[0])==SQLITE_NULL
     || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid)
    )){















      if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
        rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel);
      }else{
        rc = fts3InsertData(p, apVal, pRowid);
        bInsertDone = 1;
      }
    }







|




>




>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
  ** If the on-conflict mode is REPLACE, this means that the existing row
  ** should be deleted from the database before inserting the new row. Or,
  ** if the on-conflict mode is other than REPLACE, then this method must
  ** detect the conflict and return SQLITE_CONSTRAINT before beginning to
  ** modify the database file.
  */
  if( nArg>1 ){
    /* Find the value object that holds the new rowid value. */
    sqlite3_value *pNewRowid = apVal[3+p->nColumn];
    if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
      pNewRowid = apVal[1];
    }

    if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && ( 
        sqlite3_value_type(apVal[0])==SQLITE_NULL
     || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid)
    )){
      /* The new rowid is not NULL (in this case the rowid will be
      ** automatically assigned and there is no chance of a conflict), and 
      ** the statement is either an INSERT or an UPDATE that modifies the
      ** rowid column. So if the conflict mode is REPLACE, then delete any
      ** existing row with rowid=pNewRowid. 
      **
      ** Or, if the conflict mode is not REPLACE, insert the new record into 
      ** the %_content table. If we hit the duplicate rowid constraint (or any
      ** other error) while doing so, return immediately.
      **
      ** This branch may also run if pNewRowid contains a value that cannot
      ** be losslessly converted to an integer. In this case, the eventual 
      ** call to fts3InsertData() (either just below or further on in this
      ** function) will return SQLITE_MISMATCH.
      */
      if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
        rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel);
      }else{
        rc = fts3InsertData(p, apVal, pRowid);
        bInsertDone = 1;
      }
    }

Changes to main.mk.

295
296
297
298
299
300
301

302
303
304
305
306
307
308
  $(TOP)/src/vdbe.c \
  $(TOP)/src/vdbemem.c \
  $(TOP)/src/where.c \
  parse.c \
  $(TOP)/ext/fts3/fts3.c \
  $(TOP)/ext/fts3/fts3_aux.c \
  $(TOP)/ext/fts3/fts3_expr.c \

  $(TOP)/ext/fts3/fts3_tokenizer.c \
  $(TOP)/ext/fts3/fts3_write.c \
  $(TOP)/ext/async/sqlite3async.c

# Header files used by all library source files.
#
HDR = \







>







295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
  $(TOP)/src/vdbe.c \
  $(TOP)/src/vdbemem.c \
  $(TOP)/src/where.c \
  parse.c \
  $(TOP)/ext/fts3/fts3.c \
  $(TOP)/ext/fts3/fts3_aux.c \
  $(TOP)/ext/fts3/fts3_expr.c \
  $(TOP)/ext/fts3/fts3_term.c \
  $(TOP)/ext/fts3/fts3_tokenizer.c \
  $(TOP)/ext/fts3/fts3_write.c \
  $(TOP)/ext/async/sqlite3async.c

# Header files used by all library source files.
#
HDR = \

Changes to src/sqlite.h.in.

4603
4604
4605
4606
4607
4608
4609





4610
4611
4612
4613
4614
4615
4616
  int (*xSync)(sqlite3_vtab *pVTab);
  int (*xCommit)(sqlite3_vtab *pVTab);
  int (*xRollback)(sqlite3_vtab *pVTab);
  int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName,
                       void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
                       void **ppArg);
  int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);





};

/*
** CAPI3REF: Virtual Table Indexing Information
** KEYWORDS: sqlite3_index_info
**
** The sqlite3_index_info structure and its substructures is used as part







>
>
>
>
>







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

/*
** CAPI3REF: Virtual Table Indexing Information
** KEYWORDS: sqlite3_index_info
**
** The sqlite3_index_info structure and its substructures is used as part

Changes to src/sqliteInt.h.

3045
3046
3047
3048
3049
3050
3051

3052
3053
3054
3055
3056
3057
3058
3059

3060
3061
3062
3063
3064
3065
3066
#  define sqlite3VtabSync(X,Y) SQLITE_OK
#  define sqlite3VtabRollback(X)
#  define sqlite3VtabCommit(X)
#  define sqlite3VtabInSync(db) 0
#  define sqlite3VtabLock(X) 
#  define sqlite3VtabUnlock(X)
#  define sqlite3VtabUnlockList(X)

#else
   void sqlite3VtabClear(sqlite3 *db, Table*);
   int sqlite3VtabSync(sqlite3 *db, char **);
   int sqlite3VtabRollback(sqlite3 *db);
   int sqlite3VtabCommit(sqlite3 *db);
   void sqlite3VtabLock(VTable *);
   void sqlite3VtabUnlock(VTable *);
   void sqlite3VtabUnlockList(sqlite3*);

#  define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0)
#endif
void sqlite3VtabMakeWritable(Parse*,Table*);
void sqlite3VtabBeginParse(Parse*, Token*, Token*, Token*);
void sqlite3VtabFinishParse(Parse*, Token*);
void sqlite3VtabArgInit(Parse*);
void sqlite3VtabArgExtend(Parse*, Token*);







>








>







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

Changes to src/vdbe.c.

2817
2818
2819
2820
2821
2822
2823



2824

2825
2826
2827
2828
2829
2830
2831
    ){
      assert( sqlite3BtreeIsInTrans(pBt) );
      if( p->iStatement==0 ){
        assert( db->nStatement>=0 && db->nSavepoint>=0 );
        db->nStatement++; 
        p->iStatement = db->nSavepoint + db->nStatement;
      }



      rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);


      /* Store the current value of the database handles deferred constraint
      ** counter. If the statement transaction needs to be rolled back,
      ** the value of this counter needs to be restored too.  */
      p->nStmtDefCons = db->nDeferredCons;
    }
  }







>
>
>
|
>







2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
    ){
      assert( sqlite3BtreeIsInTrans(pBt) );
      if( p->iStatement==0 ){
        assert( db->nStatement>=0 && db->nSavepoint>=0 );
        db->nStatement++; 
        p->iStatement = db->nSavepoint + db->nStatement;
      }

      rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement);
      if( rc==SQLITE_OK ){
        rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
      }

      /* Store the current value of the database handles deferred constraint
      ** counter. If the statement transaction needs to be rolled back,
      ** the value of this counter needs to be restored too.  */
      p->nStmtDefCons = db->nDeferredCons;
    }
  }

Changes to src/vdbeaux.c.

2008
2009
2010
2011
2012
2013
2014









2015
2016
2017
2018
2019
2020
2021
        if( rc==SQLITE_OK ){
          rc = rc2;
        }
      }
    }
    db->nStatement--;
    p->iStatement = 0;










    /* If the statement transaction is being rolled back, also restore the 
    ** database handles deferred constraint counter to the value it had when 
    ** the statement transaction was opened.  */
    if( eOp==SAVEPOINT_ROLLBACK ){
      db->nDeferredCons = p->nStmtDefCons;
    }







>
>
>
>
>
>
>
>
>







2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
        if( rc==SQLITE_OK ){
          rc = rc2;
        }
      }
    }
    db->nStatement--;
    p->iStatement = 0;

    if( rc==SQLITE_OK ){
      if( eOp==SAVEPOINT_ROLLBACK ){
        rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint);
      }
      if( rc==SQLITE_OK ){
        rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint);
      }
    }

    /* If the statement transaction is being rolled back, also restore the 
    ** database handles deferred constraint counter to the value it had when 
    ** the statement transaction was opened.  */
    if( eOp==SAVEPOINT_ROLLBACK ){
      db->nDeferredCons = p->nStmtDefCons;
    }

Changes to src/vtab.c.

832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851


























852
853
854
855
856
857
858
    return SQLITE_OK;
  } 
  pModule = pVTab->pVtab->pModule;

  if( pModule->xBegin ){
    int i;


    /* If pVtab is already in the aVTrans array, return early */
    for(i=0; i<db->nVTrans; i++){
      if( db->aVTrans[i]==pVTab ){
        return SQLITE_OK;
      }
    }

    /* Invoke the xBegin method */
    rc = pModule->xBegin(pVTab->pVtab);
    if( rc==SQLITE_OK ){
      rc = addToVTrans(db, pVTab);
    }


























  }
  return rc;
}

/*
** The first parameter (pDef) is a function implementation.  The
** second parameter (pExpr) is the first argument to this function.







<












>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







832
833
834
835
836
837
838

839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
    return SQLITE_OK;
  } 
  pModule = pVTab->pVtab->pModule;

  if( pModule->xBegin ){
    int i;


    /* If pVtab is already in the aVTrans array, return early */
    for(i=0; i<db->nVTrans; i++){
      if( db->aVTrans[i]==pVTab ){
        return SQLITE_OK;
      }
    }

    /* Invoke the xBegin method */
    rc = pModule->xBegin(pVTab->pVtab);
    if( rc==SQLITE_OK ){
      rc = addToVTrans(db, pVTab);
    }
  }
  return rc;
}

int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
  int i;
  int rc = SQLITE_OK;

  assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN );

  for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){
    sqlite3_vtab *pVtab = db->aVTrans[i]->pVtab;
    sqlite3_module *pMod = db->aVTrans[i]->pMod->pModule;
    if( pMod->iVersion>=1 ){
      switch( op ){
        case SAVEPOINT_BEGIN:
          rc = pMod->xSavepoint(pVtab, iSavepoint);
          break;
        case SAVEPOINT_ROLLBACK:
          rc = pMod->xRollbackTo(pVtab, iSavepoint);
          break;
        default:
          rc = pMod->xRelease(pVtab, iSavepoint);
          break;
      }
    }
  }
  return rc;
}

/*
** The first parameter (pDef) is a function implementation.  The
** second parameter (pExpr) is the first argument to this function.

Changes to test/fts3conf.test.

18
19
20
21
22
23
24




































25
26
27
28
29
30
31
32
33
34
35
36
37
38



39
40
41
42
43
44
45
46
47
48
49
50












51
52
53
54
55
56
57
58
59
60

61

62


63

# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
  finish_test
  return
}





































do_execsql_test 1.0 {
  CREATE VIRTUAL TABLE t1 USING fts3(x);
  INSERT INTO t1(rowid, x) VALUES(1, 'a b c d');
  INSERT INTO t1(rowid, x) VALUES(2, 'e f g h');

  CREATE TABLE source(a, b);
  INSERT INTO source VALUES(4, 'z');
  INSERT INTO source VALUES(2, 'y');
}
db_save_and_close

set T1 "INTO t1(rowid, x) VALUES(1, 'x')"
set T2 "INTO t1(rowid, x) SELECT * FROM source"




foreach {tn sql constraint data} [subst {
  1    "INSERT OR ROLLBACK $T1"   1 {{a b c d} {e f g h}}
  2    "INSERT OR ABORT    $T1"   1 {{a b c d} {e f g h} {i j k l}}
  3    "INSERT OR FAIL     $T1"   1 {{a b c d} {e f g h} {i j k l}}
  4    "INSERT OR IGNORE   $T1"   0 {{a b c d} {e f g h} {i j k l}}
  5    "INSERT OR REPLACE  $T1"   0 {x {e f g h} {i j k l}}

  6    "INSERT OR ROLLBACK $T2"   1 {{a b c d} {e f g h}}
  7    "INSERT OR ABORT    $T2"   1 {{a b c d} {e f g h} {i j k l}}
  8    "INSERT OR FAIL     $T2"   1 {{a b c d} {e f g h} {i j k l} z}
  9    "INSERT OR IGNORE   $T2"   0 {{a b c d} {e f g h} {i j k l} z}
  10   "INSERT OR REPLACE  $T2"   0 {{a b c d} y {i j k l} z}












}] {
  db_restore_and_reopen
  execsql { 
    BEGIN;
      INSERT INTO t1(rowid, x) VALUES(3, 'i j k l');
  }
  set R(0) {0 {}}
  set R(1) {1 {constraint failed}}
  do_catchsql_test 1.$tn.1 $sql $R($constraint)
  do_catchsql_test 1.$tn.2 { SELECT * FROM t1 } [list 0 $data]

}




finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|













>
>
>












>
>
>
>
>
>
>
>
>
>
>
>










>
|
>
|
>
>

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

# If SQLITE_ENABLE_FTS3 is defined, omit this file.
ifcapable !fts3 {
  finish_test
  return
}


proc fts3_integrity {tn db tbl} {

  if {[sqlite3_get_autocommit $db]==0} {
    error "fts3_integrity does not work with an open transaction"
  }

  set sql [db one {SELECT sql FROM sqlite_master WHERE name = $tbl}]
  regexp -nocase {[^(]* using (.*)} $sql -> tail
  set cols [list]
  $db eval "PRAGMA table_info($tbl)" {
    lappend cols $name
  }
  set cols [join [concat docid $cols] ,]

  $db eval [subst {
    CREATE VIRTUAL TABLE fts3check USING fts4term($tbl);
    CREATE VIRTUAL TABLE temp.fts3check2 USING $tail;
    INSERT INTO temp.fts3check2($cols) SELECT docid, * FROM $tbl;
    CREATE VIRTUAL TABLE temp.fts3check3 USING fts4term(fts3check2);
  }]

  set m1 [$db one {SELECT md5sum(term, docid, col, pos) FROM fts3check}]
  set m2 [$db one {SELECT md5sum(term, docid, col, pos) FROM fts3check3}]

  $db eval {
    DROP TABLE fts3check;
    DROP TABLE temp.fts3check2;
    DROP TABLE temp.fts3check3;
  }
  
  uplevel [list do_test $tn [list set {} $m1] $m2]
}



do_execsql_test 1.0.1 {
  CREATE VIRTUAL TABLE t1 USING fts3(x);
  INSERT INTO t1(rowid, x) VALUES(1, 'a b c d');
  INSERT INTO t1(rowid, x) VALUES(2, 'e f g h');

  CREATE TABLE source(a, b);
  INSERT INTO source VALUES(4, 'z');
  INSERT INTO source VALUES(2, 'y');
}
db_save_and_close

set T1 "INTO t1(rowid, x) VALUES(1, 'x')"
set T2 "INTO t1(rowid, x) SELECT * FROM source"

set T3 "t1 SET docid = 2 WHERE docid = 1"
set T4 "t1 SET docid = CASE WHEN docid = 1 THEN 4 ELSE 3 END WHERE docid <=2"

foreach {tn sql constraint data} [subst {
  1    "INSERT OR ROLLBACK $T1"   1 {{a b c d} {e f g h}}
  2    "INSERT OR ABORT    $T1"   1 {{a b c d} {e f g h} {i j k l}}
  3    "INSERT OR FAIL     $T1"   1 {{a b c d} {e f g h} {i j k l}}
  4    "INSERT OR IGNORE   $T1"   0 {{a b c d} {e f g h} {i j k l}}
  5    "INSERT OR REPLACE  $T1"   0 {x {e f g h} {i j k l}}

  6    "INSERT OR ROLLBACK $T2"   1 {{a b c d} {e f g h}}
  7    "INSERT OR ABORT    $T2"   1 {{a b c d} {e f g h} {i j k l}}
  8    "INSERT OR FAIL     $T2"   1 {{a b c d} {e f g h} {i j k l} z}
  9    "INSERT OR IGNORE   $T2"   0 {{a b c d} {e f g h} {i j k l} z}
  10   "INSERT OR REPLACE  $T2"   0 {{a b c d} y {i j k l} z}

  11   "UPDATE OR ROLLBACK $T3"   1 {{a b c d} {e f g h}}
  12   "UPDATE OR ABORT    $T3"   1 {{a b c d} {e f g h} {i j k l}}
  13   "UPDATE OR FAIL     $T3"   1 {{a b c d} {e f g h} {i j k l}}
  14   "UPDATE OR IGNORE   $T3"   0 {{a b c d} {e f g h} {i j k l}}
  15   "UPDATE OR REPLACE  $T3"   0 {{a b c d} {i j k l}}

  16   "UPDATE OR ROLLBACK $T4"   1 {{a b c d} {e f g h}}
  17   "UPDATE OR ABORT    $T4"   1 {{a b c d} {e f g h} {i j k l}}
  18   "UPDATE OR FAIL     $T4"   1 {{e f g h} {i j k l} {a b c d}}
  19   "UPDATE OR IGNORE   $T4"   0 {{e f g h} {i j k l} {a b c d}}
  20   "UPDATE OR REPLACE  $T4"   0 {{e f g h} {a b c d}}
}] {
  db_restore_and_reopen
  execsql { 
    BEGIN;
      INSERT INTO t1(rowid, x) VALUES(3, 'i j k l');
  }
  set R(0) {0 {}}
  set R(1) {1 {constraint failed}}
  do_catchsql_test 1.$tn.1 $sql $R($constraint)
  do_catchsql_test 1.$tn.2 { SELECT * FROM t1 } [list 0 $data]
  catchsql COMMIT

  fts3_integrity 1.$tn.3 db t1
}


finish_test