]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Fix various bugs and compiler warnings. All tests now passing on linux, mac,
authordrh <>
Fri, 14 Nov 2025 13:07:45 +0000 (13:07 +0000)
committerdrh <>
Fri, 14 Nov 2025 13:07:45 +0000 (13:07 +0000)
and windows.  More testing needed, though.

FossilOrigin-Name: 2220cb70c2f1ee30dcdf917a20feacdfcb3789433d0645fea626fd4c5cf0d099

1  2 
ext/qrf/qrf.c
manifest
manifest.uuid
src/shell.c.in
test/qrf01.test
test/shell1.test
test/shellB.test

diff --cc ext/qrf/qrf.c
index 8338061ea73b9041cdfcdf11e2c367a145a55fab,0000000000000000000000000000000000000000..2646b8689e8dd8aee229ef608c3f9417c2efd96b
mode 100644,000000..100644
--- /dev/null
@@@ -1,2235 -1,0 +1,2266 @@@
- #include <ctype.h>
 +/*
 +** 2025-10-20
 +**
 +** 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.
 +**
 +*************************************************************************
 +** Implementation of the Result-Format or "qrf" utility library for SQLite.
 +** See the qrf.md documentation for additional information.
 +*/
 +#ifndef SQLITE_QRF_H
 +#include "qrf.h"
 +#endif
 +#include <string.h>
-   if( bWrap && z[i]!=0 && !isspace(z[i]) && isalnum(c)==isalnum(z[i]) ){
 +#include <assert.h>
 +
 +typedef sqlite3_int64 i64;
 +
 +/* A single line in the EQP output */
 +typedef struct qrfEQPGraphRow qrfEQPGraphRow;
 +struct qrfEQPGraphRow {
 +  int iEqpId;            /* ID for this row */
 +  int iParentId;         /* ID of the parent row */
 +  qrfEQPGraphRow *pNext; /* Next row in sequence */
 +  char zText[1];         /* Text to display for this row */
 +};
 +
 +/* All EQP output is collected into an instance of the following */
 +typedef struct qrfEQPGraph qrfEQPGraph;
 +struct qrfEQPGraph {
 +  qrfEQPGraphRow *pRow;  /* Linked list of all rows of the EQP output */
 +  qrfEQPGraphRow *pLast; /* Last element of the pRow list */
 +  char zPrefix[100];     /* Graph prefix */
 +};
 +
 +/*
 +** Private state information.  Subject to change from one release to the
 +** next.
 +*/
 +typedef struct Qrf Qrf;
 +struct Qrf {
 +  sqlite3_stmt *pStmt;        /* The statement whose output is to be rendered */
 +  sqlite3 *db;                /* The corresponding database connection */
 +  sqlite3_stmt *pJTrans;      /* JSONB to JSON translator statement */
 +  char **pzErr;               /* Write error message here, if not NULL */
 +  sqlite3_str *pOut;          /* Accumulated output */
 +  int iErr;                   /* Error code */
 +  int nCol;                   /* Number of output columns */
 +  int expMode;                /* Original sqlite3_stmt_isexplain() plus 1 */
 +  int mxWidth;                /* Screen width */
 +  int mxHeight;               /* nLineLimit */
 +  union {
 +    struct {                  /* Content for QRF_STYLE_Line */
 +      int mxColWth;             /* Maximum display width of any column */
 +      const char **azCol;       /* Names of output columns (MODE_Line) */
 +    } sLine;
 +    qrfEQPGraph *pGraph;      /* EQP graph (Eqp, Stats, and StatsEst) */
 +    struct {                  /* Content for QRF_STYLE_Explain */
 +      int nIndent;              /* Slots allocated for aiIndent */
 +      int iIndent;              /* Current slot */
 +      int *aiIndent;            /* Indentation for each opcode */
 +    } sExpln;
 +  } u;
 +  sqlite3_int64 nRow;         /* Number of rows handled so far */
 +  int *actualWidth;           /* Actual width of each column */
 +  sqlite3_qrf_spec spec;      /* Copy of the original spec */
 +};
 +
++/*
++** Data for substitute ctype.h functions.  Used for x-platform
++** consistency and so that '_' is counted as an alphabetic
++** character.
++**
++**    0x01 -  space
++**    0x02 -  digit
++**    0x04 -  alphabetic, including '_'
++*/
++static const char qrfCType[] = {
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
++  0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
++  4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 4,
++  0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
++  4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
++  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
++};
++#define qrfSpace(x) ((qrfCType[(unsigned char)x]&1)!=0)
++#define qrfDigit(x) ((qrfCType[(unsigned char)x]&2)!=0)
++#define qrfAlpha(x) ((qrfCType[(unsigned char)x]&4)!=0)
++#define qrfAlnum(x) ((qrfCType[(unsigned char)x]&6)!=0)
++
 +/*
 +** Set an error code and error message.
 +*/
 +static void qrfError(
 +  Qrf *p,                /* Query result state */
 +  int iCode,             /* Error code */
 +  const char *zFormat,   /* Message format (or NULL) */
 +  ...
 +){
 +  p->iErr = iCode;
 +  if( p->pzErr!=0 ){
 +    sqlite3_free(*p->pzErr);
 +    *p->pzErr = 0;
 +    if( zFormat ){
 +      va_list ap;
 +      va_start(ap, zFormat);
 +      *p->pzErr = sqlite3_vmprintf(zFormat, ap);
 +      va_end(ap);
 +    }
 +  }
 +}
 +
 +/*
 +** Out-of-memory error.
 +*/
 +static void qrfOom(Qrf *p){
 +  qrfError(p, SQLITE_NOMEM, "out of memory");
 +}
 +
 +
 +
 +/*
 +** Add a new entry to the EXPLAIN QUERY PLAN data
 +*/
 +static void qrfEqpAppend(Qrf *p, int iEqpId, int p2, const char *zText){
 +  qrfEQPGraphRow *pNew;
 +  sqlite3_int64 nText;
 +  if( zText==0 ) return;
 +  if( p->u.pGraph==0 ){
 +    p->u.pGraph = sqlite3_malloc64( sizeof(qrfEQPGraph) );
 +    if( p->u.pGraph==0 ){
 +      qrfOom(p);
 +      return;
 +    }
 +    memset(p->u.pGraph, 0, sizeof(qrfEQPGraph) );
 +  }
 +  nText = strlen(zText);
 +  pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
 +  if( pNew==0 ){
 +    qrfOom(p);
 +    return;
 +  }
 +  pNew->iEqpId = iEqpId;
 +  pNew->iParentId = p2;
 +  memcpy(pNew->zText, zText, nText+1);
 +  pNew->pNext = 0;
 +  if( p->u.pGraph->pLast ){
 +    p->u.pGraph->pLast->pNext = pNew;
 +  }else{
 +    p->u.pGraph->pRow = pNew;
 +  }
 +  p->u.pGraph->pLast = pNew;
 +}
 +
 +/*
 +** Free and reset the EXPLAIN QUERY PLAN data that has been collected
 +** in p->u.pGraph.
 +*/
 +static void qrfEqpReset(Qrf *p){
 +  qrfEQPGraphRow *pRow, *pNext;
 +  if( p->u.pGraph ){
 +    for(pRow = p->u.pGraph->pRow; pRow; pRow = pNext){
 +      pNext = pRow->pNext;
 +      sqlite3_free(pRow);
 +    }
 +    sqlite3_free(p->u.pGraph);
 +    p->u.pGraph = 0;
 +  }
 +}
 +
 +/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
 +** pOld, or return the first such line if pOld is NULL
 +*/
 +static qrfEQPGraphRow *qrfEqpNextRow(Qrf *p, int iEqpId, qrfEQPGraphRow *pOld){
 +  qrfEQPGraphRow *pRow = pOld ? pOld->pNext : p->u.pGraph->pRow;
 +  while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
 +  return pRow;
 +}
 +
 +/* Render a single level of the graph that has iEqpId as its parent.  Called
 +** recursively to render sublevels.
 +*/
 +static void qrfEqpRenderLevel(Qrf *p, int iEqpId){
 +  qrfEQPGraphRow *pRow, *pNext;
 +  i64 n = strlen(p->u.pGraph->zPrefix);
 +  char *z;
 +  for(pRow = qrfEqpNextRow(p, iEqpId, 0); pRow; pRow = pNext){
 +    pNext = qrfEqpNextRow(p, iEqpId, pRow);
 +    z = pRow->zText;
 +    sqlite3_str_appendf(p->pOut, "%s%s%s\n", p->u.pGraph->zPrefix,
 +                            pNext ? "|--" : "`--", z);
 +    if( n<(i64)sizeof(p->u.pGraph->zPrefix)-7 ){
 +      memcpy(&p->u.pGraph->zPrefix[n], pNext ? "|  " : "   ", 4);
 +      qrfEqpRenderLevel(p, pRow->iEqpId);
 +      p->u.pGraph->zPrefix[n] = 0;
 +    }
 +  }
 +}
 +
 +/*
 +** Display and reset the EXPLAIN QUERY PLAN data
 +*/
 +static void qrfEqpRender(Qrf *p, i64 nCycle){
 +  qrfEQPGraphRow *pRow;
 +  if( p->u.pGraph!=0 && (pRow = p->u.pGraph->pRow)!=0 ){
 +    if( pRow->zText[0]=='-' ){
 +      if( pRow->pNext==0 ){
 +        qrfEqpReset(p);
 +        return;
 +      }
 +      sqlite3_str_appendf(p->pOut, "%s\n", pRow->zText+3);
 +      p->u.pGraph->pRow = pRow->pNext;
 +      sqlite3_free(pRow);
 +    }else if( nCycle>0 ){
 +      sqlite3_str_appendf(p->pOut, "QUERY PLAN (cycles=%lld [100%%])\n",nCycle);
 +    }else{
 +      sqlite3_str_appendall(p->pOut, "QUERY PLAN\n");
 +    }
 +    p->u.pGraph->zPrefix[0] = 0;
 +    qrfEqpRenderLevel(p, 0);
 +    qrfEqpReset(p);
 +  }
 +}
 +
 +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
 +/*
 +** Helper function for qrfExpStats().
 +**
 +*/
 +static int qrfStatsHeight(sqlite3_stmt *p, int iEntry){
 +  int iPid = 0;
 +  int ret = 1;
 +  sqlite3_stmt_scanstatus_v2(p, iEntry,
 +      SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
 +  );
 +  while( iPid!=0 ){
 +    int ii;
 +    for(ii=0; 1; ii++){
 +      int iId;
 +      int res;
 +      res = sqlite3_stmt_scanstatus_v2(p, ii,
 +          SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId
 +      );
 +      if( res ) break;
 +      if( iId==iPid ){
 +        sqlite3_stmt_scanstatus_v2(p, ii,
 +            SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
 +        );
 +      }
 +    }
 +    ret++;
 +  }
 +  return ret;
 +}
 +#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */
 +
 +
 +/*
 +** Generate ".scanstatus est" style of EQP output.
 +*/
 +static void qrfEqpStats(Qrf *p){
 +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
 +  qrfError(p, SQLITE_ERROR, "not available in this build");
 +#else
 +  static const int f = SQLITE_SCANSTAT_COMPLEX;
 +  sqlite3_stmt *pS = p->pStmt;
 +  int i = 0;
 +  i64 nTotal = 0;
 +  int nWidth = 0;
 +  sqlite3_str *pLine = sqlite3_str_new(p->db);
 +  sqlite3_str *pStats = sqlite3_str_new(p->db);
 +  qrfEqpReset(p);
 +
 +  for(i=0; 1; i++){
 +    const char *z = 0;
 +    int n = 0;
 +    if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
 +      break;
 +    }
 +    n = (int)strlen(z) + qrfStatsHeight(pS,i)*3;
 +    if( n>nWidth ) nWidth = n;
 +  }
 +  nWidth += 4;
 +
 +  sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
 +  for(i=0; 1; i++){
 +    i64 nLoop = 0;
 +    i64 nRow = 0;
 +    i64 nCycle = 0;
 +    int iId = 0;
 +    int iPid = 0;
 +    const char *zo = 0;
 +    const char *zName = 0;
 +    double rEst = 0.0;
 +
 +    if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){
 +      break;
 +    }
 +    sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_EST,f,(void*)&rEst);
 +    sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop);
 +    sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow);
 +    sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle);
 +    sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId);
 +    sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
 +    sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
 +
 +    if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
 +      const char *zSp = "";
 +      double rpl;
 +      sqlite3_str_reset(pStats);
 +      if( nCycle>=0 && nTotal>0 ){
 +        sqlite3_str_appendf(pStats, "cycles=%lld [%d%%]",
 +            nCycle, ((nCycle*100)+nTotal/2) / nTotal
 +        );
 +        zSp = " ";
 +      }
 +      if( nLoop>=0 ){
 +        sqlite3_str_appendf(pStats, "%sloops=%lld", zSp, nLoop);
 +        zSp = " ";
 +      }
 +      if( nRow>=0 ){
 +        sqlite3_str_appendf(pStats, "%srows=%lld", zSp, nRow);
 +        zSp = " ";
 +      }
 +
 +      if( p->spec.eStyle==QRF_STYLE_StatsEst ){
 +        rpl = (double)nRow / (double)nLoop;
 +        sqlite3_str_appendf(pStats, "%srpl=%.1f est=%.1f", zSp, rpl, rEst);
 +      }
 +
 +      sqlite3_str_appendf(pLine,
 +          "% *s (%s)", -1*(nWidth-qrfStatsHeight(pS,i)*3), zo,
 +          sqlite3_str_value(pStats)
 +      );
 +      sqlite3_str_reset(pStats);
 +      qrfEqpAppend(p, iId, iPid, sqlite3_str_value(pLine));
 +      sqlite3_str_reset(pLine);
 +    }else{
 +      qrfEqpAppend(p, iId, iPid, zo);
 +    }
 +  }
 +  sqlite3_free(sqlite3_str_finish(pLine));
 +  sqlite3_free(sqlite3_str_finish(pStats));
 +#endif
 +}
 +
 +
 +/*
 +** 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 qrfWrite(Qrf *p){
 +  int n;
 +  if( p->spec.xWrite && (n = sqlite3_str_length(p->pOut))>0 ){
 +    int rc = p->spec.xWrite(p->spec.pWriteArg,
 +                 sqlite3_str_value(p->pOut),
 +                 (sqlite3_int64)n);
 +    sqlite3_str_reset(p->pOut);
 +    if( rc ){
 +      qrfError(p, rc, "Failed to write %d bytes of output", n);
 +    }
 +  }
 +}
 +
 +/* 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;
 +}
 +
 +/*
 +** 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 *zIn){
 +  const unsigned char *z = (const unsigned char*)zIn;
 +  int n = 0;
 +  while( *z ){
 +    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(z, &u);
 +      z += len;
 +      n += sqlite3_qrf_wcwidth(u);
 +    }
 +  }
 +  return n;
 +}
 +
 +/*
 +** Return the display width of the longest line of text
 +** in the (possibly) multi-line input string zIn[0..nByte].
 +** zIn[] is not necessarily zero-terminated.  Take
 +** into account tab characters, zero- and double-width
 +** characters, CR and NL, and VT100 escape codes.
 +**
 +** Write the number of newlines into *pnNL.  So, *pnNL will
 +** return 0 if everything fits on one line, or positive it
 +** it will need to be split.
 +*/
 +static int qrfDisplayWidth(const char *zIn, sqlite3_int64 nByte, int *pnNL){
 +  const unsigned char *z = (const unsigned char*)zIn;
 +  const unsigned char *zEnd = &z[nByte];
 +  int mx = 0;
 +  int n = 0;
 +  int nNL = 0;
 +  while( z<zEnd ){
 +    if( z[0]<' ' ){
 +      int k;
 +      if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
 +        z += k;
 +      }else{
 +        if( z[0]=='\t' ){
 +          n = (n+8)&~7;
 +        }else if( z[0]=='\n' || z[0]=='\r' ){
 +          nNL++;
 +          if( n>mx ) mx = n;
 +          n = 0;
 +        }
 +        z++;
 +      }
 +    }else if( (0x80&z[0])==0 ){
 +      n++;
 +      z++;
 +    }else{
 +      int u = 0;
 +      int len = sqlite3_qrf_decode_utf8(z, &u);
 +      z += len;
 +      n += sqlite3_qrf_wcwidth(u);
 +    }
 +  }
 +  if( mx>n ) n = mx;
 +  if( pnNL ) *pnNL = nNL;
 +  return n;
 +}
 +
 +/*
 +** Escape the input string if it is needed and in accordance with
 +** eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol.
 +**
 +** Escaping is needed if the string contains any control characters
 +** other than \t, \n, and \r\n
 +**
 +** If no escaping is needed (the common case) then set *ppOut to NULL
 +** and return 0.  If escaping is needed, write the escaped string into
 +** memory obtained from sqlite3_malloc64() and make *ppOut point to that
 +** memory and return 0.  If an error occurs, return non-zero.
 +**
 +** The caller is responsible for freeing *ppFree if it is non-NULL in order
 +** to reclaim memory.
 +*/
 +static void qrfEscape(
 +  int eEsc,            /* QRF_ESC_Ascii or QRF_ESC_Symbol */
 +  sqlite3_str *pStr,      /* String to be escaped */
 +  int iStart              /* Begin escapding on this byte of pStr */
 +){
 +  sqlite3_int64 i, j;     /* Loop counters */
 +  sqlite3_int64 sz;       /* Size of the string prior to escaping */
 +  sqlite3_int64 nCtrl = 0;/* Number of control characters to escape */
 +  unsigned char *zIn;     /* Text to be escaped */
 +  unsigned char c;        /* A single character of the text */
 +  unsigned char *zOut;    /* Where to write the results */
 +
 +  /* Find the text to be escaped */
 +  zIn = (unsigned char*)sqlite3_str_value(pStr);
 +  if( zIn==0 ) return;
 +  zIn += iStart;
 +
 +  /* Count the control characters */
 +  for(i=0; (c = zIn[i])!=0; i++){
 +    if( c<=0x1f
 +     && c!='\t'
 +     && c!='\n'
 +     && (c!='\r' || zIn[i+1]!='\n')
 +    ){
 +      nCtrl++;
 +    }
 +  }
 +  if( nCtrl==0 ) return;  /* Early out if no control characters */
 +
 +  /* Make space to hold the escapes.  Copy the original text to the end
 +  ** of the available space. */
 +  sz = sqlite3_str_length(pStr) - iStart;
 +  if( eEsc==QRF_ESC_Symbol ) nCtrl *= 2;
 +  sqlite3_str_appendchar(pStr, nCtrl, ' ');
 +  zOut = (unsigned char*)sqlite3_str_value(pStr);
 +  if( zOut==0 ) return;
 +  zOut += iStart;
 +  zIn = zOut + nCtrl;
 +  memmove(zIn,zOut,sz);
 +
 +  /* Convert the control characters */
 +  for(i=j=0; (c = zIn[i])!=0; i++){
 +    if( c>0x1f
 +     || c=='\t'
 +     || c=='\n'
 +     || (c=='\r' && zIn[i+1]=='\n')
 +    ){
 +      continue;
 +    }
 +    if( i>0 ){
 +      memmove(&zOut[j], zIn, i);
 +      j += i;
 +    }
 +    zIn += i+1;
 +    i = -1;
 +    if( eEsc==QRF_ESC_Symbol ){
 +      zOut[j++] = 0xe2;
 +      zOut[j++] = 0x90;
 +      zOut[j++] = 0x80+c;
 +    }else{
 +      zOut[j++] = '^';
 +      zOut[j++] = 0x40+c;
 +    }
 +  }
 +}
 +
 +/*
 +** 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 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,
 +  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
 +  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
 +  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
 +  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0,
 +  0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 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,
 +  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, 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, 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,
 +};
 +
 +/*
 +** Encode text appropriately and append it to pOut.
 +*/
 +static void qrfEncodeText(Qrf *p, sqlite3_str *pOut, const char *zTxt){
 +  int iStart = sqlite3_str_length(pOut);
 +  switch( p->spec.eText ){
 +    case QRF_TEXT_Sql: {
 +      if( p->spec.eEsc==QRF_ESC_Off ){
 +        sqlite3_str_appendf(pOut, "%Q", zTxt);
 +      }else{
 +        sqlite3_str_appendf(pOut, "%#Q", zTxt);
 +      }
 +      break;
 +    }
 +    case QRF_TEXT_Csv: {
 +      unsigned int i;
 +      for(i=0; zTxt[i]; i++){
 +        if( qrfCsvQuote[((const unsigned char*)zTxt)[i]] ){
 +          i = 0;
 +          break;
 +        }
 +      }
 +      if( i==0 || strstr(zTxt, p->spec.zColumnSep)!=0 ){
 +        sqlite3_str_appendf(pOut, "\"%w\"", zTxt);
 +      }else{
 +        sqlite3_str_appendall(pOut, zTxt);
 +      }
 +      break;
 +    }
 +    case QRF_TEXT_Html: {
 +      const unsigned char *z = (const unsigned char*)zTxt;
 +      while( *z ){
 +        unsigned int i = 0;
 +        unsigned char c;
 +        while( (c=z[i])>'>'
 +            || (c && c!='<' && c!='>' && c!='&' && c!='\"' && c!='\'')
 +        ){
 +          i++;
 +        }
 +        if( i>0 ){
 +          sqlite3_str_append(pOut, (const char*)z, i);
 +        }
 +        switch( z[i] ){
 +          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;
 +      }
 +      break;
 +    }
 +    case QRF_TEXT_Tcl:
 +    case QRF_TEXT_Json: {
 +      const unsigned char *z = (const unsigned char*)zTxt;
 +      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(pOut, (const char*)z, i);
 +        }
 +        if( z[i]==0 ) break;
 +        switch( z[i] ){
 +          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.eText==QRF_TEXT_Json ){
 +              sqlite3_str_appendf(pOut, "\\u%04x", z[i]);
 +            }else{
 +              sqlite3_str_appendf(pOut, "\\%03o", z[i]);
 +            }
 +            break;
 +          }
 +        }
 +        z += i + 1;
 +      }
 +      sqlite3_str_append(pOut, "\"", 1);
 +      break;
 +    }
 +    default: {
 +      sqlite3_str_appendall(pOut, zTxt);
 +      break;
 +    }
 +  }
 +  if( p->spec.eEsc!=QRF_ESC_Off ){
 +    qrfEscape(p->spec.eEsc, pOut, iStart);
 +  }
 +}
 +
 +/*
 +** The current iCol-th column of p->pStmt is known to be a BLOB.  Check
 +** to see if that BLOB is really a JSONB blob.  If it is, then translate
 +** it into a text JSON representation and return a pointer to that text JSON.
 +**
 +** The memory used to hold the JSON text is managed internally by the
 +** "p" object and is overwritten and/or deallocated upon the next call
 +** to this routine (with the same p argument) or when the p object is
 +** finailized.
 +*/
 +static const char *qrfJsonbToJson(Qrf *p, int iCol){
 +  int nByte;
 +  const void *pBlob;
 +  int rc;
 +  if( p->pJTrans==0 ){
 +    sqlite3 *db;
 +    rc = sqlite3_open(":memory:",&db);
 +    if( rc ){
 +      sqlite3_close(db);
 +      return 0;
 +    }
 +    rc = sqlite3_prepare_v2(db, "SELECT json(?1)", -1, &p->pJTrans, 0);
 +    if( rc ){
 +      sqlite3_finalize(p->pJTrans);
 +      p->pJTrans = 0;
 +      sqlite3_close(db);
 +      return 0;
 +    }
 +  }else{
 +    sqlite3_reset(p->pJTrans);
 +  }
 +  nByte = sqlite3_column_bytes(p->pStmt, iCol);
 +  pBlob = sqlite3_column_blob(p->pStmt, iCol);
 +  sqlite3_bind_blob(p->pJTrans, 1, (void*)pBlob, nByte, SQLITE_STATIC);
 +  rc = sqlite3_step(p->pJTrans);
 +  if( rc==SQLITE_ROW ){
 +    return (const char*)sqlite3_column_text(p->pJTrans, 0);
 +  }else{
 +    return 0;
 +  }
 +}
 +
 +/*
 +** Render value pVal into pOut
 +*/
 +static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
 +  if( p->spec.xRender ){
 +    sqlite3_value *pVal;
 +    char *z;
 +    pVal = sqlite3_value_dup(sqlite3_column_value(p->pStmt,iCol));
 +    z = p->spec.xRender(p->spec.pRenderArg, pVal);
 +    sqlite3_value_free(pVal);
 +    if( z ){
 +      sqlite3_str_appendall(pOut, z);
 +      sqlite3_free(z);
 +      return;
 +    }
 +  }
 +  switch( sqlite3_column_type(p->pStmt,iCol) ){
 +    case SQLITE_INTEGER: {
 +      sqlite3_str_appendf(pOut, "%lld", sqlite3_column_int64(p->pStmt,iCol));
 +      break;
 +    }
 +    case SQLITE_FLOAT: {
 +      const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
 +      sqlite3_str_appendall(pOut, zTxt);
 +      break;
 +    }
 +    case SQLITE_BLOB: {
 +      if( p->spec.bTextJsonb==QRF_Yes ){
 +        const char *zJson = qrfJsonbToJson(p, iCol);
 +        if( zJson ){
 +          qrfEncodeText(p, pOut, zJson);
 +          break;
 +        }
 +      }
 +      switch( p->spec.eBlob ){
 +        case QRF_BLOB_Hex:
 +        case QRF_BLOB_Sql: {
 +          int iStart;
 +          int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
 +          int i, j;
 +          char *zVal;
 +          const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
 +          if( p->spec.eBlob==QRF_BLOB_Sql ){
 +            sqlite3_str_append(pOut, "x'", 2);
 +          }
 +          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(pOut, 1, '\'');
 +          }
 +          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];
 +            zVal[j+1] = "0123456789abcdef"[(c)&0xf];
 +          }
 +          break;
 +        }
 +        case QRF_BLOB_Tcl:
 +        case QRF_BLOB_Json: {
 +          int iStart;
 +          int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
 +          int i, j;
 +          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(pOut, "\"", 1);
 +          iStart = sqlite3_str_length(pOut);
 +          for(i=szC; i>0; i--){
 +            sqlite3_str_appendchar(pOut, nBlob, ' ');
 +          }
 +          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] = '\\';
 +            if( szC==4 ){
 +              zVal[j+1] = '0' + ((c>>6)&3);
 +              zVal[j+2] = '0' + ((c>>3)&7);
 +              zVal[j+3] = '0' + (c&7);
 +            }else{
 +              zVal[j+1] = 'u';
 +              zVal[j+2] = '0';
 +              zVal[j+3] = '0';
 +              zVal[j+4] = "0123456789abcdef"[(c>>4)&0xf];
 +              zVal[j+5] = "0123456789abcdef"[(c)&0xf];
 +            }
 +          }
 +          break;
 +        }
 +        default: {
 +          const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
 +          qrfEncodeText(p, pOut, zTxt);
 +        }
 +      }
 +      break;
 +    }
 +    case SQLITE_NULL: {
 +      if( p->spec.bTextNull==QRF_Yes ){
 +        qrfEncodeText(p, pOut, p->spec.zNull);
 +      }else{
 +        sqlite3_str_appendall(pOut, p->spec.zNull);
 +      }
 +      break;
 +    }
 +    case SQLITE_TEXT: {
 +      const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
 +      qrfEncodeText(p, pOut, zTxt);
 +      break;
 +    }
 +  }
 +}
 +
 +/*
 +** 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( a==0 ) a = (const unsigned char*)"";
 +  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, ' ');
 +  }
 +}
 +
 +/*
 +** Data for columnar layout, collected into a single object so
 +** that it can be more easily passed into subroutines.
 +*/
 +typedef struct qrfColData qrfColData;
 +struct qrfColData {
 +  Qrf *p;                  /* The QRF instance */
 +  int nCol;                /* Number of columns in the table */
 +  unsigned char bMultiRow; /* One or more cells will span multiple lines */
 +  sqlite3_int64 nRow;      /* Number of rows */
 +  sqlite3_int64 nAlloc;    /* Number of cells allocated */
 +  sqlite3_int64 n;         /* Number of cells.  nCol*nRow */
 +  char **azThis;           /* Cache of pointers to current row */
 +  char **az;               /* Content of all cells */
 +  int *aiWth;              /* Width of each cell */
 +  int *aiCol;              /* Width of each column */
 +  unsigned char *aAlign;   /* Alignment for each column */
 +};
 +
 +/*
 +** Free all the memory allocates in the qrfColData object
 +*/
 +static void qrfColDataFree(qrfColData *p){
 +  sqlite3_int64 i;
 +  for(i=0; i<p->n; i++) sqlite3_free(p->az[i]);
 +  sqlite3_free(p->az);
 +  sqlite3_free(p->aiWth);
 +  sqlite3_free(p->azThis);
 +  memset(p, 0, sizeof(*p));
 +}
 +
 +/*
 +** Allocate space for more cells in the qrfColData object.
 +** Return non-zero if a memory allocation fails.
 +*/
 +static int qrfColDataEnlarge(qrfColData *p){
 +  char **azData;
 +  int *aiWth;
 +  p->nAlloc = 2*p->nAlloc + 10*p->nCol;
 +  azData = sqlite3_realloc64(p->az, p->nAlloc*sizeof(char*));
 +  if( azData==0 ){
 +    qrfOom(p->p);
 +    qrfColDataFree(p);
 +    return 1;
 +  }
 +  p->az = azData;
 +  aiWth = sqlite3_realloc64(p->aiWth, p->nAlloc*sizeof(int));
 +  if( aiWth==0 ){
 +    qrfOom(p->p);
 +    qrfColDataFree(p);
 +    return 1;
 +  }
 +  p->aiWth = aiWth;
 +  return 0;
 +}
 +
 +/*
 +** (*pz)[] is a line of text that is to be displayed the box or table or
 +** similar tabular formats.  z[] contain newlines or might be too wide
 +** to fit in the columns so will need to be split into multiple line.
 +**
 +** This routine determines:
 +**
 +**    *  How many bytes of z[] should be shown on the current line.
 +**    *  How many character positions those bytes will cover.
 +**    *  The byte offset to the start of the next line.
 +*/
 +static void qrfWrapLine(
 +  const char *zIn,   /* Input text to be displayed */
 +  int w,             /* Column width in characters (not bytes) */
 +  int bWrap,         /* True if we should do word-wrapping */
 +  int *pnThis,       /* OUT: How many bytes of z[] for the current line */
 +  int *pnWide,       /* OUT: How wide is the text of this line */
 +  int *piNext        /* OUT: Offset into z[] to start of the next line */
 +){
 +  int i;                 /* Input bytes consumed */
 +  int k;                 /* Bytes in a VT100 code */
 +  int n;                 /* Output column number */
 +  const unsigned char *z = (const unsigned char*)zIn;
 +  unsigned char c = 0;
 +
 +  if( zIn[0]==0 ){
 +    *pnThis = 0;
 +    *pnWide = 0;
 +    *piNext = 0;
 +    return;
 +  }
 +  n = 0;
 +  for(i=0; n<w; i++){
 +    c = zIn[i];
 +    if( c>=0xc0 ){
 +      int u;
 +      int len = sqlite3_qrf_decode_utf8(&z[i], &u);
 +      int wcw = sqlite3_qrf_wcwidth(u);
 +      if( wcw+n>w ) break;
 +      i += len-1;
 +      n += wcw;
 +      continue;
 +    }
 +    if( c>=' ' ){
 +      n++;
 +      continue;
 +    }
 +    if( c==0 || c=='\n' ) break;
 +    if( c=='\r' && zIn[i+1]=='\n' ){ c = zIn[++i]; break; }
 +    if( c=='\t' ){
 +      int wcw = 8 - (n&7);
 +      if( n+wcw>w ) break;
 +      n += wcw;
 +      continue;
 +    }
 +    if( c==0x1b && (k = qrfIsVt100(&z[i]))>0 ){
 +      i += k-1;
 +    }
 +  }
 +  if( c==0 ){
 +    *pnThis = i;
 +    *pnWide = n;
 +    *piNext = i;
 +    return;
 +  }
 +  if( c=='\n' ){
 +    *pnThis = i;
 +    *pnWide = n;
 +    *piNext = i+1;
 +    return;
 +  }
 +
 +  /* If we get this far, that means the current line will end at some
 +  ** point that is neither a "\n" or a 0x00.  Figure out where that
 +  ** split should occur
 +  */
-       if( isspace(z[k]) ) break;
++  if( bWrap && z[i]!=0 && !qrfSpace(z[i]) && qrfAlnum(c)==qrfAlnum(z[i]) ){
 +    /* Perhaps try to back up to a better place to break the line */
 +    for(k=i-1; k>=i/2; k--){
-         if( isalnum(z[k-1])!=isalnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
++      if( qrfSpace(z[k]) ) break;
 +    }
 +    if( k<i/2 ){
 +      for(k=i; k>=i/2; k--){
-   if( !isalpha(z[0]) && z[0]!='_' ) return 1;
++        if( qrfAlnum(z[k-1])!=qrfAlnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
 +      }
 +    }
 +    if( k>=i/2 ){
 +      i = k;
 +      n = qrfDisplayWidth((const char*)z, k, 0);
 +    }
 +  }
 +  *pnThis = i;
 +  *pnWide = n;
 +  while( zIn[i]==' ' || zIn[i]=='\t' || zIn[i]=='\r' ){ i++; }
 +  *piNext = i;
 +}
 +
 +/*
 +** Print a markdown or table-style row separator using ascii-art
 +*/
 +static void qrfRowSeparator(sqlite3_str *pOut, qrfColData *p, char cSep){
 +  int i;
 +  if( p->nCol>0 ){
 +    sqlite3_str_append(pOut, &cSep, 1);
 +    sqlite3_str_appendchar(pOut, p->aiCol[0]+2, '-');
 +    for(i=1; i<p->nCol; i++){
 +      sqlite3_str_append(pOut, &cSep, 1);
 +      sqlite3_str_appendchar(pOut, p->aiCol[i]+2, '-');
 +    }
 +    sqlite3_str_append(pOut, &cSep, 1);
 +  }
 +  sqlite3_str_append(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_STYLE_Box table.
 +*/
 +static void qrfBoxSeparator(
 +  sqlite3_str *pOut,
 +  qrfColData *p,
 +  const char *zSep1,
 +  const char *zSep2,
 +  const char *zSep3
 +){
 +  int i;
 +  if( p->nCol>0 ){
 +    sqlite3_str_appendall(pOut, zSep1);
 +    qrfBoxLine(pOut, p->aiCol[0]+2);
 +    for(i=1; i<p->nCol; i++){
 +      sqlite3_str_appendall(pOut, zSep2);
 +      qrfBoxLine(pOut, p->aiCol[i]+2);
 +    }
 +    sqlite3_str_appendall(pOut, zSep3);
 +  }
 +  sqlite3_str_append(pOut, "\n", 1);
 +}
 +
 +/*
 +** Load into pData the default alignment for the body of a table.
 +*/
 +static void qrfLoadAlignment(qrfColData *pData, Qrf *p){
 +  sqlite3_int64 i;
 +  memset(pData->aAlign, p->spec.eDfltAlign, pData->nCol);
 +  for(i=0; i<pData->nCol; i++){
 +    if( i<p->spec.nAlign ){
 +      unsigned char ax = p->spec.aAlign[i];
 +      if( (ax & QRF_ALIGN_HMASK)!=0 ){
 +        pData->aAlign[i] = (ax & QRF_ALIGN_HMASK) |
 +                            (pData->aAlign[i] & QRF_ALIGN_VMASK);
 +      }
 +    }else if( i<p->spec.nWidth ){
 +      if( p->spec.aWidth[i]<0 ){
 +         pData->aAlign[i] = QRF_ALIGN_Right |
 +                               (pData->aAlign[i] & QRF_ALIGN_VMASK);
 +      }
 +    }else{
 +      break;
 +    }
 +  }
 +}
 +
 +/*
 +** Output horizontally justified text into pOut.  The text is the
 +** first nVal bytes of zVal.  Include nWS bytes of whitespace, either
 +** split between both sides, or on the left, or on the right, depending
 +** on eAlign.
 +*/
 +static void qrfPrintAligned(
 +  sqlite3_str *pOut,       /* Append text here */
 +  const char *zVal,        /* Text to append */
 +  int nVal,                /* Use only the first nVal bytes of zVal[] */
 +  int nWS,                 /* Whitespace for horizonal alignment */
 +  unsigned char eAlign     /* Alignment type */
 +){
 +  eAlign &= QRF_ALIGN_HMASK;
 +  if( eAlign==QRF_ALIGN_Center ){
 +    /* Center the text */
 +    sqlite3_str_appendchar(pOut, nWS/2, ' ');
 +    sqlite3_str_append(pOut, zVal, nVal);
 +    sqlite3_str_appendchar(pOut, nWS - nWS/2, ' ');
 +  }else if( eAlign==QRF_ALIGN_Right){
 +    /* Right justify the text */
 +    sqlite3_str_appendchar(pOut, nWS, ' ');
 +    sqlite3_str_append(pOut, zVal, nVal);
 +  }else{
 +    /* Left justify the next */
 +    sqlite3_str_append(pOut, zVal, nVal);
 +    sqlite3_str_appendchar(pOut, nWS, ' ');
 +  }
 +}
 +
 +/*
 +** 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 i, j;                     /* Loop counters */
 +  const char *colSep = 0;                 /* Column separator text */
 +  const char *rowSep = 0;                 /* Row terminator text */
 +  const char *rowStart = 0;               /* Row start text */
 +  int szColSep, szRowSep, szRowStart;     /* Size in bytes of previous 3 */
 +  int rc;                                 /* Result code */
 +  int nColumn = p->nCol;                  /* Number of columns */
 +  int bWW;                                /* True to do word-wrap */
 +  sqlite3_str *pStr;                      /* Temporary rendering */
 +  qrfColData data;                        /* Columnar layout data */
 +
 +  rc = sqlite3_step(p->pStmt);
 +  if( rc!=SQLITE_ROW || nColumn==0 ){
 +    return;   /* No output */
 +  }
 +
 +  /* Initialize the data container */
 +  memset(&data, 0, sizeof(data));
 +  data.nCol = p->nCol;
 +  data.azThis = sqlite3_malloc64( nColumn*(sizeof(char*) + sizeof(int) + 1) );
 +  if( data.azThis==0 ){
 +    qrfOom(p);
 +    return;
 +  }
 +  data.aiCol = (int*)&data.azThis[nColumn];
 +  data.aAlign = (unsigned char*)&data.aiCol[nColumn];
 +  if( qrfColDataEnlarge(&data) ) return;
 +  assert( data.az!=0 );
 +  assert( data.aAlign!=0 );
 +
 +  /* Load the column header names and all cell content into data */
 +  if( p->spec.bTitles==QRF_Yes ){
 +    unsigned char saved_eText = p->spec.eText;
 +    p->spec.eText = p->spec.eTitle;
 +    for(i=0; i<nColumn; i++){
 +      const char *z = (const char*)sqlite3_column_name(p->pStmt,i);
 +      int nNL = 0;
 +      int n;
 +      pStr = sqlite3_str_new(p->db);
 +      qrfEncodeText(p, pStr, z ? z : "");
 +      n = sqlite3_str_length(pStr);
 +      z = data.az[data.n] = sqlite3_str_finish(pStr);
 +      data.aiWth[data.n] = qrfDisplayWidth(z, n, &nNL);
 +      data.n++;
 +      if( nNL ) data.bMultiRow = 1;
 +    }
 +    p->spec.eText = saved_eText;
 +    p->nRow++;
 +  }
 +  do{
 +    if( data.n+nColumn > data.nAlloc ){
 +      if( qrfColDataEnlarge(&data) ) return;
 +    }
 +    for(i=0; i<nColumn; i++){
 +      char *z;
 +      int nNL = 0;
 +      int n;
 +      pStr = sqlite3_str_new(p->db);
 +      qrfRenderValue(p, pStr, i);
 +      n = sqlite3_str_length(pStr);
 +      z = data.az[data.n] = sqlite3_str_finish(pStr);
 +      data.aiWth[data.n] = qrfDisplayWidth(z, n, &nNL);
 +      data.n++;
 +      if( nNL ) data.bMultiRow = 1;
 +    }
 +    p->nRow++;
 +  }while( sqlite3_step(p->pStmt)==SQLITE_ROW && p->iErr==SQLITE_OK );
 +  if( p->iErr ){
 +    qrfColDataFree(&data);
 +    return;
 +  }
 +
 +  /* Compute the width and alignment of every column */
 +  if( p->spec.bTitles==QRF_No ){
 +    qrfLoadAlignment(&data, p);
 +  }else if( p->spec.eTitleAlign==QRF_Auto ){
 +    memset(data.aAlign, QRF_ALIGN_Center, nColumn);
 +  }else{
 +    memset(data.aAlign, p->spec.eTitleAlign, nColumn);
 +  }
 +
 +  for(i=0; i<nColumn; i++){
 +    int w = 0;
 +    if( i<p->spec.nWidth ){
 +      w = p->spec.aWidth[i];
 +      if( w==(-32768) ){
 +        w = 0;
 +        if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){
 +          data.aAlign[i] |= QRF_ALIGN_Right;
 +        }
 +      }else if( w<0 ){
 +        w = -w;
 +        if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){
 +          data.aAlign[i] |= QRF_ALIGN_Right;
 +        }
 +      }
 +    }
 +    if( w==0 ){
 +      for(j=i; j<data.n; j+=nColumn){
 +        if( data.aiWth[j] > w ){
 +          w = data.aiWth[j];
 +          if( p->spec.nWrap>0 && w>p->spec.nWrap ){
 +            w = p->spec.nWrap;
 +            data.bMultiRow = 1;
 +            break;
 +          }
 +        }
 +      }
 +    }else if( data.bMultiRow==0 || w==1 ){
 +      for(j=i; j<data.n; j+=nColumn){
 +        if( data.aiWth[j] > w ){
 +          data.bMultiRow = 1;
 +          if( w==1 ){
 +            /* If aiWth[j] is 2 or more, then there might be a double-wide
 +            ** character somewhere.  So make the column width at least 2. */
 +            w = 2;
 +          }
 +          break;
 +        }
 +      }
 +    }
 +    data.aiCol[i] = w;
 +  }
 +
 +  /* TBD: Narrow columns so that the total is less than p->spec.nScreenWidth */
 +
 +  /* Draw the line across the top of the table.  Also initialize
 +  ** the row boundary and column separator texts. */
 +  switch( p->spec.eStyle ){
 +    case QRF_STYLE_Box:
 +      rowStart = BOX_13 " ";
 +      colSep = " " BOX_13 " ";
 +      rowSep = " " BOX_13 "\n";
 +      qrfBoxSeparator(p->pOut, &data, BOX_23, BOX_234, BOX_34);
 +      break;
 +    case QRF_STYLE_Table:
 +      rowStart = "| ";
 +      colSep = " | ";
 +      rowSep = " |\n";
 +      qrfRowSeparator(p->pOut, &data, '+');
 +      break;
 +    case QRF_STYLE_Column:
 +      rowStart = "";
 +      colSep = "  ";
 +      rowSep = "\n";
 +      break;
 +    default:  /*case QRF_STYLE_Markdown:*/
 +      rowStart = "| ";
 +      colSep = " | ";
 +      rowSep = " |\n";
 +      break;
 +  }
 +  szRowStart = (int)strlen(rowStart);
 +  szRowSep = (int)strlen(rowSep);
 +  szColSep = (int)strlen(colSep);
 +
 +  bWW = (p->spec.bWordWrap==QRF_Yes && data.bMultiRow);
 +  for(i=0; i<data.n; i+=nColumn){
 +    int bMore;
 +    int nRow = 0;
 +
 +    /* Draw a single row of the table.  This might be the title line
 +    ** (if there is a title line) or a row in the body of the table.
 +    ** The column number will be j.  The row number is i/nColumn.
 +    */
 +    for(j=0; j<nColumn; j++){ data.azThis[j] = data.az[i+j]; }
 +    do{
 +      sqlite3_str_append(p->pOut, rowStart, szRowStart);
 +      bMore = 0;
 +      for(j=0; j<nColumn; j++){
 +        int nThis = 0;
 +        int nWide = 0;
 +        int iNext = 0;
 +        int nWS;
 +        qrfWrapLine(data.azThis[j], data.aiCol[j], bWW, &nThis, &nWide, &iNext);
 +        nWS = data.aiCol[j] - nWide;
 +        qrfPrintAligned(p->pOut, data.azThis[j], nThis, nWS, data.aAlign[j]);
 +        data.azThis[j] += iNext;
 +        if( data.azThis[j][0]!=0 ) bMore = 1;
 +        if( j<nColumn-1 ){
 +          sqlite3_str_append(p->pOut, colSep, szColSep);
 +        }else{
 +          sqlite3_str_append(p->pOut, rowSep, szRowSep);
 +        }
 +      }
 +    }while( bMore && ++nRow < p->mxHeight );
 +    if( bMore ){
 +      /* This row was terminated by nLineLimit.  Show ellipsis. */
 +      sqlite3_str_append(p->pOut, rowStart, szRowStart);
 +      for(j=0; j<nColumn; j++){
 +        if( data.azThis[j][0]==0 ){
 +          sqlite3_str_appendchar(p->pOut, data.aiCol[j], ' ');
 +        }else{
 +          int nE = 3;
 +          if( nE>data.aiCol[j] ) nE = data.aiCol[j];
 +          qrfPrintAligned(p->pOut, "...", nE, data.aiCol[j]-nE, data.aAlign[j]);
 +        }
 +        if( j<nColumn-1 ){
 +          sqlite3_str_append(p->pOut, colSep, szColSep);
 +        }else{
 +          sqlite3_str_append(p->pOut, rowSep, szRowSep);
 +        }
 +      }
 +    }
 +
 +    /* Draw either (1) the separator between the title line and the body
 +    ** of the table, or (2) separators between individual rows of the table
 +    ** body.  isTitleDataSeparator will be true if we are doing (1).
 +    */
 +    if( (i==0 || data.bMultiRow) && i+nColumn<data.n ){
 +      int isTitleDataSeparator = (i==0 && p->spec.bTitles==QRF_Yes);
 +      if( isTitleDataSeparator ){
 +        qrfLoadAlignment(&data, p);
 +      }
 +      switch( p->spec.eStyle ){
 +        case QRF_STYLE_Table: {
 +          if( isTitleDataSeparator || data.bMultiRow ){
 +            qrfRowSeparator(p->pOut, &data, '+');
 +          }
 +          break;
 +        }
 +        case QRF_STYLE_Box: {
 +          if( isTitleDataSeparator || data.bMultiRow ){
 +            qrfBoxSeparator(p->pOut, &data, BOX_123, BOX_1234, BOX_134);
 +          }
 +          break;
 +        }
 +        case QRF_STYLE_Markdown: {
 +          if( isTitleDataSeparator ){
 +            qrfRowSeparator(p->pOut, &data, '|');
 +          }
 +          break;
 +        }
 +        case QRF_STYLE_Column: {
 +          if( isTitleDataSeparator ){
 +            for(j=0; j<nColumn; j++){
 +              sqlite3_str_appendchar(p->pOut, data.aiCol[j], '-');
 +              if( j<nColumn-1 ){
 +                sqlite3_str_append(p->pOut, colSep, szColSep);
 +              }else{
 +                sqlite3_str_append(p->pOut, rowSep, szRowSep);
 +              }
 +            }
 +          }else if( data.bMultiRow ){
 +            sqlite3_str_append(p->pOut, "\n", 1);
 +          }
 +          break;
 +        }
 +      }
 +    }
 +  }
 +
 +  /* Draw the line across the bottom of the table */
 +  switch( p->spec.eStyle ){
 +    case QRF_STYLE_Box:
 +      qrfBoxSeparator(p->pOut, &data, BOX_12, BOX_124, BOX_14);
 +      break;
 +    case QRF_STYLE_Table:
 +      qrfRowSeparator(p->pOut, &data, '+');
 +      break;
 +  }
 +  qrfWrite(p);
 +
 +  qrfColDataFree(&data);
 +  return;
 +}
 +
 +/*
 +** Parameter azArray points to a zero-terminated array of strings. zStr
 +** points to a single nul-terminated string. Return non-zero if zStr
 +** is equal, according to strcmp(), to any of the strings in the array.
 +** Otherwise, return zero.
 +*/
 +static int qrfStringInArray(const char *zStr, const char **azArray){
 +  int i;
 +  if( zStr==0 ) return 0;
 +  for(i=0; azArray[i]; i++){
 +    if( 0==strcmp(zStr, azArray[i]) ) return 1;
 +  }
 +  return 0;
 +}
 +
 +/*
 +** Print out an EXPLAIN with indentation.  This is a two-pass algorithm.
 +**
 +** On the first pass, we compute aiIndent[iOp] which is the amount of
 +** indentation to apply to the iOp-th opcode.  The output actually occurs
 +** on the second pass.
 +**
 +** The indenting rules are:
 +**
 +**     * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent
 +**       all opcodes that occur between the p2 jump destination and the opcode
 +**       itself by 2 spaces.
 +**
 +**     * Do the previous for "Return" instructions for when P2 is positive.
 +**       See tag-20220407a in wherecode.c and vdbe.c.
 +**
 +**     * For each "Goto", if the jump destination is earlier in the program
 +**       and ends on one of:
 +**          Yield  SeekGt  SeekLt  RowSetRead  Rewind
 +**       or if the P1 parameter is one instead of zero,
 +**       then indent all opcodes between the earlier instruction
 +**       and "Goto" by 2 spaces.
 +*/
 +static void qrfExplain(Qrf *p){
 +  int *abYield = 0;     /* abYield[iOp] is rue if opcode iOp is an OP_Yield */
 +  int *aiIndent = 0;    /* Indent the iOp-th opcode by aiIndent[iOp] */
 +  i64 nAlloc = 0;       /* Allocated size of aiIndent[], abYield */
 +  int nIndent = 0;      /* Number of entries in aiIndent[] */
 +  int iOp;              /* Opcode number */
 +  int i;                /* Column loop counter */
 +
 +  const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext",
 +                           "Return", 0 };
 +  const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead",
 +                            "Rewind", 0 };
 +  const char *azGoto[] = { "Goto", 0 };
 +
 +  /* The caller guarantees that the leftmost 4 columns of the statement
 +  ** passed to this function are equivalent to the leftmost 4 columns
 +  ** of EXPLAIN statement output. In practice the statement may be
 +  ** an EXPLAIN, or it may be a query on the bytecode() virtual table.  */
 +  assert( sqlite3_column_count(p->pStmt)>=4 );
 +  assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 0), "addr" ) );
 +  assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 1), "opcode" ) );
 +  assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 2), "p1" ) );
 +  assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 3), "p2" ) );
 +
 +  for(iOp=0; SQLITE_ROW==sqlite3_step(p->pStmt); iOp++){
 +    int iAddr = sqlite3_column_int(p->pStmt, 0);
 +    const char *zOp = (const char*)sqlite3_column_text(p->pStmt, 1);
 +    int p1 = sqlite3_column_int(p->pStmt, 2);
 +    int p2 = sqlite3_column_int(p->pStmt, 3);
 +
 +    /* Assuming that p2 is an instruction address, set variable p2op to the
 +    ** index of that instruction in the aiIndent[] array. p2 and p2op may be
 +    ** different if the current instruction is part of a sub-program generated
 +    ** by an SQL trigger or foreign key.  */
 +    int p2op = (p2 + (iOp-iAddr));
 +
 +    /* Grow the aiIndent array as required */
 +    if( iOp>=nAlloc ){
 +      nAlloc += 100;
 +      aiIndent = (int*)sqlite3_realloc64(aiIndent, nAlloc*sizeof(int));
 +      abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
 +      if( aiIndent==0 || abYield==0 ){
 +        qrfOom(p);
 +        sqlite3_free(aiIndent);
 +        sqlite3_free(abYield);
 +        return;
 +      }
 +    }
 +
 +    abYield[iOp] = qrfStringInArray(zOp, azYield);
 +    aiIndent[iOp] = 0;
 +    nIndent = iOp+1;
 +    if( qrfStringInArray(zOp, azNext) && p2op>0 ){
 +      for(i=p2op; i<iOp; i++) aiIndent[i] += 2;
 +    }
 +    if( qrfStringInArray(zOp, azGoto) && p2op<iOp && (abYield[p2op] || p1) ){
 +      for(i=p2op; i<iOp; i++) aiIndent[i] += 2;
 +    }
 +  }
 +  sqlite3_free(abYield);
 +
 +  /* Second pass.  Actually generate output */
 +  sqlite3_reset(p->pStmt);
 +  if( p->iErr==SQLITE_OK ){
 +    static const int aExplainWidth[] = {4,       13, 4, 4, 4, 13, 2, 13};
 +    static const int aExplainMap[] =   {0,       1,  2, 3, 4, 5,  6, 7 };
 +    static const int aScanExpWidth[] = {4,15, 6, 13, 4, 4, 4, 13, 2, 13};
 +    static const int aScanExpMap[] =   {0, 9, 8, 1,  2, 3, 4, 5,  6, 7 };
 +    const int *aWidth = aExplainWidth;
 +    const int *aMap = aExplainMap;
 +    int nWidth = sizeof(aExplainWidth)/sizeof(int);
 +    int iIndent = 1;
 +    int nArg = p->nCol;
 +    if( p->spec.eStyle==QRF_STYLE_StatsVm ){
 +      aWidth = aScanExpWidth;
 +      aMap = aScanExpMap;
 +      nWidth = sizeof(aScanExpWidth)/sizeof(int);
 +      iIndent = 3;
 +    }
 +    if( nArg>nWidth ) nArg = nWidth;
 +
 +    for(iOp=0; sqlite3_step(p->pStmt)==SQLITE_ROW; iOp++){
 +      /* If this is the first row seen, print out the headers */
 +      if( iOp==0 ){
 +        for(i=0; i<nArg; i++){
 +          const char *zCol = sqlite3_column_name(p->pStmt, aMap[i]);
 +          qrfWidthPrint(p,p->pOut, aWidth[i], zCol);
 +          if( i==nArg-1 ){
 +            sqlite3_str_append(p->pOut, "\n", 1);
 +          }else{
 +            sqlite3_str_append(p->pOut, "  ", 2);
 +          }
 +        }
 +        for(i=0; i<nArg; i++){
 +          sqlite3_str_appendf(p->pOut, "%.*c", aWidth[i], '-');
 +          if( i==nArg-1 ){
 +            sqlite3_str_append(p->pOut, "\n", 1);
 +          }else{
 +            sqlite3_str_append(p->pOut, "  ", 2);
 +          }
 +        }
 +      }
 +  
 +      for(i=0; i<nArg; i++){
 +        const char *zSep = "  ";
 +        int w = aWidth[i];
 +        const char *zVal = (const char*)sqlite3_column_text(p->pStmt, aMap[i]);
 +        int len;
 +        if( i==nArg-1 ) w = 0;
 +        if( zVal==0 ) zVal = "";
 +        len = qrfDisplayLength(zVal);
 +        if( len>w ){
 +          w = len;
 +          zSep = " ";
 +        }
 +        if( i==iIndent && aiIndent && iOp<nIndent ){
 +          sqlite3_str_appendchar(p->pOut, aiIndent[iOp], ' ');
 +        }
 +        qrfWidthPrint(p, p->pOut, w, zVal);
 +        if( i==nArg-1 ){
 +          sqlite3_str_append(p->pOut, "\n", 1);
 +        }else{
 +          sqlite3_str_appendall(p->pOut, zSep);
 +        }
 +      }
 +      p->nRow++;
 +    }
 +    qrfWrite(p);
 +  }
 +  sqlite3_free(aiIndent);
 +}
 +
 +/*
 +** Do a "scanstatus vm" style EXPLAIN listing on p->pStmt.
 +**
 +** p->pStmt is probably not an EXPLAIN query.  Instead, construct a
 +** new query that is a bytecode() rendering of p->pStmt with extra
 +** columns for the "scanstatus vm" outputs, and run the results of
 +** that new query through the normal EXPLAIN formatting.
 +*/
 +static void qrfScanStatusVm(Qrf *p){
 +  sqlite3_stmt *pOrigStmt = p->pStmt;
 +  sqlite3_stmt *pExplain;
 +  int rc;
 +  static const char *zSql =
 +      "  SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec,"
 +      "   format('% 6s (%.2f%%)',"
 +      "      CASE WHEN ncycle<100_000 THEN ncycle || ' '"
 +      "         WHEN ncycle<100_000_000 THEN (ncycle/1_000) || 'K'"
 +      "         WHEN ncycle<100_000_000_000 THEN (ncycle/1_000_000) || 'M'"
 +      "         ELSE (ncycle/1000_000_000) || 'G' END,"
 +      "       ncycle*100.0/(sum(ncycle) OVER ())"
 +      "   )  AS cycles"
 +      "   FROM bytecode(?1)";
 +  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pExplain, 0);
 +  if( rc ){
 +    qrfError(p, rc, "%s", sqlite3_errmsg(p->db));
 +    sqlite3_finalize(pExplain);
 +    return;
 +  }
 +  sqlite3_bind_pointer(pExplain, 1, pOrigStmt, "stmt-pointer", 0);
 +  p->pStmt = pExplain;
 +  p->nCol = 10;
 +  qrfExplain(p);
 +  sqlite3_finalize(pExplain);
 +  p->pStmt = pOrigStmt;
 +}
 +
 +/*
 +** Attempt to determine if identifier zName needs to be quoted, either
 +** because it contains non-alphanumeric characters, or because it is an
 +** SQLite keyword.  Be conservative in this estimate:  When in doubt assume
 +** that quoting is required.
 +**
 +** Return 1 if quoting is required.  Return 0 if no quoting is required.
 +*/
 +
 +static int qrf_need_quote(const char *zName){
 +  int i;
 +  const unsigned char *z = (const unsigned char*)zName;
 +  if( z==0 ) return 1;
-     if( !isalnum(z[i]) && z[i]!='_' ) return 1;
++  if( !qrfAlpha(z[0]) ) return 1;
 +  for(i=0; z[i]; i++){
++    if( !qrfAlnum(z[i]) ) return 1;
 +  }
 +  return sqlite3_keyword_check(zName, i)!=0;
 +}
 +
 +/*
 +** Helper function for QRF_STYLE_Json and QRF_STYLE_JObject.
 +** The initial "{" for a JSON object that will contain row content
 +** has been output.  Now output all the content.
 +*/
 +static void qrfOneJsonRow(Qrf *p){
 +  int i, nItem; 
 +  for(nItem=i=0; i<p->nCol; i++){
 +    const char *zCName;
 +    zCName = sqlite3_column_name(p->pStmt, i);
 +    if( nItem>0 ) sqlite3_str_append(p->pOut, ",", 1);
 +    nItem++;
 +    qrfEncodeText(p, p->pOut, zCName);
 +    sqlite3_str_append(p->pOut, ":", 1);
 +    qrfRenderValue(p, p->pOut, i);
 +  }
 +  qrfWrite(p);
 +}
 +
 +/*
 +** Render a single row of output for non-columnar styles - any
 +** style that lets us render row by row as the content is received
 +** from the query.
 +*/
 +static void qrfOneSimpleRow(Qrf *p){
 +  int i;
 +  switch( p->spec.eStyle ){
 +    case QRF_STYLE_Off:
 +    case QRF_STYLE_Count: {
 +      /* No-op */
 +      break;
 +    }
 +    case QRF_STYLE_Json: {
 +      if( p->nRow==0 ){
 +        sqlite3_str_append(p->pOut, "[{", 2);
 +      }else{
 +        sqlite3_str_append(p->pOut, "},\n{", 4);
 +      }
 +      qrfOneJsonRow(p);
 +      break;
 +    }
 +    case QRF_STYLE_JObject: {
 +      if( p->nRow==0 ){
 +        sqlite3_str_append(p->pOut, "{", 1);
 +      }else{
 +        sqlite3_str_append(p->pOut, "}\n{", 3);
 +      }
 +      qrfOneJsonRow(p);
 +      break;
 +    }
 +    case QRF_STYLE_Html: {
 +      if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){
 +        sqlite3_str_append(p->pOut, "<TR>", 4);
 +        for(i=0; i<p->nCol; i++){
 +          const char *zCName = sqlite3_column_name(p->pStmt, i);
 +          sqlite3_str_append(p->pOut, "\n<TH>", 5);
 +          qrfEncodeText(p, p->pOut, zCName);
 +        }
 +        sqlite3_str_append(p->pOut, "\n</TR>\n", 7);
 +      }
 +      sqlite3_str_append(p->pOut, "<TR>", 4);
 +      for(i=0; i<p->nCol; i++){
 +        sqlite3_str_append(p->pOut, "\n<TD>", 5);
 +        qrfRenderValue(p, p->pOut, i);
 +      }
 +      sqlite3_str_append(p->pOut, "\n</TR>\n", 7);
 +      qrfWrite(p);
 +      break;
 +    }
 +    case QRF_STYLE_Insert: {
 +      if( qrf_need_quote(p->spec.zTableName) ){
 +        sqlite3_str_appendf(p->pOut,"INSERT INTO \"%w\"",p->spec.zTableName);
 +      }else{
 +        sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName);
 +      }
 +      if( p->spec.bTitles==QRF_Yes ){
 +        for(i=0; i<p->nCol; i++){
 +          const char *zCName = sqlite3_column_name(p->pStmt, i);
 +          if( qrf_need_quote(zCName) ){
 +            sqlite3_str_appendf(p->pOut, "%c\"%w\"",
 +                                i==0 ? '(' : ',', zCName);
 +          }else{
 +            sqlite3_str_appendf(p->pOut, "%c%s",
 +                                i==0 ? '(' : ',', zCName);
 +          }
 +        }
 +        sqlite3_str_append(p->pOut, ")", 1);
 +      }
 +      sqlite3_str_append(p->pOut," VALUES(", 8);
 +      for(i=0; i<p->nCol; i++){
 +        if( i>0 ) sqlite3_str_append(p->pOut, ",", 1);
 +        qrfRenderValue(p, p->pOut, i);
 +      }
 +      sqlite3_str_append(p->pOut, ");\n", 3);
 +      qrfWrite(p);
 +      break;
 +    }
 +    case QRF_STYLE_Line: {
 +      sqlite3_str *pVal;
 +      int mxW;
 +      int bWW;
 +      if( p->u.sLine.azCol==0 ){
 +        p->u.sLine.azCol = sqlite3_malloc64( p->nCol*sizeof(char*) );
 +        if( p->u.sLine.azCol==0 ){
 +          qrfOom(p);
 +          break;
 +        }
 +        p->u.sLine.mxColWth = 0;
 +        for(i=0; i<p->nCol; i++){
 +          int sz;
 +          p->u.sLine.azCol[i] = sqlite3_column_name(p->pStmt, i);
 +          if( p->u.sLine.azCol[i]==0 ) p->u.sLine.azCol[i] = "unknown";
 +          sz = qrfDisplayLength(p->u.sLine.azCol[i]);
 +          if( sz > p->u.sLine.mxColWth ) p->u.sLine.mxColWth = sz;
 +        }
 +      }
 +      if( p->nRow ) sqlite3_str_append(p->pOut, "\n", 1);
 +      pVal = sqlite3_str_new(p->db);
 +      mxW = p->mxWidth - (3 + p->u.sLine.mxColWth);
 +      bWW = p->spec.bWordWrap==QRF_Yes;
 +      for(i=0; i<p->nCol; i++){
 +        const char *zVal;
 +        int cnt = 0;
 +        qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth, p->u.sLine.azCol[i]);
 +        sqlite3_str_append(p->pOut, " = ", 3);
 +        qrfRenderValue(p, pVal, i);
 +        zVal = sqlite3_str_value(pVal);
 +        if( zVal==0 ) zVal = "";
 +        do{
 +          int nThis, nWide, iNext;
 +          qrfWrapLine(zVal, mxW, bWW, &nThis, &nWide, &iNext);
 +          if( cnt ) sqlite3_str_appendchar(p->pOut,p->u.sLine.mxColWth+3,' ');
 +          cnt++;
 +          if( cnt>p->mxHeight ){
 +            zVal = "...";
 +            nThis = iNext = 3;
 +          }
 +          sqlite3_str_append(p->pOut, zVal, nThis);
 +          sqlite3_str_append(p->pOut, "\n", 1);
 +          zVal += iNext;
 +        }while( zVal[0] );
 +        sqlite3_str_reset(pVal);
 +      }
 +      sqlite3_free(sqlite3_str_finish(pVal));
 +      qrfWrite(p);
 +      break;
 +    }
 +    case QRF_STYLE_Eqp: {
 +      const char *zEqpLine = (const char*)sqlite3_column_text(p->pStmt,3);
 +      int iEqpId = sqlite3_column_int(p->pStmt, 0);
 +      int iParentId = sqlite3_column_int(p->pStmt, 1);
 +      if( zEqpLine==0 ) zEqpLine = "";
 +      if( zEqpLine[0]=='-' ) qrfEqpRender(p, 0);
 +      qrfEqpAppend(p, iEqpId, iParentId, zEqpLine);
 +      break;
 +    }
 +    default: {  /* QRF_STYLE_List */
 +      if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){
 +        int saved_eText = p->spec.eText;
 +        p->spec.eText = p->spec.eTitle;
 +        for(i=0; i<p->nCol; i++){
 +          const char *zCName = sqlite3_column_name(p->pStmt, i);
 +          if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
 +          qrfEncodeText(p, p->pOut, zCName);
 +        }
 +        sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
 +        qrfWrite(p);
 +        p->spec.eText = saved_eText;
 +      }
 +      for(i=0; i<p->nCol; i++){
 +        if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
 +        qrfRenderValue(p, p->pOut, i);
 +      }
 +      sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
 +      qrfWrite(p);
 +      break;
 +    }
 +  }
 +  p->nRow++;
 +}
 +
 +/*
 +** Initialize the internal Qrf object.
 +*/
 +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 */
 +  memset(p, 0, sizeof(*p));
 +  p->pzErr = pzErr;
 +  if( pSpec->iVersion!=1 ){
 +    qrfError(p, SQLITE_ERROR,
 +       "unusable sqlite3_qrf_spec.iVersion (%d)",
 +       pSpec->iVersion);
 +    return;
 +  }
 +  p->pStmt = pStmt;
 +  p->db = sqlite3_db_handle(pStmt);
 +  p->pOut = sqlite3_str_new(p->db);
 +  if( p->pOut==0 ){
 +    qrfOom(p);
 +    return;
 +  }
 +  p->iErr = 0;
 +  p->nCol = sqlite3_column_count(p->pStmt);
 +  p->nRow = 0;
 +  sz = sizeof(sqlite3_qrf_spec);
 +  memcpy(&p->spec, pSpec, sz);
 +  if( p->spec.zNull==0 ) p->spec.zNull = "";
 +  p->mxWidth = p->spec.nScreenWidth;
 +  if( p->mxWidth<=0 ) p->mxWidth = QRF_MAX_WIDTH;
 +  p->mxHeight = p->spec.nLineLimit;
 +  if( p->mxHeight<=0 ) p->mxHeight = 2147483647;
 +qrf_reinit:
 +  switch( p->spec.eStyle ){
 +    case QRF_Auto: {
 +      switch( sqlite3_stmt_isexplain(pStmt) ){
 +        case 0:  p->spec.eStyle = QRF_STYLE_Box;      break;
 +        case 1:  p->spec.eStyle = QRF_STYLE_Explain;  break;
 +        default: p->spec.eStyle = QRF_STYLE_Eqp;      break;
 +      }
 +      goto qrf_reinit;
 +    }
 +    case QRF_STYLE_List: {
 +      if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = "|";
 +      if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n";
 +      break;
 +    }
 +    case QRF_STYLE_JObject:
 +    case QRF_STYLE_Json: {
 +      p->spec.eText = QRF_TEXT_Json;
 +      p->spec.eBlob = QRF_BLOB_Json;
 +      p->spec.zNull = "null";
 +      break;
 +    }
 +    case QRF_STYLE_Html: {
 +      p->spec.eText = QRF_TEXT_Html;
 +      p->spec.zNull = "null";
 +      break;
 +    }
 +    case QRF_STYLE_Insert: {
 +      p->spec.eText = QRF_TEXT_Sql;
 +      p->spec.eBlob = QRF_BLOB_Sql;
 +      p->spec.zNull = "NULL";
 +      if( p->spec.zTableName==0 || p->spec.zTableName[0]==0 ){
 +        p->spec.zTableName = "tab";
 +      }
 +      break;
 +    }
 +    case QRF_STYLE_Csv: {
 +      p->spec.eStyle = QRF_STYLE_List;
 +      p->spec.eText = QRF_TEXT_Csv;
 +      p->spec.eBlob = QRF_BLOB_Tcl;
 +      p->spec.zColumnSep = ",";
 +      p->spec.zRowSep = "\r\n";
 +      break;
 +    }
 +    case QRF_STYLE_Quote: {
 +      p->spec.eText = QRF_TEXT_Sql;
 +      p->spec.eBlob = QRF_BLOB_Sql;
 +      p->spec.zNull = "NULL";
 +      p->spec.zColumnSep = ",";
 +      p->spec.zRowSep = "\n";
 +      break;
 +    }
 +    case QRF_STYLE_Eqp: {
 +      int expMode = sqlite3_stmt_isexplain(p->pStmt);
 +      if( expMode!=2 ){
 +        sqlite3_stmt_explain(p->pStmt, 2);
 +        p->expMode = expMode+1;
 +      }
 +      break;
 +    }
 +    case QRF_STYLE_Explain: {
 +      int expMode = sqlite3_stmt_isexplain(p->pStmt);
 +      if( expMode!=1 ){
 +        sqlite3_stmt_explain(p->pStmt, 1);
 +        p->expMode = expMode+1;
 +      }
 +      break;
 +    }
 +  }
 +  if( p->spec.eEsc==QRF_Auto ){
 +    p->spec.eEsc = QRF_ESC_Ascii;
 +  }
 +  if( p->spec.eText==QRF_Auto ){
 +    p->spec.eText = QRF_TEXT_Plain;
 +  }
 +  if( p->spec.eTitle==QRF_Auto ){
 +    switch( p->spec.eStyle ){
 +      case QRF_STYLE_Box:
 +      case QRF_STYLE_Column:
 +      case QRF_STYLE_Table:
 +        p->spec.eTitle = QRF_TEXT_Plain;
 +        break;
 +      default:
 +        p->spec.eTitle = p->spec.eText;
 +        break;
 +    }
 +  }
 +  if( p->spec.eBlob==QRF_Auto ){
 +    switch( p->spec.eText ){
 +      case QRF_TEXT_Sql:  p->spec.eBlob = QRF_BLOB_Sql;  break;
 +      case QRF_TEXT_Csv:  p->spec.eBlob = QRF_BLOB_Tcl;  break;
 +      case QRF_TEXT_Tcl:  p->spec.eBlob = QRF_BLOB_Tcl;  break;
 +      case QRF_TEXT_Json: p->spec.eBlob = QRF_BLOB_Json; break;
 +      default:            p->spec.eBlob = QRF_BLOB_Text; break;
 +    }
 +  }
 +  if( p->spec.bTitles==QRF_Auto ){
 +    switch( p->spec.eStyle ){
 +      case QRF_STYLE_Box:
 +      case QRF_STYLE_Csv:
 +      case QRF_STYLE_Column:
 +      case QRF_STYLE_Table:
 +      case QRF_STYLE_Markdown:
 +        p->spec.bTitles = QRF_Yes;
 +        break;
 +      default:
 +        p->spec.bTitles = QRF_No;
 +        break;
 +    }
 +  }
 +  if( p->spec.bWordWrap==QRF_Auto ){
 +    p->spec.bWordWrap = QRF_Yes;
 +  }
 +  if( p->spec.bTextJsonb==QRF_Auto ){
 +    p->spec.bTextJsonb = QRF_No;
 +  }
 +  if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = ",";
 +  if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n";
 +}
 +
 +/*
 +** Finish rendering the results
 +*/
 +static void qrfFinalize(Qrf *p){
 +  switch( p->spec.eStyle ){
 +    case QRF_STYLE_Count: {
 +      sqlite3_str_appendf(p->pOut, "%lld\n", p->nRow);
 +      qrfWrite(p);
 +      break;
 +    }
 +    case QRF_STYLE_Json: {
 +      sqlite3_str_append(p->pOut, "}]\n", 3);
 +      qrfWrite(p);
 +      break;
 +    }
 +    case QRF_STYLE_JObject: {
 +      sqlite3_str_append(p->pOut, "}\n", 2);
 +      qrfWrite(p);
 +      break;
 +    }
 +    case QRF_STYLE_Line: {
 +      if( p->u.sLine.azCol ) sqlite3_free(p->u.sLine.azCol);
 +      break;
 +    }
 +    case QRF_STYLE_Stats:
 +    case QRF_STYLE_StatsEst:
 +    case QRF_STYLE_Eqp: {
 +      qrfEqpRender(p, 0);
 +      qrfWrite(p);
 +      break;
 +    }
 +  }
 +  if( p->spec.pzOutput ){
 +    if( p->spec.pzOutput[0] ){
 +      sqlite3_int64 n, sz;
 +      char *zCombined;
 +      sz = strlen(p->spec.pzOutput[0]);
 +      n = sqlite3_str_length(p->pOut);
 +      zCombined = sqlite3_realloc(p->spec.pzOutput[0], sz+n+1);
 +      if( zCombined==0 ){
 +        sqlite3_free(p->spec.pzOutput[0]);
 +        p->spec.pzOutput[0] = 0;
 +        qrfOom(p);
 +      }else{
 +        p->spec.pzOutput[0] = zCombined;
 +        memcpy(zCombined+sz, sqlite3_str_value(p->pOut), n+1);
 +      }
 +      sqlite3_free(sqlite3_str_finish(p->pOut));
 +    }else{
 +      p->spec.pzOutput[0] = sqlite3_str_finish(p->pOut);
 +    }
 +  }else if( p->pOut ){
 +    sqlite3_free(sqlite3_str_finish(p->pOut));
 +  }
 +  if( p->expMode>0 ){
 +    sqlite3_stmt_explain(p->pStmt, p->expMode-1);
 +  }
 +  if( p->actualWidth ){
 +    sqlite3_free(p->actualWidth);
 +  }
 +  if( p->pJTrans ){
 +    sqlite3 *db = sqlite3_db_handle(p->pJTrans);
 +    sqlite3_finalize(p->pJTrans);
 +    sqlite3_close(db);
 +  }
 +}
 +
 +/*
 +** Run the prepared statement pStmt and format the results according
 +** to the specification provided in pSpec.  Return an error code.
 +** If pzErr is not NULL and if an error occurs, write an error message
 +** into *pzErr.
 +*/
 +int sqlite3_format_query_result(
 +  sqlite3_stmt *pStmt,                 /* Statement to evaluate */
 +  const sqlite3_qrf_spec *pSpec,       /* Format specification */
 +  char **pzErr                         /* Write error message here */
 +){
 +  Qrf qrf;         /* The new Qrf being created */
 +
 +  if( pStmt==0 ) return SQLITE_OK;       /* No-op */
 +  if( pSpec==0 ) return SQLITE_MISUSE;
 +  qrfInitialize(&qrf, pStmt, pSpec, pzErr);
 +  switch( qrf.spec.eStyle ){
 +    case QRF_STYLE_Box:
 +    case QRF_STYLE_Column:
 +    case QRF_STYLE_Markdown: 
 +    case QRF_STYLE_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;
 +    }
 +    case QRF_STYLE_Explain: {
 +      qrfExplain(&qrf);
 +      break;
 +    }
 +    case QRF_STYLE_StatsVm: {
 +      qrfScanStatusVm(&qrf);
 +      break;
 +    }
 +    case QRF_STYLE_Stats:
 +    case QRF_STYLE_StatsEst: {
 +      qrfEqpStats(&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;
 +    }
 +  }
 +  qrfResetStmt(&qrf);
 +  qrfFinalize(&qrf);
 +  return qrf.iErr;
 +}
diff --cc manifest
index 09e9c8cae3cf77a6a4a08600ce152808b11a6bab,9b9b87735066cf364b5e7d13d8da1fa83d087a42..0f5d9d74105c5d6f2bb1716f067cd462937a7d03
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Merge\scompiler-warning\sfix\sfrom\strunk\sinto\sthe\sqrf\sbranch.
- D 2025-11-14T11:06:27.128
 -C Fix\sa\sharmless\scompiler\swarning\sin\stesting\scode.
 -D 2025-11-14T11:02:49.203
++C Fix\svarious\sbugs\sand\scompiler\swarnings.\s\sAll\stests\snow\spassing\son\slinux,\smac,\nand\swindows.\s\sMore\stesting\sneeded,\sthough.
++D 2025-11-14T13:07:45.192
  F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@@ -416,9 -416,6 +416,9 @@@ F ext/misc/wholenumber.c 0fa0c082676b78
  F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c
  F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c
  F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
- F ext/qrf/qrf.c 259d4391d68c865f11eb36f65c8c89da7d9aa0ff2de9a507d9470cb13b7c6e7f
 +F ext/qrf/README.md db3710552dfdacfc600e4c9772f84dccdf84ceb9983837a86269d4f6cb67219a
++F ext/qrf/qrf.c 7175ce8274b1566672cf26e29be06b39547cf6761bf75bdda4d8355f6b5837a9
 +F ext/qrf/qrf.h b4b3489b3b3683523fd248d15cf5945830643b036943efacdb772a3e00367aa2
  F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
  F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
  F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363
@@@ -735,7 -732,7 +735,7 @@@ F src/random.c 606b00941a1d7dd09c381d32
  F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a
  F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
  F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a
- F src/shell.c.in cecf781e9b57d7adbc65bfb76cfcea4351bb5ad859fd822db77ebdc3f75847d6
 -F src/shell.c.in ceb0a9cc008ac82d8d2e6ef353db14a54bc40dfd60a8cfbb6bc98d071f538761
++F src/shell.c.in 4f034dea340795f025160a81b0ea26d27729376df23b0a9bc784cc328e8aefd2
  F src/sqlite.h.in 684c19c3b093cca7a38e3f6405d067777464285f17a58a78f7f89d6763e011e7
  F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
  F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52
@@@ -1507,8 -1503,6 +1507,8 @@@ F test/printf2.test 3f55c1871a5a6550741
  F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
  F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
  F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd
- F test/qrf01.test 8e10a9d5f69db76666191899d0f3e48bf1e1ea55d81905ebb1a3e59aaf9388fc
++F test/qrf01.test 3e863267cf237516a5803d94e19046005cf148623ea24c8646acbd304a598c78
 +F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
  F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
  F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
  F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a
@@@ -1602,17 -1596,16 +1602,17 @@@ F test/sharedA.test 64bdd21216dda2c6a3b
  F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
  F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
  F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
- F test/shell1.test f16539b28a1dd53eb88733b6010350fd861d073adada16a4158d8a3e235e9d0d
 -F test/shell1.test ebe953d64c937ad42a0f33170ac0d2d2568faae26813fc7a95203756446d54aa
 -F test/shell2.test ab23f01ea2347e4b72bb2399af7ee82aa00f9c059141749f7c4064abca5ad728
++F test/shell1.test 6b256fffeac83d62e3625f47e7ef4a25db73215349bb92e818c9692ac403da3d
 +F test/shell2.test d8da6a06dcce1d8f04f776f918d4d57c28ddc28c54f3a44f95429794892e3a91
  F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e
  F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d
 -F test/shell5.test d17e7927ab8b7f720efbdd9b5d05fceb6c3c56c25917901b315400214bf24ef4
 +F test/shell5.test 683f9b5df61192426d030874a04adcb15b5f14c5f3062e2637d4378a3e7224f8
  F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8
  F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3
  F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871
  F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209
 -F test/shellA.test 4ecff8b7b2c0122ba8174abfbcc4b0f59e44d80f2a911068f8cd4cfc6661032d
 +F test/shellA.test 3a3ccbac90367d51f12c3d762c90e84723492e3bc5d228cfefd4c6ebfc72394d
- F test/shellB.test 714c13ba4d964277ab236fa210d8c8e05b5f224ce342981548ef4929794d3768
++F test/shellB.test ca8a5ce5b9a59098732fa140c911162f0306f70239c6c2de6da9b718ca304cad
  F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c
  F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
  F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@@ -2175,8 -2167,8 +2175,8 @@@ F tool/version-info.c 33d0390ef484b3b1c
  F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
  F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
  F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
- P 3300ed34b5a3598c46cdc4bdf1e9e81818a5029585ae917424f64c11c718bfa8 5252a2e629e1adb61169d32ca6458c6decd1ec562f358bb9d0b448a2f0243c56
- R f0481fcb31b39097a1040d0ea2b4dd6d
 -P bf399992cb98e5d5f002a90b521328d5c2f113ebab8601653452d78222077bde
 -R 822e867bdd04a0341d8323d597259571
++P 6ffab43ca32230975e79d91080dfa2e80a4c21deef31ab86455581af18a399cd 5252a2e629e1adb61169d32ca6458c6decd1ec562f358bb9d0b448a2f0243c56
++R 55001368278a1a9bd00f0d0045e947e0
  U drh
- Z b51caee9fb8648c2e805a8f6306d9bd9
 -Z 2d7ed663e8bf5d6719badf286180c421
++Z 4eb8cd588f9e324fca2d92f8eebd5607
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index ebcdd9899be229ff311bdc06b32ad8d034bb2479,04059e98a21ee3212c5a4b4d33eeccb84f58c6d2..6e66bf68bcb7b315200eff81b50435e965e5587d
@@@ -1,1 -1,1 +1,1 @@@
- 6ffab43ca32230975e79d91080dfa2e80a4c21deef31ab86455581af18a399cd
 -5252a2e629e1adb61169d32ca6458c6decd1ec562f358bb9d0b448a2f0243c56
++2220cb70c2f1ee30dcdf917a20feacdfcb3789433d0645fea626fd4c5cf0d099
diff --cc src/shell.c.in
index 3bb057fc980f7afc22d7a2626bb80b96e7e056ba,85ec8f3c0311d4ac7df13f92d3213409a2a13780..49710dc6de68cf4df550eb24daa2ded99cd1f017
@@@ -718,10 -918,30 +718,10 @@@ static void SQLITE_CDECL iotracePrintf(
  ** lower 30 bits of a 32-bit signed integer.
  */
  static int strlen30(const char *z){
 -  const char *z2 = z;
 -  while( *z2 ){ z2++; }
 -  return 0x3fffffff & (int)(z2 - z);
 -}
 -
 -/*
 -** Return the length of a string in characters.  Multibyte UTF8 characters
 -** count as a single character for single-width characters, or as two
 -** characters for double-width characters.
 -*/
 -static int strlenChar(const char *z){
 -  int n = 0;
 -  while( *z ){
 -    if( (0x80&z[0])==0 ){
 -      n++;
 -      z++;
 -    }else{
 -      int u = 0;
 -      int len = decodeUtf8((const u8*)z, &u);
 -      z += len;
 -      n += cli_wcwidth(u);
 -    }
 -  }
 -  return n;
 +  size_t n;
 +  if( z==0 ) return 0;
 +  n = strlen(z);
-   return n>0x3fffffff ? 0x3fffffff : n;
++  return n>0x3fffffff ? 0x3fffffff : (int)n;
  }
  
  /*
@@@ -1677,45 -1752,10 +1677,45 @@@ static void failIfSafeMode
      va_start(ap, zErrMsg);
      zMsg = sqlite3_vmprintf(zErrMsg, ap);
      va_end(ap);
 -    sqlite3_fprintf(stderr, "line %lld: %s\n", p->lineno, zMsg);
 -    exit(1);
 +    cli_printf(stderr, "%s %s\n", zLoc, zMsg);
 +    cli_exit(1);
 +  }
 +}
 +
 +/*
 +** Issue an error message from a dot-command.
 +*/
 +static void dotCmdError(
 +  ShellState *p,       /* Shell state */
 +  int iArg,            /* Index of argument on which error occurred */
 +  const char *zBrief,  /* Brief (<20 character) error description */
 +  const char *zDetail, /* Error details */
 +  ...
 +){
 +  FILE *out = stderr;
 +  char *zLoc = shellErrorLocation(p);
 +  if( zBrief!=0 && iArg>=0 && iArg<p->dot.nArg ){
 +    int i = p->dot.aiOfst[iArg];
-     int nPrompt = strlen(zBrief) + 5;
++    int nPrompt = strlen30(zBrief) + 5;
 +    cli_printf(out, "%s %s\n", zLoc, p->dot.zOrig);
 +    if( i > nPrompt ){
 +      cli_printf(out, "%s %*s%s ---^\n", zLoc, 1+i-nPrompt, "", zBrief);
 +    }else{
 +      cli_printf(out, "%s %*s^--- %s\n", zLoc, i, "", zBrief);
 +    }
 +  }
 +  if( zDetail ){
 +    char *zMsg;
 +    va_list ap;
 +    va_start(ap, zDetail);
 +    zMsg = sqlite3_vmprintf(zDetail,ap);
 +    va_end(ap);
 +    cli_printf(out,"%s %s\n", zLoc, zMsg);
 +    sqlite3_free(zMsg);
    }
 +  sqlite3_free(zLoc);
  }
 +  
  
  /*
  ** SQL function:   edit(VALUE)
        rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
        rc_err_oom_die(rc);
  #endif
 -      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 -      rc_err_oom_die(rc);
 -      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 -      rc_err_oom_die(rc);
 -      sqlite3_bind_int(pStmt, 1, nDigits);
 -      rc = sqlite3_step(pStmt);
 -      sqlite3_finalize(pStmt);
 -      if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
 +      rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
 +      rc_err_oom_die(rc);
 +      rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
 +      rc_err_oom_die(rc);
 +      sqlite3_bind_int(pStmt, 1, nDigits);
 +      rc = sqlite3_step(pStmt);
 +      sqlite3_finalize(pStmt);
 +      if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
 +    }
 +    assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */
 +    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 +    rc_err_oom_die(rc);
 +    rc = sqlite3_step(pStmt);
 +    if( rc==SQLITE_ROW ){
 +      zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +    }else{
 +      zColsSpec = 0;
 +    }
 +    if( pzRenamed!=0 ){
 +      if( !hasDupes ) *pzRenamed = 0;
 +      else{
 +        sqlite3_finalize(pStmt);
 +        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 +            && SQLITE_ROW==sqlite3_step(pStmt) ){
 +          *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +        }else
 +          *pzRenamed = 0;
 +      }
 +    }
 +    sqlite3_finalize(pStmt);
 +    sqlite3_close(*pDb);
 +    *pDb = 0;
 +    return zColsSpec;
 +  }
 +}
 +
 +/*
 +** Check if the sqlite_schema table contains one or more virtual tables. If
 +** parameter zLike is not NULL, then it is an SQL expression that the
 +** sqlite_schema row must also match. If one or more such rows are found,
 +** print the following warning to the output:
 +**
 +** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled
 +*/
 +static int outputDumpWarning(ShellState *p, const char *zLike){
 +  int rc = SQLITE_OK;
 +  sqlite3_stmt *pStmt = 0;
 +  shellPreparePrintf(p->db, &rc, &pStmt,
 +    "SELECT 1 FROM sqlite_schema o WHERE "
 +    "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true"
 +  );
 +  if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 +    cli_puts("/* WARNING: "
 +          "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n",
 +          p->out
 +    );
 +  }
 +  shellFinalize(&rc, pStmt);
 +  return rc;
 +}
 +
 +/*
 +** Fault-Simulator state and logic.
 +*/
 +static struct {
 +  int iId;           /* ID that triggers a simulated fault.  -1 means "any" */
 +  int iErr;          /* The error code to return on a fault */
 +  int iCnt;          /* Trigger the fault only if iCnt is already zero */
 +  int iInterval;     /* Reset iCnt to this value after each fault */
 +  int eVerbose;      /* When to print output */
 +  int nHit;          /* Number of hits seen so far */
 +  int nRepeat;       /* Turn off after this many hits.  0 for never */
 +  int nSkip;         /* Skip this many before first fault */
 +} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0};
 +
 +/*
 +** This is the fault-sim callback
 +*/
 +static int faultsim_callback(int iArg){
 +  if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){
 +    return SQLITE_OK;
 +  }
 +  if( faultsim_state.iCnt ){
 +    if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--;
 +    if( faultsim_state.eVerbose>=2 ){
 +      cli_printf(stdout, 
 +         "FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt);
 +    }
 +    return SQLITE_OK;
 +  }
 +  if( faultsim_state.eVerbose>=1 ){
 +    cli_printf(stdout, 
 +         "FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr);
 +  }
 +  faultsim_state.iCnt = faultsim_state.iInterval;
 +  faultsim_state.nHit++;
 +  if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){
 +    faultsim_state.iCnt = -1;
 +  }
 +  return faultsim_state.iErr;
 +}
 +
 +/*
 +** pickStr(zArg, &zErr, zS1, zS2, ..., "");
 +**
 +** Try to match zArg against zS1, zS2, and so forth until the first
 +** emptry string.  Return the index of the match or -1 if none is found.
 +** If no match is found, and &zErr is not NULL, then write into
 +** zErr a message describing the valid choices.
 +*/
 +static int pickStr(const char *zArg, char **pzErr, ...){
 +  int i, n;
 +  const char *z;
 +  sqlite3_str *pMsg;
 +  va_list ap;
 +  va_start(ap, pzErr);
 +  i = 0;
 +  while( (z = va_arg(ap,const char*))!=0 && z[0]!=0 ){
 +    if( cli_strcmp(zArg, z)==0 ) return i;
 +    i++;
 +  }
 +  va_end(ap);
 +  if( pzErr==0 ) return -1;
 +  n = i;
 +  pMsg = sqlite3_str_new(0);
 +  va_start(ap, pzErr);
 +  sqlite3_str_appendall(pMsg, "should be");
 +  i = 0;
 +  while( (z = va_arg(ap, const char*))!=0 && z[0]!=0 ){
 +    if( i==n-1 ){
 +      sqlite3_str_append(pMsg,", or",4);
 +    }else if( i>0 ){
 +      sqlite3_str_append(pMsg, ",", 1);
 +    }
 +    sqlite3_str_appendf(pMsg, " %s", z);
 +    i++;
 +  }
 +  va_end(ap);
 +  *pzErr = sqlite3_str_finish(pMsg);
 +  return -1;
 +}
 +
 +/*
 +** This function computes what to show the user about the configured
 +** titles (or column-names).  Output is an integer between 0 and 3:
 +**
 +**    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
 +**
 +** Inputs are:
 +**
 +**    spec.bTitles   (bT)     Whether or not to show the titles
 +**    spec.eTitle    (eT)     The actual encoding to be used for titles
 +**    ModeInfo.bHdr  (bH)     Default value for spec.bTitles
 +**    ModeInfo.eHdr  (eH)     Default value for spec.eTitle
 +**    bAll                    Whether the -v option is used
 +*/
 +static int modeTitleDsply(ShellState *p, int bAll){
 +  int eMode = p->mode.eMode;
 +  const ModeInfo *pI = &aModeInfo[eMode];
 +  int bT = p->mode.spec.bTitles;
 +  int eT = p->mode.spec.eTitle;
 +  int bH = pI->bHdr;
 +  int eH = pI->eHdr;
 +
 +  /* Variable "v" is the truth table that will determine the answer
 +  **
 +  **                   Actual encoding is different from default 
 +  **                   vvvvvvvv                                 */
 +  sqlite3_uint64 v = 0x0133013311220102;
 +  /*                   ^^^^    ^^^^
 +  **                   Upper 2-byte groups for when ON/OFF disagrees with
 +  **                   the default.                                         */
 +
 +  if( bH==0 ) return 0;  /* Header not appliable.  Ex: off, count */
 +
 +  if( eT==0 ) eT = eH;   /* Fill in missing spec.eTitle */
 +  if( bT==0 ) bT = bH;   /* Fill in missing spec.bTitles */
 +
 +  if( eT!=eH ) v >>= 32; /* Encoding disagree in upper 4-bytes */
 +  if( bT!=bH ) v >>= 16; /* ON/OFF disagree in upper 2-byte pairs */
 +  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 */
 +}
 +
 +/*
 +** DOT-COMMAND:  .mode
 +**
 +** USAGE:  .mode [MODE] [OPTIONS]
 +**
 +** Change the output mode to MODE and/or apply OPTIONS to the
 +** output mode.  If no arguments, show the current output mode
 +** and relevant options.
 +**
 +** Options:
 +**
 +**   --align  STRING          Set the alignment of text in columnar modes
 +**                            String consists of characters 'L', 'C', 'R'
 +**                            meaning "left", "centered", and "right", with
 +**                            one letter per column starting from the left.
 +**                            Unspecified alignment defaults to 'L'.
 +**
 +**   --charlimit N            Set the maximum number of output characters to
 +**                            show for any single SQL value to N. Longer values
 +**                            truncated. Zero means "no limit".
 +**
 +**   --colsep  STRING         Use STRING as the column separator
 +**
 +**   --escape  ESC            Enable/disable escaping of control characters
 +**                            in output. ESC can be "off", "ascii", or
 +**                            "symbol".
 +**
 +**   --linelimit N            Set the maximum number of output lines to show for
 +**                            any single SQL value to N. Longer values are
 +**                            truncated. Zero means "no limit". Only works
 +**                            in "line" mode and in columnar modes.
 +**
 +**   --null  STRING           Render SQL NULL values as the given string
 +**
 +**   --quote  ARG             Enable/disable quoting of text. ARG can be
 +**                            "off", "on", "sql", "csv", "html", "tcl",
 +**                            or "json".  "off" means show the text as-is.
 +**                            "on and "sql" are synonyms.
 +**
 +**   --reset                  Changes all mode settings back to their default.
 +**
 +**   --rowsep  STRING         Use STRING as the row separator
 +**
 +**   --screenwidth N          Declare the screen width of the output device
 +**                            to be N characters.  An attempt may be made to
 +**                            wrap output text to fit within this limit. Zero
 +**                            means "no limit".  Or N can be "auto" to set the
 +**                            width automatically.
 +**
 +**   --tablename  NAME        Set the name of the table for "insert" mode.
 +**
 +**   --textjsonb  BOOLEAN     If enabled, JSONB text is displayed as text JSON.
 +**
 +**   --title  ARG             Whether or not to show column headers, and if so
 +**                            how to encode them.  ARG can be "off", "on",
 +**                            "sql", "csv", "html", "tcl", or "json".
 +**
 +**   -v|--verbose             Verbose output
 +**
 +**   --widths  LIST           Set the columns widths for columnar modes. The
 +**                            argument is a list of integers, one for each
 +**                            column. A "0" width means use a dynamic width
 +**                            based on the actual width of data. If there are
 +**                            fewer entries in LIST than columns, "0" is used
 +**                            for the unspecified widths.
 +**
 +**   --wordwrap  BOOLEAN      Enable/disable word wrapping
 +**
 +**   --wrap  N                Wrap columns wider than N characters
 +**
 +**   --ww                     Shorthand for "--wordwrap on"
 +*/
 +static int dotCmdMode(ShellState *p){
 +  int nArg = p->dot.nArg;     /* Number of arguments */
 +  char **azArg = p->dot.azArg;/* Argument list */
 +  int eMode = -1;             /* New mode value, or -1 for none */
 +  int iMode = -1;             /* Index of the argument that is the mode name */
 +  int i;                      /* Loop counter */
 +  int k;                      /* Misc index variable */
 +  int chng = 0;               /* True if anything has changed */
 +  int bAll = 0;               /* Show all details of the mode */
 +
 +  for(i=1; i<nArg; i++){
 +    const char *z = azArg[i];
 +    if( z[0]=='-' && z[1]=='-' ) z++;
 +    if( z[0]!='-'
 +     && iMode<0
 +     && (eMode = modeFind(azArg[i]))>=0
 +     && eMode!=MODE_Www
 +    ){
 +      iMode = i;
 +      modeChange(&p->mode, eMode);
 +      /* (Legacy) If the mode is MODE_Insert and the next argument
 +      ** is not an option, then the next argument must be the table
 +      ** name.
 +      */
 +      if( i+1<nArg && azArg[i+1][0]!='-' ){
 +        i++;
 +        modeSetStr(&p->mode.spec.zTableName, azArg[i]);
 +      }
 +      chng = 1;
 +    }else if( optionMatch(z,"align") ){
 +      char *zAlign;
 +      int nAlign;
 +      int nErr = 0;
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      i++;
 +      zAlign = azArg[i];
 +      nAlign = 0x3fff & strlen(zAlign);
 +      free(p->mode.spec.aAlign);
 +      p->mode.spec.aAlign = malloc(nAlign);
 +      shell_check_oom(p->mode.spec.aAlign);
 +      for(k=0; k<nAlign; k++){
 +        unsigned char c = 0;
 +        switch( zAlign[k] ){
 +          case 'l': case 'L':   c = QRF_ALIGN_Left;   break;
 +          case 'c': case 'C':   c = QRF_ALIGN_Center; break;
 +          case 'r': case 'R':   c = QRF_ALIGN_Right;  break;
 +          default:  nErr++; break;
 +        }
 +        p->mode.spec.aAlign[k] = c;
 +      }
 +      p->mode.spec.nAlign = nAlign;
 +      chng = 1;
 +      if( nErr ){
 +        dotCmdError(p, i, "bad alignment string",
 +             "Should contain only characters L, C, and R.");
 +        return 1;
 +      }
 +    }else if( 0<=(k=pickStr(z,0,"-charlimit","-linelimit","")) ){
 +      int w;                /*   0            1  */
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      w = integerValue(azArg[++i]);
 +      if( k==0 ){
 +        p->mode.spec.nCharLimit = w;
 +      }else{
 +        p->mode.spec.nLineLimit = 2;
 +      }
 +      chng = 1;
 +    }else if( 0<=(k=pickStr(z,0,"-tablename","-rowsep","-colsep","-null","")) ){
 +                            /*   0            1         2         3 */
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      i++;
 +      switch( k ){
 +        case 0: modeSetStr(&p->mode.spec.zTableName, azArg[i]); break;
 +        case 1: modeSetStr(&p->mode.spec.zRowSep, azArg[i]);    break;
 +        case 2: modeSetStr(&p->mode.spec.zColumnSep, azArg[i]); break;
 +        case 3: modeSetStr(&p->mode.spec.zNull, azArg[i]);      break;
 +      }
 +      chng = 1;
 +    }else if( optionMatch(z,"escape") ){
 +      /* See similar code at tag-20250224-1 */
 +      char *zErr = 0;
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      i++;                    /*  0     1       2  <-- One less than QRF_ESC_ */
 +      k = pickStr(azArg[i],&zErr,"off","ascii","symbol","");
 +      if( k<0 ){
 +        dotCmdError(p, i, "unknown escape type", "%s", zErr);
 +        sqlite3_free(zErr);
 +        return 1;
 +      }
 +      p->mode.spec.eEsc = k+1;
 +      chng = 1;
 +    }else if( optionMatch(z,"quote") ){
 +      if( i+1<nArg
 +       && azArg[i+1][0]!='-'
 +       && (iMode>0 || modeFind(azArg[i+1])<0)
 +      ){
 +        /* --quote is followed by an argument other that is not an option
 +        ** or a mode name.  See it must be a boolean or a keyword to describe
 +        ** how to set quoting. */
 +        i++;
 +        if( (k = pickStr(azArg[i],0,"no","yes","0","1",""))>=0 ){
 +          k &= 1;   /* 0 for "off".  1 for "on". */
 +        }else{
 +          char *zErr = 0;         /*  0     1    2     3     4      5     6 */
 +          k = pickStr(azArg[i],&zErr,"off","on","sql","csv","html","tcl","json",
 +                                     "");
 +          if( k<0 ){
 +            dotCmdError(p, i, "unknown", "%z", zErr);
 +            return 1;
 +          }
 +        }
 +      }else{
 +        /* (Legacy) no following boolean argument.  Turn quoting on */ 
 +        k = 1;
 +      }
 +      switch( k ){
 +        case 1:  /* on */
 +        case 2:  /* sql */
 +          p->mode.spec.eText = QRF_TEXT_Sql;
 +          p->mode.spec.eBlob = QRF_BLOB_Sql;
 +          break;
 +        case 3:  /* csv */
 +          p->mode.spec.eText = QRF_TEXT_Csv;
 +          p->mode.spec.eBlob = QRF_BLOB_Text;
 +          break;
 +        case 4:  /* html */
 +          p->mode.spec.eText = QRF_TEXT_Html;
 +          p->mode.spec.eBlob = QRF_BLOB_Text;
 +          break;
 +        case 5:  /* tcl */
 +          p->mode.spec.eText = QRF_TEXT_Tcl;
 +          p->mode.spec.eBlob = QRF_BLOB_Text;
 +          break;
 +        case 6:  /* json */
 +          p->mode.spec.eText = QRF_TEXT_Json;
 +          p->mode.spec.eBlob = QRF_BLOB_Json;
 +          break;
 +        default:  /* off */
 +          p->mode.spec.eText = QRF_TEXT_Plain;
 +          p->mode.spec.eBlob = QRF_BLOB_Text;
 +          break;
 +      }
 +      chng = 1;
 +    }else if( optionMatch(z,"noquote") ){
 +      /* (undocumented legacy) --noquote always turns quoting off */
 +      p->mode.spec.eText = QRF_TEXT_Plain;
 +      p->mode.spec.eBlob = QRF_BLOB_Text;
 +      chng = 1;
 +    }else if( optionMatch(z,"reset") ){
-       int eMode = p->mode.eMode;
++      int saved_eMode = p->mode.eMode;
 +      modeFree(&p->mode);
-       modeChange(&p->mode, eMode);
++      modeChange(&p->mode, saved_eMode);
 +    }else if( optionMatch(z,"screenwidth") ){
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      k = pickStr(azArg[i+1],0,"off","auto","");
 +      if( k==0 ){
 +        p->mode.bAutoScreenWidth = 0;
 +        p->mode.spec.nScreenWidth = 0;
 +      }else if( k==1 ){
 +        p->mode.bAutoScreenWidth = 1;
 +      }else{
 +        i64 w = integerValue(azArg[i+1]);
 +        p->mode.bAutoScreenWidth = 0;
 +        if( w<0 ) w = 0;
 +        if( w>QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH;
 +        p->mode.spec.nScreenWidth = w;
 +      }
 +      i++;
 +      chng = 1;
 +    }else if( optionMatch(z,"textjsonb") ){
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      p->mode.spec.bTextJsonb = booleanValue(azArg[++i]) ? QRF_Yes : QRF_No;
 +      chng = 1;
 +    }else if( optionMatch(z,"titles") ){
 +      char *zErr = 0;
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      k = pickStr(azArg[++i],&zErr,
 +              "off","on","plain","sql","csv","html","tcl","json","");
 +          /*   0     1    2       3     4     5     6      7 */
 +      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.spec.eTitle = k>1 ? k-1 : aModeInfo[p->mode.eMode].eHdr;
 +      chng = 1;
-     }else if( optionMatch(z,"widths") ){
++    }else if( optionMatch(z,"widths") || optionMatch(z,"width") ){
 +      int nWidth = 0;
 +      short int *aWidth;
-       const char *z;
++      const char *zW;
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
-       z = azArg[++i];
++      zW = azArg[++i];
 +      /* Every width value takes at least 2 bytes in the input string to
-       ** specify, so strlen(z) bytes should be plenty of space to hold the
++      ** specify, so strlen(zW) bytes should be plenty of space to hold the
 +      ** result. */
-       aWidth = malloc( strlen(z) );
-       while( isspace(z[0]) ) z++;
-       while( z[0] ){
++      aWidth = malloc( strlen(zW) );
++      while( isspace(zW[0]) ) zW++;
++      while( zW[0] ){
 +        int w = 0;
-         k = z[0]=='-' && isdigit(z[1]);
-         while( isdigit(z[k]) ){
-           w = w*10 + z[k] - '0';
++        k = zW[0]=='-' && isdigit(zW[1]);
++        while( isdigit(zW[k]) ){
++          w = w*10 + zW[k] - '0';
 +          if( w>QRF_MAX_WIDTH ){
 +            dotCmdError(p,i+1,"width too big",
 +                        "Maximum column width is %d", QRF_MAX_WIDTH);
 +            free(aWidth);
 +            return 1;
 +          }
 +          k++;
 +        }
-         if( z[0]=='-' ) w = -w;
++        if( zW[0]=='-' ) w = -w;
 +        aWidth[nWidth++] = w;
-         z += k;
-         if( z[0]==',' ) z++;
-         while( isspace(z[0]) ) z++;
++        zW += k;
++        if( zW[0]==',' ) zW++;
++        while( isspace(zW[0]) ) zW++;
 +      }
 +      free(p->mode.spec.aWidth);
 +      p->mode.spec.aWidth = aWidth;
 +      p->mode.spec.nWidth = nWidth;
 +      chng = 1;
 +    }else if( optionMatch(z,"wrap") ){
 +      int w;
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      w = integerValue(azArg[++i]);
 +      if( w<(-QRF_MAX_WIDTH) ) w = -QRF_MAX_WIDTH;
 +      if( w>QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH;
 +      p->mode.spec.nWrap = w;
 +      chng = 1;
 +    }else if( optionMatch(z,"ww") ){
 +      p->mode.spec.bWordWrap = QRF_Yes;
 +      chng = 1;
 +    }else if( optionMatch(z,"wordwrap") ){
 +      if( i+1>=nArg ){
 +        dotCmdError(p, i, "missing argument", 0);
 +        return 1;
 +      }
 +      p->mode.spec.bWordWrap = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No;
 +      chng = 1;
 +    }else if( optionMatch(z,"v") || optionMatch(z,"verbose") ){
 +      bAll = 1;
 +    }else if( z[0]=='-' ){
 +      dotCmdError(p, i, "bad option", "Use \".help .mode\" for more info");
 +      return 1;
 +    }else{
 +      dotCmdError(p, i, iMode>0?"bad argument":"unknown mode",
 +                  "Use \".help .mode\" for more info");
 +      return 1;
 +    }
 +  }
 +  if( !chng || bAll ){
 +    const ModeInfo *pI = aModeInfo + p->mode.eMode;
 +    sqlite3_str *pDesc = sqlite3_str_new(p->db);
 +    char *zDesc;
 +    const char *zSetting;
 +
 +    sqlite3_str_appendall(pDesc,pI->zName);
 +    if( bAll || (p->mode.spec.nAlign && pI->eCx==2) ){
-       int i;
++      int ii;
 +      sqlite3_str_appendall(pDesc, " --align \"");
-       for(i=0; i<p->mode.spec.nAlign; i++){
-         unsigned char a = p->mode.spec.aAlign[i];
++      for(ii=0; ii<p->mode.spec.nAlign; ii++){
++        unsigned char a = p->mode.spec.aAlign[ii];
 +        sqlite3_str_appendchar(pDesc, 1, "LLCR"[a&3]);
 +      }
 +      sqlite3_str_append(pDesc, "\"", 1);
 +    }
 +    if( bAll || p->mode.spec.nCharLimit>0 ){
 +      sqlite3_str_appendf(pDesc, " --charlimit %d",p->mode.spec.nCharLimit);
 +    }
 +    zSetting = aModeStr[pI->eCSep];
 +    if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zColumnSep)!=0) ){
 +      sqlite3_str_appendf(pDesc, " --colsep ");
 +      append_c_string(pDesc, p->mode.spec.zColumnSep);
 +    }
 +    if( bAll || p->mode.spec.eEsc!=QRF_Auto ){
 +      sqlite3_str_appendf(pDesc, " --escape %s",qrfEscNames[p->mode.spec.eEsc]);
 +    }
 +    if( bAll || (p->mode.spec.nLineLimit>0 && pI->eCx>0) ){
 +      sqlite3_str_appendf(pDesc, " --linelimit %d",p->mode.spec.nLineLimit);
 +    }
 +    zSetting = aModeStr[pI->eNull];
 +    if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zNull)!=0) ){
 +      sqlite3_str_appendf(pDesc, " --null ");
 +      append_c_string(pDesc, p->mode.spec.zNull);
 +    }
 +    if( bAll 
 +     || (pI->eText!=p->mode.spec.eText && (pI->eText>1 || p->mode.spec.eText>1))
 +    ){
 +      sqlite3_str_appendf(pDesc," --quote %s",qrfQuoteNames[p->mode.spec.eText]);
 +    }
 +    zSetting = aModeStr[pI->eRSep];
 +    if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zRowSep)!=0) ){
 +      sqlite3_str_appendf(pDesc, " --rowsep ");
 +      append_c_string(pDesc, p->mode.spec.zRowSep);
 +    }
 +    if( bAll
 +     || (pI->eCx && (p->mode.spec.nScreenWidth>0 || p->mode.bAutoScreenWidth))
 +    ){
 +      if( p->mode.bAutoScreenWidth ){
 +        sqlite3_str_appendall(pDesc, " --screenwidth auto");
 +      }else{
 +        sqlite3_str_appendf(pDesc," --screenwidth %d",
 +                            p->mode.spec.nScreenWidth);
 +      }
 +    }
 +    if( bAll || p->mode.eMode==MODE_Insert ){
 +      sqlite3_str_appendf(pDesc," --tablename ");
 +      append_c_string(pDesc, p->mode.spec.zTableName);
 +    }
 +    if( bAll || p->mode.spec.bTextJsonb ){
 +      sqlite3_str_appendf(pDesc," --textjsonb %s",
 +             p->mode.spec.bTextJsonb==QRF_Yes ? "on" : "off");
 +    }
 +    k = modeTitleDsply(p, bAll);
 +    if( k==1 ){
 +      sqlite3_str_appendall(pDesc, " --titles off");
 +    }else if( k==2 ){
 +      sqlite3_str_appendall(pDesc, " --titles on");
 +    }else if( k==3 ){
 +      static const char *azTitle[] =
 +          { "plain", "sql", "csv", "html", "tcl", "json"};
 +      sqlite3_str_appendf(pDesc, " --titles %s",
 +                   azTitle[p->mode.spec.eTitle-1]);
 +    }    
 +    if( p->mode.spec.nWidth>0 && (bAll || pI->eCx==2) ){
-       int i;
++      int ii;
 +      const char *zSep = " --widths ";
-       for(i=0; i<p->mode.spec.nWidth; i++){
-         sqlite3_str_appendf(pDesc, "%s%d", zSep, (int)p->mode.spec.aWidth[i]);
++      for(ii=0; ii<p->mode.spec.nWidth; ii++){
++        sqlite3_str_appendf(pDesc, "%s%d", zSep, (int)p->mode.spec.aWidth[ii]);
 +        zSep = ",";
 +      }
 +    }else if( bAll ){
 +      sqlite3_str_appendall(pDesc, " --widths \"\"");
 +    }
 +    if( bAll || (pI->eCx>0 && p->mode.spec.bWordWrap) ){
 +      if( bAll ){
 +        sqlite3_str_appendf(pDesc, " --wordwrap %s",
 +          p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off");
 +      }
 +      if( p->mode.spec.nWrap ){
 +        sqlite3_str_appendf(pDesc, " --wrap %d", p->mode.spec.nWrap);
 +      }
 +      if( !bAll ) sqlite3_str_append(pDesc, " --ww", 5);
 +    }
 +    zDesc = sqlite3_str_finish(pDesc);
 +    cli_printf(p->out, "current output mode: %s\n", zDesc);
 +    sqlite3_free(zDesc);
 +  }
 +  return 0;
 +}
 +
 +/*
 +** DOT-COMMAND: .output [OPTIONS] [FILE]
 +** ONELINER:     Redirect output
 +**
 +** Begin redirecting output to FILE.  Or if FILE is omitted, revert
 +** to sending output to the console.  If FILE begins with "|" then
 +** the remainder of file is taken as a pipe and output is directed
 +** into that pipe.  If FILE is "memory" then output is captured in an
 +** internal memory buffer.  If FILE is "off" then output is redirected
 +** into /dev/null or the equivalent.
 +**
 +** Options:
 +**
 +**   --bom             Prepend a byte-order mark to the output
 +**
 +**   -e                Accumulate output in a temporary text file then
 +**                     launch a text editor when the redirection ends.
 +**
 +**   --error-prefix X  Use X as the left-margin prefix for error messages.
 +**                     Set to an empty string to restore the default.
 +**
 +**   --glob GLOB       Raise an error if the memory buffer does not match
 +**                     the GLOB pattern.
 +**
 +**   --keep            Continue using the same "memory" buffer.  Do not
 +**                     reset it or delete it.  Useful in combination with
 +**                     --glob, --not-glob, and/or --verify.
 +**
 +**   ---notglob GLOB   Raise an error if the memory buffer does not match
 +**                     the GLOB pattern.
 +**
 +**   --plain           Use plain text rather than HTML tables with -w
 +**
 +**   --show            Write the memory buffer to the screen, for debugging.
 +**
 +**   --verify ENDMARK  Read subsequent lines of text until the first line
 +**                     that matches ENDMARK.  Discard the ENDMARK.  Compare
 +**                     the text against the accumulated output in memory and
 +**                     raise an error if there are any differences.
 +**
 +**   -w                Show the output in a web browser.  Output is
 +**                     written into a temporary HTML file until the
 +**                     redirect ends, then the web browser is launched.
 +**                     Query results  are shown as HTML tables, unless
 +**                     the --plain is used too.
 +**
 +**   -x                Show the output in a spreadsheet.  Output is
 +**                     written to a temp file as CSV then the spreadsheet
 +**                     is launched when
 +**
 +** DOT-COMMAND: .once [OPTIONS] FILE ...
 +** ONELINER:     Redirect output for the next SQL statement or dot-command
 +**
 +** Write the output for the next line of SQL or the next dot-command into
 +** FILE.  If FILE begins with "|" then it is a program into which output
 +** is written. The FILE argument should be omitted if one of the -e, -w,
 +** or -x options is used.
 +**
 +** Options:
 +**
 +**   -e                Capture output into a temporary file then bring up
 +**                     a text editor on that temporary file.
 +**
 +**   --plain           Use plain text rather than HTML tables with -w
 +**
 +**   -w                Capture output into an HTML file then bring up that
 +**                     file in a web browser
 +**
 +**   -x                Show the output in a spreadsheet.  Output is
 +**                     written to a temp file as CSV then the spreadsheet
 +**                     is launched when
 +**
 +** DOT-COMMAND: .excel
 +** ONELINER:    Display results of the next SQL statement in a spreadsheet
 +**
 +** Shorthand for ".once -x"
 +**
 +** DOT-COMMAND: .www [--plain]
 +** ONELINER:    Display result of the next SQL statement in a web browser
 +**
 +** Shorthand for ".once -w" or ".once --plain -w"
 +*/
 +static int dotCmdOutput(ShellState *p){
 +  int nArg = p->dot.nArg;        /* Number of arguments */
 +  char **azArg = p->dot.azArg;   /* Text of the arguments */
 +  char *zFile = 0;               /* The FILE argument */
 +  int i;                  /* Loop counter */
 +  int eMode = 0;          /* 0: .outout/.once, 'x'=.excel, 'w'=.www */
 +  int bOnce = 0;          /* 0: .output, 1: .once, 2: .excel/.www */
 +  int bPlain = 0;         /* --plain option */
 +  int bKeep = 0;          /* --keep option */
 +  char *zCheck = 0;       /* Argument to --glob, --notglob, --verify */
 +  int eCheck = 0;         /* 1: --glob,  2: --notglob,  3: --verify */
 +  static const char *zBomUtf8 = "\357\273\277";
 +  const char *zBom = 0;
 +  char c = azArg[0][0];
 +  int n = strlen30(azArg[0]);
 +
 +  failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
 +  if( c=='e' ){
 +    eMode = 'x';
 +    bOnce = 2;
 +  }else if( c=='w' ){
 +    eMode = 'w';
 +    bOnce = 2;
 +  }else if( n>=2 && cli_strncmp(azArg[0],"once",n)==0 ){
 +    bOnce = 1;
 +  }
 +  for(i=1; i<nArg; i++){
 +    char *z = azArg[i];
 +    if( z[0]=='-' ){
 +      if( z[1]=='-' ) z++;
 +      if( cli_strcmp(z,"-bom")==0 ){
 +        zBom = zBomUtf8;
 +      }else if( cli_strcmp(z,"-plain")==0 ){
 +        bPlain = 1;
 +      }else if( c=='o' && z[0]=='1' && z[1]!=0 && z[2]==0
 +             && (z[1]=='x' || z[1]=='e' || z[1]=='w') ){
 +        if( bKeep || eMode || eCheck ){
 +          dotCmdError(p, i, "incompatible with prior options",0);
 +          goto dotCmdOutput_error;
 +        }
 +        eMode = z[1];
 +      }else if( cli_strcmp(z,"-keep")==0 ){
 +        bKeep = 1;
 +      }else if( cli_strcmp(z,"-show")==0 ){
 +        if( cli_output_capture ){
 +          sqlite3_fprintf(stdout, "%s", sqlite3_str_value(cli_output_capture));
 +        }
 +        bKeep = 1;
 +      }else if( cli_strcmp(z,"-glob")==0
 +             || cli_strcmp(z,"-notglob")==0
 +             || cli_strcmp(z,"-verify")==0
 +      ){
 +        if( eCheck || eMode ){
 +          dotCmdError(p, i, "incompatible with prior options",0);
 +          goto dotCmdOutput_error;
 +        }
 +        if( i+1>=nArg ){
 +          dotCmdError(p, i, "missing argument", 0);
 +          goto dotCmdOutput_error;
 +        }
 +        zCheck = azArg[++i];
 +        eCheck = z[1]=='g' ? 1 : z[1]=='n' ? 2 : 3;
 +      }else if( optionMatch(z,"error-prefix") ){
 +        if( i+1>=nArg ){
 +          dotCmdError(p, i, "missing argument", 0);
 +          return 1;
 +        }
 +        free(p->zErrPrefix);
 +        i++;
 +        p->zErrPrefix = azArg[i][0]==0 ? 0 : strdup(azArg[i]);
 +      }else{
 +        dotCmdError(p, i, "unknown option", 0);
 +        sqlite3_free(zFile);
 +        return 1;
 +      }
 +    }else if( zFile==0 && eMode==0 ){
 +      if( bKeep || eCheck ){
 +        dotCmdError(p, i, "incompatible with prior options",0);
 +        goto dotCmdOutput_error;
 +      }
 +      if( cli_strcmp(z, "memory")==0 && bOnce ){
 +        dotCmdError(p, 0, "cannot redirect to \"memory\"", 0);
 +        goto dotCmdOutput_error;
 +      }
 +      if( cli_strcmp(z, "off")==0 ){
 +#ifdef _WIN32
 +        zFile = sqlite3_mprintf("nul");
 +#else
 +        zFile = sqlite3_mprintf("/dev/null");
 +#endif
 +      }else{
 +        zFile = sqlite3_mprintf("%s", z);
 +      }
 +      if( zFile && zFile[0]=='|' ){
 +        while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
 +        break;
 +      }
 +    }else{
 +      dotCmdError(p, i, "surplus argument", 0);
 +      sqlite3_free(zFile);
 +      return 1;
 +    }
 +  }
 +  if( zFile==0 && !bKeep ){
 +    zFile = sqlite3_mprintf("stdout");
 +    shell_check_oom(zFile);
 +  }
 +  if( bOnce ){
 +    p->outCount = 2;
 +  }else{
 +    p->outCount = 0;
 +  }
 +  if( eCheck ){
 +    char *zTest;
 +    if( cli_output_capture ){
 +      zTest = sqlite3_str_value(cli_output_capture);
 +    }else{
 +      zTest = "";
 +    }
 +    p->nTestRun++;
 +    if( eCheck==3 ){
 +      int nCheck = strlen30(zCheck);
 +      sqlite3_str *pPattern = sqlite3_str_new(p->db);
 +      char *zPattern;
 +      sqlite3_int64 iStart = p->lineno;
 +      char zLine[2000];
 +      while( sqlite3_fgets(zLine,sizeof(zLine),p->in) ){
 +        if( strchr(zLine,'\n') ) p->lineno++;
 +        if( cli_strncmp(zCheck,zLine,nCheck)==0 ) break;
 +        sqlite3_str_appendall(pPattern, zLine);
 +      }
 +      zPattern = sqlite3_str_finish(pPattern);
 +      if( cli_strcmp(zPattern,zTest)!=0 ){
 +        sqlite3_fprintf(stderr,
 +            "%s:%lld: --verify does matches prior output\n",
 +          p->zInFile, iStart);
 +        p->nTestErr++;
 +      }
 +      sqlite3_free(zPattern);
 +    }else{
 +      char *zGlob = sqlite3_mprintf("*%s*", zCheck);
 +      if( eCheck==1 && sqlite3_strglob(zGlob, zTest)!=0 ){
 +        sqlite3_fprintf(stderr,
 +            "%s:%lld: --glob \"%s\" does not match prior output\n",
 +            p->zInFile, p->lineno, zCheck);
 +        p->nTestErr++;       
 +      }else if( eCheck==2 && sqlite3_strglob(zGlob, zTest)==0 ){
 +        sqlite3_fprintf(stderr,
 +            "%s:%lld: --notglob \"%s\" matches prior output\n",
 +            p->zInFile, p->lineno, zCheck);
 +        p->nTestErr++;       
 +      }
 +      sqlite3_free(zGlob);
 +    }
 +  }
 +  if( !bKeep ) output_reset(p);
 +#ifndef SQLITE_NOHAVE_SYSTEM
 +  if( eMode=='e' || eMode=='x' || eMode=='w' ){
 +    p->doXdgOpen = 1;
 +    outputModePush(p);
 +    if( eMode=='x' ){
 +      /* spreadsheet mode.  Output as CSV. */
 +      newTempFile(p, "csv");
 +      p->mode.bEcho = 0;
 +      p->mode.eMode = MODE_Csv;
 +      modeSetStr(&p->mode.spec.zColumnSep, SEP_Comma);
 +      modeSetStr(&p->mode.spec.zRowSep, SEP_CrLf);
 +#ifdef _WIN32
 +      zBom = zBomUtf8;  /* Always include the BOM on Windows, as Excel does
 +                        ** not work without it. */
 +#endif
 +    }else if( eMode=='w' ){
 +      /* web-browser mode. */
 +      newTempFile(p, "html");
 +      if( !bPlain ) p->mode.eMode = MODE_Www;
 +    }else{
 +      /* text editor mode */
 +      newTempFile(p, "txt");
      }
 -    assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */
 -    rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
 -    rc_err_oom_die(rc);
 -    rc = sqlite3_step(pStmt);
 -    if( rc==SQLITE_ROW ){
 -      zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 +    sqlite3_free(zFile);
 +    zFile = sqlite3_mprintf("%s", p->zTempFile);
 +  }
 +#endif /* SQLITE_NOHAVE_SYSTEM */
 +  if( !bKeep ) shell_check_oom(zFile);
 +  if( bKeep ){
 +    /* no-op */
 +  }else if( cli_strcmp(zFile,"memory")==0 ){
 +    if( cli_output_capture ){
 +      sqlite3_free(sqlite3_str_finish(cli_output_capture));
 +    }
 +    cli_output_capture = sqlite3_str_new(0);
 +  }else if( zFile[0]=='|' ){
 +#ifdef SQLITE_OMIT_POPEN
 +    eputz("Error: pipes are not supported in this OS\n");
 +    output_redir(p, stdout);
 +    goto dotCmdOutput_error;
 +#else
 +    FILE *pfPipe = sqlite3_popen(zFile + 1, "w");
 +    if( pfPipe==0 ){
 +      assert( stderr!=NULL );
 +      cli_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
 +      goto dotCmdOutput_error;
      }else{
 -      zColsSpec = 0;
 +      output_redir(p, pfPipe);
 +      if( zBom ) cli_puts(zBom, pfPipe);
 +      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
      }
 -    if( pzRenamed!=0 ){
 -      if( !hasDupes ) *pzRenamed = 0;
 -      else{
 -        sqlite3_finalize(pStmt);
 -        if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
 -            && SQLITE_ROW==sqlite3_step(pStmt) ){
 -          *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
 -        }else
 -          *pzRenamed = 0;
 +#endif
 +  }else{
 +    FILE *pfFile = output_file_open(zFile);
 +    if( pfFile==0 ){
 +      if( cli_strcmp(zFile,"off")!=0 ){
 +       assert( stderr!=NULL );
 +       cli_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
 +      }
 +      goto dotCmdOutput_error;
 +    } else {
 +      output_redir(p, pfFile);
 +      if( zBom ) cli_puts(zBom, pfFile);
 +      if( bPlain && eMode=='w' ){
 +        cli_puts(
 +          "<!DOCTYPE html>\n<BODY>\n<PLAINTEXT>\n",
 +          pfFile
 +        );
        }
 +      sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
      }
 -    sqlite3_finalize(pStmt);
 -    sqlite3_close(*pDb);
 -    *pDb = 0;
 -    return zColsSpec;
    }
 -}
 +  sqlite3_free(zFile);
 +  return 0;
  
 -/*
 -** Check if the sqlite_schema table contains one or more virtual tables. If
 -** parameter zLike is not NULL, then it is an SQL expression that the
 -** sqlite_schema row must also match. If one or more such rows are found,
 -** print the following warning to the output:
 -**
 -** WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled
 -*/
 -static int outputDumpWarning(ShellState *p, const char *zLike){
 -  int rc = SQLITE_OK;
 -  sqlite3_stmt *pStmt = 0;
 -  shellPreparePrintf(p->db, &rc, &pStmt,
 -    "SELECT 1 FROM sqlite_schema o WHERE "
 -    "sql LIKE 'CREATE VIRTUAL TABLE%%' AND %s", zLike ? zLike : "true"
 -  );
 -  if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
 -    sqlite3_fputs("/* WARNING: "
 -          "Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */\n",
 -          p->out
 -    );
 -  }
 -  shellFinalize(&rc, pStmt);
 -  return rc;
 +dotCmdOutput_error:
 +  sqlite3_free(zFile);
 +  return 1;
  }
--
  /*
- ** If an input line begins with "." then invoke this routine to
- ** process that line.
- **
- ** Return 1 on error, 2 to exit, and 0 otherwise.
 -** Fault-Simulator state and logic.
++** Parse input line zLine up into individual arguments.  Retain the
++** parse in the p->dot substructure.
  */
- static int do_meta_command(const char *zLine, ShellState *p){
 -static struct {
 -  int iId;           /* ID that triggers a simulated fault.  -1 means "any" */
 -  int iErr;          /* The error code to return on a fault */
 -  int iCnt;          /* Trigger the fault only if iCnt is already zero */
 -  int iInterval;     /* Reset iCnt to this value after each fault */
 -  int eVerbose;      /* When to print output */
 -  int nHit;          /* Number of hits seen so far */
 -  int nRepeat;       /* Turn off after this many hits.  0 for never */
 -  int nSkip;         /* Skip this many before first fault */
 -} faultsim_state = {-1, 0, 0, 0, 0, 0, 0, 0};
++static void parseDotCmdArgs(const char *zLine, ShellState *p){
++  char *z;
 +  int h = 1;
 +  int nArg = 0;
-   int n, c;
-   int rc = 0;
-   char *z;
-   char **azArg;
  
- #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION)
-   if( p->expert.pExpert ){
-     expertFinish(p, 1, 0);
 -/*
 -** This is the fault-sim callback
 -*/
 -static int faultsim_callback(int iArg){
 -  if( faultsim_state.iId>0 && faultsim_state.iId!=iArg ){
 -    return SQLITE_OK;
--  }
- #endif
-   /* Parse the input line into tokens.
-   */
 -  if( faultsim_state.iCnt ){
 -    if( faultsim_state.iCnt>0 ) faultsim_state.iCnt--;
 -    if( faultsim_state.eVerbose>=2 ){
 -      sqlite3_fprintf(stdout, 
 -         "FAULT-SIM id=%d no-fault (cnt=%d)\n", iArg, faultsim_state.iCnt);
 +  p->dot.zOrig = zLine;
 +  free(p->dot.zCopy);
 +  z = p->dot.zCopy = strdup(zLine);
 +  shell_check_oom(z);
-   nArg = 0;
 +  while( z[h] ){
 +    while( IsSpace(z[h]) ){ h++; }
 +    if( z[h]==0 ) break;
 +    if( nArg+2>p->dot.nAlloc ){
 +      p->dot.nAlloc = nArg+22;
 +      p->dot.azArg = realloc(p->dot.azArg,p->dot.nAlloc*sizeof(char*));
 +      shell_check_oom(p->dot.azArg);
 +      p->dot.aiOfst = realloc(p->dot.aiOfst,p->dot.nAlloc*sizeof(int));
 +      shell_check_oom(p->dot.aiOfst);
 +      p->dot.abQuot = realloc(p->dot.abQuot,p->dot.nAlloc);
 +      shell_check_oom(p->dot.abQuot);
 +    }
 +    if( z[h]=='\'' || z[h]=='"' ){
 +      int delim = z[h++];
 +      p->dot.abQuot[nArg] = 1;
 +      p->dot.azArg[nArg] = &z[h];
 +      p->dot.aiOfst[nArg] = h;
 +      while( z[h] && z[h]!=delim ){
 +        if( z[h]=='\\' && delim=='"' && z[h+1]!=0 ) h++;
 +        h++;
 +      }
 +      if( z[h]==delim ){
 +        z[h++] = 0;
 +      }
 +      if( delim=='"' ) resolve_backslashes(p->dot.azArg[nArg]);
 +    }else{
 +      p->dot.abQuot[nArg] = 0;
 +      p->dot.azArg[nArg] = &z[h];
 +      p->dot.aiOfst[nArg] = h;
 +      while( z[h] && !IsSpace(z[h]) ){ h++; }
 +      if( z[h] ) z[h++] = 0;
      }
 -    return SQLITE_OK;
 -  }
 -  if( faultsim_state.eVerbose>=1 ){
 -    sqlite3_fprintf(stdout, 
 -         "FAULT-SIM id=%d returns %d\n", iArg, faultsim_state.iErr);
 -  }
 -  faultsim_state.iCnt = faultsim_state.iInterval;
 -  faultsim_state.nHit++;
 -  if( faultsim_state.nRepeat>0 && faultsim_state.nRepeat<=faultsim_state.nHit ){
 -    faultsim_state.iCnt = -1;
 +    nArg++;
    }
 -  return faultsim_state.iErr;
 +  p->dot.nArg = nArg;
-   if( p->dot.nAlloc==0 ){
-     return 0;  /* No input tokens */
-   }
 +  p->dot.azArg[nArg] = 0;
+ }
+ /*
+ ** If an input line begins with "." then invoke this routine to
+ ** process that line.
+ **
+ ** Return 1 on error, 2 to exit, and 0 otherwise.
+ */
 -static int do_meta_command(char *zLine, ShellState *p){
 -  int h = 1;
 -  int nArg = 0;
++static int do_meta_command(const char *zLine, ShellState *p){
++  int nArg;
+   int n, c;
+   int rc = 0;
 -  char *azArg[52];
++  char **azArg;
+ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION)
+   if( p->expert.pExpert ){
+     expertFinish(p, 1, 0);
+   }
+ #endif
 -  /* Parse the input line into tokens.
++  /* Parse the input line into tokens stored in p->dot.
+   */
 -  while( zLine[h] && nArg<ArraySize(azArg)-1 ){
 -    while( IsSpace(zLine[h]) ){ h++; }
 -    if( zLine[h]==0 ) break;
 -    if( zLine[h]=='\'' || zLine[h]=='"' ){
 -      int delim = zLine[h++];
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && zLine[h]!=delim ){
 -        if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
 -        h++;
 -      }
 -      if( zLine[h]==delim ){
 -        zLine[h++] = 0;
 -      }
 -      if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
 -    }else{
 -      azArg[nArg++] = &zLine[h];
 -      while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
 -      if( zLine[h] ) zLine[h++] = 0;
 -    }
 -  }
 -  azArg[nArg] = 0;
++  parseDotCmdArgs(zLine, p);
++  nArg = p->dot.nArg;
 +  azArg = p->dot.azArg;
  
    /* Process the input line.
    */
@@@ -12931,19 -13942,8 +12937,20 @@@ int SQLITE_CDECL wmain(int argc, wchar_
    for(i=0; i<argcToFree; i++) free(argvToFree[i]);
    free(argvToFree);
  #endif
 -  free(data.colWidth);
 +  modeFree(&data.mode);
 +  free(data.zErrPrefix);
    free(data.zNonce);
-     sqlite3_fprintf(stderr, "%d test%s run with %d error%s\n",
 +  free(data.dot.zCopy);
 +  free(data.dot.azArg);
 +  free(data.dot.aiOfst);
 +  free(data.dot.abQuot);
 +  if( data.nTestRun ){
++    sqlite3_fprintf(stdout, "%d test%s run with %d error%s\n",
 +       data.nTestRun, data.nTestRun==1 ? "" : "s",
 +       data.nTestErr, data.nTestErr==1 ? "" : "s");
++    fflush(stdout);
 +    rc = data.nTestErr>0;
 +  }
    /* Clear the global data structure so that valgrind will detect memory
    ** leaks */
    memset(&data, 0, sizeof(data));
diff --cc test/qrf01.test
index 1fb8629b13e42b99db181ef8bab5a1c7383dc06f,0000000000000000000000000000000000000000..f13c5d1e3c124643103f353aac0f6a3f95054141
mode 100644,000000..100644
--- /dev/null
@@@ -1,749 -1,0 +1,750 @@@
- }
 +# 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)
 +#
 +
 +set testdir [file dirname $argv0]
 +source $testdir/tester.tcl
 +set testprefix qrf01
 +
 +do_execsql_test 1.0 {
 +  CREATE TABLE t1(a, b, c);
 +  INSERT INTO t1 VALUES(1,2.5,'three'),(x'424c4f42',NULL,'Ἀμήν');
 +}
 +
 +do_test 1.10 {
 +  set result "\n[db format {SELECT * FROM t1}]"
 +} {
 +┌──────┬─────┬───────┐
 +│  a   │  b  │   c   │
 +├──────┼─────┼───────┤
 +│ 1    │ 2.5 │ three │
 +│ BLOB │     │ Ἀμήν  │
 +└──────┴─────┴───────┘
 +}
 +do_test 1.11a {
 +  set result "\n[db format -title 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 "unk": must be auto, csv, html, json, plain, 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, jobject, json, line, list, markdown, quote, stats, stats-est, stats-vm, or table}
 +
 +      
 +do_test 1.20 {
 +  set result "\n[db format -style box {SELECT * FROM t1}]"
 +} {
 +┌──────┬─────┬───────┐
 +│  a   │  b  │   c   │
 +├──────┼─────┼───────┤
 +│ 1    │ 2.5 │ three │
 +│ BLOB │     │ Ἀμήν  │
 +└──────┴─────┴───────┘
 +}
 +
 +do_test 1.30 {
 +  set result "\n[db format -style table {SELECT * FROM t1}]"
 +} {
 ++------+-----+-------+
 +|  a   |  b  |   c   |
 ++------+-----+-------+
 +| 1    | 2.5 | three |
 +| BLOB |     | Ἀμήν  |
 ++------+-----+-------+
 +}
 +do_test 1.31 {
 +  set result "\n[db format -style table -title off {SELECT * FROM t1}]"
 +} {
 ++------+-----+-------+
 +| 1    | 2.5 | three |
 +| BLOB |     | Ἀμήν  |
 ++------+-----+-------+
 +}
 +
 +do_test 1.40 {
 +  set result "\n[db format -style column {SELECT * FROM t1}]"
 +} {
 +a     b    c    
 +----  ---  -----
 +1     2.5  three
 +BLOB       Ἀμήν 
 +}
 +do_test 1.41 {
 +  set result "\n[db format -style column -title off {SELECT * FROM t1}]"
 +} {
 +1     2.5  three
 +BLOB       Ἀμήν 
 +}
 +
 +do_test 1.50 {
 +  db format -style count {SELECT * FROM t1}
 +} 2
 +
 +do_test 1.60 {
 +  db format -style csv {SELECT * FROM t1}
 +} "1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n"
 +do_test 1.61 {
 +  db format -style csv -title auto {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 -title csv {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}]"
 +} {
 +<TR>
 +<TD>1
 +<TD>2.5
 +<TD>three
 +</TR>
 +<TR>
 +<TD>BLOB
 +<TD>null
 +<TD>Ἀμήν
 +</TR>
 +}
 +do_test 1.71 {
 +  set result "\n[db format -style html -title auto {SELECT * FROM t1}]"
 +} {
 +<TR>
 +<TH>a
 +<TH>b
 +<TH>c
 +</TR>
 +<TR>
 +<TD>1
 +<TD>2.5
 +<TD>three
 +</TR>
 +<TR>
 +<TD>BLOB
 +<TD>null
 +<TD>Ἀμήν
 +</TR>
 +}
 +
 +do_test 1.80 {
 +  set result "\n[db format -style insert {SELECT * FROM t1}]"
 +} {
 +INSERT INTO tab VALUES(1,2.5,'three');
 +INSERT INTO tab VALUES(x'424c4f42',NULL,'Ἀμήν');
 +}
 +do_test 1.81 {
 +  set result "\n[db format -style insert -tablename t1 {SELECT * FROM t1}]"
 +} {
 +INSERT INTO t1 VALUES(1,2.5,'three');
 +INSERT INTO t1 VALUES(x'424c4f42',NULL,'Ἀμήν');
 +}
 +do_test 1.82 {
 +  set result "\n[db format -style insert -tablename t1 -title auto \
 +      {SELECT * FROM t1}]"
 +} {
 +INSERT INTO t1(a,b,c) VALUES(1,2.5,'three');
 +INSERT INTO t1(a,b,c) VALUES(x'424c4f42',NULL,'Ἀμήν');
 +}
 +do_test 1.83 {
 +  set result "\n[db format -style insert -tablename drop -title on \
 +      {SELECT a AS "a-b", b, c AS "123" FROM t1}]"
 +} {
 +INSERT INTO "drop"("a-b",b,"123") VALUES(1,2.5,'three');
 +INSERT INTO "drop"("a-b",b,"123") VALUES(x'424c4f42',NULL,'Ἀμήν');
 +}
 +
 +do_test 1.90 {
 +  set result "\n[db format -style json {SELECT * FROM t1}]"
 +} {
 +[{"a":1,"b":2.5,"c":"three"},
 +{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"}]
 +}
 +do_test 1.91 {
 +  set result "\n[db format -style jobject {SELECT * FROM t1}]"
 +} {
 +{"a":1,"b":2.5,"c":"three"}
 +{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"}
 +}
 +do_test 1.92 {
 +  set result "\n[db format -style jobject {SELECT *, unistr('abc\u000a123\u000d\u000axyz') AS xyz FROM t1}]"
 +} {
 +{"a":1,"b":2.5,"c":"three","xyz":"abc\n123\r\nxyz"}
 +{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν","xyz":"abc\n123\r\nxyz"}
 +}
 +
 +do_test 1.100 {
 +  set result "\n[db format -style line {SELECT * FROM t1}]"
 +} {
 +a = 1
 +b = 2.5
 +c = three
 +
 +a = BLOB
 +b = 
 +c = Ἀμήν
 +}
 +do_test 1.101 {
 +  set result "\n[db format -style line -null (NULL) {SELECT * FROM t1}]"
 +} {
 +a = 1
 +b = 2.5
 +c = three
 +
 +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}]"
 +} {
 +1|2.5|three
 +BLOB||Ἀμήν
 +}
 +do_test 1.111 {
 +  set result "\n[db format -style list -title on {SELECT * FROM t1}]"
 +} {
 +a|b|c
 +1|2.5|three
 +BLOB||Ἀμήν
 +}
 +do_test 1.112 {
 +  set result "\n[db format -style list -title on -text sql -null NULL \
 +                 -title plain {SELECT * FROM t1}]"
 +} {
 +a|b|c
 +1|2.5|'three'
 +x'424c4f42'|NULL|'Ἀμήν'
 +}
 +do_test 1.118 {
 +  set rc [catch {db format -style list -title unk {SELECT * FROM t1}} res]
 +  lappend rc $res
 +} {1 {bad -title "unk": must be off, on, auto, csv, html, json, plain, sql, or tcl}}
 +
 +
 +do_test 1.120 {
 +  set result "\n[db format -style markdown {SELECT * FROM t1}]"
 +} {
 +|  a   |  b  |   c   |
 +|------|-----|-------|
 +| 1    | 2.5 | three |
 +| BLOB |     | Ἀμήν  |
 +}
 +do_test 1.121 {
 +  set result "\n[db format -style markdown -title off {SELECT * FROM t1}]"
 +} {
 +| 1    | 2.5 | three |
 +| BLOB |     | Ἀμήν  |
 +}
 +
 +do_test 1.130 {
 +  set result "\n[db format -style quote {SELECT * FROM t1}]"
 +} {
 +1,2.5,'three'
 +x'424c4f42',NULL,'Ἀμήν'
 +}
 +do_test 1.131 {
 +  set result "\n[db format -style quote -title on {SELECT * FROM t1}]"
 +} {
 +'a','b','c'
 +1,2.5,'three'
 +x'424c4f42',NULL,'Ἀμήν'
 +}
 +
 +
 +do_execsql_test 2.0 {
 +  DELETE FROM t1;
 +  INSERT INTO t1 VALUES(1,2,'The quick fox jumps over the lazy brown dog.');
 +}
 +do_test 2.1 {
 +  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 19} -wordwrap off \
 +                 {SELECT * FROM t1}]"
 +} {
 +┌───────┬───────┬─────────────────────┐
 +│   a   │   b   │          c          │
 +├───────┼───────┼─────────────────────┤
 +│ 1     │     2 │ The quick fox jumps │
 +│       │       │ over the lazy brown │
 +│       │       │ dog.                │
 +└───────┴───────┴─────────────────────┘
 +}
 +do_test 2.6 {
 +  set result "\n[db format -widths {5 -5 18} -wordwrap off \
 +                 {SELECT * FROM t1}]"
 +} {
 +┌───────┬───────┬────────────────────┐
 +│   a   │   b   │         c          │
 +├───────┼───────┼────────────────────┤
 +│ 1     │     2 │ The quick fox jump │
 +│       │       │ s over the lazy br │
 +│       │       │ own dog.           │
 +└───────┴───────┴────────────────────┘
 +}
 +do_test 2.7 {
 +  set result "\n[db format -widths {5 5 18} -wordwrap yes \
 +                 -align {left center right} -titlealign right \
 +                 {SELECT * FROM t1}]"
 +} {
 +┌───────┬───────┬────────────────────┐
 +│     a │     b │                  c │
 +├───────┼───────┼────────────────────┤
 +│ 1     │   2   │      The quick fox │
 +│       │       │     jumps over the │
 +│       │       │    lazy brown dog. │
 +└───────┴───────┴────────────────────┘
 +}
 +do_test 2.8 {
 +  set result "\n[db format -widths {5 8 11} -wordwrap yes \
 +                 -align {auto auto center} -titlealign left \
 +                 -defaultalign right \
 +                 {SELECT * FROM t1}]"
 +} {
 +┌───────┬──────────┬─────────────┐
 +│ a     │ b        │ c           │
 +├───────┼──────────┼─────────────┤
 +│     1 │        2 │  The quick  │
 +│       │          │  fox jumps  │
 +│       │          │  over the   │
 +│       │          │ lazy brown  │
 +│       │          │    dog.     │
 +└───────┴──────────┴─────────────┘
 +}
 +do_test 2.9 {
 +  catch {db format -align {auto xyz 123} {SELECT * FROM t1}} res
 +  set res
 +} {bad column alignment (-align) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w}
 +do_test 2.10 {
 +  catch {db format -defaultalign xyz {SELECT * FROM t1}} res
 +  set res
 +} {bad default alignment (-defaultalign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w}
 +do_test 2.11 {
 +  catch {db format -titlealign xyz {SELECT * FROM t1}} res
 +  set res
 +} {bad title alignment (-titlealign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w}
 +
 +
 +do_execsql_test 2.30 {
 +  UPDATE t1 SET c='Η γρήγορη αλεπού πηδάει πάνω από το τεμπέλικο καφέ σκυλί';
++  SELECT hex(c) FROM t1;
++} {CE9720CEB3CF81CEAECEB3CEBFCF81CEB720CEB1CEBBCEB5CF80CEBFCF8D20CF80CEB7CEB4CEACCEB5CEB920CF80CEACCEBDCF8920CEB1CF80CF8C20CF84CEBF20CF84CEB5CEBCCF80CEADCEBBCEB9CEBACEBF20CEBACEB1CF86CEAD20CF83CEBACF85CEBBCEAF}
 +do_test 2.31 {
 +  set result "\n[db format -widths {5 -5 18} -wordwrap on \
 +                 {SELECT * FROM t1}]"
 +} {
 +┌───────┬───────┬────────────────────┐
 +│   a   │   b   │         c          │
 +├───────┼───────┼────────────────────┤
 +│ 1     │     2 │ Η γρήγορη αλεπού   │
 +│       │       │ πηδάει πάνω από το │
 +│       │       │ τεμπέλικο καφέ     │
 +│       │       │ σκυλί              │
 +└───────┴───────┴────────────────────┘
 +}
 +do_test 2.32 {
 +  set result "\n[db format -widths {5 5 18} -align {left center center} -wordwrap on \
 +                 {SELECT * FROM t1}]"
 +} {
 +┌───────┬───────┬────────────────────┐
 +│   a   │   b   │         c          │
 +├───────┼───────┼────────────────────┤
 +│ 1     │     2 │ Η γρήγορη αλεπού   │
 +│       │       │ πηδάει πάνω από το │
 +│       │       │ τεμπέλικο καφέ     │
 +│       │       │ σκυλί              │
 +└───────┴───────┴────────────────────┘
 +}
 +
 +
 +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}
 +
 +do_execsql_test 4.0 {
 +  DELETE FROM t1;
 +  INSERT INTO t1 VALUES(json('{a:5,b:6}'), jsonb('{c:1,d:2}'), 99);
 +}
 +do_test 4.1 {
 +  set result "\n[db format -text sql {SELECT * FROM t1}]"
 +} {
 +┌─────────────────┬───────────────────────┬────┐
 +│        a        │           b           │ c  │
 +├─────────────────┼───────────────────────┼────┤
 +│ '{"a":5,"b":6}' │ x'8c1763133117641332' │ 99 │
 +└─────────────────┴───────────────────────┴────┘
 +}
 +do_test 4.2 {
 +  set result "\n[db format -text sql -textjsonb on {SELECT * FROM t1}]"
 +} {
 +┌─────────────────┬─────────────────┬────┐
 +│        a        │        b        │ c  │
 +├─────────────────┼─────────────────┼────┤
 +│ '{"a":5,"b":6}' │ '{"c":1,"d":2}' │ 99 │
 +└─────────────────┴─────────────────┴────┘
 +}
 +do_test 4.3 {
 +  set result "\n[db format -text plain -textjsonb on -wrap 11 \
 +              {SELECT a AS json, b AS jsonb, c AS num FROM t1}]"
 +} {
 +┌─────────────┬─────────────┬─────┐
 +│    json     │    jsonb    │ num │
 +├─────────────┼─────────────┼─────┤
 +│ {"a":5,"b": │ {"c":1,"d": │ 99  │
 +│ 6}          │ 2}          │     │
 +└─────────────┴─────────────┴─────┘
 +}
 +
 +do_execsql_test 5.0 {
 +  DROP TABLE t1;
 +  CREATE TABLE t1(name, mtime, value);
 +  INSERT INTO t1 VALUES
 +    ('entry-one',1708791504,zeroblob(300)),
 +    (unistr('one\u000atwo\u000athree'),1333206973,NULL),
 +    ('sample-jsonb',1333101221,jsonb('{
 +       "alpha":53.11688723,
 +       "beta":"qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth);",
 +       "zeta":[15,null,1333206973,"fd8ffe000104a46494600010101"]}'));
 +}
 +do_test 5.1 {
 +  set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time,
 +                        value FROM t1 ORDER BY mtime}
 +  set result "\n[db format -style line -screenwidth 60 -blob sql \
 +                   -text sql -wordwrap off -linelimit 77 $sql]"
 +} {
 + name = 'sample-jsonb'
 +mtime = 1333101221
 + time = '2012-03-30 09:53:41'
 +value = x'cc7c57616c706861b535332e31313638383732334762657461
 +        c73071726657696474685072696e7428702c20702d3e704f7574
 +        2c202d702d3e752e734c696e652e6d78436f6c577468293b477a
 +        657461cb2c23313500a331333333323036393733c71b66643866
 +        6665303030313034613436343934363030303130313031'
 +
 + name = unistr('one\u000atwo\u000athree')
 +mtime = 1333206973
 + time = '2012-03-31 15:16:13'
 +value = 
 +
 + name = 'entry-one'
 +mtime = 1708791504
 + time = '2024-02-24 16:18:24'
 +value = x'00000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        000000000000000000000000000000'
 +}
 +do_test 5.2 {
 +  set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time,
 +                        value FROM t1 ORDER BY mtime}
 +  set result "\n[db format -style line -screenwidth 60 -blob sql \
 +                   -text plain -esc off -textjsonb yes \
 +                   -wordwrap yes -linelimit 3 $sql]"
 +} {
 + name = sample-jsonb
 +mtime = 1333101221
 + time = 2012-03-30 09:53:41
 +value = {"alpha":53.11688723,"beta":"qrfWidthPrint(p,
 +        p->pOut, -p->u.sLine.mxColWth);","zeta":[15,null,
 +        1333206973,"fd8ffe000104a46494600010101"]}
 +
 + name = one
 +        two
 +        three
 +mtime = 1333206973
 + time = 2012-03-31 15:16:13
 +value = 
 +
 + name = entry-one
 +mtime = 1708791504
 + time = 2024-02-24 16:18:24
 +value = x'00000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        0000000000000000000000000000000000000000000000000000
 +        ...
 +}
 +do_test 5.3a {
 +  set result "\n[db format -style box -widths {0 10 10 14}\
 +                 -align {left right right center} \
 +                 -blob sql \
 +                 -text plain -esc off -textjsonb no \
 +                   -wordwrap yes -linelimit 2 $sql]"
 +} {
 +┌──────────────┬────────────┬────────────┬────────────────┐
 +│     name     │   mtime    │    time    │     value      │
 +├──────────────┼────────────┼────────────┼────────────────┤
 +│ sample-jsonb │ 1333101221 │ 2012-03-30 │ x'cc7c57616c70 │
 +│              │            │   09:53:41 │ 6861b535332e31 │
 +│              │            │            │      ...       │
 +├──────────────┼────────────┼────────────┼────────────────┤
 +│ one          │ 1333206973 │ 2012-03-31 │                │
 +│ two          │            │   15:16:13 │                │
 +│ ...          │            │            │                │
 +├──────────────┼────────────┼────────────┼────────────────┤
 +│ entry-one    │ 1708791504 │ 2024-02-24 │ x'000000000000 │
 +│              │            │   16:18:24 │ 00000000000000 │
 +│              │            │            │      ...       │
 +└──────────────┴────────────┴────────────┴────────────────┘
 +}
 +do_test 5.3b {
 +  set result "\n[db format -style table -widths {0 10 10 14}\
 +                 -align {center right right right} \
 +                 -blob sql \
 +                 -text plain -esc off -textjsonb no \
 +                   -wordwrap yes -linelimit 2 $sql]"
 +} {
 ++--------------+------------+------------+----------------+
 +|     name     |   mtime    |    time    |     value      |
 ++--------------+------------+------------+----------------+
 +| sample-jsonb | 1333101221 | 2012-03-30 | x'cc7c57616c70 |
 +|              |            |   09:53:41 | 6861b535332e31 |
 +|              |            |            |            ... |
 ++--------------+------------+------------+----------------+
 +|     one      | 1333206973 | 2012-03-31 |                |
 +|     two      |            |   15:16:13 |                |
 +|     ...      |            |            |                |
 ++--------------+------------+------------+----------------+
 +|  entry-one   | 1708791504 | 2024-02-24 | x'000000000000 |
 +|              |            |   16:18:24 | 00000000000000 |
 +|              |            |            |            ... |
 ++--------------+------------+------------+----------------+
 +}
 +do_test 5.3c {
 +  set result "\n[db format -style column -widths {0 10 10 14}\
 +                 -align {center right right right} \
 +                 -blob sql \
 +                 -text plain -esc off -textjsonb no \
 +                   -wordwrap yes -linelimit 2 $sql]"
 +} {
 +    name        mtime        time         value     
 +------------  ----------  ----------  --------------
 +sample-jsonb  1333101221  2012-03-30  x'cc7c57616c70
 +                            09:53:41  6861b535332e31
 +                                                 ...
 +
 +    one       1333206973  2012-03-31                
 +    two                     15:16:13                
 +    ...                                             
 +
 + entry-one    1708791504  2024-02-24  x'000000000000
 +                            16:18:24  00000000000000
 +                                                 ...
 +}
 +do_test 5.4 {
 +  db eval {
 +    CREATE TABLE t2(a,b,c,d,e);
 +    WITH v(x) AS (SELECT 'abcdefghijklmnopqrstuvwxyz')
 +    INSERT INTO t2 SELECT x,x,x,x,x FROM v;
 +  }
 +  set sql {SELECT char(0x61,0xa,0x62,0xa,0x63,0xa,0x64) a,
 +           mtime b, mtime c, mtime d, mtime e FROM t1}
 +  set result "\n[db format -style box -widths {1 2 3 4 5}\
 +                -linelimit 3 -wordwrap off {SELECT *, 'x' AS x FROM t2}]"
 +} {
 +┌────┬────┬─────┬──────┬───────┬───┐
 +│ a  │ b  │  c  │  d   │   e   │ x │
 +├────┼────┼─────┼──────┼───────┼───┤
 +│ ab │ ab │ abc │ abcd │ abcde │ x │
 +│ cd │ cd │ def │ efgh │ fghij │   │
 +│ ef │ ef │ ghi │ ijkl │ klmno │   │
 +│ .. │ .. │ ... │ ...  │ ...   │   │
 +└────┴────┴─────┴──────┴───────┴───┘
 +}
 +
 +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 1203bcb02804289cb87947c4cb816f175e8755e8,abf214a9071ce8d74de2b5450b98a2b236e63e96..58b250cae766d00cfe498a83a100b78ecc903f18
@@@ -1334,12 -1344,12 +1334,12 @@@ select * from t;
  #
  do_test shell1-9.1 {
    catchcmd :memory: {
--.mode csv
++.mode csv --rowsep "\n"
  /*
  x */ select 1,2; --x
   -- .nada
  ;
--.mode csv
++.mode csv --rowsep "\n"
  --x
  select 2,1; select 3,4;
  }
index ed0a20cef439956513c910ed58529cf282794031,0000000000000000000000000000000000000000..4802ed1a548eac39cdb12693bcdd6b13afbb878a
mode 100644,000000..100644
--- /dev/null
@@@ -1,43 -1,0 +1,44 @@@
-   set mapping [list <NAME> $name]
 +# 2025-11-12
 +#
 +# 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.
 +#
 +#***********************************************************************
 +# TESTRUNNER: shell
 +#
 +# Test cases for the command-line shell using the new ".output memory"
 +# feature.
 +#
 +#
 +
 +set testdir [file dirname $argv0]
 +source $testdir/tester.tcl
 +set CLI [test_cli_invocation]
 +
 +# Run an instance of the CLI on the file $name.
 +# Capture the number of test cases and the number of
 +# errors and increment the counts.
 +#
 +proc do_clitest {name} {
-     catch {exec {*}$::CLI :memory: ".read $::testdir/<NAME>" 2>@stdout} res
++  set mapping [list <NAME> $::testdir/$name <CLI> $::CLI]
 +  set script [string map $mapping {
-     set answer "error count: $nerr"
++    catch {exec <CLI> :memory: ".read <NAME>" 2>@stdout} res
 +    set ntest 0
 +    set nerr 999
 +    regexp {.*(\d+) tests? run with (\d+) errors?} $res all ntest nerr
 +    set_test_counter count [expr {[set_test_counter count]+$ntest-1}]
 +    set_test_counter errors [expr {[set_test_counter errors]+$nerr}]
++    if {$nerr==0} {set res "error count: 0"}
++    set res
 +  }]
 +  # puts $script
 +  do_test shellB-$name $script {error count: 0}
 +}
 +
 +do_clitest modeA.clitest
 +
 +finish_test