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
This commit is contained in:
dan 2013-08-05 18:00:56 +00:00
parent c55521a60b
commit e043201d3d
5 changed files with 350 additions and 192 deletions

@ -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

@ -1 +1 @@
badd24d987240db5528b37d1c177431617079f9b
3a71afe67418ce00097cd9714c395fe9ff16f23b

@ -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;
}

@ -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;

@ -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