From: drh <> Date: Tue, 7 Jul 2026 00:23:38 +0000 (+0000) Subject: Continuing work on --rowcount and --title always. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=0fe99878808694214f40237447c17727b3384a0a;p=thirdparty%2Fsqlite.git Continuing work on --rowcount and --title always. FossilOrigin-Name: adeb4fbb86a8a781d7efb0eecb9c84ce605d0bd45f63992bf2ecd60765a60d2c --- diff --git a/ext/qrf/README.md b/ext/qrf/README.md index 8555cb0780..fed33a5760 100644 --- a/ext/qrf/README.md +++ b/ext/qrf/README.md @@ -187,10 +187,9 @@ ignored, depending on the value of eStyle. ### 2.4 Show Column Names (bTitles) -The sqlite3_qrf_spec.bTitles field can be either QRF_SW_Auto, -QRF_SW_On, or QRF_SW_Off. Those three constants also have shorter -alternative spellings: QRF_Auto, QRF_No, and -QRF_Yes. +The sqlite3_qrf_spec.bTitles field can be one of QRF_SW_Auto, +QRF_SW_On, QRF_SW_Off, or QRF_Always. The first three constants also +have shorter alternative spellings: QRF_Auto, QRF_No, and QRF_Yes. > ~~~ #define QRF_SW_Auto 0 /* Let QRF choose the best value */ @@ -199,11 +198,16 @@ QRF_Yes. #define QRF_Auto 0 /* Alternate spelling for QRF_SW_Auto and others */ #define QRF_No 1 /* Alternate spelling for QRF_SW_Off */ #define QRF_Yes 2 /* Alternate spelling for QRF_SW_On */ +#define QRF_Always 3 /* Always try to show column names */ ~~~ -If the value is QRF_Yes, then column names appear in the output. +If the value is QRF_Yes, then column names appear in the output when +there are one or more rows of output. If the value is QRF_No, column names are omitted. If the value is QRF_Auto, then an appropriate default is chosen. +The value QRF_Always is like QRF_Yes except that the output might +still show column headers even if the number of result rows is zero, +depending on eStyle. ### 2.5 Control Character Escapes (eEsc) @@ -594,6 +598,12 @@ not applicable to xRender because title values come from the sqlite3_column_name() interface not from sqlite3_column_value(), and so that names of columns are never processed by xRender. +### 2.18 Show Row Counts On Query Output (bRowCount) + +If the value of bRowCount is QRF_Yes, then an extra line of text +of the form "(N rows)" might be appended to the end of query output, +depending on eStyle. + ## 3.0 The `sqlite3_format_query_result()` Interface Invoke the `sqlite3_format_query_result(P,S,E)` interface to run diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c index af21458592..03a5a72d38 100644 --- a/ext/qrf/qrf.c +++ b/ext/qrf/qrf.c @@ -1955,7 +1955,18 @@ static void qrfColumnar(Qrf *p){ if( nColumn==0 ){ return; /* Not a query. No results every shown. */ } - if( rc!=SQLITE_ROW && (rc!=SQLITE_DONE || p->spec.bTitles!=QRF_Always) ){ + if( rc==SQLITE_DONE && nColumn>0 ){ + /* Empty query */ + if( p->spec.bTitles==QRF_Always ){ + /* fall through into the columnar logic below */ + }else{ + if( p->spec.bRowCount==QRF_Yes ){ + sqlite3_str_appendf(p->pOut, "(0 rows)\n"); + qrfWrite(p); + } + return; /* No output, other than the row count */ + } + }else if( rc!=SQLITE_ROW ){ return; /* No output */ } @@ -2543,6 +2554,45 @@ static void qrfOneJsonRow(Qrf *p){ qrfWrite(p); } +/* +** Render a title row (a row containing column names) if the spec +** calls for one and if it makes sense for the eStyle. Title rows +** do not make sense for some eStyles, such as QRF_STYLE_Off, +** QRF_STYLE_Count, QRF_STYLE_Json, and similar. +*/ +static void qrfSimpleTitle(Qrf *p){ + assert( p->nRow==0 ); + assert( p->spec.bTitles>=QRF_Yes ); + switch( p->spec.eStyle ){ + case QRF_STYLE_Html: { + int i; + sqlite3_str_append(p->pOut, "", 4); + for(i=0; inCol; i++){ + const char *zCName = sqlite3_column_name(p->pStmt, i); + sqlite3_str_append(p->pOut, "\n", 5); + qrfEncodeText(p, p->pOut, zCName); + } + sqlite3_str_append(p->pOut, "\n\n", 7); + break; + } + case QRF_STYLE_Quote: + case QRF_STYLE_List: { + int i; + int saved_eText = p->spec.eText; + p->spec.eText = p->spec.eTitle; + for(i=0; inCol; i++){ + const char *zCName = sqlite3_column_name(p->pStmt, i); + if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep); + qrfEncodeText(p, p->pOut, zCName); + } + sqlite3_str_appendall(p->pOut, p->spec.zRowSep); + qrfWrite(p); + p->spec.eText = saved_eText; + break; + } + } +} + /* ** Render a single row of output for non-columnar styles - any ** style that lets us render row by row as the content is received @@ -2576,13 +2626,7 @@ static void qrfOneSimpleRow(Qrf *p){ } case QRF_STYLE_Html: { if( p->nRow==0 && p->spec.bTitles>=QRF_Yes ){ - sqlite3_str_append(p->pOut, "", 4); - for(i=0; inCol; i++){ - const char *zCName = sqlite3_column_name(p->pStmt, i); - sqlite3_str_append(p->pOut, "\n", 5); - qrfEncodeText(p, p->pOut, zCName); - } - sqlite3_str_append(p->pOut, "\n\n", 7); + qrfSimpleTitle(p); } sqlite3_str_append(p->pOut, "", 4); for(i=0; inCol; i++){ @@ -2707,16 +2751,7 @@ static void qrfOneSimpleRow(Qrf *p){ } default: { /* QRF_STYLE_List */ if( p->nRow==0 && p->spec.bTitles>=QRF_Yes ){ - int saved_eText = p->spec.eText; - p->spec.eText = p->spec.eTitle; - for(i=0; inCol; i++){ - const char *zCName = sqlite3_column_name(p->pStmt, i); - if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep); - qrfEncodeText(p, p->pOut, zCName); - } - sqlite3_str_appendall(p->pOut, p->spec.zRowSep); - qrfWrite(p); - p->spec.eText = saved_eText; + qrfSimpleTitle(p); } for(i=0; inCol; i++){ if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep); @@ -3032,6 +3067,26 @@ int sqlite3_format_query_result( while( qrf.iErr==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ qrfOneSimpleRow(&qrf); } + if( qrf.nCol>0 && qrf.nRow==0 && qrf.spec.bTitles==QRF_Always ){ + qrfSimpleTitle(&qrf); + } + if( qrf.nCol>0 && qrf.spec.bRowCount==QRF_Yes ){ + switch( qrf.spec.eStyle ){ + case QRF_STYLE_Line: + if( qrf.nRow>0 ) sqlite3_str_append(qrf.pOut, "\n", 1); + /* Fall through */ + case QRF_STYLE_Csv: + case QRF_STYLE_List: + case QRF_STYLE_Quote: + sqlite3_str_appendf(qrf.pOut,"(%d row%s)\n", + qrf.nRow, qrf.nRow==1 ? "" : "s"); + break; + case QRF_STYLE_Html: + sqlite3_str_appendf(qrf.pOut,"\n", + qrf.nRow, qrf.nRow==1 ? "" : "s"); + break; + } + } break; } } diff --git a/manifest b/manifest index b125638df7..f4efb16909 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\snew\soption\s"bRowCount"\sto\sQRF.\s\sAdd\sQRF_Always\sas\sa\spossibility\sfor\nbTitles.\s\sUse\sthese\sfeature\sto\senhance\s".mode\spsql"\sto\sbe\sa\sbetter\napproximation\sof\swhat\spsql\sactually\sdoes. -D 2026-07-06T19:24:15.260 +C Continuing\swork\son\s--rowcount\sand\s--title\salways. +D 2026-07-07T00:23:38.996 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -423,9 +423,9 @@ F ext/misc/wholenumber.c e41953e078894e66a0ff05dd6c76a61f904828c9a4620c7255fac26 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c F ext/misc/zipfile.c 5cf901996c840b32a5c03d947c4ee86bda166f6e595a41871840df8520458054 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee -F ext/qrf/README.md 9e644615d7d7b77ef7e9db798765679e50c5ed12eda48bce21c9ef9eb4715e9d +F ext/qrf/README.md 3dfd5de860cdc0c572d8926ba0c1b3cc8925487dfbda10e63af66a8811907687 F ext/qrf/dev-notes.md e68a6d91ce4c7eb296ef2daadc2bb79c95c317ad15b9fafe40850c67b29c2430 -F ext/qrf/qrf.c 99c3ab46eedbc68211296e58797d601a80667f75a8db746a75cb92ec20ca923a +F ext/qrf/qrf.c 151396f96eb0262443b2dda0313976ffb072b17341374d80cd9681d38345000d F ext/qrf/qrf.h fcbc33578176b2fd9dda8310d8f6ba4eaf4f75052f466dfe682802e6664df8bd F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 @@ -743,7 +743,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 54395ee97eb710e695202d4112cf2b1c1c7767a57afcea745df71abb1c917768 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c f553420eaf5c72a49cef786621eea79dd8c4411671839fb05250bb49ca74a0a0 -F src/shell.c.in c26f8aca2312ed8a8be1f63f887c23f45e37911d4ede17f9c6b9d7897d574b80 +F src/shell.c.in ad7b45289ca6867ca2d87230aefc5acc63b86511f9605f23765b0129105e5576 F src/sqlite.h.in d9ec41feb4cd804e68b174328b43beb3f1f71bba13e1c7a439efb826d301cccc F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 0efd4723bad9124ea1f581d9f1ea0254ac1c6f3e5fb29e4f3dcf36c72485a456 @@ -751,7 +751,7 @@ F src/sqliteInt.h 92563b8edc673513b82d87bf6a8d7aeb554b2c7b7d5e30992fbca04010db79 F src/sqliteLimit.h c70656b67ab5b96741a8f1c812bdd80c81f2b1c1e443d0cc3ea8c33bb1f1a092 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c 7401c73c917a4d1b380c896a324c8d8eb533a999559d9e339d479596553bebfd +F src/tclsqlite.c 9ac18aac16dd91fec546d1329c17a41b6b2fd095b2294ba063fa02aca0161f8e F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a F src/test1.c df2df0a74c9aa81cbbad9d7d8277e49ac255334cba97da9ee22501beac274201 F src/test2.c 2b9ab96bba63a1c369d5769390475259ad210f144a877805f2e32e563f9e93c1 @@ -1533,7 +1533,7 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd -F test/qrf01.test 0b5095dfc841eaa12806fe20fc0ede72573885390b3152edc0edb66378fb55f7 +F test/qrf01.test cc17b3efe5d4de8b1f2c9ddd0bb8a6b6934d4f32252803d9d145fac3cecb369e F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92 F test/qrf03.test e7efe46d204671726b4707585126cd78d107368de4a7d0c7b8d5157cdd8624ed F test/qrf04.test 0894692c998d2401dcc33449c02051b503ecce0c94217be54fb007c82d2d1379 @@ -2213,11 +2213,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 7f49a7a90eda01753c0dff65197bd7bc48a751e24a46919d30af6e2baf0788fc -R d458787ae041129a9286190077b172aa -T *branch * psql-mode -T *sym-psql-mode * -T -sym-trunk * +P 600c6b464050078b38cc688668148f684243e7c5f08d0aae4d5f72aa6f985482 +R 6b8e1ec56e5918c44216302c94fac591 U drh -Z e2f6af93169d3a630e55ed3303140615 +Z 7a466c86906d29efc86be3f3db820048 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 8fe0186d00..71f1f78609 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -600c6b464050078b38cc688668148f684243e7c5f08d0aae4d5f72aa6f985482 +adeb4fbb86a8a781d7efb0eecb9c84ce605d0bd45f63992bf2ecd60765a60d2c diff --git a/src/shell.c.in b/src/shell.c.in index eaf0cc801d..c9ebae848c 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -2165,6 +2165,7 @@ static void modeChange(ShellState *p, unsigned char eMode){ pM->spec.bRowCount = QRF_Yes; }else{ pM->spec.bBorder = QRF_Auto; + pM->spec.bRowCount = QRF_No; } if( pI->mFlg & 0x02 ){ pM->spec.bSplitColumn = QRF_Yes; @@ -8660,7 +8661,7 @@ static int modeTitleDsply(ShellState *p, int bAll){ ** "tcl", or "json". "off" means show the text as-is. ** "on" is an alias for "sql". ** --reset Changes all mode settings back to their default. -** --rowcount BOOLEAN Show "(N rows)" at the end of output. +** --rowcount BOOLEAN Show "(N rows)" at the end of query results. ** --rowsep STRING Use STRING as the row separator ** --sw|--screenwidth N Declare the screen width of the output device ** to be N characters. An attempt may be made to @@ -8672,7 +8673,7 @@ static int modeTitleDsply(ShellState *p, int bAll){ ** --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON. ** --title ARG Whether or not to show column headers, and if so ** how to encode them. ARG can be "off", "on", -** "sql", "csv", "html", "tcl", or "json". +** "sql", "csv", "html", "tcl", "json", or "always". ** --titlelimit N Limit the length of column titles to N characters. ** -v|--verbose Verbose output ** --widths LIST Set the columns widths for columnar modes. The diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 8f46f7e810..bee03fe3d9 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -2066,6 +2066,7 @@ static void DbHookCmd( ** -defaultalign ("auto"|"left"|...) Default alignment ** -titalalign ("auto"|"left"|"right"|...) Default column name alignment ** -border ("auto"|"off"|"on") Border for box and table styles +** -rowcount ("auto"|"off"|"on") Show row counts ** -wrap NUMBER Max width of any single column ** -screenwidth NUMBER Width of the display TTY ** -linelimit NUMBER Max lines for any cell @@ -2136,7 +2137,7 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ }; memset(&qrf, 0, sizeof(qrf)); - qrf.iVersion = 1; + qrf.iVersion = 2; qrf.pzOutput = &zResult; for(i=2; iinterp, "option has no argument: ", zArg, (char*)0); + Tcl_AppendResult(pDb->interp, "option has no argument: ", zArg,(char*)0); rc = TCL_ERROR; goto format_failed; }else if( strcmp(zArg,"-style")==0 ){ @@ -2195,7 +2196,7 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ /* NB: --title can be "off" or "on but --text may not be. Thus we put ** the "off" and "on" choices first and start the search on the ** thrid element of the array when processing --text */ - static const char *azText[] = { "off", "on", + static const char *azText[] = { "off", "on", "always", "auto", "csv", "html", "json", "plain", "relaxed", "sql", "tcl", 0 @@ -2207,17 +2208,20 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ }; int txt; int k = zArg[2]=='e'; - rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], &azText[k*2], zArg, + rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], &azText[k*3], zArg, 0, &txt); if( rc ) goto format_failed; if( k ){ qrf.eText = aTextMap[txt]; - }else if( txt<=1 ){ - qrf.bTitles = txt ? QRF_Yes : QRF_No; + }else if( txt<=2 ){ + assert( QRF_No==1 ); + assert( QRF_Yes==2 ); + assert( QRF_Always==3 ); + qrf.bTitles = txt+QRF_No; qrf.eTitle = QRF_TEXT_Auto; }else{ qrf.bTitles = QRF_Yes; - qrf.eTitle = aTextMap[txt-2]; + qrf.eTitle = aTextMap[txt-3]; } i++; }else if( strcmp(zArg,"-blob")==0 ){ @@ -2247,6 +2251,7 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ }else if( strcmp(zArg,"-textjsonb")==0 || strcmp(zArg,"-splitcolumn")==0 || strcmp(zArg,"-border")==0 + || strcmp(zArg,"-rowcount")==0 ){ int v = 0; rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, @@ -2256,11 +2261,14 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ qrf.bTextJsonb = aBoolMap[v]; }else if( zArg[1]=='b' ){ qrf.bBorder = aBoolMap[v]; + }else if( zArg[1]=='r' ){ + qrf.bRowCount = aBoolMap[v]; }else{ qrf.bSplitColumn = aBoolMap[v]; } i++; - }else if( strcmp(zArg,"-defaultalign")==0 || strcmp(zArg,"-titlealign")==0){ + }else if( strcmp(zArg,"-defaultalign")==0 + || strcmp(zArg,"-titlealign")==0){ int ax = 0; rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azAlign, zArg[1]=='d' ? "default alignment (-defaultalign)" : diff --git a/test/qrf01.test b/test/qrf01.test index 25b7a893be..c8a6527419 100644 --- a/test/qrf01.test +++ b/test/qrf01.test @@ -66,6 +66,51 @@ do_test 1.11d { 1 │ 2.5 │ three x'424c4f42' │ │ Ἀμήν } +do_test 1.11e { + set result "\n[db format -rowcount on {SELECT * FROM t1}]" +} { +╭──────┬─────┬───────╮ +│ a │ b │ c │ +╞══════╪═════╪═══════╡ +│ 1 │ 2.5 │ three │ +│ BLOB │ │ Ἀμήν │ +╰──────┴─────┴───────╯ +(2 rows) +} +do_test 1.11f { + set result "\n[db format -rowcount on {SELECT * FROM t1 WHERE a=1}]" +} { +╭───┬─────┬───────╮ +│ a │ b │ c │ +╞═══╪═════╪═══════╡ +│ 1 │ 2.5 │ three │ +╰───┴─────┴───────╯ +(1 row) +} +do_test 1.11g { + set result "\n[db format {SELECT * FROM t1 WHERE a=2}]" +} {} +do_test 1.11h { + set result "\n[db format -rowcount on {SELECT * FROM t1 WHERE a=2}]" +} { +(0 rows) +} +do_test 1.11i { + set result "\n[db format -title always -rowcount off {SELECT * FROM t1 WHERE a=2}]" +} { +╭───┬───┬───╮ +│ a │ b │ c │ +╰───┴───┴───╯ +} +do_test 1.11j { + set result "\n[db format -title always -rowcount on {SELECT * FROM t1 WHERE a=2}]" +} { +╭───┬───┬───╮ +│ a │ b │ c │ +╰───┴───┴───╯ +(0 rows) +} + do_test 1.12 { set result "\n[db format -text csv {SELECT * FROM t1}]" } { @@ -378,7 +423,7 @@ x'424c4f42'|NULL|'Ἀμήν' do_test 1.118 { set rc [catch {db format -style list -title unk {SELECT * FROM t1}} res] lappend rc $res -} {1 {bad -title "unk": must be off, on, auto, csv, html, json, plain, relaxed, sql, or tcl}} +} {1 {bad -title "unk": must be off, on, always, auto, csv, html, json, plain, relaxed, sql, or tcl}} do_test 1.120 { @@ -410,7 +455,6 @@ do_test 1.131 { x'424c4f42',NULL,'Ἀμήν' } - do_execsql_test 2.0 { DELETE FROM t1; INSERT INTO t1 VALUES(1,2,'The quick fox jumps over the lazy brown dog.');