From: drh <> Date: Thu, 23 Oct 2025 20:13:57 +0000 (+0000) Subject: Add support for QUOTE and EXPLAIN modes. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c873460fd13564442403a241c02fe7abfeecfcd5;p=thirdparty%2Fsqlite.git Add support for QUOTE and EXPLAIN modes. FossilOrigin-Name: c4d612a5720eace463ee8b1d7484eddd9701b8f2155479077a1410532e8d3e4e --- diff --git a/ext/qrf/qrf-tester.c b/ext/qrf/qrf-tester.c index 6c57bb3ddd..d264a7a7e9 100644 --- a/ext/qrf/qrf-tester.c +++ b/ext/qrf/qrf-tester.c @@ -198,6 +198,7 @@ int main(int argc, char **argv){ { "list", QRF_MODE_List, }, { "markdown", QRF_MODE_Markdown, }, { "off", QRF_MODE_Off, }, + { "quote", QRF_MODE_Quote, }, { "table", QRF_MODE_Table, }, { "scanexp", QRF_MODE_ScanExp, }, }; diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c index 4ccc3e46f4..dbe1f05a65 100644 --- a/ext/qrf/qrf.c +++ b/ext/qrf/qrf.c @@ -15,6 +15,7 @@ #include "qrf.h" #include #include +#include typedef sqlite3_int64 i64; @@ -47,10 +48,17 @@ struct Qrf { sqlite3_str *pOut; /* Accumulated output */ int iErr; /* Error code */ int nCol; /* Number of output columns */ - int mxColWth; /* Maximum size of any column name */ union { - const char **azCol; /* Names of output columns (MODE_Line) */ - EQPGraph *pGraph; /* EQP graph (MODE_EQP) */ + struct { /* Content for QRF_MODE_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) */ + struct { /* Content for QRF_MODE_Explain */ + int nIndent; /* Slots allocated for aiIndent */ + int iIndent; /* Current slot */ + int *aiIndent; /* Indentation for each opcode */ + } sExpln; } u; sqlite3_int64 nRow; /* Number of rows handled so far */ int *actualWidth; /* Actual width of each column */ @@ -86,6 +94,8 @@ static void qrfOom(Qrf *p){ qrfError(p, SQLITE_NOMEM, "out of memory"); } + + /* ** Add a new entry to the EXPLAIN QUERY PLAN data */ @@ -1221,6 +1231,177 @@ qrf_column_end: return; } +/* +** Parameter azArray points to a zero-terminated array of strings. zStr +** points to a single nul-terminated string. Return non-zero if zStr +** is equal, according to strcmp(), to any of the strings in the array. +** Otherwise, return zero. +*/ +static int qrfStringInArray(const char *zStr, const char **azArray){ + int i; + if( zStr==0 ) return 0; + for(i=0; azArray[i]; i++){ + if( 0==strcmp(zStr, azArray[i]) ) return 1; + } + return 0; +} + +/* +** Print out an EXPLAIN with indentation. This is a two-pass algorithm. +** +** On the first pass, we compute aiIndent[iOp] which is the amount of +** indentation to apply to the iOp-th opcode. The output actually occurs +** on the second pass. +** +** The indenting rules are: +** +** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent +** all opcodes that occur between the p2 jump destination and the opcode +** itself by 2 spaces. +** +** * Do the previous for "Return" instructions for when P2 is positive. +** See tag-20220407a in wherecode.c and vdbe.c. +** +** * For each "Goto", if the jump destination is earlier in the program +** and ends on one of: +** Yield SeekGt SeekLt RowSetRead Rewind +** or if the P1 parameter is one instead of zero, +** then indent all opcodes between the earlier instruction +** and "Goto" by 2 spaces. +*/ +static void qrfExplain(Qrf *p){ + int *abYield = 0; /* abYield[iOp] is rue if opcode iOp is an OP_Yield */ + int *aiIndent = 0; /* Indent the iOp-th opcode by aiIndent[iOp] */ + i64 nAlloc = 0; /* Allocated size of aiIndent[], abYield */ + int nIndent; /* Number of entries in aiIndent[] */ + int iOp; /* Opcode number */ + int i; /* Column loop counter */ + + const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", + "Return", 0 }; + const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead", + "Rewind", 0 }; + const char *azGoto[] = { "Goto", 0 }; + + /* The caller guarantees that the leftmost 4 columns of the statement + ** passed to this function are equivalent to the leftmost 4 columns + ** of EXPLAIN statement output. In practice the statement may be + ** an EXPLAIN, or it may be a query on the bytecode() virtual table. */ + assert( sqlite3_column_count(p->pStmt)>=4 ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 0), "addr" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 1), "opcode" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 2), "p1" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 3), "p2" ) ); + + for(iOp=0; SQLITE_ROW==sqlite3_step(p->pStmt); iOp++){ + int i; + int iAddr = sqlite3_column_int(p->pStmt, 0); + const char *zOp = (const char*)sqlite3_column_text(p->pStmt, 1); + int p1 = sqlite3_column_int(p->pStmt, 2); + int p2 = sqlite3_column_int(p->pStmt, 3); + + /* Assuming that p2 is an instruction address, set variable p2op to the + ** index of that instruction in the aiIndent[] array. p2 and p2op may be + ** different if the current instruction is part of a sub-program generated + ** by an SQL trigger or foreign key. */ + int p2op = (p2 + (iOp-iAddr)); + + /* Grow the aiIndent array as required */ + if( iOp>=nAlloc ){ + nAlloc += 100; + aiIndent = (int*)sqlite3_realloc64(aiIndent, nAlloc*sizeof(int)); + abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); + if( aiIndent==0 || abYield==0 ){ + qrfOom(p); + sqlite3_free(aiIndent); + sqlite3_free(abYield); + return; + } + } + + abYield[iOp] = qrfStringInArray(zOp, azYield); + aiIndent[iOp] = 0; + nIndent = iOp+1; + if( qrfStringInArray(zOp, azNext) && p2op>0 ){ + for(i=p2op; ipStmt); + if( p->iErr==SQLITE_OK ){ + static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13}; + static const int aExplainMap[] = {0, 1, 2, 3, 4, 5, 6, 7 }; + static const int aScanExpWidth[] = {4,15, 6, 13, 4, 4, 4, 13, 2, 13}; + static const int aScanExpMap[] = {0, 9, 8, 1, 2, 3, 4, 5, 6, 7 }; + const int *aWidth = aExplainWidth; + const int *aMap = aExplainMap; + int nWidth = sizeof(aExplainWidth)/sizeof(int); + int iIndent = 1; + int nArg = p->nCol; + if( p->spec.eFormat==QRF_MODE_ScanExp ){ + aWidth = aScanExpWidth; + aMap = aScanExpMap; + nWidth = sizeof(aScanExpWidth)/sizeof(int); + iIndent = 3; + } + if( nArg>nWidth ) nArg = nWidth; + + for(iOp=0; sqlite3_step(p->pStmt)==SQLITE_ROW; iOp++){ + /* If this is the first row seen, print out the headers */ + if( iOp==0 ){ + for(i=0; ipStmt, aMap[i]); + qrfWidthPrint(p,p->pOut, aWidth[i], zCol); + if( i==nArg-1 ){ + sqlite3_str_append(p->pOut, "\n", 1); + }else{ + sqlite3_str_append(p->pOut, " ", 2); + } + } + for(i=0; ipOut, "%.*c", aWidth[i], '-'); + if( i==nArg-1 ){ + sqlite3_str_append(p->pOut, "\n", 1); + }else{ + sqlite3_str_append(p->pOut, " ", 2); + } + } + } + + for(i=0; ipStmt, aMap[i]); + int len; + if( i==nArg-1 ) w = 0; + if( zVal==0 ) zVal = ""; + len = qrfDisplayLength(zVal); + if( len>w ){ + w = len; + zSep = " "; + } + if( i==iIndent && aiIndent && iOppOut, aiIndent[iOp], ' '); + } + qrfWidthPrint(p, p->pOut, w, zVal); + if( i==nArg-1 ){ + sqlite3_str_append(p->pOut, "\n", 1); + }else{ + sqlite3_str_appendall(p->pOut, zSep); + } + } + p->nRow++; + } + qrfWrite(p); + } + sqlite3_free(aiIndent); +} + /* ** Initialize the internal Qrf object. */ @@ -1291,6 +1472,13 @@ static void qrfInitialize( p->spec.zRowSep = "\r\n"; break; } + case QRF_MODE_Quote: { + p->spec.eQuote = QRF_TXT_Sql; + p->spec.eBlob = QRF_BLOB_Sql; + p->spec.zColumnSep = ","; + p->spec.zRowSep = "\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 @@ -1299,12 +1487,29 @@ static void qrfInitialize( ** 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.eFormat = QRF_MODE_Quote; + p->spec.bShowCNames = 1; p->spec.eQuote = QRF_TXT_Sql; p->spec.eBlob = QRF_BLOB_Sql; p->spec.zColumnSep = ","; p->spec.zRowSep = "\n"; + } + break; + } + case QRF_MODE_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.eFormat = QRF_MODE_Quote; p->spec.bShowCNames = 1; + p->spec.eQuote = QRF_TXT_Sql; + p->spec.eBlob = QRF_BLOB_Sql; + p->spec.zColumnSep = ","; + p->spec.zRowSep = "\n"; } break; } @@ -1384,24 +1589,24 @@ static void qrfOneSimpleRow(Qrf *p){ break; } case QRF_MODE_Line: { - if( p->u.azCol==0 ){ - p->u.azCol = sqlite3_malloc64( p->nCol*sizeof(p->u.azCol[0]) ); - if( p->u.azCol==0 ){ + if( p->u.sLine.azCol==0 ){ + p->u.sLine.azCol = sqlite3_malloc64( p->nCol*sizeof(char*) ); + if( p->u.sLine.azCol==0 ){ qrfOom(p); break; } - p->mxColWth = 0; + p->u.sLine.mxColWth = 0; for(i=0; inCol; i++){ int sz; - 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; + p->u.sLine.azCol[i] = sqlite3_column_name(p->pStmt, i); + if( p->u.sLine.azCol[i]==0 ) p->u.sLine.azCol[i] = "unknown"; + sz = qrfDisplayLength(p->u.sLine.azCol[i]); + if( sz > p->u.sLine.mxColWth ) p->u.sLine.mxColWth = sz; } } if( p->nRow ) sqlite3_str_append(p->pOut, "\n", 1); for(i=0; inCol; i++){ - qrfWidthPrint(p, p->pOut, -p->mxColWth, p->u.azCol[i]); + qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth, p->u.sLine.azCol[i]); sqlite3_str_append(p->pOut, " = ", 3); qrfRenderValue(p, p->pOut, i); if( inCol-1 ){ @@ -1460,7 +1665,7 @@ static void qrfFinalize(Qrf *p){ break; } case QRF_MODE_Line: { - if( p->u.azCol ) sqlite3_free(p->u.azCol); + if( p->u.sLine.azCol ) sqlite3_free(p->u.sLine.azCol); break; } case QRF_MODE_EQP: { @@ -1503,6 +1708,10 @@ int sqlite3_format_query_result( qrfColumnar(&qrf); break; } + case QRF_MODE_Explain: { + qrfExplain(&qrf); + break; + } default: { /* Non-columnar modes where the output can occur after each row ** of result is received */ diff --git a/ext/qrf/qrf.h b/ext/qrf/qrf.h index d40d02e24b..8083660bb6 100644 --- a/ext/qrf/qrf.h +++ b/ext/qrf/qrf.h @@ -68,15 +68,16 @@ int sqlite3_format_query_result( #define QRF_MODE_Json 3 /* Output is a list of JSON objects */ #define QRF_MODE_Insert 4 /* Generate SQL "insert" statements */ #define QRF_MODE_Csv 5 /* Comma-separated-value */ -#define QRF_MODE_Explain 6 /* EXPLAIN output */ -#define QRF_MODE_ScanExp 7 /* EXPLAIN output with vm stats */ -#define QRF_MODE_EQP 8 /* Format EXPLAIN QUERY PLAN output */ -#define QRF_MODE_Markdown 9 /* Markdown formatting */ -#define QRF_MODE_Column 10 /* One record per line in neat columns */ -#define QRF_MODE_Table 11 /* MySQL-style table formatting */ -#define QRF_MODE_Box 12 /* Unicode box-drawing characters */ -#define QRF_MODE_Count 13 /* Output only a count of the rows of output */ -#define QRF_MODE_Off 14 /* No query output shown */ +#define QRF_MODE_Quote 6 /* SQL-quoted, comma-separated */ +#define QRF_MODE_Explain 7 /* EXPLAIN output */ +#define QRF_MODE_ScanExp 8 /* EXPLAIN output with vm stats */ +#define QRF_MODE_EQP 9 /* Format EXPLAIN QUERY PLAN output */ +#define QRF_MODE_Markdown 10 /* Markdown formatting */ +#define QRF_MODE_Column 11 /* One record per line in neat columns */ +#define QRF_MODE_Table 12 /* MySQL-style table formatting */ +#define QRF_MODE_Box 13 /* Unicode box-drawing characters */ +#define QRF_MODE_Count 14 /* Output only a count of the rows of output */ +#define QRF_MODE_Off 15 /* No query output shown */ /* ** Quoting styles for text. diff --git a/manifest b/manifest index 0afdf62cc5..ce15f6ce19 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssupport\sfor\sEQP\smode. -D 2025-10-23T18:57:15.892 +C Add\ssupport\sfor\sQUOTE\sand\sEXPLAIN\smodes. +D 2025-10-23T20:13:57.000 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -416,9 +416,9 @@ F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f6 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee -F ext/qrf/qrf-tester.c b35585d96e36cdefa0dcef5716830d0bac123b0c776e27622744216c16d213da -F ext/qrf/qrf.c d1956c9cf3c2a6341a511602281bd43a2c694512ba9c1c2ed9f22ce6a4e03eda -F ext/qrf/qrf.h 8b4a6ee36c38778fc9cec6f34b555b3d33cc4d388babade6de0f64cda6a24d73 +F ext/qrf/qrf-tester.c 7d72e3268db31a348c2e8002b174eac9a25d1f93350d845e1ef5f60dfddd95a9 +F ext/qrf/qrf.c 2c262b394f7da3ff08af74e6b740d4517f3dbac1184b873eeba3228c8564fe47 +F ext/qrf/qrf.h 525fe1161fec27106d8affa6f1e4ead64cadbc9d5e7debecaf1bad5a23594673 F ext/qrf/qrf.md 4eea619191dab7bbf483eff3fe3b074a07d7c8c50bc86070a4485797d386d1ff F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 @@ -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 bf0fec0d4961bd4df3ff874bf8d32b28df48a1739e107ffb0b7bf57affe5f0b6 -R 83abd8eeaf8a5c4adee9e866c94bc5e8 +P 61b2011d8ec2bfe98595ac9ccae5ed0037444757a800706d0ecbc279014fd3fb +R 300fe5b838870830daae1638e8fbd758 U drh -Z e9931b276084795d788f130b069357cb +Z 500f02b47e428273d17848f35caac609 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3790e7cab3..d74fa129d2 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -61b2011d8ec2bfe98595ac9ccae5ed0037444757a800706d0ecbc279014fd3fb +c4d612a5720eace463ee8b1d7484eddd9701b8f2155479077a1410532e8d3e4e