/* ** 2016-03-01 ** ** 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. ** ************************************************************************* ** Code for testing the virtual table xBestIndex method and the query ** planner. */ /* ** INSTRUCTIONS ** ** This module exports a single tcl command - [register_tcl_module]. When ** invoked, it registers a special virtual table module with a database ** connection. ** ** The virtual table is currently read-only. And always returns zero rows. ** It is created with a single argument - the name of a Tcl command - as ** follows: ** ** CREATE VIRTUAL TABLE x1 USING tcl(tcl_command); ** ** The command [tcl_command] is invoked when the table is first created (or ** connected), when the xBestIndex() method is invoked and when the xFilter() ** method is called. When it is created (or connected), it is invoked as ** follows: ** ** tcl_command xConnect ** ** In this case the return value of the script is passed to the ** sqlite3_declare_vtab() function to create the virtual table schema. ** ** When the xBestIndex() method is called by SQLite, the Tcl command is ** invoked as: ** ** tcl_command xBestIndex CONSTRAINTS ORDERBY MASK ** ** where CONSTRAINTS is a tcl representation of the aConstraints[] array, ** ORDERBY is a representation of the contents of the aOrderBy[] array and ** MASK is a copy of sqlite3_index_info.colUsed. For example if the virtual ** table is declared as: ** ** CREATE TABLE x1(a, b, c) ** ** and the query is: ** ** SELECT * FROM x1 WHERE a=? AND c module name ("fs") ** argv[1] -> database name ** argv[2] -> table name ** argv[...] -> other module argument fields. */ static int tclConnect( sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVtab, char **pzErr ){ Tcl_Interp *interp = (Tcl_Interp*)pAux; tcl_vtab *pTab = 0; char *zCmd = 0; Tcl_Obj *pScript = 0; int rc = SQLITE_OK; if( argc!=4 ){ *pzErr = sqlite3_mprintf("wrong number of arguments"); return SQLITE_ERROR; } zCmd = sqlite3_malloc64(strlen(argv[3])+1); pTab = (tcl_vtab*)sqlite3_malloc64(sizeof(tcl_vtab)); if( zCmd && pTab ){ memcpy(zCmd, argv[3], strlen(argv[3])+1); tclDequote(zCmd); memset(pTab, 0, sizeof(tcl_vtab)); pTab->pCmd = Tcl_NewStringObj(zCmd, -1); pTab->interp = interp; pTab->db = db; Tcl_IncrRefCount(pTab->pCmd); pScript = Tcl_DuplicateObj(pTab->pCmd); Tcl_IncrRefCount(pScript); Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xConnect", -1)); rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); if( rc!=TCL_OK ){ *pzErr = sqlite3_mprintf("%s", Tcl_GetStringResult(interp)); rc = SQLITE_ERROR; }else{ rc = sqlite3_declare_vtab(db, Tcl_GetStringResult(interp)); } if( rc!=SQLITE_OK ){ sqlite3_free(pTab); pTab = 0; } }else{ rc = SQLITE_NOMEM; } sqlite3_free(zCmd); *ppVtab = &pTab->base; return rc; } /* The xDisconnect and xDestroy methods are also the same */ static int tclDisconnect(sqlite3_vtab *pVtab){ tcl_vtab *pTab = (tcl_vtab*)pVtab; Tcl_DecrRefCount(pTab->pCmd); sqlite3_free(pTab); return SQLITE_OK; } /* ** Open a new tcl cursor. */ static int tclOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ tcl_cursor *pCur; pCur = sqlite3_malloc(sizeof(tcl_cursor)); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(tcl_cursor)); *ppCursor = &pCur->base; return SQLITE_OK; } /* ** Close a tcl cursor. */ static int tclClose(sqlite3_vtab_cursor *cur){ tcl_cursor *pCur = (tcl_cursor *)cur; if( pCur ){ sqlite3_finalize(pCur->pStmt); sqlite3_free(pCur); } return SQLITE_OK; } static int tclNext(sqlite3_vtab_cursor *pVtabCursor){ tcl_cursor *pCsr = (tcl_cursor*)pVtabCursor; if( pCsr->pStmt ){ tcl_vtab *pTab = (tcl_vtab*)(pVtabCursor->pVtab); int rc = sqlite3_step(pCsr->pStmt); if( rc!=SQLITE_ROW ){ const char *zErr; rc = sqlite3_finalize(pCsr->pStmt); pCsr->pStmt = 0; if( rc!=SQLITE_OK ){ zErr = sqlite3_errmsg(pTab->db); pTab->base.zErrMsg = sqlite3_mprintf("%s", zErr); } } } return SQLITE_OK; } static int tclFilter( sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ tcl_cursor *pCsr = (tcl_cursor*)pVtabCursor; tcl_vtab *pTab = (tcl_vtab*)(pVtabCursor->pVtab); Tcl_Interp *interp = pTab->interp; Tcl_Obj *pScript; Tcl_Obj *pArg; int ii; int rc; pScript = Tcl_DuplicateObj(pTab->pCmd); Tcl_IncrRefCount(pScript); Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xFilter", -1)); Tcl_ListObjAppendElement(interp, pScript, Tcl_NewIntObj(idxNum)); if( idxStr ){ Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(idxStr, -1)); }else{ Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("", -1)); } pArg = Tcl_NewObj(); Tcl_IncrRefCount(pArg); for(ii=0; iibase.zErrMsg = sqlite3_mprintf("%s", zErr); }else{ /* Analyze the scripts return value. The return value should be a tcl ** list object with an even number of elements. The first element of each ** pair must be one of: ** ** "sql" (SQL statement to return data) */ Tcl_Obj *pRes = Tcl_GetObjResult(interp); Tcl_Obj **apElem = 0; int nElem; rc = Tcl_ListObjGetElements(interp, pRes, &nElem, &apElem); if( rc!=TCL_OK ){ const char *zErr = Tcl_GetStringResult(interp); rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("%s", zErr); }else{ for(ii=0; rc==SQLITE_OK && iidb, zSql, -1, &pCsr->pStmt, 0); if( rc!=SQLITE_OK ){ const char *zErr = sqlite3_errmsg(pTab->db); pTab->base.zErrMsg = sqlite3_mprintf("unexpected: %s", zErr); } }else{ rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("unexpected: %s", zCmd); } } } } if( rc==SQLITE_OK ){ rc = tclNext(pVtabCursor); } return rc; } static int tclColumn( sqlite3_vtab_cursor *pVtabCursor, sqlite3_context *ctx, int i ){ tcl_cursor *pCsr = (tcl_cursor*)pVtabCursor; sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pStmt, i+1)); return SQLITE_OK; } static int tclRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ tcl_cursor *pCsr = (tcl_cursor*)pVtabCursor; *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); return SQLITE_OK; } static int tclEof(sqlite3_vtab_cursor *pVtabCursor){ tcl_cursor *pCsr = (tcl_cursor*)pVtabCursor; return (pCsr->pStmt==0); } static void testBestIndexObjConstraints( Tcl_Interp *interp, sqlite3_index_info *pIdxInfo ){ int ii; Tcl_Obj *pRes = Tcl_NewObj(); Tcl_IncrRefCount(pRes); for(ii=0; iinConstraint; ii++){ struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii]; Tcl_Obj *pElem = Tcl_NewObj(); const char *zOp = "?"; Tcl_IncrRefCount(pElem); switch( pCons->op ){ case SQLITE_INDEX_CONSTRAINT_EQ: zOp = "eq"; break; case SQLITE_INDEX_CONSTRAINT_GT: zOp = "gt"; break; case SQLITE_INDEX_CONSTRAINT_LE: zOp = "le"; break; case SQLITE_INDEX_CONSTRAINT_LT: zOp = "lt"; break; case SQLITE_INDEX_CONSTRAINT_GE: zOp = "ge"; break; case SQLITE_INDEX_CONSTRAINT_MATCH: zOp = "match"; break; case SQLITE_INDEX_CONSTRAINT_LIKE: zOp = "like"; break; case SQLITE_INDEX_CONSTRAINT_GLOB: zOp = "glob"; break; case SQLITE_INDEX_CONSTRAINT_REGEXP: zOp = "regexp"; break; case SQLITE_INDEX_CONSTRAINT_NE: zOp = "ne"; break; case SQLITE_INDEX_CONSTRAINT_ISNOT: zOp = "isnot"; break; case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: zOp = "isnotnull"; break; case SQLITE_INDEX_CONSTRAINT_ISNULL: zOp = "isnull"; break; case SQLITE_INDEX_CONSTRAINT_IS: zOp = "is"; break; case SQLITE_INDEX_CONSTRAINT_LIMIT: zOp = "limit"; break; case SQLITE_INDEX_CONSTRAINT_OFFSET: zOp = "offset"; break; } Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("op", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj(zOp, -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("column", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->iColumn)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("usable", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pCons->usable)); Tcl_ListObjAppendElement(0, pRes, pElem); Tcl_DecrRefCount(pElem); } Tcl_SetObjResult(interp, pRes); Tcl_DecrRefCount(pRes); } static void testBestIndexObjOrderby( Tcl_Interp *interp, sqlite3_index_info *pIdxInfo ){ int ii; Tcl_Obj *pRes = Tcl_NewObj(); Tcl_IncrRefCount(pRes); for(ii=0; iinOrderBy; ii++){ struct sqlite3_index_orderby const *pOrder = &pIdxInfo->aOrderBy[ii]; Tcl_Obj *pElem = Tcl_NewObj(); Tcl_IncrRefCount(pElem); Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("column", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pOrder->iColumn)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("desc", -1)); Tcl_ListObjAppendElement(0, pElem, Tcl_NewIntObj(pOrder->desc)); Tcl_ListObjAppendElement(0, pRes, pElem); Tcl_DecrRefCount(pElem); } Tcl_SetObjResult(interp, pRes); Tcl_DecrRefCount(pRes); } /* ** Implementation of the handle passed to each xBestIndex callback. This ** object features the following sub-commands: ** ** $hdl constraints ** $hdl orderby ** $hdl mask ** ** $hdl distinct ** Return the result (an integer) of calling sqlite3_vtab_distinct() ** on the index-info structure. ** ** $hdl in IDX BOOLEAN ** Wrapper around sqlite3_vtab_in(). Returns an integer. ** ** $hdl rhs_value IDX ?DEFAULT? ** Wrapper around sqlite3_vtab_rhs_value(). */ static int SQLITE_TCLAPI testBestIndexObj( ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ int objc, /* Number of arguments */ Tcl_Obj *CONST objv[] /* Command arguments */ ){ const char *azSub[] = { "constraints", /* 0 */ "orderby", /* 1 */ "mask", /* 2 */ "distinct", /* 3 */ "in", /* 4 */ "rhs_value", /* 5 */ 0 }; int ii; sqlite3_index_info *pIdxInfo = (sqlite3_index_info*)clientData; if( objc<2 ){ Tcl_WrongNumArgs(interp, 1, objv, "SUB-COMMAND"); return TCL_ERROR; } if( Tcl_GetIndexFromObj(interp, objv[1], azSub, "sub-command", 0, &ii) ){ return TCL_ERROR; } if( ii<4 && objc!=2 ){ Tcl_WrongNumArgs(interp, 2, objv, ""); return TCL_ERROR; } if( ii==4 && objc!=4 ){ Tcl_WrongNumArgs(interp, 2, objv, "INDEX BOOLEAN"); return TCL_ERROR; } if( ii==5 && objc!=3 && objc!=4 ){ Tcl_WrongNumArgs(interp, 2, objv, "INDEX ?DEFAULT?"); return TCL_ERROR; } switch( ii ){ case 0: assert( sqlite3_stricmp(azSub[ii], "constraints")==0 ); testBestIndexObjConstraints(interp, pIdxInfo); break; case 1: assert( sqlite3_stricmp(azSub[ii], "orderby")==0 ); testBestIndexObjOrderby(interp, pIdxInfo); break; case 2: assert( sqlite3_stricmp(azSub[ii], "mask")==0 ); Tcl_SetObjResult(interp, Tcl_NewWideIntObj(pIdxInfo->colUsed)); break; case 3: assert( sqlite3_stricmp(azSub[ii], "distinct")==0 ); { int bDistinct = sqlite3_vtab_distinct(pIdxInfo); Tcl_SetObjResult(interp, Tcl_NewIntObj(bDistinct)); break; } case 4: assert( sqlite3_stricmp(azSub[ii], "in")==0 ); { int iCons; int bHandle; if( Tcl_GetIntFromObj(interp, objv[2], &iCons) || Tcl_GetBooleanFromObj(interp, objv[3], &bHandle) ){ return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewIntObj(sqlite3_vtab_in(pIdxInfo, iCons, bHandle)) ); break; } case 5: assert( sqlite3_stricmp(azSub[ii], "rhs_value")==0 ); { int iCons = 0; int rc; sqlite3_value *pVal = 0; const char *zVal = ""; if( Tcl_GetIntFromObj(interp, objv[2], &iCons) ){ return TCL_ERROR; } rc = sqlite3_vtab_rhs_value(pIdxInfo, iCons, &pVal); if( rc!=SQLITE_OK && rc!=SQLITE_NOTFOUND ){ Tcl_SetResult(interp, (char *)sqlite3ErrName(rc), TCL_VOLATILE); return TCL_ERROR; } if( pVal ){ zVal = (const char*)sqlite3_value_text(pVal); }else if( objc==4 ){ zVal = Tcl_GetString(objv[3]); } Tcl_SetObjResult(interp, Tcl_NewStringObj(zVal, -1)); break; } } return TCL_OK; } static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ tcl_vtab *pTab = (tcl_vtab*)tab; Tcl_Interp *interp = pTab->interp; int rc = SQLITE_OK; static int iNext = 43; char zHdl[24]; Tcl_Obj *pScript; pScript = Tcl_DuplicateObj(pTab->pCmd); Tcl_IncrRefCount(pScript); Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xBestIndex", -1)); sqlite3_snprintf(sizeof(zHdl), zHdl, "bestindex%d", iNext++); Tcl_CreateObjCommand(interp, zHdl, testBestIndexObj, pIdxInfo, 0); Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj(zHdl, -1)); rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); Tcl_DeleteCommand(interp, zHdl); Tcl_DecrRefCount(pScript); if( rc!=TCL_OK ){ const char *zErr = Tcl_GetStringResult(interp); rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("%s", zErr); }else{ /* Analyze the scripts return value. The return value should be a tcl ** list object with an even number of elements. The first element of each ** pair must be one of: ** ** "orderby" (value of orderByConsumed flag) ** "cost" (value of estimatedCost field) ** "rows" (value of estimatedRows field) ** "use" (index of used constraint in aConstraint[]) ** "idxnum" (value of idxNum field) ** "idxstr" (value of idxStr field) ** "omit" (index of omitted constraint in aConstraint[]) */ Tcl_Obj *pRes = Tcl_GetObjResult(interp); Tcl_Obj **apElem = 0; int nElem; rc = Tcl_ListObjGetElements(interp, pRes, &nElem, &apElem); if( rc!=TCL_OK ){ const char *zErr = Tcl_GetStringResult(interp); rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("%s", zErr); }else{ int ii; int iArgv = 1; for(ii=0; rc==SQLITE_OK && iiestimatedCost); }else if( sqlite3_stricmp("orderby", zCmd)==0 ){ rc = Tcl_GetIntFromObj(interp, p, &pIdxInfo->orderByConsumed); }else if( sqlite3_stricmp("idxnum", zCmd)==0 ){ rc = Tcl_GetIntFromObj(interp, p, &pIdxInfo->idxNum); }else if( sqlite3_stricmp("idxstr", zCmd)==0 ){ sqlite3_free(pIdxInfo->idxStr); pIdxInfo->idxStr = sqlite3_mprintf("%s", Tcl_GetString(p)); pIdxInfo->needToFreeIdxStr = 1; }else if( sqlite3_stricmp("rows", zCmd)==0 ){ Tcl_WideInt x = 0; rc = Tcl_GetWideIntFromObj(interp, p, &x); pIdxInfo->estimatedRows = (tRowcnt)x; }else if( sqlite3_stricmp("use", zCmd)==0 || sqlite3_stricmp("omit", zCmd)==0 ){ int iCons; rc = Tcl_GetIntFromObj(interp, p, &iCons); if( rc==SQLITE_OK ){ if( iCons<0 || iCons>=pIdxInfo->nConstraint ){ rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("unexpected: %d", iCons); }else{ int bOmit = (zCmd[0]=='o' || zCmd[0]=='O'); pIdxInfo->aConstraintUsage[iCons].argvIndex = iArgv++; pIdxInfo->aConstraintUsage[iCons].omit = bOmit; } } }else{ rc = SQLITE_ERROR; pTab->base.zErrMsg = sqlite3_mprintf("unexpected: %s", zCmd); } if( rc!=SQLITE_OK && pTab->base.zErrMsg==0 ){ const char *zErr = Tcl_GetStringResult(interp); pTab->base.zErrMsg = sqlite3_mprintf("%s", zErr); } } } } return rc; } /* ** A virtual table module that provides read-only access to a ** Tcl global variable namespace. */ static sqlite3_module tclModule = { 0, /* iVersion */ tclConnect, tclConnect, tclBestIndex, tclDisconnect, tclDisconnect, tclOpen, /* xOpen - open a cursor */ tclClose, /* xClose - close a cursor */ tclFilter, /* xFilter - configure scan constraints */ tclNext, /* xNext - advance a cursor */ tclEof, /* xEof - check for end of scan */ tclColumn, /* xColumn - read data */ tclRowid, /* xRowid - read data */ 0, /* xUpdate */ 0, /* xBegin */ 0, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ }; /* ** Decode a pointer to an sqlite3 object. */ extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); /* ** Register the echo virtual table module. */ static int SQLITE_TCLAPI register_tcl_module( ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ int objc, /* Number of arguments */ Tcl_Obj *CONST objv[] /* Command arguments */ ){ sqlite3 *db; if( objc!=2 ){ Tcl_WrongNumArgs(interp, 1, objv, "DB"); return TCL_ERROR; } if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3_create_module(db, "tcl", &tclModule, (void *)interp); #endif return TCL_OK; } #endif /* ** Register commands with the TCL interpreter. */ int Sqlitetesttcl_Init(Tcl_Interp *interp){ #ifndef SQLITE_OMIT_VIRTUALTABLE static struct { char *zName; Tcl_ObjCmdProc *xProc; void *clientData; } aObjCmd[] = { { "register_tcl_module", register_tcl_module, 0 }, }; int i; for(i=0; i