From: dan Date: Sat, 9 Sep 2017 19:41:12 +0000 (+0000) Subject: Enhance the vtab interface to handle IS, !=, IS NOT, IS NULL and IS NOT NULL X-Git-Tag: version-3.21.0~99^2~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d03024d81429ec770c82a01b19c181e0322be729;p=thirdparty%2Fsqlite.git Enhance the vtab interface to handle IS, !=, IS NOT, IS NULL and IS NOT NULL constraints. FossilOrigin-Name: 34c8e952616013deb6fffec701ac5989afac9bef1bf92458a2e4ba92c7ee924f --- diff --git a/manifest b/manifest index 4b71f03671..3e1369face 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sharmless\scompiler\swarnings\sseen\swith\sMSVC. -D 2017-09-09T00:51:36.496 +C Enhance\sthe\svtab\sinterface\sto\shandle\sIS,\s!=,\sIS\sNOT,\sIS\sNULL\sand\sIS\sNOT\sNULL\nconstraints. +D 2017-09-09T19:41:12.986 F Makefile.in c644bbe8ebe4aae82ad6783eae6b6beea4c727b99ff97568b847ced5e2ac7afb F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc 6a7a74bf60ad395098c0bd175ab054cd65ef85d7f034198d52bcc4d9e5fb4c6b @@ -460,7 +460,7 @@ F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac F src/select.c c9b3d8444bbf6f167d84f41ca6f3672e2521cb163a8c706b19058dc82fffe9b8 F src/shell.c c1206a23d9239f8f51751d3be9b8c3b02fa4103546bea1add7f864d84a8276ab F src/shell.c.in bb9720a8c5c98d3984b16ab7540e7142bcae959666ecf248bfc523a1d44220ee -F src/sqlite.h.in 21f62793a3611b43f6fb31f0a4c8b38489a4df025416e9b7db7cc01bf5ef5aaa +F src/sqlite.h.in d0ab3cae93cc9819f9e7ba5c8c8e3708e657c6cdbc61ecfa7dfadd19c0308ffa F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a1fd3aa82f967da436164e0728a7d6841651fd0c6e27b9044e0eb9f6c8462e47 F src/sqliteInt.h f9ae3609a583aa51712083e1d5817f62c7d97c0a203036a9a62c439059e8258b @@ -475,12 +475,12 @@ F src/test4.c 18ec393bb4d0ad1de729f0b94da7267270f3d8e6 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d F src/test6.c e8d839fbc552ce044bec8234561a2d5b8819b48e29548ad0ba400471697946a8 F src/test7.c 5612e9aecf934d6df7bba6ce861fdf5ba5456010 -F src/test8.c 4f4904721167b32f7a4fa8c7b32a07a673d6cc86 +F src/test8.c 3f7d0cc4e12e06832ba3db4455cb16867ccadafa602eb6ff5fcf097bffce56ed F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 F src/test_backup.c bf5da90c9926df0a4b941f2d92825a01bbe090a0 -F src/test_bestindex.c d23f80d334c59662af69191854c76b8d3d0c8c96 +F src/test_bestindex.c 78809f11026f18a93fcfd798d9479cba37e1201c830260bf1edc674b2fa9b857 F src/test_blob.c ae4a0620b478548afb67963095a7417cd06a4ec0a56adb453542203bfdcb31ce F src/test_btree.c 8b2dc8b8848cf3a4db93f11578f075e82252a274 F src/test_config.c abf6fc1fe9d041b699578c42e3db81f8831c4f5b804f1927958102ee8f2b773e @@ -539,10 +539,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 839db09792fead5052bb35e533fa485e134913d547d05b5f42e537b73e63f07a F src/wal.h 8de5d2d3de0956d6f6cb48c83a4012d5f227b8fe940f3a349a4b7e85ebcb492a F src/walker.c 3ccfa8637f95355bff61144e01a615b8ef26f79c312880848da73f03367da1e6 -F src/where.c 101f0a645c45c12141b38a61b593232555fc001bf7786dcb03eb8f313783b404 +F src/where.c 7cc9692dc4f270f5a196d33d2ee1011ce6218a6061b73df592771a404ee3500c F src/whereInt.h 93bb90b77d39901eda31b44d8e90da1351193ccfe96876f89b58a93a33b84c3d -F src/wherecode.c d246d19f5453d3f154ed8fcea892ce6d70ae4a5ddaebae34bd49d73f4c913bc7 -F src/whereexpr.c fe1fe600d7334e91f3d9d487021362d543fba8ab2f1be5e0d68063d619379c05 +F src/wherecode.c e8c2ece5843ea56e6c90277d421f2d628f3f7b7c976642369cc519f008e1d2b1 +F src/whereexpr.c ffc3c90f68ad28c6eca1c8b05029f361bc151187be578985d992788d31f295ae F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd F test/affinity3.test 6a101af2fc945ce2912f6fe54dd646018551710d @@ -610,6 +610,7 @@ F test/bestindex1.test 0cf1bd2d7b97d3a3a8c10736125274f64765c4ee F test/bestindex2.test 4a06b8922ab2fd09434870da8d1cdf525aaf7060 F test/bestindex3.test 578b6a52dab819e63f28e3640e04b32c85aed320 F test/bestindex4.test 4cb5ff7dbaebadb87d366f51969271778423b455 +F test/bestindex5.test a7f1c32dc21d5c85afd4e9611e1160247107387b85a371fded36852c1c4959e0 F test/between.test 34d375fb5ce1ae283ffe82b6b233e9f38e84fc6c F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59 F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc @@ -1652,7 +1653,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 e1e3ca7ea43a68b9b57dc38d8855f63b63a53feb8128b666a1becf87a2c70341 -R 14621bd82abfa0f650a4c6242c2108d7 -U mistachkin -Z 11f83c8a0999c8fee516d7917728daf2 +P faa22e29a5a05a16d46a428d838acedfa7d6fad6239495d59a6a1f4764e1b1b6 +R 71eba3dbcd321cff2772c9856db2cb4e +T *branch * vtab-extra-ops +T *sym-vtab-extra-ops * +T -sym-trunk * +U dan +Z f5cb136cb2d9007b3611d202913746bf diff --git a/manifest.uuid b/manifest.uuid index ce2953ddb7..e02149a44e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -faa22e29a5a05a16d46a428d838acedfa7d6fad6239495d59a6a1f4764e1b1b6 \ No newline at end of file +34c8e952616013deb6fffec701ac5989afac9bef1bf92458a2e4ba92c7ee924f \ No newline at end of file diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 8b3c22c92d..e2edfe4605 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -6256,6 +6256,11 @@ struct sqlite3_index_info { #define SQLITE_INDEX_CONSTRAINT_LIKE 65 #define SQLITE_INDEX_CONSTRAINT_GLOB 66 #define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 /* ** CAPI3REF: Register A Virtual Table Implementation diff --git a/src/test8.c b/src/test8.c index daab504e4e..2684f801fa 100644 --- a/src/test8.c +++ b/src/test8.c @@ -897,17 +897,18 @@ static int echoBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ case SQLITE_INDEX_CONSTRAINT_REGEXP: zOp = "regexp"; break; } - if( zOp[0]=='L' ){ - zNew = sqlite3_mprintf(" %s %s LIKE (SELECT '%%'||?||'%%')", - zSep, zNewCol); - } else { - zNew = sqlite3_mprintf(" %s %s %s ?", zSep, zNewCol, zOp); + if( zOp ){ + if( zOp[0]=='L' ){ + zNew = sqlite3_mprintf(" %s %s LIKE (SELECT '%%'||?||'%%')", + zSep, zNewCol); + } else { + zNew = sqlite3_mprintf(" %s %s %s ?", zSep, zNewCol, zOp); + } + string_concat(&zQuery, zNew, 1, &rc); + zSep = "AND"; + pUsage->argvIndex = ++nArg; + pUsage->omit = 1; } - string_concat(&zQuery, zNew, 1, &rc); - - zSep = "AND"; - pUsage->argvIndex = ++nArg; - pUsage->omit = 1; } } diff --git a/src/test_bestindex.c b/src/test_bestindex.c index 94017f0349..2cd79baf2b 100644 --- a/src/test_bestindex.c +++ b/src/test_bestindex.c @@ -414,6 +414,16 @@ static int tclBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ zOp = "glob"; break; case SQLITE_INDEX_CONSTRAINT_REGEXP: zOp = "regexp"; break; + case SQLITE_INDEX_CONSTRAINT_NE: + zOp = "ne"; break; + case SQLITE_INDEX_CONSTRAINT_ISNOT: + zOp = "isnot"; break; + case SQLITE_INDEX_CONSTRAINT_ISNOTNULL: + zOp = "isnotnull"; break; + case SQLITE_INDEX_CONSTRAINT_ISNULL: + zOp = "isnull"; break; + case SQLITE_INDEX_CONSTRAINT_IS: + zOp = "is"; break; } Tcl_ListObjAppendElement(0, pElem, Tcl_NewStringObj("op", -1)); diff --git a/src/where.c b/src/where.c index 7541873023..0e908e1516 100644 --- a/src/where.c +++ b/src/where.c @@ -868,7 +868,7 @@ static sqlite3_index_info *allocateIndexInfo( testcase( pTerm->eOperator & WO_ISNULL ); testcase( pTerm->eOperator & WO_IS ); testcase( pTerm->eOperator & WO_ALL ); - if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue; + if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; assert( pTerm->u.leftColumn>=(-1) ); nTerm++; @@ -916,7 +916,7 @@ static sqlite3_index_info *allocateIndexInfo( pUsage; for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){ - u8 op; + u16 op; if( pTerm->leftCursor != pSrc->iCursor ) continue; if( pTerm->prereqRight & mUnusable ) continue; assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); @@ -924,34 +924,41 @@ static sqlite3_index_info *allocateIndexInfo( testcase( pTerm->eOperator & WO_IS ); testcase( pTerm->eOperator & WO_ISNULL ); testcase( pTerm->eOperator & WO_ALL ); - if( (pTerm->eOperator & ~(WO_ISNULL|WO_EQUIV|WO_IS))==0 ) continue; + if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; if( pTerm->wtFlags & TERM_VNULL ) continue; assert( pTerm->u.leftColumn>=(-1) ); pIdxCons[j].iColumn = pTerm->u.leftColumn; pIdxCons[j].iTermOffset = i; - op = (u8)pTerm->eOperator & WO_ALL; + op = 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 - ** following asserts verify this fact. */ - assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ ); - assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT ); - assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE ); - assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT ); - assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE ); - assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH ); - assert( pTerm->eOperator & (WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) ); - - if( op & (WO_LT|WO_LE|WO_GT|WO_GE) - && sqlite3ExprIsVector(pTerm->pExpr->pRight) - ){ - if( i<16 ) mNoOmit |= (1 << i); - if( op==WO_LT ) pIdxCons[j].op = WO_LE; - if( op==WO_GT ) pIdxCons[j].op = WO_GE; + pIdxCons[j].op = pTerm->eMatchOp; + }else if( op & (WO_ISNULL|WO_IS) ){ + if( op==WO_ISNULL ){ + pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL; + }else{ + pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS; + } + }else{ + pIdxCons[j].op = (u8)op; + /* The direct assignment in the previous line is possible only because + ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The + ** following asserts verify this fact. */ + assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ ); + assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT ); + assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE ); + assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT ); + assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE ); + assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH ); + assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) ); + + if( op & (WO_LT|WO_LE|WO_GT|WO_GE) + && sqlite3ExprIsVector(pTerm->pExpr->pRight) + ){ + if( i<16 ) mNoOmit |= (1 << i); + if( op==WO_LT ) pIdxCons[j].op = WO_LE; + if( op==WO_GT ) pIdxCons[j].op = WO_GE; + } } j++; diff --git a/src/wherecode.c b/src/wherecode.c index 30ff3f25c4..6cdf7b566b 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -1017,7 +1017,7 @@ static void codeDeferredSeek( */ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ assert( nReg>0 ); - if( sqlite3ExprIsVector(p) ){ + if( p && sqlite3ExprIsVector(p) ){ #ifndef SQLITE_OMIT_SUBQUERY if( (p->flags & EP_xIsSelect) ){ Vdbe *v = pParse->pVdbe; diff --git a/src/whereexpr.c b/src/whereexpr.c index ffd31a6ee5..45d3a7e33f 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -317,43 +317,77 @@ static int isLikeOrGlob( ** column OP expr ** ** where OP is one of MATCH, GLOB, LIKE or REGEXP and "column" is a -** column of a virtual table. +** column of a virtual table. If so, set *ppLeft to point to the +** expression for "column", *ppRight to "expr" and return 1. ** -** If it is then return TRUE. If not, return FALSE. +** Also check if the expression is one of: +** +** column != expr +** column IS NOT expr +** column IS NOT NULL +** +** where "column" is a column of a virtual table. If so, set *ppLeft +** to point to "column", *ppRight to "expr" and return 1. Or, if "expr" +** is also a column of a virtual table, return 2. +** +** If the expression matches none of the patterns above, return 0. */ static int isMatchOfColumn( Expr *pExpr, /* Test this expression */ - unsigned char *peOp2 /* OUT: 0 for MATCH, or else an op2 value */ + unsigned char *peOp2, /* OUT: 0 for MATCH, or else an op2 value */ + Expr **ppLeft, /* Column expression to left of MATCH/op2 */ + Expr **ppRight /* Expression to left of MATCH/op2 */ ){ - static const 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 ){ + static const 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; - } - pList = pExpr->x.pList; - if( pList==0 || pList->nExpr!=2 ){ - return 0; - } - pCol = pList->a[1].pExpr; - if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){ - return 0; - } - for(i=0; iu.zToken, aOp[i].zOp)==0 ){ - *peOp2 = aOp[i].eOp2; - return 1; + if( pExpr->op!=TK_FUNCTION ){ + return 0; + } + pList = pExpr->x.pList; + if( pList==0 || pList->nExpr!=2 ){ + return 0; + } + pCol = pList->a[1].pExpr; + if( pCol->op!=TK_COLUMN || !IsVirtual(pCol->pTab) ){ + return 0; + } + for(i=0; iu.zToken, aOp[i].zOp)==0 ){ + *peOp2 = aOp[i].eOp2; + *ppRight = pList->a[0].pExpr; + *ppLeft = pCol; + return 1; + } + } + }else if( pExpr->op==TK_NE || pExpr->op==TK_ISNOT || pExpr->op==TK_NOTNULL ){ + int res = 0; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; + if( pLeft->op==TK_COLUMN && IsVirtual(pLeft->pTab) ){ + res++; } + if( pRight && pRight->op==TK_COLUMN && IsVirtual(pRight->pTab) ){ + res++; + SWAP(Expr*, pLeft, pRight); + } + *ppLeft = pLeft; + *ppRight = pRight; + if( pExpr->op==TK_NE ) *peOp2 = SQLITE_INDEX_CONSTRAINT_NE; + if( pExpr->op==TK_ISNOT ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOT; + if( pExpr->op==TK_NOTNULL ) *peOp2 = SQLITE_INDEX_CONSTRAINT_ISNOTNULL; + return res; } return 0; } @@ -1192,35 +1226,38 @@ static void exprAnalyze( ** virtual tables. The native query optimizer does not attempt ** to do anything with MATCH functions. */ - if( pWC->op==TK_AND && isMatchOfColumn(pExpr, &eOp2) ){ - int idxNew; + if( pWC->op==TK_AND ){ Expr *pRight, *pLeft; - WhereTerm *pNewTerm; - Bitmask prereqColumn, prereqExpr; - - pRight = pExpr->x.pList->a[0].pExpr; - pLeft = pExpr->x.pList->a[1].pExpr; - prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight); - prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft); - if( (prereqExpr & prereqColumn)==0 ){ - Expr *pNewExpr; - pNewExpr = sqlite3PExpr(pParse, TK_MATCH, - 0, sqlite3ExprDup(db, pRight, 0)); - if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ - ExprSetProperty(pNewExpr, EP_FromJoin); + int i; + int res = isMatchOfColumn(pExpr, &eOp2, &pLeft, &pRight); + for(i=0; ia[idxNew]; + pNewTerm->prereqRight = prereqExpr; + 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; + pNewTerm->prereqAll = pTerm->prereqAll; } - idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); - testcase( idxNew==0 ); - pNewTerm = &pWC->a[idxNew]; - pNewTerm->prereqRight = prereqExpr; - 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; - pNewTerm->prereqAll = pTerm->prereqAll; + SWAP(Expr*, pLeft, pRight); } } #endif /* SQLITE_OMIT_VIRTUALTABLE */ diff --git a/test/bestindex5.test b/test/bestindex5.test new file mode 100644 index 0000000000..237041f56f --- /dev/null +++ b/test/bestindex5.test @@ -0,0 +1,192 @@ +# 2017 September 10 +# +# 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. +# +#*********************************************************************** +# Test the virtual table interface. In particular the xBestIndex +# method. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set testprefix bestindex4 + +ifcapable !vtab { + finish_test + return +} + +#------------------------------------------------------------------------- +# Virtual table callback for a virtual table named $tbl. +# +proc vtab_cmd {method args} { + + set binops(ne) != + set binops(eq) = + set binops(isnot) "IS NOT" + set binops(is) "IS" + + set unops(isnotnull) "IS NOT NULL" + set unops(isnull) "IS NULL" + + set cols(0) a + set cols(1) b + set cols(2) c + + switch -- $method { + xConnect { + return "CREATE TABLE t1(a, b, c)" + } + + xBestIndex { + foreach {clist orderby mask} $args {} + + set cost 1000000.0 + set ret [list] + set str [list] + + set v 0 + for {set i 0} {$i < [llength $clist]} {incr i} { + array unset C + array set C [lindex $clist $i] + if {$C(usable)} { + if {[info exists binops($C(op))]} { + lappend ret omit $i + lappend str "$cols($C(column)) $binops($C(op)) %$v%" + incr v + set cost [expr $cost / 2] + } + if {[info exists unops($C(op))]} { + lappend ret omit $i + lappend str "$cols($C(column)) $unops($C(op))" + incr v + set cost [expr $cost / 2] + } + } + } + + lappend ret idxstr [join $str " AND "] + lappend ret cost $cost + return $ret + } + + xFilter { + set q [lindex $args 1] + set a [lindex $args 2] + for {set v 0} {$v < [llength $a]} {incr v} { + set val [lindex $a $v] + set q [string map [list %$v% '$val'] $q] + } + if {$q==""} { set q 1 } + lappend ::xFilterQueries "WHERE $q" + return [list sql "SELECT rowid, * FROM t1x WHERE $q"] + } + } + return "" +} + +proc vtab_simple {method args} { + switch -- $method { + xConnect { + return "CREATE TABLE t2(x)" + } + xBestIndex { + return [list cost 999999.0] + } + xFilter { + return [list sql "SELECT rowid, * FROM t2x"] + } + } + return "" +} + +register_tcl_module db + +proc do_vtab_query_test {tn query result} { + set ::xFilterQueries [list] + uplevel [list + do_test $tn [string map [list %QUERY% $query] { + set r [execsql {%QUERY%}] + set r [concat $::xFilterQueries $r] + set r + }] [list {*}$result] + ] +} + +do_execsql_test 1.0 { + CREATE VIRTUAL TABLE t1 USING tcl('vtab_cmd'); + CREATE TABLE t1x(a INTEGER, b TEXT, c REAL); + INSERT INTO t1x VALUES(1, 2, 3); + INSERT INTO t1x VALUES(4, 5, 6); + INSERT INTO t1x VALUES(7, 8, 9); + + CREATE VIRTUAL TABLE t2 USING tcl('vtab_simple'); + CREATE TABLE t2x(x INTEGER); + INSERT INTO t2x VALUES(1); +} + +do_vtab_query_test 1.1 { SELECT * FROM t1 WHERE a!='hello'; } { + "WHERE a != 'hello'" + 1 2 3.0 4 5 6.0 7 8 9.0 +} + +do_vtab_query_test 1.2.1 { SELECT * FROM t1 WHERE b!=8 } { + "WHERE b != '8'" + 1 2 3.0 4 5 6.0 +} +do_vtab_query_test 1.2.2 { SELECT * FROM t1 WHERE 8!=b } { + "WHERE b != '8'" + 1 2 3.0 4 5 6.0 +} + +do_vtab_query_test 1.3 { SELECT * FROM t1 WHERE c IS NOT 3 } { + "WHERE c IS NOT '3'" + 4 5 6.0 7 8 9.0 +} +do_vtab_query_test 1.3.2 { SELECT * FROM t1 WHERE 3 IS NOT c } { + "WHERE c IS NOT '3'" + 4 5 6.0 7 8 9.0 +} + +do_vtab_query_test 1.4.1 { SELECT * FROM t1, t2 WHERE x != a } { + "WHERE a != '1'" + 4 5 6.0 1 7 8 9.0 1 +} +do_vtab_query_test 1.4.2 { SELECT * FROM t1, t2 WHERE a != x } { + "WHERE a != '1'" + 4 5 6.0 1 7 8 9.0 1 +} + +do_vtab_query_test 1.5.1 { SELECT * FROM t1 WHERE a IS NOT NULL } { + "WHERE a IS NOT NULL" + 1 2 3.0 4 5 6.0 7 8 9.0 +} +do_vtab_query_test 1.5.2 { SELECT * FROM t1 WHERE NULL IS NOT a } { + "WHERE a IS NOT ''" + 1 2 3.0 4 5 6.0 7 8 9.0 +} + +do_vtab_query_test 1.6.1 { SELECT * FROM t1 WHERE a IS NULL } { + "WHERE a IS NULL" +} + +do_vtab_query_test 1.6.2 { SELECT * FROM t1 WHERE NULL IS a } { + "WHERE a IS ''" +} + +do_vtab_query_test 1.7.1 { SELECT * FROM t1 WHERE (a, b) IS (1, 2) } { + "WHERE a IS '1' AND b IS '2'" + 1 2 3.0 +} +do_vtab_query_test 1.7.2 { SELECT * FROM t1 WHERE (5, 4) IS (b, a) } { + {WHERE b IS '5' AND a IS '4'} + 4 5 6.0 +} + +finish_test +