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 ******************************************************************************
17 #if defined(INCLUDE_SQLITE_TCL_H)
18 # include "sqlite_tcl.h"
21 # ifndef SQLITE_TCLAPI
22 # define SQLITE_TCLAPI
26 #ifdef SQLITE_ENABLE_FTS5
33 extern int sqlite3_fts5_may_be_corrupt
;
35 extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3
*);
36 extern int sqlite3Fts5TestRegisterTok(sqlite3
*, fts5_api
*);
38 /*************************************************************************
39 ** This is a copy of the first part of the SqliteDb structure in
40 ** tclsqlite.c. We need it here so that the get_sqlite_pointer routine
41 ** can extract the sqlite3* pointer from an existing Tcl SQLite
45 extern const char *sqlite3ErrName(int);
52 ** Decode a pointer to an sqlite3 object.
54 static int f5tDbPointer(Tcl_Interp
*interp
, Tcl_Obj
*pObj
, sqlite3
**ppDb
){
57 char *z
= Tcl_GetString(pObj
);
58 if( Tcl_GetCommandInfo(interp
, z
, &cmdInfo
) ){
59 p
= (struct SqliteDb
*)cmdInfo
.objClientData
;
66 /* End of code that accesses the SqliteDb struct.
67 **************************************************************************/
69 static int f5tResultToErrorCode(const char *zRes
){
74 { SQLITE_DONE
, "SQLITE_DONE" },
75 { SQLITE_ERROR
, "SQLITE_ERROR" },
76 { SQLITE_OK
, "SQLITE_OK" },
81 for(i
=0; i
<sizeof(aErr
)/sizeof(aErr
[0]); i
++){
82 if( 0==sqlite3_stricmp(zRes
, aErr
[i
].zError
) ){
90 static int SQLITE_TCLAPI
f5tDbAndApi(
97 int rc
= f5tDbPointer(interp
, pObj
, &db
);
101 sqlite3_stmt
*pStmt
= 0;
104 rc
= sqlite3_prepare_v2(db
, "SELECT fts5(?1)", -1, &pStmt
, 0);
106 Tcl_AppendResult(interp
, "error: ", sqlite3_errmsg(db
), 0);
109 sqlite3_bind_pointer(pStmt
, 1, (void*)&pApi
, "fts5_api_ptr", 0);
112 if( sqlite3_finalize(pStmt
)!=SQLITE_OK
){
113 Tcl_AppendResult(interp
, "error: ", sqlite3_errmsg(db
), 0);
124 typedef struct F5tFunction F5tFunction
;
130 typedef struct F5tApi F5tApi
;
132 const Fts5ExtensionApi
*pApi
;
137 ** An object of this type is used with the xSetAuxdata() and xGetAuxdata()
138 ** API test wrappers. The tcl interface allows a single tcl value to be
139 ** saved using xSetAuxdata(). Instead of simply storing a pointer to the
140 ** tcl object, the code in this file wraps it in an sqlite3_malloc'd
141 ** instance of the following struct so that if the destructor is not
142 ** correctly invoked it will be reported as an SQLite memory leak.
144 typedef struct F5tAuxData F5tAuxData
;
149 static int xTokenizeCb(
152 const char *zToken
, int nToken
,
155 F5tFunction
*p
= (F5tFunction
*)pCtx
;
156 Tcl_Obj
*pEval
= Tcl_DuplicateObj(p
->pScript
);
159 Tcl_IncrRefCount(pEval
);
160 Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewStringObj(zToken
, nToken
));
161 Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewIntObj(iStart
));
162 Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewIntObj(iEnd
));
164 rc
= Tcl_EvalObjEx(p
->interp
, pEval
, 0);
165 Tcl_DecrRefCount(pEval
);
167 rc
= f5tResultToErrorCode(Tcl_GetStringResult(p
->interp
));
173 static int SQLITE_TCLAPI
xF5tApi(void*, Tcl_Interp
*, int, Tcl_Obj
*CONST
[]);
175 static int xQueryPhraseCb(
176 const Fts5ExtensionApi
*pApi
,
180 F5tFunction
*p
= (F5tFunction
*)pCtx
;
181 static sqlite3_int64 iCmd
= 0;
190 sprintf(zCmd
, "f5t_2_%lld", iCmd
++);
191 Tcl_CreateObjCommand(p
->interp
, zCmd
, xF5tApi
, &sApi
, 0);
193 pEval
= Tcl_DuplicateObj(p
->pScript
);
194 Tcl_IncrRefCount(pEval
);
195 Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewStringObj(zCmd
, -1));
196 rc
= Tcl_EvalObjEx(p
->interp
, pEval
, 0);
197 Tcl_DecrRefCount(pEval
);
198 Tcl_DeleteCommand(p
->interp
, zCmd
);
201 rc
= f5tResultToErrorCode(Tcl_GetStringResult(p
->interp
));
207 static void xSetAuxdataDestructor(void *p
){
208 F5tAuxData
*pData
= (F5tAuxData
*)p
;
209 Tcl_DecrRefCount(pData
->pObj
);
214 ** api sub-command...
218 static int SQLITE_TCLAPI
xF5tApi(
222 Tcl_Obj
*CONST objv
[]
229 { "xColumnCount", 0, "" }, /* 0 */
230 { "xRowCount", 0, "" }, /* 1 */
231 { "xColumnTotalSize", 1, "COL" }, /* 2 */
232 { "xTokenize", 2, "TEXT SCRIPT" }, /* 3 */
233 { "xPhraseCount", 0, "" }, /* 4 */
234 { "xPhraseSize", 1, "PHRASE" }, /* 5 */
235 { "xInstCount", 0, "" }, /* 6 */
236 { "xInst", 1, "IDX" }, /* 7 */
237 { "xRowid", 0, "" }, /* 8 */
238 { "xColumnText", 1, "COL" }, /* 9 */
239 { "xColumnSize", 1, "COL" }, /* 10 */
240 { "xQueryPhrase", 2, "PHRASE SCRIPT" }, /* 11 */
241 { "xSetAuxdata", 1, "VALUE" }, /* 12 */
242 { "xGetAuxdata", 1, "CLEAR" }, /* 13 */
243 { "xSetAuxdataInt", 1, "INTEGER" }, /* 14 */
244 { "xGetAuxdataInt", 1, "CLEAR" }, /* 15 */
245 { "xPhraseForeach", 4, "IPHRASE COLVAR OFFVAR SCRIPT" }, /* 16 */
246 { "xPhraseColumnForeach", 3, "IPHRASE COLVAR SCRIPT" }, /* 17 */
248 { "xQueryToken", 2, "IPHRASE ITERM" }, /* 18 */
249 { "xInstToken", 2, "IDX ITERM" }, /* 19 */
255 F5tApi
*p
= (F5tApi
*)clientData
;
258 Tcl_WrongNumArgs(interp
, 1, objv
, "SUB-COMMAND");
262 rc
= Tcl_GetIndexFromObjStruct(
263 interp
, objv
[1], aSub
, sizeof(aSub
[0]), "SUB-COMMAND", 0, &iSub
265 if( rc
!=TCL_OK
) return rc
;
266 if( aSub
[iSub
].nArg
!=objc
-2 ){
267 Tcl_WrongNumArgs(interp
, 1, objv
, aSub
[iSub
].zMsg
);
271 #define CASE(i,str) case i: assert( strcmp(aSub[i].zName, str)==0 );
273 CASE(0, "xColumnCount") {
275 nCol
= p
->pApi
->xColumnCount(p
->pFts
);
277 Tcl_SetObjResult(interp
, Tcl_NewIntObj(nCol
));
281 CASE(1, "xRowCount") {
283 rc
= p
->pApi
->xRowCount(p
->pFts
, &nRow
);
285 Tcl_SetObjResult(interp
, Tcl_NewWideIntObj(nRow
));
289 CASE(2, "xColumnTotalSize") {
292 if( Tcl_GetIntFromObj(interp
, objv
[2], &iCol
) ) return TCL_ERROR
;
293 rc
= p
->pApi
->xColumnTotalSize(p
->pFts
, iCol
, &nSize
);
295 Tcl_SetObjResult(interp
, Tcl_NewWideIntObj(nSize
));
299 CASE(3, "xTokenize") {
301 char *zText
= Tcl_GetStringFromObj(objv
[2], &nText
);
304 ctx
.pScript
= objv
[3];
305 rc
= p
->pApi
->xTokenize(p
->pFts
, zText
, nText
, &ctx
, xTokenizeCb
);
307 Tcl_ResetResult(interp
);
311 CASE(4, "xPhraseCount") {
313 nPhrase
= p
->pApi
->xPhraseCount(p
->pFts
);
315 Tcl_SetObjResult(interp
, Tcl_NewIntObj(nPhrase
));
319 CASE(5, "xPhraseSize") {
322 if( Tcl_GetIntFromObj(interp
, objv
[2], &iPhrase
) ){
325 sz
= p
->pApi
->xPhraseSize(p
->pFts
, iPhrase
);
327 Tcl_SetObjResult(interp
, Tcl_NewIntObj(sz
));
331 CASE(6, "xInstCount") {
333 rc
= p
->pApi
->xInstCount(p
->pFts
, &nInst
);
335 Tcl_SetObjResult(interp
, Tcl_NewIntObj(nInst
));
340 int iIdx
, ip
, ic
, io
;
341 if( Tcl_GetIntFromObj(interp
, objv
[2], &iIdx
) ){
344 rc
= p
->pApi
->xInst(p
->pFts
, iIdx
, &ip
, &ic
, &io
);
346 Tcl_Obj
*pList
= Tcl_NewObj();
347 Tcl_ListObjAppendElement(interp
, pList
, Tcl_NewIntObj(ip
));
348 Tcl_ListObjAppendElement(interp
, pList
, Tcl_NewIntObj(ic
));
349 Tcl_ListObjAppendElement(interp
, pList
, Tcl_NewIntObj(io
));
350 Tcl_SetObjResult(interp
, pList
);
355 sqlite3_int64 iRowid
= p
->pApi
->xRowid(p
->pFts
);
356 Tcl_SetObjResult(interp
, Tcl_NewWideIntObj(iRowid
));
359 CASE(9, "xColumnText") {
363 if( Tcl_GetIntFromObj(interp
, objv
[2], &iCol
) ){
366 rc
= p
->pApi
->xColumnText(p
->pFts
, iCol
, &z
, &n
);
368 Tcl_SetObjResult(interp
, Tcl_NewStringObj(z
, n
));
372 CASE(10, "xColumnSize") {
375 if( Tcl_GetIntFromObj(interp
, objv
[2], &iCol
) ){
378 rc
= p
->pApi
->xColumnSize(p
->pFts
, iCol
, &n
);
380 Tcl_SetObjResult(interp
, Tcl_NewIntObj(n
));
384 CASE(11, "xQueryPhrase") {
387 if( Tcl_GetIntFromObj(interp
, objv
[2], &iPhrase
) ){
391 ctx
.pScript
= objv
[3];
392 rc
= p
->pApi
->xQueryPhrase(p
->pFts
, iPhrase
, &ctx
, xQueryPhraseCb
);
394 Tcl_ResetResult(interp
);
398 CASE(12, "xSetAuxdata") {
399 F5tAuxData
*pData
= (F5tAuxData
*)sqlite3_malloc(sizeof(F5tAuxData
));
401 Tcl_AppendResult(interp
, "out of memory", 0);
404 pData
->pObj
= objv
[2];
405 Tcl_IncrRefCount(pData
->pObj
);
406 rc
= p
->pApi
->xSetAuxdata(p
->pFts
, pData
, xSetAuxdataDestructor
);
409 CASE(13, "xGetAuxdata") {
412 if( Tcl_GetBooleanFromObj(interp
, objv
[2], &bClear
) ){
415 pData
= (F5tAuxData
*)p
->pApi
->xGetAuxdata(p
->pFts
, bClear
);
417 Tcl_ResetResult(interp
);
419 Tcl_SetObjResult(interp
, pData
->pObj
);
421 xSetAuxdataDestructor((void*)pData
);
427 /* These two - xSetAuxdataInt and xGetAuxdataInt - are similar to the
428 ** xSetAuxdata and xGetAuxdata methods implemented above. The difference
429 ** is that they may only save an integer value as auxiliary data, and
430 ** do not specify a destructor function. */
431 CASE(14, "xSetAuxdataInt") {
433 if( Tcl_GetIntFromObj(interp
, objv
[2], &iVal
) ) return TCL_ERROR
;
434 rc
= p
->pApi
->xSetAuxdata(p
->pFts
, (void*)((char*)0 + iVal
), 0);
437 CASE(15, "xGetAuxdataInt") {
440 if( Tcl_GetBooleanFromObj(interp
, objv
[2], &bClear
) ) return TCL_ERROR
;
441 iVal
= (int)((char*)p
->pApi
->xGetAuxdata(p
->pFts
, bClear
) - (char*)0);
442 Tcl_SetObjResult(interp
, Tcl_NewIntObj(iVal
));
446 CASE(16, "xPhraseForeach") {
452 Tcl_Obj
*pScript
= objv
[5];
455 if( Tcl_GetIntFromObj(interp
, objv
[2], &iPhrase
) ) return TCL_ERROR
;
456 zColvar
= Tcl_GetString(objv
[3]);
457 zOffvar
= Tcl_GetString(objv
[4]);
459 rc
= p
->pApi
->xPhraseFirst(p
->pFts
, iPhrase
, &iter
, &iCol
, &iOff
);
461 Tcl_AppendResult(interp
, sqlite3ErrName(rc
), 0);
464 for( ;iCol
>=0; p
->pApi
->xPhraseNext(p
->pFts
, &iter
, &iCol
, &iOff
) ){
465 Tcl_SetVar2Ex(interp
, zColvar
, 0, Tcl_NewIntObj(iCol
), 0);
466 Tcl_SetVar2Ex(interp
, zOffvar
, 0, Tcl_NewIntObj(iOff
), 0);
467 rc
= Tcl_EvalObjEx(interp
, pScript
, 0);
468 if( rc
==TCL_CONTINUE
) rc
= TCL_OK
;
470 if( rc
==TCL_BREAK
) rc
= TCL_OK
;
478 CASE(17, "xPhraseColumnForeach") {
482 Tcl_Obj
*pScript
= objv
[4];
485 if( Tcl_GetIntFromObj(interp
, objv
[2], &iPhrase
) ) return TCL_ERROR
;
486 zColvar
= Tcl_GetString(objv
[3]);
488 rc
= p
->pApi
->xPhraseFirstColumn(p
->pFts
, iPhrase
, &iter
, &iCol
);
490 Tcl_SetResult(interp
, (char*)sqlite3ErrName(rc
), TCL_VOLATILE
);
493 for( ; iCol
>=0; p
->pApi
->xPhraseNextColumn(p
->pFts
, &iter
, &iCol
)){
494 Tcl_SetVar2Ex(interp
, zColvar
, 0, Tcl_NewIntObj(iCol
), 0);
495 rc
= Tcl_EvalObjEx(interp
, pScript
, 0);
496 if( rc
==TCL_CONTINUE
) rc
= TCL_OK
;
498 if( rc
==TCL_BREAK
) rc
= TCL_OK
;
506 CASE(18, "xQueryToken") {
507 const char *pTerm
= 0;
512 if( Tcl_GetIntFromObj(interp
, objv
[2], &iPhrase
) ) return TCL_ERROR
;
513 if( Tcl_GetIntFromObj(interp
, objv
[3], &iTerm
) ) return TCL_ERROR
;
514 rc
= p
->pApi
->xQueryToken(p
->pFts
, iPhrase
, iTerm
, &pTerm
, &nTerm
);
516 Tcl_SetObjResult(interp
, Tcl_NewStringObj(pTerm
, nTerm
));
522 CASE(19, "xInstToken") {
523 const char *pTerm
= 0;
528 if( Tcl_GetIntFromObj(interp
, objv
[2], &iIdx
) ) return TCL_ERROR
;
529 if( Tcl_GetIntFromObj(interp
, objv
[3], &iTerm
) ) return TCL_ERROR
;
530 rc
= p
->pApi
->xInstToken(p
->pFts
, iIdx
, iTerm
, &pTerm
, &nTerm
);
532 Tcl_SetObjResult(interp
, Tcl_NewStringObj(pTerm
, nTerm
));
545 Tcl_SetResult(interp
, (char*)sqlite3ErrName(rc
), TCL_VOLATILE
);
552 static void xF5tFunction(
553 const Fts5ExtensionApi
*pApi
, /* API offered by current FTS version */
554 Fts5Context
*pFts
, /* First arg to pass to pApi functions */
555 sqlite3_context
*pCtx
, /* Context for returning result/error */
556 int nVal
, /* Number of values in apVal[] array */
557 sqlite3_value
**apVal
/* Array of trailing arguments */
559 F5tFunction
*p
= (F5tFunction
*)pApi
->xUserData(pFts
);
560 Tcl_Obj
*pEval
; /* Script to evaluate */
564 static sqlite3_int64 iCmd
= 0;
570 sprintf(zCmd
, "f5t_%lld", iCmd
++);
571 Tcl_CreateObjCommand(p
->interp
, zCmd
, xF5tApi
, &sApi
, 0);
572 pEval
= Tcl_DuplicateObj(p
->pScript
);
573 Tcl_IncrRefCount(pEval
);
574 Tcl_ListObjAppendElement(p
->interp
, pEval
, Tcl_NewStringObj(zCmd
, -1));
576 for(i
=0; i
<nVal
; i
++){
578 switch( sqlite3_value_type(apVal
[i
]) ){
580 pObj
= Tcl_NewStringObj((const char*)sqlite3_value_text(apVal
[i
]), -1);
583 pObj
= Tcl_NewByteArrayObj(
584 sqlite3_value_blob(apVal
[i
]), sqlite3_value_bytes(apVal
[i
])
588 pObj
= Tcl_NewWideIntObj(sqlite3_value_int64(apVal
[i
]));
591 pObj
= Tcl_NewDoubleObj(sqlite3_value_double(apVal
[i
]));
597 Tcl_ListObjAppendElement(p
->interp
, pEval
, pObj
);
600 rc
= Tcl_EvalObjEx(p
->interp
, pEval
, TCL_GLOBAL_ONLY
);
601 Tcl_DecrRefCount(pEval
);
602 Tcl_DeleteCommand(p
->interp
, zCmd
);
605 sqlite3_result_error(pCtx
, Tcl_GetStringResult(p
->interp
), -1);
607 Tcl_Obj
*pVar
= Tcl_GetObjResult(p
->interp
);
609 const char *zType
= (pVar
->typePtr
? pVar
->typePtr
->name
: "");
611 if( c
=='b' && strcmp(zType
,"bytearray")==0 && pVar
->bytes
==0 ){
612 /* Only return a BLOB type if the Tcl variable is a bytearray and
613 ** has no string representation. */
614 unsigned char *data
= Tcl_GetByteArrayFromObj(pVar
, &n
);
615 sqlite3_result_blob(pCtx
, data
, n
, SQLITE_TRANSIENT
);
616 }else if( c
=='b' && strcmp(zType
,"boolean")==0 ){
617 Tcl_GetIntFromObj(0, pVar
, &n
);
618 sqlite3_result_int(pCtx
, n
);
619 }else if( c
=='d' && strcmp(zType
,"double")==0 ){
621 Tcl_GetDoubleFromObj(0, pVar
, &r
);
622 sqlite3_result_double(pCtx
, r
);
623 }else if( (c
=='w' && strcmp(zType
,"wideInt")==0) ||
624 (c
=='i' && strcmp(zType
,"int")==0) ){
626 Tcl_GetWideIntFromObj(0, pVar
, &v
);
627 sqlite3_result_int64(pCtx
, v
);
629 unsigned char *data
= (unsigned char *)Tcl_GetStringFromObj(pVar
, &n
);
630 sqlite3_result_text(pCtx
, (char *)data
, n
, SQLITE_TRANSIENT
);
635 static void xF5tDestroy(void *pCtx
){
636 F5tFunction
*p
= (F5tFunction
*)pCtx
;
637 Tcl_DecrRefCount(p
->pScript
);
642 ** sqlite3_fts5_create_function DB NAME SCRIPT
646 static int SQLITE_TCLAPI
f5tCreateFunction(
650 Tcl_Obj
*CONST objv
[]
656 F5tFunction
*pCtx
= 0;
660 Tcl_WrongNumArgs(interp
, 1, objv
, "DB NAME SCRIPT");
663 if( f5tDbAndApi(interp
, objv
[1], &db
, &pApi
) ) return TCL_ERROR
;
665 zName
= Tcl_GetString(objv
[2]);
667 pCtx
= (F5tFunction
*)ckalloc(sizeof(F5tFunction
));
668 pCtx
->interp
= interp
;
669 pCtx
->pScript
= pScript
;
670 Tcl_IncrRefCount(pScript
);
672 rc
= pApi
->xCreateFunction(
673 pApi
, zName
, (void*)pCtx
, xF5tFunction
, xF5tDestroy
676 Tcl_AppendResult(interp
, "error: ", sqlite3_errmsg(db
), 0);
683 typedef struct F5tTokenizeCtx F5tTokenizeCtx
;
684 struct F5tTokenizeCtx
{
690 static int xTokenizeCb2(
693 const char *zToken
, int nToken
,
696 F5tTokenizeCtx
*p
= (F5tTokenizeCtx
*)pCtx
;
698 Tcl_ListObjAppendElement(0, p
->pRet
, Tcl_NewStringObj(zToken
, nToken
));
699 Tcl_ListObjAppendElement(
700 0, p
->pRet
, Tcl_NewStringObj(&p
->zInput
[iStart
], iEnd
-iStart
)
703 Tcl_ListObjAppendElement(0, p
->pRet
, Tcl_NewStringObj(zToken
, nToken
));
704 Tcl_ListObjAppendElement(0, p
->pRet
, Tcl_NewIntObj(iStart
));
705 Tcl_ListObjAppendElement(0, p
->pRet
, Tcl_NewIntObj(iEnd
));
712 ** sqlite3_fts5_tokenize DB TOKENIZER TEXT
716 static int SQLITE_TCLAPI
f5tTokenize(
720 Tcl_Obj
*CONST objv
[]
726 Fts5Tokenizer
*pTok
= 0;
727 fts5_tokenizer tokenizer
;
736 if( objc
!=4 && objc
!=5 ){
737 Tcl_WrongNumArgs(interp
, 1, objv
, "?-subst? DB NAME TEXT");
741 char *zOpt
= Tcl_GetString(objv
[1]);
742 if( strcmp("-subst", zOpt
) ){
743 Tcl_AppendResult(interp
, "unrecognized option: ", zOpt
, 0);
747 if( f5tDbAndApi(interp
, objv
[objc
-3], &db
, &pApi
) ) return TCL_ERROR
;
748 if( Tcl_SplitList(interp
, Tcl_GetString(objv
[objc
-2]), &nArg
, &azArg
) ){
752 Tcl_AppendResult(interp
, "no such tokenizer: ", 0);
753 Tcl_Free((void*)azArg
);
756 zText
= Tcl_GetStringFromObj(objv
[objc
-1], &nText
);
758 rc
= pApi
->xFindTokenizer(pApi
, azArg
[0], &pUserdata
, &tokenizer
);
760 Tcl_AppendResult(interp
, "no such tokenizer: ", azArg
[0], 0);
764 rc
= tokenizer
.xCreate(pUserdata
, &azArg
[1], nArg
-1, &pTok
);
766 Tcl_AppendResult(interp
, "error in tokenizer.xCreate()", 0);
771 Tcl_IncrRefCount(pRet
);
772 ctx
.bSubst
= (objc
==5);
775 rc
= tokenizer
.xTokenize(
776 pTok
, (void*)&ctx
, FTS5_TOKENIZE_DOCUMENT
, zText
, nText
, xTokenizeCb2
778 tokenizer
.xDelete(pTok
);
780 Tcl_AppendResult(interp
, "error in tokenizer.xTokenize()", 0);
781 Tcl_DecrRefCount(pRet
);
786 Tcl_Free((void*)azArg
);
787 Tcl_SetObjResult(interp
, pRet
);
788 Tcl_DecrRefCount(pRet
);
792 /*************************************************************************
793 ** Start of tokenizer wrapper.
796 typedef struct F5tTokenizerContext F5tTokenizerContext
;
797 typedef struct F5tTokenizerCb F5tTokenizerCb
;
798 typedef struct F5tTokenizerModule F5tTokenizerModule
;
799 typedef struct F5tTokenizerInstance F5tTokenizerInstance
;
801 struct F5tTokenizerContext
{
803 int (*xToken
)(void*, int, const char*, int, int, int);
806 struct F5tTokenizerModule
{
809 F5tTokenizerContext
*pContext
;
812 struct F5tTokenizerInstance
{
815 F5tTokenizerContext
*pContext
;
818 static int f5tTokenizerCreate(
822 Fts5Tokenizer
**ppOut
824 F5tTokenizerModule
*pMod
= (F5tTokenizerModule
*)pCtx
;
829 pEval
= Tcl_DuplicateObj(pMod
->pScript
);
830 Tcl_IncrRefCount(pEval
);
831 for(i
=0; rc
==TCL_OK
&& i
<nArg
; i
++){
832 Tcl_Obj
*pObj
= Tcl_NewStringObj(azArg
[i
], -1);
833 rc
= Tcl_ListObjAppendElement(pMod
->interp
, pEval
, pObj
);
837 rc
= Tcl_EvalObjEx(pMod
->interp
, pEval
, TCL_GLOBAL_ONLY
);
839 Tcl_DecrRefCount(pEval
);
842 F5tTokenizerInstance
*pInst
;
843 pInst
= (F5tTokenizerInstance
*)ckalloc(sizeof(F5tTokenizerInstance
));
844 memset(pInst
, 0, sizeof(F5tTokenizerInstance
));
845 pInst
->interp
= pMod
->interp
;
846 pInst
->pScript
= Tcl_GetObjResult(pMod
->interp
);
847 pInst
->pContext
= pMod
->pContext
;
848 Tcl_IncrRefCount(pInst
->pScript
);
849 *ppOut
= (Fts5Tokenizer
*)pInst
;
856 static void f5tTokenizerDelete(Fts5Tokenizer
*p
){
857 F5tTokenizerInstance
*pInst
= (F5tTokenizerInstance
*)p
;
858 Tcl_DecrRefCount(pInst
->pScript
);
859 ckfree((char *)pInst
);
862 static int f5tTokenizerTokenize(
866 const char *pText
, int nText
,
867 int (*xToken
)(void*, int, const char*, int, int, int)
869 F5tTokenizerInstance
*pInst
= (F5tTokenizerInstance
*)p
;
871 int (*xOldToken
)(void*, int, const char*, int, int, int);
876 pOldCtx
= pInst
->pContext
->pCtx
;
877 xOldToken
= pInst
->pContext
->xToken
;
879 pInst
->pContext
->pCtx
= pCtx
;
880 pInst
->pContext
->xToken
= xToken
;
883 flags
==FTS5_TOKENIZE_DOCUMENT
884 || flags
==FTS5_TOKENIZE_AUX
885 || flags
==FTS5_TOKENIZE_QUERY
886 || flags
==(FTS5_TOKENIZE_QUERY
| FTS5_TOKENIZE_PREFIX
)
888 pEval
= Tcl_DuplicateObj(pInst
->pScript
);
889 Tcl_IncrRefCount(pEval
);
891 case FTS5_TOKENIZE_DOCUMENT
:
894 case FTS5_TOKENIZE_AUX
:
897 case FTS5_TOKENIZE_QUERY
:
900 case (FTS5_TOKENIZE_PREFIX
| FTS5_TOKENIZE_QUERY
):
901 zFlags
= "prefixquery";
909 Tcl_ListObjAppendElement(pInst
->interp
, pEval
, Tcl_NewStringObj(zFlags
, -1));
910 Tcl_ListObjAppendElement(pInst
->interp
, pEval
, Tcl_NewStringObj(pText
,nText
));
911 rc
= Tcl_EvalObjEx(pInst
->interp
, pEval
, TCL_GLOBAL_ONLY
);
912 Tcl_DecrRefCount(pEval
);
914 pInst
->pContext
->pCtx
= pOldCtx
;
915 pInst
->pContext
->xToken
= xOldToken
;
920 ** sqlite3_fts5_token ?-colocated? TEXT START END
922 static int SQLITE_TCLAPI
f5tTokenizerReturn(
926 Tcl_Obj
*CONST objv
[]
928 F5tTokenizerContext
*p
= (F5tTokenizerContext
*)clientData
;
938 char *zArg
= Tcl_GetStringFromObj(objv
[1], &nArg
);
939 if( nArg
<=10 && nArg
>=2 && memcmp("-colocated", zArg
, nArg
)==0 ){
940 tflags
|= FTS5_TOKEN_COLOCATED
;
948 zToken
= Tcl_GetStringFromObj(objv
[objc
-3], &nToken
);
949 if( Tcl_GetIntFromObj(interp
, objv
[objc
-2], &iStart
)
950 || Tcl_GetIntFromObj(interp
, objv
[objc
-1], &iEnd
)
956 Tcl_AppendResult(interp
,
957 "sqlite3_fts5_token may only be used by tokenizer callback", 0
962 rc
= p
->xToken(p
->pCtx
, tflags
, zToken
, nToken
, iStart
, iEnd
);
963 Tcl_SetResult(interp
, (char*)sqlite3ErrName(rc
), TCL_VOLATILE
);
964 return rc
==SQLITE_OK
? TCL_OK
: TCL_ERROR
;
967 Tcl_WrongNumArgs(interp
, 1, objv
, "?-colocated? TEXT START END");
971 static void f5tDelTokenizer(void *pCtx
){
972 F5tTokenizerModule
*pMod
= (F5tTokenizerModule
*)pCtx
;
973 Tcl_DecrRefCount(pMod
->pScript
);
974 ckfree((char *)pMod
);
978 ** sqlite3_fts5_create_tokenizer DB NAME SCRIPT
980 ** Register a tokenizer named NAME implemented by script SCRIPT. When
981 ** a tokenizer instance is created (fts5_tokenizer.xCreate), any tokenizer
982 ** arguments are appended to SCRIPT and the result executed.
984 ** The value returned by (SCRIPT + args) is itself a tcl script. This
985 ** script - call it SCRIPT2 - is executed to tokenize text using the
986 ** tokenizer instance "returned" by SCRIPT. Specifically, to tokenize
987 ** text SCRIPT2 is invoked with a single argument appended to it - the
990 ** SCRIPT2 should invoke the [sqlite3_fts5_token] command once for each
991 ** token within the tokenized text.
993 static int SQLITE_TCLAPI
f5tCreateTokenizer(
994 ClientData clientData
,
997 Tcl_Obj
*CONST objv
[]
999 F5tTokenizerContext
*pContext
= (F5tTokenizerContext
*)clientData
;
1005 F5tTokenizerModule
*pMod
;
1009 Tcl_WrongNumArgs(interp
, 1, objv
, "DB NAME SCRIPT");
1012 if( f5tDbAndApi(interp
, objv
[1], &db
, &pApi
) ){
1015 zName
= Tcl_GetString(objv
[2]);
1018 t
.xCreate
= f5tTokenizerCreate
;
1019 t
.xTokenize
= f5tTokenizerTokenize
;
1020 t
.xDelete
= f5tTokenizerDelete
;
1022 pMod
= (F5tTokenizerModule
*)ckalloc(sizeof(F5tTokenizerModule
));
1023 pMod
->interp
= interp
;
1024 pMod
->pScript
= pScript
;
1025 pMod
->pContext
= pContext
;
1026 Tcl_IncrRefCount(pScript
);
1027 rc
= pApi
->xCreateTokenizer(pApi
, zName
, (void*)pMod
, &t
, f5tDelTokenizer
);
1028 if( rc
!=SQLITE_OK
){
1029 Tcl_AppendResult(interp
, "error in fts5_api.xCreateTokenizer()", 0);
1036 static void SQLITE_TCLAPI
xF5tFree(ClientData clientData
){
1041 ** sqlite3_fts5_may_be_corrupt BOOLEAN
1043 ** Set or clear the global "may-be-corrupt" flag. Return the old value.
1045 static int SQLITE_TCLAPI
f5tMayBeCorrupt(
1049 Tcl_Obj
*CONST objv
[]
1052 int bOld
= sqlite3_fts5_may_be_corrupt
;
1054 if( objc
!=2 && objc
!=1 ){
1055 Tcl_WrongNumArgs(interp
, 1, objv
, "?BOOLEAN?");
1060 if( Tcl_GetBooleanFromObj(interp
, objv
[1], &bNew
) ) return TCL_ERROR
;
1061 sqlite3_fts5_may_be_corrupt
= bNew
;
1064 Tcl_SetObjResult(interp
, Tcl_NewIntObj(bOld
));
1070 static unsigned int f5t_fts5HashKey(int nSlot
, const char *p
, int n
){
1072 unsigned int h
= 13;
1073 for(i
=n
-1; i
>=0; i
--){
1074 h
= (h
<< 3) ^ h
^ p
[i
];
1079 static int SQLITE_TCLAPI
f5tTokenHash(
1083 Tcl_Obj
*CONST objv
[]
1091 Tcl_WrongNumArgs(interp
, 1, objv
, "NSLOT TOKEN");
1094 if( Tcl_GetIntFromObj(interp
, objv
[1], &nSlot
) ){
1097 z
= Tcl_GetStringFromObj(objv
[2], &n
);
1099 iVal
= f5t_fts5HashKey(nSlot
, z
, n
);
1100 Tcl_SetObjResult(interp
, Tcl_NewIntObj(iVal
));
1104 static int SQLITE_TCLAPI
f5tRegisterMatchinfo(
1108 Tcl_Obj
*CONST objv
[]
1114 Tcl_WrongNumArgs(interp
, 1, objv
, "DB");
1117 if( f5tDbPointer(interp
, objv
[1], &db
) ){
1121 rc
= sqlite3Fts5TestRegisterMatchinfo(db
);
1122 if( rc
!=SQLITE_OK
){
1123 Tcl_SetResult(interp
, (char*)sqlite3ErrName(rc
), TCL_VOLATILE
);
1129 static int SQLITE_TCLAPI
f5tRegisterTok(
1133 Tcl_Obj
*CONST objv
[]
1140 Tcl_WrongNumArgs(interp
, 1, objv
, "DB");
1143 if( f5tDbAndApi(interp
, objv
[1], &db
, &pApi
) ){
1147 rc
= sqlite3Fts5TestRegisterTok(db
, pApi
);
1148 if( rc
!=SQLITE_OK
){
1149 Tcl_SetResult(interp
, (char*)sqlite3ErrName(rc
), TCL_VOLATILE
);
1155 typedef struct OriginTextCtx OriginTextCtx
;
1156 struct OriginTextCtx
{
1161 typedef struct OriginTextTokenizer OriginTextTokenizer
;
1162 struct OriginTextTokenizer
{
1163 Fts5Tokenizer
*pTok
; /* Underlying tokenizer object */
1164 fts5_tokenizer tokapi
; /* API implementation for pTok */
1168 ** Delete the OriginTextCtx object indicated by the only argument.
1170 static void f5tOrigintextTokenizerDelete(void *pCtx
){
1171 OriginTextCtx
*p
= (OriginTextCtx
*)pCtx
;
1175 static int f5tOrigintextCreate(
1179 Fts5Tokenizer
**ppOut
1181 OriginTextCtx
*p
= (OriginTextCtx
*)pCtx
;
1182 OriginTextTokenizer
*pTok
= 0;
1186 pTok
= (OriginTextTokenizer
*)sqlite3_malloc(sizeof(OriginTextTokenizer
));
1192 /* Locate the underlying tokenizer */
1193 rc
= p
->pApi
->xFindTokenizer(p
->pApi
, azArg
[0], &pTokCtx
, &pTok
->tokapi
);
1196 /* Create the new tokenizer instance */
1197 if( rc
==SQLITE_OK
){
1198 rc
= pTok
->tokapi
.xCreate(pTokCtx
, &azArg
[1], nArg
-1, &pTok
->pTok
);
1201 if( rc
!=SQLITE_OK
){
1205 *ppOut
= (Fts5Tokenizer
*)pTok
;
1209 static void f5tOrigintextDelete(Fts5Tokenizer
*pTokenizer
){
1210 OriginTextTokenizer
*p
= (OriginTextTokenizer
*)pTokenizer
;
1212 p
->tokapi
.xDelete(p
->pTok
);
1217 typedef struct OriginTextCb OriginTextCb
;
1218 struct OriginTextCb
{
1222 int (*xToken
)(void *, int, const char *, int, int, int);
1224 char *aBuf
; /* Buffer to use */
1225 int nBuf
; /* Allocated size of aBuf[] */
1228 static int xOriginToken(
1229 void *pCtx
, /* Copy of 2nd argument to xTokenize() */
1230 int tflags
, /* Mask of FTS5_TOKEN_* flags */
1231 const char *pToken
, /* Pointer to buffer containing token */
1232 int nToken
, /* Size of token in bytes */
1233 int iStart
, /* Byte offset of token within input text */
1234 int iEnd
/* Byte offset of end of token within input */
1236 OriginTextCb
*p
= (OriginTextCb
*)pCtx
;
1239 if( nToken
==(iEnd
-iStart
) && 0==memcmp(pToken
, &p
->pText
[iStart
], nToken
) ){
1240 /* Token exactly matches document text. Pass it through as is. */
1241 ret
= p
->xToken(p
->pCtx
, tflags
, pToken
, nToken
, iStart
, iEnd
);
1243 int nReq
= nToken
+ 1 + (iEnd
-iStart
);
1245 sqlite3_free(p
->aBuf
);
1246 p
->aBuf
= sqlite3_malloc(nReq
*2);
1247 if( p
->aBuf
==0 ) return SQLITE_NOMEM
;
1251 memcpy(p
->aBuf
, pToken
, nToken
);
1252 p
->aBuf
[nToken
] = '\0';
1253 memcpy(&p
->aBuf
[nToken
+1], &p
->pText
[iStart
], iEnd
-iStart
);
1254 ret
= p
->xToken(p
->pCtx
, tflags
, p
->aBuf
, nReq
, iStart
, iEnd
);
1261 static int f5tOrigintextTokenize(
1262 Fts5Tokenizer
*pTokenizer
,
1264 int flags
, /* Mask of FTS5_TOKENIZE_* flags */
1265 const char *pText
, int nText
,
1266 int (*xToken
)(void *, int, const char *, int, int, int)
1268 OriginTextTokenizer
*p
= (OriginTextTokenizer
*)pTokenizer
;
1272 memset(&cb
, 0, sizeof(cb
));
1278 ret
= p
->tokapi
.xTokenize(p
->pTok
,(void*)&cb
,flags
,pText
,nText
,xOriginToken
);
1279 sqlite3_free(cb
.aBuf
);
1284 ** sqlite3_fts5_register_origintext DB
1288 static int SQLITE_TCLAPI
f5tRegisterOriginText(
1292 Tcl_Obj
*CONST objv
[]
1297 fts5_tokenizer tok
= {0, 0, 0};
1298 OriginTextCtx
*pCtx
= 0;
1301 Tcl_WrongNumArgs(interp
, 1, objv
, "DB");
1304 if( f5tDbAndApi(interp
, objv
[1], &db
, &pApi
) ) return TCL_ERROR
;
1306 pCtx
= (OriginTextCtx
*)ckalloc(sizeof(OriginTextCtx
));
1310 tok
.xCreate
= f5tOrigintextCreate
;
1311 tok
.xDelete
= f5tOrigintextDelete
;
1312 tok
.xTokenize
= f5tOrigintextTokenize
;
1313 rc
= pApi
->xCreateTokenizer(
1314 pApi
, "origintext", (void*)pCtx
, &tok
, f5tOrigintextTokenizerDelete
1317 Tcl_ResetResult(interp
);
1318 if( rc
!=SQLITE_OK
){
1319 Tcl_AppendResult(interp
, "error: ", sqlite3_errmsg(db
), 0);
1328 int Fts5tcl_Init(Tcl_Interp
*interp
){
1331 Tcl_ObjCmdProc
*xProc
;
1334 { "sqlite3_fts5_create_tokenizer", f5tCreateTokenizer
, 1 },
1335 { "sqlite3_fts5_token", f5tTokenizerReturn
, 1 },
1336 { "sqlite3_fts5_tokenize", f5tTokenize
, 0 },
1337 { "sqlite3_fts5_create_function", f5tCreateFunction
, 0 },
1338 { "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt
, 0 },
1339 { "sqlite3_fts5_token_hash", f5tTokenHash
, 0 },
1340 { "sqlite3_fts5_register_matchinfo", f5tRegisterMatchinfo
, 0 },
1341 { "sqlite3_fts5_register_fts5tokenize", f5tRegisterTok
, 0 },
1342 { "sqlite3_fts5_register_origintext",f5tRegisterOriginText
, 0 }
1345 F5tTokenizerContext
*pContext
;
1347 pContext
= (F5tTokenizerContext
*)ckalloc(sizeof(F5tTokenizerContext
));
1348 memset(pContext
, 0, sizeof(*pContext
));
1350 for(i
=0; i
<sizeof(aCmd
)/sizeof(aCmd
[0]); i
++){
1351 struct Cmd
*p
= &aCmd
[i
];
1353 if( p
->bTokenizeCtx
) pCtx
= (void*)pContext
;
1354 Tcl_CreateObjCommand(interp
, p
->zName
, p
->xProc
, pCtx
, (i
? 0 : xF5tFree
));
1359 #else /* SQLITE_ENABLE_FTS5 */
1360 int Fts5tcl_Init(Tcl_Interp
*interp
){
1363 #endif /* SQLITE_ENABLE_FTS5 */
1364 #endif /* SQLITE_TEST */