From: drh <> Date: Mon, 6 Jul 2026 19:24:15 +0000 (+0000) Subject: Add new option "bRowCount" to QRF. Add QRF_Always as a possibility for X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=8e409df9844799207cbee4d00974f4f57d9c585e;p=thirdparty%2Fsqlite.git Add new option "bRowCount" to QRF. Add QRF_Always as a possibility for bTitles. Use these feature to enhance ".mode psql" to be a better approximation of what psql actually does. FossilOrigin-Name: 600c6b464050078b38cc688668148f684243e7c5f08d0aae4d5f72aa6f985482 --- diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c index eab9c1ad55..af21458592 100644 --- a/ext/qrf/qrf.c +++ b/ext/qrf/qrf.c @@ -1952,7 +1952,10 @@ static void qrfColumnar(Qrf *p){ int bRTrim; /* Trim trailing space */ rc = sqlite3_step(p->pStmt); - if( rc!=SQLITE_ROW || nColumn==0 ){ + if( nColumn==0 ){ + return; /* Not a query. No results every shown. */ + } + if( rc!=SQLITE_ROW && (rc!=SQLITE_DONE || p->spec.bTitles!=QRF_Always) ){ return; /* No output */ } @@ -1969,8 +1972,8 @@ static void qrfColumnar(Qrf *p){ if( qrfColDataEnlarge(&data) ) return; assert( data.az!=0 ); - /* Load the column header names and all cell content into data */ - if( p->spec.bTitles==QRF_Yes ){ + /* Load the column header names into data */ + if( p->spec.bTitles>=QRF_Yes ){ unsigned char saved_eText = p->spec.eText; p->spec.eText = p->spec.eTitle; memset(data.abNum, 0, nColumn); @@ -1995,9 +1998,10 @@ static void qrfColumnar(Qrf *p){ if( nNL ) data.bMultiRow = 1; } p->spec.eText = saved_eText; - p->nRow++; } - do{ + + /* Load query results into data */ + while( rc==SQLITE_ROW && p->iErr==SQLITE_OK ){ if( data.n+nColumn > data.nAlloc ){ if( qrfColDataEnlarge(&data) ) return; } @@ -2018,7 +2022,8 @@ static void qrfColumnar(Qrf *p){ if( nNL ) data.bMultiRow = 1; } p->nRow++; - }while( sqlite3_step(p->pStmt)==SQLITE_ROW && p->iErr==SQLITE_OK ); + rc = sqlite3_step(p->pStmt); + } if( p->iErr ){ qrfColDataFree(&data); return; @@ -2226,7 +2231,7 @@ static void qrfColumnar(Qrf *p){ ** body. isTitleDataSeparator will be true if we are doing (1). */ if( (i==0 || data.bMultiRow) && i+nColumnspec.bTitles==QRF_Yes); + int isTitleDataSeparator = (i==0 && p->spec.bTitles>=QRF_Yes); if( isTitleDataSeparator ){ qrfLoadAlignment(&data, p); } @@ -2273,7 +2278,7 @@ static void qrfColumnar(Qrf *p){ } /* Draw the line across the bottom of the table */ - if( p->spec.bBorder!=QRF_No ){ + if( p->spec.bBorder!=QRF_No || p->nRow==0 ){ switch( p->spec.eStyle ){ case QRF_STYLE_Box: qrfBoxSeparator(p->pOut, &data, BOX_R12, BOX_124, BOX_R14, 0); @@ -2283,6 +2288,10 @@ static void qrfColumnar(Qrf *p){ break; } } + if( p->spec.bRowCount==QRF_Yes ){ + sqlite3_int64 n = p->nRow; + sqlite3_str_appendf(p->pOut, "(%d row%s)\n", n, n==1 ? "" : "s"); + } qrfWrite(p); qrfColDataFree(&data); @@ -2566,7 +2575,7 @@ static void qrfOneSimpleRow(Qrf *p){ break; } case QRF_STYLE_Html: { - if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){ + 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); @@ -2597,7 +2606,7 @@ static void qrfOneSimpleRow(Qrf *p){ }else{ sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName); } - if( p->spec.bTitles==QRF_Yes ){ + if( p->spec.bTitles>=QRF_Yes ){ for(i=0; inCol; i++){ const char *zCName = sqlite3_column_name(p->pStmt, i); if( qrf_need_quote(zCName) ){ @@ -2697,7 +2706,7 @@ static void qrfOneSimpleRow(Qrf *p){ break; } default: { /* QRF_STYLE_List */ - if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){ + 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++){ @@ -2733,7 +2742,7 @@ static void qrfInitialize( size_t sz; /* Size of pSpec[], based on pSpec->iVersion */ memset(p, 0, sizeof(*p)); p->pzErr = pzErr; - if( pSpec->iVersion>1 ){ + if( pSpec->iVersion>2 ){ qrfError(p, SQLITE_ERROR, "unusable sqlite3_qrf_spec.iVersion (%d)", pSpec->iVersion); @@ -2761,6 +2770,9 @@ static void qrfInitialize( if( p->spec.eText>QRF_TEXT_Relaxed ) p->spec.eText = QRF_Auto; if( p->spec.eTitle>QRF_TEXT_Relaxed ) p->spec.eTitle = QRF_Auto; if( p->spec.eBlob>QRF_BLOB_Size ) p->spec.eBlob = QRF_Auto; + if( pSpec->iVersion<=1 ){ + p->spec.bRowCount = 0; + } qrf_reinit: switch( p->spec.eStyle ){ case QRF_Auto: { diff --git a/ext/qrf/qrf.h b/ext/qrf/qrf.h index 7ba9149242..104c439d8e 100644 --- a/ext/qrf/qrf.h +++ b/ext/qrf/qrf.h @@ -57,6 +57,8 @@ struct sqlite3_qrf_spec { void *pRenderArg; /* First argument to the xRender callback */ void *pWriteArg; /* First argument to the xWrite callback */ char **pzOutput; /* Storage location for output string */ + /* The following are available in iVersion 2 and later */ + unsigned char bRowCount; /* Show the number of rows at end of each query */ /* Additional fields may be added in the future */ }; @@ -145,6 +147,11 @@ int sqlite3_format_query_result( #define QRF_No 1 /* Alternate spelling for QRF_SW_Off */ #define QRF_Yes 2 /* Alternate spelling for QRF_SW_On */ +/* +** Additional value allowed for bTitles +*/ +#define QRF_Always 3 /* Always show titles, even if no rows */ + /* ** Possible alignment values alignment settings ** diff --git a/manifest b/manifest index 3dfd26b3cf..b125638df7 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Reduce\sdivergence\sbetween\sext/misc/fossildelta.c,\nthe\sdelta\scode\sin\sext/rbu/sqlite3rbu.c,\sand\sFossil\sitself. -D 2026-07-06T16:26:30.821 +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 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -425,8 +425,8 @@ F ext/misc/zipfile.c 5cf901996c840b32a5c03d947c4ee86bda166f6e595a41871840df85204 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee F ext/qrf/README.md 9e644615d7d7b77ef7e9db798765679e50c5ed12eda48bce21c9ef9eb4715e9d F ext/qrf/dev-notes.md e68a6d91ce4c7eb296ef2daadc2bb79c95c317ad15b9fafe40850c67b29c2430 -F ext/qrf/qrf.c c8e1badc5f48dcf5e31fb6a4c9da786d64f981784be116a55b5503127d27eb17 -F ext/qrf/qrf.h 7a832022bc3f40dc09fff1cb6f18025395a19313de090265a4a1d22bb9b400be +F ext/qrf/qrf.c 99c3ab46eedbc68211296e58797d601a80667f75a8db746a75cb92ec20ca923a +F ext/qrf/qrf.h fcbc33578176b2fd9dda8310d8f6ba4eaf4f75052f466dfe682802e6664df8bd F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363 @@ -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 cabdf85361fa9ac9c9f6ee900fb7cce012984917583a2302c03ef40829e6a90c +F src/shell.c.in c26f8aca2312ed8a8be1f63f887c23f45e37911d4ede17f9c6b9d7897d574b80 F src/sqlite.h.in d9ec41feb4cd804e68b174328b43beb3f1f71bba13e1c7a439efb826d301cccc F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 0efd4723bad9124ea1f581d9f1ea0254ac1c6f3e5fb29e4f3dcf36c72485a456 @@ -1461,7 +1461,7 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 -F test/modeA.sql 859e608297646e2d573e431507f38e465bc1513fa5862916ec8fb20a1d6314a4 +F test/modeA.sql b3110eb88e3d7eb86bbf0e52e8d45bde036608baa3c98fbce07dfe3eab1928e1 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7 @@ -1634,7 +1634,7 @@ F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8e F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 F test/shell-prompt.sql 8a320a78812d75349bd5e1a95178f7ff1f2194bb4cceee572865d127c51684b3 -F test/shell1.test 72893bc7957ff6402552ce4e31aa98a94478fa91bf5c4009a458711ad2e6efac +F test/shell1.test df7a33a7056220ae12966228731658b36c98c22ba27e114bbf2785ff5b84fea2 F test/shell2.test e0842ea6a28f86f1ef235e117b3a381d16b999e7bdd9655b1de663026dd8ea27 F test/shell3.test 91efdd545097a61a1f72cf79c9ad5b49da080f3f10282eaf4c3c272cd1012db2 F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db @@ -2213,8 +2213,11 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P aeb85fdfca0a546cfd3ef26efd58b9ffc8d5fda15562558d8479dee55321596f -R 1ee393656bab6e0046f97e98b14c8cbd +P 7f49a7a90eda01753c0dff65197bd7bc48a751e24a46919d30af6e2baf0788fc +R d458787ae041129a9286190077b172aa +T *branch * psql-mode +T *sym-psql-mode * +T -sym-trunk * U drh -Z 617b711efa64511b929c4bdfdf8bb7a8 +Z e2f6af93169d3a630e55ed3303140615 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index bec971799f..7d3b590fe6 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch psql-mode +tag psql-mode diff --git a/manifest.uuid b/manifest.uuid index d79b2a569a..8fe0186d00 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -7f49a7a90eda01753c0dff65197bd7bc48a751e24a46919d30af6e2baf0788fc +600c6b464050078b38cc688668148f684243e7c5f08d0aae4d5f72aa6f985482 diff --git a/src/shell.c.in b/src/shell.c.in index cbc534ce57..eaf0cc801d 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -594,7 +594,7 @@ static const ModeInfo aModeInfo[] = { { "list", 2, 1, 9, 1, 1, 0, 1, 12, 0, 0 }, { "markdown", 0, 0, 9, 1, 1, 0, 2, 13, 2, 0 }, { "off", 0, 0, 0, 0, 0, 0, 0, 14, 0, 0 }, - { "psql", 0, 0, 9, 1, 1, 0, 2, 19, 2, 1 }, + { "psql", 0, 0, 9, 1, 1, 0, 3, 19, 2, 1 }, { "qbox", 0, 0, 10, 2, 1, 0, 2, 1, 2, 0 }, { "quote", 4, 1, 10, 2, 2, 0, 1, 12, 0, 0 }, { "split", 0, 0, 9, 1, 1, 0, 1, 2, 2, 2 }, @@ -613,7 +613,7 @@ static const ModeInfo aModeInfo[] = { ** 1: plain v----------' 0: n/a ** 2: sql 0: auto 1: no ** 3: csv 1: as-text 2: yes - ** 4: html 2: sql + ** 4: html 2: sql 3: always ** 5: c 3: hex ** 6: json 4: c ** 5: json @@ -2046,7 +2046,7 @@ static void modeFree(Mode *p){ free(p->spec.zTableName); free(p->spec.zNull); memset(p, 0, sizeof(*p)); - p->spec.iVersion = 1; + p->spec.iVersion = 2; p->autoExplain = autoExplain; } @@ -2162,6 +2162,7 @@ static void modeChange(ShellState *p, unsigned char eMode){ pM->spec.eTitle = pI->eHdr; if( pI->mFlg & 0x01 ){ pM->spec.bBorder = QRF_No; + pM->spec.bRowCount = QRF_Yes; }else{ pM->spec.bBorder = QRF_Auto; } @@ -2200,7 +2201,7 @@ static void modeChange(ShellState *p, unsigned char eMode){ ** already been freed and zeroed prior to calling this routine. */ static void modeDefault(ShellState *p){ - p->mode.spec.iVersion = 1; + p->mode.spec.iVersion = 2; p->mode.autoExplain = 1; if( stdin_is_interactive || stdout_is_console ){ modeChange(p, MODE_TTY); @@ -8555,8 +8556,10 @@ static int dotCmdImport(ShellState *p){ ** 0: The titles do not matter. Never show anything. ** 1: Show "--titles off" ** 2: Show "--titles on" -** 3: Show "--title VALUE" where VALUE is an encoding method +** 3: Show "--titles always" +** 4: Show "--title VALUE" where VALUE is an encoding method ** to use, one of: plain sql csv html tcl json +** 5: Show "--title VALUE" as in 3, followed by "--titles always" ** ** Inputs are: ** @@ -8593,7 +8596,23 @@ static int modeTitleDsply(ShellState *p, int bAll){ if( bT<2 ) v >>= 8; /* ON in even bytes, OFF in odd bytes (1st byte 0) */ if( !bAll ) v >>= 4; /* bAll values are in the lower half-byte */ - return v & 3; /* Return the selected truth-table entry */ + v &= 3; + /* At this point v means: + ** 0: The titles do not matter. Never show anything. + ** 1: Show "--titles off" + ** 2: Show "--titles on" + ** 3: Show "--title VALUE" where VALUE is an encoding method + ** to use, one of: plain sql csv html tcl json + ** If bTitles is QRF_Always, convert 2->3, 3->5. If bTitles is not + ** QRF_Always, convert 3->4. + */ + if( p->mode.spec.bTitles==QRF_Always ){ + if( v==3 ) v = 5; + if( v==2 ) v = 3; + }else{ + if( v==3 ) v = 4; + } + return v; } /* @@ -8641,6 +8660,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. ** --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 @@ -8880,6 +8900,13 @@ static int dotCmdMode(ShellState *p){ int saved_eMode = p->mode.eMode; modeFree(&p->mode); modeChange(p, saved_eMode); + }else if( optionMatch(z,"rowcount") ){ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + p->mode.spec.bRowCount = booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; + chng = 1; }else if( optionMatch(z,"screenwidth") || optionMatch(z,"sw") ){ if( (++i)>=nArg ){ dotCmdError(p, i-1, "missing argument", 0); @@ -8939,15 +8966,19 @@ static int dotCmdMode(ShellState *p){ return 1; } k = pickStr(azArg[++i],&zErr, - "off","on","plain","sql","csv","html","tcl","json",""); - /* 0 1 2 3 4 5 6 7 */ + "off","on","plain","sql","csv","html","tcl","json","always",""); + /* 0 1 2 3 4 5 6 7 8 */ if( k<0 ){ dotCmdError(p, i, "bad --titles value","%z", zErr); return 1; } - p->mode.spec.bTitles = k>=1 ? QRF_Yes : QRF_No; - p->mode.mFlags &= ~MFLG_HDR; - p->mode.spec.eTitle = k>1 ? k-1 : aModeInfo[p->mode.eMode].eHdr; + if( k==8 ){ + p->mode.spec.bTitles = QRF_Always; + }else{ + p->mode.spec.bTitles = k>=1 ? QRF_Yes : QRF_No; + p->mode.mFlags &= ~MFLG_HDR; + p->mode.spec.eTitle = k>1 ? k-1 : aModeInfo[p->mode.eMode].eHdr; + } chng = 1; }else if( optionMatch(z,"widths") || optionMatch(z,"width") ){ int nWidth = 0; @@ -9102,6 +9133,12 @@ static int dotCmdMode(ShellState *p){ ){ sqlite3_str_appendf(pDesc," --quote %s",qrfQuoteNames[p->mode.spec.eText]); } + if( bAll + || ((p->mode.spec.bRowCount==QRF_Yes) != ((pI->mFlg&1)!=0)) + ){ + sqlite3_str_appendf(pDesc," --rowcount %s", + p->mode.spec.bRowCount==QRF_Yes ? "on" : "off"); + } zSetting = aModeStr[pI->eRSep]; if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zRowSep)!=0) ){ sqlite3_str_appendf(pDesc, " --rowsep "); @@ -9131,10 +9168,15 @@ static int dotCmdMode(ShellState *p){ }else if( k==2 ){ sqlite3_str_appendall(pDesc, " --titles on"); }else if( k==3 ){ + sqlite3_str_appendall(pDesc, " --titles always"); + }else if( k>=4 ){ static const char *azTitle[] = { "plain", "sql", "csv", "html", "tcl", "json"}; sqlite3_str_appendf(pDesc, " --titles %s", azTitle[p->mode.spec.eTitle-1]); + if( k==5 ){ + sqlite3_str_appendf(pDesc, " --titles always"); + } } if( p->mode.spec.nWidth>0 && (bAll || pI->eCx==2) ){ int ii; diff --git a/test/modeA.sql b/test/modeA.sql index e9a1f66bc7..b20f1a6a08 100644 --- a/test/modeA.sql +++ b/test/modeA.sql @@ -103,7 +103,7 @@ END .testcase 140 .mode -v .check <