]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Re-enable reading from the sqlite_stat3 table (as well as sqlite_stat4).
authordan <dan@noemail.net>
Mon, 12 Aug 2013 16:34:32 +0000 (16:34 +0000)
committerdan <dan@noemail.net>
Mon, 12 Aug 2013 16:34:32 +0000 (16:34 +0000)
FossilOrigin-Name: 6d45078e621526fc2bac0eaefbb0f9602b9a8ec5

manifest
manifest.uuid
src/analyze.c
src/func.c
src/sqliteInt.h
src/test_func.c
test/analyze9.test
test/analyzeA.test [new file with mode: 0644]

index c08ca1d77cfe7d1dd7df6504dfafb6e1c6a09367..3b395f8f6e0bf85e3cfa0cb482e0d9646b1f52d8 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sa\sbug\sin\scalculating\sthe\saverage\snumber\sof\sentries\sfor\skeys\snot\spresent\sin\sthe\ssqlite_stat4\stable.
-D 2013-08-12T11:21:10.969
+C Re-enable\sreading\sfrom\sthe\ssqlite_stat3\stable\s(as\swell\sas\ssqlite_stat4).
+D 2013-08-12T16:34:32.514
 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 fd1bcb9bc4ca29cd36f60c620cc501c933048c28
+F src/analyze.c e5ce42f04f58003fa45908791a5ad06bdd2c2ff1
 F src/attach.c 1816f5a9eea8d2010fc2b22b44f0f63eb3a62704
 F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34
 F src/backup.c 43b348822db3e4cef48b2ae5a445fbeb6c73a165
@@ -175,7 +175,7 @@ F src/delete.c 2317c814866d9aa71fea16b3faf4fdd4d6a49b94
 F src/expr.c 0bbb44462a19169189b2709fbbd800950521b5ae
 F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
 F src/fkey.c 914a6bbd987d857c41ac9d244efa6641f36faadb
-F src/func.c 5c50c1ea31fd864b0fe921fe1a8d4c55acd609ef
+F src/func.c 78c371ddfb0bb2d4c4356e8d7336b582d4ca96a9
 F src/global.c 5caf4deab621abb45b4c607aad1bd21c20aac759
 F src/hash.c ac3470bbf1ca4ae4e306a8ecb0fdf1731810ffe4
 F src/hash.h 8890a25af81fb85a9ad7790d32eedab4b994da22
@@ -221,7 +221,7 @@ F src/shell.c 128eb16ccec68509a4a2f1948f2483819bf63425
 F src/sqlite.h.in bd1451ba1ab681022a53bccc3c39580ba094a3ff
 F src/sqlite3.rc fea433eb0a59f4c9393c8e6d76a6e2596b1fe0c0
 F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
-F src/sqliteInt.h 0ff47977058e1babf0c4265f1791c379d172b02f
+F src/sqliteInt.h 646063fc1564842fd8e54eee00f8b8b429e2eb1f
 F src/sqliteLimit.h 164b0e6749d31e0daa1a4589a169d31c0dec7b3d
 F src/status.c 7ac05a5c7017d0b9f0b4bcd701228b784f987158
 F src/table.c 2cd62736f845d82200acfa1287e33feb3c15d62e
@@ -243,7 +243,7 @@ F src/test_config.c 636ecd15a6ba18bf97a590b5a21f47573c8c2b65
 F src/test_demovfs.c 69b2085076654ebc18014cbc6386f04409c959a9
 F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
 F src/test_fs.c ced436e3d4b8e4681328409b8081051ce614e28f
-F src/test_func.c fcd238feb694332d5962ee08578ef30ff4ac6559
+F src/test_func.c 338a6e5ade3560ad36280881bbcf45f28d06cb68
 F src/test_hexio.c abfdecb6fa58c354623978efceb088ca18e379cd
 F src/test_init.c 3cbad7ce525aec925f8fda2192d576d47f0d478a
 F src/test_intarray.c 87847c71c3c36889c0bcc9c4baf9d31881665d61
@@ -308,7 +308,8 @@ F test/analyze5.test 96ac783a56142bbbedb58a7c1eebd1808b49cfae
 F test/analyze6.test 3c01e084309706a1033f850330ea24f6f7846297
 F test/analyze7.test c0af22c5e0140e2e4ac556a21c2b6fff58229c98
 F test/analyze8.test 8d1f76ff1e47c4093bb7be3971ba08fa56dc470d
-F test/analyze9.test 3e1bd0209354bb987832fba580c754cf77dc6ba3
+F test/analyze9.test 1b419d03407f2a6f4f1485620d54cb3e1bab3a71
+F test/analyzeA.test 949c3344280e0ca6de0b49805e4f291cdc1daa43
 F test/async.test 1d0e056ba1bb9729283a0f22718d3a25e82c277b
 F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b
 F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7
@@ -1106,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 088d1ff94890ada50d43e6a366a58167ec5a8e96
-R 4b6821682980828caa2dcfc6f067a6e7
+P ec3ffb174844406a6186c3dcc41b76d0331b502c
+R e94619aadc884e84bf22674359c1bbe0
 U dan
-Z 150d3ee65bd4be58d3670346ad74798d
+Z c424468455b7fdc35c2aa42e6130568e
index 4e199eec40d3242a2db922ce7485b5dc98914804..4b73be3aaa3cc87bb034c7017840bc4067d3e6af 100644 (file)
@@ -1 +1 @@
-ec3ffb174844406a6186c3dcc41b76d0331b502c
\ No newline at end of file
+6d45078e621526fc2bac0eaefbb0f9602b9a8ec5
\ No newline at end of file
index 641d416aa1276adb96d2de6adb3763442135986e..acdf1fde7619e07bab0311b8544db4f5ae155978 100644 (file)
@@ -168,8 +168,10 @@ static void openStatTable(
     const char *zCols;
   } aTable[] = {
     { "sqlite_stat1", "tbl,idx,stat" },
-#ifdef SQLITE_ENABLE_STAT4
+#if defined(SQLITE_ENABLE_STAT4)
     { "sqlite_stat4", "tbl,idx,neq,nlt,ndlt,sample" },
+#elif defined(SQLITE_ENABLE_STAT3)
+    { "sqlite_stat3", "tbl,idx,neq,nlt,ndlt,sample" },
 #endif
   };
 
@@ -1206,11 +1208,81 @@ void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){
 }
 
 #ifdef SQLITE_ENABLE_STAT4
+
 /*
-** Load content from the sqlite_stat4 table into the Index.aSample[]
-** arrays of all indices.
+** The implementation of the sqlite_record() function. This function accepts
+** a single argument of any type. The return value is a formatted database 
+** record (a blob) containing the argument value.
+**
+** This is used to convert the value stored in the 'sample' column of the
+** sqlite_stat3 table to the record format SQLite uses internally.
 */
-static int loadStat4(sqlite3 *db, const char *zDb){
+static void recordFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  const int file_format = 1;
+  int iSerial;                    /* Serial type */
+  int nSerial;                    /* Bytes of space for iSerial as varint */
+  int nVal;                       /* Bytes of space required for argv[0] */
+  int nRet;
+  sqlite3 *db;
+  u8 *aRet;
+
+  iSerial = sqlite3VdbeSerialType(argv[0], file_format);
+  nSerial = sqlite3VarintLen(iSerial);
+  nVal = sqlite3VdbeSerialTypeLen(iSerial);
+  db = sqlite3_context_db_handle(context);
+
+  nRet = 1 + nSerial + nVal;
+  aRet = sqlite3DbMallocRaw(db, nRet);
+  if( aRet==0 ){
+    sqlite3_result_error_nomem(context);
+  }else{
+    aRet[0] = nSerial+1;
+    sqlite3PutVarint(&aRet[1], iSerial);
+    sqlite3VdbeSerialPut(&aRet[1+nSerial], nVal, argv[0], file_format);
+    sqlite3_result_blob(context, aRet, nRet, SQLITE_TRANSIENT);
+    sqlite3DbFree(db, aRet);
+  }
+}
+
+/*
+** Register built-in functions used to help read ANALYZE data.
+*/
+void sqlite3AnalyzeFunctions(void){
+  static SQLITE_WSD FuncDef aAnalyzeTableFuncs[] = {
+    FUNCTION(sqlite_record,   1, 0, 0, recordFunc),
+  };
+  int i;
+  FuncDefHash *pHash = &GLOBAL(FuncDefHash, sqlite3GlobalFunctions);
+  FuncDef *aFunc = (FuncDef*)&GLOBAL(FuncDef, aAnalyzeTableFuncs);
+  for(i=0; i<ArraySize(aAnalyzeTableFuncs); i++){
+    sqlite3FuncDefInsert(pHash, &aFunc[i]);
+  }
+}
+
+/*
+** Load the content from either the sqlite_stat4 or sqlite_stat3 table 
+** into the relevant Index.aSample[] arrays.
+**
+** Arguments zSql1 and zSql2 must point to SQL statements that return
+** data equivalent to the following (statements are different for stat3,
+** see the caller of this function for details):
+**
+**    zSql1: SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx
+**    zSql2: SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4
+**
+** where %Q is replaced with the database name before the SQL is executed.
+*/
+static int loadStatTbl(
+  sqlite3 *db,                  /* Database handle */
+  int bStat3,                   /* Assume single column records only */
+  const char *zSql1,            /* SQL statement 1 (see above) */
+  const char *zSql2,            /* SQL statement 2 (see above) */
+  const char *zDb               /* Database name (e.g. "main") */
+){
   int rc;                       /* Result codes from subroutines */
   sqlite3_stmt *pStmt = 0;      /* An SQL statement being run */
   char *zSql;                   /* Text of the SQL statement */
@@ -1219,13 +1291,7 @@ static int loadStat4(sqlite3 *db, const char *zDb){
   IndexSample *pSample;         /* A slot in pIdx->aSample[] */
 
   assert( db->lookaside.bEnabled==0 );
-  if( !sqlite3FindTable(db, "sqlite_stat4", zDb) ){
-    return SQLITE_OK;
-  }
-
-  zSql = sqlite3MPrintf(db, 
-      "SELECT idx,count(*) FROM %Q.sqlite_stat4"
-      " GROUP BY idx", zDb);
+  zSql = sqlite3MPrintf(db, zSql1, zDb);
   if( !zSql ){
     return SQLITE_NOMEM;
   }
@@ -1234,6 +1300,9 @@ static int loadStat4(sqlite3 *db, const char *zDb){
   if( rc ) return rc;
 
   while( sqlite3_step(pStmt)==SQLITE_ROW ){
+    int nIdxCol = 1;              /* Number of columns in stat4 records */
+    int nAvgCol = 1;              /* Number of entries in Index.aAvgEq */
+
     char *zIndex;   /* Index name */
     Index *pIdx;    /* Pointer to the index object */
     int nSample;    /* Number of samples */
@@ -1247,10 +1316,14 @@ static int loadStat4(sqlite3 *db, const char *zDb){
     pIdx = sqlite3FindIndex(db, zIndex, zDb);
     if( pIdx==0 ) continue;
     assert( pIdx->nSample==0 );
+    if( bStat3==0 ){
+      nIdxCol = pIdx->nColumn+1;
+      nAvgCol = pIdx->nColumn;
+    }
     pIdx->nSample = nSample;
     nByte = sizeof(IndexSample) * nSample;
-    nByte += sizeof(tRowcnt) * (pIdx->nColumn+1) * 3 * nSample;
-    nByte += pIdx->nColumn * sizeof(tRowcnt);    /* Space for Index.aAvgEq[] */
+    nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample;
+    nByte += nAvgCol * sizeof(tRowcnt);     /* Space for Index.aAvgEq[] */
 
     pIdx->aSample = sqlite3DbMallocZero(db, nByte);
     if( pIdx->aSample==0 ){
@@ -1258,19 +1331,18 @@ static int loadStat4(sqlite3 *db, const char *zDb){
       return SQLITE_NOMEM;
     }
     pSpace = (tRowcnt*)&pIdx->aSample[nSample];
-    pIdx->aAvgEq = pSpace; pSpace += pIdx->nColumn;
+    pIdx->aAvgEq = pSpace; pSpace += nAvgCol;
     for(i=0; i<pIdx->nSample; i++){
-      pIdx->aSample[i].anEq = pSpace; pSpace += pIdx->nColumn+1;
-      pIdx->aSample[i].anLt = pSpace; pSpace += pIdx->nColumn+1;
-      pIdx->aSample[i].anDLt = pSpace; pSpace += pIdx->nColumn+1;
+      pIdx->aSample[i].anEq = pSpace; pSpace += nIdxCol;
+      pIdx->aSample[i].anLt = pSpace; pSpace += nIdxCol;
+      pIdx->aSample[i].anDLt = pSpace; pSpace += nIdxCol;
     }
     assert( ((u8*)pSpace)-nByte==(u8*)(pIdx->aSample) );
   }
   rc = sqlite3_finalize(pStmt);
   if( rc ) return rc;
 
-  zSql = sqlite3MPrintf(db, 
-      "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4", zDb);
+  zSql = sqlite3MPrintf(db, zSql2, zDb);
   if( !zSql ){
     return SQLITE_NOMEM;
   }
@@ -1279,10 +1351,10 @@ static int loadStat4(sqlite3 *db, const char *zDb){
   if( rc ) return rc;
 
   while( sqlite3_step(pStmt)==SQLITE_ROW ){
-    char *zIndex;   /* Index name */
-    Index *pIdx;    /* Pointer to the index object */
-    int i;          /* Loop counter */
-    int nCol;       /* Number of columns in index */
+    char *zIndex;                 /* Index name */
+    Index *pIdx;                  /* Pointer to the index object */
+    int i;                        /* Loop counter */
+    int nCol = 1;                 /* Number of columns in index */
 
     zIndex = (char *)sqlite3_column_text(pStmt, 0);
     if( zIndex==0 ) continue;
@@ -1297,7 +1369,9 @@ static int loadStat4(sqlite3 *db, const char *zDb){
     assert( idx<pIdx->nSample );
     pSample = &pIdx->aSample[idx];
 
-    nCol = pIdx->nColumn+1;
+    if( bStat3==0 ){
+      nCol = pIdx->nColumn+1;
+    }
     decodeIntArray((char*)sqlite3_column_text(pStmt,1), nCol, pSample->anEq, 0);
     decodeIntArray((char*)sqlite3_column_text(pStmt,2), nCol, pSample->anLt, 0);
     decodeIntArray((char*)sqlite3_column_text(pStmt,3), nCol, pSample->anDLt,0);
@@ -1327,6 +1401,7 @@ static int loadStat4(sqlite3 *db, const char *zDb){
         }
         if( avgEq==0 ) avgEq = 1;
         pIdx->aAvgEq[iCol] = avgEq;
+        if( bStat3 ) break;
       }
     }
 
@@ -1340,6 +1415,33 @@ static int loadStat4(sqlite3 *db, const char *zDb){
   }
   return sqlite3_finalize(pStmt);
 }
+
+/*
+** Load content from the sqlite_stat4 and sqlite_stat3 tables into 
+** the Index.aSample[] arrays of all indices.
+*/
+static int loadStat4(sqlite3 *db, const char *zDb){
+  int rc = SQLITE_OK;             /* Result codes from subroutines */
+
+  assert( db->lookaside.bEnabled==0 );
+  if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){
+    rc = loadStatTbl(db, 0,
+      "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx", 
+      "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4",
+      zDb
+    );
+  }
+
+  if( rc==SQLITE_OK && sqlite3FindTable(db, "sqlite_stat3", zDb) ){
+    rc = loadStatTbl(db, 1,
+      "SELECT idx,count(*) FROM %Q.sqlite_stat3 GROUP BY idx", 
+      "SELECT idx,neq,nlt,ndlt,sqlite_record(sample) FROM %Q.sqlite_stat3",
+      zDb
+    );
+  }
+
+  return rc;
+}
 #endif /* SQLITE_ENABLE_STAT4 */
 
 /*
index 49f6c892be28eb55680cc195d636496076011cc5..c6f70e18fa3ad2f6e9c80833dce97d1477cd481e 100644 (file)
@@ -1715,4 +1715,7 @@ void sqlite3RegisterGlobalFunctions(void){
 #ifndef SQLITE_OMIT_ALTERTABLE
   sqlite3AlterFunctions();
 #endif
+#if defined(SQLITE_ENABLE_STAT3) || defined(SQLITE_ENABLE_STAT4)
+  sqlite3AnalyzeFunctions();
+#endif
 }
index 19b0d5560d9e3818f1f04be3cf3210fd9f6c1f25..2b8c1ad55e276c0c16e035a0398f373f1ce99514 100644 (file)
@@ -3055,6 +3055,7 @@ extern int sqlite3PendingByte;
 void sqlite3RootPageMoved(sqlite3*, int, int, int);
 void sqlite3Reindex(Parse*, Token*, Token*);
 void sqlite3AlterFunctions(void);
+void sqlite3AnalyzeFunctions(void);
 void sqlite3AlterRenameTable(Parse*, SrcList*, Token*);
 int sqlite3GetToken(const unsigned char *, int *);
 void sqlite3NestedParse(Parse*, const char*, ...);
index f12e3c200a13160eb85520709f0a024224b5b21e..cc4596d488828941e2b97d07d268609f7aa609fa 100644 (file)
@@ -461,6 +461,54 @@ static void real2hex(
   sqlite3_result_text(context, zOut, -1, SQLITE_TRANSIENT);
 }
 
+/*
+** tclcmd: test_extract(record, field)
+**
+** This function implements an SQL user-function that accepts a blob
+** containing a formatted database record as the first argument. The
+** second argument is the index of the field within that record to
+** extract and return.
+*/
+static void test_extract(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  sqlite3 *db = sqlite3_context_db_handle(context);
+  u8 *pRec;
+  u8 *pEndHdr;                    /* Points to one byte past record header */
+  u8 *pHdr;                       /* Current point in record header */
+  u8 *pBody;                      /* Current point in record data */
+  u64 nHdr;                       /* Bytes in record header */
+  int iIdx;                       /* Required field */
+  int iCurrent = 0;               /* Current field */
+
+  assert( argc==2 );
+  pRec = (u8*)sqlite3_value_blob(argv[0]);
+  iIdx = sqlite3_value_int(argv[1]);
+
+  pHdr = pRec + sqlite3GetVarint(pRec, &nHdr);
+  pBody = pEndHdr = &pRec[nHdr];
+
+  for(iCurrent=0; pHdr<pEndHdr && iCurrent<=iIdx; iCurrent++){
+    u64 iSerialType;
+    Mem mem;
+
+    memset(&mem, 0, sizeof(mem));
+    mem.db = db;
+    mem.enc = SQLITE_UTF8;
+    pHdr += sqlite3GetVarint(pHdr, &iSerialType);
+    pBody += sqlite3VdbeSerialGet(pBody, (u32)iSerialType, &mem);
+    sqlite3VdbeMemStoreType(&mem);
+
+    if( iCurrent==iIdx ){
+      sqlite3_result_value(context, &mem);
+    }
+
+    sqlite3DbFree(db, mem.zMalloc);
+  }
+}
+
 /*
 ** tclcmd: test_decode(record)
 **
@@ -579,6 +627,7 @@ static int registerTestFunctions(sqlite3 *db){
     { "test_counter",          1, SQLITE_UTF8, counterFunc},
     { "real2hex",              1, SQLITE_UTF8, real2hex},
     { "test_decode",           1, SQLITE_UTF8, test_decode},
+    { "test_extract",          2, SQLITE_UTF8, test_extract},
   };
   int i;
 
index 0323b860ba083bb973345b5c9a05bf4f26dbd649..cbb2319f8c9b7fec761f4af0a418c1cabb73da23 100644 (file)
@@ -9,6 +9,9 @@
 #
 #***********************************************************************
 #
+# This file contains automated tests used to verify that the sqlite_stat4
+# functionality is working.
+#
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
diff --git a/test/analyzeA.test b/test/analyzeA.test
new file mode 100644 (file)
index 0000000..c1257a2
--- /dev/null
@@ -0,0 +1,132 @@
+# 2013 August 3
+#
+# 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 contains automated tests used to verify that the current build
+# (which must be either ENABLE_STAT3 or ENABLE_STAT4) works with both stat3
+# and stat4 data.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix analyzeA
+
+ifcapable !stat4&&!stat3 {
+  finish_test
+  return
+}
+
+proc populate_stat3 {} {
+  # Open a second connection on database "test.db" and run ANALYZE. If this
+  # is an ENABLE_STAT3 build, this is all that is required to create and
+  # populate the sqlite_stat3 table. 
+  # 
+  sqlite3 db2 test.db
+  execsql { ANALYZE }
+
+  # Now, if this is an ENABLE_STAT4 build, create and populate the 
+  # sqlite_stat3 table based on the stat4 data gathered by the ANALYZE
+  # above. Then drop the sqlite_stat4 table.
+  #
+  ifcapable stat4 {
+    db2 func lindex lindex
+    execsql {
+      PRAGMA writable_schema = on;
+      CREATE TABLE sqlite_stat3(tbl,idx,neq,nlt,ndlt,sample);
+      INSERT INTO sqlite_stat3 
+      SELECT DISTINCT tbl, idx, 
+      lindex(neq,0), lindex(nlt,0), lindex(ndlt,0), test_extract(sample, 0)
+      FROM sqlite_stat4;
+      DROP TABLE sqlite_stat4;
+      PRAGMA writable_schema = off;
+    } db2
+  }
+
+  # Modify the database schema cookie to ensure that the other connection
+  # reloads the schema.
+  #
+  execsql {
+    CREATE TABLE obscure_tbl_nm(x);
+    DROP TABLE obscure_tbl_nm;
+  } db2
+  db2 close
+}
+
+
+proc populate_stat4 {} {
+  execsql { ANALYZE }
+#  ifcapable stat3 {
+#    execsql {
+#      PRAGMA writable_schema = on;
+#      CREATE TABLE sqlite_stat4(tbl,idx,neq,nlt,ndlt,sample);
+#      INSERT INTO sqlite_stat4 SELECT 
+#          tbl, idx, nlt, neq, ndlt,
+#          test_extract(sample, 1)
+#      FROM sqlite_stat4;
+#      DROP TABLE sqlite_stat4;
+#      PRAGMA writable_schema = off;
+#      ANALYZE sqlite_master;
+#    }
+#  }
+}
+
+foreach {tn analyze_cmd} {1 populate_stat4 2 populate_stat3} {
+  reset_db
+  do_test 1.$tn.1 {
+    execsql { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c) }
+    for {set i 0} {$i < 100} {incr i} {
+      set c [expr int(pow(1.1,$i)/100)]
+      set b [expr 125 - int(pow(1.1,99-$i))/100]
+      execsql {INSERT INTO t1 VALUES($i, $b, $c)}
+    }
+  } {}
+
+  execsql { CREATE INDEX t1b ON t1(b) }
+  execsql { CREATE INDEX t1c ON t1(c) }
+  $analyze_cmd
+
+  do_execsql_test 1.$tn.2.1 { SELECT count(*) FROM t1 WHERE b=31 } 1
+  do_execsql_test 1.$tn.2.2 { SELECT count(*) FROM t1 WHERE c=0  } 49
+  do_execsql_test 1.$tn.2.3 { SELECT count(*) FROM t1 WHERE b=125  } 49
+  do_execsql_test 1.$tn.2.4 { SELECT count(*) FROM t1 WHERE c=16  } 1
+
+  do_eqp_test 1.$tn.2.5 {
+    SELECT * FROM t1 WHERE b = 31 AND c = 0;
+  } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b=?)}}
+  do_eqp_test 1.$tn.2.6 {
+    SELECT * FROM t1 WHERE b = 125 AND c = 16;
+  } {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c=?)}}
+
+  do_execsql_test 1.$tn.3.1 { 
+    SELECT count(*) FROM t1 WHERE b BETWEEN 0 AND 50
+  } {6}
+  do_execsql_test 1.$tn.3.2 { 
+    SELECT count(*) FROM t1 WHERE c BETWEEN 0 AND 50
+  } {90}
+
+  do_execsql_test 1.$tn.3.3 { 
+    SELECT count(*) FROM t1 WHERE b BETWEEN 75 AND 125
+  } {90}
+  do_execsql_test 1.$tn.3.4 { 
+    SELECT count(*) FROM t1 WHERE c BETWEEN 75 AND 125
+  } {6}
+
+  do_eqp_test 1.$tn.3.5 {
+    SELECT * FROM t1 WHERE b BETWEEN 0 AND 50 AND c BETWEEN 0 AND 50
+  } {0 0 0 {SEARCH TABLE t1 USING INDEX t1b (b>? AND b<?)}}
+
+  do_eqp_test 1.$tn.3.6 {
+    SELECT * FROM t1 WHERE b BETWEEN 75 AND 125 AND c BETWEEN 75 AND 125
+  } {0 0 0 {SEARCH TABLE t1 USING INDEX t1c (c>? AND c<?)}}
+}
+
+
+finish_test
+