]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add the bTextNull field to sqlite3_qrf_spec. Use QRF to implement "tcl"
authordrh <>
Mon, 10 Nov 2025 12:32:04 +0000 (12:32 +0000)
committerdrh <>
Mon, 10 Nov 2025 12:32:04 +0000 (12:32 +0000)
mode in the CLI.

FossilOrigin-Name: 2ba92320db3c16c3c91e29ea935ae92da546261f25846d242bd2dd27e0b7e032

ext/qrf/README.md
ext/qrf/qrf.c
ext/qrf/qrf.h
manifest
manifest.uuid
src/shell.c.in
src/tclsqlite.c
test/qrf01.test
test/shell1.test

index ad85a5275eccd1ebb3791420f3b047309420ff84..172d572a6ce9b4ec2fb362bb3178189dc8e05876 100644 (file)
@@ -76,6 +76,7 @@ struct sqlite3_qrf_spec {
   unsigned char bColumnNames; /* True to show column names */
   unsigned char bWordWrap;    /* Try to wrap on word boundaries */
   unsigned char bTextJsonb;   /* Render JSONB blobs as JSON text */
+  unsigned char bTextNull;    /* Apply eText encoding to the zNull[] value */
   short int mxColWidth;       /* Maximum width of any individual column */
   short int nScreenWidth;     /* Try to keep output short so that it fits */
   short int mxRowHeight;      /* Maximum number of lines for any row */
@@ -470,12 +471,17 @@ and these settings are ignored for those styles.
 The sqlite3_qrf_spec.zTableName value is the name of the output table
 when eStyle is QRF_STYLE_Insert.
 
-### 2.15 The Rendering Of NULL
+### 2.15 The Rendering Of NULL (zNull, eTextNull)
 
 If a value is NULL then show the NULL using the string
 found in sqlite3_qrf_spec.zNull.  If zNull is itself a NULL pointer
 then NULL values are rendered as an empty string.
 
+If the sqlite3_qrf_spec.bTextNull field is QRF_Yes, then the
+text encoding specified by eText is applied to the value in
+zNull.  If bTextNull is QRF_No or QRF_Auto, then the value
+in zNull is shown verbatim.
+
 ### 2.16 Optional Value Rendering Callback
 
 If the sqlite3_qrf_spec.xRender field is not NULL, then each
index 9aa05f420adb29526dc05e4941e76dc83eb26d53..8cb0a94b03abe2bef8cfa9496bbd8b57e8c51194 100644 (file)
@@ -925,7 +925,11 @@ static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
       break;
     }
     case SQLITE_NULL: {
-      sqlite3_str_appendall(pOut, p->spec.zNull);
+      if( p->spec.bTextNull==QRF_Yes ){
+        qrfEncodeText(p, pOut, p->spec.zNull);
+      }else{
+        sqlite3_str_appendall(pOut, p->spec.zNull);
+      }
       break;
     }
     case SQLITE_TEXT: {
index 7094ce0544b23afab113b8ea638e237f8108c2af..c1cb8d5ef54be729eb6abcde291ae43e33c4cbaf 100644 (file)
@@ -31,6 +31,7 @@ struct sqlite3_qrf_spec {
   unsigned char bColumnNames; /* True to show column names */
   unsigned char bWordWrap;    /* Try to wrap on word boundaries */
   unsigned char bTextJsonb;   /* Render JSONB blobs as JSON text */
+  unsigned char bTextNull;    /* Apply eText encoding to zNull[] */
   unsigned char eDfltAlign;   /* Default alignment, no covered by aAlignment */
   unsigned char eTitleAlign;  /* Alignment for column headers */
   short int mxColWidth;       /* Maximum width of any individual column */
index ca5777b3c96e6fd0e02e36ac4226dc0ba167726e..b4903ea05e582d0bc2eee3d4cf6794a76cb89952 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Merge\sall\sthe\slatest\strunk\sfixes,\sand\sespecially\sthe\sfix\sfor\sthe\s".www"\ncommand\sin\sthe\sCLI,\sinto\sthe\sqrf\sbranch.
-D 2025-11-10T10:46:55.745
+C Add\sthe\sbTextNull\sfield\sto\ssqlite3_qrf_spec.\s\sUse\sQRF\sto\simplement\s"tcl"\nmode\sin\sthe\sCLI.
+D 2025-11-10T12:32:04.765
 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/README.md 5011393a53bfb26b59488e5d8d8576de521058876b61318d4fe476375a4ae9a2
-F ext/qrf/qrf.c 7d3c500a73e8185a3ae097b0c47a3609ef96e0f9bc6b8b7d2f0db8c2e5330fd9
-F ext/qrf/qrf.h 49a106a7484c9761bb66bebe0043fbf43302624734a9f9734e0fa410a908e525
+F ext/qrf/README.md d54205c032e4c2b73f6edf75927ce14d6ce317c32c200f1a60ba0317a62b9196
+F ext/qrf/qrf.c 144a1d2e8a499f83c54805afd79a39cf635f7113dea9f5341813828a74651678
+F ext/qrf/qrf.h b199c3a9d1e2b37da02d9ccc139192791f5310c1b72684d15c8e47d68fb7c185
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363
@@ -735,7 +735,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a
-F src/shell.c.in 1096866ed70c24b3cf66ed0d44841ba992847257667d51c6130f043661a57adc
+F src/shell.c.in 188ed543703abf77edc83e4f8b18d3f635999de7eee88b0cf873ba37ac8d8454
 F src/sqlite.h.in 7403a952a8f1239de7525b73c4e3a0f9540ec0607ed24fec887f5832642d44b8
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52
@@ -743,7 +743,7 @@ F src/sqliteInt.h 88f7fc9ce1630d9a5f7e0a8e1f3287cdc63882fba985c18e7eee1b9f457f59
 F src/sqliteLimit.h fe70bd8983e5d317a264f2ea97473b359faf3ebb0827877a76813f5cf0cdc364
 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
-F src/tclsqlite.c 667adb8b432f95ca79a86024fff4fd76f503fc8db72ea29b6f4d0b0d4382518a
+F src/tclsqlite.c 79cf2fe03fbba118d594bb8317096ac5a7ee434a2c5e8a1f1926ce2b1f91d1c0
 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a
 F src/test1.c f880ab766eeedf2c063662bd9538b923fd42c4341b7bfc2150a6d93ab8b9341c
 F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
@@ -1506,7 +1506,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 f229fcfe7c9907a1c0471ab2b0beed2fac01a69407d45248b490425716849603
+F test/qrf01.test d36946b63e1f42d61da541a0c0313bb399d9166713bdbc74da18c9dc3d296c2a
 F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
 F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
@@ -1601,7 +1601,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21
 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
-F test/shell1.test 49c6ebb23d41b1171a3fac7b19a9091b98966a736cd632103d409e59310916ce
+F test/shell1.test d70a7a741e0abae444576f963eab5eff13a2ef6580678d627fe405291ec353ee
 F test/shell2.test d8da6a06dcce1d8f04f776f918d4d57c28ddc28c54f3a44f95429794892e3a91
 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e
 F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d
@@ -2173,8 +2173,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 38e4171697057437fc9f70f8734c7f557b91da47804ff10b3d2481802377222f bb4fd5b789cebf2b224c29023fea3e620a86fb36730c36c0d85d9f35880bf643
-R 81ab1f40b9f0f7a027cb449f4a6b6756
+P 07a5523cf27fd7c1d8fbc5cde80cb45fadef49317ca59fec9f8e42b2839c3e19
+R 87f6415d434e97597df92d062309a39f
 U drh
-Z 3ca265c3c4bd81ca6bd532ca63dabffd
+Z f07f7fec67c974040146f4f7655e4ed4
 # Remove this line to create a well-formed Fossil manifest.
index 99b543edc54d8a85ccb8722b9a3c77f6f2ba4f7f..39f7789b360017bb8f87e42f4dd133c049deb41c 100644 (file)
@@ -1 +1 @@
-07a5523cf27fd7c1d8fbc5cde80cb45fadef49317ca59fec9f8e42b2839c3e19
+2ba92320db3c16c3c91e29ea935ae92da546261f25846d242bd2dd27e0b7e032
index bbc4ecaa14e2583fc7e78ce53861afd0d50cd793..148650c74c1cf8240d94b34e85b0eb100f08f166 100644 (file)
@@ -1392,9 +1392,9 @@ static const unsigned char aQrfStyle[] = {
   /* html */         QRF_STYLE_Html,
   /* insert */       QRF_STYLE_Insert,
   /* quote */        QRF_STYLE_Quote,
-  /* tcl */          102,
+  /* tcl */          QRF_STYLE_List,
   /* csv */          QRF_STYLE_Csv,
-  /* ascii */        104,
+  /* ascii */        QRF_STYLE_List,
   /* prettyprint */  105,
   /* json */         QRF_STYLE_Json,
   /* markdown */     QRF_STYLE_Markdown,
@@ -2112,42 +2112,12 @@ static int shell_callback(
       sqlite3_fputs("</TR>\n", p->out);
       break;
     }
-    case MODE_Tcl: {
-      if( p->cnt++==0 && p->showHeader ){
-        for(i=0; i<nArg; i++){
-          output_c_string(p->out, azCol[i] ? azCol[i] : "");
-          if(i<nArg-1) sqlite3_fputs(p->colSeparator, p->out);
-        }
-        sqlite3_fputs(p->rowSeparator, p->out);
-      }
-      if( azArg==0 ) break;
-      for(i=0; i<nArg; i++){
-        output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
-        if(i<nArg-1) sqlite3_fputs(p->colSeparator, p->out);
-      }
-      sqlite3_fputs(p->rowSeparator, p->out);
-      break;
-    }
-    case MODE_Csv:      assert(0);  break;
+    case MODE_Tcl:      assert(0); break;
+    case MODE_Csv:      assert(0); break;
     case MODE_Insert:   assert(0); break;
     case MODE_Json:     assert(0); break;
     case MODE_Quote:    assert(0); break;
-    case MODE_Ascii: {
-      if( p->cnt++==0 && p->showHeader ){
-        for(i=0; i<nArg; i++){
-          if( i>0 ) sqlite3_fputs(p->colSeparator, p->out);
-          sqlite3_fputs(azCol[i] ? azCol[i] : "", p->out);
-        }
-        sqlite3_fputs(p->rowSeparator, p->out);
-      }
-      if( azArg==0 ) break;
-      for(i=0; i<nArg; i++){
-        if( i>0 ) sqlite3_fputs(p->colSeparator, p->out);
-        sqlite3_fputs(azArg[i] ? azArg[i] : p->nullValue, p->out);
-      }
-      sqlite3_fputs(p->rowSeparator, p->out);
-      break;
-    }
+    case MODE_Ascii:    assert(0); break;
   }
   return 0;
 }
@@ -2999,6 +2969,12 @@ static int shell_exec(
       }
       break;
     }
+    case MODE_Tcl: {
+      spec.eText = QRF_TEXT_Tcl;
+      spec.eTitle = QRF_TEXT_Tcl;
+      spec.bTextNull = QRF_Yes;
+      break;
+    }
     case MODE_Box:
     case MODE_Table:
     case MODE_Markdown: {
index 06e49b03762119c2c30f089cab5315d770ff1d32..4909fd28191abf7b6b4c5e59409e21e4161a0db8 100644 (file)
@@ -2063,6 +2063,7 @@ static void DbHookCmd(
 **     -columnnames ("auto"|"off"|"on")        Show column names?
 **     -wordwrap ("auto"|"off"|"on")           Try to wrap at word boundry?
 **     -textjsonb ("auto"|"off"|"on")          Auto-convert JSONB to text?
+**     -textnull ("auto"|"off"|"on")           Use text encoding for -null.
 **     -defaultalign ("auto"|"left"|...)       Default alignment
 **     -titalalign ("auto"|"left"|"right"|...) Default column name alignment
 **     -maxcolwidth NUMBER                     Max width of any single column
@@ -2089,6 +2090,7 @@ static void DbHookCmd(
 **     -columnnames      bColumnNames
 **     -wordwrap         bWordWrap
 **     -textjsonb        bTextJsonb
+**     -textnull         bTestNull
 **     -defaultalign     eDfltAlign
 **     -titlealign       eTitleAlign
 **     -maxcolwidth      mxColWidth
@@ -2236,12 +2238,16 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
       if( rc ) goto format_failed;
       qrf.bWordWrap = aBoolMap[v];
       i++;
-    }else if( strcmp(zArg,"-textjsonb")==0 ){
+    }else if( strcmp(zArg,"-textjsonb")==0 || strcmp(zArg,"-textnull")==0 ){
       int v = 0;
       rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
-                              "-testjsonb", 0, &v);
+                              zArg, 0, &v);
       if( rc ) goto format_failed;
-      qrf.bTextJsonb = aBoolMap[v];
+      if( zArg[5]=='j' ){
+        qrf.bTextJsonb = aBoolMap[v];
+      }else{
+        qrf.bTextNull = aBoolMap[v];
+      }
       i++;
     }else if( strcmp(zArg,"-defaultalign")==0 || strcmp(zArg,"-titlealign")==0){
       int ax = 0;
index 5ab178c57f2f893c5209687475c7c427d147c75d..ba8507e6d2e9f3ec2e2804150fb56e4063aaccb1 100644 (file)
@@ -708,4 +708,42 @@ do_test 5.4 {
 └────┴────┴─────┴──────┴───────┴───┘
 }
 
+do_execsql_test 6.0 {
+  DELETE FROM t2;
+  INSERT INTO t2 VALUES
+     (1, 2.5, 'three', x'342028666f757229', null);
+}
+do_test 6.1a {
+  set result "\n[db format -style list -null NULL \
+                 -text tcl -columnsep , \
+                 {SELECT * FROM t2}]"
+} {
+1,2.5,"three","\064\040\050\146\157\165\162\051",NULL
+}
+do_test 6.1b {
+  set result "\n[db format -style list -null NULL \
+                 -text tcl -columnsep , -textnull off \
+                 {SELECT * FROM t2}]"
+} {
+1,2.5,"three","\064\040\050\146\157\165\162\051",NULL
+}
+do_test 6.1c {
+  set result "\n[db format -style list -null NULL \
+                 -text tcl -columnsep , -textnull auto \
+                 {SELECT * FROM t2}]"
+} {
+1,2.5,"three","\064\040\050\146\157\165\162\051",NULL
+}
+do_test 6.2 {
+  set result "\n[db format -style list -null NULL \
+                 -text tcl -columnsep , -textnull yes \
+                 {SELECT * FROM t2}]"
+} {
+1,2.5,"three","\064\040\050\146\157\165\162\051","NULL"
+}
+do_test 6.3 {
+  catch {db format -textnull xyz {SELECT * FROM t2}} res
+  set res
+} {bad -textnull "xyz": must be auto, yes, no, on, or off}
+
 finish_test
index 9a69aa767c4f59e16b54500e9d99726f65abb81a..8f20fabaa171349555001181795af54614cb941c 100644 (file)
@@ -1068,8 +1068,8 @@ do_test shell1-4.3 {
   catchcmd test.db ".mode tcl\nselect * from t1;"
 } {0 {""
 ""
-"1"
-"2.25"
+1
+2.25
 "hello"
 "\200\177"}}
 
@@ -1082,7 +1082,7 @@ do_test shell1-4.4 {
   }
   catchcmd test.db ".mode tcl\nselect * from t2;"
 } {0 {"" ""
-"1" "2.25"
+1 2.25
 "hello" "\200\177"}}
 
 # Test the output of ".mode tcl" with ".nullvalue"
@@ -1090,7 +1090,7 @@ do_test shell1-4.4 {
 do_test shell1-4.5 {
   catchcmd test.db ".mode tcl\n.nullvalue NULL\nselect * from t2;"
 } {0 {"NULL" ""
-"1" "2.25"
+1 2.25
 "hello" "\200\177"}}
 
 # Test the output of ".mode tcl" with Tcl reserved characters