Add further tests and related fixes for GLOB/REGEXP/LIKE support in virtual tables.

FossilOrigin-Name: c5e9fd0dc92a07db3d3b5f5c5ad8fb63b3425c2b
This commit is contained in:
dan 2015-11-24 17:39:01 +00:00
parent 07bdba86d5
commit 43970dd774
6 changed files with 252 additions and 47 deletions

View File

@ -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

View File

@ -1 +1 @@
277a5b4027d4c2caba8143228a4f7d6df899dbb4
c5e9fd0dc92a07db3d3b5f5c5ad8fb63b3425c2b

View File

@ -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 = 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;
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_EQ ){
struct sqlite3_index_constraint_usage *pUsage;
pUsage = &pIdxInfo->aConstraintUsage[ii];
pUsage->omit = 0;
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;
}
}
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;
/* 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

View 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++){

View 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

View 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