aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrh <drh@noemail.net>2015-11-25 01:57:42 +0000
committerdrh <drh@noemail.net>2015-11-25 01:57:42 +0000
commitd4a3ce49d7e09cadc43308ae4545f1039f9cf84f (patch)
treeb2c88af14500478947eedec7977ffbd39de9b6f5
parent5a8d190b9ecf9e38857bfa77440a4e9c91aa28e3 (diff)
parent8b4a94adc1dea3254a7eed1e33c210509c496e2d (diff)
downloadsqlite-d4a3ce49d7e09cadc43308ae4545f1039f9cf84f.tar.gz
sqlite-d4a3ce49d7e09cadc43308ae4545f1039f9cf84f.zip
Enhancement the virtual table interface to support LIKE, GLOB, and REGEXP
operators. Also add the sqlite3_strlike() interface, which might be useful as part of the implementation of LIKE on some virtual tables. FossilOrigin-Name: a6bfd4692c3f8b107546fbcaeb985d2c1817b3c1
-rw-r--r--manifest34
-rw-r--r--manifest.uuid2
-rw-r--r--src/analyze.c2
-rw-r--r--src/func.c7
-rw-r--r--src/sqlite.h.in42
-rw-r--r--src/sqlite3ext.h4
-rw-r--r--src/test8.c6
-rw-r--r--src/test_tclvar.c194
-rw-r--r--src/where.c3
-rw-r--r--src/whereInt.h1
-rw-r--r--src/whereexpr.c39
-rw-r--r--test/vtab1.test8
-rw-r--r--test/vtabE.test2
-rw-r--r--test/vtabH.test110
14 files changed, 390 insertions, 64 deletions
diff --git a/manifest b/manifest
index 36e5b2757..85f162120 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Remove\sfrom\sos_unix.c\spointless\slogic\sthat\stries\sto\sprevent\sa\srecurrence\sof\na\swarning\smessage\sthat\scan\sonly\soccur\sonce.
-D 2015-11-24T16:40:23.118
+C Enhancement\sthe\svirtual\stable\sinterface\sto\ssupport\sLIKE,\sGLOB,\sand\sREGEXP\noperators.\s\sAlso\sadd\sthe\ssqlite3_strlike()\sinterface,\swhich\smight\sbe\suseful\nas\spart\sof\sthe\simplementation\sof\sLIKE\son\ssome\svirtual\stables.
+D 2015-11-25T01:57:42.585
F Makefile.in d828db6afa6c1fa060d01e33e4674408df1942a1
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc e928e68168df69b353300ac87c10105206653a03
@@ -276,7 +276,7 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
F src/alter.c 9d649e46c780166e416fb11dbd23f8d49aab8267
-F src/analyze.c 4c308880cf53c558070cb8513bdff4ffb1a38a77
+F src/analyze.c 977bd50c751bb939ef52917892e12bedbfcea7ce
F src/attach.c e944d0052b577703b9b83aac1638452ff42a8395
F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240
F src/backup.c 2869a76c03eb393ee795416e2387005553df72bc
@@ -295,7 +295,7 @@ F src/delete.c 00af9f08a15ddc5cba5962d3d3e5bf2d67b2e7da
F src/expr.c cb1a419508e5b27769a91e00e36e94724e7b1d51
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c 31900763094a3736a5fc887469202eb579fef2d0
-F src/func.c ecdd69ec6a1e406f04cc73324be2ebbf6354197f
+F src/func.c 5790a898a0c53e6787020ec268425d415e7e03c9
F src/global.c 508e4087f7b41d688e4762dcf4d4fe28cfbc87f9
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
@@ -341,9 +341,9 @@ F src/resolve.c f4c897ca76ca6d5e0b3f0499c627392ffe657c8e
F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e
F src/select.c e10586c750d87211caa8f4b239e2bfa6a2049e5b
F src/shell.c f0f59ea60ad297f671b7ae0fb957a736ad17c92c
-F src/sqlite.h.in fa62718f73553f06b2f2e362fd09ccb4e1cbb626
+F src/sqlite.h.in cbe8643b6a1c7313bf6447a2aa7abdb31c73dd77
F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad
-F src/sqlite3ext.h 4b66e3e3435da4b4c8c83696d0349f0c503b3924
+F src/sqlite3ext.h 41ef50b0418a7c5ad1337bb80db5a7928dee764f
F src/sqliteInt.h 64256d193a16a147d9f6317cc4e095fdd3e0a2e9
F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
F src/status.c 70912d7be68e9e2dbc4010c93d344af61d4c59ba
@@ -356,7 +356,7 @@ F src/test4.c d168f83cc78d02e8d35567bb5630e40dcd85ac1e
F src/test5.c 5a34feec76d9b3a86aab30fd4f6cc9c48cbab4c1
F src/test6.c 41cacf3b0dd180823919bf9e1fbab287c9266723
F src/test7.c 9c89a4f1ed6bb13af0ed805b8d782bd83fcd57e3
-F src/test8.c 697c9c84a13e08c72ea95a3637d4374caf54fc93
+F src/test8.c 85b8c6a0309130300d560e2334ee71e70391785e
F src/test9.c bea1e8cf52aa93695487badedd6e1886c321ea60
F src/test_async.c 21e11293a2f72080eda70e1124e9102044531cd8
F src/test_autoext.c dea8a01a7153b9adc97bd26161e4226329546e12
@@ -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 d86412527da65468ee6fa1b8607c65d0af736bc4
F src/test_thread.c af391ec03d23486dffbcc250b7e58e073f172af9
F src/test_vfs.c 3b65d42e18b262805716bd96178c81da8f2d9283
F src/test_vfstrace.c bab9594adc976cbe696ff3970728830b4c5ed698
@@ -416,10 +416,10 @@ F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb
F src/wal.c 18b0ed49830cf04fe2d68224b41838a73ac6cd24
F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba
-F src/where.c 6aceb72cc58dc06922a9e1604d559c8ca4c3e728
-F src/whereInt.h 7892bb54cf9ca0ae5c7e6094491b94c9286dc647
+F src/where.c 6687fb2675d9c1c1936ceca77529e2f21fb3a769
+F src/whereInt.h 6afc0d70cf6213e58e8fbe10b6e50d1aa16f122f
F src/wherecode.c 4c96182e7b25e4be54008dee2da5b9c2f8480b9b
-F src/whereexpr.c bd4877cd4dd11f6ab551ef0054535ca3c6224950
+F src/whereexpr.c eebba8340c90de73b3d3bbe8c43b84559b8e6e2c
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
@@ -1259,7 +1259,7 @@ F test/vacuum4.test d3f8ecff345f166911568f397d2432c16d2867d9
F test/varint.test ab7b110089a08b9926ed7390e7e97bdefeb74102
F test/veryquick.test 57ab846bacf7b90cf4e9a672721ea5c5b669b661
F test/view.test f6c3a39e0c819891265e1d0754e99960d81ef6c9
-F test/vtab1.test 6210e076997f176bedc300a87ad6404651b601dd
+F test/vtab1.test ec5cb767277d7e0eff34d3a02824c1dd959a5959
F test/vtab2.test f8cd1bb9aba7143eba97812d9617880a36d247ad
F test/vtab3.test b45f47d20f225ccc9c28dc915d92740c2dee311e
F test/vtab4.test 942f8b8280b3ea8a41dae20e7822d065ca1cb275
@@ -1272,8 +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 694aa399eb28ed0db2aef59f2f37532781eeb957
F test/vtab_alter.test 9e374885248f69e251bdaacf480b04a197f125e5
F test/vtab_err.test 0d4d8eb4def1d053ac7c5050df3024fd47a3fbd8
F test/vtab_shared.test ea8778d5b0df200adef2ca7c00c3c37d4375f772
@@ -1404,7 +1405,8 @@ F tool/vdbe_profile.tcl 246d0da094856d72d2c12efec03250d71639d19f
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 32e138796c66c88e8cbb77aa3a4282a38d1f959a
-R 39dda72890996a1a2961e4723fcfeb24
+P 20256177072caa4f2b4114038ad1c8f6e26bc562 e70ec71d6883f2f8fc75301ff985bccb5aa06127
+R d30ed8c32e99c9a94479405580943fff
+T +closed e70ec71d6883f2f8fc75301ff985bccb5aa06127
U drh
-Z 6e0fb81bf9792119f74030230681d485
+Z 535a3252c539f51f296331cdb8ca572e
diff --git a/manifest.uuid b/manifest.uuid
index 9df0621fe..453c2a5a9 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-20256177072caa4f2b4114038ad1c8f6e26bc562 \ No newline at end of file
+a6bfd4692c3f8b107546fbcaeb985d2c1817b3c1 \ No newline at end of file
diff --git a/src/analyze.c b/src/analyze.c
index ad752d2c0..06918eb74 100644
--- a/src/analyze.c
+++ b/src/analyze.c
@@ -990,7 +990,7 @@ static void analyzeOneTable(
/* Do not gather statistics on views or virtual tables */
return;
}
- if( sqlite3_strnicmp(pTab->zName, "sqlite_", 7)==0 ){
+ if( sqlite3_strlike("sqlite_%", pTab->zName, 0)==0 ){
/* Do not gather statistics on system tables */
return;
}
diff --git a/src/func.c b/src/func.c
index 8ea116932..b134c1a7c 100644
--- a/src/func.c
+++ b/src/func.c
@@ -764,6 +764,13 @@ int sqlite3_strglob(const char *zGlobPattern, const char *zString){
}
/*
+** The sqlite3_strlike() interface.
+*/
+int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){
+ return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc)==0;
+}
+
+/*
** Count the number of times that the LIKE operator (or GLOB which is
** just a variation of LIKE) gets called. This is used for testing
** only.
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 13c97d8d4..b93ff5873 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -5709,12 +5709,15 @@ struct sqlite3_index_info {
** an operator that is part of a constraint term in the wHERE clause of
** a query that uses a [virtual table].
*/
-#define SQLITE_INDEX_CONSTRAINT_EQ 2
-#define SQLITE_INDEX_CONSTRAINT_GT 4
-#define SQLITE_INDEX_CONSTRAINT_LE 8
-#define SQLITE_INDEX_CONSTRAINT_LT 16
-#define SQLITE_INDEX_CONSTRAINT_GE 32
-#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+#define SQLITE_INDEX_CONSTRAINT_EQ 2
+#define SQLITE_INDEX_CONSTRAINT_GT 4
+#define SQLITE_INDEX_CONSTRAINT_LE 8
+#define SQLITE_INDEX_CONSTRAINT_LT 16
+#define SQLITE_INDEX_CONSTRAINT_GE 32
+#define SQLITE_INDEX_CONSTRAINT_MATCH 64
+#define SQLITE_INDEX_CONSTRAINT_LIKE 65
+#define SQLITE_INDEX_CONSTRAINT_GLOB 66
+#define SQLITE_INDEX_CONSTRAINT_REGEXP 67
/*
** CAPI3REF: Register A Virtual Table Implementation
@@ -7374,10 +7377,37 @@ int sqlite3_strnicmp(const char *, const char *, int);
**
** Note that this routine returns zero on a match and non-zero if the strings
** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+**
+** See also: sqlite3_strlike().
*/
int sqlite3_strglob(const char *zGlob, const char *zStr);
/*
+** CAPI3REF: String LIKE Matching
+*
+** ^The [sqlite3_strlike(P,X,E)] interface returns zero if string X matches
+** the LIKE pattern P with escape character E, and it returns non-zero if
+** string X does not match the like pattern.
+** ^The definition of LIKE pattern matching used in
+** [sqlite3_strlike(P,X,E)] is the same as for the "X LIKE P ESCAPE E"
+** operator in the SQL dialect used by SQLite. ^For "X LIKE P" without
+** the ESCAPE clause, set the E parameter of [sqlite3_strlike(P,X,E)] to 0.
+** ^As with the LIKE operator, the [sqlite3_str(P,X,E)] function is case
+** insensitive - equivalent upper and lower case ASCII characters match
+** one another.
+**
+** ^The [sqlite3_strlike(P,X,E)] function matches Unicode characters, though
+** only ASCII characters are case folded. (This is because when SQLite was
+** first written, the case folding rules for Unicode where still in flux.)
+**
+** Note that this routine returns zero on a match and non-zero if the strings
+** do not match, the same as [sqlite3_stricmp()] and [sqlite3_strnicmp()].
+**
+** See also: sqlite3_strglob().
+*/
+int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc);
+
+/*
** CAPI3REF: Error Logging Interface
**
** ^The [sqlite3_log()] interface writes a message into the [error log]
diff --git a/src/sqlite3ext.h b/src/sqlite3ext.h
index 017ea308b..3029a82fa 100644
--- a/src/sqlite3ext.h
+++ b/src/sqlite3ext.h
@@ -275,6 +275,8 @@ struct sqlite3_api_routines {
/* Version 3.9.0 and later */
unsigned int (*value_subtype)(sqlite3_value*);
void (*result_subtype)(sqlite3_context*,unsigned int);
+ /* Version 3.10.0 and later */
+ int (*strlike)(const char*,const char*,unsigned int);
};
/*
@@ -514,6 +516,8 @@ struct sqlite3_api_routines {
/* Version 3.9.0 and later */
#define sqlite3_value_subtype sqlite3_api->value_subtype
#define sqlite3_result_subtype sqlite3_api->result_subtype
+/* Version 3.10.0 and later */
+#define sqlite3_strlike sqlite3_api->strlike
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
diff --git a/src/test8.c b/src/test8.c
index 7d3756aec..0c5dc0206 100644
--- a/src/test8.c
+++ b/src/test8.c
@@ -856,6 +856,12 @@ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){
** wrapping the bound parameter with literal '%'s).
*/
zOp = "LIKE"; 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;
}
if( zOp[0]=='L' ){
zNew = sqlite3_mprintf(" %s %s LIKE (SELECT '%%'||?||'%%')",
diff --git a/src/test_tclvar.c b/src/test_tclvar.c
index 1219190c0..63ed39473 100644
--- a/src/test_tclvar.c
+++ b/src/test_tclvar.c
@@ -23,6 +23,15 @@
#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 = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_NAME_MATCH:
+ zMatch = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_VALUE_GLOB:
+ zGlob = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_VALUE_REGEXP:
+ zRegexp = (const char*)sqlite3_value_text(argv[i]);
+ break;
+ case TCLVAR_VALUE_LIKE:
+ zLike = (const char*)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
diff --git a/src/where.c b/src/where.c
index 1c87706ea..737bfc4e6 100644
--- a/src/where.c
+++ b/src/where.c
@@ -893,6 +893,9 @@ static sqlite3_index_info *allocateIndexInfo(
pIdxCons[j].iTermOffset = i;
op = (u8)pTerm->eOperator & WO_ALL;
if( op==WO_IN ) op = WO_EQ;
+ if( op==WO_MATCH ){
+ op = pTerm->eMatchOp;
+ }
pIdxCons[j].op = op;
/* The direct assignment in the previous line is possible only because
** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The
diff --git a/src/whereInt.h b/src/whereInt.h
index cae09acc8..86164d8c1 100644
--- a/src/whereInt.h
+++ b/src/whereInt.h
@@ -253,6 +253,7 @@ struct WhereTerm {
u16 eOperator; /* A WO_xx value describing <op> */
u16 wtFlags; /* TERM_xxx bit flags. See below */
u8 nChild; /* Number of children that must disable us */
+ u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */
WhereClause *pWC; /* The clause this term is part of */
Bitmask prereqRight; /* Bitmask of tables used by pExpr->pRight */
Bitmask prereqAll; /* Bitmask of tables referenced by pExpr */
diff --git a/src/whereexpr.c b/src/whereexpr.c
index 21301ac04..99a97079b 100644
--- a/src/whereexpr.c
+++ b/src/whereexpr.c
@@ -277,29 +277,48 @@ 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.
*/
static int isMatchOfColumn(
- Expr *pExpr /* Test this expression */
+ Expr *pExpr, /* Test this expression */
+ unsigned char *peOp2 /* OUT: 0 for MATCH, or else an op2 value */
){
+ struct Op2 {
+ const char *zOp;
+ unsigned char eOp2;
+ } aOp[] = {
+ { "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 ){
return 0;
}
- if( sqlite3StrICmp(pExpr->u.zToken,"match")!=0 ){
- return 0;
- }
pList = pExpr->x.pList;
- if( pList->nExpr!=2 ){
+ if( pList==0 || 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;
}
- return 1;
+ for(i=0; i<ArraySize(aOp); i++){
+ if( sqlite3StrICmp(pExpr->u.zToken, aOp[i].zOp)==0 ){
+ *peOp2 = aOp[i].eOp2;
+ return 1;
+ }
+ }
+ return 0;
}
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@@ -876,6 +895,7 @@ static void exprAnalyze(
int op; /* Top-level operator. pExpr->op */
Parse *pParse = pWInfo->pParse; /* Parsing context */
sqlite3 *db = pParse->db; /* Database connection */
+ unsigned char eOp2; /* op2 value for LIKE/REGEXP/GLOB */
if( db->mallocFailed ){
return;
@@ -1099,7 +1119,7 @@ static void exprAnalyze(
** virtual tables. The native query optimizer does not attempt
** to do anything with MATCH functions.
*/
- if( isMatchOfColumn(pExpr) ){
+ if( isMatchOfColumn(pExpr, &eOp2) ){
int idxNew;
Expr *pRight, *pLeft;
WhereTerm *pNewTerm;
@@ -1120,6 +1140,7 @@ static void exprAnalyze(
pNewTerm->leftCursor = pLeft->iTable;
pNewTerm->u.leftColumn = pLeft->iColumn;
pNewTerm->eOperator = WO_MATCH;
+ pNewTerm->eMatchOp = eOp2;
markTermAsChild(pWC, idxNew, idxTerm);
pTerm = &pWC->a[idxTerm];
pTerm->wtFlags |= TERM_COPIED;
diff --git a/test/vtab1.test b/test/vtab1.test
index 84de4cffa..d5fae941a 100644
--- a/test/vtab1.test
+++ b/test/vtab1.test
@@ -1306,10 +1306,10 @@ foreach {tn sql res filter} {
{xFilter {SELECT rowid, * FROM 't6' WHERE b >= ? AND b < ?} J K}
1.3 "SELECT a FROM e6 WHERE b LIKE 'J%'" {3 4}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, * FROM 't6' WHERE b like ?} J%}
1.4 "SELECT a FROM e6 WHERE b LIKE 'j%'" {3 4}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, * FROM 't6' WHERE b like ?} j%}
} {
set echo_module {}
do_execsql_test 18.$tn.1 $sql $res
@@ -1319,10 +1319,10 @@ foreach {tn sql res filter} {
do_execsql_test 18.2.0 { PRAGMA case_sensitive_like = ON }
foreach {tn sql res filter} {
2.1 "SELECT a FROM e6 WHERE b LIKE 'J%'" {3 4}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, * FROM 't6' WHERE b like ?} J%}
2.2 "SELECT a FROM e6 WHERE b LIKE 'j%'" {}
- {xFilter {SELECT rowid, * FROM 't6'}}
+ {xFilter {SELECT rowid, * FROM 't6' WHERE b like ?} j%}
} {
set echo_module {}
do_execsql_test 18.$tn.1 $sql $res
diff --git a/test/vtabE.test b/test/vtabE.test
index 22ec0181b..aeb478e3e 100644
--- a/test/vtabE.test
+++ b/test/vtabE.test
@@ -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
diff --git a/test/vtabH.test b/test/vtabH.test
new file mode 100644
index 000000000..53ecf0a94
--- /dev/null
+++ b/test/vtabH.test
@@ -0,0 +1,110 @@
+# 2015 Nov 24
+#
+# 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.
+#
+#***********************************************************************
+# 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]
+source $testdir/tester.tcl
+set testprefix vtabH
+
+ifcapable !vtab {
+ finish_test
+ return
+}
+
+register_echo_module db
+
+do_execsql_test 1.0 {
+ CREATE TABLE t6(a, b TEXT);
+ CREATE INDEX i6 ON t6(b, a);
+ CREATE VIRTUAL TABLE e6 USING echo(t6);
+}
+
+foreach {tn sql expect} {
+ 1 "SELECT * FROM e6 WHERE b LIKE 'abc'" {
+ xBestIndex {SELECT rowid, * FROM 't6' WHERE b like ?}
+ xFilter {SELECT rowid, * FROM 't6' WHERE b like ?} abc
+ }
+
+ 2 "SELECT * FROM e6 WHERE b GLOB 'abc'" {
+ xBestIndex {SELECT rowid, * FROM 't6' WHERE b glob ?}
+ xFilter {SELECT rowid, * FROM 't6' WHERE b glob ?} abc
+ }
+} {
+ do_test 1.$tn {
+ set echo_module {}
+ execsql $sql
+ set ::echo_module
+ } [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