]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Code in place to do column formats. Compiles, but does not work.
authordrh <>
Wed, 22 Oct 2025 18:09:54 +0000 (18:09 +0000)
committerdrh <>
Wed, 22 Oct 2025 18:09:54 +0000 (18:09 +0000)
This is an incremental check-in.

FossilOrigin-Name: a05e021354ff5868f75ac01d384c3aaaedb7c483dd8f9533f6ab9fbb66b5f077

ext/qrf/qrf.c
manifest
manifest.uuid

index 5a020a6632a3b714cc14210ebbbe02f552261291..9e7fbf59c92f5c2165a871a83ceae0e57ed6c918 100644 (file)
@@ -9,11 +9,12 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** Implementation of the Result-Format or "resfmt" utility library for SQLite.
-** See the resfmt.md documentation for additional information.
+** Implementation of the Result-Format or "qrf" utility library for SQLite.
+** See the qrf.md documentation for additional information.
 */
 #include "qrf.h"
 #include <string.h>
+#include <ctype.h>
 
 /*
 ** Private state information.  Subject to change from one release to the
@@ -28,16 +29,17 @@ struct Qrf {
   int iErr;                   /* Error code */
   int nCol;                   /* Number of output columns */
   sqlite3_int64 nRow;         /* Number of rows handled so far */
-  sqlite3_qrf_spec spec;   /* Copy of the original spec */
+  int *actualWidth;           /* Actual width of each column */
+  sqlite3_qrf_spec spec;      /* Copy of the original spec */
 };
 
 /*
 ** Set an error code and error message.
 */
-static void resfmtError(
-  Qrf *p,       /* Query result state */
-  int iCode,               /* Error code */
-  const char *zFormat,     /* Message format (or NULL) */
+static void qrfError(
+  Qrf *p,                /* Query result state */
+  int iCode,             /* Error code */
+  const char *zFormat,   /* Message format (or NULL) */
   ...
 ){
   p->iErr = iCode;
@@ -56,15 +58,25 @@ static void resfmtError(
 /*
 ** Out-of-memory error.
 */
-static void resfmtOom(Qrf *p){
-  resfmtError(p, SQLITE_NOMEM, "out of memory");
+static void qrfOom(Qrf *p){
+  qrfError(p, SQLITE_NOMEM, "out of memory");
+}
+
+/*
+** Reset the prepared statement.
+*/
+static void qrfResetStmt(Qrf *p){
+  int rc = sqlite3_reset(p->pStmt);
+  if( rc!=SQLITE_OK && p->iErr==SQLITE_OK ){
+    qrfError(p, rc, "%s", sqlite3_errmsg(p->db));
+  }
 }
 
 /*
 ** If xWrite is defined, send all content of pOut to xWrite and
 ** reset pOut.
 */
-static void resfmtWrite(Qrf *p){
+static void qrfWrite(Qrf *p){
   int n;
   if( p->spec.xWrite && (n = sqlite3_str_length(p->pOut))>0 ){
     p->spec.xWrite(p->spec.pWriteArg,
@@ -74,6 +86,160 @@ static void resfmtWrite(Qrf *p){
   }
 }
 
+/* Lookup table to estimate the number of columns consumed by a Unicode
+** character.
+*/
+static const struct {
+  unsigned char w;    /* Width of the character in columns */
+  int iFirst;         /* First character in a span having this width */
+} aQrfUWidth[] = {
+   /* {1, 0x00000}, */
+  {0, 0x00300},  {1, 0x00370},  {0, 0x00483},  {1, 0x00487},  {0, 0x00488},
+  {1, 0x0048a},  {0, 0x00591},  {1, 0x005be},  {0, 0x005bf},  {1, 0x005c0},
+  {0, 0x005c1},  {1, 0x005c3},  {0, 0x005c4},  {1, 0x005c6},  {0, 0x005c7},
+  {1, 0x005c8},  {0, 0x00600},  {1, 0x00604},  {0, 0x00610},  {1, 0x00616},
+  {0, 0x0064b},  {1, 0x0065f},  {0, 0x00670},  {1, 0x00671},  {0, 0x006d6},
+  {1, 0x006e5},  {0, 0x006e7},  {1, 0x006e9},  {0, 0x006ea},  {1, 0x006ee},
+  {0, 0x0070f},  {1, 0x00710},  {0, 0x00711},  {1, 0x00712},  {0, 0x00730},
+  {1, 0x0074b},  {0, 0x007a6},  {1, 0x007b1},  {0, 0x007eb},  {1, 0x007f4},
+  {0, 0x00901},  {1, 0x00903},  {0, 0x0093c},  {1, 0x0093d},  {0, 0x00941},
+  {1, 0x00949},  {0, 0x0094d},  {1, 0x0094e},  {0, 0x00951},  {1, 0x00955},
+  {0, 0x00962},  {1, 0x00964},  {0, 0x00981},  {1, 0x00982},  {0, 0x009bc},
+  {1, 0x009bd},  {0, 0x009c1},  {1, 0x009c5},  {0, 0x009cd},  {1, 0x009ce},
+  {0, 0x009e2},  {1, 0x009e4},  {0, 0x00a01},  {1, 0x00a03},  {0, 0x00a3c},
+  {1, 0x00a3d},  {0, 0x00a41},  {1, 0x00a43},  {0, 0x00a47},  {1, 0x00a49},
+  {0, 0x00a4b},  {1, 0x00a4e},  {0, 0x00a70},  {1, 0x00a72},  {0, 0x00a81},
+  {1, 0x00a83},  {0, 0x00abc},  {1, 0x00abd},  {0, 0x00ac1},  {1, 0x00ac6},
+  {0, 0x00ac7},  {1, 0x00ac9},  {0, 0x00acd},  {1, 0x00ace},  {0, 0x00ae2},
+  {1, 0x00ae4},  {0, 0x00b01},  {1, 0x00b02},  {0, 0x00b3c},  {1, 0x00b3d},
+  {0, 0x00b3f},  {1, 0x00b40},  {0, 0x00b41},  {1, 0x00b44},  {0, 0x00b4d},
+  {1, 0x00b4e},  {0, 0x00b56},  {1, 0x00b57},  {0, 0x00b82},  {1, 0x00b83},
+  {0, 0x00bc0},  {1, 0x00bc1},  {0, 0x00bcd},  {1, 0x00bce},  {0, 0x00c3e},
+  {1, 0x00c41},  {0, 0x00c46},  {1, 0x00c49},  {0, 0x00c4a},  {1, 0x00c4e},
+  {0, 0x00c55},  {1, 0x00c57},  {0, 0x00cbc},  {1, 0x00cbd},  {0, 0x00cbf},
+  {1, 0x00cc0},  {0, 0x00cc6},  {1, 0x00cc7},  {0, 0x00ccc},  {1, 0x00cce},
+  {0, 0x00ce2},  {1, 0x00ce4},  {0, 0x00d41},  {1, 0x00d44},  {0, 0x00d4d},
+  {1, 0x00d4e},  {0, 0x00dca},  {1, 0x00dcb},  {0, 0x00dd2},  {1, 0x00dd5},
+  {0, 0x00dd6},  {1, 0x00dd7},  {0, 0x00e31},  {1, 0x00e32},  {0, 0x00e34},
+  {1, 0x00e3b},  {0, 0x00e47},  {1, 0x00e4f},  {0, 0x00eb1},  {1, 0x00eb2},
+  {0, 0x00eb4},  {1, 0x00eba},  {0, 0x00ebb},  {1, 0x00ebd},  {0, 0x00ec8},
+  {1, 0x00ece},  {0, 0x00f18},  {1, 0x00f1a},  {0, 0x00f35},  {1, 0x00f36},
+  {0, 0x00f37},  {1, 0x00f38},  {0, 0x00f39},  {1, 0x00f3a},  {0, 0x00f71},
+  {1, 0x00f7f},  {0, 0x00f80},  {1, 0x00f85},  {0, 0x00f86},  {1, 0x00f88},
+  {0, 0x00f90},  {1, 0x00f98},  {0, 0x00f99},  {1, 0x00fbd},  {0, 0x00fc6},
+  {1, 0x00fc7},  {0, 0x0102d},  {1, 0x01031},  {0, 0x01032},  {1, 0x01033},
+  {0, 0x01036},  {1, 0x01038},  {0, 0x01039},  {1, 0x0103a},  {0, 0x01058},
+  {1, 0x0105a},  {2, 0x01100},  {0, 0x01160},  {1, 0x01200},  {0, 0x0135f},
+  {1, 0x01360},  {0, 0x01712},  {1, 0x01715},  {0, 0x01732},  {1, 0x01735},
+  {0, 0x01752},  {1, 0x01754},  {0, 0x01772},  {1, 0x01774},  {0, 0x017b4},
+  {1, 0x017b6},  {0, 0x017b7},  {1, 0x017be},  {0, 0x017c6},  {1, 0x017c7},
+  {0, 0x017c9},  {1, 0x017d4},  {0, 0x017dd},  {1, 0x017de},  {0, 0x0180b},
+  {1, 0x0180e},  {0, 0x018a9},  {1, 0x018aa},  {0, 0x01920},  {1, 0x01923},
+  {0, 0x01927},  {1, 0x01929},  {0, 0x01932},  {1, 0x01933},  {0, 0x01939},
+  {1, 0x0193c},  {0, 0x01a17},  {1, 0x01a19},  {0, 0x01b00},  {1, 0x01b04},
+  {0, 0x01b34},  {1, 0x01b35},  {0, 0x01b36},  {1, 0x01b3b},  {0, 0x01b3c},
+  {1, 0x01b3d},  {0, 0x01b42},  {1, 0x01b43},  {0, 0x01b6b},  {1, 0x01b74},
+  {0, 0x01dc0},  {1, 0x01dcb},  {0, 0x01dfe},  {1, 0x01e00},  {0, 0x0200b},
+  {1, 0x02010},  {0, 0x0202a},  {1, 0x0202f},  {0, 0x02060},  {1, 0x02064},
+  {0, 0x0206a},  {1, 0x02070},  {0, 0x020d0},  {1, 0x020f0},  {2, 0x02329},
+  {1, 0x0232b},  {2, 0x02e80},  {0, 0x0302a},  {2, 0x03030},  {1, 0x0303f},
+  {2, 0x03040},  {0, 0x03099},  {2, 0x0309b},  {1, 0x0a4d0},  {0, 0x0a806},
+  {1, 0x0a807},  {0, 0x0a80b},  {1, 0x0a80c},  {0, 0x0a825},  {1, 0x0a827},
+  {2, 0x0ac00},  {1, 0x0d7a4},  {2, 0x0f900},  {1, 0x0fb00},  {0, 0x0fb1e},
+  {1, 0x0fb1f},  {0, 0x0fe00},  {2, 0x0fe10},  {1, 0x0fe1a},  {0, 0x0fe20},
+  {1, 0x0fe24},  {2, 0x0fe30},  {1, 0x0fe70},  {0, 0x0feff},  {2, 0x0ff00},
+  {1, 0x0ff61},  {2, 0x0ffe0},  {1, 0x0ffe7},  {0, 0x0fff9},  {1, 0x0fffc},
+  {0, 0x10a01},  {1, 0x10a04},  {0, 0x10a05},  {1, 0x10a07},  {0, 0x10a0c},
+  {1, 0x10a10},  {0, 0x10a38},  {1, 0x10a3b},  {0, 0x10a3f},  {1, 0x10a40},
+  {0, 0x1d167},  {1, 0x1d16a},  {0, 0x1d173},  {1, 0x1d183},  {0, 0x1d185},
+  {1, 0x1d18c},  {0, 0x1d1aa},  {1, 0x1d1ae},  {0, 0x1d242},  {1, 0x1d245},
+  {2, 0x20000},  {1, 0x2fffe},  {2, 0x30000},  {1, 0x3fffe},  {0, 0xe0001},
+  {1, 0xe0002},  {0, 0xe0020},  {1, 0xe0080},  {0, 0xe0100},  {1, 0xe01f0}
+};
+
+/*
+** Return an estimate of the width, in columns, for the single Unicode
+** character c.  For normal characters, the answer is always 1.  But the
+** estimate might be 0 or 2 for zero-width and double-width characters.
+**
+** Different display devices display unicode using different widths.  So
+** it is impossible to know that true display width with 100% accuracy.
+** Inaccuracies in the width estimates might cause columns to be misaligned.
+** Unfortunately, there is nothing we can do about that.
+*/
+int sqlite3_qrf_wcwidth(int c){
+  int iFirst, iLast;
+
+  /* Fast path for common characters */
+  if( c<=0x300 ) return 1;
+
+  /* The general case */
+  iFirst = 0;
+  iLast = sizeof(aQrfUWidth)/sizeof(aQrfUWidth[0]) - 1;
+  while( iFirst<iLast-1 ){
+    int iMid = (iFirst+iLast)/2;
+    int cMid = aQrfUWidth[iMid].iFirst;
+    if( cMid < c ){
+      iFirst = iMid;
+    }else if( cMid > c ){
+      iLast = iMid - 1;
+    }else{
+      return aQrfUWidth[iMid].w;
+    }
+  }
+  if( aQrfUWidth[iLast].iFirst > c ) return aQrfUWidth[iFirst].w;
+  return aQrfUWidth[iLast].w;
+}
+
+/*
+** Compute the value and length of a multi-byte UTF-8 character that
+** begins at z[0]. Return the length.  Write the Unicode value into *pU.
+**
+** This routine only works for *multi-byte* UTF-8 characters.  It does
+** not attempt to detect illegal characters.
+*/
+int sqlite3_qrf_decode_utf8(const unsigned char *z, int *pU){
+  if( (z[0] & 0xe0)==0xc0 && (z[1] & 0xc0)==0x80 ){
+    *pU = ((z[0] & 0x1f)<<6) | (z[1] & 0x3f);
+    return 2;
+  }
+  if( (z[0] & 0xf0)==0xe0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 ){
+    *pU = ((z[0] & 0x0f)<<12) | ((z[1] & 0x3f)<<6) | (z[2] & 0x3f);
+    return 3;
+  }
+  if( (z[0] & 0xf8)==0xf0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80
+   && (z[3] & 0xc0)==0x80
+  ){
+    *pU = ((z[0] & 0x0f)<<18) | ((z[1] & 0x3f)<<12) | ((z[2] & 0x3f))<<6
+                              | (z[3] & 0x3f);
+    return 4;
+  }
+  *pU = 0;
+  return 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){
+  int n = 0;
+  while( *z ){
+    if( (0x80&z[0])==0 ){
+      n++;
+      z++;
+    }else{
+      int u = 0;
+      int len = sqlite3_qrf_decode_utf8((const unsigned char*)z, &u);
+      z += len;
+      n += sqlite3_qrf_wcwidth(u);
+    }
+  }
+  return n;
+}
+
 /*
 ** Escape the input string if it is needed and in accordance with
 ** eEscape, which is either QRF_ESC_Ascii or QRF_ESC_Symbol.
@@ -89,7 +255,7 @@ static void resfmtWrite(Qrf *p){
 ** The caller is responsible for freeing *ppFree if it is non-NULL in order
 ** to reclaim memory.
 */
-static void resfmtEscape(
+static void qrfEscape(
   int eEscape,            /* QRF_ESC_Ascii or QRF_ESC_Symbol */
   sqlite3_str *pStr,      /* String to be escaped */
   int iStart              /* Begin escapding on this byte of pStr */
@@ -159,7 +325,7 @@ static void resfmtEscape(
 ** If a field contains any character identified by a 1 in the following
 ** array, then the string must be quoted for CSV.
 */
-static const char resfmtCsvQuote[] = {
+static const char qrfCsvQuote[] = {
   1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 1, 1, 1, 1, 1,
   1, 1, 1, 1, 1, 1, 1, 1,   1, 1, 1, 1, 1, 1, 1, 1,
   1, 0, 1, 0, 0, 0, 0, 1,   0, 0, 0, 0, 0, 0, 0, 0,
@@ -179,27 +345,27 @@ static const char resfmtCsvQuote[] = {
 };
 
 /*
-** Encode text appropriately and append it to p->pOut.
+** Encode text appropriately and append it to pOut.
 */
-static void resfmtEncodeText(Qrf *p, const char *zTxt){
-  int iStart = sqlite3_str_length(p->pOut);
+static void qrfEncodeText(Qrf *p, sqlite3_str *pOut, const char *zTxt){
+  int iStart = sqlite3_str_length(pOut);
   switch( p->spec.eQuote ){
     case QRF_TXT_Sql: {
-      sqlite3_str_appendf(p->pOut, "%Q", zTxt);
+      sqlite3_str_appendf(pOut, "%Q", zTxt);
       break;
     }
     case QRF_TXT_Csv: {
       unsigned int i;
       for(i=0; zTxt[i]; i++){
-        if( resfmtCsvQuote[((const unsigned char*)zTxt)[i]] ){
+        if( qrfCsvQuote[((const unsigned char*)zTxt)[i]] ){
           i = 0;
           break;
         }
       }
       if( i==0 || strstr(zTxt, p->spec.zColumnSep)!=0 ){
-        sqlite3_str_appendf(p->pOut, "\"%w\"", zTxt);
+        sqlite3_str_appendf(pOut, "\"%w\"", zTxt);
       }else{
-        sqlite3_str_appendall(p->pOut, zTxt);
+        sqlite3_str_appendall(pOut, zTxt);
       }
       break;
     }
@@ -214,14 +380,14 @@ static void resfmtEncodeText(Qrf *p, const char *zTxt){
           i++;
         }
         if( i>0 ){
-          sqlite3_str_append(p->pOut, (const char*)z, i);
+          sqlite3_str_append(pOut, (const char*)z, i);
         }
         switch( z[i] ){
-          case '>':   sqlite3_str_append(p->pOut, "&lt;", 4);   break;
-          case '&':   sqlite3_str_append(p->pOut, "&amp;", 5);  break;
-          case '<':   sqlite3_str_append(p->pOut, "&lt;", 4);   break;
-          case '"':   sqlite3_str_append(p->pOut, "&quot;", 6); break;
-          case '\'':  sqlite3_str_append(p->pOut, "&#39;", 5);  break;
+          case '>':   sqlite3_str_append(pOut, "&lt;", 4);   break;
+          case '&':   sqlite3_str_append(pOut, "&amp;", 5);  break;
+          case '<':   sqlite3_str_append(pOut, "&lt;", 4);   break;
+          case '"':   sqlite3_str_append(pOut, "&quot;", 6); break;
+          case '\'':  sqlite3_str_append(pOut, "&#39;", 5);  break;
           default:    i--;
         }
         z += i + 1;
@@ -231,49 +397,49 @@ static void resfmtEncodeText(Qrf *p, const char *zTxt){
     case QRF_TXT_Tcl:
     case QRF_TXT_Json: {
       const unsigned char *z = (const unsigned char*)zTxt;
-      sqlite3_str_append(p->pOut, "\"", 1);
+      sqlite3_str_append(pOut, "\"", 1);
       while( *z ){
         unsigned int i;
         for(i=0; z[i]>=0x20 && z[i]!='\\' && z[i]!='"'; i++){}
         if( i>0 ){
-          sqlite3_str_append(p->pOut, (const char*)z, i);
+          sqlite3_str_append(pOut, (const char*)z, i);
         }
         switch( z[i] ){
-          case '"':   sqlite3_str_append(p->pOut, "\\\"", 2);  break;
-          case '\\':  sqlite3_str_append(p->pOut, "\\\\", 2);  break;
-          case '\b':  sqlite3_str_append(p->pOut, "\\b", 2);   break;
-          case '\f':  sqlite3_str_append(p->pOut, "\\f", 2);   break;
-          case '\n':  sqlite3_str_append(p->pOut, "\\n", 2);   break;
-          case '\r':  sqlite3_str_append(p->pOut, "\\r", 2);   break;
-          case '\t':  sqlite3_str_append(p->pOut, "\\t", 2);   break;
+          case '"':   sqlite3_str_append(pOut, "\\\"", 2);  break;
+          case '\\':  sqlite3_str_append(pOut, "\\\\", 2);  break;
+          case '\b':  sqlite3_str_append(pOut, "\\b", 2);   break;
+          case '\f':  sqlite3_str_append(pOut, "\\f", 2);   break;
+          case '\n':  sqlite3_str_append(pOut, "\\n", 2);   break;
+          case '\r':  sqlite3_str_append(pOut, "\\r", 2);   break;
+          case '\t':  sqlite3_str_append(pOut, "\\t", 2);   break;
           default: {
             if( p->spec.eQuote==QRF_TXT_Json ){
-              sqlite3_str_appendf(p->pOut, "\\u%04x", z[i]);
+              sqlite3_str_appendf(pOut, "\\u%04x", z[i]);
             }else{
-              sqlite3_str_appendf(p->pOut, "\\%03o", z[i]);
+              sqlite3_str_appendf(pOut, "\\%03o", z[i]);
             }
             break;
           }
         }
         z += i + 1;
       }
-      sqlite3_str_append(p->pOut, "\"", 1);
+      sqlite3_str_append(pOut, "\"", 1);
       break;
     }
     default: {
-      sqlite3_str_appendall(p->pOut, zTxt);
+      sqlite3_str_appendall(pOut, zTxt);
       break;
     }
   }
   if( p->spec.eEscape!=QRF_ESC_Off ){
-    resfmtEscape(p->spec.eEscape, p->pOut, iStart);
+    qrfEscape(p->spec.eEscape, pOut, iStart);
   }
 }
 
 /*
-** Render value pVal into p->pOut
+** Render value pVal into pOut
 */
-static void resfmtRenderValue(Qrf *p, int iCol){
+static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
   if( p->spec.xRender ){
     sqlite3_value *pVal;
     char *z;
@@ -281,23 +447,23 @@ static void resfmtRenderValue(Qrf *p, int iCol){
     z = p->spec.xRender(p->spec.pRenderArg, pVal);
     sqlite3_value_free(pVal);
     if( z ){
-      sqlite3_str_appendall(p->pOut, z);
+      sqlite3_str_appendall(pOut, z);
       sqlite3_free(z);
       return;
     }
   }
   switch( sqlite3_column_type(p->pStmt,iCol) ){
     case SQLITE_INTEGER: {
-      sqlite3_str_appendf(p->pOut, "%lld", sqlite3_column_int64(p->pStmt,iCol));
+      sqlite3_str_appendf(pOut, "%lld", sqlite3_column_int64(p->pStmt,iCol));
       break;
     }
     case SQLITE_FLOAT: {
       if( p->spec.zFloatFmt ){
         double r = sqlite3_column_double(p->pStmt,iCol);
-        sqlite3_str_appendf(p->pOut, p->spec.zFloatFmt, r);
+        sqlite3_str_appendf(pOut, p->spec.zFloatFmt, r);
       }else{
         const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
-        sqlite3_str_appendall(p->pOut, zTxt);
+        sqlite3_str_appendall(pOut, zTxt);
       }
       break;
     }
@@ -311,16 +477,16 @@ static void resfmtRenderValue(Qrf *p, int iCol){
           char *zVal;
           const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
           if( p->spec.eBlob==QRF_BLOB_Sql ){
-            sqlite3_str_append(p->pOut, "x'", 2);
+            sqlite3_str_append(pOut, "x'", 2);
           }
-          iStart = sqlite3_str_length(p->pOut);
-          sqlite3_str_appendchar(p->pOut, nBlob, ' ');
-          sqlite3_str_appendchar(p->pOut, nBlob, ' ');
+          iStart = sqlite3_str_length(pOut);
+          sqlite3_str_appendchar(pOut, nBlob, ' ');
+          sqlite3_str_appendchar(pOut, nBlob, ' ');
           if( p->spec.eBlob==QRF_BLOB_Sql ){
-            sqlite3_str_appendchar(p->pOut, 1, '\'');
+            sqlite3_str_appendchar(pOut, 1, '\'');
           }
-          if( sqlite3_str_errcode(p->pOut) ) return;
-          zVal = sqlite3_str_value(p->pOut);
+          if( sqlite3_str_errcode(pOut) ) return;
+          zVal = sqlite3_str_value(pOut);
           for(i=0, j=iStart; i<nBlob; i++, j+=2){
             unsigned char c = a[i];
             zVal[j] = "0123456789abcdef"[(c>>4)&0xf];
@@ -336,14 +502,14 @@ static void resfmtRenderValue(Qrf *p, int iCol){
           char *zVal;
           const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
           int szC = p->spec.eBlob==QRF_BLOB_Json ? 6 : 4;
-          sqlite3_str_append(p->pOut, "\"", 1);
-          iStart = sqlite3_str_length(p->pOut);
+          sqlite3_str_append(pOut, "\"", 1);
+          iStart = sqlite3_str_length(pOut);
           for(i=szC; i>0; i--){
-            sqlite3_str_appendchar(p->pOut, nBlob, ' ');
+            sqlite3_str_appendchar(pOut, nBlob, ' ');
           }
-          sqlite3_str_appendchar(p->pOut, 1, '"');
-          if( sqlite3_str_errcode(p->pOut) ) return;
-          zVal = sqlite3_str_value(p->pOut);
+          sqlite3_str_appendchar(pOut, 1, '"');
+          if( sqlite3_str_errcode(pOut) ) return;
+          zVal = sqlite3_str_value(pOut);
           for(i=0, j=iStart; i<nBlob; i++, j+=szC){
             unsigned char c = a[i];
             zVal[j] = '\\';
@@ -363,37 +529,579 @@ static void resfmtRenderValue(Qrf *p, int iCol){
         }
         default: {
           const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
-          resfmtEncodeText(p, zTxt);
+          qrfEncodeText(p, pOut, zTxt);
         }
       }
       break;
     }
     case SQLITE_NULL: {
-      sqlite3_str_appendall(p->pOut, p->spec.zNull);
+      sqlite3_str_appendall(pOut, p->spec.zNull);
       break;
     }
     case SQLITE_TEXT: {
       const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
-      resfmtEncodeText(p, zTxt);
+      qrfEncodeText(p, pOut, zTxt);
+      break;
+    }
+  }
+}
+
+/*
+** 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
+** as \n, \t, \f, or \r.
+**
+** Compute characters to display on the first line of z[].  Stop at the
+** first \r, \n, or \f.  Expand \t into spaces.  Return a copy (obtained
+** from sqlite3_malloc()) of that first line.  The caller is responsible
+** for eventually freeing that line of text.
+**
+** Write anything to display on the next line into *pzTail.  If this is
+** the last line, write a NULL into *pzTail. (*pzTail is not allocated.)
+*/
+static char *qrfTableCell(
+  Qrf *p,                        /* To access display settings */
+  const unsigned char *z,        /* Input text to be transformed */
+  const unsigned char **pzTail,  /* OUT: Tail of the input for next line */
+  int mxWidth,                   /* Max width.  0 means no limit */
+  int bWordWrap                  /* If true, avoid breaking mid-word */
+){
+  int i;                 /* Input bytes consumed */
+  int j;                 /* Output bytes generated */
+  int k;                 /* Input bytes to be displayed */
+  int n;                 /* Output column number */
+  unsigned char *zOut;   /* Output text */
+
+  if( z==0 ){
+    *pzTail = 0;
+    return 0;
+  }
+  if( mxWidth<0 ) mxWidth = -mxWidth;
+  if( mxWidth==0 ) mxWidth = 1000000;
+  i = j = n = 0;
+  while( n<mxWidth ){
+    unsigned char c = z[i];
+    if( c>=0xc0 ){
+      int u;
+      int len = sqlite3_qrf_decode_utf8(&z[i], &u);
+      i += len;
+      j += len;
+      n += sqlite3_qrf_wcwidth(u);
+      continue;
+    }
+    if( c>=' ' ){
+      n++;
+      i++;
+      j++;
+      continue;
+    }
+    if( c==0 || c=='\n' || (c=='\r' && z[i+1]=='\n') ) break;
+    if( c=='\t' ){
+      do{
+        n++;
+        j++;
+      }while( (n&7)!=0 && n<mxWidth );
+      i++;
+      continue;
+    }
+    if( c==0x1b && (k = qrfIsVt100(&z[i]))>0 ){
+      i += k;
+      j += k;
+    }else{
+      n++;
+      j += 3;
+      i++;
+    }
+  }
+  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;
+    }
+    if( k<=i/2 ){
+      for(k=i; k>i/2; k--){
+        if( isalnum(z[k-1])!=isalnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
+      }
+    }
+    if( k<=i/2 ){
+      k = i;
+    }else{
+      i = k;
+      while( z[i]==' ' ) i++;
+    }
+  }else{
+    k = 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;
+  }else if( z[i]==0 || z[i+1]==0 ){
+    *pzTail = 0;
+  }else{
+    *pzTail = &z[i+1];
+  }
+  zOut = sqlite3_malloc64( j+1 );
+  if( zOut==0 ){
+    qrfOom(p);
+    return 0;
+  }
+  i = j = n = 0;
+  while( i<k ){
+    unsigned char c = z[i];
+    if( c>=0xc0 ){
+      int u;
+      int len = sqlite3_qrf_decode_utf8(&z[i], &u);
+      do{ zOut[j++] = z[i++]; }while( (--len)>0 );
+      n += sqlite3_qrf_wcwidth(u);
+      continue;
+    }
+    if( c>=' ' ){
+      n++;
+      zOut[j++] = z[i++];
+      continue;
+    }
+    if( c==0 ) break;
+    if( z[i]=='\t' ){
+      do{
+        n++;
+        zOut[j++] = ' ';
+      }while( (n&7)!=0 && n<mxWidth );
+      i++;
+      continue;
+    }
+    i++;
+  }
+  zOut[j] = 0;
+  return (char*)zOut;
+}
+
+/*
+** Store string zUtf to pOut as w characters.  If w is negative,
+** then right-justify the text.  W is the width in display characters, not
+** in bytes.  Double-width unicode characters count as two characters.
+** VT100 escape sequences count as zero.  And so forth.
+*/
+static void qrfWidthPrint(Qrf *p, sqlite3_str *pOut, int w, const char *zUtf){
+  const unsigned char *a = (const unsigned char*)zUtf;
+  static const int mxW = 10000000;
+  unsigned char c;
+  int i = 0;
+  int n = 0;
+  int k;
+  int aw;
+  if( w<-mxW ){
+    w = -mxW;
+  }else if( w>mxW ){
+    w= mxW;
+  }
+  aw = w<0 ? -w : w;
+  if( zUtf==0 ) zUtf = "";
+  while( (c = a[i])!=0 ){
+    if( (c&0xc0)==0xc0 ){
+      int u;
+      int len = sqlite3_qrf_decode_utf8(a+i, &u);
+      int x = sqlite3_qrf_wcwidth(u);
+      if( x+n>aw ){
+        break;
+      }
+      i += len;
+      n += x;
+    }else if( c==0x1b && (k = qrfIsVt100(&a[i]))>0 ){
+      i += k;       
+    }else if( n>=aw ){
+      break;
+    }else{
+      n++;
+      i++;
+    }
+  }
+  if( n>=aw ){
+    sqlite3_str_append(pOut, zUtf, i);
+  }else if( w<0 ){
+    if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' ');
+    sqlite3_str_append(pOut, zUtf, i);
+  }else{
+    sqlite3_str_append(pOut, zUtf, i);
+    if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' ');
+  }
+}
+
+/*
+** Print a markdown or table-style row separator using ascii-art
+*/
+static void qrfRowSeparator(Qrf *p, const char *zSep){
+  int i;
+  if( p->nCol>0 ){
+    int nSep = (int)(strlen(zSep)&0x3fffffff);
+    sqlite3_str_append(p->pOut, zSep, nSep);
+    sqlite3_str_appendchar(p->pOut, p->actualWidth[0]+2, '-');
+    for(i=1; i<p->nCol; i++){
+      sqlite3_str_append(p->pOut, zSep, nSep);
+      sqlite3_str_appendchar(p->pOut, p->actualWidth[i]+2, '-');
+    }
+    sqlite3_str_append(p->pOut, zSep, nSep);
+  }
+  sqlite3_str_append(p->pOut, "\n", 1);
+}
+
+
+/*
+** UTF8 box-drawing characters.  Imagine box lines like this:
+**
+**           1
+**           |
+**       4 --+-- 2
+**           |
+**           3
+**
+** Each box characters has between 2 and 4 of the lines leading from
+** the center.  The characters are here identified by the numbers of
+** their corresponding lines.
+*/
+#define BOX_24   "\342\224\200"  /* U+2500 --- */
+#define BOX_13   "\342\224\202"  /* U+2502  |  */
+#define BOX_23   "\342\224\214"  /* U+250c  ,- */
+#define BOX_34   "\342\224\220"  /* U+2510 -,  */
+#define BOX_12   "\342\224\224"  /* U+2514  '- */
+#define BOX_14   "\342\224\230"  /* U+2518 -'  */
+#define BOX_123  "\342\224\234"  /* U+251c  |- */
+#define BOX_134  "\342\224\244"  /* U+2524 -|  */
+#define BOX_234  "\342\224\254"  /* U+252c -,- */
+#define BOX_124  "\342\224\264"  /* U+2534 -'- */
+#define BOX_1234 "\342\224\274"  /* U+253c -|- */
+
+/* Draw horizontal line N characters long using unicode box
+** characters
+*/
+static void qrfBoxLine(sqlite3_str *pOut, int N){
+  const char zDash[] =
+      BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24
+      BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24;
+  const int nDash = sizeof(zDash) - 1;
+  N *= 3;
+  while( N>nDash ){
+    sqlite3_str_append(pOut, zDash, nDash);
+    N -= nDash;
+  }
+  sqlite3_str_append(pOut, zDash, N);
+}
+
+/*
+** Draw a horizontal separator for a QRF_MODE_Box table.
+*/
+static void qrfBoxSeparator(
+  Qrf *p,
+  const char *zSep1,
+  const char *zSep2,
+  const char *zSep3
+){
+  int i;
+  if( p->nCol>0 ){
+    sqlite3_str_appendall(p->pOut, zSep1);
+    qrfBoxLine(p->pOut, p->actualWidth[0]+2);
+    for(i=1; i<p->nCol; i++){
+      sqlite3_str_appendall(p->pOut, zSep2);
+      qrfBoxLine(p->pOut, p->actualWidth[i]+2);
+    }
+    sqlite3_str_appendall(p->pOut, zSep3);
+  }
+  sqlite3_str_append(p->pOut, "\n", 1);
+}
+
+
+/*
+** Columnar modes require that the entire query be evaluated first, with
+** results written into memory, so that we can compute appropriate column
+** widths.
+*/
+static void qrfColumnar(Qrf *p){
+  sqlite3_int64 nRow = 0;        /* Rows displayed.  nRow>=p->nRow */
+  char **azData = 0;             /* Text for all cells */
+  int *aiDspyWth = 0;            /* Display width of each cell */
+  sqlite3_int64 nAlloc = 0;      /* Slots allocated for azData[], aiDspyWth[] */
+  char *abRowDiv = 0;            /* Row is a continuation or is complete */
+  const unsigned char *uz;
+  const char *z;
+  sqlite3_str **aCol = 0;        /* Temporary storage for column data */
+  sqlite3_int64 i, j, k, nData;
+  int rc, nTotal, w, n;
+  const char *colSep = 0;
+  const char *rowSep = 0;
+  const unsigned char **azNextLine = 0;
+  int bNextLine = 0;
+  int bMultiLineRowExists = 0;
+  int bw = p->spec.bWordWrap;
+  int nColumn = p->nCol;
+
+  rc = sqlite3_step(p->pStmt);
+  if( rc!=SQLITE_ROW || nColumn==0 ){
+    return;   /* No output */
+  }
+
+  /* Initialize data structures */
+  nAlloc = nColumn*4;
+  azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
+  if( azData==0 ){ qrfOom(p); goto qrf_column_end; }
+  aiDspyWth = sqlite3_malloc64( nAlloc*sizeof(int) );
+  if( aiDspyWth==0 ){ qrfOom(p); goto qrf_column_end; }
+  azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
+  if( azNextLine==0 ){ qrfOom(p); goto qrf_column_end; }
+  memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
+  aCol = sqlite3_malloc64( nColumn*sizeof(aCol[0]) );
+  if( aCol==0 ){ qrfOom(p); goto qrf_column_end; }
+  for(i=0; i<nColumn; i++){
+    aCol[i] = sqlite3_str_new(p->db);
+    if( aCol[i]==0 ){ qrfOom(p); goto qrf_column_end; }
+  }
+  abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
+  if( abRowDiv==0 ){ qrfOom(p); goto qrf_column_end; }
+  p->actualWidth = sqlite3_malloc64( sizeof(int)*nColumn );
+  if( p->actualWidth==0 ){ qrfOom(p); goto qrf_column_end; }
+  for(i=0; i<p->spec.nWidth && i<nColumn; i++){
+    int w = p->spec.aWidth[i];
+    if( w<0 ) w = -w;
+    p->actualWidth[i] = w;
+  }
+  while( i<nColumn ){
+    p->actualWidth[i++] = 0;
+  }
+
+  /* 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]);
+  }
+
+  /* Capture column content for all rows */
+  k = nColumn;
+  do{
+    p->nRow++;
+    do{
+      int useNextLine = bNextLine;
+      bNextLine = 0;
+      if( (nRow+2)*nColumn >= nAlloc ){
+        char **azNewData;
+        char *abNewDiv;
+        int *aiNewWidth;
+        
+        nAlloc *= 2;
+        azNewData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
+        if( azNewData==0 ){ qrfOom(p); goto qrf_column_end; }
+        azData = azNewData;
+        abNewDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
+        if( abNewDiv==0 ){ qrfOom(p); goto qrf_column_end; }
+        abRowDiv = abNewDiv;
+        aiNewWidth = sqlite3_realloc64(aiDspyWth, nAlloc*sizeof(int));
+        if( aiNewWidth==0 ){ qrfOom(p); goto qrf_column_end; }
+        aiDspyWth = aiNewWidth;
+      }
+      abRowDiv[nRow] = 1;
+      nRow++;
+      for(i=0; i<nColumn; i++){
+        int wx = i<p->spec.nWidth ? p->spec.aWidth[i] : 0;
+        if( wx==0 ){
+          wx = p->spec.mxWidth;
+        }
+        if( wx<0 ) wx = -wx;
+        if( useNextLine ){
+          uz = azNextLine[i];
+          if( uz==0 ) uz = (unsigned char*)"";
+        }else{
+          sqlite3_str_reset(aCol[i]);
+          qrfRenderValue(p, aCol[i], i);
+          uz = (unsigned char*)sqlite3_str_value(aCol[i]);
+          if( uz==0 ){ qrfOom(p); goto qrf_column_end; }
+        }
+        azData[k] = qrfTableCell(p, uz, &azNextLine[i], wx, bw);
+        if( p->iErr ) goto qrf_column_end;
+        aiDspyWth[k] = qrfDisplayLength(azData[k]);
+        if( aiDspyWth[k]>p->actualWidth[i] ){
+          p->actualWidth[i] = aiDspyWth[k];
+        }
+        if( azNextLine[i] ){
+          bNextLine = 1;
+          abRowDiv[nRow-1] = 0;
+          bMultiLineRowExists = 1;
+        }
+      }
+    }while( bNextLine );
+  }while( sqlite3_step(p->pStmt)==SQLITE_ROW );
+  if( sqlite3_is_interrupted(p->db) ) goto qrf_column_end;
+
+  /* Generate the column titles */
+  switch( p->spec.eFormat ){
+    case QRF_MODE_Column: {
+      colSep = "  ";
+      rowSep = "\n";
+      if( p->spec.bShowCNames ){
+        for(i=0; i<nColumn; i++){
+          w = p->actualWidth[i];
+          if( p->spec.aWidth[i]<0 ) w = -w;
+          qrfWidthPrint(p, p->pOut, w, azData[i]);
+          sqlite3_str_append(p->pOut, i==nColumn-1?"\n":"  ", 1);
+        }
+        for(i=0; i<nColumn; i++){
+          sqlite3_str_appendchar(p->pOut, p->actualWidth[i], '-');
+          sqlite3_str_append(p->pOut, i==nColumn-1?"\n":"  ", 1);
+        }
+      }
+      break;
+    }
+    case QRF_MODE_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);
+      }
+      qrfRowSeparator(p, "+");
+      break;
+    }
+    case QRF_MODE_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);
+      }
+      qrfRowSeparator(p, "|");
+      break;
+    }
+    case QRF_MODE_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" ");
+      }
+      qrfBoxSeparator(p, BOX_123, BOX_1234, BOX_134);
       break;
     }
   }
+
+  /* Render the body of the table */
+  nTotal = nColumn*(nRow+1);
+  for(i=nColumn, j=0; i<nTotal; i++, j++){
+    if( j==0 && p->spec.eFormat!=QRF_MODE_Column ){
+      sqlite3_str_appendall(p->pOut,
+             p->spec.eFormat==QRF_MODE_Box ? BOX_13" " : "| ");
+    }
+    z = azData[i];
+    if( z==0 ) z = "";
+    w = p->actualWidth[j];
+    if( p->spec.aWidth[j]<0 ) w = -w;
+    qrfWidthPrint(p, p->pOut, w, z);
+    if( j==nColumn-1 ){
+      sqlite3_str_appendall(p->pOut, rowSep);
+      if( bMultiLineRowExists && abRowDiv[i/nColumn-1] && i+1<nTotal ){
+        switch( p->spec.eFormat ){
+          case QRF_MODE_Table:
+            qrfRowSeparator(p, "+");
+            break;
+          case QRF_MODE_Box:
+            qrfBoxSeparator(p, BOX_123, BOX_1234, BOX_134);
+            break;
+          case QRF_MODE_Column:
+            sqlite3_str_append(p->pOut, "\n", 1);
+            break;
+        }
+      }
+      j = -1;
+      qrfWrite(p);
+      if( sqlite3_is_interrupted(p->db) ) break;
+    }else{
+      sqlite3_str_appendall(p->pOut, colSep);
+    }
+  }
+  if( p->spec.eFormat==QRF_MODE_Table ){
+    qrfRowSeparator(p, "+");
+  }else if( p->spec.eFormat==QRF_MODE_Box ){
+    qrfBoxSeparator(p, BOX_12, BOX_124, BOX_14);
+  }
+
+qrf_column_end:
+  /* Free allocated memory */
+  if( aCol ){
+    for(i=0; i<nColumn; i++){
+      sqlite3_free(sqlite3_str_finish(aCol[i]));
+    }
+    sqlite3_free(aCol);
+  }
+  sqlite3_free(azNextLine);
+  sqlite3_free(aiDspyWth);
+  nData = (nRow+1)*nColumn;
+  for(i=0; i<nData; i++){
+    sqlite3_free(azData[i]);
+  }
+  sqlite3_free(azData);
+  
+  return;
 }
 
 /*
 ** Initialize the internal Qrf object.
 */
-static void resfmtInitialize(
-  Qrf *p,                 /* State object to be initialized */
-  sqlite3_stmt *pStmt,               /* Query whose output to be formatted */
-  const sqlite3_qrf_spec *pSpec,  /* Format specification */
-  char **pzErr                       /* Write errors here */
+static void qrfInitialize(
+  Qrf *p,                        /* State object to be initialized */
+  sqlite3_stmt *pStmt,           /* Query whose output to be formatted */
+  const sqlite3_qrf_spec *pSpec, /* Format specification */
+  char **pzErr                   /* Write errors here */
 ){
-  size_t sz;                  /* Size of pSpec[], based on pSpec->iVersion */
+  size_t sz;                     /* Size of pSpec[], based on pSpec->iVersion */
   memset(p, 0, sizeof(*p));
   p->pzErr = pzErr;
   if( pSpec->iVersion!=1 ){
-    resfmtError(p, SQLITE_ERROR,
+    qrfError(p, SQLITE_ERROR,
        "unusable sqlite3_qrf_spec.iVersion (%d)",
        pSpec->iVersion);
     return;
@@ -402,7 +1110,7 @@ static void resfmtInitialize(
   p->db = sqlite3_db_handle(pStmt);
   p->pOut = sqlite3_str_new(p->db);
   if( p->pOut==0 ){
-    resfmtOom(p);
+    qrfOom(p);
     return;
   }
   p->iErr = 0;
@@ -417,7 +1125,7 @@ static void resfmtInitialize(
       case QRF_TXT_Csv:  p->spec.eBlob = QRF_BLOB_Tcl;  break;
       case QRF_TXT_Tcl:  p->spec.eBlob = QRF_BLOB_Tcl;  break;
       case QRF_TXT_Json: p->spec.eBlob = QRF_BLOB_Json; break;
-      default:            p->spec.eBlob = QRF_BLOB_Text; break;
+      default:           p->spec.eBlob = QRF_BLOB_Text; break;
     }
   }
   switch( p->spec.eFormat ){
@@ -432,7 +1140,7 @@ static void resfmtInitialize(
 /*
 ** Render a single row of output.
 */
-static void resfmtDoOneRow(Qrf *p){
+static void qrfOneSimpleRow(Qrf *p){
   int i;
   switch( p->spec.eFormat ){
     case QRF_MODE_Off:
@@ -445,17 +1153,17 @@ static void resfmtDoOneRow(Qrf *p){
         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);
-          resfmtEncodeText(p, zCName);
+          qrfEncodeText(p, p->pOut, zCName);
         }
         sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
-        resfmtWrite(p);
+        qrfWrite(p);
       }
       for(i=0; i<p->nCol; i++){
         if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
-        resfmtRenderValue(p, i);
+        qrfRenderValue(p, p->pOut, i);
       }
       sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
-      resfmtWrite(p);
+      qrfWrite(p);
       break;
     }
   }
@@ -465,11 +1173,11 @@ static void resfmtDoOneRow(Qrf *p){
 /*
 ** Finish rendering the results
 */
-static void resfmtFinalize(Qrf *p){
+static void qrfFinalize(Qrf *p){
   switch( p->spec.eFormat ){
     case QRF_MODE_Count: {
       sqlite3_str_appendf(p->pOut, "%lld\n", p->nRow);
-      resfmtWrite(p);
+      qrfWrite(p);
       break;
     }
   }
@@ -478,6 +1186,7 @@ static void resfmtFinalize(Qrf *p){
   }else if( p->pOut ){
     sqlite3_free(sqlite3_str_finish(p->pOut));
   }
+  if( p->actualWidth ) sqlite3_free(p->actualWidth);
 }
 
 /*
@@ -495,16 +1204,27 @@ int sqlite3_format_query_result(
 
   if( pStmt==0 ) return SQLITE_OK;       /* No-op */
   if( pSpec==0 ) return SQLITE_MISUSE;
-  resfmtInitialize(&qrf, pStmt, pSpec, pzErr);
-  while( qrf.iErr==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-    resfmtDoOneRow(&qrf);
-  }
-  if( qrf.iErr==SQLITE_OK ){
-    int rc = sqlite3_reset(qrf.pStmt);
-    if( rc!=SQLITE_OK ){
-      resfmtError(&qrf, rc, "%s", sqlite3_errmsg(qrf.db));
+  qrfInitialize(&qrf, pStmt, pSpec, pzErr);
+  switch( qrf.spec.eFormat ){
+    case QRF_MODE_Box:
+    case QRF_MODE_Column:
+    case QRF_MODE_Markdown: 
+    case QRF_MODE_Table: {
+      /* Columnar modes require that the entire query be evaluated and the
+      ** results stored in memory, so that we can compute column widths */
+      qrfColumnar(&qrf);
+      break;
+    }
+    default: {
+      /* Non-columnar modes where the output can occur after each row
+      ** of result is received */
+      while( qrf.iErr==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+        qrfOneSimpleRow(&qrf);
+      }
+      break;
     }
   }
-  resfmtFinalize(&qrf);
+  qrfResetStmt(&qrf);
+  qrfFinalize(&qrf);
   return qrf.iErr;
 }
index 8a59437516326d2565823b6536aaf6b5da72568e..1a581d66c78b8b818dd87b148333f94e76de8339 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Merge\sva_list\smisuse\sfix.
-D 2025-10-22T12:59:34.472
+C Code\sin\splace\sto\sdo\scolumn\sformats.\s\sCompiles,\sbut\sdoes\snot\swork.\nThis\sis\san\sincremental\scheck-in.
+D 2025-10-22T18:09:54.467
 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/qrf-tester.c 59cb5b1ed889ba57836d0fbb07b583fdfdfe409b1344f4f119fc13dff3a7356a
-F ext/qrf/qrf.c aea2ed76f530818706ab99cc7b8bddc05725b581116dbfac87428215b65ea49d
+F ext/qrf/qrf.c 8bd0adcf1eea3eb10d57efbbbc4dd25fc3783cd857db9543648177d0920db6aa
 F ext/qrf/qrf.h e9585fbadc45e8182faf208a0f86984d5130d89742364424fd1c39f12457f013
 F ext/qrf/qrf.md 4eea619191dab7bbf483eff3fe3b074a07d7c8c50bc86070a4485797d386d1ff
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
@@ -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 8646a2ee224819cf459989aa03bd1b9e1a77fc1f8bee8fd45dba778c63467d61 3d044121768548df59a94bbbe4ee360d6464d1416a5cbe7437f4333b06496381
-R a0cefa8d831fc9721f1ccf4dcb767699
+P e33ca68fee5bb6c5195f5a8fef231dcf08433806f5f44b855c2c670d37df6ac7
+R 4d8b08236a565a7b1259c528870b0576
 U drh
-Z a74d0a3608476365ed4fc9d5191c691c
+Z c5eef447fa054545ce6feb7b549f988b
 # Remove this line to create a well-formed Fossil manifest.
index 6c3f3c2154881a2136feb3450a21f48253e49d08..70d8b6fd417c79e88bcacba9f1a021a7cc329928 100644 (file)
@@ -1 +1 @@
-e33ca68fee5bb6c5195f5a8fef231dcf08433806f5f44b855c2c670d37df6ac7
+a05e021354ff5868f75ac01d384c3aaaedb7c483dd8f9533f6ab9fbb66b5f077