]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Change the way ANALYZE works to use a single cursor when scanning indices.
authordan <dan@noemail.net>
Wed, 14 Aug 2013 19:54:12 +0000 (19:54 +0000)
committerdan <dan@noemail.net>
Wed, 14 Aug 2013 19:54:12 +0000 (19:54 +0000)
FossilOrigin-Name: bdce612b35193abf72de1a563ea7962375b3574e

manifest
manifest.uuid
src/analyze.c
test/analyze8.test

index 2b09ccada8e7e37a6e0dabacfec95d493bde1992..35822cf8b7b046a753cafc96554feb69f89503bc 100644 (file)
--- 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
index 5f36a65cc707f4312e184df68dcf74936b417d19..c82b437d55d39ef6e2d4a2570320cef7b6736be5 100644 (file)
@@ -1 +1 @@
-cca8bf4372ab7a0258aa5c9397818415c6cf0abf
\ No newline at end of file
+bdce612b35193abf72de1a563ea7962375b3574e
\ No newline at end of file
index be6b9586f14ce2d86bde357d5d8115aa8374cf1f..d8edadf88576939d47a0b9850b796e131bcbd641 100644 (file)
 #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; i<ArraySize(aRoot); i++){
     sqlite3VdbeAddOp3(v, OP_OpenWrite, iStatCur+i, aRoot[i], iDb);
     sqlite3VdbeChangeP4(v, -1, (char *)3, P4_INT32);
     sqlite3VdbeChangeP5(v, aCreateTbl[i]);
+    if( !IsStat3 && !IsStat4 ) break;
   }
 }
 
@@ -246,44 +252,37 @@ static void openStatTable(
 #endif
 
 /*
-** Three SQL functions - stat4_init(), stat4_push(), and stat4_pop() -
+** Three SQL functions - stat_init(), stat_push(), and stat_get() -
 ** share an instance of the following structure to hold their state
 ** information.
-**
-** bHaveP, bHaveNonP:
-**   The stat4_push() user-defined-function may be invoked multiple
-**   times with index keys that are identical except for the rowid 
-**   field. An argument is passed to stat4_push() to indicate if this
-**   is the case or not.
-**
-**   bHaveP is set to true if a periodic sample corresponding to the
-**   current index key has already been added. bHaveNonP is true if a
-**   non-periodic sample has been added.
 */
 typedef struct Stat4Accum Stat4Accum;
+typedef struct Stat4Sample Stat4Sample;
+struct Stat4Sample {
+  i64 iRowid;                     /* Rowid in main table of the key */
+  tRowcnt *anEq;                  /* sqlite_stat4.nEq */
+  tRowcnt *anLt;                  /* sqlite_stat4.nLt */
+  tRowcnt *anDLt;                 /* sqlite_stat4.nDLt */
+  u8 isPSample;                   /* True if a periodic sample */
+  int iCol;                       /* If !isPSample, the reason for inclusion */
+  u32 iHash;                      /* Tiebreaker hash */
+};                                                    
 struct Stat4Accum {
   tRowcnt nRow;             /* Number of rows in the entire table */
   tRowcnt nPSample;         /* How often to do a periodic sample */
-  int iMin;                 /* Index of entry with minimum nEq and hash */
+  int nCol;                 /* Number of columns in index + rowid */
   int mxSample;             /* Maximum number of samples to accumulate */
-  int nSample;              /* Current number of samples */
-  int nCol;                 /* Number of columns in the index including rowid */
+  Stat4Sample current;      /* Current row as a Stat4Sample */
   u32 iPrn;                 /* Pseudo-random number used for sampling */
-  int bHaveP;
-  int bHaveNonP;
-  struct Stat4Sample {
-    i64 iRowid;                /* Rowid in main table of the key */
-    tRowcnt *anEq;             /* sqlite_stat4.nEq */
-    tRowcnt *anLt;             /* sqlite_stat4.nLt */
-    tRowcnt *anDLt;            /* sqlite_stat4.nDLt */
-    u8 isPSample;              /* True if a periodic sample */
-    u32 iHash;                 /* Tiebreaker hash */
-  } *a;                     /* An array of samples */
+  Stat4Sample *aBest;       /* Array of (nCol-1) best samples */
+  int iMin;                 /* Index in a[] of entry with minimum score */
+  int nSample;              /* Current number of samples */
+  int iGet;                 /* Index of current sample accessed by stat_get() */
+  Stat4Sample *a;           /* Array of mxSample Stat4Sample objects */
 };
 
-#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_ENABLE_STAT3)
 /*
-** Implementation of the stat4_init(C,N,S) SQL function. The three parameters
+** Implementation of the stat_init(C,N,S) SQL function. The three parameters
 ** are the number of rows in the table or index (C), the number of columns
 ** in the index (N) and the number of samples to accumulate (S).
 **
@@ -291,7 +290,7 @@ struct Stat4Accum {
 ** value is a pointer to the the Stat4Accum object encoded as a blob (i.e. 
 ** the size of the blob is sizeof(void*) bytes). 
 */
-static void stat4Init(
+static void statInit(
   sqlite3_context *context,
   int argc,
   sqlite3_value **argv
@@ -312,218 +311,312 @@ static void stat4Init(
   assert( nCol>1 );               /* >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; i<mxSample; i++){
+
+  /* Set up the Stat4Accum.a[] and aBest[] arrays */
+  p->a = (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; i<nCol; i++){
+    p->aBest[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->iCol<pOld->iCol)
+   || (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; i<p->mxSample; 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->nSample<p->mxSample
+       || 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; j<p->nCol; 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->nSample<p->mxSample || 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( iChng<p->nCol );
 
-  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->nSample<p->mxSample );
-      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; i<iChng; i++){
+      p->current.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].iHash<h ){
-      p->nSample--;
-      doInsert = 1;
+    for(i=iChng; i<p->nCol; 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->nSample<p->mxSample ){
-    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( nEq<aMinEq[i] ) break;
-      if( nEq>aMinEq[i] ){
-        doInsert = 1;
-        break;
-      }
-    }
-    if( i<0 && h>p->a[iMin].iHash ){
-      doInsert = 1;
-    }
-    p->bHaveNonP = doInsert;
+    for(i=0; i<p->nCol; 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; i<nSampleCol; i++){
-    pSample->anEq[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; i<p->mxSample; 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].iHash<p->a[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( n<p->nSample ){
+    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->iGet<p->nSample ){
+      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->iGet<p->nSample );
+    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; i<regDLte+nCol; i++){
-      sqlite3VdbeAddOp2(v, OP_Integer, 0, i);
-    }
-    sqlite3VdbeAddOp2(v, OP_Integer, 0, regCnt);
-    sqlite3VdbeAddOp2(v, OP_Integer, 0, regEof);
-
-    /* Rewind all cursors open on the index. If the table is entry, this
-    ** will cause control to jump to address endOfScan immediately.  */
-    endOfScan = sqlite3VdbeMakeLabel(v);
-    for(i=0; i<(nCol+1); i++){
-      sqlite3VdbeAddOp2(v, OP_Rewind, iIdxCur+i, endOfScan);
-    }
-
-    for(i=0; i<nCol; i++){
-      char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[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; i<nCol; i++){
       char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[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; i<nCol; i++){
-        int iCol = pIdx->aiColumn[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; i<nCol; i++){
+      sqlite3VdbeJumpHere(v, aGotoChng[i]);
+      sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regPrev+i);
     }
 
-    sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regNumEq);
-    sqlite3VdbeChangeP4(v, -1, (char*)&stat4GetFuncdef, P4_FUNCDEF);
+    /*
+    **  chng_addr_N:
+    **   regRowid = idx(rowid)
+    **   stat_push(P, regRowid, regChng)
+    **   Next csr
+    **   if !eof(csr) goto next_row;
+    */
+    sqlite3VdbeJumpHere(v, aGotoChng[nCol]);
+    sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid);
+    sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regTemp);
+    sqlite3VdbeChangeP4(v, -1, (char*)&statPushFuncdef, P4_FUNCDEF);
     sqlite3VdbeChangeP5(v, 3);
+    assert( regRowid==(regStat4+1) && regChng==(regStat4+2) );
+    sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow);
 
-    sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regNumLt);
-    sqlite3VdbeChangeP4(v, -1, (char*)&stat4GetFuncdef, P4_FUNCDEF);
-    sqlite3VdbeChangeP5(v, 4);
-
-    sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regNumDLt);
-    sqlite3VdbeChangeP4(v, -1, (char*)&stat4GetFuncdef, P4_FUNCDEF);
-    sqlite3VdbeChangeP5(v, 5);
+    /* Add the entry to the stat1 table. */
+    callStatGet(v, regStat4, STAT_GET_STAT1, regStat1);
+    sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "aaa", 0);
+    sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid);
+    sqlite3VdbeAddOp3(v, OP_Insert, iStatCur, regTemp, regNewRowid);
+    sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
 
-    sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 6, regRec, "bbbbbb", 0);
-    sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid);
-    sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regRec, regNewRowid);
-    sqlite3VdbeAddOp2(v, OP_Goto, 0, shortJump);
-    sqlite3VdbeJumpHere(v, shortJump+2);
-#endif        
+    /* Add the entries to the stat3 or stat4 table. */
+    if( IsStat3 || IsStat4 ){
+      int regEq = regStat1;
+      int regLt = regStat1+1;
+      int regDLt = regStat1+2;
+      int regSample = regStat1+3;
+      int regCol = regStat1+4;
+      int regSampleRowid = regCol + nCol;
+      int addrNext;
+      int addrIsNull;
+
+      pParse->nMem = 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; i<nCol; i++){
+          int iCol = pIdx->aiColumn[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; i<nCol; i++){
-      sqlite3VdbeAddOp4(v, OP_String8, 0, regTemp, 0, " ", 0);
-      sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regStat1, regStat1);
-      sqlite3VdbeAddOp3(v, OP_Add, regCnt, regDLte+i, regTemp);
-      sqlite3VdbeAddOp2(v, OP_AddImm, regTemp, -1);
-      sqlite3VdbeAddOp3(v, OP_Divide, regDLte+i, regTemp, regTemp);
-      sqlite3VdbeAddOp1(v, OP_ToInt, regTemp);
-      sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regStat1, regStat1);
+      sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 6, regTemp, "bbbbbb", 0);
+      sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur+1, regNewRowid);
+      sqlite3VdbeAddOp3(v, OP_Insert, iStatCur+1, regTemp, regNewRowid);
+      sqlite3VdbeAddOp2(v, OP_Goto, 0, addrNext);
+      sqlite3VdbeJumpHere(v, addrIsNull);
     }
-    if( pIdx->pPartIdxWhere!=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);
   }
index b46d38651f06a48d8ed7154b2e8c9558ce8ba89d..4384c39676fc683a526626fbdd545b2082818494 100644 (file)
@@ -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<?)}}
 do_test 3.2 {
   eqp {SELECT * FROM t1
-       WHERE b BETWEEN 40 AND 44 AND c BETWEEN 800000 AND 900000}
+       WHERE b BETWEEN 50 AND 54 AND c BETWEEN 800000 AND 900000}
 } {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c>? AND c<?)}}
-
 do_test 3.3 {
   eqp {SELECT * FROM t1 WHERE a=100 AND c BETWEEN 0 AND 100000}
 } {0 0 0 {SEARCH TABLE t1 USING INDEX t1a (a=?)}}