diff --git a/main.mk b/main.mk index e9920180d9..78cb0ad137 100644 --- a/main.mk +++ b/main.mk @@ -285,6 +285,7 @@ TESTSRC = \ $(TOP)/src/test_autoext.c \ $(TOP)/src/test_async.c \ $(TOP)/src/test_backup.c \ + $(TOP)/src/test_bestindex.c \ $(TOP)/src/test_blob.c \ $(TOP)/src/test_btree.c \ $(TOP)/src/test_config.c \ diff --git a/manifest b/manifest index 3cdd9396b7..ca3e2793bd 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Better\sestimatedCost\sin\sthe\sxBestIndex\smethod\sof\sthe\sgenerate_series\svtab. -D 2016-03-01T02:11:50.810 +C Add\stest\scode\suseful\sfor\stesting\sthe\splanners\suse\sof\steh\svirtual\stable\sxBestIndex()\smethod. +D 2016-03-01T18:07:43.649 F Makefile.in 4e90dc1521879022aa9479268a4cd141d1771142 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc 4f319afb7c049d40aff7af6e8c4e7cc2ba18e079 @@ -273,7 +273,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 791597b87204f6bd56cdf7b2ab2feeecd608c60d +F main.mk 31027cdd40130bdbb3a0862bb97a8c837c5987d9 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271 F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504 @@ -358,7 +358,7 @@ F src/sqliteInt.h 01b43972162c2b5ed864060a23502af3abe0e4f4 F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46 F src/status.c 70912d7be68e9e2dbc4010c93d344af61d4c59ba F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9 -F src/tclsqlite.c 13debcc6a5ca1217486f8903768c01114fbe8b58 +F src/tclsqlite.c 54dca4eaf3a9cb0a4b845ef54ffa99225c30a52e F src/test1.c f14a6f9e2cff6cba4d83e2b0c52857f61886cead F src/test2.c 5586f43fcd9a1be0830793cf9d354082c261b25b F src/test3.c a8887dabbbee3059af338f20d290084a63ed1b0f @@ -371,6 +371,7 @@ F src/test9.c bea1e8cf52aa93695487badedd6e1886c321ea60 F src/test_async.c 21e11293a2f72080eda70e1124e9102044531cd8 F src/test_autoext.c dea8a01a7153b9adc97bd26161e4226329546e12 F src/test_backup.c 2e6e6a081870150f20c526a2e9d0d29cda47d803 +F src/test_bestindex.c 2004eeca2d227fe62ed581776dc7fd349d45b8f3 F src/test_blob.c b2551a9b5573232db5f66f292307c37067937239 F src/test_btree.c 2e9978eca99a9a4bfa8cae949efb00886860a64f F src/test_config.c 0dee90328e3dedf8ba002ee94b6a7e7ea7726fe4 @@ -492,6 +493,7 @@ F test/backup_malloc.test 7162d604ec2b4683c4b3799a48657fb8b5e2d450 F test/badutf.test d5360fc31f643d37a973ab0d8b4fb85799c3169f F test/badutf2.test f5bc7f2d280670ecd79b9cf4f0f1760c607fe51f F test/bc_common.tcl 3eda41ef9cda7d5f6c205462c96228b301da4191 +F test/bestindex1.test a7213b26f039edfcdb4c0cc13392b79cd4a23779 F test/between.test 34d375fb5ce1ae283ffe82b6b233e9f38e84fc6c F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -1451,7 +1453,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P c9a30e117f2c6c9ef0cc0c6ca5227d2961715b8f -R 78072d3e9e5d5130b27dfb7458df9d26 -U drh -Z 00eb8a065721ce5867f81f8e93fd77c0 +P f2c16094a536e7ef62444d0fe38cbee2a4999426 +R c8660859b43a9c62a3adc82692a6c451 +T *branch * test-bestindex +T *sym-test-bestindex * +T -sym-trunk * +U dan +Z 7443660e56c432a07b1f019ab23aa0dc diff --git a/manifest.uuid b/manifest.uuid index ead2aaed5e..3fb12f3617 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -f2c16094a536e7ef62444d0fe38cbee2a4999426 \ No newline at end of file +de034c0db66298454ae8418949d58eb6e223c0de \ No newline at end of file diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 77da6eef52..4e19f92644 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -3774,6 +3774,7 @@ static void init_all(Tcl_Interp *interp){ extern int SqlitetestSyscall_Init(Tcl_Interp*); extern int Fts5tcl_Init(Tcl_Interp *); extern int SqliteRbu_Init(Tcl_Interp*); + extern int Sqlitetesttcl_Init(Tcl_Interp*); #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) extern int Sqlitetestfts3_Init(Tcl_Interp *interp); #endif @@ -3818,6 +3819,7 @@ static void init_all(Tcl_Interp *interp){ SqlitetestSyscall_Init(interp); Fts5tcl_Init(interp); SqliteRbu_Init(interp); + Sqlitetesttcl_Init(interp); #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4) Sqlitetestfts3_Init(interp); diff --git a/src/test_bestindex.c b/src/test_bestindex.c new file mode 100644 index 0000000000..051a784c6d --- /dev/null +++ b/src/test_bestindex.c @@ -0,0 +1,436 @@ +/* +** 2013 Jan 11 +** +** 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) and when the xBestIndex() method is invoked. 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; + const char *zCmd; + Tcl_Obj *pScript = 0; + int rc; + + if( argc!=4 ){ + *pzErr = sqlite3_mprintf("wrong number of arguments"); + return SQLITE_ERROR; + } + zCmd = argv[3]; + + pTab = (tcl_vtab*)sqlite3_malloc(sizeof(tcl_vtab)); + if( pTab==0 ) return SQLITE_NOMEM; + memset(pTab, 0, sizeof(tcl_vtab)); + + pTab->pCmd = Tcl_NewStringObj(zCmd, -1); + pTab->interp = interp; + 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; + } + + *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; + sqlite3_free(pCur); + return SQLITE_OK; +} + +static int tclNext(sqlite3_vtab_cursor *cur){ + return SQLITE_OK; +} + +static int tclFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + return SQLITE_OK; +} + +static int tclColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ + return SQLITE_OK; +} + +static int tclRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + return SQLITE_OK; +} + +static int tclEof(sqlite3_vtab_cursor *cur){ + return 1; +} + +static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + tcl_vtab *pTab = (tcl_vtab*)tab; + Tcl_Interp *interp = pTab->interp; + Tcl_Obj *pArg; + Tcl_Obj *pScript; + int ii; + int rc = SQLITE_OK; + + pScript = Tcl_DuplicateObj(pTab->pCmd); + Tcl_IncrRefCount(pScript); + Tcl_ListObjAppendElement(interp, pScript, Tcl_NewStringObj("xBestIndex", -1)); + + pArg = Tcl_NewObj(); + Tcl_IncrRefCount(pArg); + 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; + } + + 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, pArg, pElem); + Tcl_DecrRefCount(pElem); + } + + Tcl_ListObjAppendElement(0, pScript, pArg); + Tcl_DecrRefCount(pArg); + + pArg = Tcl_NewObj(); + Tcl_IncrRefCount(pArg); + 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, pArg, pElem); + Tcl_DecrRefCount(pElem); + } + + Tcl_ListObjAppendElement(0, pScript, pArg); + Tcl_DecrRefCount(pArg); + + Tcl_ListObjAppendElement(0, pScript, Tcl_NewWideIntObj(pIdxInfo->colUsed)); + + rc = Tcl_EvalObjEx(interp, pScript, TCL_EVAL_GLOBAL); + 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) + */ + 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 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)); + }else + if( sqlite3_stricmp("rows", zCmd)==0 ){ + rc = Tcl_GetWideIntFromObj(interp, p, &pIdxInfo->estimatedRows); + }else + if( sqlite3_stricmp("use", 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{ + pIdxInfo->aConstraintUsage[iCons].argvIndex = iArgv++; + } + } + }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 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