]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
The ".selecttrace 0x4000" command causes the AST to be output to stdout as ast-trace-demo
authordrh <drh@noemail.net>
Tue, 1 May 2018 01:18:21 +0000 (01:18 +0000)
committerdrh <drh@noemail.net>
Tue, 1 May 2018 01:18:21 +0000 (01:18 +0000)
a table with four columns.

FossilOrigin-Name: 2a75e631eed603e8e21a5bb77f2a56ac9d9d5a1fd33cb24461b24c3bb9778703

manifest
manifest.uuid
src/select.c
src/sqliteInt.h
src/treeview.c

index 64af0212bc139a51058ba94d8d06083da61f852f..4cb3d90dbabda9dd1133a2528e91ee651130ea26 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Test\scases\sadded\sfor\sSQLITE_DBCONFIG_RESET_DATABASE.
-D 2018-04-28T19:08:02.897
+C The\s".selecttrace\s0x4000"\scommand\scauses\sthe\sAST\sto\sbe\soutput\sto\sstdout\sas\na\stable\swith\sfour\scolumns.
+D 2018-05-01T01:18:21.511
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F Makefile.in 5ce9343cba9c189046f1afe6d2bcc1f68079439febc05267b98aec6ecc752439
@@ -493,12 +493,12 @@ F src/printf.c d3b7844ddeb11fbbdd38dd84d09c9c1ac171d21fb038473c3aa97981201cc660
 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c 6415381a0e9d22c0e7cba33ca4a53f81474190862f5d4838190f5eb5b0b47bc9
 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
-F src/select.c daf07d8defce3311f9e69f1280a874d78bc1d16c305f6aa689640f7afa02842f
+F src/select.c f84fef1a0d69eff9ebf35b29d5261822869a20ba333db9d4da30c46d7a2a2493
 F src/shell.c.in 54b902ab7d840f60ddfabc13124c85d4980342c88aff7679f2cc25f67c21ade7
 F src/sqlite.h.in d669de545f18f2f01362de02e309cd7f15185958c71bac8f53cd5438b46d2bea
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 83a3c4ce93d650bedfd1aa558cb85a516bd6d094445ee989740827d0d944368d
-F src/sqliteInt.h 8ac0138eae10337b745b03dad0124fd63ae911c0503e795729503e7fc6234d57
+F src/sqliteInt.h 9aef9530fc7f971d88770eef29dcf5b632ceedc67cf91649d8229e8cbc25df5c
 F src/sqliteLimit.h 1513bfb7b20378aa0041e7022d04acb73525de35b80b252f1b83fedb4de6a76b
 F src/status.c 46e7aec11f79dad50965a5ca5fa9de009f7d6bde08be2156f1538a0a296d4d0e
 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34
@@ -556,7 +556,7 @@ F src/test_windirent.h 90dfbe95442c9762357fe128dc7ae3dc199d006de93eb33ba3972e0a9
 F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
 F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
 F src/tokenize.c bbde32eac9eb1280f5292bcdfef66f5a57e43176cbf9347e0efab9f75e133f97
-F src/treeview.c 6cea286ca9af8b126dae9714799265353387244eba0d451c3cc2cd60946cc4c7
+F src/treeview.c c66a74b9cda27393607a58a4ba4d44875a862c0e0428827ce5840dd7a663834d
 F src/trigger.c 4ace6d1d5ba9a89822deb287317f33c810440526eafe185c2d8a48c31df1e995
 F src/update.c 5be2f501ddc704fc04183bdb28b25eab930bb8553d973429a089ec94fa85cf2b
 F src/upsert.c ae4a4823b45c4daf87e8aea8c0f582a8844763271f5ed54ee5956c4c612734f4
@@ -1727,7 +1727,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 564ae8297d417ba4b7978e430d41f125007177673163f6ed9adc3a3974f73d24
-R f62951ddf79b7c70897c7cedc3fc5902
+P 08665a9e2e50a0a1e62529884cf65f8090debe89a306a3904b53268729ab5ad5
+R 43ceb6108f34ca0a76ce471754b59fcb
+T *branch * ast-trace-demo
+T *sym-ast-trace-demo *
+T -sym-trunk *
 U drh
-Z d48406f4893f856e5db8717a8971e33f
+Z 05d2e828fe1abfa136e23bf30c643644
index c789d5b903ad999fbe9217b3bb5021e3308696c0..6bee54be0c828f8de9239ede7994e23b8075e85a 100644 (file)
@@ -1 +1 @@
-08665a9e2e50a0a1e62529884cf65f8090debe89a306a3904b53268729ab5ad5
\ No newline at end of file
+2a75e631eed603e8e21a5bb77f2a56ac9d9d5a1fd33cb24461b24c3bb9778703
\ No newline at end of file
index ac313d028899491250522a929fe85fc77be03ca1..de634d957217abf62b37d6501a5efc2afc27dd62 100644 (file)
@@ -5494,6 +5494,10 @@ int sqlite3Select(
     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;
@@ -6288,6 +6292,10 @@ select_end:
   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 c700c261b8c142a6d2cc4c1fbe8f5ab80224c874..b67330f6d4a449785d60c6592f15b2effdd17c55 100644 (file)
@@ -3678,6 +3678,11 @@ char *sqlite3VMPrintf(sqlite3*,const char*, va_list);
   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
 
 
index 51fa7f06ca9ba9f3f2c63694033cd9aef64b8411..4b1d1781d619e15d239fefe26a110e88da741e56 100644 (file)
@@ -570,4 +570,488 @@ void sqlite3TreeViewExprList(
   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; i<pList->nExpr; 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; i<pWith->nCte; 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; i<p->pSrc->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 */