diff --git a/manifest b/manifest index 679ecd4e0c..b5d0802698 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sthe\s"#/value-list/"\sstyle\sof\sresults\sfor\sapproximate\svalue\smatching\nin\sthe\sdo_test\scommand\sof\sthe\stest\sinfrastructure.\s\sUse\sthis\snew\sresult\sstyle\nto\smake\sthe\sSQLITE_DBSTATUS_CACHE_SIZE_SHARED\stests\scross-platform. -D 2016-07-09T17:47:01.013 +C Add\ssome\ssupport\sfor\susing\srow\svalue\sconstructors\sin\scertain\sparts\sof\sSQL\sexpressions.\sThere\sare\smany\sbugs\son\sthis\sbranch. +D 2016-07-09T20:23:55.716 F Makefile.in 6c20d44f72d4564f11652b26291a214c8367e5db F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434 F Makefile.msc d66d0395c38571aab3804f8db0fa20707ae4609a @@ -337,7 +337,7 @@ F src/ctime.c 61949e83c4c36e37195a8398ebc752780b534d95 F src/date.c 1cc9fb516ec9932c6fd4d2a0d2f8bc4480145c39 F src/dbstat.c 4f6f7f52b49beb9636ffbd517cfe44a402ba4ad0 F src/delete.c 4aba4214a377ce8ddde2d2e609777bcc8235200f -F src/expr.c 523a5b1db2b6d88c6eefb224877bf635a3bcfc92 +F src/expr.c 330854fe9fdea1d244abaef6d680f6b91df07cb4 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb F src/fkey.c bc4145347595b7770f9a598cff1c848302cf5413 F src/func.c 61a4114cf7004f10c542cfabbab9f2bcb9033045 @@ -372,7 +372,7 @@ F src/os_win.c 520f23475f1de530c435d30b67b7b15fe90874b0 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/pager.c c368634b888b1c8740aea83b36bfd266f2443e60 F src/pager.h 031a87445e5e0afc85312d1c380e123ad6c7aeaf -F src/parse.y f374ab20106362eb3f5c01b3e6a002f0bbead7ff +F src/parse.y fa040d742fe4922b219143fe2e04f74061daabcb F src/pcache.c 5583c8ade4b05075a60ba953ef471d1c1a9c05df F src/pcache.h 2cedcd8407eb23017d92790b112186886e179490 F src/pcache1.c 7f51d2b541aab57596adf62db2c4bb025d34f04d @@ -381,14 +381,14 @@ F src/pragma.h 64c78a648751b9f4f297276c4eb7507b14b4628c F src/prepare.c 22df6171aec1d86904ed2ad30c2348a5748aa04e F src/printf.c a5f0ca08ddede803c241266abb46356ec748ded1 F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 -F src/resolve.c cca3aa77b95706df5d635a2141a4d1de60ae6598 +F src/resolve.c 9680caadd54772699e5a0a8ebd680e014703e4ee F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac -F src/select.c f3c6e9065fb34f6a23af27ec7f1f717ffbfc2ee4 +F src/select.c 0115f5d222f5cf9b5511ec4072088417354d738a F src/shell.c 14ff7f660530a52b117d110ba3390b7b2eb719b6 F src/sqlite.h.in b9ba728c1083b7a8ab5f6a628b25cd2a00325fbf F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 2a170163d121095c6ab1ef05ed0413722f391d01 -F src/sqliteInt.h dcf43b8abc5605b70f54ba80f42b6ad054b8ba95 +F src/sqliteInt.h dd2dd1d880ffd33137d20dc6da21f169836b8f5a F src/sqliteLimit.h c0373387c287c8d0932510b5547ecde31b5da247 F src/status.c 5b18f9526900f61189ab0b83f1ef41d9f871a2ab F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9 @@ -449,7 +449,7 @@ F src/update.c 4f05ea8cddfa367d045e03589756c02199e8f9bd F src/utf.c 699001c79f28e48e9bcdf8a463da029ea660540c F src/util.c 810ec3f22e2d1b62e66c30fe3621ebdedd23584d F src/vacuum.c feb1eabb20987983d9350cad98299b21fa811f52 -F src/vdbe.c 22b46c3b725e950e9f2760e2d76953d592600ad4 +F src/vdbe.c 680c118a20b4b496644001e7ff4819c3e3ff8d85 F src/vdbe.h 67bc551f7faf04c33493892e4b378aada823ed10 F src/vdbeInt.h c59381049af5c7751a83456c39b80d1a6fde1f9d F src/vdbeapi.c 02bcbc2ca5d2004b029088b05b468b394881e103 @@ -463,10 +463,10 @@ F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 F src/wal.c 02eeecc265f6ffd0597378f5d8ae9070b62a406a F src/wal.h 6dd221ed384afdc204bc61e25c23ef7fd5a511f2 F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354 -F src/where.c 48eed8ebe319c6cbc7bf7682018f32af0f5189f5 -F src/whereInt.h e5b939701a7ceffc5a3a8188a37f9746416ebcd0 -F src/wherecode.c e20cb381ff621e56a4684c71e31999aca2547ca6 -F src/whereexpr.c d7dcbf14ce1b5876c1f76496162c30fcba669563 +F src/where.c 898a45969bae1298cbaaaf05e6aeacfb45971293 +F src/whereInt.h 1ad3be2a43cb821418e1100476a3a14cd57635c4 +F src/wherecode.c a66c589bdcaa9da45d9576d54663666083e0c109 +F src/whereexpr.c d88ee6ce356cb9fd986d0e81249a2cd66a513093 F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2 @@ -1017,6 +1017,8 @@ F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d +F test/rowvalue.test 979738b3d49f1d93e3fee56a71d4446217917abc +F test/rowvalue2.test 8d5dfe75b8f4d1868a2f91f0356f20d36cba64ff F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d @@ -1419,7 +1421,7 @@ F test/zerodamage.test e59a56443d6298ecf7435f618f0b27654f0c849e F tool/GetFile.cs a15e08acb5dd7539b75ba23501581d7c2b462cb5 F tool/GetTclKit.bat 629d87562e0487c386db630033931d12d62e6372 F tool/Replace.cs 02c67258801c2fb5f63231e0ac0f220b4b36ba91 -F tool/addopcodes.tcl 2b089684eb8b7d0db64cf9d8e6d2fe1b6d279e8d +F tool/addopcodes.tcl 7d4954564d7d4bfde5ba139ebced25542696fa52 F tool/build-all-msvc.bat 3e4e4043b53f1aede4308e0d2567bbd773614630 x F tool/build-shell.sh 950f47c6174f1eea171319438b93ba67ff5bf367 F tool/cg_anno.tcl 692ce4b8693d59e3a3de77ca97f4139ecfa641b0 x @@ -1505,7 +1507,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 29fb988f1afc3fe623097acee1a5d08bf8386626 -R 767a7d8abd9b9525d1b45069b4c3dba2 -U drh -Z df36acb2648e26f22b53c52b73476f6c +P c869bf34a8ee42ac6542862e59c7a4b89b042f79 +R e70f91ca2d7d2c655198f54d385cd62e +T *branch * rowvalue +T *sym-rowvalue * +T -sym-trunk * +U dan +Z e86f169a98e6cbe5718a428ba6e6f5e0 diff --git a/manifest.uuid b/manifest.uuid index d451076edf..8f187444bd 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -c869bf34a8ee42ac6542862e59c7a4b89b042f79 \ No newline at end of file +b2204215b231202aef7a218411cc2ddaecf28f35 \ No newline at end of file diff --git a/src/expr.c b/src/expr.c index ce3a476561..7941c228fd 100644 --- a/src/expr.c +++ b/src/expr.c @@ -309,6 +309,114 @@ static int codeCompare( return addr; } +int sqlite3ExprVectorSize(Expr *pExpr){ + if( (pExpr->flags & EP_Vector)==0 ) return 1; + if( pExpr->flags & EP_xIsSelect ){ + return pExpr->x.pSelect->pEList->nExpr; + } + return pExpr->x.pList->nExpr; +} + +static Expr *exprVectorField(Expr *pVector, int i){ + if( pVector->flags & EP_xIsSelect ){ + return pVector->x.pSelect->pEList->a[i].pExpr; + } + return pVector->x.pList->a[i].pExpr; +} + +static void codeVectorCompare(Parse *pParse, Expr *pExpr, int dest){ + Vdbe *v = pParse->pVdbe; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; + int nLeft = sqlite3ExprVectorSize(pLeft); + int nRight = sqlite3ExprVectorSize(pRight); + int addr = sqlite3VdbeMakeLabel(v); + + /* Check that both sides of the comparison are vectors, and that + ** both are the same length. */ + if( nLeft!=nRight ){ + sqlite3ErrorMsg(pParse, "invalid use of row value"); + }else{ + int p5 = (pExpr->op==TK_IS || pExpr->op==TK_ISNOT) ? SQLITE_NULLEQ : 0; + int opCmp; + int opTest; + int i; + int p3 = 1; + int regLeft = 0; + int regRight = 0; + + assert( pExpr->op==TK_EQ || pExpr->op==TK_NE + || pExpr->op==TK_IS || pExpr->op==TK_ISNOT + || pExpr->op==TK_LT || pExpr->op==TK_GT + || pExpr->op==TK_LE || pExpr->op==TK_GE + ); + + switch( pExpr->op ){ + case TK_EQ: + case TK_IS: + opTest = OP_IfNot; + opCmp = OP_Eq; + break; + + case TK_NE: + case TK_ISNOT: + opTest = OP_If; + opCmp = OP_Ne; + break; + + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + opCmp = OP_Cmp; + opTest = OP_CmpTest; + p3 = pExpr->op; + break; + } + + if( pLeft->flags & EP_xIsSelect ){ + assert( pLeft->op==TK_SELECT || pLeft->op==TK_REGISTER ); + regLeft = sqlite3ExprCodeTarget(pParse, pLeft, 1); + assert( regLeft!=1 ); + } + if( pRight->flags & EP_xIsSelect ){ + assert( pRight->op==TK_SELECT || pRight->op==TK_REGISTER ); + regRight = sqlite3ExprCodeTarget(pParse, pRight, 1); + assert( regRight!=1 ); + } + if( pParse->nErr ) return; + + for(i=0; ix.pSelect->pEList->a[i].pExpr; + r1 = regLeft+i; + }else{ + pL = pLeft->x.pList->a[i].pExpr; + r1 = sqlite3ExprCodeTemp(pParse, pL, ®Free1); + } + + if( regRight ){ + pR = pRight->x.pSelect->pEList->a[i].pExpr; + r2 = regRight+i; + }else{ + pR = pRight->x.pList->a[i].pExpr; + r2 = sqlite3ExprCodeTemp(pParse, pR, ®Free1); + } + + codeCompare(pParse, pL, pR, opCmp, r1, r2, dest, SQLITE_STOREP2 | p5); + sqlite3VdbeAddOp3(v, opTest, dest, addr, p3); + sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ReleaseTempReg(pParse, regFree2); + } + } + + sqlite3VdbeResolveLabel(v, addr); +} + #if SQLITE_MAX_EXPR_DEPTH>0 /* ** Check that argument nHeight is less than or equal to the maximum @@ -743,7 +851,7 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ if( !ExprHasProperty(p, EP_TokenOnly) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ assert( p->x.pList==0 || p->pRight==0 ); - sqlite3ExprDelete(db, p->pLeft); + if( p->op!=TK_SELECT_COLUMN ) sqlite3ExprDelete(db, p->pLeft); sqlite3ExprDelete(db, p->pRight); if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); if( ExprHasProperty(p, EP_xIsSelect) ){ @@ -1835,7 +1943,7 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ /* If no preexisting index is available for the IN clause ** and IN_INDEX_NOOP is an allowed reply ** and the RHS of the IN operator is a list, not a subquery - ** and the RHS is not contant or has two or fewer terms, + ** and the RHS is not constant or has two or fewer terms, ** then it is not worth creating an ephemeral table to evaluate ** the IN operator so return IN_INDEX_NOOP. */ @@ -1872,6 +1980,30 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ } #endif +static char *exprINAffinity(Parse *pParse, Expr *pExpr){ + Expr *pLeft = pExpr->pLeft; + int nVal = sqlite3ExprVectorSize(pLeft); + char *zRet; + + zRet = sqlite3DbMallocZero(pParse->db, nVal+1); + if( zRet ){ + int i; + for(i=0; ix.pSelect->pEList->a[i].pExpr, a); + } + zRet[nVal] = '\0'; + } + return zRet; +} + /* ** Generate code for scalar subqueries used as a subquery expression, EXISTS, ** or IN operators. Examples: @@ -1939,12 +2071,12 @@ int sqlite3CodeSubselect( switch( pExpr->op ){ case TK_IN: { - char affinity; /* Affinity of the LHS of the IN */ int addr; /* Address of OP_OpenEphemeral instruction */ Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */ KeyInfo *pKeyInfo = 0; /* Key information */ - - affinity = sqlite3ExprAffinity(pLeft); + int nVal; /* Size of vector pLeft */ + + nVal = sqlite3ExprVectorSize(pLeft); /* Whether this is an 'x IN(SELECT...)' or an 'x IN()' ** expression it is handled the same way. An ephemeral table is @@ -1960,8 +2092,9 @@ int sqlite3CodeSubselect( ** is used. */ pExpr->iTable = pParse->nTab++; - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, !isRowid); - pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, 1, 1); + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, + pExpr->iTable, (isRowid?0:nVal)); + pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, nVal, 1); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* Case 1: expr IN (SELECT ...) @@ -1970,27 +2103,38 @@ int sqlite3CodeSubselect( ** table allocated and opened above. */ Select *pSelect = pExpr->x.pSelect; - SelectDest dest; - ExprList *pEList; + ExprList *pEList = pSelect->pEList; assert( !isRowid ); - sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); - dest.affSdst = (u8)affinity; - assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); - pSelect->iLimit = 0; - testcase( pSelect->selFlags & SF_Distinct ); - testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ - if( sqlite3Select(pParse, pSelect, &dest) ){ - sqlite3KeyInfoUnref(pKeyInfo); - return 0; + if( pEList->nExpr!=nVal ){ + sqlite3ErrorMsg(pParse, "SELECT has %d columns - expected %d", + pEList->nExpr, nVal); + }else{ + SelectDest dest; + int i; + sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); + dest.zAffSdst = exprINAffinity(pParse, pExpr); + assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); + pSelect->iLimit = 0; + testcase( pSelect->selFlags & SF_Distinct ); + testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ + if( sqlite3Select(pParse, pSelect, &dest) ){ + sqlite3DbFree(pParse->db, dest.zAffSdst); + sqlite3KeyInfoUnref(pKeyInfo); + return 0; + } + sqlite3DbFree(pParse->db, dest.zAffSdst); + assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ + assert( pEList!=0 ); + assert( pEList->nExpr>0 ); + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); + for(i=0; i1) ? exprVectorField(pLeft, i) : pLeft; + pKeyInfo->aColl[i] = sqlite3BinaryCompareCollSeq( + pParse, p, pEList->a[i].pExpr + ); + } } - pEList = pSelect->pEList; - assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ - assert( pEList!=0 ); - assert( pEList->nExpr>0 ); - assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); - pKeyInfo->aColl[0] = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, - pEList->a[0].pExpr); }else if( ALWAYS(pExpr->x.pList!=0) ){ /* Case 2: expr IN (exprlist) ** @@ -1999,11 +2143,13 @@ int sqlite3CodeSubselect( ** that columns affinity when building index keys. If is not ** a column, use numeric affinity. */ + char affinity; /* Affinity of the LHS of the IN */ int i; ExprList *pList = pExpr->x.pList; struct ExprList_item *pItem; int r1, r2, r3; + affinity = sqlite3ExprAffinity(pLeft); if( !affinity ){ affinity = SQLITE_AFF_BLOB; } @@ -2067,18 +2213,22 @@ int sqlite3CodeSubselect( */ Select *pSel; /* SELECT statement to encode */ SelectDest dest; /* How to deal with SELECt result */ + int nReg; /* Registers to allocate */ testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + pSel = pExpr->x.pSelect; - sqlite3SelectDestInit(&dest, 0, ++pParse->nMem); + nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1; + sqlite3SelectDestInit(&dest, 0, pParse->nMem+1); + pParse->nMem += nReg; if( pExpr->op==TK_SELECT ){ dest.eDest = SRT_Mem; dest.iSdst = dest.iSDParm; - sqlite3VdbeAddOp2(v, OP_Null, 0, dest.iSDParm); + dest.nSdst = nReg; + sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1); VdbeComment((v, "Init subquery result")); }else{ dest.eDest = SRT_Exists; @@ -2112,6 +2262,75 @@ int sqlite3CodeSubselect( } #endif /* SQLITE_OMIT_SUBQUERY */ +#ifndef SQLITE_OMIT_SUBQUERY +void exprCodeVectorIN( + Parse *pParse, /* Parsing and code generating context */ + Expr *pExpr, /* The IN expression */ + int destIfFalse, /* Jump here if LHS is not contained in the RHS */ + int destIfNull /* Jump here if the results are unknown due to NULLs */ +){ + int i; + int addrNext; + int iSkip; + int r1; + int r2 = sqlite3GetTempReg(pParse); + int r3 = sqlite3GetTempReg(pParse); + int r4 = sqlite3GetTempReg(pParse); + int regResult = sqlite3GetTempReg(pParse); + int nVal = sqlite3ExprVectorSize(pExpr->pLeft); + + Expr *pLeft = pExpr->pLeft; + Vdbe *v = pParse->pVdbe; + + /* Code the LHS, the from " IN (...)". Leave the results in + ** an array of nVal registers starting at r1. */ + sqlite3ExprCachePush(pParse); + if( pLeft->flags & EP_xIsSelect ){ + r1 = sqlite3CodeSubselect(pParse, pLeft, 0, 0); + }else{ + r1 = pParse->nMem + 1; + pParse->nMem += nVal; + sqlite3ExprCodeExprList(pParse, pLeft->x.pList, r1, 0, 0); + } + + /* Generate an epheremal index containing the contents of the SELECT + ** to the right of the " IN (SELECT ...)" expression. The cursor + ** number for the epheremal table is left in pExpr->iTable. */ + assert( pExpr->flags & EP_xIsSelect ); + sqlite3CodeSubselect(pParse, pExpr, 0, 0); + + sqlite3VdbeAddOp2(v, OP_Integer, 0, regResult); + + /* Iterate through the ephemeral table just populated */ + addrNext = 1 + sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse); + for(i=0; iiTable, i, r2); + sqlite3VdbeAddOp4(v, OP_Eq, r1+i, i==0?r3:r4, r2, (void*)pColl,P4_COLLSEQ); + sqlite3VdbeChangeP5(v, SQLITE_STOREP2); + VdbeCoverage(v); + if( i!=0 ){ + sqlite3VdbeAddOp3(v, OP_And, r3, r4, r4); + } + } + sqlite3VdbeAddOp2(v, OP_If, r4, sqlite3VdbeCurrentAddr(v)+6); + sqlite3VdbeAddOp2(v, OP_IfNot, r4, sqlite3VdbeCurrentAddr(v)+2); + sqlite3VdbeAddOp2(v, OP_Null, 0, regResult); + sqlite3VdbeAddOp2(v, OP_Next, pExpr->iTable, addrNext); + sqlite3VdbeAddOp3(v, OP_If, regResult, destIfNull, 1); + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); + + sqlite3ReleaseTempReg(pParse, r2); + sqlite3ReleaseTempReg(pParse, r3); + sqlite3ReleaseTempReg(pParse, r4); + sqlite3ReleaseTempReg(pParse, regResult); + sqlite3ExprCachePop(pParse); +} +#endif + #ifndef SQLITE_OMIT_SUBQUERY /* ** Generate code for an IN expression. @@ -2142,6 +2361,10 @@ static void sqlite3ExprCodeIN( int r1; /* Temporary use register */ Vdbe *v; /* Statement under construction */ + if( pExpr->pLeft->flags & EP_Vector ){ + return exprCodeVectorIN(pParse, pExpr, destIfFalse, destIfNull); + } + /* Compute the RHS. After this step, the table with cursor ** pExpr->iTable will contains the values that make up the RHS. */ @@ -2669,6 +2892,8 @@ static void exprToRegister(Expr *p, int iReg){ ExprClearProperty(p, EP_Skip); } +static void exprCodeBetween(Parse*,Expr*,int,void(*)(Parse*,Expr*,int,int),int); + /* ** Generate code into the current Vdbe to evaluate the given ** expression. Attempt to store the results in register "target". @@ -2689,6 +2914,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ int r1, r2, r3, r4; /* Various register numbers */ sqlite3 *db = pParse->db; /* The database connection */ Expr tempX; /* Temporary expression node */ + int p5 = 0; assert( target>0 && target<=pParse->nMem ); if( v==0 ){ @@ -2801,39 +3027,34 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ break; } #endif /* SQLITE_OMIT_CAST */ + case TK_IS: + case TK_ISNOT: + op = (op==TK_IS) ? TK_EQ : TK_NE; + p5 = SQLITE_NULLEQ; + /* fall-through */ case TK_LT: case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2); - assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); - assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); - assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); - assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); - assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); - assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - break; - } - case TK_IS: - case TK_ISNOT: { - testcase( op==TK_IS ); - testcase( op==TK_ISNOT ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - op = (op==TK_IS) ? TK_EQ : TK_NE; - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2 | SQLITE_NULLEQ); - VdbeCoverageIf(v, op==TK_EQ); - VdbeCoverageIf(v, op==TK_NE); - testcase( regFree1==0 ); - testcase( regFree2==0 ); + Expr *pLeft = pExpr->pLeft; + if( (pLeft->flags & EP_Vector) ){ + codeVectorCompare(pParse, pExpr, target); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + codeCompare(pParse, pLeft, pExpr->pRight, op, + r1, r2, inReg, SQLITE_STOREP2 | p5); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + testcase( regFree1==0 ); + testcase( regFree2==0 ); + } break; } case TK_AND: @@ -3085,6 +3306,8 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ ** Z is stored in pExpr->pList->a[1].pExpr. */ case TK_BETWEEN: { + exprCodeBetween(pParse, pExpr, target, 0, 0); +#if 0 Expr *pLeft = pExpr->pLeft; struct ExprList_item *pLItem = pExpr->x.pList->a; Expr *pRight = pLItem->pExpr; @@ -3107,6 +3330,7 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ sqlite3VdbeAddOp3(v, OP_And, r3, r4, target); sqlite3ReleaseTempReg(pParse, r3); sqlite3ReleaseTempReg(pParse, r4); +#endif break; } case TK_SPAN: @@ -3172,6 +3396,22 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){ break; } + case TK_VECTOR: { + sqlite3ErrorMsg(pParse, "invalid use of row value (1)"); + break; + } + + case TK_SELECT_COLUMN: { + Expr *pLeft = pExpr->pLeft; + assert( pLeft ); + assert( pLeft->op==TK_SELECT || pLeft->op==TK_REGISTER ); + if( pLeft->op==TK_SELECT ){ + pLeft->iTable = sqlite3CodeSubselect(pParse, pLeft, 0, 0); + pLeft->op = TK_REGISTER; + } + inReg = pLeft->iTable + pExpr->iColumn; + break; + } /* ** Form A: @@ -3500,7 +3740,7 @@ static void exprCodeBetween( Parse *pParse, /* Parsing and code generating context */ Expr *pExpr, /* The BETWEEN expression */ int dest, /* Jump here if the jump is taken */ - int jumpIfTrue, /* Take the jump if the BETWEEN is true */ + void (*xJumpIf)(Parse*,Expr*,int,int), int jumpIfNull /* Take the jump if the BETWEEN is NULL */ ){ Expr exprAnd; /* The AND operator in x>=y AND x<=z */ @@ -3509,6 +3749,10 @@ static void exprCodeBetween( Expr exprX; /* The x subexpression */ int regFree1 = 0; /* Temporary use register */ + memset(&compLeft, 0, sizeof(Expr)); + memset(&compRight, 0, sizeof(Expr)); + memset(&exprAnd, 0, sizeof(Expr)); + assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); exprX = *pExpr->pLeft; exprAnd.op = TK_AND; @@ -3520,11 +3764,14 @@ static void exprCodeBetween( compRight.op = TK_LE; compRight.pLeft = &exprX; compRight.pRight = pExpr->x.pList->a[1].pExpr; - exprToRegister(&exprX, sqlite3ExprCodeTemp(pParse, &exprX, ®Free1)); - if( jumpIfTrue ){ - sqlite3ExprIfTrue(pParse, &exprAnd, dest, jumpIfNull); + if( (exprX.flags & EP_Vector)==0 ){ + exprToRegister(&exprX, sqlite3ExprCodeTemp(pParse, &exprX, ®Free1)); + } + if( xJumpIf ){ + xJumpIf(pParse, &exprAnd, dest, jumpIfNull); }else{ - sqlite3ExprIfFalse(pParse, &exprAnd, dest, jumpIfNull); + exprX.flags |= EP_FromJoin; + sqlite3ExprCodeTarget(pParse, &exprAnd, dest); } sqlite3ReleaseTempReg(pParse, regFree1); @@ -3564,7 +3811,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ if( NEVER(pExpr==0) ) return; /* No way this can happen */ op = pExpr->op; - switch( op ){ + switch( op | (pExpr->pLeft ? (pExpr->pLeft->flags & EP_Vector) : 0)){ case TK_AND: { int d2 = sqlite3VdbeMakeLabel(v); testcase( jumpIfNull==0 ); @@ -3633,7 +3880,7 @@ void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } case TK_BETWEEN: { testcase( jumpIfNull==0 ); - exprCodeBetween(pParse, pExpr, dest, 1, jumpIfNull); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfTrue, jumpIfNull); break; } #ifndef SQLITE_OMIT_SUBQUERY @@ -3716,7 +3963,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ assert( pExpr->op!=TK_GT || op==OP_Le ); assert( pExpr->op!=TK_GE || op==OP_Lt ); - switch( pExpr->op ){ + switch( pExpr->op | (pExpr->pLeft ? (pExpr->pLeft->flags & EP_Vector) : 0)){ case TK_AND: { testcase( jumpIfNull==0 ); sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); @@ -3783,7 +4030,7 @@ void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int jumpIfNull){ } case TK_BETWEEN: { testcase( jumpIfNull==0 ); - exprCodeBetween(pParse, pExpr, dest, 0, jumpIfNull); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfFalse, jumpIfNull); break; } #ifndef SQLITE_OMIT_SUBQUERY diff --git a/src/parse.y b/src/parse.y index 8a65131e28..b107057fe0 100644 --- a/src/parse.y +++ b/src/parse.y @@ -217,6 +217,7 @@ columnname(A) ::= nm(A) typetoken(Y). {sqlite3AddColumn(pParse,&A,&Y);} %left CONCAT. %left COLLATE. %right BITNOT. +%right VECTOR. // An IDENTIFIER can be a generic identifier, or one of several // keywords. Any non-standard keyword can also be an identifier. @@ -946,6 +947,14 @@ term(A) ::= CTIME_KW(OP). { } } +expr(A) ::= LP(L) nexprlist(X) COMMA expr(Y) RP(R). { + A.pExpr = sqlite3PExpr(pParse, TK_VECTOR, 0, 0, 0); + if( A.pExpr ){ + A.pExpr->x.pList = sqlite3ExprListAppend(pParse, X, Y.pExpr); + spanSet(&A, &L, &R); + } +} + expr(A) ::= expr(A) AND(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);} expr(A) ::= expr(A) OR(OP) expr(Y). {spanBinaryExpr(pParse,@OP,&A,&Y);} expr(A) ::= expr(A) LT|GT|GE|LE(OP) expr(Y). diff --git a/src/resolve.c b/src/resolve.c index 77ce37f6d7..cd3d714613 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -765,6 +765,17 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ExprSetProperty(pExpr, EP_VarSelect); pNC->ncFlags |= NC_VarSelect; } + + if( pExpr->op==TK_SELECT && pExpr->x.pSelect->pEList->nExpr>1 ){ + if( !ExprHasProperty(pExpr, EP_VectorOk) ){ + sqlite3ErrorMsg(pParse, "invalid use of row value"); + }else{ + ExprSetProperty(pExpr, EP_Vector); + } + } + if( pExpr->op==TK_IN ){ + ExprSetProperty(pExpr->pLeft, EP_VectorOk); + } } break; } @@ -772,6 +783,30 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr); break; } + + case TK_BETWEEN: { + ExprSetProperty(pExpr->pLeft, EP_VectorOk); + ExprSetProperty(pExpr->x.pList->a[0].pExpr, EP_VectorOk); + ExprSetProperty(pExpr->x.pList->a[1].pExpr, EP_VectorOk); + break; + } + + case TK_EQ: case TK_NE: case TK_IS: case TK_ISNOT: + case TK_LE: case TK_LT: case TK_GE: case TK_GT: + { + ExprSetProperty(pExpr->pLeft, EP_VectorOk); + ExprSetProperty(pExpr->pRight, EP_VectorOk); + break; + }; + + case TK_VECTOR: { + if( !ExprHasProperty(pExpr, EP_VectorOk) ){ + sqlite3ErrorMsg(pParse, "invalid use of row value"); + }else{ + ExprSetProperty(pExpr, EP_Vector); + } + break; + } } return (pParse->nErr || pParse->db->mallocFailed) ? WRC_Abort : WRC_Continue; } diff --git a/src/select.c b/src/select.c index e21ac57dc5..f49a5c1fed 100644 --- a/src/select.c +++ b/src/select.c @@ -88,7 +88,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){ pDest->eDest = (u8)eDest; pDest->iSDParm = iParm; - pDest->affSdst = 0; + pDest->zAffSdst = 0; pDest->iSdst = 0; pDest->nSdst = 0; } @@ -673,7 +673,7 @@ static int checkForMultiColumnSelectError( int nExpr /* Number of result columns returned by SELECT */ ){ int eDest = pDest->eDest; - if( nExpr>1 && (eDest==SRT_Mem || eDest==SRT_Set) ){ + if( 0 && nExpr>1 && eDest==SRT_Set ){ sqlite3ErrorMsg(pParse, "only a single result allowed for " "a SELECT that is part of an expression"); return 1; @@ -892,9 +892,6 @@ static void selectInnerLoop( ** item into the set table with bogus data. */ case SRT_Set: { - assert( nResultCol==1 ); - pDest->affSdst = - sqlite3CompareAffinity(pEList->a[0].pExpr, pDest->affSdst); if( pSort ){ /* At first glance you would think we could optimize out the ** ORDER BY in this case since the order of entries in the set @@ -903,8 +900,10 @@ static void selectInnerLoop( pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg); }else{ int r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult,1,r1, &pDest->affSdst, 1); - sqlite3ExprCacheAffinityChange(pParse, regResult, 1); + assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, + r1, pDest->zAffSdst, 1); + sqlite3ExprCacheAffinityChange(pParse, regResult, nResultCol); sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); sqlite3ReleaseTempReg(pParse, r1); } @@ -924,9 +923,9 @@ static void selectInnerLoop( ** of the scan loop. */ case SRT_Mem: { - assert( nResultCol==1 ); + assert( nResultCol==pDest->nSdst ); if( pSort ){ - pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg); + pushOntoSorter(pParse, pSort, p, regResult, regResult, nResultCol, nPrefixReg); }else{ assert( regResult==iParm ); /* The LIMIT clause will jump out of the loop for us */ @@ -1241,7 +1240,7 @@ static void generateSortTail( sqlite3VdbeResolveLabel(v, pSort->labelBkOut); } iTab = pSort->iECursor; - if( eDest==SRT_Output || eDest==SRT_Coroutine ){ + if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){ regRowid = 0; regRow = pDest->iSdst; nSortData = nColumn; @@ -1283,16 +1282,15 @@ static void generateSortTail( } #ifndef SQLITE_OMIT_SUBQUERY case SRT_Set: { - assert( nColumn==1 ); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, 1, regRowid, - &pDest->affSdst, 1); - sqlite3ExprCacheAffinityChange(pParse, regRow, 1); + assert( nColumn==sqlite3Strlen30(pDest->zAffSdst) ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn, regRowid, + pDest->zAffSdst, 1); + sqlite3ExprCacheAffinityChange(pParse, regRow, nColumn); sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRowid); break; } case SRT_Mem: { - assert( nColumn==1 ); - sqlite3ExprCodeMove(pParse, regRow, iParm, 1); + /* sqlite3ExprCodeMove(pParse, regRow, iParm, nColumn); */ /* The LIMIT clause will terminate the loop for us */ break; } @@ -2659,10 +2657,9 @@ static int generateOutputSubroutine( case SRT_Set: { int r1; assert( pIn->nSdst==1 || pParse->nErr>0 ); - pDest->affSdst = - sqlite3CompareAffinity(p->pEList->a[0].pExpr, pDest->affSdst); r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, 1, r1, &pDest->affSdst,1); + sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, + r1, pDest->zAffSdst,1); sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, 1); sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, r1); sqlite3ReleaseTempReg(pParse, r1); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 998a17dac3..3caa65d8e3 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2317,6 +2317,8 @@ struct Expr { #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ #define EP_Alias 0x400000 /* Is an alias for a result set column */ +#define EP_VectorOk 0x800000 /* This expression may be a row value */ +#define EP_Vector 0x1000000/* This expression is a row value */ /* ** Combinations of two or more EP_* flags @@ -2762,7 +2764,7 @@ struct Select { */ struct SelectDest { u8 eDest; /* How to dispose of the results. On of SRT_* above. */ - char affSdst; /* Affinity used when eDest==SRT_Set */ + char *zAffSdst; /* Affinity used when eDest==SRT_Set */ int iSDParm; /* A parameter used by the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ @@ -4237,4 +4239,6 @@ int sqlite3ThreadJoin(SQLiteThread*, void**); int sqlite3DbstatRegister(sqlite3*); #endif +int sqlite3ExprVectorSize(Expr *pExpr); + #endif /* SQLITEINT_H */ diff --git a/src/vdbe.c b/src/vdbe.c index 6adbcbbec1..3f2fdf9cff 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -1958,6 +1958,7 @@ case OP_Cast: { /* in1 */ ** the content of register P3 is greater than or equal to the content of ** register P1. See the Lt opcode for additional information. */ +case OP_Cmp: /* in1, in3 */ case OP_Eq: /* same as TK_EQ, jump, in1, in3 */ case OP_Ne: /* same as TK_NE, jump, in1, in3 */ case OP_Lt: /* same as TK_LT, jump, in1, in3 */ @@ -2056,7 +2057,8 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ case OP_Lt: res = res<0; break; case OP_Le: res = res<=0; break; case OP_Gt: res = res>0; break; - default: res = res>=0; break; + case OP_Ge: res = res>=0; break; + default: assert( pOp->opcode==OP_Cmp ); break; } /* Undo any changes made by applyAffinity() to the input registers. */ @@ -2072,6 +2074,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ pOut->u.i = res; REGISTER_TRACE(pOp->p2, pOut); }else{ + assert( pOp->opcode!=OP_Cmp ); VdbeBranchTaken(res!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); if( res ){ goto jump_to_p2; @@ -3867,7 +3870,42 @@ seek_not_found: } break; } + +/* Opcode: CmpTest P1 P2 P3 * * +** +** P2 is a jump destination. Register P1 is guaranteed to contain either +** an integer value or a NULL. The jump is taken if P1 contains any value +** other than 0 (i.e. NULL does cause a jump). +** +** If P1 is not NULL, its value is modified to integer value 0 or 1 +** according to the value of the P3 operand: +** +** P3 modification +** -------------------------- +** OP_Lt (P1 = (P1 < 0)) +** OP_Le (P1 = (P1 <= 0)) +** OP_Gt (P1 = (P1 > 0)) +** OP_Ge (P1 = (P1 >= 0)) +*/ +case OP_CmpTest: { /* in1, jump */ + int bJump; + pIn1 = &aMem[pOp->p1]; + if( (pIn1->flags & MEM_Int) ){ + bJump = (pIn1->u.i!=0); + switch( pOp->p3 ){ + case OP_Lt: pIn1->u.i = (pIn1->u.i < 0); break; + case OP_Le: pIn1->u.i = (pIn1->u.i <= 0); break; + case OP_Gt: pIn1->u.i = (pIn1->u.i > 0); break; + default: assert( pOp->p3==OP_Ge ); pIn1->u.i = (pIn1->u.i >= 0); break; + } + }else{ + bJump = 1; + } + + if( bJump ) goto jump_to_p2; + break; +} /* Opcode: Found P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] diff --git a/src/where.c b/src/where.c index a65f30968e..36bf98b444 100644 --- a/src/where.c +++ b/src/where.c @@ -2187,6 +2187,51 @@ static void whereLoopOutputAdjust( if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; } +/* +** Term pTerm is a vector range comparison operation. The first comparison +** in the vector can be optimized using column nEq of the index. +*/ +int whereRangeVectorLen( + Parse *pParse, int iCur, Index *pIdx, int nEq, WhereTerm *pTerm +){ + int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft); + int i; + + nCmp = MIN(nCmp, (pIdx->nColumn - nEq)); + for(i=1; ipExpr->pLeft->x.pList->a[i].pExpr; + Expr *pRhs = pTerm->pExpr->pRight; + if( pRhs->flags & EP_xIsSelect ){ + pRhs = pRhs->x.pSelect->pEList->a[i].pExpr; + }else{ + pRhs = pRhs->x.pList->a[i].pExpr; + } + + /* Check that the LHS of the comparison is a column reference to + ** the right column of the right source table. + */ + if( pLhs->op!=TK_COLUMN + || pLhs->iTable!=iCur + || pLhs->iColumn!=pIdx->aiColumn[i+nEq] + ){ + break; + } + + aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs)); + idxaff = pIdx->pTable->aCol[pLhs->iColumn].affinity; + if( aff!=idxaff ) break; + + pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; + } + return i; +} + /* ** Adjust the cost C by the costMult facter T. This only occurs if ** compiled with -DSQLITE_ENABLE_COSTMULT @@ -2225,6 +2270,8 @@ static int whereLoopAddBtreeIndex( Bitmask saved_prereq; /* Original value of pNew->prereq */ u16 saved_nLTerm; /* Original value of pNew->nLTerm */ u16 saved_nEq; /* Original value of pNew->u.btree.nEq */ + u16 saved_nBtm; /* Original value of pNew->u.btree.nBtm */ + u16 saved_nTop; /* Original value of pNew->u.btree.nTop */ u16 saved_nSkip; /* Original value of pNew->nSkip */ u32 saved_wsFlags; /* Original value of pNew->wsFlags */ LogEst saved_nOut; /* Original value of pNew->nOut */ @@ -2241,6 +2288,7 @@ static int whereLoopAddBtreeIndex( if( pNew->wsFlags & WHERE_BTM_LIMIT ){ opMask = WO_LT|WO_LE; }else{ + assert( pNew->u.btree.nBtm==0 ); opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS; } if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); @@ -2248,6 +2296,8 @@ static int whereLoopAddBtreeIndex( assert( pNew->u.btree.nEqnColumn ); saved_nEq = pNew->u.btree.nEq; + saved_nBtm = pNew->u.btree.nBtm; + saved_nTop = pNew->u.btree.nTop; saved_nSkip = pNew->nSkip; saved_nLTerm = pNew->nLTerm; saved_wsFlags = pNew->wsFlags; @@ -2291,6 +2341,8 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags = saved_wsFlags; pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; pNew->nLTerm = saved_nLTerm; if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ pNew->aLTerm[pNew->nLTerm++] = pTerm; @@ -2334,6 +2386,9 @@ static int whereLoopAddBtreeIndex( testcase( eOp & WO_GT ); testcase( eOp & WO_GE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; + pNew->u.btree.nBtm = whereRangeVectorLen( + pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm + ); pBtm = pTerm; pTop = 0; if( pTerm->wtFlags & TERM_LIKEOPT ){ @@ -2346,12 +2401,16 @@ static int whereLoopAddBtreeIndex( if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ pNew->aLTerm[pNew->nLTerm++] = pTop; pNew->wsFlags |= WHERE_TOP_LIMIT; + pNew->u.btree.nTop = 1; } }else{ assert( eOp & (WO_LT|WO_LE) ); testcase( eOp & WO_LT ); testcase( eOp & WO_LE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; + pNew->u.btree.nTop = whereRangeVectorLen( + pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm + ); pTop = pTerm; pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? pNew->aLTerm[pNew->nLTerm-2] : 0; @@ -2451,6 +2510,8 @@ static int whereLoopAddBtreeIndex( } pNew->prereq = saved_prereq; pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; pNew->nSkip = saved_nSkip; pNew->wsFlags = saved_wsFlags; pNew->nOut = saved_nOut; @@ -2572,7 +2633,7 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ /* ** Add all WhereLoop objects for a single table of the join where the table -** is idenfied by pBuilder->pNew->iTab. That table is guaranteed to be +** is identified by pBuilder->pNew->iTab. That table is guaranteed to be ** a b-tree table, not a virtual table. ** ** The costs (WhereLoop.rRun) of the b-tree loops added by this function @@ -2726,6 +2787,8 @@ static int whereLoopAddBtree( } rSize = pProbe->aiRowLogEst[0]; pNew->u.btree.nEq = 0; + pNew->u.btree.nBtm = 0; + pNew->u.btree.nTop = 0; pNew->nSkip = 0; pNew->nLTerm = 0; pNew->iSortIdx = 0; diff --git a/src/whereInt.h b/src/whereInt.h index 075c04e5e5..d288fa6192 100644 --- a/src/whereInt.h +++ b/src/whereInt.h @@ -122,6 +122,8 @@ struct WhereLoop { union { struct { /* Information for internal btree tables */ u16 nEq; /* Number of equality constraints */ + u16 nBtm; /* Size of BTM vector */ + u16 nTop; /* Size of TOP vector */ Index *pIndex; /* Index used, or NULL */ } btree; struct { /* Information for virtual tables */ diff --git a/src/wherecode.c b/src/wherecode.c index a017b40c7d..3060d8d324 100644 --- a/src/wherecode.c +++ b/src/wherecode.c @@ -861,6 +861,30 @@ static void codeDeferredSeek( } } +static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ + assert( nReg>0 ); + if( p->flags & EP_Vector ){ + int i; + if( (p->flags & EP_xIsSelect)==0 ){ + ExprList *pList = p->x.pList; + assert( nReg<=pList->nExpr ); + for(i=0; ia[i].pExpr, iReg+i); + } + }else{ + Vdbe *v = pParse->pVdbe; + int iSelect = sqlite3CodeSubselect(pParse, p, 0, 0); + sqlite3VdbeAddOp3(v, OP_Copy, iSelect, iReg, nReg-1); + p->op2 = p->op; + p->op = TK_REGISTER; + p->iTable = iSelect; + } + }else{ + assert( nReg==1 ); + sqlite3ExprCode(pParse, p, iReg); + } +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -1185,6 +1209,8 @@ Bitmask sqlite3WhereCodeOneLoopStart( OP_IdxLT, /* 3: (end_constraints && bRev && endEq) */ }; u16 nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */ + u16 nBtm = pLoop->u.btree.nBtm; /* Length of BTM vector */ + u16 nTop = pLoop->u.btree.nTop; /* Length of TOP vector */ int regBase; /* Base register holding constraint values */ WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */ WhereTerm *pRangeEnd = 0; /* Inequality constraint at range end */ @@ -1231,14 +1257,14 @@ Bitmask sqlite3WhereCodeOneLoopStart( j = nEq; if( pLoop->wsFlags & WHERE_BTM_LIMIT ){ pRangeStart = pLoop->aLTerm[j++]; - nExtraReg = 1; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nBtm); /* Like optimization range constraints always occur in pairs */ assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || (pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 ); } if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ pRangeEnd = pLoop->aLTerm[j++]; - nExtraReg = 1; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nTop); #ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){ assert( pRangeStart!=0 ); /* LIKE opt constraints */ @@ -1274,6 +1300,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); SWAP(u8, bSeekPastNull, bStopAtNull); + SWAP(u8, nBtm, nTop); } /* Generate code to evaluate all constraint terms using == or IN @@ -1298,7 +1325,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( nConstraint = nEq; if( pRangeStart ){ Expr *pRight = pRangeStart->pExpr->pRight; - sqlite3ExprCode(pParse, pRight, regBase+nEq); + codeExprOrVector(pParse, pRight, regBase+nEq, nBtm); whereLikeOptimizationStringFixup(v, pLevel, pRangeStart); if( (pRangeStart->wtFlags & TERM_VNULL)==0 && sqlite3ExprCanBeNull(pRight) @@ -1317,8 +1344,13 @@ Bitmask sqlite3WhereCodeOneLoopStart( zStartAff[nEq] = SQLITE_AFF_BLOB; } } - nConstraint++; + nConstraint += nBtm; testcase( pRangeStart->wtFlags & TERM_VIRTUAL ); + if( (pRight->flags & EP_Vector)==0 ){ + disableTerm(pLevel, pRangeStart); + }else{ + startEq = 1; + } }else if( bSeekPastNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); nConstraint++; @@ -1350,7 +1382,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; sqlite3ExprCacheRemove(pParse, regBase+nEq, 1); - sqlite3ExprCode(pParse, pRight, regBase+nEq); + codeExprOrVector(pParse, pRight, regBase+nEq, nTop); whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); if( (pRangeEnd->wtFlags & TERM_VNULL)==0 && sqlite3ExprCanBeNull(pRight) @@ -1363,8 +1395,14 @@ Bitmask sqlite3WhereCodeOneLoopStart( ){ codeApplyAffinity(pParse, regBase+nEq, 1, &cEndAff); } - nConstraint++; + nConstraint += nTop; testcase( pRangeEnd->wtFlags & TERM_VIRTUAL ); + + if( (pRight->flags & EP_Vector)==0 ){ + disableTerm(pLevel, pRangeEnd); + }else{ + endEq = 1; + } }else if( bStopAtNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); endEq = 0; @@ -1385,9 +1423,11 @@ Bitmask sqlite3WhereCodeOneLoopStart( testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE ); } + /* Disable the start and end range terms if possible */ + /* disableTerm(pLevel, pRangeStart); */ + /* disableTerm(pLevel, pRangeEnd); */ + /* Seek the table cursor, if required */ - disableTerm(pLevel, pRangeStart); - disableTerm(pLevel, pRangeEnd); if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ }else if( HasRowid(pIdx->pTable) ){ @@ -1411,9 +1451,7 @@ Bitmask sqlite3WhereCodeOneLoopStart( iRowidReg, pPk->nKeyCol); VdbeCoverage(v); } - /* Record the instruction used to terminate the loop. Disable - ** WHERE clause terms made redundant by the index range scan. - */ + /* Record the instruction used to terminate the loop. */ if( pLoop->wsFlags & WHERE_ONEROW ){ pLevel->op = OP_Noop; }else if( bRev ){ diff --git a/src/whereexpr.c b/src/whereexpr.c index 1cb6be8458..35af70687b 100644 --- a/src/whereexpr.c +++ b/src/whereexpr.c @@ -95,7 +95,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ /* ** Return TRUE if the given operator is one of the operators that is ** allowed for an indexable WHERE clause term. The allowed operators are -** "=", "<", ">", "<=", ">=", "IN", and "IS NULL" +** "=", "<", ">", "<=", ">=", "IN", "IS", and "IS NULL" */ static int allowedOp(int op){ assert( TK_GT>TK_EQ && TK_GT, <, >= or <=), perform the processing + ** on the first element of the vector. */ + assert( TK_GT+1==TK_LE && TK_GT+2==TK_LT && TK_GT+3==TK_GE ); + if( (pExpr->flags & (EP_Vector|EP_xIsSelect))==EP_Vector + && (op>=TK_GT && op<=TK_GE) + ){ + pExpr = pExpr->x.pList->a[0].pExpr; + } + if( pExpr->op==TK_COLUMN ){ *piCur = pExpr->iTable; *piColumn = pExpr->iColumn; @@ -862,6 +874,17 @@ static int exprMightBeIndexed( return 0; } +static Expr *exprVectorExpr(Parse *pParse, Expr *p, int iField){ + Expr *pRet; + if( p->flags & EP_xIsSelect ){ + pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, p, 0, 0); + if( pRet ) pRet->iColumn = iField; + }else{ + pRet = sqlite3ExprDup(pParse->db, p->x.pList->a[iField].pExpr, 0); + } + return pRet; +} + /* ** The input to this routine is an WhereTerm structure with only the ** "pExpr" field filled in. The job of this routine is to analyze the @@ -937,14 +960,14 @@ static void exprAnalyze( Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft); Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight); u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV; - if( exprMightBeIndexed(pSrc, prereqLeft, pLeft, &iCur, &iColumn) ){ + if( exprMightBeIndexed(pSrc, op, prereqLeft, pLeft, &iCur, &iColumn) ){ pTerm->leftCursor = iCur; pTerm->u.leftColumn = iColumn; pTerm->eOperator = operatorMask(op) & opMask; } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; if( pRight - && exprMightBeIndexed(pSrc, pTerm->prereqRight, pRight, &iCur, &iColumn) + && exprMightBeIndexed(pSrc, op, pTerm->prereqRight, pRight, &iCur,&iColumn) ){ WhereTerm *pNew; Expr *pDup; @@ -1152,6 +1175,26 @@ static void exprAnalyze( } #endif /* SQLITE_OMIT_VIRTUALTABLE */ + if( pWC->op==TK_AND + && (pExpr->op==TK_EQ || pExpr->op==TK_IS) + && (pExpr->pLeft->flags & EP_Vector) + && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 + || (pExpr->pRight->flags & EP_xIsSelect)==0 + )){ + int i; + for(i=0; ipLeft); i++){ + int idxNew; + Expr *pNew; + Expr *pLeft = exprVectorExpr(pParse, pExpr->pLeft, i); + Expr *pRight = exprVectorExpr(pParse, pExpr->pRight, i); + + pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight, 0); + idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); + exprAnalyze(pSrc, pWC, idxNew); + markTermAsChild(pWC, idxNew, idxTerm); + } + } + #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* When sqlite_stat3 histogram data is available an operator of the ** form "x IS NOT NULL" can sometimes be evaluated more efficiently diff --git a/test/rowvalue.test b/test/rowvalue.test new file mode 100644 index 0000000000..3bfcfe2c12 --- /dev/null +++ b/test/rowvalue.test @@ -0,0 +1,123 @@ +# 2016 June 17 +# +# 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. The +# focus of this file is testing the SELECT statement. +# + + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue + +do_execsql_test 0.0 { + CREATE TABLE one(o); + INSERT INTO one VALUES(1); +} + +foreach {tn v1 v2 eq ne is isnot} { + 1 "1, 2, 3" "1, 2, 3" 1 0 1 0 + 2 "1, 0, 3" "1, 2, 3" 0 1 0 1 + 3 "1, 2, NULL" "1, 2, 3" {} {} 0 1 + 4 "1, 2, NULL" "1, 2, NULL" {} {} 1 0 + 5 "NULL, NULL, NULL" "NULL, NULL, NULL" {} {} 1 0 +} { + do_execsql_test 1.$tn.eq "SELECT ($v1) == ($v2)" [list $eq] + do_execsql_test 1.$tn.ne "SELECT ($v1) != ($v2)" [list $ne] + + do_execsql_test 1.$tn.is "SELECT ($v1) IS ($v2)" [list $is] + do_execsql_test 1.$tn.isnot "SELECT ($v1) IS NOT ($v2)" [list $isnot] + + do_execsql_test 1.$tn.2.eq "SELECT (SELECT $v1) == (SELECT $v2)" [list $eq] + do_execsql_test 1.$tn.2.ne "SELECT (SELECT $v1) != (SELECT $v2)" [list $ne] +} + +foreach {tn v1 v2 lt gt le ge} { + 1 "(1, 1, 3)" "(1, 2, 3)" 1 0 1 0 + 2 "(1, 2, 3)" "(1, 2, 3)" 0 0 1 1 + 3 "(1, 3, 3)" "(1, 2, 3)" 0 1 0 1 + + 4 "(1, NULL, 3)" "(1, 2, 3)" {} {} {} {} + 5 "(1, 3, 3)" "(1, NULL, 3)" {} {} {} {} + 6 "(1, NULL, 3)" "(1, NULL, 3)" {} {} {} {} +} { + foreach {tn2 expr res} [list \ + 2.$tn.lt "$v1 < $v2" $lt \ + 2.$tn.gt "$v1 > $v2" $gt \ + 2.$tn.le "$v1 <= $v2" $le \ + 2.$tn.ge "$v1 >= $v2" $ge \ + ] { + do_execsql_test $tn2 "SELECT $expr" [list $res] + + set map(0) [list] + set map() [list] + set map(1) [list 1] + do_execsql_test $tn2.where1 "SELECT * FROM one WHERE $expr" $map($res) + + set map(0) [list 1] + set map() [list] + set map(1) [list] + do_execsql_test $tn2.where2 "SELECT * FROM one WHERE NOT $expr" $map($res) + } +} + +do_execsql_test 3.0 { + CREATE TABLE t1(x, y); + INSERT INTO t1 VALUES(1, 1); + INSERT INTO t1 VALUES(1, 2); + INSERT INTO t1 VALUES(2, 3); + INSERT INTO t1 VALUES(2, 4); + INSERT INTO t1 VALUES(3, 5); + INSERT INTO t1 VALUES(3, 6); +} + +foreach {tn r order} { + 1 "(1, 1)" "ORDER BY y" + 2 "(1, 1)" "ORDER BY x, y" + 3 "(1, 2)" "ORDER BY x, y DESC" + 4 "(3, 6)" "ORDER BY x DESC, y DESC" + 5 "((3, 5))" "ORDER BY x DESC, y" + 6 "(SELECT 3, 5)" "ORDER BY x DESC, y" +} { + do_execsql_test 3.$tn.1 "SELECT $r == (SELECT x,y FROM t1 $order)" 1 + do_execsql_test 3.$tn.2 "SELECT $r == (SELECT * FROM t1 $order)" 1 + + do_execsql_test 3.$tn.3 " + SELECT (SELECT * FROM t1 $order) == (SELECT * FROM t1 $order) + " 1 + do_execsql_test 3.$tn.4 " + SELECT (SELECT 0, 0) == (SELECT * FROM t1 $order) + " 0 +} + +foreach {tn expr res} { + 1 {(2, 2) BETWEEN (2, 2) AND (3, 3)} 1 + 2 {(2, 2) BETWEEN (2, NULL) AND (3, 3)} {} + 3 {(2, 2) BETWEEN (3, NULL) AND (3, 3)} 0 +} { + do_execsql_test 4.$tn "SELECT $expr" [list $res] +} + +foreach {tn expr res} { + 1 {(2, 4) IN (SELECT * FROM t1)} 1 + 2 {(3, 4) IN (SELECT * FROM t1)} 0 + + 3 {(NULL, 4) IN (SELECT * FROM t1)} {} + 4 {(NULL, 0) IN (SELECT * FROM t1)} 0 + + 5 {(NULL, 4) NOT IN (SELECT * FROM t1)} {} + 6 {(NULL, 0) NOT IN (SELECT * FROM t1)} 1 +} { + do_execsql_test 5.$tn "SELECT $expr" [list $res] +} + +finish_test + + diff --git a/test/rowvalue2.test b/test/rowvalue2.test new file mode 100644 index 0000000000..c7d061e3e2 --- /dev/null +++ b/test/rowvalue2.test @@ -0,0 +1,252 @@ +# 2016 June 17 +# +# 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. The +# focus of this file is testing the SELECT statement. +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set ::testprefix rowvalue2 + +do_execsql_test 1.0 { + CREATE TABLE t1(a, b, c); + INSERT INTO t1 VALUES(0, 0, 0); + INSERT INTO t1 VALUES(0, 1, 1); + INSERT INTO t1 VALUES(1, 0, 2); + INSERT INTO t1 VALUES(1, 1, 3); + + CREATE INDEX i1 ON t1(a, b); +} + +do_execsql_test 1.1.1 { SELECT c FROM t1 WHERE (a, b) >= (1, 0) } {2 3} +do_execsql_test 1.1.2 { SELECT c FROM t1 WHERE (a, b) > (1, 0) } {3} + +#------------------------------------------------------------------------- + +do_execsql_test 2.0.1 { + CREATE TABLE t2(a INTEGER, b INTEGER, c INTEGER, d INTEGER); + CREATE INDEX i2 ON t2(a, b, c); +} +do_test 2.0.2 { + foreach a {0 1 2 3} { + foreach b {0 1 2 3} { + foreach c {0 1 2 3} { + execsql { INSERT INTO t2 VALUES($a, $b, $c, $c + $b*4 + $a*16); } + }}} +} {} + +do_execsql_test 2.1 { + SELECT d FROM t2 WHERE (a, b) > (2, 2); +} [db eval { SELECT d FROM t2 WHERE a>2 OR (a=2 AND b>2) }] + +do_execsql_test 2.2 { + SELECT d FROM t2 WHERE (a, b) >= (2, 2); +} [db eval { SELECT d FROM t2 WHERE a>2 OR (a=2 AND b>=2) }] + +do_execsql_test 2.3 { + SELECT d FROM t2 WHERE a=1 AND (b, c) >= (1, 2); +} [db eval { SELECT d FROM t2 WHERE +a=1 AND (b>1 OR (b==1 AND c>=2)) }] + +do_execsql_test 2.4 { + SELECT d FROM t2 WHERE a=1 AND (b, c) > (1, 2); +} [db eval { SELECT d FROM t2 WHERE +a=1 AND (b>1 OR (b==1 AND c>2)) }] + +#------------------------------------------------------------------------- + +set words { +airfare airfield airfields airflow airfoil +airfoils airframe airframes airily airing +airings airless airlift airlifts airline +airliner airlines airlock airlocks airmail +airmails airman airmen airplane airplanes + +arraignment arraignments arraigns arrange arranged +arrangement arrangements arranger arrangers arranges +arranging arrant array arrayed arrays +arrears arrest arrested arrester arresters +arresting arrestingly arrestor arrestors arrests + +edifices edit edited editing edition +editions editor editorial editorially editorials +editors edits educable educate educated +educates educating education educational educationally +educations educator educators eel eelgrass +} + +do_test 3.0 { + execsql { CREATE TABLE t3(a, b, c, w); } + foreach w $words { + set a [string range $w 0 2] + set b [string range $w 3 5] + set c [string range $w 6 end] + execsql { INSERT INTO t3 VALUES($a, $b, $c, $w) } + } +} {} + + +foreach {tn idx} { + IDX1 {} + IDX2 { CREATE INDEX i3 ON t3(a, b, c); } + IDX3 { CREATE INDEX i3 ON t3(a, b); } + IDX4 { CREATE INDEX i3 ON t3(a); } +} { + execsql { DROP INDEX IF EXISTS i3 } + execsql $idx + + foreach w $words { + set a [string range $w 0 2] + set b [string range $w 3 5] + set c [string range $w 6 end] + + foreach op [list > >= < <= == IS] { + do_execsql_test 3.1.$tn.$w.$op [subst -novar { + SELECT rowid FROM t3 WHERE (a, b, c) [set op] ($a, $b, $c) + ORDER BY +rowid + }] [db eval [subst -novar { + SELECT rowid FROM t3 WHERE w [set op] $w ORDER BY +rowid + }]] + + do_execsql_test 3.1.$tn.$w.$op.subselect [subst -novar { + SELECT rowid FROM t3 WHERE (a, b, c) [set op] ( + SELECT a, b, c FROM t3 WHERE w = $w + ) + ORDER BY +rowid + }] [db eval [subst -novar { + SELECT rowid FROM t3 WHERE w [set op] $w ORDER BY +rowid + }]] + } + + } +} + +#------------------------------------------------------------------------- +# + +do_execsql_test 4.0 { + CREATE TABLE t4(a, b, c); + INSERT INTO t4 VALUES(NULL, NULL, NULL); + INSERT INTO t4 VALUES(NULL, NULL, 0); + INSERT INTO t4 VALUES(NULL, NULL, 1); + INSERT INTO t4 VALUES(NULL, 0, NULL); + INSERT INTO t4 VALUES(NULL, 0, 0); + INSERT INTO t4 VALUES(NULL, 0, 1); + INSERT INTO t4 VALUES(NULL, 1, NULL); + INSERT INTO t4 VALUES(NULL, 1, 0); + INSERT INTO t4 VALUES(NULL, 1, 1); + + INSERT INTO t4 VALUES( 0, NULL, NULL); + INSERT INTO t4 VALUES( 0, NULL, 0); + INSERT INTO t4 VALUES( 0, NULL, 1); + INSERT INTO t4 VALUES( 0, 0, NULL); + INSERT INTO t4 VALUES( 0, 0, 0); + INSERT INTO t4 VALUES( 0, 0, 1); + INSERT INTO t4 VALUES( 0, 1, NULL); + INSERT INTO t4 VALUES( 0, 1, 0); + INSERT INTO t4 VALUES( 0, 1, 1); + + INSERT INTO t4 VALUES( 1, NULL, NULL); + INSERT INTO t4 VALUES( 1, NULL, 0); + INSERT INTO t4 VALUES( 1, NULL, 1); + INSERT INTO t4 VALUES( 1, 0, NULL); + INSERT INTO t4 VALUES( 1, 0, 0); + INSERT INTO t4 VALUES( 1, 0, 1); + INSERT INTO t4 VALUES( 1, 1, NULL); + INSERT INTO t4 VALUES( 1, 1, 0); + INSERT INTO t4 VALUES( 1, 1, 1); +} + +proc make_expr1 {cList vList op} { + return "([join $cList ,]) $op ([join $vList ,])" +} + +proc make_expr3 {cList vList op} { + set n [llength $cList] + + set aList [list] + foreach c [lrange $cList 0 end-1] v [lrange $vList 0 end-1] { + lappend aList "$c == $v" + } + lappend aList "[lindex $cList end] $op [lindex $vList end]" + + return "([join $aList { AND }])" +} + +proc make_expr2 {cList vList op} { + set ret "" + + switch -- $op { + == - IS { + set aList [list] + foreach c $cList v $vList { lappend aList "($c $op $v)" } + set ret [join $aList " AND "] + } + + < - > { + set oList [list] + for {set i 0} {$i < [llength $cList]} {incr i} { + lappend oList [make_expr3 [lrange $cList 0 $i] [lrange $vList 0 $i] $op] + } + set ret [join $oList " OR "] + } + + <= - >= { + set o2 [string range $op 0 0] + set oList [list] + for {set i 0} {$i < [llength $cList]-1} {incr i} { + lappend oList [make_expr3 [lrange $cList 0 $i] [lrange $vList 0 $i] $o2] + } + lappend oList [make_expr3 $cList $vList $op] + set ret [join $oList " OR "] + } + + + default { + error "Unknown op: $op" + } + } + + set ret +} + +foreach {tn idx} { + IDX1 {} + IDX2 { CREATE INDEX i4 ON t4(a, b, c); } + IDX3 { CREATE INDEX i4 ON t4(a, b); } + IDX4 { CREATE INDEX i4 ON t4(a); } +} { + execsql { DROP INDEX IF EXISTS i4 } + execsql $idx + + foreach {tn2 vector} { + 1 {0 0 0} + 2 {1 1 1} + 3 {0 0 NULL} + 4 {0 NULL 0} + 5 {NULL 0 0} + 6 {1 1 NULL} + 7 {1 NULL 1} + 8 {NULL 1 1} + } { + foreach op { IS == < <= > >= } { + set e1 [make_expr1 {a b c} $vector $op] + set e2 [make_expr2 {a b c} $vector $op] + + do_execsql_test 4.$tn.$tn2.$op \ + "SELECT rowid FROM t4 WHERE $e2 ORDER BY +rowid" [ + db eval "SELECT rowid FROM t4 WHERE $e1 ORDER BY +rowid" + ] + } + } +} + + +finish_test + diff --git a/tool/addopcodes.tcl b/tool/addopcodes.tcl index 9b34cf2e03..29409a4900 100644 --- a/tool/addopcodes.tcl +++ b/tool/addopcodes.tcl @@ -37,6 +37,7 @@ set extras { UMINUS UPLUS REGISTER + SELECT_COLUMN ASTERISK SPAN SPACE