From: drh Date: Fri, 20 Mar 2020 16:13:41 +0000 (+0000) Subject: Revamp the EXPLAIN infrastructure to facilitate sqlite3_stmt_mode(). The X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cc6408ffa3f8c5de8f62c5788d9e6cc79b9174d1;p=thirdparty%2Fsqlite.git Revamp the EXPLAIN infrastructure to facilitate sqlite3_stmt_mode(). The currently code mostly works, but there are test failures. This is an incremental check-in. FossilOrigin-Name: e9e17e2125dbbafd5da4adb3bd2893735fa4d0aaa5f3daee75f866cb32231a8d --- diff --git a/manifest b/manifest index 67aab6cc0c..b7a8854007 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initial\scode\sfor\sa\sproposed\snew\ssqlite3_stmt_mode()\sAPI.\nThis\sis\san\sincomplete\ssnapshot\sof\sa\swork-in-progress. -D 2020-03-19T21:17:11.830 +C Revamp\sthe\sEXPLAIN\sinfrastructure\sto\sfacilitate\ssqlite3_stmt_mode().\s\sThe\ncurrently\scode\smostly\sworks,\sbut\sthere\sare\stest\sfailures.\s\sThis\sis\san\nincremental\scheck-in. +D 2020-03-20T16:13:41.030 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -603,11 +603,11 @@ F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78 F src/utf.c 95fb6e03a5ca679045c5adccd05380f0addccabef5911abddcb06af069500ab7 F src/util.c a285c1e026907b69fa2592bd05047a565a1d8a1aef2b73c924b6a8ffe772871a F src/vacuum.c 813b510ba887fee6492bcb11f2bf77d7eb58b232b83649136372e0a2fc17f4b9 -F src/vdbe.c ca986d3f9961045256c1b077a6986cd2e9cfa86909adfd0acafb761e6656b5bb +F src/vdbe.c b7b9ec1d604a12f4cafedf796027a297bc306f0d3afb340948898256cb4473b9 F src/vdbe.h 51282fbe819ee0e8eeeaab176240860d334c20a12b14f3b363e7f1a4e05d60b9 -F src/vdbeInt.h a341e6994ffd865531089f0c94d3910acc66d25d312259c4a45f422693deab9b -F src/vdbeapi.c e8351c4f351e492e260fb8b0f9f57575772f583fbbe97dca4c31dcadd06760d6 -F src/vdbeaux.c ce2521d1979d6e081c21766523244891149df247092202db3a0e097f4eae40f2 +F src/vdbeInt.h 1a8807b7dfac4142f5b9047767295327be41f02c17f7bb6bd8ffef6219dac588 +F src/vdbeapi.c d840fb3b80bbbd25fd44e1bb2d4a209e297832f527d21ea718fa53502a2a2d97 +F src/vdbeaux.c 734cb197ffdf83322832f9c881e9841b43b27f43677b8c75ad444cd2ccee2802 F src/vdbeblob.c 253ed82894924c362a7fa3079551d3554cd1cdace39aa833da77d3bc67e7c1b1 F src/vdbemem.c 39b942ecca179f4f30a32b54579a85d74ccaefa5af2a0ad2700abe5ef0768b22 F src/vdbesort.c 2be76d26998ce2b3324cdcc9f6443728e54b6c7677c553ad909c7d7cfab587df @@ -1860,10 +1860,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 11e0844f71e8f2d27ce9363fb505e02fd7795c61dae0b3886cf0d8df4484dd97 -R bfd344317fdbc4dd2c74ef078364da12 -T *branch * sqlite3_stmt_mode -T *sym-sqlite3_stmt_mode * -T -sym-trunk * +P 3cf7537b5e14e218218b18b3c0c668c950a71fcddc68a5faf0f197519718a6c2 +R 858f863db42629ad352a7a54d45b01f7 U drh -Z 4fdf6d3f28007c100776cbe88029096e +Z acdb8ce72295d433409d053ae8da03ad diff --git a/manifest.uuid b/manifest.uuid index 05aae7265e..ce65b1e44e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3cf7537b5e14e218218b18b3c0c668c950a71fcddc68a5faf0f197519718a6c2 \ No newline at end of file +e9e17e2125dbbafd5da4adb3bd2893735fa4d0aaa5f3daee75f866cb32231a8d \ No newline at end of file diff --git a/src/vdbe.c b/src/vdbe.c index 1eae9c6294..c7ba320ba4 100644 --- a/src/vdbe.c +++ b/src/vdbe.c @@ -539,9 +539,10 @@ void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr){ #ifdef SQLITE_DEBUG /* -** Print the value of a register for tracing purposes: +** Print the value of a Mem object on standard output. +** Used for tracing and for interactive debugging only. */ -static void memTracePrint(Mem *p){ +static void memPrint(Mem *p){ if( p->flags & MEM_Undefined ){ printf(" undefined"); }else if( p->flags & MEM_Null ){ @@ -567,9 +568,19 @@ static void memTracePrint(Mem *p){ } if( p->flags & MEM_Subtype ) printf(" subtype=0x%02x", p->eSubtype); } +/* Print N Mem objects beginning with p. Used for interactive debugging */ +void sqlite3MemPrint(Mem *p, int N){ + int i; + for(i=0; i1 ) printf("%3d:", i); + memPrint(p+i); + printf("\n"); + } + fflush(stdout); +} static void registerTrace(int iReg, Mem *p){ printf("R[%d] = ", iReg); - memTracePrint(p); + memPrint(p); if( p->pScopyFrom ){ printf(" <== R[%d]", (int)(p->pScopyFrom - &p[-iReg])); } @@ -705,7 +716,7 @@ int sqlite3VdbeExec( assert( p->bIsReader || p->readOnly!=0 ); p->iCurrentTime = 0; assert( p->explain==SQLITE_STMTMODE_RUN ); - p->pResultSet = 0; + p->nRes = 0; db->busyHandler.nBusy = 0; if( db->u1.isInterrupted ) goto abort_due_to_interrupt; sqlite3VdbeIOTraceSql(p); @@ -1493,6 +1504,7 @@ case OP_ResultRow: { ** a side effect. */ pMem = p->pResultSet = &aMem[pOp->p1]; + p->nRes = pOp->p2; for(i=0; ip2; i++){ assert( memIsValid(&pMem[i]) ); Deephemeralize(&pMem[i]); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 1717d64a6f..5a40e29e78 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -26,6 +26,11 @@ # define SQLITE_MAX_SCHEMA_RETRY 50 #endif +/* +** Maximum number of columns in any "EXPLAIN" output +*/ +#define VDBE_EXPLAIN_COLS 9 + /* ** VDBE_DISPLAY_P4 is true or false depending on whether or not the ** "explain" P4 display logic is enabled. @@ -337,11 +342,6 @@ struct sqlite3_context { sqlite3_value *argv[1]; /* Argument set */ }; -/* A bitfield type for use inside of structures. Always follow with :N where -** N is the number of bits. -*/ -typedef unsigned bft; /* Bit Field Type */ - /* The ScanStatus object holds a single value for the ** sqlite3_stmt_scanstatus() interface. */ @@ -414,19 +414,20 @@ struct Vdbe { int rcApp; /* errcode set by sqlite3_result_error_code() */ u32 nWrite; /* Number of write operations that have occurred */ #endif - u16 nResColumn; /* Number of columns in one row of the result set */ + u16 nResColumn; /* Columns in one row of a normal result set */ + u16 nRes; /* Columns in the current actual result set */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ - bft expired:2; /* 1: recompile VM immediately 2: when convenient */ - bft explain:2; /* True if EXPLAIN present on SQL command */ - bft origExplain:2; /* The original value of explain */ - bft doingRerun:1; /* True if rerunning after an auto-reprepare */ - bft changeCntOn:1; /* True to update the change-counter */ - bft runOnlyOnce:1; /* Automatically expire on reset */ - bft usesStmtJournal:1; /* True if uses a statement journal */ - bft readOnly:1; /* True for statements that do not write */ - bft bIsReader:1; /* True for statements that read */ + u8 explain:2; /* True if EXPLAIN present on SQL command */ + u8 origExplain:2; /* The original value of explain */ + u8 expired:2; /* 1: recompile VM immediately 2: when convenient */ + u8 doingRerun:1; /* True if rerunning after an auto-reprepare */ + u8 changeCntOn:1; /* True to update the change-counter */ + u8 runOnlyOnce:1; /* Automatically expire on reset */ + u8 usesStmtJournal:1; /* True if uses a statement journal */ + u8 readOnly:1; /* True for statements that do not write */ + u8 bIsReader:1; /* True for statements that read */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ u32 aCounter[7]; /* Counters used by sqlite3_stmt_status() */ @@ -499,6 +500,7 @@ int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*); int sqlite3VdbeIdxRowid(sqlite3*, BtCursor*, i64*); int sqlite3VdbeExec(Vdbe*); #ifndef SQLITE_OMIT_EXPLAIN +Mem *sqlite3VdbeSetExplainColumnNames(Vdbe*); int sqlite3VdbeList(Vdbe*); #endif int sqlite3VdbeHalt(Vdbe*); diff --git a/src/vdbeapi.c b/src/vdbeapi.c index c007a77121..dda71d64e6 100644 --- a/src/vdbeapi.c +++ b/src/vdbeapi.c @@ -1000,7 +1000,7 @@ int sqlite3_column_count(sqlite3_stmt *pStmt){ 0, /* SQLITE_STMTMODE_RUN (Use pVm->nResColumn instead) */ 8, /* SQLITE_STMTMODE_EXPLAIN */ 4, /* SQLITE_STMTMODE_EQP */ - 6 /* SQLITE_STMTMODE_TABLELIST */ + 5 /* SQLITE_STMTMODE_TABLELIST */ }; Vdbe *pVm = (Vdbe *)pStmt; if( pVm==0 ) return 0; @@ -1015,8 +1015,8 @@ int sqlite3_column_count(sqlite3_stmt *pStmt){ */ int sqlite3_data_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; - if( pVm==0 || pVm->pResultSet==0 ) return 0; - return pVm->nResColumn; + if( pVm==0 || pVm->nRes==0 ) return 0; + return sqlite3_column_count(pStmt); } /* @@ -1070,7 +1070,7 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ if( pVm==0 ) return (Mem*)columnNullValue(); assert( pVm->db ); sqlite3_mutex_enter(pVm->db->mutex); - if( pVm->pResultSet!=0 && inResColumn && i>=0 ){ + if( inRes && i>=0 ){ pOut = &pVm->pResultSet[i]; }else{ sqlite3Error(pVm->db, SQLITE_RANGE); @@ -1704,7 +1704,9 @@ int sqlite3_stmt_mode(sqlite3_stmt *pStmt, int iNewMode){ || iNewMode==SQLITE_STMTMODE_TABLELIST || iNewMode==v->origExplain ){ - v->explain = iNewMode; + if( v->magic==VDBE_MAGIC_RUN && v->pc<0 ){ + v->explain = iNewMode; + } } return v->explain; } diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 41b7e7bcc6..d5f2a3b094 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -1872,7 +1872,11 @@ static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ } /* -** Release an array of N Mem elements +** Release all content from an array of N Mem elements. All elements +** are left in a state of "MEM_Undefined". +** +** This routine does not free the array itself. It just frees the +** content held by the Mem elements in the array. */ static void releaseMemArray(Mem *p, int N){ if( p && N ){ @@ -1963,22 +1967,61 @@ void sqlite3VdbeFrameDelete(VdbeFrame *p){ #ifndef SQLITE_OMIT_EXPLAIN /* -** Give a listing of the program in the virtual machine. +** There should are VDBE_EXPLAIN_COLS*2 extra registers on the end of the +** Vdbe.aColName array for statements that are in one of the +** "explain" modes. The first VDBE_EXPLAIN_COLS registers hold the names of +** the columns, and the last VDBE_EXPLAIN_COLS registers are used for to store +** result set values when running the explain. ** -** The interface is the same as sqlite3VdbeExec(). But instead of -** running the code, it invokes the callback once for each instruction. -** This feature is used to implement "EXPLAIN". +** This routine makes sure those extra register exist and return +** a pointer to the first of them. If the extra registers do +** not exist, then the are allocated. ** -** When p->explain==1, each instruction is listed. When -** p->explain==2, only OP_Explain instructions are listed and these -** are shown in a different format. p->explain==2 is used to implement -** EXPLAIN QUERY PLAN. -** 2018-04-24: In p->explain==2 mode, the OP_Init opcodes of triggers -** are also shown, so that the boundaries between the main program and -** each trigger are clear. +** NULL is returned if there is a memory allocation error. +*/ +Mem *sqlite3VdbeSetExplainColumnNames(Vdbe *p){ + int n = p->nResColumn*COLNAME_N; + if( p->nColName<=n ){ + static const char * const azColName[] = { + "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", + "id", "parent", "notused", "detail", + "schema", "type", "name", "rw", "rootpage" + }; + int iFirst, cnt, i; + Mem *pNew = sqlite3DbRealloc(p->db, p->aColName, + (n+VDBE_EXPLAIN_COLS*2)*sizeof(Mem)); + if( pNew==0 ){ + return 0; + } + p->aColName = pNew; + p->nColName = n+VDBE_EXPLAIN_COLS*2; + initMemArray(p->aColName+n, VDBE_EXPLAIN_COLS*2, p->db, MEM_Null); + if( p->explain==SQLITE_STMTMODE_EXPLAIN ){ + iFirst = 0; + cnt = 8; + }else if( p->explain==SQLITE_STMTMODE_EQP ){ + iFirst = 8; + cnt = 4; + }else{ + iFirst = 12; + cnt = 5; + } + for(i=0; iaColName + n; +} +#endif + +#ifndef SQLITE_OMIT_EXPLAIN +/* +** Give a listing of the program in the virtual machine. ** -** When p->explain==1, first the main program is listed, then each of -** the trigger subprograms are listed one by one. +** The interface is the same as sqlite3VdbeExec(). But instead of +** running the code, it returns rows based on the bytecode content, +** rather than on actually running the bytecode. */ int sqlite3VdbeList( Vdbe *p /* The VDBE */ @@ -1990,11 +2033,13 @@ int sqlite3VdbeList( sqlite3 *db = p->db; /* The database connection */ int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ - Mem *pMem = &p->aMem[1]; /* First Mem of result set */ + Mem *pMem; /* First Mem of result set */ int bListSubprogs = (p->explain==1 || (db->flags & SQLITE_TriggerEQP)!=0); Op *pOp = 0; - assert( p->explain ); + assert( p->explain==SQLITE_STMTMODE_EXPLAIN + || p->explain==SQLITE_STMTMODE_EQP + || p->explain==SQLITE_STMTMODE_TABLELIST ); assert( p->magic==VDBE_MAGIC_RUN ); assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); @@ -2002,8 +2047,15 @@ int sqlite3VdbeList( ** the result, result columns may become dynamic if the user calls ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ - releaseMemArray(pMem, 8); - p->pResultSet = 0; + pMem = sqlite3VdbeSetExplainColumnNames(p); + if( pMem==0 ){ + p->rc = SQLITE_NOMEM; + return SQLITE_ERROR; + } + pMem += VDBE_EXPLAIN_COLS; + releaseMemArray(pMem, VDBE_EXPLAIN_COLS); + p->pResultSet = pMem; + p->nRes = 0; if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or @@ -2021,12 +2073,8 @@ int sqlite3VdbeList( */ nRow = p->nOp; if( bListSubprogs ){ - /* The first 8 memory cells are used for the result set. So we will - ** commandeer the 9th cell to use as storage for an array of pointers - ** to trigger subprograms. The VDBE is guaranteed to have at least 9 - ** cells. */ - assert( p->nMem>9 ); - pSub = &p->aMem[9]; + pSub = pMem++; + p->pResultSet = pMem; if( pSub->flags&MEM_Blob ){ /* On the first call to sqlite3_step(), pSub will hold a NULL. It is ** initialized to a BLOB by the P4_SUBPROGRAM processing logic below */ @@ -2065,7 +2113,7 @@ int sqlite3VdbeList( /* When an OP_Program opcode is encounter (the only opcode that has ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms - ** kept in p->aMem[9].z to hold the new program - assuming this subprogram + ** to hold the new program - assuming this subprogram ** has not already been seen. */ if( bListSubprogs && pOp->p4type==P4_SUBPROGRAM ){ @@ -2087,7 +2135,7 @@ int sqlite3VdbeList( nRow += pOp->p4.pProgram->nOp; } } - if( p->explain<2 ) break; + if( p->explain==SQLITE_STMTMODE_EXPLAIN ) break; if( pOp->opcode==OP_Explain ) break; if( pOp->opcode==OP_Init && p->pc>1 ) break; } @@ -2099,7 +2147,7 @@ int sqlite3VdbeList( sqlite3VdbeError(p, sqlite3ErrStr(p->rc)); }else{ char *zP4; - if( p->explain==1 ){ + if( p->explain==SQLITE_STMTMODE_EXPLAIN ){ pMem->flags = MEM_Int; pMem->u.i = i; /* Program counter */ pMem++; @@ -2140,7 +2188,7 @@ int sqlite3VdbeList( } pMem++; - if( p->explain==1 ){ + if( p->explain==SQLITE_STMTMODE_EXPLAIN ){ if( sqlite3VdbeMemClearAndResize(pMem, 4) ){ assert( p->db->mallocFailed ); return SQLITE_ERROR; @@ -2162,10 +2210,11 @@ int sqlite3VdbeList( #else pMem->flags = MEM_Null; /* Comment */ #endif + pMem++; } - p->nResColumn = 8 - 4*(p->explain-1); - p->pResultSet = &p->aMem[1]; + p->nRes = (int)(pMem - p->pResultSet); + assert( p->nRes<=VDBE_EXPLAIN_COLS ); p->rc = SQLITE_OK; rc = SQLITE_ROW; } @@ -2369,25 +2418,6 @@ void sqlite3VdbeMakeReady( resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); if( pParse->explain ){ - static const char * const azColName[] = { - "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", - "id", "parent", "notused", "detail" - }; - int iFirst, mx, i; - if( nMem<10 ) nMem = 10; - if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(p, 4); - iFirst = 8; - mx = 12; - }else{ - sqlite3VdbeSetNumCols(p, 8); - iFirst = 0; - mx = 8; - } - for(i=iFirst; iexplain = p->origExplain = pParse->explain; } p->expired = 0; @@ -3310,7 +3340,7 @@ int sqlite3VdbeReset(Vdbe *p){ #endif sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = 0; - p->pResultSet = 0; + p->nRes = 0; #ifdef SQLITE_DEBUG p->nWrite = 0; #endif