#define QRF_STYLE_Csv 6 /* Comma-separated-value */
#define QRF_STYLE_Quote 7 /* SQL-quoted, comma-separated */
#define QRF_STYLE_Explain 8 /* EXPLAIN output */
-#define QRF_STYLE_ScanExp 9 /* EXPLAIN output with vm stats */
-#define QRF_STYLE_EQP 10 /* Format EXPLAIN QUERY PLAN output */
-#define QRF_STYLE_Markdown 11 /* Markdown formatting */
-#define QRF_STYLE_Column 12 /* One record per line in neat columns */
-#define QRF_STYLE_Table 13 /* MySQL-style table formatting */
-#define QRF_STYLE_Box 14 /* Unicode box-drawing characters */
-#define QRF_STYLE_Count 15 /* Output only a count of the rows of output */
-#define QRF_STYLE_Off 16 /* No query output shown */
+#define QRF_STYLE_Eqp 10 /* Format EXPLAIN QUERY PLAN output */
+#define QRF_STYLE_Stats 11 /* EQP-like output but with performance stats */
+#define QRF_STYLE_StatsEst 12 /* EQP-like output with planner estimates */
+#define QRF_STYLE_StatsVm 13 /* EXPLAIN-like output with performance stats */
+#define QRF_STYLE_Markdown 14 /* Markdown formatting */
+#define QRF_STYLE_Column 15 /* One record per line in neat columns */
+#define QRF_STYLE_Table 16 /* MySQL-style table formatting */
+#define QRF_STYLE_Box 17 /* Unicode box-drawing characters */
+#define QRF_STYLE_Count 18 /* Output only a count of the rows of output */
+#define QRF_STYLE_Off 19 /* No query output shown */
~~~
### 5.0 Source Code Files
* `qrf.c` → The implementation, written in portable C99
* `qrf.h` → A header file defining interfaces
- * `qrf.md` → This documentation, in Markdown
+ * `README.md` → This documentation, in Markdown
To use the SQLite result formatter, include the "`qrf.h`" header file
and link the application against the "`qrf.c`" source file.
sqlite3_str *pOut; /* Accumulated output */
int iErr; /* Error code */
int nCol; /* Number of output columns */
+ int expMode; /* Original sqlite3_stmt_isexplain() plus 1 */
union {
struct { /* Content for QRF_STYLE_Line */
int mxColWth; /* Maximum display width of any column */
const char **azCol; /* Names of output columns (MODE_Line) */
} sLine;
- EQPGraph *pGraph; /* EQP graph (MODE_EQP) */
+ EQPGraph *pGraph; /* EQP graph (Eqp, Stats, and StatsEst) */
struct { /* Content for QRF_STYLE_Explain */
int nIndent; /* Slots allocated for aiIndent */
int iIndent; /* Current slot */
** Display and reset the EXPLAIN QUERY PLAN data
*/
static void qrfEqpRender(Qrf *p, i64 nCycle){
- EQPGraphRow *pRow = p->u.pGraph->pRow;
- if( pRow ){
+ EQPGraphRow *pRow;
+ if( p->u.pGraph!=0 && (pRow = p->u.pGraph->pRow)!=0 ){
if( pRow->zText[0]=='-' ){
if( pRow->pNext==0 ){
qrfEqpReset(p);
}
}
+#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
+/*
+** Helper function for qrfExpStats().
+**
+*/
+static int qrfStatsHeight(sqlite3_stmt *p, int iEntry){
+ int iPid = 0;
+ int ret = 1;
+ sqlite3_stmt_scanstatus_v2(p, iEntry,
+ SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
+ );
+ while( iPid!=0 ){
+ int ii;
+ for(ii=0; 1; ii++){
+ int iId;
+ int res;
+ res = sqlite3_stmt_scanstatus_v2(p, ii,
+ SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId
+ );
+ if( res ) break;
+ if( iId==iPid ){
+ sqlite3_stmt_scanstatus_v2(p, ii,
+ SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
+ );
+ }
+ }
+ ret++;
+ }
+ return ret;
+}
+#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */
+
+
+/*
+** Generate ".scanstatus est" style of EQP output.
+*/
+static void qrfEqpStats(Qrf *p){
+#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
+ qrfError(p, SQLITE_ERROR, "not available in this build");
+#else
+ static const int f = SQLITE_SCANSTAT_COMPLEX;
+ sqlite3_stmt *pS = p->pStmt;
+ int i = 0;
+ i64 nTotal = 0;
+ int nWidth = 0;
+ sqlite3_str *pLine = sqlite3_str_new(p->db);
+ sqlite3_str *pStats = sqlite3_str_new(p->db);
+ qrfEqpReset(p);
+
+ for(i=0; 1; i++){
+ const char *z = 0;
+ int n = 0;
+ if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
+ break;
+ }
+ n = (int)strlen(z) + qrfStatsHeight(pS,i)*3;
+ if( n>nWidth ) nWidth = n;
+ }
+ nWidth += 4;
+
+ sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
+ for(i=0; 1; i++){
+ i64 nLoop = 0;
+ i64 nRow = 0;
+ i64 nCycle = 0;
+ int iId = 0;
+ int iPid = 0;
+ const char *zo = 0;
+ const char *zName = 0;
+ double rEst = 0.0;
+
+ if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){
+ break;
+ }
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_EST,f,(void*)&rEst);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
+ sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
+
+ if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
+ const char *zSp = "";
+ double rpl;
+ sqlite3_str_reset(pStats);
+ if( nCycle>=0 && nTotal>0 ){
+ sqlite3_str_appendf(pStats, "cycles=%lld [%d%%]",
+ nCycle, ((nCycle*100)+nTotal/2) / nTotal
+ );
+ zSp = " ";
+ }
+ if( nLoop>=0 ){
+ sqlite3_str_appendf(pStats, "%sloops=%lld", zSp, nLoop);
+ zSp = " ";
+ }
+ if( nRow>=0 ){
+ sqlite3_str_appendf(pStats, "%srows=%lld", zSp, nRow);
+ zSp = " ";
+ }
+
+ if( p->spec.eStyle==QRF_STYLE_StatsEst ){
+ rpl = (double)nRow / (double)nLoop;
+ sqlite3_str_appendf(pStats, "%srpl=%.1f est=%.1f", zSp, rpl, rEst);
+ }
+
+ sqlite3_str_appendf(pLine,
+ "% *s (%s)", -1*(nWidth-qrfStatsHeight(pS,i)*3), zo,
+ sqlite3_str_value(pStats)
+ );
+ sqlite3_str_reset(pStats);
+ qrfEqpAppend(p, iId, iPid, sqlite3_str_value(pLine));
+ sqlite3_str_reset(pLine);
+ }else{
+ qrfEqpAppend(p, iId, iPid, zo);
+ }
+ }
+ sqlite3_free(sqlite3_str_finish(pLine));
+ sqlite3_free(sqlite3_str_finish(pStats));
+#endif
+}
+
+
/*
** Reset the prepared statement.
*/
int nWidth = sizeof(aExplainWidth)/sizeof(int);
int iIndent = 1;
int nArg = p->nCol;
- if( p->spec.eStyle==QRF_STYLE_ScanExp ){
+ if( p->spec.eStyle==QRF_STYLE_StatsVm ){
aWidth = aScanExpWidth;
aMap = aScanExpMap;
nWidth = sizeof(aScanExpWidth)/sizeof(int);
switch( sqlite3_stmt_isexplain(pStmt) ){
case 0: p->spec.eStyle = QRF_STYLE_Box; break;
case 1: p->spec.eStyle = QRF_STYLE_Explain; break;
- default: p->spec.eStyle = QRF_STYLE_EQP; break;
+ default: p->spec.eStyle = QRF_STYLE_Eqp; break;
}
goto qrf_reinit;
}
p->spec.zRowSep = "\n";
break;
}
- case QRF_STYLE_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.eStyle = QRF_STYLE_Column;
- p->spec.bColumnNames = QRF_SW_On;
- p->spec.eText = QRF_TEXT_Sql;
- p->spec.eBlob = QRF_BLOB_Sql;
- p->spec.zColumnSep = " ";
- p->spec.zRowSep = "\n";
+ case QRF_STYLE_Eqp: {
+ int expMode = sqlite3_stmt_isexplain(p->pStmt);
+ if( expMode!=2 ){
+ sqlite3_stmt_explain(p->pStmt, 2);
+ p->expMode = expMode+1;
}
break;
}
case QRF_STYLE_Explain: {
- if( sqlite3_stmt_isexplain(p->pStmt)!=1 ){
- /* If Explain mode is requested, but the statement is not an EXPLAIN
- ** tatement, then convert the mode to a comma-separate list of
- ** SQL-quoted values. Downstream expects an Explain statement if the
- ** mode is Explain, so do not leave the mode in Explain if the
- ** statement is not an EXPLAIN statement.
- */
- p->spec.eStyle = QRF_STYLE_Column;
- p->spec.bColumnNames = QRF_SW_On;
- p->spec.eText = QRF_TEXT_Sql;
- p->spec.eBlob = QRF_BLOB_Sql;
- p->spec.zColumnSep = " ";
- p->spec.zRowSep = "\n";
+ int expMode = sqlite3_stmt_isexplain(p->pStmt);
+ if( expMode!=1 ){
+ sqlite3_stmt_explain(p->pStmt, 1);
+ p->expMode = expMode+1;
}
break;
}
qrfWrite(p);
break;
}
- case QRF_STYLE_EQP: {
+ case QRF_STYLE_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( p->u.sLine.azCol ) sqlite3_free(p->u.sLine.azCol);
break;
}
- case QRF_STYLE_EQP: {
+ case QRF_STYLE_Stats:
+ case QRF_STYLE_StatsEst:
+ case QRF_STYLE_Eqp: {
qrfEqpRender(p, 0);
qrfWrite(p);
break;
}else if( p->pOut ){
sqlite3_free(sqlite3_str_finish(p->pOut));
}
- if( p->actualWidth ) sqlite3_free(p->actualWidth);
+ if( p->expMode>0 ){
+ sqlite3_stmt_explain(p->pStmt, p->expMode-1);
+ }
+ if( p->actualWidth ){
+ sqlite3_free(p->actualWidth);
+ }
if( p->pJTrans ){
sqlite3 *db = sqlite3_db_handle(p->pJTrans);
sqlite3_finalize(p->pJTrans);
qrfExplain(&qrf);
break;
}
- case QRF_STYLE_ScanExp: {
+ case QRF_STYLE_StatsVm: {
qrfScanStatusVm(&qrf);
break;
}
+ case QRF_STYLE_Stats:
+ case QRF_STYLE_StatsEst: {
+ qrfEqpStats(&qrf);
+ break;
+ }
default: {
/* Non-columnar modes where the output can occur after each row
** of result is received */
-C Changes\sto\stry\sto\simprove\sEXPLAIN\scapabilities.\s\sThis\sis\sa\ssnapshot\nprior\sto\san\sattempt\sto\smake\ssweeping\schanges\sin\sthat\sarea\sof\sthe\ninterface.
-D 2025-11-06T17:28:34.534
+C Revamp\sso\sthat\sExplain\sand\sEqp\sstyles\sforce\sthe\sstatement\sinto\sthe\nthe\sappropriate\sexplain-mode\sfor\sthe\srendering\sand\srestore\sit\safterwards.\nAdd\sthe\snew\sscanstatus\sstyles:\sStats,\sStatsEst,\sand\sStatsVm.
+D 2025-11-06T19:22:34.130
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c
F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c
F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
-F ext/qrf/README.md 5f12f91104d5df8ba8783e2240635698569ce54935e721b3a4e93abf4e00ddcb
-F ext/qrf/qrf.c 598c793d08461d62ed4e74078f8542e2ff4a733ff4eeaa05de8dbd8c5829f311
-F ext/qrf/qrf.h 98e02eded10848c42053d37e56bd15ab69cb6fe9fc1f1ca05823d9047e9c8509
+F ext/qrf/README.md 9e0064b137239d19427a1ce1c148c5b45bba2741c466f03af19221dddf90c4c9
+F ext/qrf/qrf.c 82392dc3fb199d019dea4aa0bb381b7f4fa3b9cefb8e24f4806306f6fae17c02
+F ext/qrf/qrf.h c7ae981ab59b96d7ec2baeb391c0d7ce1e4f90ad673b7b1a8a7391fc6796de0e
F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363
F src/sqliteLimit.h fe70bd8983e5d317a264f2ea97473b359faf3ebb0827877a76813f5cf0cdc364
F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
-F src/tclsqlite.c 6e87dda56e2add1581063c281f9fd2d4f65605bbabd6d36660905e579a20fc13
+F src/tclsqlite.c cf04173ac121e053b7cc30cd987c479af9eb027a5bcf1648b7a7dde8567c9ccb
F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a
F src/test1.c f880ab766eeedf2c063662bd9538b923fd42c4341b7bfc2150a6d93ab8b9341c
F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd
-F test/qrf01.test 75acb2a17dfa8cbc5c0e70bcb6b90eb176ec39a08a8427e73210d537e0280f04
+F test/qrf01.test 1560df867282d37c4d43a4b2e66606ab129ade70fa1a1a3b9a720769ec8e36d0
F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 4e2ada6ad8a56d00aeb73570dc4ee22c484b65b8100a42a7085414d0a4c68afd
-R ada052660ca6412932dab58de0554df7
+P e7367d8089d43a06933f919c33c6edb15ee9bedf52d9a2d9ea989654077d998f
+R 93a1daf233952c02bd3cbe8cc59f80a0
U drh
-Z e00ad94c88d3ff9e91d08c82898645f1
+Z d7063f3e75c72a15a6aaacd6e379ef65
# Remove this line to create a well-formed Fossil manifest.