From: dan Date: Mon, 5 Aug 2013 18:00:56 +0000 (+0000) Subject: Use N separate cursors when scanning an index with N columns to collect sqlite_stat4... X-Git-Tag: version-3.8.1~132^2~38 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e043201d3d58726d86da4fbeb776c258fb7ad971;p=thirdparty%2Fsqlite.git Use N separate cursors when scanning an index with N columns to collect sqlite_stat4 data. This fixes a problem with collecting incorrect nEq values from multi-column indexes. FossilOrigin-Name: 3a71afe67418ce00097cd9714c395fe9ff16f23b --- diff --git a/manifest b/manifest index e6ce4135d9..1bdf53de0c 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index ced4c6d590..22a684eebe 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -badd24d987240db5528b37d1c177431617079f9b \ No newline at end of file +3a71afe67418ce00097cd9714c395fe9ff16f23b \ No newline at end of file diff --git a/src/analyze.c b/src/analyze.c index 8981db789f..07366b76ab 100644 --- a/src/analyze.c +++ b/src/analyze.c @@ -391,7 +391,8 @@ static void stat4Push( for(i=0; inCol; 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; itnum, 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; iazColl && 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; iazColl[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; iazColl[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; itnum, 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->nMemnMem = regRec; } diff --git a/src/test_func.c b/src/test_func.c index 6f9bb03dc8..f12e3c200a 100644 --- a/src/test_func.c +++ b/src/test_func.c @@ -18,6 +18,9 @@ #include #include +#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> 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; diff --git a/test/analyze9.test b/test/analyze9.test index 51817893ac..fd83df6031 100644 --- a/test/analyze9.test +++ b/test/analyze9.test @@ -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