Index: src/select.c ================================================================== --- src/select.c +++ src/select.c @@ -5492,10 +5492,14 @@ #if SELECTTRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end compound-select processing\n")); if( pParse->iSelectId==0 && (sqlite3SelectTrace & 0x2000)!=0 ){ sqlite3TreeViewSelect(0, p, 0); } + if( pParse->iSelectId==0 && (sqlite3SelectTrace & 0x4000)!=0 ){ + int iNodeId = 0; + sqlite3AstSelect(p, &iNodeId, 0); + } #endif explainSetInteger(pParse->iSelectId, iRestoreSelectId); return rc; } #endif @@ -6285,10 +6289,14 @@ sqlite3DbFree(db, sAggInfo.aFunc); #if SELECTTRACE_ENABLED SELECTTRACE(0x1,pParse,p,("end processing\n")); if( pParse->iSelectId==0 && (sqlite3SelectTrace & 0x2000)!=0 ){ sqlite3TreeViewSelect(0, p, 0); + } + if( pParse->iSelectId==0 && (sqlite3SelectTrace & 0x4000)!=0 ){ + int iNodeId = 0; + sqlite3AstSelect(p, &iNodeId, 0); } #endif explainSetInteger(pParse->iSelectId, iRestoreSelectId); return rc; } Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -3676,10 +3676,15 @@ void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*); void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); void sqlite3TreeViewSelect(TreeView*, const Select*, u8); void sqlite3TreeViewWith(TreeView*, const With*, u8); + + void sqlite3AstExpr(const Expr*,int*,int); + void sqlite3AstExprList(const ExprList*,int*,int,int); + void sqlite3AstSelect(const Select*,int*,int); + void sqlite3AstWith(const With*,int*,int); #endif void sqlite3SetString(char **, sqlite3*, const char*); void sqlite3ErrorMsg(Parse*, const char*, ...); Index: src/treeview.c ================================================================== --- src/treeview.c +++ src/treeview.c @@ -567,7 +567,491 @@ ){ pView = sqlite3TreeViewPush(pView, moreToFollow); sqlite3TreeViewBareExprList(pView, pList, zLabel); sqlite3TreeViewPop(pView); } + +/**************************************************************************** +** Experimental (demonstration-only) routines for printing a AST as a +** relation: +** +** CREATE TABLE AstNode( +** nodeId INTEGER PRIMARY KEY, +** parentId INTEGER REFERENCES AstNode, +** name TEXT, +** value ANY +** ); +*/ + +#define ASTVT_NULL 0 +#define ASTVT_TEXT 1 +#define ASTVT_INT 2 + +/* +** Output a single row of the AstNode relation +*/ +static int sqlite3AstOneRow( + int *pNodeCnt, + int parentId, + const char *zName, + int eValtype, + int iValue, + const char *zValue +){ + int nodeId = ++(*pNodeCnt); + printf("%d,", nodeId); + if( parentId ){ + printf("%d,", parentId); + }else{ + printf("NULL,"); + } + printf("'%s',", zName); + switch( eValtype ){ + case ASTVT_INT: printf("%d\n", iValue); break; + case ASTVT_TEXT: printf("'%s'\n", zValue); break; + default: printf("NULL\n"); break; + } + return nodeId; +} +static int sqlite3AstOneRowInt( + int *pNodeCnt, + int parentId, + const char *zName, + int iValue +){ + return sqlite3AstOneRow(pNodeCnt,parentId,zName,ASTVT_INT,iValue,0); +} +static int sqlite3AstOneRowText( + int *pNodeCnt, + int parentId, + const char *zName, + const char *zValue +){ + return sqlite3AstOneRow(pNodeCnt,parentId,zName,ASTVT_TEXT,0,zValue); +} +static int sqlite3AstOneRowNull( + int *pNodeCnt, + int parentId, + const char *zName +){ + return sqlite3AstOneRow(pNodeCnt,parentId,zName,ASTVT_NULL,0,0); +} +static int sqlite3AstOneRowOp( + int *pNodeCnt, + int parentId, + const char *zName, + const Expr *pExpr +){ + char zFlg[30]; + sqlite3_snprintf(sizeof(zFlg),zFlg,"0x%6x",pExpr->flags); + return sqlite3AstOneRow(pNodeCnt,parentId,zName,ASTVT_TEXT,0,zFlg); +} + + +void sqlite3AstExpr(const Expr *pExpr, int *pNodeCnt, int iParentId){ + int iNodeId; + const char *zBinOp = 0; + const char *zUniOp = 0; + + if( pExpr==0 ) return; + switch( pExpr->op ){ + case TK_AGG_COLUMN: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "agg_column", pExpr); + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "table", pExpr->iTable); + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "column", pExpr->iColumn); + break; + } + case TK_COLUMN: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "column", pExpr); + if( pExpr->iTable>=0 ){ + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "table", pExpr->iTable); + } + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "column", pExpr->iColumn); + break; + } + case TK_INTEGER: { + if( pExpr->flags & EP_IntValue ){ + iNodeId = sqlite3AstOneRowInt(pNodeCnt, iParentId, + "integer", pExpr->u.iValue); + }else{ + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, + "integer", pExpr->u.zToken); + } + break; + } +#ifndef SQLITE_OMIT_FLOATING_POINT + case TK_FLOAT: { + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, + "float", pExpr->u.zToken); + break; + } +#endif + case TK_STRING: { + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, + "string", pExpr->u.zToken); + break; + } + case TK_NULL: { + iNodeId = sqlite3AstOneRowNull(pNodeCnt, iParentId, "null"); + break; + } + case TK_TRUEFALSE: { + iNodeId = sqlite3AstOneRowNull(pNodeCnt, iParentId, + sqlite3ExprTruthValue(pExpr) ? "true" : "false"); + break; + } +#ifndef SQLITE_OMIT_BLOB_LITERAL + case TK_BLOB: { + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, "blob", + pExpr->u.zToken); + break; + } +#endif + case TK_VARIABLE: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "variable", pExpr); + sqlite3AstOneRowText(pNodeCnt, iNodeId, "name", pExpr->u.zToken); + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "column", pExpr->iColumn); + break; + } + case TK_REGISTER: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "register", pExpr); + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "registerNumber", + pExpr->iTable); + break; + } + case TK_ID: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "id", pExpr); + sqlite3AstOneRowText(pNodeCnt, iNodeId, "Name", pExpr->u.zToken); + break; + } + case TK_LT: zBinOp = "LT"; break; + case TK_LE: zBinOp = "LE"; break; + case TK_GT: zBinOp = "GT"; break; + case TK_GE: zBinOp = "GE"; break; + case TK_NE: zBinOp = "NE"; break; + case TK_EQ: zBinOp = "EQ"; break; + case TK_IS: zBinOp = "IS"; break; + case TK_ISNOT: zBinOp = "ISNOT"; break; + case TK_AND: zBinOp = "AND"; break; + case TK_OR: zBinOp = "OR"; break; + case TK_PLUS: zBinOp = "ADD"; break; + case TK_STAR: zBinOp = "MUL"; break; + case TK_MINUS: zBinOp = "SUB"; break; + case TK_REM: zBinOp = "REM"; break; + case TK_BITAND: zBinOp = "BITAND"; break; + case TK_BITOR: zBinOp = "BITOR"; break; + case TK_SLASH: zBinOp = "DIV"; break; + case TK_LSHIFT: zBinOp = "LSHIFT"; break; + case TK_RSHIFT: zBinOp = "RSHIFT"; break; + case TK_CONCAT: zBinOp = "CONCAT"; break; + case TK_DOT: zBinOp = "DOT"; break; + + case TK_CAST: zUniOp = "CAST"; break; + case TK_UMINUS: zUniOp = "UMINUS"; break; + case TK_UPLUS: zUniOp = "UPLUS"; break; + case TK_BITNOT: zUniOp = "BITNOT"; break; + case TK_NOT: zUniOp = "NOT"; break; + case TK_ISNULL: zUniOp = "ISNULL"; break; + case TK_NOTNULL: zUniOp = "NOTNULL"; break; + case TK_IF_NULL_ROW: zUniOp = "IFNULLROW"; break; + + case TK_TRUTH: { + int x; + const char *azOp[] = { + "IS-FALSE", "IS-TRUE", "IS-NOT-FALSE", "IS-NOT-TRUE" + }; + assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT ); + assert( pExpr->pRight ); + assert( pExpr->pRight->op==TK_TRUEFALSE ); + x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight); + zUniOp = azOp[x]; + break; + } + + case TK_SPAN: { + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, "span", + pExpr->u.zToken); + sqlite3AstExpr(pExpr->pLeft, pNodeCnt, iNodeId); + break; + } + + case TK_COLLATE: { + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, "collate", + pExpr->u.zToken); + sqlite3AstExpr(pExpr->pLeft, pNodeCnt, iNodeId); + break; + } + + case TK_AGG_FUNCTION: + case TK_FUNCTION: { + ExprList *pFarg; /* List of function arguments */ + if( ExprHasProperty(pExpr, EP_TokenOnly) ){ + pFarg = 0; + }else{ + pFarg = pExpr->x.pList; + } + if( pExpr->op==TK_AGG_FUNCTION ){ + char zId[20]; + sqlite3_snprintf(sizeof(zId),zId,"agg_function%d", pExpr->op2); + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, zId, + pExpr->u.zToken); + }else{ + iNodeId = sqlite3AstOneRowText(pNodeCnt, iParentId, "function", + pExpr->u.zToken); + } + if( pFarg ){ + sqlite3AstExprList(pFarg, pNodeCnt, iNodeId, 0); + } + break; + } +#ifndef SQLITE_OMIT_SUBQUERY + case TK_EXISTS: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "exists-expr", pExpr); + sqlite3AstSelect(pExpr->x.pSelect, pNodeCnt, iNodeId); + break; + } + case TK_SELECT: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "select-expr", pExpr); + sqlite3AstSelect(pExpr->x.pSelect, pNodeCnt, iNodeId); + break; + } + case TK_IN: { + int id; + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "in", pExpr); + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "left"); + sqlite3AstExpr(pExpr->pLeft, pNodeCnt, id); + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "right"); + if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + sqlite3AstSelect(pExpr->x.pSelect, pNodeCnt, id); + }else{ + sqlite3AstExprList(pExpr->x.pList, pNodeCnt, id, 0); + } + break; + } +#endif /* SQLITE_OMIT_SUBQUERY */ + + /* + ** x BETWEEN y AND z + ** + ** This is equivalent to + ** + ** x>=y AND x<=z + ** + ** X is stored in pExpr->pLeft. + ** Y is stored in pExpr->pList->a[0].pExpr. + ** Z is stored in pExpr->pList->a[1].pExpr. + */ + case TK_BETWEEN: { + int id; + Expr *pX = pExpr->pLeft; + Expr *pY = pExpr->x.pList->a[0].pExpr; + Expr *pZ = pExpr->x.pList->a[1].pExpr; + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "between", pExpr); + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "left"); + sqlite3AstExpr(pX, pNodeCnt, id); + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "lower"); + sqlite3AstExpr(pY, pNodeCnt, id); + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "upper"); + sqlite3AstExpr(pZ, pNodeCnt, id); + break; + } + case TK_TRIGGER: { + /* If the opcode is TK_TRIGGER, then the expression is a reference + ** to a column in the new.* or old.* pseudo-tables available to + ** trigger programs. In this case Expr.iTable is set to 1 for the + ** new.* pseudo-table, or 0 for the old.* pseudo-table. Expr.iColumn + ** is set to the column of the pseudo-table to read, or to -1 to + ** read the rowid field. + */ + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, + pExpr->iTable ? "new-column" : "old-column", pExpr); + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "column", pExpr->iColumn); + break; + } + case TK_CASE: { + int id; + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "case", pExpr); + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "left"); + sqlite3AstExpr(pExpr->pLeft, pNodeCnt, id); + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "list"); + sqlite3AstExprList(pExpr->x.pList, pNodeCnt, id, 0); + break; + } + case TK_RAISE: { + const char *zType = "unk"; + switch( pExpr->affinity ){ + case OE_Rollback: zType = "rollback"; break; + case OE_Abort: zType = "abort"; break; + case OE_Fail: zType = "fail"; break; + case OE_Ignore: zType = "ignore"; break; + } + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "raise", pExpr); + sqlite3AstOneRowText(pNodeCnt, iNodeId, "type", zType); + sqlite3AstOneRowText(pNodeCnt, iNodeId, "arg", pExpr->u.zToken); + break; + } + case TK_MATCH: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "match", pExpr); + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "table", pExpr->iTable); + sqlite3AstOneRowInt(pNodeCnt, iNodeId, "column", pExpr->iColumn); + break; + } + case TK_VECTOR: { + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, "vector", pExpr); + sqlite3AstExprList(pExpr->x.pList, pNodeCnt, iNodeId, 0); + break; + } + case TK_SELECT_COLUMN: { + iNodeId = sqlite3AstOneRowInt(pNodeCnt, iParentId, "select-column", + pExpr->iColumn); + sqlite3AstSelect(pExpr->pLeft->x.pSelect, pNodeCnt, iNodeId); + break; + } + default: { + sqlite3AstOneRowInt(pNodeCnt, iParentId, "unknown", pExpr->op); + break; + } + } + if( zBinOp ){ + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, zBinOp, pExpr); + if( pExpr->pLeft ){ + int id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "left"); + sqlite3AstExpr(pExpr->pLeft, pNodeCnt, id); + } + if( pExpr->pRight ){ + int id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "right"); + sqlite3AstExpr(pExpr->pRight, pNodeCnt, id); + } + } + if( zUniOp ){ + iNodeId = sqlite3AstOneRowOp(pNodeCnt, iParentId, zUniOp, pExpr); + if( pExpr->pLeft ){ + sqlite3AstExpr(pExpr->pLeft, pNodeCnt, iNodeId); + } + } +} + +void sqlite3AstExprList( + const ExprList *pList, + int *pNodeCnt, + int iParentId, + int showSortOrder +){ + int i; + if( pList==0 ) return; + for(i=0; inExpr; i++){ + const struct ExprList_item *pItem = pList->a + i; + int id = sqlite3AstOneRowNull(pNodeCnt, iParentId, "listitem"); + if( pItem->zName && pItem->zName[0] ){ + sqlite3AstOneRowText(pNodeCnt, id, "name", pItem->zName); + } + if( showSortOrder && pItem->sortOrder ){ + sqlite3AstOneRowInt(pNodeCnt, id, "desc", 1); + } + sqlite3AstExpr(pList->a[i].pExpr, pNodeCnt, id); + } +} + + +/* +** Generate a human-readable description of a WITH clause. +*/ +void sqlite3AstWith(const With *pWith, int *pNodeCnt, int iParentId){ + int i; + if( pWith==0 ) return; + if( pWith->nCte==0 ) return; + if( pWith && pWith->nCte>0 ){ + int iNodeId = sqlite3AstOneRowNull(pNodeCnt, iParentId, "WITH"); + for(i=0; inCte; i++){ + const struct Cte *pCte = &pWith->a[i]; + int id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "with-cte"); + sqlite3AstOneRowText(pNodeCnt, id, "name", pCte->zName); + if( pCte->pCols && pCte->pCols->nExpr>0 ){ + int x = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "column-list"); + sqlite3AstExprList(pCte->pCols, pNodeCnt, x, 0); + } + sqlite3AstSelect(pCte->pSelect, pNodeCnt, iNodeId); + } + } +} + + +/* +** Generate a human-readable description of a Select object. +*/ +void sqlite3AstSelect(const Select *p, int *pNodeCnt, int iParentId){ + if( p==0 ) return; + if( p->pWith ){ + sqlite3AstWith(p->pWith, pNodeCnt, iParentId); + } + do{ + int iNodeId = sqlite3AstOneRowNull(pNodeCnt, iParentId, "SELECT"); + int id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "result-set"); + sqlite3AstExprList(p->pEList, pNodeCnt, id, 0); + if( p->pSrc && p->pSrc->nSrc ){ + int i; + for(i=0; ipSrc->nSrc; i++){ + struct SrcList_item *pItem = &p->pSrc->a[i]; + int iFrom = sqlite3AstOneRowInt(pNodeCnt, iNodeId, + "from-item", pItem->iCursor); + if( pItem->zDatabase ){ + sqlite3AstOneRowText(pNodeCnt, iFrom, "database", pItem->zDatabase); + } + if( pItem->zName ){ + sqlite3AstOneRowText(pNodeCnt, iFrom, "name", pItem->zName); + } + if( pItem->pTab ){ + sqlite3AstOneRowText(pNodeCnt, iFrom, "tabname", pItem->pTab->zName); + } + if( pItem->zAlias ){ + sqlite3AstOneRowText(pNodeCnt, iFrom, "as", pItem->zAlias); + } + if( pItem->fg.jointype & JT_LEFT ){ + sqlite3AstOneRowInt(pNodeCnt, iFrom, "left-join", 1); + } + if( pItem->pSelect ){ + sqlite3AstSelect(pItem->pSelect, pNodeCnt, iFrom); + } + if( pItem->fg.isTabFunc ){ + int x = sqlite3AstOneRowNull(pNodeCnt, iFrom, "func-args"); + sqlite3AstExprList(pItem->u1.pFuncArg, pNodeCnt, x, 0); + } + } + } + if( p->pWhere ){ + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "where"); + sqlite3AstExpr(p->pWhere, pNodeCnt, id); + } + if( p->pGroupBy ){ + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "groupby"); + sqlite3AstExprList(p->pGroupBy, pNodeCnt, id, 0); + } + if( p->pHaving ){ + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "having"); + sqlite3AstExpr(p->pHaving, pNodeCnt, id); + } + if( p->pOrderBy ){ + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "orderby"); + sqlite3AstExprList(p->pOrderBy, pNodeCnt, id, 1); + } + if( p->pLimit ){ + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "limit"); + sqlite3AstExpr(p->pLimit->pLeft, pNodeCnt, id); + if( p->pLimit->pRight ){ + id = sqlite3AstOneRowNull(pNodeCnt, iNodeId, "offset"); + sqlite3AstExpr(p->pLimit->pRight, pNodeCnt, id); + } + } + if( p->pPrior ){ + const char *zOp = "UNION"; + switch( p->op ){ + case TK_ALL: zOp = "UNION ALL"; break; + case TK_INTERSECT: zOp = "INTERSECT"; break; + case TK_EXCEPT: zOp = "EXCEPT"; break; + } + iParentId = sqlite3AstOneRowNull(pNodeCnt, iNodeId, zOp); + } + p = p->pPrior; + }while( p!=0 ); +} #endif /* SQLITE_DEBUG */