From 52d1e3aef3faac5f3d11692f81d8e54d173c41a6 Mon Sep 17 00:00:00 2001 From: drh <> Date: Thu, 23 Oct 2025 18:57:15 +0000 Subject: [PATCH] Add support for EQP mode. FossilOrigin-Name: 61b2011d8ec2bfe98595ac9ccae5ed0037444757a800706d0ecbc279014fd3fb --- ext/qrf/qrf.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++--- manifest | 12 ++-- manifest.uuid | 2 +- 3 files changed, 175 insertions(+), 17 deletions(-) diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c index 3f063894e7..4ccc3e46f4 100644 --- a/ext/qrf/qrf.c +++ b/ext/qrf/qrf.c @@ -16,6 +16,25 @@ #include #include +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; inCol; 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; inCol; 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( inCol-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; inCol; 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); - } /* diff --git a/manifest b/manifest index b617bbbbeb..0afdf62cc5 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index 212d6cf894..3790e7cab3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -bf0fec0d4961bd4df3ff874bf8d32b28df48a1739e107ffb0b7bf57affe5f0b6 +61b2011d8ec2bfe98595ac9ccae5ed0037444757a800706d0ecbc279014fd3fb -- 2.47.3