]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add test code useful for testing the planners use of teh virtual table xBestIndex...
authordan <dan@noemail.net>
Tue, 1 Mar 2016 18:07:43 +0000 (18:07 +0000)
committerdan <dan@noemail.net>
Tue, 1 Mar 2016 18:07:43 +0000 (18:07 +0000)
FossilOrigin-Name: de034c0db66298454ae8418949d58eb6e223c0de

main.mk
manifest
manifest.uuid
src/tclsqlite.c
src/test_bestindex.c [new file with mode: 0644]
test/bestindex1.test [new file with mode: 0644]

diff --git a/main.mk b/main.mk
index e9920180d97edea606e6fdc2364606322b12a151..78cb0ad1370a96db2083a4a2f80613ab309ac67f 100644 (file)
--- 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 \
index 3cdd9396b7b78d5a090218e3b70ef86a3e281adb..ca3e2793bddbd591da59f90649845179695e5c63 100644 (file)
--- 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
index ead2aaed5ef1d73655d554a43cebc4e64b8183bd..3fb12f361730959e23cd8870bd9469a84e822cd5 100644 (file)
@@ -1 +1 @@
-f2c16094a536e7ef62444d0fe38cbee2a4999426
\ No newline at end of file
+de034c0db66298454ae8418949d58eb6e223c0de
\ No newline at end of file
index 77da6eef5266922625dac6f8a5f7db4a07120159..4e19f92644c0b6e0b8ac66ab8e2179fa321bbb1e 100644 (file)
@@ -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 (file)
index 0000000..051a784
--- /dev/null
@@ -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<? ORDER BY b, c;
+**
+** then the Tcl command is:
+**
+**   tcl_command xBestIndex                                  \
+**     {{op eq column 0 usable 1} {op lt column 2 usable 1}} \
+**     {{column 1 desc 0} {column 2 desc 0}}                 \
+**     7
+**
+** The return value of the script is a list of key-value pairs used to
+** populate the output fields of the sqlite3_index_info structure. Possible
+** keys and the usage of the accompanying values are:
+** 
+**   "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)
+**
+** Refer to code below for further details.
+*/
+
+
+#include "sqliteInt.h"
+#include "tcl.h"
+
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+
+typedef struct tcl_vtab tcl_vtab;
+typedef struct tcl_cursor tcl_cursor;
+
+/* 
+** A fs virtual-table object 
+*/
+struct tcl_vtab {
+  sqlite3_vtab base;
+  Tcl_Interp *interp;
+  Tcl_Obj *pCmd;
+};
+
+/* A tcl cursor object */
+struct tcl_cursor {
+  sqlite3_vtab_cursor base;
+};
+
+/*
+** This function is the implementation of both the xConnect and xCreate
+** methods of the fs virtual table.
+**
+** The argv[] array contains the following:
+**
+**   argv[0]   -> 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; ii<pIdxInfo->nConstraint; 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; ii<pIdxInfo->nOrderBy; 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 && ii<nElem; ii+=2){
+        const char *zCmd = Tcl_GetString(apElem[ii]);
+        Tcl_Obj *p = apElem[ii+1];
+        if( sqlite3_stricmp("cost", zCmd)==0 ){
+          rc = Tcl_GetDoubleFromObj(interp, p, &pIdxInfo->estimatedCost);
+        }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<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){
+    Tcl_CreateObjCommand(interp, aObjCmd[i].zName, 
+        aObjCmd[i].xProc, aObjCmd[i].clientData, 0);
+  }
+#endif
+  return TCL_OK;
+}
diff --git a/test/bestindex1.test b/test/bestindex1.test
new file mode 100644 (file)
index 0000000..e3443dd
--- /dev/null
@@ -0,0 +1,61 @@
+# 2016 February 19
+#
+# 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.
+#
+#***********************************************************************
+# 
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix bestindex1
+
+register_tcl_module db
+
+proc vtab_command {method args} {
+  switch -- $method {
+    xConnect {
+      return "CREATE TABLE t1(a, b, c)"
+    }
+
+    xBestIndex {
+      set clist [lindex $args 0]
+      if {[llength $clist]!=1} { error "unexpected constraint list" }
+      catch { array unset C }
+      array set C [lindex $clist 0]
+      if {$C(usable)} {
+        return "use 0 cost 0 rows 1 idxnum 555 idxstr eq!"
+      } else {
+        return "cost 1000000 rows 0 idxnum 0 idxstr scan..."
+      }
+    }
+
+  }
+
+  return {}
+}
+
+do_execsql_test 1.0 {
+  CREATE VIRTUAL TABLE x1 USING tcl(vtab_command);
+} {}
+
+do_eqp_test 1.1 {
+  SELECT * FROM x1 WHERE a = 'abc'
+} {
+  0 0 0 {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!}
+}
+
+#set sqlite_where_trace 0x3ff
+do_eqp_test 1.2 {
+  SELECT * FROM x1 WHERE a IN ('abc', 'def');
+} {
+  0 0 0 {SCAN TABLE x1 VIRTUAL TABLE INDEX 555:eq!}
+}
+
+finish_test
+