]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Further testing and bug fixing.
authordrh <>
Wed, 5 Nov 2025 22:33:47 +0000 (22:33 +0000)
committerdrh <>
Wed, 5 Nov 2025 22:33:47 +0000 (22:33 +0000)
FossilOrigin-Name: d6b1bac15a692a69f3707fca721b57b3b283edb3850efa34ba42b02c3669fc71

ext/qrf/qrf.c
manifest
manifest.uuid
src/tclsqlite.c
test/qrf01.test
test/qrf02.test [new file with mode: 0644]

index 5f0dd801223635865de88f273e502d5cebe6e909..7347fcc7c8e3710fbab080e1b4c208d0089a1bc3 100644 (file)
@@ -358,21 +358,46 @@ int sqlite3_qrf_decode_utf8(const unsigned char *z, int *pU){
   return 1;
 }
 
+/*
+** Check to see if z[] is a valid VT100 escape.  If it is, then
+** return the number of bytes in the escape sequence.  Return 0 if
+** z[] is not a VT100 escape.
+**
+** This routine assumes that z[0] is \033 (ESC).
+*/
+static int qrfIsVt100(const unsigned char *z){
+  int i;
+  if( z[1]!='[' ) return 0;
+  i = 2;
+  while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
+  while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
+  if( z[i]<0x40 || z[i]>0x7e ) return 0;
+  return i+1;
+}
+
 /*
 ** Return the length of a string in display characters.
 ** Multibyte UTF8 characters count as a single character
 ** for single-width characters, or as two characters for
 ** double-width characters.
 */
-static int qrfDisplayLength(const char *z){
+static int qrfDisplayLength(const char *zIn){
+  const unsigned char *z = (const unsigned char*)zIn;
   int n = 0;
   while( *z ){
-    if( (0x80&z[0])==0 ){
+    if( z[0]<' ' ){
+      int k;
+      if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
+        z += k;
+      }else{
+        z++;
+      }
+    }else if( (0x80&z[0])==0 ){
       n++;
       z++;
     }else{
       int u = 0;
-      int len = sqlite3_qrf_decode_utf8((const unsigned char*)z, &u);
+      int len = sqlite3_qrf_decode_utf8(z, &u);
       z += len;
       n += sqlite3_qrf_wcwidth(u);
     }
@@ -731,23 +756,6 @@ static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
   }
 }
 
-/*
-** Check to see if z[] is a valid VT100 escape.  If it is, then
-** return the number of bytes in the escape sequence.  Return 0 if
-** z[] is not a VT100 escape.
-**
-** This routine assumes that z[0] is \033 (ESC).
-*/
-static int qrfIsVt100(const unsigned char *z){
-  int i;
-  if( z[1]!='[' ) return 0;
-  i = 2;
-  while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
-  while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
-  if( z[i]<0x40 || z[i]>0x7e ) return 0;
-  return i+1;
-}
-
 /*
 ** z[] is a line of text that is to be displayed the box or table or
 ** similar tabular formats.  z[] might contain control characters such
@@ -781,7 +789,7 @@ static char *qrfTableCell(
   if( mxWidth<0 ) mxWidth = -mxWidth;
   if( mxWidth==0 ) mxWidth = 1000000;
   i = j = n = 0;
-  while( n<mxWidth ){
+  while( n<=mxWidth ){
     unsigned char c = z[i];
     if( c>=0xc0 ){
       int u;
@@ -815,7 +823,7 @@ static char *qrfTableCell(
       i++;
     }
   }
-  if( n>=mxWidth && bWordWrap  ){
+  if( n>mxWidth && bWordWrap  ){
     /* Perhaps try to back up to a better place to break the line */
     for(k=i; k>i/2; k--){
       if( isspace(z[k-1]) ) break;
@@ -834,7 +842,7 @@ static char *qrfTableCell(
   }else{
     k = i;
   }
-  if( n>=mxWidth && z[i]>=' ' ){
+  if( n>mxWidth && z[i]>=' ' ){
    *pzTail = &z[i];
   }else if( z[i]=='\r' && z[i+1]=='\n' ){
     *pzTail = z[i+2] ? &z[i+2] : 0;
@@ -843,6 +851,7 @@ static char *qrfTableCell(
   }else{
     *pzTail = &z[i+1];
   }
+  if( n>=mxWidth && k>0 && z[k-1]==' ' ) k--;
   zOut = sqlite3_malloc64( j+1 );
   if( zOut==0 ){
     qrfOom(p);
@@ -858,7 +867,7 @@ static char *qrfTableCell(
       n += sqlite3_qrf_wcwidth(u);
       continue;
     }
-    if( c>=' ' ){
+    if( c>=' ' || c=='\033' ){
       n++;
       zOut[j++] = z[i++];
       continue;
@@ -1073,24 +1082,29 @@ static void qrfColumnar(Qrf *p){
   }
 
   /* Capture the column names as the first row of data */
-  for(i=0; i<nColumn; i++){
-    const unsigned char *zNotUsed;
-    int wx = p->actualWidth[i];
-    if( wx==0 ){
-      wx = p->spec.mxWidth;
-    }
-    if( wx<0 ) wx = -wx;
-    uz = (const unsigned char*)sqlite3_column_name(p->pStmt,i);
-    if( uz==0 ) uz = (unsigned char*)"";
-    qrfEncodeText(p, aCol[i], (const char*)uz);
-    uz = (unsigned char*)sqlite3_str_value(aCol[i]);
-    azData[i] = qrfTableCell(p, uz, &zNotUsed, wx, bw);
-    if( p->iErr ) goto qrf_column_end;
-    aiDspyWth[i] = qrfDisplayLength(azData[i]);
-    if( aiDspyWth[i]>p->actualWidth[i] ){
-      p->actualWidth[i] = aiDspyWth[i];
-    }
-    sqlite3_str_reset(aCol[i]);
+  if( p->spec.bColumnNames ){
+    int saved_eText = p->spec.eText;
+    p->spec.eText = QRF_TEXT_Off;
+    for(i=0; i<nColumn; i++){
+      const unsigned char *zNotUsed;
+      int wx = p->actualWidth[i];
+      if( wx==0 ){
+        wx = p->spec.mxWidth;
+      }
+      if( wx<0 ) wx = -wx;
+      uz = (const unsigned char*)sqlite3_column_name(p->pStmt,i);
+      if( uz==0 ) uz = (unsigned char*)"";
+      qrfEncodeText(p, aCol[i], (const char*)uz);
+      uz = (unsigned char*)sqlite3_str_value(aCol[i]);
+      azData[i] = qrfTableCell(p, uz, &zNotUsed, wx, bw);
+      if( p->iErr ) goto qrf_column_end;
+      aiDspyWth[i] = qrfDisplayLength(azData[i]);
+      if( aiDspyWth[i]>p->actualWidth[i] ){
+        p->actualWidth[i] = aiDspyWth[i];
+      }
+      sqlite3_str_reset(aCol[i]);
+    }
+    p->spec.eText = saved_eText;
   }
 
   /* Capture column content for all rows */
@@ -1172,15 +1186,17 @@ static void qrfColumnar(Qrf *p){
     case QRF_STYLE_Table: {
       colSep = " | ";
       rowSep = " |\n";
-      qrfRowSeparator(p, "+");
-      sqlite3_str_append(p->pOut, "| ", 2);
-      for(i=0; i<nColumn; i++){
-        w = p->actualWidth[i];
-        n = aiDspyWth[i];
-        sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
-        sqlite3_str_appendall(p->pOut, azData[i]);
-        sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
-        sqlite3_str_append(p->pOut, i==nColumn-1?" |\n":" | ", 3);
+      if( p->spec.bColumnNames==QRF_SW_On ){
+        qrfRowSeparator(p, "+");
+        sqlite3_str_append(p->pOut, "| ", 2);
+        for(i=0; i<nColumn; i++){
+          w = p->actualWidth[i];
+          n = aiDspyWth[i];
+          sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
+          sqlite3_str_appendall(p->pOut, azData[i]);
+          sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
+          sqlite3_str_append(p->pOut, i==nColumn-1?" |\n":" | ", 3);
+        }
       }
       qrfRowSeparator(p, "+");
       break;
@@ -1188,32 +1204,36 @@ static void qrfColumnar(Qrf *p){
     case QRF_STYLE_Markdown: {
       colSep = " | ";
       rowSep = " |\n";
-      sqlite3_str_append(p->pOut, "| ", 2);
-      for(i=0; i<nColumn; i++){
-        w = p->actualWidth[i];
-        n = aiDspyWth[i];
-        sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
-        sqlite3_str_appendall(p->pOut, azData[i]);
-        sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
-        sqlite3_str_append(p->pOut, i==nColumn-1 ? " |\n" : " | ", 3);
+      if( p->spec.bColumnNames==QRF_SW_On ){
+        sqlite3_str_append(p->pOut, "| ", 2);
+        for(i=0; i<nColumn; i++){
+          w = p->actualWidth[i];
+          n = aiDspyWth[i];
+          sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
+          sqlite3_str_appendall(p->pOut, azData[i]);
+          sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
+          sqlite3_str_append(p->pOut, i==nColumn-1 ? " |\n" : " | ", 3);
+        }
+        qrfRowSeparator(p, "|");
       }
-      qrfRowSeparator(p, "|");
       break;
     }
     case QRF_STYLE_Box: {
       colSep = " " BOX_13 " ";
       rowSep = " " BOX_13 "\n";
       qrfBoxSeparator(p, BOX_23, BOX_234, BOX_34);
-      sqlite3_str_appendall(p->pOut, BOX_13 " ");
-      for(i=0; i<nColumn; i++){
-        w = p->actualWidth[i];
-        n = aiDspyWth[i];
-        sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
-        sqlite3_str_appendall(p->pOut, azData[i]);
-        sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
-        sqlite3_str_appendall(p->pOut, i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
+      if( p->spec.bColumnNames==QRF_SW_On ){
+        sqlite3_str_appendall(p->pOut, BOX_13 " ");
+        for(i=0; i<nColumn; i++){
+          w = p->actualWidth[i];
+          n = aiDspyWth[i];
+          sqlite3_str_appendchar(p->pOut, (w-n)/2, ' ');
+          sqlite3_str_appendall(p->pOut, azData[i]);
+          sqlite3_str_appendchar(p->pOut, (w-n+1)/2, ' ');
+          sqlite3_str_appendall(p->pOut, i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
+        }
+        qrfBoxSeparator(p, BOX_123, BOX_1234, BOX_134);
       }
-      qrfBoxSeparator(p, BOX_123, BOX_1234, BOX_134);
       break;
     }
   }
@@ -1607,6 +1627,8 @@ qrf_reinit:
   if( p->spec.bTextJsonb==QRF_SW_Auto ){
     p->spec.bTextJsonb = QRF_SW_Off;
   }
+  if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = ",";
+  if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n";
 }
 
 /*
@@ -1744,6 +1766,8 @@ static void qrfOneSimpleRow(Qrf *p){
     }
     default: {  /* QRF_STYLE_List */
       if( p->nRow==0 && p->spec.bColumnNames==QRF_SW_On ){
+        int saved_eText = p->spec.eText;
+        if( p->spec.eText!=QRF_TEXT_Csv ) p->spec.eText = QRF_TEXT_Off;
         for(i=0; i<p->nCol; i++){
           const char *zCName = sqlite3_column_name(p->pStmt, i);
           if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
@@ -1751,6 +1775,7 @@ static void qrfOneSimpleRow(Qrf *p){
         }
         sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
         qrfWrite(p);
+        p->spec.eText = saved_eText;
       }
       for(i=0; i<p->nCol; i++){
         if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
index 532caceb3655459b8d3261bbfb8821ef40d27dd1..e8abbca481ca35e7fd82155e5160b9103a4e3a41 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C The\sqrf-tester.c\sprogram\swas\sremoved\sin\sthe\sprevious\scheck-in,\sbut\sthe\nmakefile\srules\sto\sbuild\sit\swere\snot.\s\sThis\scheck-in\sfixes\sthat\soversight.
-D 2025-11-05T18:35:54.490
+C Further\stesting\sand\sbug\sfixing.
+D 2025-11-05T22:33:47.393
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -417,7 +417,7 @@ F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6
 F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c
 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
 F ext/qrf/README.md 5f12f91104d5df8ba8783e2240635698569ce54935e721b3a4e93abf4e00ddcb
-F ext/qrf/qrf.c 8dd8f3245f86686ddacdf92354655dd290e5ed7668f6cf7d6bcf1f9453160062
+F ext/qrf/qrf.c 4286a30696a106a39f7fe43f65384a239157c2e1bb7e55b92600086799b5c00c
 F ext/qrf/qrf.h 98e02eded10848c42053d37e56bd15ab69cb6fe9fc1f1ca05823d9047e9c8509
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
@@ -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 45476ca0b3088504c7e296ce52b4ab0b6b9af57132abeca2867fbcf1a50f84af
+F src/tclsqlite.c be2a4b5593298d1937e7c39b94e552eb74d8ff2297db8cda9c4bac41acc67d74
 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a
 F src/test1.c f880ab766eeedf2c063662bd9538b923fd42c4341b7bfc2150a6d93ab8b9341c
 F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
@@ -1506,7 +1506,8 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c
 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
 F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
 F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd
-F test/qrf01.test 84699d3a41d371dda947a9ca994e8bc19cbc3e43b83d6716c6b43b1b6a7c1b6f
+F test/qrf01.test 12b211db9dcd5926d657b1ee64b40db059270133d8b0338d0666dbcf301ec35b
+F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
 F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
 F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a
@@ -2172,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 633fe4fe584ae14ed6ced1ae137bf96a434a012423d70f0f93876737b0ca075c
-R 70942b4daded82313531eeb35df731ae
+P d1542696409b67635d0f172042642709452118d62102ebfab2ad3ff2b5732687
+R f4a27ec41a5221a665784b11fc4b6185
 U drh
-Z 687a4d4793d1bb836e5c2deeb266cdf6
+Z c4401bada00b73794af6d52fe089dac2
 # Remove this line to create a well-formed Fossil manifest.
index d1fcb0a154c73a9c7dfe54f6d40725b1b354c9da..944ac86bc2af4381e029c7b3d5b886767e46af5c 100644 (file)
@@ -1 +1 @@
-d1542696409b67635d0f172042642709452118d62102ebfab2ad3ff2b5732687
+d6b1bac15a692a69f3707fca721b57b3b283edb3850efa34ba42b02c3669fc71
index bda4b8bd4059dc5b39cbd5a8af11613bf31c3238..7471605d86dfdf853da7183ff973304745095169 100644 (file)
@@ -2050,6 +2050,43 @@ static void DbHookCmd(
 ** Based on provided options, format the results of the SQL statement(s)
 ** provided into human-readable form using the Query Result Formatter (QRF)
 ** and return the resuling text.
+**
+** Syntax:    db format OPTIONS SQL
+**
+** OPTIONS may be:
+**
+**     -style ("auto"|"box"|"column"|...)      Output style
+**     -esc ("auto"|"off"|"ascii"|"symbol")    How to deal with ctrl chars
+**     -text ("auto"|"off"|"sql"|"csv"|...)    How to escape TEXT values
+**     -blob ("auto"|"text"|"sql"|...)         How to escape BLOB values
+**     -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?
+**     -maxwidth NUMBER                        Default column width
+**     -widths LIST-OF-NUMBERS                 Widths for individual columns
+**     -columnsep TEXT                         Column separator text
+**     -rowsep TEXT                            Row separator text
+**     -tablename TEXT                         Table name for style "insert"
+**     -null TEXT                              Text for NULL values
+**
+** A mapping from TCL "format" command options to sqlite3_qrf_spec fields
+** is below.  Use this to reference the QRF documentation:
+**
+**     TCL Option        spec field
+**     ----------        ----------
+**     -style            eStyle
+**     -esc              eEsc
+**     -text             eText
+**     -blob             eBlob
+**     -columnnames      bColumnNames
+**     -wordwrap         bWordWrap
+**     -textjsonb        bTextJsonb
+**     -maxwidth         mxWidth
+**     -widths           nWidth, aWidth
+**     -columnsep        zColumnSep
+**     -rowsep           zRowSep
+**     -tablename        zTableName
+**     -null             zNull
 */
 static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
 #ifndef SQLITE_QRF_H
@@ -2098,21 +2135,54 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
       };
       int style;
       rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles,
-                              "format style", 0, &style);
+                              "format style (-style)", 0, &style);
       if( rc ) goto format_failed;
       qrf.eStyle = aStyleMap[style];
       i++;
-    }else if( strcmp(zArg,"-null")==0 ){
-      qrf.zNull = Tcl_GetString(objv[i+1]);
-      i++;
-    }else if( strcmp(zArg,"-rowsep")==0 ){
-      qrf.zRowSep = Tcl_GetString(objv[i+1]);
+    }else if( strcmp(zArg,"-esc")==0 ){
+      static const char *azEsc[] = {
+        "ascii",        "auto",         "off",      "symbol",   0
+      };
+      static unsigned char aEscMap[] = {
+        QRF_ESC_Ascii,  QRF_ESC_Auto,   QRF_ESC_Off, QRF_ESC_Symbol
+      };
+      int esc;
+      rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azEsc,
+                              "control character escape (-esc)", 0, &esc);
+      if( rc ) goto format_failed;
+      qrf.eEsc = aEscMap[esc];
       i++;
-    }else if( strcmp(zArg,"-columnsep")==0 ){
-      qrf.zColumnSep = Tcl_GetString(objv[i+1]);
+    }else if( strcmp(zArg,"-text")==0 ){
+      static const char *azText[] = {
+        "auto",             "csv",              "html",
+        "json",             "off",              "sql",
+        "tcl",              0
+      };
+      static unsigned char aTextMap[] = {
+        QRF_TEXT_Auto,      QRF_TEXT_Csv,       QRF_TEXT_Html,
+        QRF_TEXT_Json,      QRF_TEXT_Off,       QRF_TEXT_Sql,
+        QRF_TEXT_Tcl
+      };
+      int txt;
+      rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azText,
+                              "text encoding (-text)", 0, &txt);
+      if( rc ) goto format_failed;
+      qrf.eText = aTextMap[txt];
       i++;
-    }else if( strcmp(zArg,"-tablename")==0 ){
-      qrf.zTableName = Tcl_GetString(objv[i+1]);
+    }else if( strcmp(zArg,"-blob")==0 ){
+      static const char *azBlob[] = {
+        "auto",             "hex",              "json",
+        "tcl",              "text",             "sql",      0
+      };
+      static unsigned char aBlobMap[] = {
+        QRF_BLOB_Auto,      QRF_BLOB_Hex,       QRF_BLOB_Json,
+        QRF_BLOB_Tcl,       QRF_BLOB_Text,      QRF_BLOB_Sql
+      };
+      int blob;
+      rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBlob,
+                              "BLOB encoding (-blob)", 0, &blob);
+      if( rc ) goto format_failed;
+      qrf.eBlob = aBlobMap[blob];
       i++;
     }else if( strcmp(zArg,"-columnnames")==0 ){
       int v = 0;
@@ -2179,6 +2249,18 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
         }
       }
       i++;
+    }else if( strcmp(zArg,"-columnsep")==0 ){
+      qrf.zColumnSep = Tcl_GetString(objv[i+1]);
+      i++;
+    }else if( strcmp(zArg,"-rowsep")==0 ){
+      qrf.zRowSep = Tcl_GetString(objv[i+1]);
+      i++;
+    }else if( strcmp(zArg,"-tablename")==0 ){
+      qrf.zTableName = Tcl_GetString(objv[i+1]);
+      i++;
+    }else if( strcmp(zArg,"-null")==0 ){
+      qrf.zNull = Tcl_GetString(objv[i+1]);
+      i++;
     }else{
       Tcl_AppendResult(pDb->interp, "unknown option: ", zArg, (char*)0);
       rc = TCL_ERROR;
index 64975331ca17eb807a0c03a9d8fa160e6d409330..181476fa2f8bf380af5a43c98150f31220e6d1e0 100644 (file)
@@ -31,7 +31,58 @@ do_test 1.10 {
 │ BLOB │     │ Ἀμήν  │
 └──────┴─────┴───────┘
 }
+do_test 1.11a {
+  set result "\n[db format -columnnames off {SELECT * FROM t1}]"
+} {
+┌──────┬─────┬───────┐
+│ 1    │ 2.5 │ three │
+│ BLOB │     │ Ἀμήν  │
+└──────┴─────┴───────┘
+}
+do_test 1.11b {
+  set result "\n[db format -text sql {SELECT * FROM t1}]"
+} {
+┌─────────────┬─────┬─────────┐
+│      a      │  b  │    c    │
+├─────────────┼─────┼─────────┤
+│ 1           │ 2.5 │ 'three' │
+│ x'424c4f42' │     │ 'Ἀμήν'  │
+└─────────────┴─────┴─────────┘
+}
+do_test 1.12 {
+  set result "\n[db format -text csv {SELECT * FROM t1}]"
+} {
+┌────────────────────┬─────┬────────┐
+│         a          │  b  │   c    │
+├────────────────────┼─────┼────────┤
+│ 1                  │ 2.5 │ three  │
+│ "\102\114\117\102" │     │ "Ἀμήν" │
+└────────────────────┴─────┴────────┘
+}
+do_test 1.13 {
+  set result "\n[db format -text csv -blob hex {SELECT * FROM t1}]"
+} {
+┌──────────┬─────┬────────┐
+│    a     │  b  │   c    │
+├──────────┼─────┼────────┤
+│ 1        │ 2.5 │ three  │
+│ 424c4f42 │     │ "Ἀμήν" │
+└──────────┴─────┴────────┘
+}
+do_test 1.14 {
+  catch {db format -text unk -blob hex {SELECT * FROM t1}} res
+  set res
+} {bad text encoding (-text) "unk": must be auto, csv, html, json, off, sql, or tcl}
+do_test 1.15 {
+  catch {db format -text sql -blob unk {SELECT * FROM t1}} res
+  set res
+} {bad BLOB encoding (-blob) "unk": must be auto, hex, json, tcl, text, or sql}
+do_test 1.16 {
+  catch {db format -text sql -style unk {SELECT * FROM t1}} res
+  set res
+} {bad format style (-style) "unk": must be auto, box, column, count, csv, eqp, explain, html, insert, json, line, list, markdown, quote, scanexp, or table}
 
+       
 do_test 1.20 {
   set result "\n[db format -style box {SELECT * FROM t1}]"
 } {
@@ -53,6 +104,14 @@ do_test 1.30 {
 | BLOB |     | Ἀμήν  |
 +------+-----+-------+
 }
+do_test 1.31 {
+  set result "\n[db format -style table -columnnames off {SELECT * FROM t1}]"
+} {
++------+-----+-------+
+| 1    | 2.5 | three |
+| BLOB |     | Ἀμήν  |
++------+-----+-------+
+}
 
 do_test 1.40 {
   set result "\n[db format -style column {SELECT * FROM t1}]"
@@ -79,6 +138,9 @@ do_test 1.60 {
 do_test 1.61 {
   db format -style csv -columnnames on {SELECT * FROM t1}
 } "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n"
+do_test 1.62 {
+  db format -style csv -columnnames on {SELECT a AS 'a x y', b, c FROM t1}
+} "\"a x y\",b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n"
 
 do_test 1.70 {
   set result "\n[db format -style html {SELECT * FROM t1}]"
@@ -170,6 +232,18 @@ a = BLOB
 b = (NULL)
 c = Ἀμήν
 }
+do_test 1.102 {
+  set result "\n[db format -style line -null (NULL) \
+                 -text sql {SELECT * FROM t1}]"
+} {
+a = 1
+b = 2.5
+c = 'three'
+
+a = x'424c4f42'
+b = (NULL)
+c = 'Ἀμήν'
+}
 
 do_test 1.110 {
   set result "\n[db format -style list {SELECT * FROM t1}]"
@@ -185,6 +259,14 @@ a|b|c
 BLOB||Ἀμήν
 }
 do_test 1.112 {
+  set result "\n[db format -style list -columnnames on -text sql -null NULL \
+                 {SELECT * FROM t1}]"
+} {
+a|b|c
+1|2.5|'three'
+x'424c4f42'|NULL|'Ἀμήν'
+}
+do_test 1.118 {
   set rc [catch {db format -style list -columnnames unk {SELECT * FROM t1}} res]
   lappend rc $res
 } {1 {bad -columnnames "unk": must be auto, off, or on}}
@@ -198,6 +280,12 @@ do_test 1.120 {
 | 1    | 2.5 | three |
 | BLOB |     | Ἀμήν  |
 }
+do_test 1.121 {
+  set result "\n[db format -style markdown -columnnames off {SELECT * FROM t1}]"
+} {
+| 1    | 2.5 | three |
+| BLOB |     | Ἀμήν  |
+}
 
 do_test 1.130 {
   set result "\n[db format -style quote {SELECT * FROM t1}]"
@@ -208,7 +296,7 @@ x'424c4f42',NULL,'Ἀμήν'
 do_test 1.131 {
   set result "\n[db format -style quote -columnnames on {SELECT * FROM t1}]"
 } {
-'a','b','c'
+a,b,c
 1,2.5,'three'
 x'424c4f42',NULL,'Ἀμήν'
 }
@@ -219,15 +307,111 @@ do_execsql_test 2.0 {
   INSERT INTO t1 VALUES(1,2,'The quick fox jumps over the lazy brown dog.');
 }
 do_test 2.1 {
-  set result "\n[db format -widths {10 -10 22} -wordwrap off \
+  set result "\n[db format -widths {5 -5 19} -wordwrap on \
+                 {SELECT * FROM t1}]"
+} {
+┌───────┬───────┬─────────────────────┐
+│   a   │   b   │          c          │
+├───────┼───────┼─────────────────────┤
+│ 1     │     2 │ The quick fox jumps │
+│       │       │ over the lazy brown │
+│       │       │ dog.                │
+└───────┴───────┴─────────────────────┘
+}
+do_test 2.2 {
+  set result "\n[db format -widths {5 -5 19} -wordwrap off \
+                 {SELECT * FROM t1}]"
+} {
+┌───────┬───────┬─────────────────────┐
+│   a   │   b   │          c          │
+├───────┼───────┼─────────────────────┤
+│ 1     │     2 │ The quick fox jumps │
+│       │       │ over the lazy brown │
+│       │       │ dog.                │
+└───────┴───────┴─────────────────────┘
+}
+do_test 2.3 {
+  set result "\n[db format -widths {5 -5 18} -wordwrap on \
+                 {SELECT * FROM t1}]"
+} {
+┌───────┬───────┬────────────────────┐
+│   a   │   b   │         c          │
+├───────┼───────┼────────────────────┤
+│ 1     │     2 │ The quick fox      │
+│       │       │ jumps over the     │
+│       │       │ lazy brown dog.    │
+└───────┴───────┴────────────────────┘
+}
+do_test 2.4 {
+  set result "\n[db format -widths {5 -5 -18} -wordwrap on \
+                 {SELECT * FROM t1}]"
+} {
+┌───────┬───────┬────────────────────┐
+│   a   │   b   │         c          │
+├───────┼───────┼────────────────────┤
+│ 1     │     2 │      The quick fox │
+│       │       │     jumps over the │
+│       │       │    lazy brown dog. │
+└───────┴───────┴────────────────────┘
+}
+do_test 2.5 {
+  set result "\n[db format -widths {5 -5 18} -wordwrap off \
                  {SELECT * FROM t1}]"
 } {
-┌────────────┬────────────┬────────────────────────┐
-│     a      │     b      │           c            │
-├────────────┼────────────┼────────────────────────┤
-│ 1          │          2 │ The quick fox jumps ov │
-│            │            │ er the lazy brown dog. │
-└────────────┴────────────┴────────────────────────┘
+┌───────┬───────┬─────────────────────┐
+│   a   │   b   │          c          │
+├───────┼───────┼─────────────────────┤
+│ 1     │     2 │ The quick fox jumps │
+│       │       │  over the lazy brow │
+│       │       │ n dog.              │
+└───────┴───────┴─────────────────────┘
+}
+
+
+do_execsql_test 3.0 {
+  DELETE FROM t1;
+  INSERT INTO t1 VALUES(1,2,unistr('abc\u001b[1;31m123\u001b[0mxyz'));
+}
+do_test 3.1 {
+  set result "\n[db format {SELECT * FROM t1}]"
+} {
+┌───┬───┬────────────────────────┐
+│ a │ b │           c            │
+├───┼───┼────────────────────────┤
+│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │
+└───┴───┴────────────────────────┘
+}
+do_test 3.2 {
+  set result "\n[db format -esc off {SELECT * FROM t1}]"
+  string map [list \033 X] $result
+} {
+┌───┬───┬───────────┐
+│ a │ b │     c     │
+├───┼───┼───────────┤
+│ 1 │ 2 │ abcX[1;31m123X[0mxyz │
+└───┴───┴───────────┘
+}
+do_test 3.3 {
+  set result "\n[db format -esc symbol {SELECT * FROM t1}]"
+} {
+┌───┬───┬──────────────────────┐
+│ a │ b │          c           │
+├───┼───┼──────────────────────┤
+│ 1 │ 2 │ abc␛[1;31m123␛[0mxyz │
+└───┴───┴──────────────────────┘
+}
+do_test 3.4 {
+  set result "\n[db format -esc ascii {SELECT * FROM t1}]"
+} {
+┌───┬───┬────────────────────────┐
+│ a │ b │           c            │
+├───┼───┼────────────────────────┤
+│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │
+└───┴───┴────────────────────────┘
 }
+do_test 3.5 {
+  catch {db format -esc unk {SELECT * FROM t1}} res
+  set res
+} {bad control character escape (-esc) "unk": must be ascii, auto, off, or symbol}
 
 finish_test
diff --git a/test/qrf02.test b/test/qrf02.test
new file mode 100644 (file)
index 0000000..07e1568
--- /dev/null
@@ -0,0 +1,47 @@
+# 2025-11-05
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# Test cases for the Query Result Formatter (QRF)
+#
+# These tests are for EXPLAIN and EXPLAIN QUERY PLAN formatting, the 
+# output of which can change when enhancments are made to the query
+# planner.  So expect to have to modify the expected results of these
+# test cases in the future.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix qrf02
+
+do_execsql_test 1.0 {
+  CREATE TABLE t1(a);
+  INSERT INTO t1 VALUES(1);
+}
+
+set result [db format {EXPLAIN SELECT * FROM t1}]
+do_test 1.10 {
+  set result
+} {/*addr  opcode         p1    p2    p3    p4             p5  comment      
+----  -------------  ----  ----  ----  -------------  --  -------------
+0     Init           */}
+regsub -all {\d+} $result {N} result2
+do_test 1.11 {
+  set result2
+} "/.*\nN     Rewind .*\nN       Column .*/"
+
+do_test 1.20 {
+  set result "\n[db format {EXPLAIN QUERY PLAN SELECT * FROM t1}]"
+} {
+QUERY PLAN
+`--SCAN t1
+}
+
+finish_test