]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add support for EQP mode.
authordrh <>
Thu, 23 Oct 2025 18:57:15 +0000 (18:57 +0000)
committerdrh <>
Thu, 23 Oct 2025 18:57:15 +0000 (18:57 +0000)
FossilOrigin-Name: 61b2011d8ec2bfe98595ac9ccae5ed0037444757a800706d0ecbc279014fd3fb

ext/qrf/qrf.c
manifest
manifest.uuid

index 3f063894e7ff6bdd89c9379e9876eb2c8343c8e6..4ccc3e46f43b007a5e88a3051d0ec2da676c28a7 100644 (file)
 #include <string.h>
 #include <ctype.h>
 
+typedef sqlite3_int64 i64;
+
+/* A single line in the EQP output */
+typedef struct EQPGraphRow EQPGraphRow;
+struct EQPGraphRow {
+  int iEqpId;           /* ID for this row */
+  int iParentId;        /* ID of the parent row */
+  EQPGraphRow *pNext;   /* Next row in sequence */
+  char zText[1];        /* Text to display for this row */
+};
+
+/* All EQP output is collected into an instance of the following */
+typedef struct EQPGraph EQPGraph;
+struct EQPGraph {
+  EQPGraphRow *pRow;    /* Linked list of all rows of the EQP output */
+  EQPGraphRow *pLast;   /* Last element of the pRow list */
+  char zPrefix[100];    /* Graph prefix */
+};
+
 /*
 ** Private state information.  Subject to change from one release to the
 ** next.
@@ -29,7 +48,10 @@ struct Qrf {
   int iErr;                   /* Error code */
   int nCol;                   /* Number of output columns */
   int mxColWth;               /* Maximum size of any column name */
-  const char **azCol;         /* Names of output columns */
+  union {
+    const char **azCol;         /* Names of output columns (MODE_Line) */
+    EQPGraph *pGraph;           /* EQP graph (MODE_EQP) */
+  } u;
   sqlite3_int64 nRow;         /* Number of rows handled so far */
   int *actualWidth;           /* Actual width of each column */
   sqlite3_qrf_spec spec;      /* Copy of the original spec */
@@ -64,6 +86,109 @@ static void qrfOom(Qrf *p){
   qrfError(p, SQLITE_NOMEM, "out of memory");
 }
 
+/*
+** Add a new entry to the EXPLAIN QUERY PLAN data
+*/
+static void qrfEqpAppend(Qrf *p, int iEqpId, int p2, const char *zText){
+  EQPGraphRow *pNew;
+  sqlite3_int64 nText;
+  if( zText==0 ) return;
+  if( p->u.pGraph==0 ){
+    p->u.pGraph = sqlite3_malloc64( sizeof(EQPGraph) );
+    if( p->u.pGraph==0 ){
+      qrfOom(p);
+      return;
+    }
+    memset(p->u.pGraph, 0, sizeof(EQPGraph) );
+  }
+  nText = strlen(zText);
+  pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
+  if( pNew==0 ){
+    qrfOom(p);
+    return;
+  }
+  pNew->iEqpId = iEqpId;
+  pNew->iParentId = p2;
+  memcpy(pNew->zText, zText, nText+1);
+  pNew->pNext = 0;
+  if( p->u.pGraph->pLast ){
+    p->u.pGraph->pLast->pNext = pNew;
+  }else{
+    p->u.pGraph->pRow = pNew;
+  }
+  p->u.pGraph->pLast = pNew;
+}
+
+/*
+** Free and reset the EXPLAIN QUERY PLAN data that has been collected
+** in p->u.pGraph.
+*/
+static void qrfEqpReset(Qrf *p){
+  EQPGraphRow *pRow, *pNext;
+  if( p->u.pGraph ){
+    for(pRow = p->u.pGraph->pRow; pRow; pRow = pNext){
+      pNext = pRow->pNext;
+      sqlite3_free(pRow);
+    }
+    sqlite3_free(p->u.pGraph);
+    p->u.pGraph = 0;
+  }
+}
+
+/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
+** pOld, or return the first such line if pOld is NULL
+*/
+static EQPGraphRow *qrfEqpNextRow(Qrf *p, int iEqpId, EQPGraphRow *pOld){
+  EQPGraphRow *pRow = pOld ? pOld->pNext : p->u.pGraph->pRow;
+  while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
+  return pRow;
+}
+
+/* Render a single level of the graph that has iEqpId as its parent.  Called
+** recursively to render sublevels.
+*/
+static void qrfEqpRenderLevel(Qrf *p, int iEqpId){
+  EQPGraphRow *pRow, *pNext;
+  i64 n = strlen(p->u.pGraph->zPrefix);
+  char *z;
+  for(pRow = qrfEqpNextRow(p, iEqpId, 0); pRow; pRow = pNext){
+    pNext = qrfEqpNextRow(p, iEqpId, pRow);
+    z = pRow->zText;
+    sqlite3_str_appendf(p->pOut, "%s%s%s\n", p->u.pGraph->zPrefix,
+                            pNext ? "|--" : "`--", z);
+    if( n<(i64)sizeof(p->u.pGraph->zPrefix)-7 ){
+      memcpy(&p->u.pGraph->zPrefix[n], pNext ? "|  " : "   ", 4);
+      qrfEqpRenderLevel(p, pRow->iEqpId);
+      p->u.pGraph->zPrefix[n] = 0;
+    }
+  }
+}
+
+/*
+** Display and reset the EXPLAIN QUERY PLAN data
+*/
+static void qrfEqpRender(Qrf *p, i64 nCycle){
+  EQPGraphRow *pRow = p->u.pGraph->pRow;
+  if( pRow ){
+    if( pRow->zText[0]=='-' ){
+      if( pRow->pNext==0 ){
+        qrfEqpReset(p);
+        return;
+      }
+      sqlite3_str_appendf(p->pOut, "%s\n", pRow->zText+3);
+      p->u.pGraph->pRow = pRow->pNext;
+      sqlite3_free(pRow);
+    }else if( nCycle>0 ){
+      sqlite3_str_appendf(p->pOut, "QUERY PLAN (cycles=%lld [100%%])\n",nCycle);
+    }else{
+      sqlite3_str_appendall(p->pOut, "QUERY PLAN\n");
+    }
+    p->u.pGraph->zPrefix[0] = 0;
+    qrfEqpRenderLevel(p, 0);
+    qrfEqpReset(p);
+  }
+}
+
 /*
 ** Reset the prepared statement.
 */
@@ -1166,6 +1291,23 @@ static void qrfInitialize(
       p->spec.zRowSep = "\r\n";
       break;
     }
+    case QRF_MODE_EQP: {
+      if( sqlite3_stmt_isexplain(p->pStmt)!=2 ){
+        /* If EQP mode is requested, but the statement is not an EXPLAIN QUERY
+        ** PLAN statement, then convert the mode to a comma-separate list of
+        ** SQL-quoted values.  Downstream expects an EQP statement if the
+        ** mode is EQP, so do not leave the mode in EQP if the statement is
+        ** not an EQP statement.
+        */
+        p->spec.eFormat = QRF_MODE_List;
+        p->spec.eQuote = QRF_TXT_Sql;
+        p->spec.eBlob = QRF_BLOB_Sql;
+        p->spec.zColumnSep = ",";
+        p->spec.zRowSep = "\n";
+        p->spec.bShowCNames = 1;
+      }
+      break;
+    }
   }
   if( p->spec.eBlob==QRF_BLOB_Auto ){
     switch( p->spec.eQuote ){
@@ -1242,24 +1384,24 @@ static void qrfOneSimpleRow(Qrf *p){
       break;
     }
     case QRF_MODE_Line: {
-      if( p->azCol==0 ){
-        p->azCol = sqlite3_malloc64( p->nCol*sizeof(p->azCol[0]) );
-        if( p->azCol==0 ){
+      if( p->u.azCol==0 ){
+        p->u.azCol = sqlite3_malloc64( p->nCol*sizeof(p->u.azCol[0]) );
+        if( p->u.azCol==0 ){
           qrfOom(p);
           break;
         }
         p->mxColWth = 0;
         for(i=0; i<p->nCol; i++){
           int sz;
-          p->azCol[i] = sqlite3_column_name(p->pStmt, i);
-          if( p->azCol[i]==0 ) p->azCol[i] = "unknown";
-          sz = qrfDisplayLength(p->azCol[i]);
+          p->u.azCol[i] = sqlite3_column_name(p->pStmt, i);
+          if( p->u.azCol[i]==0 ) p->u.azCol[i] = "unknown";
+          sz = qrfDisplayLength(p->u.azCol[i]);
           if( sz > p->mxColWth ) p->mxColWth = sz;
         }
       }
       if( p->nRow ) sqlite3_str_append(p->pOut, "\n", 1);
       for(i=0; i<p->nCol; i++){
-        qrfWidthPrint(p, p->pOut, -p->mxColWth, p->azCol[i]);
+        qrfWidthPrint(p, p->pOut, -p->mxColWth, p->u.azCol[i]);
         sqlite3_str_append(p->pOut, " = ", 3);
         qrfRenderValue(p, p->pOut, i);
         if( i<p->nCol-1 ){
@@ -1271,6 +1413,15 @@ static void qrfOneSimpleRow(Qrf *p){
       qrfWrite(p);
       break;
     }
+    case QRF_MODE_EQP: {
+      const char *zEqpLine = (const char*)sqlite3_column_text(p->pStmt,3);
+      int iEqpId = sqlite3_column_int(p->pStmt, 0);
+      int iParentId = sqlite3_column_int(p->pStmt, 1);
+      if( zEqpLine==0 ) zEqpLine = "";
+      if( zEqpLine[0]=='-' ) qrfEqpRender(p, 0);
+      qrfEqpAppend(p, iEqpId, iParentId, zEqpLine);
+      break;
+    }
     default: {  /* QRF_MODE_List */
       if( p->nRow==0 && p->spec.bShowCNames ){
         for(i=0; i<p->nCol; i++){
@@ -1308,6 +1459,15 @@ static void qrfFinalize(Qrf *p){
       qrfWrite(p);
       break;
     }
+    case QRF_MODE_Line: {
+      if( p->u.azCol ) sqlite3_free(p->u.azCol);
+      break;
+    }
+    case QRF_MODE_EQP: {
+      qrfEqpRender(p, 0);
+      qrfWrite(p);
+      break;
+    }
   }
   if( p->spec.pzOutput ){
     *p->spec.pzOutput = sqlite3_str_finish(p->pOut);
@@ -1315,8 +1475,6 @@ static void qrfFinalize(Qrf *p){
     sqlite3_free(sqlite3_str_finish(p->pOut));
   }
   if( p->actualWidth ) sqlite3_free(p->actualWidth);
-  if( p->azCol ) sqlite3_free(p->azCol);
-
 }
 
 /*
index b617bbbbeb49c946c00a112c7dccf2dec5c6c25f..0afdf62cc5051597689bee089ba493c99c8a782f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sCSV\smode.
-D 2025-10-23T17:09:03.667
+C Add\ssupport\sfor\sEQP\smode.
+D 2025-10-23T18:57:15.892
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -417,7 +417,7 @@ F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6
 F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c
 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
 F ext/qrf/qrf-tester.c b35585d96e36cdefa0dcef5716830d0bac123b0c776e27622744216c16d213da
-F ext/qrf/qrf.c aee2a93792fdd6119556913f8137ca3850225776d4266e765f32b20476664065
+F ext/qrf/qrf.c d1956c9cf3c2a6341a511602281bd43a2c694512ba9c1c2ed9f22ce6a4e03eda
 F ext/qrf/qrf.h 8b4a6ee36c38778fc9cec6f34b555b3d33cc4d388babade6de0f64cda6a24d73
 F ext/qrf/qrf.md 4eea619191dab7bbf483eff3fe3b074a07d7c8c50bc86070a4485797d386d1ff
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
@@ -2175,8 +2175,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P b4c4ba0ad53c6685410700aa332ebec75c9e32848f881b31bc257f86f7befe2c
-R 59b93cb40c4a2ac476e1a8b29c03e47d
+P bf0fec0d4961bd4df3ff874bf8d32b28df48a1739e107ffb0b7bf57affe5f0b6
+R 83abd8eeaf8a5c4adee9e866c94bc5e8
 U drh
-Z dc9c4073a97d2b240adb1466269536be
+Z e9931b276084795d788f130b069357cb
 # Remove this line to create a well-formed Fossil manifest.
index 212d6cf894fe16549d7da4892dfa2c48b6fcc2d8..3790e7cab39ea1ba1ad66fecf6c2c45138c60109 100644 (file)
@@ -1 +1 @@
-bf0fec0d4961bd4df3ff874bf8d32b28df48a1739e107ffb0b7bf57affe5f0b6
+61b2011d8ec2bfe98595ac9ccae5ed0037444757a800706d0ecbc279014fd3fb