Updates to the MSVC makefiles.
[sqlite.git] / ext / fts5 / fts5_vocab.c
blob82c7dc90560b150049badb21948d6b346c8ca7fe
1 /*
2 ** 2015 May 08
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.
11 ******************************************************************************
13 ** This is an SQLite virtual table module implementing direct access to an
14 ** existing FTS5 index. The module may create several different types of
15 ** tables:
17 ** col:
18 ** CREATE TABLE vocab(term, col, doc, cnt, PRIMARY KEY(term, col));
20 ** One row for each term/column combination. The value of $doc is set to
21 ** the number of fts5 rows that contain at least one instance of term
22 ** $term within column $col. Field $cnt is set to the total number of
23 ** instances of term $term in column $col (in any row of the fts5 table).
25 ** row:
26 ** CREATE TABLE vocab(term, doc, cnt, PRIMARY KEY(term));
28 ** One row for each term in the database. The value of $doc is set to
29 ** the number of fts5 rows that contain at least one instance of term
30 ** $term. Field $cnt is set to the total number of instances of term
31 ** $term in the database.
35 #include "fts5Int.h"
38 typedef struct Fts5VocabTable Fts5VocabTable;
39 typedef struct Fts5VocabCursor Fts5VocabCursor;
41 struct Fts5VocabTable {
42 sqlite3_vtab base;
43 char *zFts5Tbl; /* Name of fts5 table */
44 char *zFts5Db; /* Db containing fts5 table */
45 sqlite3 *db; /* Database handle */
46 Fts5Global *pGlobal; /* FTS5 global object for this database */
47 int eType; /* FTS5_VOCAB_COL or ROW */
50 struct Fts5VocabCursor {
51 sqlite3_vtab_cursor base;
52 sqlite3_stmt *pStmt; /* Statement holding lock on pIndex */
53 Fts5Index *pIndex; /* Associated FTS5 index */
55 int bEof; /* True if this cursor is at EOF */
56 Fts5IndexIter *pIter; /* Term/rowid iterator object */
58 int nLeTerm; /* Size of zLeTerm in bytes */
59 char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */
61 /* These are used by 'col' tables only */
62 Fts5Config *pConfig; /* Fts5 table configuration */
63 int iCol;
64 i64 *aCnt;
65 i64 *aDoc;
67 /* Output values used by 'row' and 'col' tables */
68 i64 rowid; /* This table's current rowid value */
69 Fts5Buffer term; /* Current value of 'term' column */
72 #define FTS5_VOCAB_COL 0
73 #define FTS5_VOCAB_ROW 1
75 #define FTS5_VOCAB_COL_SCHEMA "term, col, doc, cnt"
76 #define FTS5_VOCAB_ROW_SCHEMA "term, doc, cnt"
79 ** Bits for the mask used as the idxNum value by xBestIndex/xFilter.
81 #define FTS5_VOCAB_TERM_EQ 0x01
82 #define FTS5_VOCAB_TERM_GE 0x02
83 #define FTS5_VOCAB_TERM_LE 0x04
87 ** Translate a string containing an fts5vocab table type to an
88 ** FTS5_VOCAB_XXX constant. If successful, set *peType to the output
89 ** value and return SQLITE_OK. Otherwise, set *pzErr to an error message
90 ** and return SQLITE_ERROR.
92 static int fts5VocabTableType(const char *zType, char **pzErr, int *peType){
93 int rc = SQLITE_OK;
94 char *zCopy = sqlite3Fts5Strndup(&rc, zType, -1);
95 if( rc==SQLITE_OK ){
96 sqlite3Fts5Dequote(zCopy);
97 if( sqlite3_stricmp(zCopy, "col")==0 ){
98 *peType = FTS5_VOCAB_COL;
99 }else
101 if( sqlite3_stricmp(zCopy, "row")==0 ){
102 *peType = FTS5_VOCAB_ROW;
103 }else
105 *pzErr = sqlite3_mprintf("fts5vocab: unknown table type: %Q", zCopy);
106 rc = SQLITE_ERROR;
108 sqlite3_free(zCopy);
111 return rc;
116 ** The xDisconnect() virtual table method.
118 static int fts5VocabDisconnectMethod(sqlite3_vtab *pVtab){
119 Fts5VocabTable *pTab = (Fts5VocabTable*)pVtab;
120 sqlite3_free(pTab);
121 return SQLITE_OK;
125 ** The xDestroy() virtual table method.
127 static int fts5VocabDestroyMethod(sqlite3_vtab *pVtab){
128 Fts5VocabTable *pTab = (Fts5VocabTable*)pVtab;
129 sqlite3_free(pTab);
130 return SQLITE_OK;
134 ** This function is the implementation of both the xConnect and xCreate
135 ** methods of the FTS3 virtual table.
137 ** The argv[] array contains the following:
139 ** argv[0] -> module name ("fts5vocab")
140 ** argv[1] -> database name
141 ** argv[2] -> table name
143 ** then:
145 ** argv[3] -> name of fts5 table
146 ** argv[4] -> type of fts5vocab table
148 ** or, for tables in the TEMP schema only.
150 ** argv[3] -> name of fts5 tables database
151 ** argv[4] -> name of fts5 table
152 ** argv[5] -> type of fts5vocab table
154 static int fts5VocabInitVtab(
155 sqlite3 *db, /* The SQLite database connection */
156 void *pAux, /* Pointer to Fts5Global object */
157 int argc, /* Number of elements in argv array */
158 const char * const *argv, /* xCreate/xConnect argument array */
159 sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */
160 char **pzErr /* Write any error message here */
162 const char *azSchema[] = {
163 "CREATE TABlE vocab(" FTS5_VOCAB_COL_SCHEMA ")",
164 "CREATE TABlE vocab(" FTS5_VOCAB_ROW_SCHEMA ")"
167 Fts5VocabTable *pRet = 0;
168 int rc = SQLITE_OK; /* Return code */
169 int bDb;
171 bDb = (argc==6 && strlen(argv[1])==4 && memcmp("temp", argv[1], 4)==0);
173 if( argc!=5 && bDb==0 ){
174 *pzErr = sqlite3_mprintf("wrong number of vtable arguments");
175 rc = SQLITE_ERROR;
176 }else{
177 int nByte; /* Bytes of space to allocate */
178 const char *zDb = bDb ? argv[3] : argv[1];
179 const char *zTab = bDb ? argv[4] : argv[3];
180 const char *zType = bDb ? argv[5] : argv[4];
181 int nDb = (int)strlen(zDb)+1;
182 int nTab = (int)strlen(zTab)+1;
183 int eType = 0;
185 rc = fts5VocabTableType(zType, pzErr, &eType);
186 if( rc==SQLITE_OK ){
187 assert( eType>=0 && eType<ArraySize(azSchema) );
188 rc = sqlite3_declare_vtab(db, azSchema[eType]);
191 nByte = sizeof(Fts5VocabTable) + nDb + nTab;
192 pRet = sqlite3Fts5MallocZero(&rc, nByte);
193 if( pRet ){
194 pRet->pGlobal = (Fts5Global*)pAux;
195 pRet->eType = eType;
196 pRet->db = db;
197 pRet->zFts5Tbl = (char*)&pRet[1];
198 pRet->zFts5Db = &pRet->zFts5Tbl[nTab];
199 memcpy(pRet->zFts5Tbl, zTab, nTab);
200 memcpy(pRet->zFts5Db, zDb, nDb);
201 sqlite3Fts5Dequote(pRet->zFts5Tbl);
202 sqlite3Fts5Dequote(pRet->zFts5Db);
206 *ppVTab = (sqlite3_vtab*)pRet;
207 return rc;
212 ** The xConnect() and xCreate() methods for the virtual table. All the
213 ** work is done in function fts5VocabInitVtab().
215 static int fts5VocabConnectMethod(
216 sqlite3 *db, /* Database connection */
217 void *pAux, /* Pointer to tokenizer hash table */
218 int argc, /* Number of elements in argv array */
219 const char * const *argv, /* xCreate/xConnect argument array */
220 sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
221 char **pzErr /* OUT: sqlite3_malloc'd error message */
223 return fts5VocabInitVtab(db, pAux, argc, argv, ppVtab, pzErr);
225 static int fts5VocabCreateMethod(
226 sqlite3 *db, /* Database connection */
227 void *pAux, /* Pointer to tokenizer hash table */
228 int argc, /* Number of elements in argv array */
229 const char * const *argv, /* xCreate/xConnect argument array */
230 sqlite3_vtab **ppVtab, /* OUT: New sqlite3_vtab object */
231 char **pzErr /* OUT: sqlite3_malloc'd error message */
233 return fts5VocabInitVtab(db, pAux, argc, argv, ppVtab, pzErr);
237 ** Implementation of the xBestIndex method.
239 static int fts5VocabBestIndexMethod(
240 sqlite3_vtab *pUnused,
241 sqlite3_index_info *pInfo
243 int i;
244 int iTermEq = -1;
245 int iTermGe = -1;
246 int iTermLe = -1;
247 int idxNum = 0;
248 int nArg = 0;
250 UNUSED_PARAM(pUnused);
252 for(i=0; i<pInfo->nConstraint; i++){
253 struct sqlite3_index_constraint *p = &pInfo->aConstraint[i];
254 if( p->usable==0 ) continue;
255 if( p->iColumn==0 ){ /* term column */
256 if( p->op==SQLITE_INDEX_CONSTRAINT_EQ ) iTermEq = i;
257 if( p->op==SQLITE_INDEX_CONSTRAINT_LE ) iTermLe = i;
258 if( p->op==SQLITE_INDEX_CONSTRAINT_LT ) iTermLe = i;
259 if( p->op==SQLITE_INDEX_CONSTRAINT_GE ) iTermGe = i;
260 if( p->op==SQLITE_INDEX_CONSTRAINT_GT ) iTermGe = i;
264 if( iTermEq>=0 ){
265 idxNum |= FTS5_VOCAB_TERM_EQ;
266 pInfo->aConstraintUsage[iTermEq].argvIndex = ++nArg;
267 pInfo->estimatedCost = 100;
268 }else{
269 pInfo->estimatedCost = 1000000;
270 if( iTermGe>=0 ){
271 idxNum |= FTS5_VOCAB_TERM_GE;
272 pInfo->aConstraintUsage[iTermGe].argvIndex = ++nArg;
273 pInfo->estimatedCost = pInfo->estimatedCost / 2;
275 if( iTermLe>=0 ){
276 idxNum |= FTS5_VOCAB_TERM_LE;
277 pInfo->aConstraintUsage[iTermLe].argvIndex = ++nArg;
278 pInfo->estimatedCost = pInfo->estimatedCost / 2;
282 /* This virtual table always delivers results in ascending order of
283 ** the "term" column (column 0). So if the user has requested this
284 ** specifically - "ORDER BY term" or "ORDER BY term ASC" - set the
285 ** sqlite3_index_info.orderByConsumed flag to tell the core the results
286 ** are already in sorted order. */
287 if( pInfo->nOrderBy==1
288 && pInfo->aOrderBy[0].iColumn==0
289 && pInfo->aOrderBy[0].desc==0
291 pInfo->orderByConsumed = 1;
294 pInfo->idxNum = idxNum;
295 return SQLITE_OK;
299 ** Implementation of xOpen method.
301 static int fts5VocabOpenMethod(
302 sqlite3_vtab *pVTab,
303 sqlite3_vtab_cursor **ppCsr
305 Fts5VocabTable *pTab = (Fts5VocabTable*)pVTab;
306 Fts5Index *pIndex = 0;
307 Fts5Config *pConfig = 0;
308 Fts5VocabCursor *pCsr = 0;
309 int rc = SQLITE_OK;
310 sqlite3_stmt *pStmt = 0;
311 char *zSql = 0;
313 zSql = sqlite3Fts5Mprintf(&rc,
314 "SELECT t.%Q FROM %Q.%Q AS t WHERE t.%Q MATCH '*id'",
315 pTab->zFts5Tbl, pTab->zFts5Db, pTab->zFts5Tbl, pTab->zFts5Tbl
317 if( zSql ){
318 rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pStmt, 0);
320 sqlite3_free(zSql);
321 assert( rc==SQLITE_OK || pStmt==0 );
322 if( rc==SQLITE_ERROR ) rc = SQLITE_OK;
324 if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
325 i64 iId = sqlite3_column_int64(pStmt, 0);
326 pIndex = sqlite3Fts5IndexFromCsrid(pTab->pGlobal, iId, &pConfig);
329 if( rc==SQLITE_OK && pIndex==0 ){
330 rc = sqlite3_finalize(pStmt);
331 pStmt = 0;
332 if( rc==SQLITE_OK ){
333 pVTab->zErrMsg = sqlite3_mprintf(
334 "no such fts5 table: %s.%s", pTab->zFts5Db, pTab->zFts5Tbl
336 rc = SQLITE_ERROR;
340 if( rc==SQLITE_OK ){
341 int nByte = pConfig->nCol * sizeof(i64) * 2 + sizeof(Fts5VocabCursor);
342 pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte);
345 if( pCsr ){
346 pCsr->pIndex = pIndex;
347 pCsr->pStmt = pStmt;
348 pCsr->pConfig = pConfig;
349 pCsr->aCnt = (i64*)&pCsr[1];
350 pCsr->aDoc = &pCsr->aCnt[pConfig->nCol];
351 }else{
352 sqlite3_finalize(pStmt);
355 *ppCsr = (sqlite3_vtab_cursor*)pCsr;
356 return rc;
359 static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){
360 pCsr->rowid = 0;
361 sqlite3Fts5IterClose(pCsr->pIter);
362 pCsr->pIter = 0;
363 sqlite3_free(pCsr->zLeTerm);
364 pCsr->nLeTerm = -1;
365 pCsr->zLeTerm = 0;
369 ** Close the cursor. For additional information see the documentation
370 ** on the xClose method of the virtual table interface.
372 static int fts5VocabCloseMethod(sqlite3_vtab_cursor *pCursor){
373 Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
374 fts5VocabResetCursor(pCsr);
375 sqlite3Fts5BufferFree(&pCsr->term);
376 sqlite3_finalize(pCsr->pStmt);
377 sqlite3_free(pCsr);
378 return SQLITE_OK;
383 ** Advance the cursor to the next row in the table.
385 static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){
386 Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
387 Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab;
388 int rc = SQLITE_OK;
389 int nCol = pCsr->pConfig->nCol;
391 pCsr->rowid++;
393 if( pTab->eType==FTS5_VOCAB_COL ){
394 for(pCsr->iCol++; pCsr->iCol<nCol; pCsr->iCol++){
395 if( pCsr->aDoc[pCsr->iCol] ) break;
399 if( pTab->eType==FTS5_VOCAB_ROW || pCsr->iCol>=nCol ){
400 if( sqlite3Fts5IterEof(pCsr->pIter) ){
401 pCsr->bEof = 1;
402 }else{
403 const char *zTerm;
404 int nTerm;
406 zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm);
407 if( pCsr->nLeTerm>=0 ){
408 int nCmp = MIN(nTerm, pCsr->nLeTerm);
409 int bCmp = memcmp(pCsr->zLeTerm, zTerm, nCmp);
410 if( bCmp<0 || (bCmp==0 && pCsr->nLeTerm<nTerm) ){
411 pCsr->bEof = 1;
412 return SQLITE_OK;
416 sqlite3Fts5BufferSet(&rc, &pCsr->term, nTerm, (const u8*)zTerm);
417 memset(pCsr->aCnt, 0, nCol * sizeof(i64));
418 memset(pCsr->aDoc, 0, nCol * sizeof(i64));
419 pCsr->iCol = 0;
421 assert( pTab->eType==FTS5_VOCAB_COL || pTab->eType==FTS5_VOCAB_ROW );
422 while( rc==SQLITE_OK ){
423 const u8 *pPos; int nPos; /* Position list */
424 i64 iPos = 0; /* 64-bit position read from poslist */
425 int iOff = 0; /* Current offset within position list */
427 pPos = pCsr->pIter->pData;
428 nPos = pCsr->pIter->nData;
429 switch( pCsr->pConfig->eDetail ){
430 case FTS5_DETAIL_FULL:
431 pPos = pCsr->pIter->pData;
432 nPos = pCsr->pIter->nData;
433 if( pTab->eType==FTS5_VOCAB_ROW ){
434 while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
435 pCsr->aCnt[0]++;
437 pCsr->aDoc[0]++;
438 }else{
439 int iCol = -1;
440 while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){
441 int ii = FTS5_POS2COLUMN(iPos);
442 pCsr->aCnt[ii]++;
443 if( iCol!=ii ){
444 if( ii>=nCol ){
445 rc = FTS5_CORRUPT;
446 break;
448 pCsr->aDoc[ii]++;
449 iCol = ii;
453 break;
455 case FTS5_DETAIL_COLUMNS:
456 if( pTab->eType==FTS5_VOCAB_ROW ){
457 pCsr->aDoc[0]++;
458 }else{
459 while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff,&iPos) ){
460 assert_nc( iPos>=0 && iPos<nCol );
461 if( iPos>=nCol ){
462 rc = FTS5_CORRUPT;
463 break;
465 pCsr->aDoc[iPos]++;
468 break;
470 default:
471 assert( pCsr->pConfig->eDetail==FTS5_DETAIL_NONE );
472 pCsr->aDoc[0]++;
473 break;
476 if( rc==SQLITE_OK ){
477 rc = sqlite3Fts5IterNextScan(pCsr->pIter);
480 if( rc==SQLITE_OK ){
481 zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm);
482 if( nTerm!=pCsr->term.n || memcmp(zTerm, pCsr->term.p, nTerm) ){
483 break;
485 if( sqlite3Fts5IterEof(pCsr->pIter) ) break;
491 if( rc==SQLITE_OK && pCsr->bEof==0 && pTab->eType==FTS5_VOCAB_COL ){
492 while( pCsr->aDoc[pCsr->iCol]==0 ) pCsr->iCol++;
493 assert( pCsr->iCol<pCsr->pConfig->nCol );
495 return rc;
499 ** This is the xFilter implementation for the virtual table.
501 static int fts5VocabFilterMethod(
502 sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */
503 int idxNum, /* Strategy index */
504 const char *zUnused, /* Unused */
505 int nUnused, /* Number of elements in apVal */
506 sqlite3_value **apVal /* Arguments for the indexing scheme */
508 Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
509 int rc = SQLITE_OK;
511 int iVal = 0;
512 int f = FTS5INDEX_QUERY_SCAN;
513 const char *zTerm = 0;
514 int nTerm = 0;
516 sqlite3_value *pEq = 0;
517 sqlite3_value *pGe = 0;
518 sqlite3_value *pLe = 0;
520 UNUSED_PARAM2(zUnused, nUnused);
522 fts5VocabResetCursor(pCsr);
523 if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++];
524 if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++];
525 if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++];
527 if( pEq ){
528 zTerm = (const char *)sqlite3_value_text(pEq);
529 nTerm = sqlite3_value_bytes(pEq);
530 f = 0;
531 }else{
532 if( pGe ){
533 zTerm = (const char *)sqlite3_value_text(pGe);
534 nTerm = sqlite3_value_bytes(pGe);
536 if( pLe ){
537 const char *zCopy = (const char *)sqlite3_value_text(pLe);
538 pCsr->nLeTerm = sqlite3_value_bytes(pLe);
539 pCsr->zLeTerm = sqlite3_malloc(pCsr->nLeTerm+1);
540 if( pCsr->zLeTerm==0 ){
541 rc = SQLITE_NOMEM;
542 }else{
543 memcpy(pCsr->zLeTerm, zCopy, pCsr->nLeTerm+1);
549 if( rc==SQLITE_OK ){
550 rc = sqlite3Fts5IndexQuery(pCsr->pIndex, zTerm, nTerm, f, 0, &pCsr->pIter);
552 if( rc==SQLITE_OK ){
553 rc = fts5VocabNextMethod(pCursor);
556 return rc;
560 ** This is the xEof method of the virtual table. SQLite calls this
561 ** routine to find out if it has reached the end of a result set.
563 static int fts5VocabEofMethod(sqlite3_vtab_cursor *pCursor){
564 Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
565 return pCsr->bEof;
568 static int fts5VocabColumnMethod(
569 sqlite3_vtab_cursor *pCursor, /* Cursor to retrieve value from */
570 sqlite3_context *pCtx, /* Context for sqlite3_result_xxx() calls */
571 int iCol /* Index of column to read value from */
573 Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
574 int eDetail = pCsr->pConfig->eDetail;
575 int eType = ((Fts5VocabTable*)(pCursor->pVtab))->eType;
576 i64 iVal = 0;
578 if( iCol==0 ){
579 sqlite3_result_text(
580 pCtx, (const char*)pCsr->term.p, pCsr->term.n, SQLITE_TRANSIENT
582 }else if( eType==FTS5_VOCAB_COL ){
583 assert( iCol==1 || iCol==2 || iCol==3 );
584 if( iCol==1 ){
585 if( eDetail!=FTS5_DETAIL_NONE ){
586 const char *z = pCsr->pConfig->azCol[pCsr->iCol];
587 sqlite3_result_text(pCtx, z, -1, SQLITE_STATIC);
589 }else if( iCol==2 ){
590 iVal = pCsr->aDoc[pCsr->iCol];
591 }else{
592 iVal = pCsr->aCnt[pCsr->iCol];
594 }else{
595 assert( iCol==1 || iCol==2 );
596 if( iCol==1 ){
597 iVal = pCsr->aDoc[0];
598 }else{
599 iVal = pCsr->aCnt[0];
603 if( iVal>0 ) sqlite3_result_int64(pCtx, iVal);
604 return SQLITE_OK;
608 ** This is the xRowid method. The SQLite core calls this routine to
609 ** retrieve the rowid for the current row of the result set. The
610 ** rowid should be written to *pRowid.
612 static int fts5VocabRowidMethod(
613 sqlite3_vtab_cursor *pCursor,
614 sqlite_int64 *pRowid
616 Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor;
617 *pRowid = pCsr->rowid;
618 return SQLITE_OK;
621 int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){
622 static const sqlite3_module fts5Vocab = {
623 /* iVersion */ 2,
624 /* xCreate */ fts5VocabCreateMethod,
625 /* xConnect */ fts5VocabConnectMethod,
626 /* xBestIndex */ fts5VocabBestIndexMethod,
627 /* xDisconnect */ fts5VocabDisconnectMethod,
628 /* xDestroy */ fts5VocabDestroyMethod,
629 /* xOpen */ fts5VocabOpenMethod,
630 /* xClose */ fts5VocabCloseMethod,
631 /* xFilter */ fts5VocabFilterMethod,
632 /* xNext */ fts5VocabNextMethod,
633 /* xEof */ fts5VocabEofMethod,
634 /* xColumn */ fts5VocabColumnMethod,
635 /* xRowid */ fts5VocabRowidMethod,
636 /* xUpdate */ 0,
637 /* xBegin */ 0,
638 /* xSync */ 0,
639 /* xCommit */ 0,
640 /* xRollback */ 0,
641 /* xFindFunction */ 0,
642 /* xRename */ 0,
643 /* xSavepoint */ 0,
644 /* xRelease */ 0,
645 /* xRollbackTo */ 0,
647 void *p = (void*)pGlobal;
649 return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0);