#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.
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 */
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.
*/
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 ){
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 ){
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++){
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);
sqlite3_free(sqlite3_str_finish(p->pOut));
}
if( p->actualWidth ) sqlite3_free(p->actualWidth);
- if( p->azCol ) sqlite3_free(p->azCol);
-
}
/*
-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
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
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.