]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add further tests and related fixes for GLOB/REGEXP/LIKE support in virtual tables.
authordan <dan@noemail.net>
Tue, 24 Nov 2015 17:39:01 +0000 (17:39 +0000)
committerdan <dan@noemail.net>
Tue, 24 Nov 2015 17:39:01 +0000 (17:39 +0000)
FossilOrigin-Name: c5e9fd0dc92a07db3d3b5f5c5ad8fb63b3425c2b

manifest
manifest.uuid
src/test_tclvar.c
src/whereexpr.c
test/vtabE.test
test/vtabH.test

index 77ed28994e01d7c6d05db0382d5ae321639dc521..1a5fc94c5f219ffa36395e9149dac814c37fdeb2 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sexperimental\ssupport\sfor\sLIKE,\sGLOB\sand\sREGEXP\sto\sthe\svirtual\stable\sinterface.
-D 2015-11-23T21:09:54.478
+C Add\sfurther\stests\sand\srelated\sfixes\sfor\sGLOB/REGEXP/LIKE\ssupport\sin\svirtual\stables.
+D 2015-11-24T17:39:01.810
 F Makefile.in d828db6afa6c1fa060d01e33e4674408df1942a1
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc e928e68168df69b353300ac87c10105206653a03
@@ -389,7 +389,7 @@ F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
 F src/test_sqllog.c 0d138a8180a312bf996b37fa66da5c5799d4d57b
 F src/test_superlock.c 06797157176eb7085027d9dd278c0d7a105e3ec9
 F src/test_syscall.c 2e21ca7f7dc54a028f1967b63f1e76155c356f9b
-F src/test_tclvar.c f4dc67d5f780707210d6bb0eb6016a431c04c7fa
+F src/test_tclvar.c 2fd910e9f0ef7686889f50f448d33810c895da86
 F src/test_thread.c af391ec03d23486dffbcc250b7e58e073f172af9
 F src/test_vfs.c 3b65d42e18b262805716bd96178c81da8f2d9283
 F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698
@@ -419,7 +419,7 @@ F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba
 F src/where.c 6687fb2675d9c1c1936ceca77529e2f21fb3a769
 F src/whereInt.h 6afc0d70cf6213e58e8fbe10b6e50d1aa16f122f
 F src/wherecode.c 4c96182e7b25e4be54008dee2da5b9c2f8480b9b
-F src/whereexpr.c 12c6fa7576674d24bf0116364a39885925c89188
+F src/whereexpr.c 17d62d8bb7fd357920b46ee86851b5d6629412bf
 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
 F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
@@ -1272,9 +1272,9 @@ F test/vtabA.test 1317f06a03597eee29f40a49b6c21e1aaba4285f
 F test/vtabB.test 04df5dc531b9f44d9ca65b9c1b79f12b5922a796
 F test/vtabC.test 4528f459a13136f982e75614d120aef165f17292
 F test/vtabD.test 05b3f1d77117271671089e48719524b676842e96
-F test/vtabE.test 7c4693638d7797ce2eda17af74292b97e705cc61
+F test/vtabE.test d5024aa42754962f6bb0afd261681686488e7afe
 F test/vtabF.test fd5ad376f5a34fe0891df1f3cddb4fe7c3eb077e
-F test/vtabH.test 15e137d2af9b0b81fedca6697518eb8834c013f4
+F test/vtabH.test 694aa399eb28ed0db2aef59f2f37532781eeb957
 F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5
 F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8
 F test/vtab_shared.test ea8778d5b0df200adef2ca7c00c3c37d4375f772
@@ -1405,10 +1405,7 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 60de5f23424552c98aa760ac89149a3d51f895be
-R b6557d1407b51115ef511b97d0fc16c1
-T *branch * vtab-like-operator
-T *sym-vtab-like-operator *
-T -sym-trunk *
+P 277a5b4027d4c2caba8143228a4f7d6df899dbb4
+R f59356e8fc475908365127ad504d9518
 U dan
-Z 249ced27266ff2c4ebd31fcaec9b55b5
+Z 673386578c6b1eaa8b17502c533df301
index eee0121605b9cea1fb674f67e9f0c1986de46fbf..77e0da032b22f43d07b2d5b77d92fe9515d944e3 100644 (file)
@@ -1 +1 @@
-277a5b4027d4c2caba8143228a4f7d6df899dbb4
\ No newline at end of file
+c5e9fd0dc92a07db3d3b5f5c5ad8fb63b3425c2b
\ No newline at end of file
index 1219190c0300fd5ec7a3c58408f33843d20eee15..975f07fc8d939f05c4c8208f8f5f5bc78e4fa6a3 100644 (file)
 
 #ifndef SQLITE_OMIT_VIRTUALTABLE
 
+/*
+** Characters that make up the idxStr created by xBestIndex for xFilter.
+*/
+#define TCLVAR_NAME_EQ      'e'
+#define TCLVAR_NAME_MATCH   'm'
+#define TCLVAR_VALUE_GLOB   'g'
+#define TCLVAR_VALUE_REGEXP 'r'
+#define TCLVAR_VALUE_LIKE   'l'
+
 typedef struct tclvar_vtab tclvar_vtab;
 typedef struct tclvar_cursor tclvar_cursor;
 
@@ -155,15 +164,44 @@ static int tclvarFilter(
 ){
   tclvar_cursor *pCur = (tclvar_cursor *)pVtabCursor;
   Tcl_Interp *interp = ((tclvar_vtab *)(pVtabCursor->pVtab))->interp;
+  Tcl_Obj *p = Tcl_NewStringObj("tclvar_filter_cmd", -1);
 
-  Tcl_Obj *p = Tcl_NewStringObj("info vars", -1);
-  Tcl_IncrRefCount(p);
+  const char *zEq = "";
+  const char *zMatch = "";
+  const char *zGlob = "";
+  const char *zRegexp = "";
+  const char *zLike = "";
+  int i;
 
-  assert( argc==0 || argc==1 );
-  if( argc==1 ){
-    Tcl_Obj *pArg = Tcl_NewStringObj((char*)sqlite3_value_text(argv[0]), -1);
-    Tcl_ListObjAppendElement(0, p, pArg);
+  for(i=0; idxStr[i]; i++){
+    switch( idxStr[i] ){
+      case TCLVAR_NAME_EQ:
+        zEq = sqlite3_value_text(argv[i]);
+        break;
+      case TCLVAR_NAME_MATCH:
+        zMatch = sqlite3_value_text(argv[i]);
+        break;
+      case TCLVAR_VALUE_GLOB:
+        zGlob = sqlite3_value_text(argv[i]);
+        break;
+      case TCLVAR_VALUE_REGEXP:
+        zRegexp = sqlite3_value_text(argv[i]);
+        break;
+      case TCLVAR_VALUE_LIKE:
+        zLike = sqlite3_value_text(argv[i]);
+        break;
+      default:
+        assert( 0 );
+    }
   }
+
+  Tcl_IncrRefCount(p);
+  Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zEq, -1));
+  Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zMatch, -1));
+  Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zGlob, -1));
+  Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zRegexp, -1));
+  Tcl_ListObjAppendElement(0, p, Tcl_NewStringObj(zLike, -1));
+
   Tcl_EvalObjEx(interp, p, TCL_EVAL_GLOBAL);
   if( pCur->pList1 ){
     Tcl_DecrRefCount(pCur->pList1);
@@ -176,7 +214,6 @@ static int tclvarFilter(
   pCur->i2 = 0;
   pCur->pList1 = Tcl_GetObjResult(interp);
   Tcl_IncrRefCount(pCur->pList1);
-  assert( pCur->i1==0 && pCur->i2==0 && pCur->pList2==0 );
 
   Tcl_DecrRefCount(p);
   return tclvarNext(pVtabCursor);
@@ -224,32 +261,113 @@ static int tclvarEof(sqlite3_vtab_cursor *cur){
   return (pCur->pList2?0:1);
 }
 
+/*
+** If nul-terminated string zStr does not already contain the character 
+** passed as the second argument, append it and return 0. Or, if there is
+** already an instance of x in zStr, do nothing return 1;
+**
+** There is guaranteed to be enough room in the buffer pointed to by zStr
+** for the new character and nul-terminator.
+*/
+static int tclvarAddToIdxstr(char *zStr, char x){
+  int i;
+  for(i=0; zStr[i]; i++){
+    if( zStr[i]==x ) return 1;
+  }
+  zStr[i] = x;
+  zStr[i+1] = '\0';
+  return 0;
+}
+
+/*
+** Return true if variable $::tclvar_set_omit exists and is set to true.
+** False otherwise.
+*/
+static int tclvarSetOmit(Tcl_Interp *interp){
+  int rc;
+  int res = 0;
+  Tcl_Obj *pRes;
+  rc = Tcl_Eval(interp,
+    "expr {[info exists ::tclvar_set_omit] && $::tclvar_set_omit}"
+  );
+  if( rc==TCL_OK ){
+    pRes = Tcl_GetObjResult(interp);
+    rc = Tcl_GetBooleanFromObj(0, pRes, &res);
+  }
+  return (rc==TCL_OK && res);
+}
+
+/*
+** The xBestIndex() method. This virtual table supports the following
+** operators:
+**
+**     name = ?                    (omit flag clear)
+**     name MATCH ?                (omit flag set)
+**     value GLOB ?                (omit flag set iff $::tclvar_set_omit)
+**     value REGEXP ?              (omit flag set iff $::tclvar_set_omit)
+**     value LIKE ?                (omit flag set iff $::tclvar_set_omit)
+**
+** For each constraint present, the corresponding TCLVAR_XXX character is
+** appended to the idxStr value. 
+*/
 static int tclvarBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
+  tclvar_vtab *pTab = (tclvar_vtab*)tab;
   int ii;
+  char *zStr = sqlite3_malloc(32);
+  int iStr = 0;
 
-  for(ii=0; ii<pIdxInfo->nConstraint; ii++){
-    struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii];
-    if( pCons->iColumn==0 && pCons->usable
-           && pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
-      struct sqlite3_index_constraint_usage *pUsage;
-      pUsage = &pIdxInfo->aConstraintUsage[ii];
-      pUsage->omit = 0;
-      pUsage->argvIndex = 1;
-      return SQLITE_OK;
-    }
-  }
+  if( zStr==0 ) return SQLITE_NOMEM;
+  zStr[0] = '\0';
 
   for(ii=0; ii<pIdxInfo->nConstraint; ii++){
     struct sqlite3_index_constraint const *pCons = &pIdxInfo->aConstraint[ii];
-    if( pCons->iColumn==0 && pCons->usable
-           && pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH ){
-      struct sqlite3_index_constraint_usage *pUsage;
-      pUsage = &pIdxInfo->aConstraintUsage[ii];
-      pUsage->omit = 1;
-      pUsage->argvIndex = 1;
-      return SQLITE_OK;
+    struct sqlite3_index_constraint_usage *pUsage;
+    
+    pUsage = &pIdxInfo->aConstraintUsage[ii];
+    if( pCons->usable ){
+      /* name = ? */
+      if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && pCons->iColumn==0 ){
+        if( 0==tclvarAddToIdxstr(zStr, TCLVAR_NAME_EQ) ){
+          pUsage->argvIndex = ++iStr;
+          pUsage->omit = 0;
+        }
+      }
+
+      /* name MATCH ? */
+      if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH && pCons->iColumn==0 ){
+        if( 0==tclvarAddToIdxstr(zStr, TCLVAR_NAME_MATCH) ){
+          pUsage->argvIndex = ++iStr;
+          pUsage->omit = 1;
+        }
+      }
+
+      /* value GLOB ? */
+      if( pCons->op==SQLITE_INDEX_CONSTRAINT_GLOB && pCons->iColumn==2 ){
+        if( 0==tclvarAddToIdxstr(zStr, TCLVAR_VALUE_GLOB) ){
+          pUsage->argvIndex = ++iStr;
+          pUsage->omit = tclvarSetOmit(pTab->interp);
+        }
+      }
+
+      /* value REGEXP ? */
+      if( pCons->op==SQLITE_INDEX_CONSTRAINT_REGEXP && pCons->iColumn==2 ){
+        if( 0==tclvarAddToIdxstr(zStr, TCLVAR_VALUE_REGEXP) ){
+          pUsage->argvIndex = ++iStr;
+          pUsage->omit = tclvarSetOmit(pTab->interp);
+        }
+      }
+
+      /* value LIKE ? */
+      if( pCons->op==SQLITE_INDEX_CONSTRAINT_LIKE && pCons->iColumn==2 ){
+        if( 0==tclvarAddToIdxstr(zStr, TCLVAR_VALUE_LIKE) ){
+          pUsage->argvIndex = ++iStr;
+          pUsage->omit = tclvarSetOmit(pTab->interp);
+        }
+      }
     }
   }
+  pIdxInfo->idxStr = zStr;
+  pIdxInfo->needToFreeIdxStr = 1;
 
   return SQLITE_OK;
 }
@@ -295,6 +413,7 @@ static int register_tclvar_module(
   int objc,              /* Number of arguments */
   Tcl_Obj *CONST objv[]  /* Command arguments */
 ){
+  int rc = TCL_OK;
   sqlite3 *db;
   if( objc!=2 ){
     Tcl_WrongNumArgs(interp, 1, objv, "DB");
@@ -302,9 +421,30 @@ static int register_tclvar_module(
   }
   if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
 #ifndef SQLITE_OMIT_VIRTUALTABLE
-  sqlite3_create_module(db, "tclvar", &tclvarModule, (void *)interp);
+  sqlite3_create_module(db, "tclvar", &tclvarModule, (void*)interp);
+  rc = Tcl_Eval(interp, 
+      "proc like {pattern str} {\n"
+      "  set p [string map {% * _ ?} $pattern]\n"
+      "  string match $p $str\n"
+      "}\n"
+      "proc tclvar_filter_cmd {eq match glob regexp like} {\n"
+      "  set res {}\n"
+      "  set pattern $eq\n"
+      "  if {$pattern=={}} { set pattern $match }\n"
+      "  if {$pattern=={}} { set pattern * }\n"
+      "  foreach v [uplevel #0 info vars $pattern] {\n"
+      "    if {($glob=={} || [string match $glob [uplevel #0 set $v]])\n"
+      "     && ($like=={} || [like $like [uplevel #0 set $v]])\n"
+      "     && ($regexp=={} || [regexp $regexp [uplevel #0 set $v]])\n"
+      "    } {\n"
+      "      lappend res $v\n"
+      "    }\n"
+      "  }\n"
+      "  set res\n"
+      "}\n"
+  );
 #endif
-  return TCL_OK;
+  return rc;
 }
 
 #endif
index 0cc2fd7209e229eed7734488fa4fa64170dd0598..8ef91b03ebc179374212a493d33f781ccbb98a66 100644 (file)
@@ -277,7 +277,10 @@ static int isLikeOrGlob(
 /*
 ** Check to see if the given expression is of the form
 **
-**         column MATCH expr
+**         column OP expr
+**
+** where OP is one of MATCH, GLOB, LIKE or REGEXP and "column" is a 
+** column of a virtual table.
 **
 ** If it is then return TRUE.  If not, return FALSE.
 */
@@ -289,12 +292,13 @@ static int isMatchOfColumn(
     const char *zOp;
     unsigned char eOp2;
   } aOp[] = {
-    { "match", SQLITE_INDEX_CONSTRAINT_MATCH },
-    { "glob",  SQLITE_INDEX_CONSTRAINT_GLOB },
-    { "like",  SQLITE_INDEX_CONSTRAINT_LIKE },
-    { "regex", SQLITE_INDEX_CONSTRAINT_REGEXP }
+    { "match",  SQLITE_INDEX_CONSTRAINT_MATCH },
+    { "glob",   SQLITE_INDEX_CONSTRAINT_GLOB },
+    { "like",   SQLITE_INDEX_CONSTRAINT_LIKE },
+    { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP }
   };
   ExprList *pList;
+  Expr *pCol;                     /* Column reference */
   int i;
 
   if( pExpr->op!=TK_FUNCTION ){
@@ -304,7 +308,8 @@ static int isMatchOfColumn(
   if( pList->nExpr!=2 ){
     return 0;
   }
-  if( pList->a[1].pExpr->op != TK_COLUMN ){
+  pCol = pList->a[1].pExpr;
+  if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){
     return 0;
   }
   for(i=0; i<ArraySize(aOp); i++){
index 22ec0181bfbc5c9e45228e330f4c41f20196fe0c..aeb478e3e8d67ae81b28b38f179b0117e7ba6a46 100644 (file)
@@ -46,3 +46,5 @@ do_test vtabE-1 {
      ORDER BY t1.value, t2.value;
   }
 } {vtabE vtabE1 11 vtabE1 w x {} vtabE vtabE1 11 vtabE1 y z {} vtabE vtabE2 22 vtabE2 a b {} vtabE vtabE2 22 vtabE2 c d {}}
+
+finish_test
index b0577eccabaa601d234819a207a9133e33ea6734..53ecf0a94869d1807db260bc39001216a5816d00 100644 (file)
@@ -8,7 +8,9 @@
 #    May you share freely, never taking more than you give.
 #
 #***********************************************************************
-# This file implements regression tests for SQLite library.
+# This file implements regression tests for SQLite library. Specifically,
+# it tests that the GLOB, LIKE and REGEXP operators are correctly exposed
+# to virtual table implementations.
 #
 
 set testdir [file dirname $argv0]
@@ -46,4 +48,63 @@ foreach {tn sql expect} {
   } [list {*}$expect]
 }
 
+
+#--------------------------------------------------------------------------
+
+register_tclvar_module db
+set ::xyz 10
+do_execsql_test 2.0 {
+  CREATE VIRTUAL TABLE vars USING tclvar;
+  SELECT * FROM vars WHERE name = 'xyz';
+} {xyz {} 10}
+
+set x1 aback
+set x2 abaft
+set x3 abandon
+set x4 abandonint
+set x5 babble
+set x6 baboon
+set x7 backbone
+set x8 backarrow
+set x9 castle
+
+db func glob gfunc
+proc gfunc {a b} {
+  incr ::gfunc
+  return 1
+}
+
+db func like lfunc
+proc lfunc {a b} {
+  incr ::gfunc 100
+  return 1
+}
+
+db func regexp rfunc
+proc rfunc {a b} {
+  incr ::gfunc 10000
+  return 1
+}
+
+foreach ::tclvar_set_omit {0 1} {
+  foreach {tn expr res cnt} {
+    1 {value GLOB 'aban*'} {x3 abandon x4 abandonint} 2
+    2 {value LIKE '%ac%'}  {x1 aback x7 backbone x8 backarrow} 300
+    3 {value REGEXP '^......$'}  {x5 babble x6 baboon x9 castle} 30000
+  } {
+  if {$tn==3} breakpoint
+    db cache flush
+    set ::gfunc 0
+    if {$::tclvar_set_omit} {set cnt 0}
+
+    do_test 2.$tclvar_set_omit.$tn.1 {
+      execsql "SELECT name, value FROM vars WHERE name MATCH 'x*' AND $expr"
+    } $res
+
+    do_test 2.$tclvar_set_omit.$tn.2 {
+      set ::gfunc
+    } $cnt
+  }
+}
+
 finish_test