From: dan Date: Wed, 14 Aug 2013 19:54:12 +0000 (+0000) Subject: Change the way ANALYZE works to use a single cursor when scanning indices. X-Git-Tag: version-3.8.1~132^2~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f00e902580c1a0cb60395be63f50cc9575a857f5;p=thirdparty%2Fsqlite.git Change the way ANALYZE works to use a single cursor when scanning indices. FossilOrigin-Name: bdce612b35193abf72de1a563ea7962375b3574e --- diff --git a/manifest b/manifest index 2b09ccada8..35822cf8b7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C If\sENABLE_STAT3\sis\sdefined\sbut\sENABLE_STAT4\sis\snot,\shave\sANALYZE\screate\sand\spopulate\sthe\ssqlite_stat3\stable\sinstead\sof\ssqlite_stat4. -D 2013-08-12T20:14:04.167 +C Change\sthe\sway\sANALYZE\sworks\sto\suse\sa\ssingle\scursor\swhen\sscanning\sindices. +D 2013-08-14T19:54:12.120 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -157,7 +157,7 @@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc F sqlite3.pc.in ae6f59a76e862f5c561eb32a380228a02afc3cad F src/alter.c 2af0330bb1b601af7a7789bf7229675fd772a083 -F src/analyze.c cbd13a1b3c7122729814d34245ce2b4007262f5e +F src/analyze.c 37b78257fcf73790fd443b2c62b7ce62b78c3c98 F src/attach.c 1816f5a9eea8d2010fc2b22b44f0f63eb3a62704 F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34 F src/backup.c 43b348822db3e4cef48b2ae5a445fbeb6c73a165 @@ -307,7 +307,7 @@ F test/analyze4.test eff2df19b8dd84529966420f29ea52edc6b56213 F test/analyze5.test 765c4e284aa69ca172772aa940946f55629bc8c4 F test/analyze6.test 19151da2c4e918905d2081b74ac5c4d47fc850ab F test/analyze7.test bb1409afc9e8629e414387ef048b8e0e3e0bdc4f -F test/analyze8.test f3afd910c09f7d9b76f853e3f2f2fb169196d0b1 +F test/analyze8.test 093d15c1c888eed5034304a98c992f7360130b88 F test/analyze9.test 1b419d03407f2a6f4f1485620d54cb3e1bab3a71 F test/analyzeA.test 1a5c40079894847976d983ca39c707aaa44b6944 F test/async.test 1d0e056ba1bb9729283a0f22718d3a25e82c277b @@ -1107,7 +1107,7 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381 F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac -P 2a41736728d83a777ea8112da927cb047ec6684e -R ebabceacbf319f11921788de02fa2c3b +P cca8bf4372ab7a0258aa5c9397818415c6cf0abf +R 798b6e107d9cfc4282515cb67b7b0a49 U dan -Z 6caae0dd3ca2077b483e5c3e88d20d9e +Z 0758340c46c18a215a897e2672d1ddd5 diff --git a/manifest.uuid b/manifest.uuid index 5f36a65cc7..c82b437d55 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -cca8bf4372ab7a0258aa5c9397818415c6cf0abf \ No newline at end of file +bdce612b35193abf72de1a563ea7962375b3574e \ No newline at end of file diff --git a/src/analyze.c b/src/analyze.c index be6b9586f1..d8edadf885 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -140,10 +140,15 @@ #ifndef SQLITE_OMIT_ANALYZE #include "sqliteInt.h" -#ifdef SQLITE_ENABLE_STAT4 +#if defined(SQLITE_ENABLE_STAT4) +# define IsStat4 1 # define IsStat3 0 -#else +#elif defined(SQLITE_ENABLE_STAT3) +# define IsStat4 0 # define IsStat3 1 +#else +# define IsStat4 0 +# define IsStat3 0 #endif /* @@ -230,11 +235,12 @@ static void openStatTable( } } - /* Open the sqlite_stat[14] tables for writing. */ + /* Open the sqlite_stat[134] tables for writing. */ for(i=0; i1 ); /* >1 because it includes the rowid column */ /* Allocate the space required for the Stat4Accum object */ - n = sizeof(*p) + (sizeof(p->a[0]) + 3*sizeof(tRowcnt)*nCol)*mxSample; - p = sqlite3MallocZero( n ); + n = sizeof(*p) + + sizeof(tRowcnt)*nCol /* Stat4Accum.anEq */ + + sizeof(tRowcnt)*nCol /* Stat4Accum.anLt */ + + sizeof(tRowcnt)*nCol /* Stat4Accum.anDLt */ + + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */ + + sizeof(tRowcnt)*3*nCol*(nCol+mxSample); + p = sqlite3MallocZero(n); if( p==0 ){ sqlite3_result_error_nomem(context); return; } - /* Populate the new Stat4Accum object */ p->nRow = nRow; p->nCol = nCol; p->mxSample = mxSample; p->nPSample = p->nRow/(mxSample/3+1) + 1; + p->iGet = -1; + + p->current.anDLt = (tRowcnt*)&p[1]; + p->current.anEq = &p->current.anDLt[nCol]; + p->current.anLt = &p->current.anEq[nCol]; sqlite3_randomness(sizeof(p->iPrn), &p->iPrn); - p->a = (struct Stat4Sample*)&p[1]; - pSpace = (u8*)(&p->a[mxSample]); - for(i=0; ia = (struct Stat4Sample*)&p->current.anLt[nCol]; + p->aBest = &p->a[mxSample]; + pSpace = (u8*)(&p->a[mxSample+nCol]); + for(i=0; i<(mxSample+nCol); i++){ p->a[i].anEq = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nCol); p->a[i].anLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nCol); p->a[i].anDLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nCol); } assert( (pSpace - (u8*)p)==n ); + for(i=0; iaBest[i].iCol = i; + } + /* Return a pointer to the allocated object to the caller */ sqlite3_result_blob(context, p, sizeof(p), sqlite3_free); } -static const FuncDef stat4InitFuncdef = { - 3, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ - 0, /* pUserData */ - 0, /* pNext */ - stat4Init, /* xFunc */ - 0, /* xStep */ - 0, /* xFinalize */ - "stat4_init", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ +static const FuncDef statInitFuncdef = { + 3, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + statInit, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat_init", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ }; +/* +** Return true if pNew is to be preferred over pOld. +*/ +static int sampleIsBetter(Stat4Sample *pNew, Stat4Sample *pOld){ + tRowcnt nEqNew = pNew->anEq[pNew->iCol]; + tRowcnt nEqOld = pOld->anEq[pOld->iCol]; + + assert( pOld->isPSample==0 && pNew->isPSample==0 ); + assert( IsStat4 || (pNew->iCol==0 && pOld->iCol==0) ); + + if( (nEqNew>nEqOld) + || (nEqNew==nEqOld && pNew->iColiCol) + || (nEqNew==nEqOld && pNew->iCol==pOld->iCol && pNew->iHash>pOld->iHash) + ){ + return 1; + } + return 0; +} + +/* +** Copy the contents of object (*pFrom) into (*pTo). +*/ +void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ + pTo->iRowid = pFrom->iRowid; + pTo->isPSample = pFrom->isPSample; + pTo->iCol = pFrom->iCol; + pTo->iHash = pFrom->iHash; + memcpy(pTo->anEq, pFrom->anEq, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anLt, pFrom->anLt, sizeof(tRowcnt)*p->nCol); + memcpy(pTo->anDLt, pFrom->anDLt, sizeof(tRowcnt)*p->nCol); +} /* -** Implementation of the stat4_push SQL function. The arguments describe a -** single key instance. This routine makes the decision about whether or -** not to retain this key for the sqlite_stat4 table. -** -** The calling convention is: -** -** stat4_push(P, rowid, ...nEq args..., ...nLt args..., ...nDLt args...) +** Copy the contents of sample *pNew into the p->a[] array. If necessary, +** remove the least desirable sample from p->a[] to make room. +*/ +static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ + Stat4Sample *pSample; + int i; + i64 iSeq; + i64 iPos; + + assert( IsStat4 || nEqZero==0 ); + + if( pNew->isPSample==0 ){ + assert( pNew->anEq[pNew->iCol]>0 ); + + /* This sample is being added because the prefix that ends in column + ** iCol occurs many times in the table. However, if we have already + ** added a sample that shares this prefix, there is no need to add + ** this one. Instead, upgrade the priority of the existing sample. */ + for(i=p->nSample-1; i>=0; i--){ + Stat4Sample *pOld = &p->a[i]; + if( pOld->anEq[pNew->iCol]==0 ){ + if( pOld->isPSample==0 ){ + assert( sampleIsBetter(pNew, pOld) ); + assert( pOld->iCol>pNew->iCol ); + pOld->iCol = pNew->iCol; + } + goto find_new_min; + } + } + } + + /* If necessary, remove sample iMin to make room for the new sample. */ + if( p->nSample>=p->mxSample ){ + Stat4Sample *pMin = &p->a[p->iMin]; + tRowcnt *anEq = pMin->anEq; + tRowcnt *anLt = pMin->anLt; + tRowcnt *anDLt = pMin->anDLt; + memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-p->iMin-1)); + pSample = &p->a[p->nSample-1]; + pSample->anEq = anEq; + pSample->anDLt = anDLt; + pSample->anLt = anLt; + p->nSample = p->mxSample-1; + } + + /* Figure out where in the a[] array the new sample should be inserted. */ + iSeq = pNew->anLt[p->nCol-1]; + for(iPos=p->nSample; iPos>0; iPos--){ + if( iSeq>p->a[iPos-1].anLt[p->nCol-1] ) break; + } + + /* Insert the new sample */ + pSample = &p->a[iPos]; + if( iPos!=p->nSample ){ + Stat4Sample *pEnd = &p->a[p->nSample]; + tRowcnt *anEq = pEnd->anEq; + tRowcnt *anLt = pEnd->anLt; + tRowcnt *anDLt = pEnd->anDLt; + memmove(&p->a[iPos], &p->a[iPos+1], (p->nSample-iPos)*sizeof(p->a[0])); + pSample->anEq = anEq; + pSample->anDLt = anDLt; + pSample->anLt = anLt; + } + p->nSample++; + sampleCopy(p, pSample, pNew); + + /* Zero the first nEqZero entries in the anEq[] array. */ + memset(pSample->anEq, 0, sizeof(tRowcnt)*nEqZero); + + find_new_min: + if( p->nSample>=p->mxSample ){ + int iMin = -1; + for(i=0; imxSample; i++){ + if( p->a[i].isPSample ) continue; + if( iMin<0 || sampleIsBetter(&p->a[iMin], &p->a[i]) ){ + iMin = i; + } + } + assert( iMin>=0 ); + p->iMin = iMin; + } +} + +/* +** Field iChng of the index being scanned has changed. So at this point +** p->current contains a sample that reflects the previous row of the +** index. The value of anEq[iChng] and subsequent anEq[] elements are +** correct at this point. +*/ +static void samplePushPrevious(Stat4Accum *p, int iChng){ + if( IsStat4 ){ + int i; + + /* Check if any samples from the aBest[] array should be pushed + ** into IndexSample.a[] at this point. */ + for(i=(p->nCol-2); i>=iChng; i--){ + Stat4Sample *pBest = &p->aBest[i]; + if( p->nSamplemxSample + || sampleIsBetter(pBest, &p->a[p->iMin]) + ){ + sampleInsert(p, pBest, i); + } + } + + /* Update the anEq[] fields of any samples already collected. */ + for(i=p->nSample-1; i>=0; i--){ + int j; + for(j=iChng; jnCol; j++){ + if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j]; + } + } + } + + if( IsStat3 && iChng==0 ){ + tRowcnt nLt = p->current.anLt[0]; + tRowcnt nEq = p->current.anEq[0]; + + /* Check if this is to be a periodic sample. If so, add it. */ + if( (nLt/p->nPSample)!=(nLt+nEq)/p->nPSample ){ + p->current.isPSample = 1; + sampleInsert(p, &p->current, 0); + p->current.isPSample = 0; + }else + + /* Or if it is a non-periodic sample. Add it in this case too. */ + if( p->nSamplemxSample || sampleIsBetter(&p->current, &p->a[p->iMin]) ){ + sampleInsert(p, &p->current, 0); + } + } +} + +/* +** Implementation of the stat_push SQL function. ** -** where each instance of the "...nXX args..." is replaced by an array of -** nCol arguments, where nCol is the number of columns in the index being -** sampled (if the index being sampled is "CREATE INDEX i ON t(a, b)", a -** total of 8 arguments are passed when this function is invoked). +** stat_push(P,R,C) ** ** The return value is always NULL. */ -static void stat4Push( +static void statPush( sqlite3_context *context, int argc, sqlite3_value **argv ){ + int i; + + /* The three function arguments */ Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]); i64 rowid = sqlite3_value_int64(argv[1]); - int bNewKey = sqlite3_value_int(argv[2]); - struct Stat4Sample *pSample; - u32 h; /* Hash value for this key */ - int iMin = p->iMin; - int i; - int nSampleCol; /* Number of fields in samples */ - u8 isPSample = 0; /* True if this is a periodic sample */ - u8 doInsert = 0; + int iChng = sqlite3_value_int(argv[2]); - sqlite3_value **aEq = &argv[3]; - sqlite3_value **aLt = &argv[3+p->nCol]; - sqlite3_value **aDLt = &argv[3+p->nCol+p->nCol]; + assert( p->nCol>1 ); /* Includes rowid field */ + assert( iChngnCol ); - i64 nLt; - i64 nEq; + /* p->current.anEq[0] is false the first time this function is called. */ + if( p->current.anEq[0] ){ - UNUSED_PARAMETER(context); - UNUSED_PARAMETER(argc); - assert( p->nCol>0 ); - assert( argc==(3 + 3*p->nCol) ); - assert( p->bHaveNonP==0 || p->bHaveP==0 ); - - if( IsStat3 ){ - /* Stat3 builds ignore any call with bNewKey==0. And consider only - ** the first column of the index keys. */ - if( bNewKey==0 ) return; - nEq = sqlite3_value_int64(aEq[0]); - nSampleCol = 1; - }else{ - nEq = 1; - nSampleCol = p->nCol; - } - nLt = sqlite3_value_int64(aLt[nSampleCol-1]); + samplePushPrevious(p, iChng); - if( bNewKey ){ - p->bHaveP = 0; - p->bHaveNonP = 0; - } - h = p->iPrn = p->iPrn*1103515245 + 12345; - - /* Check if this should be a periodic sample. If this is a periodic - ** sample and there is already a non-periodic sample for this key, - ** replace it. */ - if( (nLt/p->nPSample) != (nLt+nEq)/p->nPSample ){ - doInsert = isPSample = 1; - if( p->bHaveNonP ){ - p->nSample--; - p->bHaveNonP = 0; - p->bHaveP = 1; - assert( p->nSamplemxSample ); - assert( p->a[p->nSample].isPSample==0 ); + /* Update anDLt[], anLt[] and anEq[] to reflect the values that apply + ** to the current row of the index. */ + for(i=0; icurrent.anEq[i]++; } - - /* Or, if this is not a periodic sample, and there is already at least one - ** periodic sample, return early. */ - }else if( p->bHaveP ){ - /* no-op */ - - /* If there is already a non-periodic sample for the key, but this one - ** has a higher hash score, replace the existing sample. */ - }else if( p->bHaveNonP ){ - if( p->a[p->nSample-1].iHashnSample--; - doInsert = 1; + for(i=iChng; inCol; i++){ + p->current.anDLt[i]++; + p->current.anLt[i] += p->current.anEq[i]; + p->current.anEq[i] = 1; } - /* Finally, check if this should be added as a non-periodic sample. */ - }else if( p->nSamplemxSample ){ - doInsert = 1; - p->bHaveNonP = 1; }else{ - tRowcnt *aMinEq = p->a[iMin].anEq; - for(i=(IsStat3 ? 0 : p->nCol-2); i>=0; i--){ - i64 nEq = sqlite3_value_int64(aEq[i]); - if( nEqaMinEq[i] ){ - doInsert = 1; - break; - } - } - if( i<0 && h>p->a[iMin].iHash ){ - doInsert = 1; - } - p->bHaveNonP = doInsert; + for(i=0; inCol; i++) p->current.anEq[i] = 1; } - if( doInsert==0 ) return; - /* Fill in the new Stat4Sample object. */ - if( p->nSample==p->mxSample ){ - struct Stat4Sample *pMin = &p->a[iMin]; - tRowcnt *anEq = pMin->anEq; - tRowcnt *anDLt = pMin->anDLt; - tRowcnt *anLt = pMin->anLt; - assert( p->nSample - iMin - 1 >= 0 ); - memmove(pMin, &pMin[1], sizeof(p->a[0])*(p->nSample-iMin-1)); - pSample = &p->a[p->nSample-1]; - pSample->anEq = anEq; - pSample->anDLt = anDLt; - pSample->anLt = anLt; - }else{ - pSample = &p->a[p->nSample++]; - } - pSample->iRowid = rowid; - pSample->iHash = h; - pSample->isPSample = isPSample; - for(i=0; ianEq[i] = sqlite3_value_int64(aEq[i]); - pSample->anLt[i] = sqlite3_value_int64(aLt[i]); - pSample->anDLt[i] = sqlite3_value_int64(aDLt[i])-1; - assert( sqlite3_value_int64(aDLt[i])>0 ); + if( IsStat4 || IsStat3 ){ + p->current.iRowid = rowid; + p->current.iHash = p->iPrn = p->iPrn*1103515245 + 12345; } - /* Find the new minimum */ - if( p->nSample==p->mxSample ){ - iMin = -1; - for(i=0; imxSample; i++){ - if( p->a[i].isPSample ) continue; - if( iMin<0 ){ - iMin = i; - }else{ - int j; - for(j=nSampleCol-1; j>=0; j++){ - i64 iCmp = (p->a[iMin].anEq[j] - p->a[i].anEq[j]); - if( iCmp<0 ){ iMin = i; } - if( iCmp ) break; - } - if( j==0 && p->a[iMin].iHasha[i].iHash ){ - iMin = i; - } + if( IsStat4 ){ + tRowcnt nLt = p->current.anLt[p->nCol-1]; + + /* Check if this is to be a periodic sample. If so, add it. */ + if( (nLt/p->nPSample)!=(nLt+1)/p->nPSample ){ + p->current.isPSample = 1; + p->current.iCol = 0; + sampleInsert(p, &p->current, p->nCol-1); + p->current.isPSample = 0; + } + + /* Update the aBest[] array. */ + for(i=0; i<(p->nCol-1); i++){ + p->current.iCol = i; + if( i>=iChng || sampleIsBetter(&p->current, &p->aBest[i]) ){ + sampleCopy(p, &p->aBest[i], &p->current); } } - assert( iMin>=0 ); - p->iMin = iMin; } } -static const FuncDef stat4PushFuncdef = { - -1, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ - 0, /* pUserData */ - 0, /* pNext */ - stat4Push, /* xFunc */ - 0, /* xStep */ - 0, /* xFinalize */ - "stat4_push", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ +static const FuncDef statPushFuncdef = { + 3, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + statPush, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat_push", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ }; +#define STAT_GET_STAT1 0 /* "stat" column of stat1 table */ +#define STAT_GET_ROWID 1 /* "rowid" column of stat[34] entry */ +#define STAT_GET_NEQ 2 /* "neq" column of stat[34] entry */ +#define STAT_GET_NLT 3 /* "nlt" column of stat[34] entry */ +#define STAT_GET_NDLT 4 /* "ndlt" column of stat[34] entry */ + /* ** Implementation of the stat3_get(P,N,...) SQL function. This routine is ** used to query the results. Content is returned for the Nth sqlite_stat3 @@ -535,32 +628,87 @@ static const FuncDef stat4PushFuncdef = { ** argc==4 result: nLt ** argc==5 result: nDLt */ -static void stat4Get( +static void statGet( sqlite3_context *context, int argc, sqlite3_value **argv ){ Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]); - int n = sqlite3_value_int(argv[1]); + int eCall = sqlite3_value_int(argv[1]); + assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ + || eCall==STAT_GET_ROWID || eCall==STAT_GET_NLT + || eCall==STAT_GET_NDLT + ); + + if( eCall==STAT_GET_STAT1 ){ + /* Return the value to store in the "stat" column of the sqlite_stat1 + ** table for this index. + ** + ** The value is a string composed of a list of integers describing + ** the index. The first integer in the list is the total number of + ** entries in the index. There is one additional integer in the list + ** for each indexed column. This additional integer is an estimate of + ** the number of rows matched by a stabbing query on the index using + ** a key with the corresponding number of fields. In other words, + ** if the index is on columns (a,b) and the sqlite_stat1 value is + ** "100 10 2", then SQLite estimates that: + ** + ** * the index contains 100 rows, + ** * "WHERE a=?" matches 10 rows, and + ** * "WHERE a=? AND b=?" matches 2 rows. + ** + ** If D is the count of distinct values and K is the total number of + ** rows, then each estimate is computed as: + ** + ** I = (K+D-1)/D + */ + char *z; + int i; + + char *zRet = sqlite3MallocZero(p->nCol * 25); + if( zRet==0 ){ + sqlite3_result_error_nomem(context); + return; + } + + sqlite3_snprintf(24, zRet, "%lld", p->nRow); + z = zRet + sqlite3Strlen30(zRet); + for(i=0; i<(p->nCol-1); i++){ + i64 nDistinct = p->current.anDLt[i] + 1; + i64 iVal = (p->nRow + nDistinct - 1) / nDistinct; + sqlite3_snprintf(24, z, " %lld", iVal); + z += sqlite3Strlen30(z); + assert( p->current.anEq[i] ); + } + assert( z[0]=='\0' && z>zRet ); - assert( p!=0 ); - if( nnSample ){ + sqlite3_result_text(context, zRet, -1, sqlite3_free); + }else if( eCall==STAT_GET_ROWID ){ + if( p->iGet<0 ){ + samplePushPrevious(p, 0); + p->iGet = 0; + } + if( p->iGetnSample ){ + sqlite3_result_int64(context, p->a[p->iGet].iRowid); + } + }else{ tRowcnt *aCnt = 0; - char *zRet; - - switch( argc ){ - case 2: - sqlite3_result_int64(context, p->a[n].iRowid); - return; - case 3: aCnt = p->a[n].anEq; break; - case 4: aCnt = p->a[n].anLt; break; - default: aCnt = p->a[n].anDLt; break; + + assert( p->iGetnSample ); + switch( eCall ){ + case STAT_GET_NEQ: aCnt = p->a[p->iGet].anEq; break; + case STAT_GET_NLT: aCnt = p->a[p->iGet].anLt; break; + default: { + aCnt = p->a[p->iGet].anDLt; + p->iGet++; + break; + } } if( IsStat3 ){ sqlite3_result_int64(context, (i64)aCnt[0]); }else{ - zRet = sqlite3MallocZero(p->nCol * 25); + char *zRet = sqlite3MallocZero(p->nCol * 25); if( zRet==0 ){ sqlite3_result_error_nomem(context); }else{ @@ -577,23 +725,27 @@ static void stat4Get( } } } -static const FuncDef stat4GetFuncdef = { - -1, /* nArg */ - SQLITE_UTF8, /* iPrefEnc */ - 0, /* flags */ - 0, /* pUserData */ - 0, /* pNext */ - stat4Get, /* xFunc */ - 0, /* xStep */ - 0, /* xFinalize */ - "stat4_get", /* zName */ - 0, /* pHash */ - 0 /* pDestructor */ +static const FuncDef statGetFuncdef = { + 2, /* nArg */ + SQLITE_UTF8, /* iPrefEnc */ + 0, /* flags */ + 0, /* pUserData */ + 0, /* pNext */ + statGet, /* xFunc */ + 0, /* xStep */ + 0, /* xFinalize */ + "stat_get", /* zName */ + 0, /* pHash */ + 0 /* pDestructor */ }; -#endif /* SQLITE_ENABLE_STAT4 */ - - +static void callStatGet(Vdbe *v, int regStat4, int iParam, int regOut){ + assert( regOut!=regStat4 && regOut!=regStat4+1 ); + sqlite3VdbeAddOp2(v, OP_Integer, iParam, regStat4+1); + sqlite3VdbeAddOp3(v, OP_Function, 0, regStat4, regOut); + sqlite3VdbeChangeP4(v, -1, (char*)&statGetFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, 2); +} /* ** Generate code to do an analysis of all indices associated with @@ -616,29 +768,17 @@ static void analyzeOneTable( int jZeroRows = -1; /* Jump from here if number of rows is zero */ int iDb; /* Index of database containing pTab */ u8 needTableCnt = 1; /* True to count the table */ - int regTabname = iMem++; /* Register containing table name */ - int regIdxname = iMem++; /* Register containing index name */ - int regStat1 = iMem++; /* The stat column of sqlite_stat1 */ -#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_ENABLE_STAT3) - int regNumEq = regStat1; /* Number of instances. Same as regStat1 */ - int regNumLt = iMem++; /* Number of keys less than regSample */ - int regNumDLt = iMem++; /* Number of distinct keys less than regSample */ - int regSample = iMem++; /* The next sample value */ - int regLoop = iMem++; /* Loop counter */ - int shortJump = 0; /* Instruction address */ -#endif - int regCol = iMem++; /* Content of a column in analyzed table */ - int regRec = iMem++; /* Register holding completed record */ - int regTemp = iMem++; /* Temporary use register */ int regNewRowid = iMem++; /* Rowid for the inserted record */ - int regEof = iMem++; /* True once cursors are all at EOF */ - int regCnt = iMem++; /* Row counter */ - int regStat4 = iMem++; /* Register to hold Stat4Accum object */ - int regRowid = iMem++; /* Rowid argument passed to stat4_push() */ - int regKeychng = iMem++; /* True if key has changed */ + int regRowid = iMem++; /* Rowid argument passed to stat_push() */ + int regChng = iMem++; /* Index of changed index field */ + int regTemp = iMem++; /* Temporary use register */ + int regTabname = iMem++; /* Register containing table name */ + int regIdxname = iMem++; /* Register containing index name */ + int regStat1 = iMem++; /* Value for the stat column of sqlite_stat1 */ + int regPrev = iMem; /* MUST BE LAST (see below) */ - pParse->nMem = MAX(pParse->nMem, regKeychng); + pParse->nMem = MAX(pParse->nMem, regChng); v = sqlite3GetVdbe(pParse); if( v==0 || NEVER(pTab==0) ){ return; @@ -663,9 +803,12 @@ static void analyzeOneTable( #endif /* Establish a read-lock on the table at the shared-cache level. - ** Also open a read-only cursor on the table. */ + ** Open a read-only cursor on the table. Also allocate a cursor number + ** to use for scanning indexes (iIdxCur). No index cursor is opened at + ** this time though. */ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); iTabCur = iTab++; + iIdxCur = iTab++; pParse->nTab = MAX(pParse->nTab, iTab); sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0); @@ -673,324 +816,192 @@ static void analyzeOneTable( for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int nCol; /* Number of columns indexed by pIdx */ KeyInfo *pKey; /* KeyInfo structure for pIdx */ - int *aChngAddr; /* Array of jump instruction addresses */ - int regPrev; /* First in array of previous values */ - int regDLte; /* First in array of nDlt registers */ - int regLt; /* First in array of nLt registers */ - int regEq; /* First in array of nEq registers */ - int endOfScan; /* Label to jump to once scan is finished */ + int *aGotoChng; /* Array of jump instruction addresses */ + int addrRewind; /* Address of "OP_Rewind iIdxCur" */ + int addrGotoChng0; /* Address of "Goto addr_chng_0" */ + int addrNextRow; /* Address of "next_row:" */ if( pOnlyIdx && pOnlyIdx!=pIdx ) continue; if( pIdx->pPartIdxWhere==0 ) needTableCnt = 0; VdbeNoopComment((v, "Begin analysis of %s", pIdx->zName)); nCol = pIdx->nColumn; - aChngAddr = sqlite3DbMallocRaw(db, sizeof(int)*(nCol+1)); - if( aChngAddr==0 ) continue; + aGotoChng = sqlite3DbMallocRaw(db, sizeof(int)*(nCol+1)); + if( aGotoChng==0 ) continue; pKey = sqlite3IndexKeyinfo(pParse, pIdx); /* Populate the register containing the index name. */ sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0); /* - ** The following pseudo-code demonstrates the way the VM scans an index - ** to call stat4_push() and collect the values for the sqlite_stat1 - ** entry. The code below is for an index with 2 columns. The actual - ** VM code generated may be for any number of columns. + ** Pseudo-code for loop that calls stat_push(): ** - ** One cursor is opened for each column in the index and one for the - ** rowid column (nCol+1 in total). All cursors scan concurrently the - ** index from start to end. All variables used in the pseudo-code are - ** initialized to zero. + ** Rewind csr + ** if eof(csr) goto end_of_scan; + ** regChng = 0 + ** goto chng_addr_0; ** - ** Rewind csr(0) - ** Rewind csr(1) - ** Rewind csr(2) - ** - ** next_0: - ** regPrev(0) = csr(0)[0] - ** regDLte(0) += 1 - ** regLt(0) += regEq(0) - ** regEq(0) = 0 - ** do { - ** regEq(0) += 1 - ** Next csr(0) - ** }while ( csr(0)[0] == regPrev(0) ) - ** if( IsStat3 ) regKeychng = 1 - ** - ** next_1: - ** regPrev(1) = csr(1)[1] - ** regDLte(1) += 1 - ** regLt(1) += regEq(1) - ** regEq(1) = 0 - ** do { - ** regEq(1) += 1 - ** Next csr(1) - ** }while ( csr(1)[0..1] == regPrev(0..1) ) - ** - ** if( IsStat3==0 ) regKeychng = 1 ** next_row: - ** regRowid = csr(2)[rowid] - ** regEq(2) = 1 - ** regLt(2) = regCnt - ** regCnt += 1 - ** regDLte(2) = regCnt - ** stat4_push(regRowid, regKeychng, regEq, regLt, regDLte); - ** regKeychng = 0 - ** Next csr(2) - ** if( eof( csr(2) ) ) goto endOfScan - ** - ** if( csr(2)[0] != regPrev(0) ) goto next_0 - ** if( csr(2)[1] != regPrev(1) ) goto next_1 - ** goto next_row + ** regChng = 0 + ** if( idx(0) != regPrev(0) ) goto chng_addr_0 + ** regChng = 1 + ** if( idx(1) != regPrev(1) ) goto chng_addr_1 + ** ... + ** regChng = N + ** goto chng_addr_N ** - ** endOfScan: - ** // done! + ** chng_addr_0: + ** regPrev(0) = idx(0) + ** chng_addr_1: + ** regPrev(1) = idx(1) + ** ... ** - ** The last two lines above modify the contents of the regDLte array - ** so that each element contains the number of distinct key prefixes - ** of the corresponding length. As required to calculate the contents - ** of the sqlite_stat1 entry. + ** chng_addr_N: + ** regRowid = idx(rowid) + ** stat_push(P, regRowid, regChng) + ** Next csr + ** if !eof(csr) goto next_row; ** - ** At this point, the last memory cell allocated (that with the largest - ** integer identifier) is regKeychng. Immediately following regKeychng - ** we allocate the following: - ** - ** regEq - nCol registers - ** regLt - nCol+1 registers - ** regDLte - nCol+1 registers - ** regPrev - nCol+1 registers - ** - ** can be passed to the stat4_push() function. - ** - ** All of the above are initialized to contain integer value 0. + ** end_of_scan: */ - regEq = regKeychng+1; /* First in array of nEq value registers */ - regLt = regEq+nCol+1; /* First in array of nLt value registers */ - regDLte = regLt+nCol+1; /* First in array of nDLt value registers */ - regPrev = regDLte+nCol+1; /* First in array of prev. value registers */ + + /* Make sure there are enough memory cells allocated to accommodate + ** the regPrev array and a trailing rowid (the rowid slot is required + ** when building a record to insert into the sample column of + ** the sqlite_stat4 table. */ pParse->nMem = MAX(pParse->nMem, regPrev+nCol); - /* Open a read-only cursor for each column of the index. And one for - ** the rowid column. A total of (nCol+1) cursors. */ + /* Open a read-only cursor on the index being analyzed. */ assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); - iIdxCur = iTab; - pParse->nTab = MAX(pParse->nTab, iTab+nCol+1); - for(i=0; i<(nCol+1); i++){ - int iMode = (i==0 ? P4_KEYINFO_HANDOFF : P4_KEYINFO); - sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur+i, pIdx->tnum, iDb); - sqlite3VdbeChangeP4(v, -1, (char*)pKey, iMode); - VdbeComment((v, "%s", pIdx->zName)); - } + sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb); + sqlite3VdbeChangeP4(v, -1, (char*)pKey, P4_KEYINFO_HANDOFF); + VdbeComment((v, "%s", pIdx->zName)); -#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_ENABLE_STAT3) - /* Invoke the stat4_init() function. The arguments are: + /* Invoke the stat_init() function. The arguments are: ** ** * the number of rows in the index, ** * the number of columns in the index including the rowid, - ** * the recommended number of samples for the stat4 table. - ** - ** If this is a stat3 build, the number of columns in the index is - ** set to 1 (as this is the number of index fields gathered). + ** * the recommended number of samples for the stat3/stat4 table. */ sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4+1); sqlite3VdbeAddOp2(v, OP_Integer, nCol+1, regStat4+2); sqlite3VdbeAddOp2(v, OP_Integer, SQLITE_STAT4_SAMPLES, regStat4+3); sqlite3VdbeAddOp3(v, OP_Function, 0, regStat4+1, regStat4); - sqlite3VdbeChangeP4(v, -1, (char*)&stat4InitFuncdef, P4_FUNCDEF); + sqlite3VdbeChangeP4(v, -1, (char*)&statInitFuncdef, P4_FUNCDEF); sqlite3VdbeChangeP5(v, 3); -#endif /* SQLITE_ENABLE_STAT4 */ - /* Initialize all the memory registers allocated above to 0. */ - for(i=regEq; iazColl[i]); - int iCsr = iIdxCur+i; - int iDo; - int iNe; /* Jump here to exit do{...}while loop */ - int j; - - /* Implementation of the following pseudo-code: - ** - ** regPrev(i) = csr(i)[i] - ** regDLte(i) += 1 - ** regLt(i) += regEq(i) - ** regEq(i) = 0 - ** regRowid = csr(i)[rowid] // innermost cursor only - */ - aChngAddr[i] = sqlite3VdbeAddOp3(v, OP_Column, iCsr, i, regPrev+i); - VdbeComment((v, "regPrev(%d) = csr(%d)(%d)", i, i, i)); - sqlite3VdbeAddOp2(v, OP_AddImm, regDLte+i, 1); - VdbeComment((v, "regDLte(%d) += 1", i)); - sqlite3VdbeAddOp3(v, OP_Add, regEq+i, regLt+i, regLt+i); - VdbeComment((v, "regLt(%d) += regEq(%d)", i, i)); - sqlite3VdbeAddOp2(v, OP_Integer, 0, regEq+i); - VdbeComment((v, "regEq(%d) = 0", i)); - - /* This bit: - ** - ** do { - ** regEq(i) += 1 - ** Next csr(i) - ** if( Eof csr(i) ){ - ** break - ** } - ** }while ( csr(i)[0..i] == regPrev(0..i) ) - */ - iDo = sqlite3VdbeAddOp2(v, OP_AddImm, regEq+i, 1); - VdbeComment((v, "regEq(%d) += 1", i)); - sqlite3VdbeAddOp2(v, OP_Next, iCsr, sqlite3VdbeCurrentAddr(v)+2); - iNe = sqlite3VdbeMakeLabel(v); - sqlite3VdbeAddOp2(v, OP_Goto, 0, iNe); - for(j=0; j<=i; j++){ - sqlite3VdbeAddOp3(v, OP_Column, iCsr, j, regCol); - sqlite3VdbeAddOp4(v, OP_Ne, regCol, iNe, regPrev+j, pColl, P4_COLLSEQ); - sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); - VdbeComment((v, "if( regPrev(%d) != csr(%d)(%d) )", j, i, j)); - } - sqlite3VdbeAddOp2(v, OP_Goto, 0, iDo); - sqlite3VdbeResolveLabel(v, iNe); - - if( IsStat3 && i==0 ){ - sqlite3VdbeAddOp2(v, OP_Integer, 1, regKeychng); - } - } + /* Implementation of the following: + ** + ** Rewind csr + ** if eof(csr) goto end_of_scan; + ** regChng = 0 + ** goto next_push_0; + ** + */ + addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng); + addrGotoChng0 = sqlite3VdbeAddOp0(v, OP_Goto); - /* This stuff: - ** - ** regKeychng = 1 + /* ** next_row: - ** regRowid = csr(2)[rowid] - ** regEq(2) = 1 - ** regLt(2) = regCnt - ** regCnt += 1 - ** regDLte(2) = regCnt - ** stat4_push(regRowid, regKeychng, regEq, regLt, regDLte); - ** regKeychng = 0 - ** Next csr(2) - ** if( eof( csr(2) ) ) goto endOfScan + ** regChng = 0 + ** if( idx(0) != regPrev(0) ) goto chng_addr_0 + ** regChng = 1 + ** if( idx(1) != regPrev(1) ) goto chng_addr_1 + ** ... + ** regChng = N + ** goto chng_addr_N */ -#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_ENABLE_STAT3) - if( 0==IsStat3 ){ - sqlite3VdbeAddOp2(v, OP_Integer, 1, regKeychng); - } - aChngAddr[nCol] = - sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur+nCol, regRowid); - sqlite3VdbeAddOp2(v, OP_Integer, 1, regEq+nCol); - sqlite3VdbeAddOp2(v, OP_Copy, regCnt, regLt+nCol); - sqlite3VdbeAddOp2(v, OP_AddImm, regCnt, 1); - sqlite3VdbeAddOp2(v, OP_Copy, regCnt, regDLte+nCol); - sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regTemp); - sqlite3VdbeChangeP4(v, -1, (char*)&stat4PushFuncdef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, 3 + 3*(nCol+1)); - sqlite3VdbeAddOp2(v, OP_Integer, 0, regKeychng); - sqlite3VdbeAddOp2(v, OP_Next, iIdxCur+nCol, sqlite3VdbeCurrentAddr(v)+2); - sqlite3VdbeAddOp2(v, OP_Goto, 0, endOfScan); -#endif - - sqlite3VdbeAddOp2(v, OP_If, regEof, endOfScan); + addrNextRow = sqlite3VdbeCurrentAddr(v); for(i=0; iazColl[i]); - sqlite3VdbeAddOp3(v, OP_Column, iIdxCur+nCol, i, regCol); - sqlite3VdbeAddOp3(v, OP_Ne, regCol, aChngAddr[i], regPrev+i); - sqlite3VdbeChangeP4(v, -1, pColl, P4_COLLSEQ); + sqlite3VdbeAddOp2(v, OP_Integer, i, regChng); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regTemp); + aGotoChng[i] = + sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0, regPrev+i, pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); } - sqlite3VdbeAddOp2(v, OP_Goto, 0, aChngAddr[nCol]); - sqlite3DbFree(db, aChngAddr); - - sqlite3VdbeResolveLabel(v, endOfScan); + sqlite3VdbeAddOp2(v, OP_Integer, nCol, regChng); + aGotoChng[nCol] = sqlite3VdbeAddOp0(v, OP_Goto); -#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_ENABLE_STAT3) - /* Add rows to the sqlite_stat4 table */ - regLoop = regStat4+1; - sqlite3VdbeAddOp2(v, OP_Integer, -1, regLoop); - shortJump = sqlite3VdbeAddOp2(v, OP_AddImm, regLoop, 1); - sqlite3VdbeAddOp3(v, OP_Function, 0, regStat4, regEq+nCol); - sqlite3VdbeChangeP4(v, -1, (char*)&stat4GetFuncdef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, 2); - sqlite3VdbeAddOp1(v, OP_IsNull, regEq+nCol); - - sqlite3VdbeAddOp3(v, OP_NotExists, iTabCur, shortJump, regEq+nCol); - if( IsStat3==0 ){ - for(i=0; iaiColumn[i]; - sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, iCol, regEq+i); - } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regEq, nCol+1, regSample); - sqlite3VdbeChangeP4(v, -1, pIdx->zColAff, 0); - }else{ - int iCol = pIdx->aiColumn[0]; - sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, iCol, regSample); + /* + ** chng_addr_0: + ** regPrev(0) = idx(0) + ** chng_addr_1: + ** regPrev(1) = idx(1) + ** ... + */ + sqlite3VdbeJumpHere(v, addrGotoChng0); + for(i=0; inMem = MAX(pParse->nMem, regCol+nCol+1); + + addrNext = sqlite3VdbeCurrentAddr(v); + callStatGet(v, regStat4, STAT_GET_ROWID, regSampleRowid); + addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regSampleRowid); + callStatGet(v, regStat4, STAT_GET_NEQ, regEq); + callStatGet(v, regStat4, STAT_GET_NLT, regLt); + callStatGet(v, regStat4, STAT_GET_NDLT, regDLt); + sqlite3VdbeAddOp3(v, OP_NotExists, iTabCur, addrNext, regSampleRowid); + if( IsStat3 ){ + int iCol = pIdx->aiColumn[0]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, iCol, regSample); + }else{ + for(i=0; iaiColumn[i]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, iCol, regCol+i); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regCol, nCol+1, regSample); + } - /* Store the results in sqlite_stat1. - ** - ** The result is a single row of the sqlite_stat1 table. The first - ** two columns are the names of the table and index. The third column - ** is a string composed of a list of integer statistics about the - ** index. The first integer in the list is the total number of entries - ** in the index. There is one additional integer in the list for each - ** column of the table. This additional integer is a guess of how many - ** rows of the table the index will select. If D is the count of distinct - ** values and K is the total number of rows, then the integer is computed - ** as: - ** - ** I = (K+D-1)/D - ** - ** If K==0 then no entry is made into the sqlite_stat1 table. - ** If K>0 then it is always the case the D>0 so division by zero - ** is never possible. - */ - sqlite3VdbeAddOp2(v, OP_SCopy, regCnt, regStat1); - jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regCnt); - for(i=0; ipPartIdxWhere!=0 ) sqlite3VdbeJumpHere(v, jZeroRows); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); - sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regNewRowid); - sqlite3VdbeChangeP5(v, OPFLAG_APPEND); - if( pIdx->pPartIdxWhere==0 ) sqlite3VdbeJumpHere(v, jZeroRows); + + /* Jump here if the index is empty */ + sqlite3VdbeJumpHere(v, addrRewind); + sqlite3DbFree(db, aGotoChng); } + /* Create a single sqlite_stat1 entry containing NULL as the index ** name and the row count as the content. */ @@ -999,9 +1010,9 @@ static void analyzeOneTable( sqlite3VdbeAddOp2(v, OP_Count, iTabCur, regStat1); jZeroRows = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); sqlite3VdbeAddOp2(v, OP_Null, 0, regIdxname); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regRec, "aaa", 0); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "aaa", 0); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); - sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regRec, regNewRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3VdbeJumpHere(v, jZeroRows); } diff --git a/test/analyze8.test b/test/analyze8.test index b46d38651f..4384c39676 100644 --- a/test/analyze8.test +++ b/test/analyze8.test @@ -86,25 +86,24 @@ do_test 2.1 { # range. # # Test 3.2 is a little unstable. It depends on the planner estimating -# that (b BETWEEN 40 AND 44) will match more rows than (c BETWEEN +# that (b BETWEEN 50 AND 54) will match more rows than (c BETWEEN # 800000 AND 900000). Which is a pretty close call (50 vs. 32), so # the planner could get it wrong with an unlucky set of samples. This -# case happens to work, but others ("b BETWEEN 50 AND 54" for example) +# case happens to work, but others ("b BETWEEN 40 AND 44" for example) # will fail. # do_execsql_test 3.0 { - SELECT count(*) FROM t1 WHERE b BETWEEN 40 AND 44; + SELECT count(*) FROM t1 WHERE b BETWEEN 50 AND 54; SELECT count(*) FROM t1 WHERE c BETWEEN 0 AND 100000; SELECT count(*) FROM t1 WHERE c BETWEEN 800000 AND 900000; } {50 376 32} do_test 3.1 { - eqp {SELECT * FROM t1 WHERE b BETWEEN 40 AND 44 AND c BETWEEN 0 AND 100000} + eqp {SELECT * FROM t1 WHERE b BETWEEN 50 AND 54 AND c BETWEEN 0 AND 100000} } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b? AND c