4 ** The author disclaims copyright to this source code. In place of
5 ** a legal notice, here is a blessing:
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
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).
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.
38 typedef struct Fts5VocabTable Fts5VocabTable
;
39 typedef struct Fts5VocabCursor Fts5VocabCursor
;
41 struct Fts5VocabTable
{
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 */
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
){
94 char *zCopy
= sqlite3Fts5Strndup(&rc
, zType
, -1);
96 sqlite3Fts5Dequote(zCopy
);
97 if( sqlite3_stricmp(zCopy
, "col")==0 ){
98 *peType
= FTS5_VOCAB_COL
;
101 if( sqlite3_stricmp(zCopy
, "row")==0 ){
102 *peType
= FTS5_VOCAB_ROW
;
105 *pzErr
= sqlite3_mprintf("fts5vocab: unknown table type: %Q", zCopy
);
116 ** The xDisconnect() virtual table method.
118 static int fts5VocabDisconnectMethod(sqlite3_vtab
*pVtab
){
119 Fts5VocabTable
*pTab
= (Fts5VocabTable
*)pVtab
;
125 ** The xDestroy() virtual table method.
127 static int fts5VocabDestroyMethod(sqlite3_vtab
*pVtab
){
128 Fts5VocabTable
*pTab
= (Fts5VocabTable
*)pVtab
;
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
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 */
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");
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;
185 rc
= fts5VocabTableType(zType
, pzErr
, &eType
);
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
);
194 pRet
->pGlobal
= (Fts5Global
*)pAux
;
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
;
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
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
;
265 idxNum
|= FTS5_VOCAB_TERM_EQ
;
266 pInfo
->aConstraintUsage
[iTermEq
].argvIndex
= ++nArg
;
267 pInfo
->estimatedCost
= 100;
269 pInfo
->estimatedCost
= 1000000;
271 idxNum
|= FTS5_VOCAB_TERM_GE
;
272 pInfo
->aConstraintUsage
[iTermGe
].argvIndex
= ++nArg
;
273 pInfo
->estimatedCost
= pInfo
->estimatedCost
/ 2;
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
;
299 ** Implementation of xOpen method.
301 static int fts5VocabOpenMethod(
303 sqlite3_vtab_cursor
**ppCsr
305 Fts5VocabTable
*pTab
= (Fts5VocabTable
*)pVTab
;
306 Fts5Index
*pIndex
= 0;
307 Fts5Config
*pConfig
= 0;
308 Fts5VocabCursor
*pCsr
= 0;
310 sqlite3_stmt
*pStmt
= 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
318 rc
= sqlite3_prepare_v2(pTab
->db
, zSql
, -1, &pStmt
, 0);
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
);
333 pVTab
->zErrMsg
= sqlite3_mprintf(
334 "no such fts5 table: %s.%s", pTab
->zFts5Db
, pTab
->zFts5Tbl
341 int nByte
= pConfig
->nCol
* sizeof(i64
) * 2 + sizeof(Fts5VocabCursor
);
342 pCsr
= (Fts5VocabCursor
*)sqlite3Fts5MallocZero(&rc
, nByte
);
346 pCsr
->pIndex
= pIndex
;
348 pCsr
->pConfig
= pConfig
;
349 pCsr
->aCnt
= (i64
*)&pCsr
[1];
350 pCsr
->aDoc
= &pCsr
->aCnt
[pConfig
->nCol
];
352 sqlite3_finalize(pStmt
);
355 *ppCsr
= (sqlite3_vtab_cursor
*)pCsr
;
359 static void fts5VocabResetCursor(Fts5VocabCursor
*pCsr
){
361 sqlite3Fts5IterClose(pCsr
->pIter
);
363 sqlite3_free(pCsr
->zLeTerm
);
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
);
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
;
389 int nCol
= pCsr
->pConfig
->nCol
;
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
) ){
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
) ){
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
));
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
) ){
440 while( 0==sqlite3Fts5PoslistNext64(pPos
, nPos
, &iOff
, &iPos
) ){
441 int ii
= FTS5_POS2COLUMN(iPos
);
455 case FTS5_DETAIL_COLUMNS
:
456 if( pTab
->eType
==FTS5_VOCAB_ROW
){
459 while( 0==sqlite3Fts5PoslistNext64(pPos
, nPos
, &iOff
,&iPos
) ){
460 assert_nc( iPos
>=0 && iPos
<nCol
);
471 assert( pCsr
->pConfig
->eDetail
==FTS5_DETAIL_NONE
);
477 rc
= sqlite3Fts5IterNextScan(pCsr
->pIter
);
481 zTerm
= sqlite3Fts5IterTerm(pCsr
->pIter
, &nTerm
);
482 if( nTerm
!=pCsr
->term
.n
|| memcmp(zTerm
, pCsr
->term
.p
, nTerm
) ){
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
);
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
;
512 int f
= FTS5INDEX_QUERY_SCAN
;
513 const char *zTerm
= 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
++];
528 zTerm
= (const char *)sqlite3_value_text(pEq
);
529 nTerm
= sqlite3_value_bytes(pEq
);
533 zTerm
= (const char *)sqlite3_value_text(pGe
);
534 nTerm
= sqlite3_value_bytes(pGe
);
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 ){
543 memcpy(pCsr
->zLeTerm
, zCopy
, pCsr
->nLeTerm
+1);
550 rc
= sqlite3Fts5IndexQuery(pCsr
->pIndex
, zTerm
, nTerm
, f
, 0, &pCsr
->pIter
);
553 rc
= fts5VocabNextMethod(pCursor
);
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
;
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
;
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 );
585 if( eDetail
!=FTS5_DETAIL_NONE
){
586 const char *z
= pCsr
->pConfig
->azCol
[pCsr
->iCol
];
587 sqlite3_result_text(pCtx
, z
, -1, SQLITE_STATIC
);
590 iVal
= pCsr
->aDoc
[pCsr
->iCol
];
592 iVal
= pCsr
->aCnt
[pCsr
->iCol
];
595 assert( iCol
==1 || iCol
==2 );
597 iVal
= pCsr
->aDoc
[0];
599 iVal
= pCsr
->aCnt
[0];
603 if( iVal
>0 ) sqlite3_result_int64(pCtx
, iVal
);
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
,
616 Fts5VocabCursor
*pCsr
= (Fts5VocabCursor
*)pCursor
;
617 *pRowid
= pCsr
->rowid
;
621 int sqlite3Fts5VocabInit(Fts5Global
*pGlobal
, sqlite3
*db
){
622 static const sqlite3_module fts5Vocab
= {
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
,
641 /* xFindFunction */ 0,
647 void *p
= (void*)pGlobal
;
649 return sqlite3_create_module_v2(db
, "fts5vocab", &fts5Vocab
, p
, 0);