Merge the skip-scan enhancement into trunk.
FossilOrigin-Name: b0bb975c0986fe01f1184c1d4888fe397174ad0f
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
C In\sthe\sshell\stool,\sif\san\s"EXPLAIN"\scommand\sis\sexecuted\sin\s".explain\son"\smode,\sattempt\sto\sautomatically\sindent\sthe\sbodies\sof\sloops\sin\sthe\soutput\sVDBE\sprogram.
|
||||
D 2013-11-13T18:35:01.894
|
||||
C Merge\sthe\sskip-scan\senhancement\sinto\strunk.
|
||||
D 2013-11-13T20:46:11.977
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 8a07bebafbfda0eb67728f4bd15a36201662d1a1
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
@@ -160,7 +160,7 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc
|
||||
F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a
|
||||
F src/alter.c 2af0330bb1b601af7a7789bf7229675fd772a083
|
||||
F src/analyze.c 27f0c132aa0679189837e0addf8762e7fd6831b6
|
||||
F src/analyze.c 581d5c18ce89c6f45d4dca65914d0de5b4dad41f
|
||||
F src/attach.c 0a17c9364895316ca4f52d06a97a72c0af1ae8b3
|
||||
F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34
|
||||
F src/backup.c 2f1987981139bd2f6d8c728d64bf09fb387443c3
|
||||
@@ -220,7 +220,7 @@ F src/random.c 0b2dbc37fdfbfa6bd455b091dfcef5bdb32dba68
|
||||
F src/resolve.c fc4673cc49b116e51e7f12de074c0acf8f2388f9
|
||||
F src/rowset.c 64655f1a627c9c212d9ab497899e7424a34222e0
|
||||
F src/select.c 7317406831ecced390edba972818f3c5f82238c0
|
||||
F src/shell.c 3b23017da75118da0a7e3518c6ce78a7b747fb05
|
||||
F src/shell.c 6ccc22b717afe4f6d7d3c8b6baa02e3b99a5fdf0
|
||||
F src/sqlite.h.in 4dedcab5b32358bf7a596badffe7363be1f1a82d
|
||||
F src/sqlite3.rc 11094cc6a157a028b301a9f06b3d03089ea37c3e
|
||||
F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
|
||||
@@ -293,8 +293,8 @@ F src/vtab.c 5a423b042eb1402ef77697d03d6a67378d97bc8d
|
||||
F src/wal.c 7dc3966ef98b74422267e7e6e46e07ff6c6eb1b4
|
||||
F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4
|
||||
F src/walker.c e9e593d5bb798c3e67fc3893dfe7055c9e7d8d74
|
||||
F src/where.c 537f4d7e869f41b7aef851ad0b050983b4d6508d
|
||||
F src/whereInt.h 63c8345d01d12ded6250b72e7c16855f0a358041
|
||||
F src/where.c 2eb88f96a73d77bd61d6afd50c33ec5d63c15dd5
|
||||
F src/whereInt.h 96a75c61f1d2b9d4a8e4bb17d89deb0cf7cba358
|
||||
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
|
||||
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
|
||||
F test/aggnested.test 45c0201e28045ad38a530b5a144b73cd4aa2cfd6
|
||||
@@ -805,6 +805,7 @@ F test/shell5.test 46c8c18d62732415c4fe084816c13d559831705e
|
||||
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
|
||||
F test/shrink.test 8c70f62b6e8eb4d54533de6d65bd06b1b9a17868
|
||||
F test/sidedelete.test f0ad71abe6233e3b153100f3b8d679b19a488329
|
||||
F test/skipscan1.test 6bb4891c2cc5efd5690a9da9e7508e53d4a68e10
|
||||
F test/soak.test 0b5b6375c9f4110c828070b826b3b4b0bb65cd5f
|
||||
F test/softheap1.test 40562fe6cac6d9827b7b42b86d45aedf12c15e24
|
||||
F test/sort.test 0e4456e729e5a92a625907c63dcdedfbe72c5dc5
|
||||
@@ -835,7 +836,7 @@ F test/tclsqlite.test 37a61c2da7e3bfe3b8c1a2867199f6b860df5d43
|
||||
F test/tempdb.test 19d0f66e2e3eeffd68661a11c83ba5e6ace9128c
|
||||
F test/temptable.test d2c9b87a54147161bcd1822e30c1d1cd891e5b30
|
||||
F test/temptrigger.test 0a48d94222d50e6e50d72ac103606c4f8e7cbb81
|
||||
F test/tester.tcl 3f675f00d22de3595be25dc2d2cba7ff623ef058
|
||||
F test/tester.tcl bce6b929932498383ce92431da6a96432c690bf7
|
||||
F test/thread001.test 9f22fd3525a307ff42a326b6bc7b0465be1745a5
|
||||
F test/thread002.test e630504f8a06c00bf8bbe68528774dd96aeb2e58
|
||||
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
|
||||
@@ -1138,7 +1139,7 @@ F tool/vdbe-compress.tcl f12c884766bd14277f4fcedcae07078011717381
|
||||
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
|
||||
F tool/warnings.sh d1a6de74685f360ab718efda6265994b99bbea01
|
||||
F tool/win/sqlite.vsix 030f3eeaf2cb811a3692ab9c14d021a75ce41fff
|
||||
P 8ce33f4c818e1c785a1c176f6f631b8184e1166b
|
||||
R 5d7c6b4eb6f0cadad72d4cdb7ed00f79
|
||||
U dan
|
||||
Z 87b1c90d92386c54cb5a5e86314bc13e
|
||||
P e7d34ec6814ed4606a6d5d7f68c218ae4d25e666 f668616a29686f3ce532731c534b168e536adbb5
|
||||
R 0f42dd23f60246e0b55a84d51974156a
|
||||
U drh
|
||||
Z e8f824146c4a7790bef9ac829b4cd088
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
e7d34ec6814ed4606a6d5d7f68c218ae4d25e666
|
||||
b0bb975c0986fe01f1184c1d4888fe397174ad0f
|
||||
+5
-3
@@ -1428,10 +1428,12 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){
|
||||
if( pTable==0 ){
|
||||
return 0;
|
||||
}
|
||||
if( argv[1] ){
|
||||
pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase);
|
||||
}else{
|
||||
if( argv[1]==0 ){
|
||||
pIndex = 0;
|
||||
}else if( sqlite3_stricmp(argv[0],argv[1])==0 ){
|
||||
pIndex = sqlite3PrimaryKeyIndex(pTable);
|
||||
}else{
|
||||
pIndex = sqlite3FindIndex(pInfo->db, argv[1], pInfo->zDatabase);
|
||||
}
|
||||
z = argv[2];
|
||||
|
||||
|
||||
+5
-4
@@ -1173,9 +1173,10 @@ static int str_in_array(const char *zStr, const char **azArray){
|
||||
** all opcodes that occur between the p2 jump destination and the opcode
|
||||
** itself by 2 spaces.
|
||||
**
|
||||
** * For each "Goto", if the jump destination is a "Yield" instruction
|
||||
** that occurs earlier in the program than the Goto itself, indent
|
||||
** all opcodes between the "Yield" and "Goto" by 2 spaces.
|
||||
** * For each "Goto", if the jump destination is a "Yield", "SeekGt",
|
||||
** or "SeekLt" instruction that occurs earlier in the program than
|
||||
** the Goto itself, indent all opcodes between the earlier instruction
|
||||
** and "Goto" by 2 spaces.
|
||||
*/
|
||||
static void explain_data_prepare(struct callback_data *p, sqlite3_stmt *pSql){
|
||||
const char *zSql; /* The text of the SQL statement */
|
||||
@@ -1185,7 +1186,7 @@ static void explain_data_prepare(struct callback_data *p, sqlite3_stmt *pSql){
|
||||
int iOp;
|
||||
|
||||
const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", 0 };
|
||||
const char *azYield[] = { "Yield", 0 };
|
||||
const char *azYield[] = { "Yield", "SeekLt", "SeekGt", 0 };
|
||||
const char *azGoto[] = { "Goto", 0 };
|
||||
|
||||
/* Try to figure out if this is really an EXPLAIN statement. If this
|
||||
|
||||
+79
-17
@@ -2422,7 +2422,7 @@ static int codeEqualityTerm(
|
||||
|
||||
/*
|
||||
** Generate code that will evaluate all == and IN constraints for an
|
||||
** index.
|
||||
** index scan.
|
||||
**
|
||||
** For example, consider table t1(a,b,c,d,e,f) with index i1(a,b,c).
|
||||
** Suppose the WHERE clause is this: a==5 AND b IN (1,2,3) AND c>5 AND c<10
|
||||
@@ -2437,9 +2437,15 @@ static int codeEqualityTerm(
|
||||
** The only thing it does is allocate the pLevel->iMem memory cell and
|
||||
** compute the affinity string.
|
||||
**
|
||||
** This routine always allocates at least one memory cell and returns
|
||||
** the index of that memory cell. The code that
|
||||
** calls this routine will use that memory cell to store the termination
|
||||
** The nExtraReg parameter is 0 or 1. It is 0 if all WHERE clause constraints
|
||||
** are == or IN and are covered by the nEq. nExtraReg is 1 if there is
|
||||
** an inequality constraint (such as the "c>=5 AND c<10" in the example) that
|
||||
** occurs after the nEq quality constraints.
|
||||
**
|
||||
** This routine allocates a range of nEq+nExtraReg memory cells and returns
|
||||
** the index of the first memory cell in that range. The code that
|
||||
** calls this routine will use that memory range to store keys for
|
||||
** start and termination conditions of the loop.
|
||||
** key value of the loop. If one or more IN operators appear, then
|
||||
** this routine allocates an additional nEq memory cells for internal
|
||||
** use.
|
||||
@@ -2466,7 +2472,8 @@ static int codeAllEqualityTerms(
|
||||
int nExtraReg, /* Number of extra registers to allocate */
|
||||
char **pzAff /* OUT: Set to point to affinity string */
|
||||
){
|
||||
int nEq; /* The number of == or IN constraints to code */
|
||||
u16 nEq; /* The number of == or IN constraints to code */
|
||||
u16 nSkip; /* Number of left-most columns to skip */
|
||||
Vdbe *v = pParse->pVdbe; /* The vm under construction */
|
||||
Index *pIdx; /* The index being used for this loop */
|
||||
WhereTerm *pTerm; /* A single constraint term */
|
||||
@@ -2480,6 +2487,7 @@ static int codeAllEqualityTerms(
|
||||
pLoop = pLevel->pWLoop;
|
||||
assert( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 );
|
||||
nEq = pLoop->u.btree.nEq;
|
||||
nSkip = pLoop->u.btree.nSkip;
|
||||
pIdx = pLoop->u.btree.pIndex;
|
||||
assert( pIdx!=0 );
|
||||
|
||||
@@ -2494,14 +2502,29 @@ static int codeAllEqualityTerms(
|
||||
pParse->db->mallocFailed = 1;
|
||||
}
|
||||
|
||||
if( nSkip ){
|
||||
int iIdxCur = pLevel->iIdxCur;
|
||||
sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur);
|
||||
VdbeComment((v, "begin skip-scan on %s", pIdx->zName));
|
||||
j = sqlite3VdbeAddOp0(v, OP_Goto);
|
||||
pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLt:OP_SeekGt),
|
||||
iIdxCur, 0, regBase, nSkip);
|
||||
sqlite3VdbeJumpHere(v, j);
|
||||
for(j=0; j<nSkip; j++){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, j, regBase+j);
|
||||
assert( pIdx->aiColumn[j]>=0 );
|
||||
VdbeComment((v, "%s", pIdx->pTable->aCol[pIdx->aiColumn[j]].zName));
|
||||
}
|
||||
}
|
||||
|
||||
/* Evaluate the equality constraints
|
||||
*/
|
||||
assert( zAff==0 || (int)strlen(zAff)>=nEq );
|
||||
for(j=0; j<nEq; j++){
|
||||
for(j=nSkip; j<nEq; j++){
|
||||
int r1;
|
||||
pTerm = pLoop->aLTerm[j];
|
||||
assert( pTerm!=0 );
|
||||
/* The following true for indices with redundant columns.
|
||||
/* The following testcase is true for indices with redundant columns.
|
||||
** Ex: CREATE INDEX i1 ON t1(a,b,a); SELECT * FROM t1 WHERE a=0 AND b=0; */
|
||||
testcase( (pTerm->wtFlags & TERM_CODED)!=0 );
|
||||
testcase( pTerm->wtFlags & TERM_VIRTUAL );
|
||||
@@ -2575,7 +2598,8 @@ static void explainAppendTerm(
|
||||
*/
|
||||
static char *explainIndexRange(sqlite3 *db, WhereLoop *pLoop, Table *pTab){
|
||||
Index *pIndex = pLoop->u.btree.pIndex;
|
||||
int nEq = pLoop->u.btree.nEq;
|
||||
u16 nEq = pLoop->u.btree.nEq;
|
||||
u16 nSkip = pLoop->u.btree.nSkip;
|
||||
int i, j;
|
||||
Column *aCol = pTab->aCol;
|
||||
i16 *aiColumn = pIndex->aiColumn;
|
||||
@@ -2589,7 +2613,14 @@ static char *explainIndexRange(sqlite3 *db, WhereLoop *pLoop, Table *pTab){
|
||||
sqlite3StrAccumAppend(&txt, " (", 2);
|
||||
for(i=0; i<nEq; i++){
|
||||
char *z = (i==pIndex->nKeyCol ) ? "rowid" : aCol[aiColumn[i]].zName;
|
||||
explainAppendTerm(&txt, i, z, "=");
|
||||
if( i>=nSkip ){
|
||||
explainAppendTerm(&txt, i, z, "=");
|
||||
}else{
|
||||
if( i ) sqlite3StrAccumAppend(&txt, " AND ", 5);
|
||||
sqlite3StrAccumAppend(&txt, "ANY(", 4);
|
||||
sqlite3StrAccumAppend(&txt, z, -1);
|
||||
sqlite3StrAccumAppend(&txt, ")", 1);
|
||||
}
|
||||
}
|
||||
|
||||
j = i;
|
||||
@@ -2955,8 +2986,9 @@ static Bitmask codeOneLoopStart(
|
||||
OP_IdxGE, /* 1: (end_constraints && !bRev) */
|
||||
OP_IdxLT /* 2: (end_constraints && bRev) */
|
||||
};
|
||||
int nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */
|
||||
int isMinQuery = 0; /* If this is an optimized SELECT min(x).. */
|
||||
u16 nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */
|
||||
u16 nSkip = pLoop->u.btree.nSkip; /* Number of left index terms to skip */
|
||||
int isMinQuery = 0; /* If this is an optimized SELECT min(x).. */
|
||||
int regBase; /* Base register holding constraint values */
|
||||
int r1; /* Temp register */
|
||||
WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */
|
||||
@@ -2974,6 +3006,7 @@ static Bitmask codeOneLoopStart(
|
||||
|
||||
pIdx = pLoop->u.btree.pIndex;
|
||||
iIdxCur = pLevel->iIdxCur;
|
||||
assert( nEq>=nSkip );
|
||||
|
||||
/* If this loop satisfies a sort order (pOrderBy) request that
|
||||
** was passed to this function to implement a "SELECT min(x) ..."
|
||||
@@ -2987,8 +3020,7 @@ static Bitmask codeOneLoopStart(
|
||||
&& (pWInfo->bOBSat!=0)
|
||||
&& (pIdx->nKeyCol>nEq)
|
||||
){
|
||||
/* assert( pOrderBy->nExpr==1 ); */
|
||||
/* assert( pOrderBy->a[0].pExpr->iColumn==pIdx->aiColumn[nEq] ); */
|
||||
assert( nSkip==0 );
|
||||
isMinQuery = 1;
|
||||
nExtraReg = 1;
|
||||
}
|
||||
@@ -3539,6 +3571,7 @@ static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){
|
||||
sqlite3ExplainBegin(v);
|
||||
for(i=0; i<p->nLTerm; i++){
|
||||
WhereTerm *pTerm = p->aLTerm[i];
|
||||
if( pTerm==0 ) continue;
|
||||
sqlite3ExplainPrintf(v, " (%d) #%-2d ", i+1, (int)(pTerm-pWC->a));
|
||||
sqlite3ExplainPush(v);
|
||||
whereExplainTerm(v, pTerm);
|
||||
@@ -3822,6 +3855,7 @@ static void whereLoopOutputAdjust(WhereClause *pWC, WhereLoop *pLoop){
|
||||
if( (pTerm->prereqAll & notAllowed)!=0 ) continue;
|
||||
for(j=pLoop->nLTerm-1; j>=0; j--){
|
||||
pX = pLoop->aLTerm[j];
|
||||
if( pX==0 ) continue;
|
||||
if( pX==pTerm ) break;
|
||||
if( pX->iParent>=0 && (&pWC->a[pX->iParent])==pTerm ) break;
|
||||
}
|
||||
@@ -3851,7 +3885,8 @@ static int whereLoopAddBtreeIndex(
|
||||
WhereScan scan; /* Iterator for WHERE terms */
|
||||
Bitmask saved_prereq; /* Original value of pNew->prereq */
|
||||
u16 saved_nLTerm; /* Original value of pNew->nLTerm */
|
||||
int saved_nEq; /* Original value of pNew->u.btree.nEq */
|
||||
u16 saved_nEq; /* Original value of pNew->u.btree.nEq */
|
||||
u16 saved_nSkip; /* Original value of pNew->u.btree.nSkip */
|
||||
u32 saved_wsFlags; /* Original value of pNew->wsFlags */
|
||||
LogEst saved_nOut; /* Original value of pNew->nOut */
|
||||
int iCol; /* Index of the column in the table */
|
||||
@@ -3886,12 +3921,26 @@ static int whereLoopAddBtreeIndex(
|
||||
pTerm = whereScanInit(&scan, pBuilder->pWC, pSrc->iCursor, iCol,
|
||||
opMask, pProbe);
|
||||
saved_nEq = pNew->u.btree.nEq;
|
||||
saved_nSkip = pNew->u.btree.nSkip;
|
||||
saved_nLTerm = pNew->nLTerm;
|
||||
saved_wsFlags = pNew->wsFlags;
|
||||
saved_prereq = pNew->prereq;
|
||||
saved_nOut = pNew->nOut;
|
||||
pNew->rSetup = 0;
|
||||
rLogSize = estLog(sqlite3LogEst(pProbe->aiRowEst[0]));
|
||||
if( pTerm==0
|
||||
&& saved_nEq==saved_nSkip
|
||||
&& saved_nEq+1<pProbe->nKeyCol
|
||||
&& pProbe->aiRowEst[saved_nEq+1]>50
|
||||
){
|
||||
LogEst nIter;
|
||||
pNew->u.btree.nEq++;
|
||||
pNew->u.btree.nSkip++;
|
||||
pNew->aLTerm[pNew->nLTerm++] = 0;
|
||||
pNew->wsFlags |= WHERE_SKIPSCAN;
|
||||
nIter = sqlite3LogEst(pProbe->aiRowEst[0]/pProbe->aiRowEst[saved_nEq+1]);
|
||||
whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nIter);
|
||||
}
|
||||
for(; rc==SQLITE_OK && pTerm!=0; pTerm = whereScanNext(&scan)){
|
||||
int nIn = 0;
|
||||
#ifdef SQLITE_ENABLE_STAT3_OR_STAT4
|
||||
@@ -3927,8 +3976,10 @@ static int whereLoopAddBtreeIndex(
|
||||
pNew->u.btree.nEq++;
|
||||
pNew->nOut = nRowEst + nInMul + nIn;
|
||||
}else if( pTerm->eOperator & (WO_EQ) ){
|
||||
assert( (pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0
|
||||
|| nInMul==0 );
|
||||
assert(
|
||||
(pNew->wsFlags & (WHERE_COLUMN_NULL|WHERE_COLUMN_IN|WHERE_SKIPSCAN))!=0
|
||||
|| nInMul==0
|
||||
);
|
||||
pNew->wsFlags |= WHERE_COLUMN_EQ;
|
||||
if( iCol<0
|
||||
|| (pProbe->onError!=OE_None && nInMul==0
|
||||
@@ -4009,6 +4060,7 @@ static int whereLoopAddBtreeIndex(
|
||||
}
|
||||
pNew->prereq = saved_prereq;
|
||||
pNew->u.btree.nEq = saved_nEq;
|
||||
pNew->u.btree.nSkip = saved_nSkip;
|
||||
pNew->wsFlags = saved_wsFlags;
|
||||
pNew->nOut = saved_nOut;
|
||||
pNew->nLTerm = saved_nLTerm;
|
||||
@@ -4155,6 +4207,7 @@ static int whereLoopAddBtree(
|
||||
if( pTerm->prereqRight & pNew->maskSelf ) continue;
|
||||
if( termCanDriveIndex(pTerm, pSrc, 0) ){
|
||||
pNew->u.btree.nEq = 1;
|
||||
pNew->u.btree.nSkip = 0;
|
||||
pNew->u.btree.pIndex = 0;
|
||||
pNew->nLTerm = 1;
|
||||
pNew->aLTerm[0] = pTerm;
|
||||
@@ -4184,6 +4237,7 @@ static int whereLoopAddBtree(
|
||||
continue; /* Partial index inappropriate for this query */
|
||||
}
|
||||
pNew->u.btree.nEq = 0;
|
||||
pNew->u.btree.nSkip = 0;
|
||||
pNew->nLTerm = 0;
|
||||
pNew->iSortIdx = 0;
|
||||
pNew->rSetup = 0;
|
||||
@@ -4718,6 +4772,7 @@ static int wherePathSatisfiesOrderBy(
|
||||
|
||||
/* Skip over == and IS NULL terms */
|
||||
if( j<pLoop->u.btree.nEq
|
||||
&& pLoop->u.btree.nSkip==0
|
||||
&& ((i = pLoop->aLTerm[j]->eOperator) & (WO_EQ|WO_ISNULL))!=0
|
||||
){
|
||||
if( i & WO_ISNULL ){
|
||||
@@ -5143,6 +5198,7 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){
|
||||
pWC = &pWInfo->sWC;
|
||||
pLoop = pBuilder->pNew;
|
||||
pLoop->wsFlags = 0;
|
||||
pLoop->u.btree.nSkip = 0;
|
||||
pTerm = findTerm(pWC, iCur, -1, 0, WO_EQ, 0);
|
||||
if( pTerm ){
|
||||
pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW;
|
||||
@@ -5716,6 +5772,7 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
VdbeNoopComment((v, "End WHERE-core"));
|
||||
sqlite3ExprCacheClear(pParse);
|
||||
for(i=pWInfo->nLevel-1; i>=0; i--){
|
||||
int addr;
|
||||
pLevel = &pWInfo->a[i];
|
||||
pLoop = pLevel->pWLoop;
|
||||
sqlite3VdbeResolveLabel(v, pLevel->addrCont);
|
||||
@@ -5735,8 +5792,13 @@ void sqlite3WhereEnd(WhereInfo *pWInfo){
|
||||
sqlite3DbFree(db, pLevel->u.in.aInLoop);
|
||||
}
|
||||
sqlite3VdbeResolveLabel(v, pLevel->addrBrk);
|
||||
if( pLevel->addrSkip ){
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->addrSkip);
|
||||
VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName));
|
||||
sqlite3VdbeJumpHere(v, pLevel->addrSkip);
|
||||
sqlite3VdbeJumpHere(v, pLevel->addrSkip-2);
|
||||
}
|
||||
if( pLevel->iLeftJoin ){
|
||||
int addr;
|
||||
addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin);
|
||||
assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0
|
||||
|| (pLoop->wsFlags & WHERE_INDEXED)!=0 );
|
||||
|
||||
@@ -65,6 +65,7 @@ struct WhereLevel {
|
||||
int iIdxCur; /* The VDBE cursor used to access pIdx */
|
||||
int addrBrk; /* Jump here to break out of the loop */
|
||||
int addrNxt; /* Jump here to start the next IN combination */
|
||||
int addrSkip; /* Jump here for next iteration of skip-scan */
|
||||
int addrCont; /* Jump here to continue with the next loop cycle */
|
||||
int addrFirst; /* First instruction of interior of the loop */
|
||||
int addrBody; /* Beginning of the body of this loop */
|
||||
@@ -455,3 +456,4 @@ struct WhereInfo {
|
||||
#define WHERE_ONEROW 0x00001000 /* Selects no more than one row */
|
||||
#define WHERE_MULTI_OR 0x00002000 /* OR using multiple indices */
|
||||
#define WHERE_AUTO_INDEX 0x00004000 /* Uses an ephemeral index */
|
||||
#define WHERE_SKIPSCAN 0x00008000 /* Uses the skip-scan algorithm */
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
# 2013-11-13
|
||||
#
|
||||
# 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 tests of the "skip-scan" query strategy.
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
do_execsql_test skipscan1-1.1 {
|
||||
CREATE TABLE t1(a TEXT, b INT, c INT, d INT);
|
||||
CREATE INDEX t1abc ON t1(a,b,c);
|
||||
INSERT INTO t1 VALUES('abc',123,4,5);
|
||||
INSERT INTO t1 VALUES('abc',234,5,6);
|
||||
INSERT INTO t1 VALUES('abc',234,6,7);
|
||||
INSERT INTO t1 VALUES('abc',345,7,8);
|
||||
INSERT INTO t1 VALUES('def',567,8,9);
|
||||
INSERT INTO t1 VALUES('def',345,9,10);
|
||||
INSERT INTO t1 VALUES('bcd',100,6,11);
|
||||
|
||||
/* Fake the sqlite_stat1 table so that the query planner believes
|
||||
** the table contains thousands of rows and that the first few
|
||||
** columns are not selective. */
|
||||
ANALYZE;
|
||||
DELETE FROM sqlite_stat1;
|
||||
INSERT INTO sqlite_stat1 VALUES('t1','t1abc','10000 5000 2000 10');
|
||||
ANALYZE sqlite_master;
|
||||
} {}
|
||||
|
||||
# Simple queries that leave the first one or two columns of the
|
||||
# index unconstrainted.
|
||||
#
|
||||
do_execsql_test skipscan1-1.2 {
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a;
|
||||
} {abc 345 7 8 | def 345 9 10 |}
|
||||
do_execsql_test skipscan1-1.2eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a;
|
||||
} {/* USING INDEX t1abc (ANY(a) AND b=?)*/}
|
||||
do_execsql_test skipscan1-1.2sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
do_execsql_test skipscan1-1.3 {
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a DESC;
|
||||
} {def 345 9 10 | abc 345 7 8 |}
|
||||
do_execsql_test skipscan1-1.3eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a;
|
||||
} {/* USING INDEX t1abc (ANY(a) AND b=?)*/}
|
||||
do_execsql_test skipscan1-1.3sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b=345 ORDER BY a;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
do_execsql_test skipscan1-1.4 {
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c=6 ORDER BY a, b, c;
|
||||
} {abc 234 6 7 | bcd 100 6 11 |}
|
||||
do_execsql_test skipscan1-1.4eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c=6 ORDER BY a, b, c;
|
||||
} {/* USING INDEX t1abc (ANY(a) AND ANY(b) AND c=?)*/}
|
||||
do_execsql_test skipscan1-1.4sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c=6 ORDER BY a, b, c;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
do_execsql_test skipscan1-1.5 {
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c IN (6,7) ORDER BY a, b, c;
|
||||
} {abc 234 6 7 | abc 345 7 8 | bcd 100 6 11 |}
|
||||
do_execsql_test skipscan1-1.5eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c IN (6,7) ORDER BY a, b, c;
|
||||
} {/* USING INDEX t1abc (ANY(a) AND ANY(b) AND c=?)*/}
|
||||
do_execsql_test skipscan1-1.5sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c IN (6,7) ORDER BY a, b, c;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
do_execsql_test skipscan1-1.6 {
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c BETWEEN 6 AND 7 ORDER BY a, b, c;
|
||||
} {abc 234 6 7 | abc 345 7 8 | bcd 100 6 11 |}
|
||||
do_execsql_test skipscan1-1.6eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c BETWEEN 6 AND 7 ORDER BY a, b, c;
|
||||
} {/* USING INDEX t1abc (ANY(a) AND ANY(b) AND c>? AND c<?)*/}
|
||||
do_execsql_test skipscan1-1.6sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE c BETWEEN 6 AND 7 ORDER BY a, b, c;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
do_execsql_test skipscan1-1.7 {
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b IN (234, 345) AND c BETWEEN 6 AND 7
|
||||
ORDER BY a, b;
|
||||
} {abc 234 6 7 | abc 345 7 8 |}
|
||||
do_execsql_test skipscan1-1.7eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b IN (234, 345) AND c BETWEEN 6 AND 7
|
||||
ORDER BY a, b;
|
||||
} {/* USING INDEX t1abc (ANY(a) AND b=? AND c>? AND c<?)*/}
|
||||
do_execsql_test skipscan1-1.7sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t1 WHERE b IN (234, 345) AND c BETWEEN 6 AND 7
|
||||
ORDER BY a, b;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
|
||||
# Joins
|
||||
#
|
||||
do_execsql_test skipscan1-1.51 {
|
||||
CREATE TABLE t1j(x TEXT, y INTEGER);
|
||||
INSERT INTO t1j VALUES('one',1),('six',6),('ninty-nine',99);
|
||||
INSERT INTO sqlite_stat1 VALUES('t1j',null,'3');
|
||||
ANALYZE sqlite_master;
|
||||
SELECT x, a, b, c, d, '|' FROM t1j, t1 WHERE c=y ORDER BY +a;
|
||||
} {six abc 234 6 7 | six bcd 100 6 11 |}
|
||||
do_execsql_test skipscan1-1.51eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT x, a, b, c, d, '|' FROM t1j, t1 WHERE c=y ORDER BY +a;
|
||||
} {/* INDEX t1abc (ANY(a) AND ANY(b) AND c=?)*/}
|
||||
|
||||
do_execsql_test skipscan1-1.52 {
|
||||
SELECT x, a, b, c, d, '|' FROM t1j LEFT JOIN t1 ON c=y ORDER BY +y, +a;
|
||||
} {one {} {} {} {} | six abc 234 6 7 | six bcd 100 6 11 | ninty-nine {} {} {} {} |}
|
||||
do_execsql_test skipscan1-1.52eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT x, a, b, c, d, '|' FROM t1j LEFT JOIN t1 ON c=y ORDER BY +y, +a;
|
||||
} {/* INDEX t1abc (ANY(a) AND ANY(b) AND c=?)*/}
|
||||
|
||||
do_execsql_test skipscan1-2.1 {
|
||||
CREATE TABLE t2(a TEXT, b INT, c INT, d INT,
|
||||
PRIMARY KEY(a,b,c));
|
||||
INSERT INTO t2 SELECT * FROM t1;
|
||||
|
||||
/* Fake the sqlite_stat1 table so that the query planner believes
|
||||
** the table contains thousands of rows and that the first few
|
||||
** columns are not selective. */
|
||||
ANALYZE;
|
||||
UPDATE sqlite_stat1 SET stat='10000 5000 2000 10' WHERE idx NOT NULL;
|
||||
ANALYZE sqlite_master;
|
||||
} {}
|
||||
|
||||
do_execsql_test skipscan1-2.2 {
|
||||
SELECT a,b,c,d,'|' FROM t2 WHERE b=345 ORDER BY a;
|
||||
} {abc 345 7 8 | def 345 9 10 |}
|
||||
do_execsql_test skipscan1-2.2eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t2 WHERE b=345 ORDER BY a;
|
||||
} {/* USING INDEX sqlite_autoindex_t2_1 (ANY(a) AND b=?)*/}
|
||||
do_execsql_test skipscan1-2.2sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t2 WHERE b=345 ORDER BY a;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
|
||||
do_execsql_test skipscan1-3.1 {
|
||||
CREATE TABLE t3(a TEXT, b INT, c INT, d INT,
|
||||
PRIMARY KEY(a,b,c)) WITHOUT ROWID;
|
||||
INSERT INTO t3 SELECT * FROM t1;
|
||||
|
||||
/* Fake the sqlite_stat1 table so that the query planner believes
|
||||
** the table contains thousands of rows and that the first few
|
||||
** columns are not selective. */
|
||||
ANALYZE;
|
||||
UPDATE sqlite_stat1 SET stat='10000 5000 2000 10' WHERE idx NOT NULL;
|
||||
ANALYZE sqlite_master;
|
||||
} {}
|
||||
|
||||
do_execsql_test skipscan1-3.2 {
|
||||
SELECT a,b,c,d,'|' FROM t3 WHERE b=345 ORDER BY a;
|
||||
} {abc 345 7 8 | def 345 9 10 |}
|
||||
do_execsql_test skipscan1-3.2eqp {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t3 WHERE b=345 ORDER BY a;
|
||||
} {/* INDEX sqlite_autoindex_t3_1 (ANY(a) AND b=?)*/}
|
||||
do_execsql_test skipscan1-3.2sort {
|
||||
EXPLAIN QUERY PLAN
|
||||
SELECT a,b,c,d,'|' FROM t3 WHERE b=345 ORDER BY a;
|
||||
} {~/*ORDER BY*/}
|
||||
|
||||
finish_test
|
||||
+17
-4
@@ -616,11 +616,24 @@ proc do_test {name cmd expected} {
|
||||
# regular expression PATTERN matches the result. "~/PATTERN/" means
|
||||
# the regular expression must not match.
|
||||
if {[string index $expected 0]=="~"} {
|
||||
set re [string map {# {[-0-9.]+}} [string range $expected 2 end-1]]
|
||||
set ok [expr {![regexp $re $result]}]
|
||||
set re [string range $expected 2 end-1]
|
||||
if {[string index $re 0]=="*"} {
|
||||
# If the regular expression begins with * then treat it as a glob instead
|
||||
set ok [string match $re $result]
|
||||
} else {
|
||||
set re [string map {# {[-0-9.]+}} $re]
|
||||
set ok [regexp $re $result]
|
||||
}
|
||||
set ok [expr {!$ok}]
|
||||
} else {
|
||||
set re [string map {# {[-0-9.]+}} [string range $expected 1 end-1]]
|
||||
set ok [regexp $re $result]
|
||||
set re [string range $expected 1 end-1]
|
||||
if {[string index $re 0]=="*"} {
|
||||
# If the regular expression begins with * then treat it as a glob instead
|
||||
set ok [string match $re $result]
|
||||
} else {
|
||||
set re [string map {# {[-0-9.]+}} $re]
|
||||
set ok [regexp $re $result]
|
||||
}
|
||||
}
|
||||
} elseif {[regexp {^~?\*.*\*$} $expected]} {
|
||||
# "expected" is of the form "*GLOB*" then the result if correct if
|
||||
|
||||
Reference in New Issue
Block a user