diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/expr.c | 50 | ||||
-rw-r--r-- | src/main.c | 14 | ||||
-rw-r--r-- | src/parse.y | 3 | ||||
-rw-r--r-- | src/printf.c | 6 | ||||
-rw-r--r-- | src/select.c | 103 | ||||
-rw-r--r-- | src/shell.c | 9 | ||||
-rw-r--r-- | src/sqlite.h.in | 3 | ||||
-rw-r--r-- | src/sqliteInt.h | 24 | ||||
-rw-r--r-- | src/vdbeInt.h | 17 | ||||
-rw-r--r-- | src/vdbeaux.c | 4 | ||||
-rw-r--r-- | src/vdbetrace.c | 117 |
11 files changed, 279 insertions, 71 deletions
diff --git a/src/expr.c b/src/expr.c index e8a9d6410..d157743ab 100644 --- a/src/expr.c +++ b/src/expr.c @@ -2939,6 +2939,56 @@ int sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int target){ return inReg; } +#if defined(SQLITE_DEBUG) +/* +** Generate a human-readable explanation of an expression tree. +*/ +void sqlite3ExplainExpr(Vdbe *pOut, Expr *p){ + if( p==0 ){ + sqlite3ExplainPrintf(pOut, "(C-null)"); + return; + } + if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + sqlite3ExplainPrintf(pOut, "(%s", p->u.zToken); + }else{ + sqlite3ExplainPrintf(pOut, "(op=%d", p->op); + } + if( p->pLeft ){ + sqlite3ExplainPrintf(pOut, " left="); + sqlite3ExplainExpr(pOut, p->pLeft); + } + if( p->pRight ){ + sqlite3ExplainPrintf(pOut, " right="); + sqlite3ExplainExpr(pOut, p->pRight); + } + sqlite3ExplainPrintf(pOut, ")"); +} +#endif /* defined(SQLITE_DEBUG) */ + +#if defined(SQLITE_DEBUG) +/* +** Generate a human-readable explanation of an expression list. +*/ +void sqlite3ExplainExprList(Vdbe *pOut, ExprList *pList){ + int i; + if( pList==0 ){ + sqlite3ExplainPrintf(pOut, "(empty-list)"); + return; + } + sqlite3ExplainPush(pOut); + for(i=0; i<pList->nExpr; i++){ + sqlite3ExplainPrintf(pOut, "%02d: ", i); + sqlite3ExplainPush(pOut); + sqlite3ExplainExpr(pOut, pList->a[i].pExpr); + sqlite3ExplainPop(pOut); + if( i<pList->nExpr-1 ){ + sqlite3ExplainNL(pOut); + } + } + sqlite3ExplainPop(pOut); +} +#endif /* SQLITE_DEBUG */ + /* ** Return TRUE if pExpr is an constant expression that is appropriate ** for factoring out of a loop. Appropriate expressions are: diff --git a/src/main.c b/src/main.c index 8562a9072..ca98d3140 100644 --- a/src/main.c +++ b/src/main.c @@ -2936,6 +2936,20 @@ int sqlite3_test_control(int op, ...){ break; } + /* sqlite3_test_control(SQLITE_TESTCTRL_EXPLAIN_STMT, + ** sqlite3_stmt*,const char**); + ** + ** If compiled with SQLITE_DEBUG, each sqlite3_stmt holds a string that + ** describes the optimized parse tree. This test-control returns a + ** pointer to that string. + */ + case SQLITE_TESTCTRL_EXPLAIN_STMT: { + sqlite3_stmt *pStmt = va_arg(ap, sqlite3_stmt*); + const char **pzRet = va_arg(ap, const char**); + *pzRet = sqlite3VdbeExplanation((Vdbe*)pStmt); + break; + } + } va_end(ap); #endif /* SQLITE_OMIT_BUILTIN_TEST */ diff --git a/src/parse.y b/src/parse.y index b838215d4..2b8e9170f 100644 --- a/src/parse.y +++ b/src/parse.y @@ -394,6 +394,9 @@ cmd ::= DROP VIEW ifexists(E) fullname(X). { cmd ::= select(X). { SelectDest dest = {SRT_Output, 0, 0, 0, 0}; sqlite3Select(pParse, X, &dest); + sqlite3ExplainBegin(pParse->pVdbe); + sqlite3ExplainSelect(pParse->pVdbe, X); + sqlite3ExplainFinish(pParse->pVdbe); sqlite3SelectDelete(pParse->db, X); } diff --git a/src/printf.c b/src/printf.c index 0babee514..58cfd2bd4 100644 --- a/src/printf.c +++ b/src/printf.c @@ -136,7 +136,7 @@ static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ /* ** Append N space characters to the given string buffer. */ -static void appendSpace(StrAccum *pAccum, int N){ +void sqlite3AppendSpace(StrAccum *pAccum, int N){ static const char zSpaces[] = " "; while( N>=(int)sizeof(zSpaces)-1 ){ sqlite3StrAccumAppend(pAccum, zSpaces, sizeof(zSpaces)-1); @@ -664,7 +664,7 @@ void sqlite3VXPrintf( register int nspace; nspace = width-length; if( nspace>0 ){ - appendSpace(pAccum, nspace); + sqlite3AppendSpace(pAccum, nspace); } } if( length>0 ){ @@ -674,7 +674,7 @@ void sqlite3VXPrintf( register int nspace; nspace = width-length; if( nspace>0 ){ - appendSpace(pAccum, nspace); + sqlite3AppendSpace(pAccum, nspace); } } sqlite3_free(zExtra); diff --git a/src/select.c b/src/select.c index da75a51cd..75b6a60e0 100644 --- a/src/select.c +++ b/src/select.c @@ -4496,96 +4496,65 @@ select_end: #if defined(SQLITE_DEBUG) /* -******************************************************************************* -** The following code is used for testing and debugging only. The code -** that follows does not appear in normal builds. -** -** These routines are used to print out the content of all or part of a -** parse structures such as Select or Expr. Such printouts are useful -** for helping to understand what is happening inside the code generator -** during the execution of complex SELECT statements. -** -** These routine are not called anywhere from within the normal -** code base. Then are intended to be called from within the debugger -** or from temporary "printf" statements inserted for debugging. +** Generate a human-readable description of a the Select object. */ -void sqlite3PrintExpr(Expr *p){ - if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ - sqlite3DebugPrintf("(%s", p->u.zToken); - }else{ - sqlite3DebugPrintf("(%d", p->op); - } - if( p->pLeft ){ - sqlite3DebugPrintf(" "); - sqlite3PrintExpr(p->pLeft); - } - if( p->pRight ){ - sqlite3DebugPrintf(" "); - sqlite3PrintExpr(p->pRight); - } - sqlite3DebugPrintf(")"); -} -void sqlite3PrintExprList(ExprList *pList){ - int i; - for(i=0; i<pList->nExpr; i++){ - sqlite3PrintExpr(pList->a[i].pExpr); - if( i<pList->nExpr-1 ){ - sqlite3DebugPrintf(", "); - } +void sqlite3ExplainSelect(Vdbe *pVdbe, Select *p){ + if( p==0 ){ + sqlite3ExplainPrintf(pVdbe, "(empty-select)"); + return; } -} -void sqlite3PrintSelect(Select *p, int indent){ - sqlite3DebugPrintf("%*sSELECT(%p) ", indent, "", p); - sqlite3PrintExprList(p->pEList); - sqlite3DebugPrintf("\n"); - if( p->pSrc ){ - char *zPrefix; + sqlite3ExplainPrintf(pVdbe, "SELECT "); + sqlite3ExplainPush(pVdbe); + sqlite3ExplainExprList(pVdbe, p->pEList); + sqlite3ExplainNL(pVdbe); + sqlite3ExplainPop(pVdbe); + if( p->pSrc && p->pSrc->nSrc ){ int i; - zPrefix = "FROM"; + sqlite3ExplainPrintf(pVdbe, " FROM "); + sqlite3ExplainPush(pVdbe); for(i=0; i<p->pSrc->nSrc; i++){ struct SrcList_item *pItem = &p->pSrc->a[i]; - sqlite3DebugPrintf("%*s ", indent+6, zPrefix); - zPrefix = ""; + sqlite3ExplainPrintf(pVdbe, "%02d: ", i); + sqlite3ExplainPush(pVdbe); if( pItem->pSelect ){ - sqlite3DebugPrintf("(\n"); - sqlite3PrintSelect(pItem->pSelect, indent+10); - sqlite3DebugPrintf("%*s)", indent+8, ""); + sqlite3ExplainSelect(pVdbe, pItem->pSelect); }else if( pItem->zName ){ - sqlite3DebugPrintf("%s", pItem->zName); + sqlite3ExplainPrintf(pVdbe, "%s", pItem->zName); } if( pItem->pTab ){ - sqlite3DebugPrintf("(table: %s)", pItem->pTab->zName); + sqlite3ExplainPrintf(pVdbe, " (table: %s)", pItem->pTab->zName); } if( pItem->zAlias ){ - sqlite3DebugPrintf(" AS %s", pItem->zAlias); - } - if( i<p->pSrc->nSrc-1 ){ - sqlite3DebugPrintf(","); + sqlite3ExplainPrintf(pVdbe, " (AS %s)", pItem->zAlias); } - sqlite3DebugPrintf("\n"); + sqlite3ExplainNL(pVdbe); + sqlite3ExplainPop(pVdbe); } + sqlite3ExplainPop(pVdbe); } if( p->pWhere ){ - sqlite3DebugPrintf("%*s WHERE ", indent, ""); - sqlite3PrintExpr(p->pWhere); - sqlite3DebugPrintf("\n"); + sqlite3ExplainPrintf(pVdbe, " WHERE "); + sqlite3ExplainExpr(pVdbe, p->pWhere); + sqlite3ExplainNL(pVdbe); } if( p->pGroupBy ){ - sqlite3DebugPrintf("%*s GROUP BY ", indent, ""); - sqlite3PrintExprList(p->pGroupBy); - sqlite3DebugPrintf("\n"); + sqlite3ExplainPrintf(pVdbe, " GROUP BY "); + sqlite3ExplainExprList(pVdbe, p->pGroupBy); + sqlite3ExplainNL(pVdbe); } if( p->pHaving ){ - sqlite3DebugPrintf("%*s HAVING ", indent, ""); - sqlite3PrintExpr(p->pHaving); - sqlite3DebugPrintf("\n"); + sqlite3ExplainPrintf(pVdbe, "HAVING "); + sqlite3ExplainExpr(pVdbe, p->pHaving); + sqlite3ExplainNL(pVdbe); } if( p->pOrderBy ){ - sqlite3DebugPrintf("%*s ORDER BY ", indent, ""); - sqlite3PrintExprList(p->pOrderBy); - sqlite3DebugPrintf("\n"); + sqlite3ExplainPrintf(pVdbe, " ORDER BY "); + sqlite3ExplainExprList(pVdbe, p->pOrderBy); + sqlite3ExplainNL(pVdbe); } + sqlite3ExplainPrintf(pVdbe, " END"); } + /* End of the structure debug printing code *****************************************************************************/ #endif /* defined(SQLITE_TEST) || defined(SQLITE_DEBUG) */ diff --git a/src/shell.c b/src/shell.c index e33a0687b..93ed9a6db 100644 --- a/src/shell.c +++ b/src/shell.c @@ -1127,6 +1127,15 @@ static int shell_exec( fprintf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql); } + /* Output TESTCTRL_EXPLAIN text of requested */ + if( pArg && pArg->mode==MODE_Explain ){ + const char *zExplain = 0; + sqlite3_test_control(SQLITE_TESTCTRL_EXPLAIN_STMT, pStmt, &zExplain); + if( zExplain && zExplain[0] ){ + fprintf(pArg->out, "%s", zExplain); + } + } + /* perform the first step. this will tell us if we ** have a result set or not and how wide it is. */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index bd5b41f43..8c9e220e0 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -5682,7 +5682,8 @@ int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_ISKEYWORD 16 #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 -#define SQLITE_TESTCTRL_LAST 18 +#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 +#define SQLITE_TESTCTRL_LAST 19 /* ** CAPI3REF: SQLite Runtime Status diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 890f44f5e..1e7af8e34 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -2650,6 +2650,29 @@ char *sqlite3MAppendf(sqlite3*,char*,const char*,...); #if defined(SQLITE_TEST) void *sqlite3TestTextToPtr(const char*); #endif + +/* Output formatting for SQLITE_TESTCTRL_EXPLAIN */ +#if defined(SQLITE_DEBUG) + void sqlite3ExplainBegin(Vdbe*); + void sqlite3ExplainPrintf(Vdbe*, const char*, ...); + void sqlite3ExplainNL(Vdbe*); + void sqlite3ExplainPush(Vdbe*); + void sqlite3ExplainPop(Vdbe*); + void sqlite3ExplainFinish(Vdbe*); + void sqlite3ExplainSelect(Vdbe*, Select*); + void sqlite3ExplainExpr(Vdbe*, Expr*); + void sqlite3ExplainExprList(Vdbe*, ExprList*); + const char *sqlite3VdbeExplanation(Vdbe*); +#else +# define sqlite3ExplainBegin(X) +# define sqlite3ExplainSelect(A,B) +# define sqlite3ExplainExpr(A,B) +# define sqlite3ExplainExprList(A,B) +# define sqlite3ExplainFinish(X) +# define sqlite3VdbeExplanation(X) 0 +#endif + + void sqlite3SetString(char **, sqlite3*, const char*, ...); void sqlite3ErrorMsg(Parse*, const char*, ...); int sqlite3Dequote(char*); @@ -3030,6 +3053,7 @@ int sqlite3OpenTempDatabase(Parse *); void sqlite3StrAccumInit(StrAccum*, char*, int, int); void sqlite3StrAccumAppend(StrAccum*,const char*,int); +void sqlite3AppendSpace(StrAccum*,int); char *sqlite3StrAccumFinish(StrAccum*); void sqlite3StrAccumReset(StrAccum*); void sqlite3SelectDestInit(SelectDest*,int,int); diff --git a/src/vdbeInt.h b/src/vdbeInt.h index 803ae1630..44071e685 100644 --- a/src/vdbeInt.h +++ b/src/vdbeInt.h @@ -33,6 +33,9 @@ typedef unsigned char Bool; /* Opaque type used by code in vdbesort.c */ typedef struct VdbeSorter VdbeSorter; +/* Opaque type used by the explainer */ +typedef struct Explain Explain; + /* ** A cursor is a pointer into a single BTree within a database file. ** The cursor can seek to a BTree entry with a particular key, or @@ -256,6 +259,18 @@ struct sqlite3_context { }; /* +** An Explain object accumulates indented output which is helpful +** in describing recursive data structures. +*/ +struct Explain { + Vdbe *pVdbe; /* Attach the explanation to this Vdbe */ + StrAccum str; /* The string being accumulated */ + int nIndent; /* Number of elements in aIndent */ + u16 aIndent[100]; /* Levels of indentation */ + char zBase[100]; /* Initial space */ +}; + +/* ** An instance of the virtual machine. This structure contains the complete ** state of the virtual machine. ** @@ -320,6 +335,8 @@ struct Vdbe { void *pFree; /* Free this when deleting the vdbe */ #ifdef SQLITE_DEBUG FILE *trace; /* Write an execution trace here, if not NULL */ + Explain *pExplain; /* The explainer */ + char *zExplain; /* Explanation of data structures */ #endif VdbeFrame *pFrame; /* Parent frame */ VdbeFrame *pDelFrame; /* List of frame objects to free on VM reset */ diff --git a/src/vdbeaux.c b/src/vdbeaux.c index 7c69d28cf..510248a5f 100644 --- a/src/vdbeaux.c +++ b/src/vdbeaux.c @@ -2464,6 +2464,10 @@ void sqlite3VdbeDeleteObject(sqlite3 *db, Vdbe *p){ sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); sqlite3DbFree(db, p->pFree); +#if defined(SQLITE_DEBUG) + sqlite3_free(p->zExplain); + sqlite3_free(p->pExplain); +#endif sqlite3DbFree(db, p); } diff --git a/src/vdbetrace.c b/src/vdbetrace.c index de123b550..bc05e5897 100644 --- a/src/vdbetrace.c +++ b/src/vdbetrace.c @@ -12,6 +12,8 @@ ** ** This file contains code used to insert the values of host parameters ** (aka "wildcards") into the SQL text output by sqlite3_trace(). +** +** The Vdbe explainer is also found here. */ #include "sqliteInt.h" #include "vdbeInt.h" @@ -152,3 +154,118 @@ char *sqlite3VdbeExpandSql( } #endif /* #ifndef SQLITE_OMIT_TRACE */ + +/***************************************************************************** +** The following code implements the data-structure explaining logic +** for the Vdbe. +*/ + +#if defined(SQLITE_DEBUG) + +/* +** Allocate a new Explain object +*/ +void sqlite3ExplainBegin(Vdbe *pVdbe){ + if( pVdbe ){ + sqlite3BeginBenignMalloc(); + Explain *p = sqlite3_malloc( sizeof(Explain) ); + if( p ){ + memset(p, 0, sizeof(*p)); + p->pVdbe = pVdbe; + sqlite3_free(pVdbe->pExplain); + pVdbe->pExplain = p; + sqlite3StrAccumInit(&p->str, p->zBase, sizeof(p->zBase), + SQLITE_MAX_LENGTH); + p->str.useMalloc = 2; + }else{ + sqlite3EndBenignMalloc(); + } + } +} + +/* +** Return true if the Explain ends with a new-line. +*/ +static int endsWithNL(Explain *p){ + return p && p->str.zText && p->str.nChar + && p->str.zText[p->str.nChar-1]=='\n'; +} + +/* +** Append text to the indentation +*/ +void sqlite3ExplainPrintf(Vdbe *pVdbe, const char *zFormat, ...){ + Explain *p; + if( pVdbe && (p = pVdbe->pExplain)!=0 ){ + va_list ap; + if( p->nIndent && endsWithNL(p) ){ + int n = p->nIndent; + if( n>ArraySize(p->aIndent) ) n = ArraySize(p->aIndent); + sqlite3AppendSpace(&p->str, p->aIndent[n-1]); + } + va_start(ap, zFormat); + sqlite3VXPrintf(&p->str, 1, zFormat, ap); + va_end(ap); + } +} + +/* +** Append a '\n' if there is not already one. +*/ +void sqlite3ExplainNL(Vdbe *pVdbe){ + Explain *p; + if( pVdbe && (p = pVdbe->pExplain)!=0 && !endsWithNL(p) ){ + sqlite3StrAccumAppend(&p->str, "\n", 1); + } +} + +/* +** Push a new indentation level. Subsequent lines will be indented +** so that they begin at the current cursor position. +*/ +void sqlite3ExplainPush(Vdbe *pVdbe){ + Explain *p; + if( pVdbe && (p = pVdbe->pExplain)!=0 ){ + if( p->str.zText && p->nIndent<ArraySize(p->aIndent) ){ + const char *z = p->str.zText; + int i = p->str.nChar-1; + int x; + while( i>=0 && z[i]!='\n' ){ i--; } + x = (p->str.nChar - 1) - i; + if( p->nIndent && x<p->aIndent[p->nIndent-1] ){ + x = p->aIndent[p->nIndent-1]; + } + p->aIndent[p->nIndent] = x; + } + p->nIndent++; + } +} + +/* +** Pop the indentation stack by one level. +*/ +void sqlite3ExplainPop(Vdbe *p){ + if( p && p->pExplain ) p->pExplain->nIndent--; +} + +/* +** Free the indentation structure +*/ +void sqlite3ExplainFinish(Vdbe *pVdbe){ + if( pVdbe && pVdbe->pExplain ){ + sqlite3_free(pVdbe->zExplain); + sqlite3ExplainNL(pVdbe); + pVdbe->zExplain = sqlite3StrAccumFinish(&pVdbe->pExplain->str); + sqlite3_free(pVdbe->pExplain); + pVdbe->pExplain = 0; + sqlite3EndBenignMalloc(); + } +} + +/* +** Return the explanation of a virtual machine. +*/ +const char *sqlite3VdbeExplanation(Vdbe *pVdbe){ + return (pVdbe && pVdbe->zExplain) ? pVdbe->zExplain : 0; +} +#endif /* defined(SQLITE_DEBUG) */ |