]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Use N separate cursors when scanning an index with N columns to collect sqlite_stat4...
authordan <dan@noemail.net>
Mon, 5 Aug 2013 18:00:56 +0000 (18:00 +0000)
committerdan <dan@noemail.net>
Mon, 5 Aug 2013 18:00:56 +0000 (18:00 +0000)
FossilOrigin-Name: 3a71afe67418ce00097cd9714c395fe9ff16f23b

manifest
manifest.uuid
src/analyze.c
src/test_func.c
test/analyze9.test

index e6ce4135d958c310a8a3b4f657195ac8ad98e13d..1bdf53de0cd280cfa90eb8155cf841b002f33dcb 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sa\scouple\sof\sproblems\sin\scode\srelated\sto\ssqlite_stat4.
-D 2013-08-05T05:34:30.270
+C Use\sN\sseparate\scursors\swhen\sscanning\san\sindex\swith\sN\scolumns\sto\scollect\ssqlite_stat4\sdata.\sThis\sfixes\sa\sproblem\swith\scollecting\sincorrect\snEq\svalues\sfrom\smulti-column\sindexes.
+D 2013-08-05T18:00:56.397
 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 f8db986c03eb0bfb221523fc9bbb9d0b70de3168
-F src/analyze.c b9fb000c4dcac4f7f3dbd1a8832ffdacf17a4d35
+F src/analyze.c a0979f7fdc8cd724f8e646ba9ef6ca1e56fa7491
 F src/attach.c 1816f5a9eea8d2010fc2b22b44f0f63eb3a62704
 F src/auth.c 523da7fb4979469955d822ff9298352d6b31de34
 F src/backup.c 43b348822db3e4cef48b2ae5a445fbeb6c73a165
@@ -243,7 +243,7 @@ F src/test_config.c 636ecd15a6ba18bf97a590b5a21f47573c8c2b65
 F src/test_demovfs.c 20a4975127993f4959890016ae9ce5535a880094
 F src/test_devsym.c e7498904e72ba7491d142d5c83b476c4e76993bc
 F src/test_fs.c 8f786bfd0ad48030cf2a06fb1f050e9c60a150d7
-F src/test_func.c 3a8dd37c08ab43b76d38eea2836e34a3897bf170
+F src/test_func.c fcd238feb694332d5962ee08578ef30ff4ac6559
 F src/test_hexio.c abfdecb6fa58c354623978efceb088ca18e379cd
 F src/test_init.c 3cbad7ce525aec925f8fda2192d576d47f0d478a
 F src/test_intarray.c 87847c71c3c36889c0bcc9c4baf9d31881665d61
@@ -308,7 +308,7 @@ F test/analyze5.test e3eece09761c935ec0b85dc4ed70dbf6cac1ed77
 F test/analyze6.test 3c01e084309706a1033f850330ea24f6f7846297
 F test/analyze7.test c0af22c5e0140e2e4ac556a21c2b6fff58229c98
 F test/analyze8.test 092425439c12f62f9d5c3127e2b4f6e7b3e170cc
-F test/analyze9.test 2ffe8f627b8f0309a72c2ff390effa430b1ef2d8
+F test/analyze9.test 30479ec9ac395e77cfced52496845500768d45f9
 F test/async.test 1d0e056ba1bb9729283a0f22718d3a25e82c277b
 F test/async2.test c0a9bd20816d7d6a2ceca7b8c03d3d69c28ffb8b
 F test/async3.test d73a062002376d7edc1fe3edff493edbec1fc2f7
@@ -1106,7 +1106,7 @@ F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
 F tool/wherecosttest.c f407dc4c79786982a475261866a161cd007947ae
 F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
-P 2beea303a1d609cd2ff252412c50b966b9e5e8f1
-R d4282412612019a7b27b2fb043ed04d3
+P badd24d987240db5528b37d1c177431617079f9b
+R 2711e68fa0a11591cfe5a22173f05afc
 U dan
-Z 13683b9d54d5fd3b76b2568a74c36c12
+Z ccb7ab66bd998be3fb0311148b3ca62c
index ced4c6d5906a5910feb1e28d7297f965af7bac37..22a684eebe523ebb30f3d17dee39847e4fc994a1 100644 (file)
@@ -1 +1 @@
-badd24d987240db5528b37d1c177431617079f9b
\ No newline at end of file
+3a71afe67418ce00097cd9714c395fe9ff16f23b
\ No newline at end of file
index 8981db789fff2ea4c4b29e141c745b1a603f81e1..07366b76aba26794149ca19698131bbbb2842325 100644 (file)
@@ -391,7 +391,8 @@ static void stat4Push(
   for(i=0; i<p->nCol; 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]);
+    pSample->anDLt[i] = sqlite3_value_int64(aDLt[i])-1;
+    assert( sqlite3_value_int64(aDLt[i])>0 );
   } 
 
   /* Find the new minimum */
@@ -564,11 +565,13 @@ static void analyzeOneTable(
   }
 #endif
 
-  /* Establish a read-lock on the table at the shared-cache level. */
+  /* Establish a read-lock on the table at the shared-cache level. 
+  ** Also open a read-only cursor on the table.  */
   sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName);
-
-  iIdxCur = pParse->nTab++;
+  iTabCur = pParse->nTab++;
+  sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead);
   sqlite3VdbeAddOp4(v, OP_String8, 0, regTabname, 0, pTab->zName, 0);
+
   for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
     int nCol;                     /* Number of columns indexed by pIdx */
     KeyInfo *pKey;                /* KeyInfo structure for pIdx */
@@ -577,12 +580,12 @@ static void analyzeOneTable(
 
     int regRowid;                 /* Register for rowid of current row */
     int regPrev;                  /* First in array of previous values */
-    int regDLt                  /* First in array of nDlt registers */
+    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 regCnt;                   /* Number of index entries */
-
-    int addrGoto;
+    int regEof;                   /* True once cursors are all at EOF */
+    int endOfScan;                /* Label to jump to once scan is finished */
 
     if( pOnlyIdx && pOnlyIdx!=pIdx ) continue;
     if( pIdx->pPartIdxWhere==0 ) needTableCnt = 0;
@@ -592,21 +595,99 @@ static void analyzeOneTable(
     if( aChngAddr==0 ) continue;
     pKey = sqlite3IndexKeyinfo(pParse, pIdx);
 
-    /* Open a cursor to the index to be analyzed. */
-    assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) );
-    sqlite3VdbeAddOp4(v, OP_OpenRead, iIdxCur, pIdx->tnum, iDb,
-        (char *)pKey, P4_KEYINFO_HANDOFF);
-    VdbeComment((v, "%s", pIdx->zName));
-
     /* Populate the register containing the index name. */
     sqlite3VdbeAddOp4(v, OP_String8, 0, regIdxname, 0, pIdx->zName, 0);
 
-#ifdef SQLITE_ENABLE_STAT4
-    if( once ){
-      once = 0;
-      sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead);
+    /*
+    ** 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.
+    **
+    ** One cursor is opened for each column in the index (nCol). All cursors 
+    ** scan concurrently the index from start to end. All variables used in
+    ** the pseudo-code are initialized to zero.
+    **
+    **   Rewind csr(0)
+    **   Rewind csr(1)
+    ** 
+    **  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) )
+    ** 
+    **  next_1:
+    **   regPrev(1) = csr(1)[1]
+    **   regDLte(1) += 1
+    **   regLt(1) += regEq(1)
+    **   regEq(1) = 0
+    **   regRowid = csr(1)[rowid]        // innermost cursor only
+    **   do {
+    **     regEq(1) += 1
+    **     regCnt += 1                   // innermost cursor only
+    **     Next csr(1)
+    **   }while ( csr(1)[0..1] == regPrev(0..1) )
+    ** 
+    **   stat4_push(regRowid, regEq, regLt, regDLte);
+    ** 
+    **   if( eof( csr(1) ) ) goto endOfScan
+    **   if( csr(1)[0] != regPrev(0) ) goto next_0
+    **   goto next_1
+    **
+    **  endOfScan:
+    **   // done!
+    **
+    ** 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.
+    **
+    ** Currently, the last memory cell allocated (that with the largest 
+    ** integer identifier) is regStat4. Immediately following regStat4
+    ** we allocate the following:
+    **
+    **     regRowid -    1 register
+    **     regEq -    nCol registers
+    **     regLt -    nCol registers
+    **     regDLte -  nCol registers
+    **     regCnt -      1 register
+    **     regPrev -  nCol registers
+    **     regEof -      1 register
+    **
+    ** The regRowid, regEq, regLt and regDLte registers must be positioned in 
+    ** that order immediately following regStat4 so that they can be passed
+    ** to the stat4_push() function.
+    **
+    ** All of the above are initialized to contain integer value 0.
+    */
+    regRowid = regStat4+1;        /* Rowid argument */
+    regEq = regRowid+1;           /* First in array of nEq value registers */
+    regLt = regEq+nCol;           /* First in array of nLt value registers */
+    regDLte = regLt+nCol;         /* First in array of nDLt value registers */
+    regCnt = regDLte+nCol;        /* Row counter */
+    regPrev = regCnt+1;           /* First in array of prev. value registers */
+    regEof = regPrev+nCol;        /* True once last row read from index */
+    if( regEof+1>pParse->nMem ){
+      pParse->nMem = regPrev+nCol;
     }
 
+    /* Open a read-only cursor for each column of the index. */
+    assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) );
+    iIdxCur = pParse->nTab++;
+    pParse->nTab += (nCol-1);
+    for(i=0; i<nCol; 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));
+    }
+
+#ifdef SQLITE_ENABLE_STAT4
     /* Invoke the stat4_init() function. The arguments are:
     ** 
     **     * the number of rows in the index,
@@ -621,181 +702,98 @@ static void analyzeOneTable(
     sqlite3VdbeChangeP5(v, 3);
 #endif /* SQLITE_ENABLE_STAT4 */
 
-    /* The block of (1 + 4*nCol) memory cells initialized here is used 
-    ** as follows:
-    **
-    ** TODO: Update this comment:
-    **
-    **    iMem:                
-    **        Loop counter. The number of rows visited so far, including
-    **        the current row (i.e. this register is set to 1 for the
-    **        first iteration of the loop).
-    **
-    **    iMem+1 .. iMem+nCol:
-    **        Number of distinct index entries seen so far, considering
-    **        the left-most N columns only, where N is between 1 and nCol,
-    **        inclusive.
-    **
-    **    iMem+nCol+1 .. Mem+2*nCol:  
-    **        Previous value of indexed columns, from left to right.
-    **
-    ** Cells iMem through iMem+nCol are initialized to 0. The others are 
-    ** initialized to contain an SQL NULL.
-    */
-    regRowid = regStat4+1;        /* Rowid argument */
-    regEq = regRowid+1;           /* First in array of nEq value registers */
-    regLt = regEq+nCol;           /* First in array of nLt value registers */
-    regDLt = regLt+nCol;          /* First in array of nDLt value registers */
-    regCnt = regDLt+nCol;         /* Row counter */
-    regPrev = regCnt+1;           /* First in array of prev. value registers */
-
-    if( regPrev+1>pParse->nMem ){
-      pParse->nMem = regPrev+1;
-    }
-    for(i=0; i<2+nCol*4; i++){
-      sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowid+i);
+    /* Initialize all the memory registers allocated above to 0. */
+    for(i=regRowid; i<=regEof; i++){
+      sqlite3VdbeAddOp2(v, OP_Integer, 0, i);
     }
 
-    /*
-    ** Loop through all entries in the b-tree index. Pseudo-code for the
-    ** body of the loop is as follows:
-    **
-    **    foreach i IN index {
-    **      regCnt += 1
-    **
-    **      if( regEq(0)==0 ) goto ne_0;
-    **
-    **      if i(0) != regPrev(0) {
-    **          stat4_push(regRowid, regEq, regLt, regDLt);
-    **          goto ne_0;
-    **      }
-    **      regEq(0) += 1
-    **
-    **      if i(1) != regPrev(1){
-    **          stat4_push(regRowid, regEq, regLt, regDLt);
-    **          goto ne_1;
-    **      }
-    **      regEq(1) += 1
-    **    
-    **      goto all_eq;
-    **    
-    **      ne_0:
-    **        regPrev(0) = i(0)
-    **        if( regEq(0) != 0 ) regDLt(0) += 1
-    **        regLt(0) += regEq(0)
-    **        regEq(0) = 1
-    **    
-    **      ne_1:
-    **        regPrev(1) = $i(1)
-    **        if( regEq(1) != 0 ) regDLt(1) += 1
-    **        regLt(1) += regEq(1)
-    **        regEq(1) = 1
-    **
-    **      all_eq:
-    **        regRowid = i(rowid)
-    **    }
-    **
-    **    stat4_push(regRowid, regEq, regLt, regDLt);
-    **    
-    **    if( regEq(0) != 0 ) regDLt(0) += 1
-    **    if( regEq(1) != 0 ) regDLt(1) += 1
-    **
-    ** The last two lines above modify the contents of the regDLt 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.
-    **
-    ** Note: if regEq(0)==0, stat4_push() is a no-op.
-    */
-    endOfLoop = sqlite3VdbeMakeLabel(v);
-    sqlite3VdbeAddOp2(v, OP_Rewind, iIdxCur, endOfLoop);
-    topOfLoop = sqlite3VdbeCurrentAddr(v);
-    sqlite3VdbeAddOp2(v, OP_AddImm, regCnt, 1);  /* Increment row counter */
-
-    /* This jump is taken for the first iteration of the loop only.
-    **
-    **     if( regEq(0)==0 ) goto ne_0;
-    */
-    addrIfNot = sqlite3VdbeAddOp1(v, OP_IfNot, regEq);
-
-    /* Code these bits:
-    **
-    **      if i(N) != regPrev(N) {
-    **          stat4_push(regRowid, regEq, regLt, regDLt);
-    **          goto ne_N;
-    **      }
-    **      regEq(N) += 1
-    */
+    /* 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; i++){
-      char *pColl;                /* Pointer to CollSeq cast to (char*) */
-      assert( pIdx->azColl && pIdx->azColl[i]!=0 );
-      pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[i]);
-
-      sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regCol);
-      sqlite3VdbeAddOp4(v, OP_Eq, regCol, 0, regPrev+i, pColl, P4_COLLSEQ);
-      sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
-
-      sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regTemp2);
-      sqlite3VdbeChangeP4(v, -1, (char*)&stat4PushFuncdef, P4_FUNCDEF);
-      sqlite3VdbeChangeP5(v, 2 + 3*nCol);
-
-      aChngAddr[i] = sqlite3VdbeAddOp0(v, OP_Goto);
-      VdbeComment((v, "jump if column %d changed", i));
-
-      sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-3);
-      sqlite3VdbeAddOp2(v, OP_AddImm, regEq+i, 1);
-      VdbeComment((v, "incr repeat count"));
+      sqlite3VdbeAddOp2(v, OP_Rewind, iIdxCur+i, endOfScan);
     }
 
-    /* Code the "continue" */
-    addrGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, endOfLoop);
-
-    /* And now these:
-    **
-    **      ne_N:
-    **        regPrev(N) = i(N)
-    **        if( regEq(N) != N ) regDLt(N) += 1
-    **        regLt(N) += regEq(N)
-    **        regEq(N) = 1
-    */
     for(i=0; i<nCol; i++){
-      sqlite3VdbeJumpHere(v, aChngAddr[i]);  /* Set jump dest for the OP_Ne */
-      if( i==0 ){
-        sqlite3VdbeJumpHere(v, addrIfNot);   /* Jump dest for OP_IfNot */
-      }
-      sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regPrev+i);
-      sqlite3VdbeAddOp2(v, OP_IfNot, regEq+i, sqlite3VdbeCurrentAddr(v)+2);
-      sqlite3VdbeAddOp2(v, OP_AddImm, regDLt+i, 1);
+      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;
+      int bInner = (i==(nCol-1)); /* True for innermost cursor */
+
+      /* 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);
-      sqlite3VdbeAddOp2(v, OP_Integer, 1, regEq+i);
+      VdbeComment((v, "regLt(%d) += regEq(%d)", i, i));
+      sqlite3VdbeAddOp2(v, OP_Integer, 0, regEq+i);
+      VdbeComment((v, "regEq(%d) = 0", i));
+      if( bInner ) sqlite3VdbeAddOp2(v, OP_IdxRowid, iCsr, regRowid);
+
+      /* This bit:
+      **
+      **   do {
+      **     regEq(i) += 1
+      **     regCnt += 1                   // innermost cursor only
+      **     Next csr(i)
+      **     if( Eof csr(i) ){
+      **       regEof = 1                  // innermost cursor only
+      **       break
+      **     }
+      **   }while ( csr(i)[0..i] == regPrev(0..i) )
+      */
+      iDo = sqlite3VdbeAddOp2(v, OP_AddImm, regEq+i, 1);
+      VdbeComment((v, "regEq(%d) += 1", i));
+      if( bInner ){
+        sqlite3VdbeAddOp2(v, OP_AddImm, regCnt, 1);
+        VdbeComment((v, "regCnt += 1"));
+      }
+      sqlite3VdbeAddOp2(v, OP_Next, iCsr, sqlite3VdbeCurrentAddr(v)+2+bInner);
+      if( bInner ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regEof);
+      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);
     }
-    sqlite3DbFree(db, aChngAddr);
-
-    /*
-    **      all_eq:
-    **        regRowid = i(rowid)
-    */
-    sqlite3VdbeJumpHere(v, addrGoto);
-    sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid);
-
-    /* The end of the loop that iterates through all index entries. Always 
-    ** jump here after updating the iMem+1...iMem+1+nCol counters. */
-    sqlite3VdbeResolveLabel(v, endOfLoop);
-    sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, topOfLoop);
-    sqlite3VdbeAddOp1(v, OP_Close, iIdxCur);
 
-    /* Final invocation of stat4_push() */
+    /* Invoke stat4_push() */
     sqlite3VdbeAddOp3(v, OP_Function, 1, regStat4, regTemp2);
     sqlite3VdbeChangeP4(v, -1, (char*)&stat4PushFuncdef, P4_FUNCDEF);
     sqlite3VdbeChangeP5(v, 2 + 3*nCol);
 
-    /* Finally:
-    **
-    **    if( regEq(0) != 0 ) regDLt(0) += 1
-    */
+    sqlite3VdbeAddOp2(v, OP_If, regEof, endOfScan);
+    for(i=0; i<nCol-1; i++){
+      char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[i]);
+      sqlite3VdbeAddOp3(v, OP_Column, iIdxCur+nCol-1, i, regCol);
+      sqlite3VdbeAddOp3(v, OP_Ne, regCol, aChngAddr[i], regPrev+i);
+      sqlite3VdbeChangeP4(v, -1, pColl, P4_COLLSEQ);
+      sqlite3VdbeChangeP5(v, SQLITE_NULLEQ);
+    }
+    sqlite3VdbeAddOp2(v, OP_Goto, 0, aChngAddr[nCol-1]);
+    sqlite3DbFree(db, aChngAddr);
+
+    sqlite3VdbeResolveLabel(v, endOfScan);
+
+    /* Close all the cursors */
     for(i=0; i<nCol; i++){
-      sqlite3VdbeAddOp2(v, OP_IfNot, regEq+i, sqlite3VdbeCurrentAddr(v)+2);
-      sqlite3VdbeAddOp2(v, OP_AddImm, regDLt+i, 1);
+      sqlite3VdbeAddOp1(v, OP_Close, iIdxCur+i);
+      VdbeComment((v, "close index cursor %d", i));
     }
 
 #ifdef SQLITE_ENABLE_STAT4
@@ -858,9 +856,9 @@ static void analyzeOneTable(
     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, regDLt+i, regTemp);
+      sqlite3VdbeAddOp3(v, OP_Add, regCnt, regDLte+i, regTemp);
       sqlite3VdbeAddOp2(v, OP_AddImm, regTemp, -1);
-      sqlite3VdbeAddOp3(v, OP_Divide, regDLt+i, regTemp, regTemp);
+      sqlite3VdbeAddOp3(v, OP_Divide, regDLte+i, regTemp, regTemp);
       sqlite3VdbeAddOp1(v, OP_ToInt, regTemp);
       sqlite3VdbeAddOp3(v, OP_Concat, regTemp, regStat1, regStat1);
     }
@@ -876,10 +874,8 @@ static void analyzeOneTable(
   ** name and the row count as the content.
   */
   if( pOnlyIdx==0 && needTableCnt ){
-    sqlite3VdbeAddOp3(v, OP_OpenRead, iIdxCur, pTab->tnum, iDb);
     VdbeComment((v, "%s", pTab->zName));
-    sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat1);
-    sqlite3VdbeAddOp1(v, OP_Close, iIdxCur);
+    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);
@@ -888,6 +884,10 @@ static void analyzeOneTable(
     sqlite3VdbeChangeP5(v, OPFLAG_APPEND);
     sqlite3VdbeJumpHere(v, jZeroRows);
   }
+
+  sqlite3VdbeAddOp1(v, OP_Close, iTabCur);
+
+  /* TODO: Not sure about this... */
   if( pParse->nMem<regRec ) pParse->nMem = regRec;
 }
 
index 6f9bb03dc88a21d658bf9999baf66dc1d52ebc54..f12e3c200a13160eb85520709f0a024224b5b21e 100644 (file)
@@ -18,6 +18,9 @@
 #include <string.h>
 #include <assert.h>
 
+#include "sqliteInt.h"
+#include "vdbeInt.h"
+
 
 /*
 ** Allocate nByte bytes of space using sqlite3_malloc(). If the
@@ -458,6 +461,99 @@ static void real2hex(
   sqlite3_result_text(context, zOut, -1, SQLITE_TRANSIENT);
 }
 
+/*
+** tclcmd: test_decode(record)
+**
+** This function implements an SQL user-function that accepts a blob
+** containing a formatted database record as its only argument. It returns
+** a tcl list (type SQLITE_TEXT) containing each of the values stored
+** in the record.
+*/
+static void test_decode(
+  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 */
+  Tcl_Obj *pRet;                  /* Return value */
+
+  pRet = Tcl_NewObj();
+  Tcl_IncrRefCount(pRet);
+
+  assert( argc==1 );
+  pRec = (u8*)sqlite3_value_blob(argv[0]);
+
+  pHdr = pRec + sqlite3GetVarint(pRec, &nHdr);
+  pBody = pEndHdr = &pRec[nHdr];
+  while( pHdr<pEndHdr ){
+    Tcl_Obj *pVal = 0;
+    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);
+    switch( sqlite3_value_type(&mem) ){
+      case SQLITE_TEXT:
+        pVal = Tcl_NewStringObj((const char*)sqlite3_value_text(&mem), -1);
+        break;
+
+      case SQLITE_BLOB: {
+        char hexdigit[] = {
+          '0', '1', '2', '3', '4', '5', '6', '7',
+          '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+        };
+        int n = sqlite3_value_bytes(&mem);
+        u8 *z = (u8*)sqlite3_value_blob(&mem);
+        int i;
+        pVal = Tcl_NewStringObj("x'", -1);
+        for(i=0; i<n; i++){
+          char hex[3];
+          hex[0] = hexdigit[((z[i] >> 4) & 0x0F)];
+          hex[1] = hexdigit[(z[i] & 0x0F)];
+          hex[2] = '\0';
+          Tcl_AppendStringsToObj(pVal, hex, 0);
+        }
+        Tcl_AppendStringsToObj(pVal, "'", 0);
+        break;
+      }
+
+      case SQLITE_FLOAT:
+        pVal = Tcl_NewDoubleObj(sqlite3_value_double(&mem));
+        break;
+
+      case SQLITE_INTEGER:
+        pVal = Tcl_NewWideIntObj(sqlite3_value_int64(&mem));
+        break;
+
+      case SQLITE_NULL:
+        pVal = Tcl_NewStringObj("NULL", -1);
+        break;
+
+      default:
+        assert( 0 );
+    }
+
+    Tcl_ListObjAppendElement(0, pRet, pVal);
+
+    if( mem.zMalloc ){
+      sqlite3DbFree(db, mem.zMalloc);
+    }
+  }
+
+  sqlite3_result_text(context, Tcl_GetString(pRet), -1, SQLITE_TRANSIENT);
+  Tcl_DecrRefCount(pRet);
+}
+
 
 static int registerTestFunctions(sqlite3 *db){
   static const struct {
@@ -482,6 +578,7 @@ static int registerTestFunctions(sqlite3 *db){
     { "test_isolation",        2, SQLITE_UTF8, test_isolation},
     { "test_counter",          1, SQLITE_UTF8, counterFunc},
     { "real2hex",              1, SQLITE_UTF8, real2hex},
+    { "test_decode",           1, SQLITE_UTF8, test_decode},
   };
   int i;
 
index 51817893aca0f998954e4047c3e26f7ea87773c6..fd83df60311ca4d9dc95dcc4ce087679bc2bceb4 100644 (file)
@@ -37,10 +37,21 @@ do_test 1.0 {
   execsql { CREATE INDEX i1 ON t1(a, b) }
 } {}
 
+
 do_execsql_test 1.1 {
   ANALYZE;
 } {}
 
+do_execsql_test 1.3 {
+  SELECT tbl,idx,nEq,nLt,nDLt,test_decode(sample) FROM sqlite_stat4;
+} {
+  t1 i1 {1 1} {0 0} {0 0} {(0) (0)}
+  t1 i1 {1 1} {1 1} {1 1} {(1) (1)}
+  t1 i1 {1 1} {2 2} {2 2} {(2) (2)}
+  t1 i1 {1 1} {3 3} {3 3} {(3) (3)}
+  t1 i1 {1 1} {4 4} {4 4} {(4) (4)}
+}
+
 do_execsql_test 1.2 {
   SELECT tbl,idx,nEq,nLt,nDLt,s(sample) FROM sqlite_stat4;
 } {
@@ -52,5 +63,55 @@ do_execsql_test 1.2 {
 }
 
 
+#-------------------------------------------------------------------------
+# This is really just to test SQL user function "test_decode".
+#
+reset_db
+do_execsql_test 2.1 {
+  CREATE TABLE t1(a, b, c);
+  INSERT INTO t1 VALUES('some text', 14, NULL);
+  INSERT INTO t1 VALUES(22.0, NULL, x'656667');
+  CREATE INDEX i1 ON t1(a, b, c);
+  ANALYZE;
+  SELECT test_decode(sample) FROM sqlite_stat4;
+} {
+  {22.0 NULL x'656667'} 
+  {{some text} 14 NULL}
+}
+
+#-------------------------------------------------------------------------
+# 
+reset_db
+do_execsql_test 3.1 {
+  CREATE TABLE t2(a, b);
+  CREATE INDEX i2 ON t2(a, b);
+  BEGIN;
+}
+
+do_test 3.2 {
+  for {set i 0} {$i < 1000} {incr i} {
+    set a [expr $i / 10]
+    set b [expr int(rand() * 15.0)]
+    execsql { INSERT INTO t2 VALUES($a, $b) }
+  }
+  execsql COMMIT
+} {}
+
+db func lindex lindex
+
+# Each value of "a" occurs exactly 10 times in the table.
+#
+do_execsql_test 3.3.1 {
+  SELECT count(*) FROM t2 GROUP BY a;
+} [lrange [string repeat "10 " 100] 0 99]
+
+# The first element in the "nEq" list of all samples should therefore be 10.
+#
+do_execsql_test 3.3.2 {
+  ANALYZE;
+  SELECT lindex(nEq, 0) FROM sqlite_stat4;
+} [lrange [string repeat "10 " 100] 0 23]
+
+
 finish_test