]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
The TCL interface for QRF is now feature complete (at least until I think
authordrh <>
Wed, 5 Nov 2025 18:27:18 +0000 (18:27 +0000)
committerdrh <>
Wed, 5 Nov 2025 18:27:18 +0000 (18:27 +0000)
of new features to add).  There is still a lot of testing, bug-fixing,
and documentation to be done, however.

FossilOrigin-Name: 633fe4fe584ae14ed6ced1ae137bf96a434a012423d70f0f93876737b0ca075c

ext/qrf/README.md
ext/qrf/qrf-tester.c [deleted file]
ext/qrf/qrf.c
ext/qrf/qrf.h
manifest
manifest.uuid
src/tclsqlite.c
test/qrf01.test

index 0379b5f3b8e741d6dbf6100e861bed4d29d5b295..e725f7502162f8abe2b98d66e2c86ce9fae4e531 100644 (file)
@@ -50,7 +50,7 @@ struct sqlite3_qrf_spec {
   unsigned char eEsc;         /* How to escape control characters in text */
   unsigned char eText;        /* Quoting style for text */
   unsigned char eBlob;        /* Quoting style for BLOBs */
-  unsigned char bColumnNames; /* True to show column names */
+  unsigned char bColumnNames; /* QRF_SW_On to show column names */
   unsigned char bWordWrap;    /* Try to wrap on word boundaries */
   unsigned char bTextJsonb;   /* Render JSONB blobs as JSON text */
   short int mxWidth;          /* Maximum width of any column */
@@ -112,8 +112,18 @@ ignored, depending on the value of eStyle.
 
 ### 2.4 Show Column Names (bColumnNames)
 
-The sqlite3_qrf_spec.bColumnNames field is a boolean.  If true, then column
-names appear in the output.  If false, column names are omitted.
+The sqlite3_qrf_spec.bColumnNames field can be either QRF_SW_Auto,
+QRF_SW_On, or QRF_SW_Off.
+
+> ~~~
+#define QRF_SW_Auto     0 /* Let QRF choose the best value */
+#define QRF_SW_Off      1 /* This setting is forced off */
+#define QRF_SW_On       2 /* This setting is forced on */
+~~~
+
+If the value is QRF_SW_On, then column names appear in the output.
+If the value is QRF_SW_Off, column names are omitted.  If the
+value is QRF_SW_Auto, then an appropriate default is chosen.
 
 ### 2.5 Control Character Escapes (eEsc)
 
@@ -196,12 +206,12 @@ the JSON specification.
 
 ### 2.7 How to display BLOB values (eBlob and bTextJsonb)
 
-If the sqlite3_qrf_spec.bTextJsonb flag is true and if the value to be
+If the sqlite3_qrf_spec.bTextJsonb flag is QRF_SW_On and if the value to be
 displayed is JSONB, then the JSONB is translated into text JSON and the
 text is shown according to the sqlite3_qrf_spec.eText setting as
 described in the previous section.
 
-If the bTextJsonb flag is false (the usual case) or if the BLOB value to
+If the bTextJsonb flag is QRF_SW_Off (the usual case) or if the BLOB value to
 be displayed is not JSONB, then the sqlite3_qrf_spec.eBlob field determines
 how the BLOB value is formatted.  The following options are available;
 
@@ -250,10 +260,10 @@ mxWidth characters wide.  "Width" hear means the actual displayed
 width of the text on a fixed-pitch font.  The code takes into account
 zero-width and double-width characters when comput the display width.
 
-If the sqlite3_qrf_spec.bWordWrap flag is set, then the formatter
-attempts to split lines at whitespace or word boundaries.
-If the bWorkWrap flag is zero, then long lines can be split in the middle
-of words.
+If the sqlite3_qrf_spec.bWordWrap flag is set to QRF_SW_On,
+then the formatter attempts to split lines at whitespace or word boundaries.
+If the bWorkWrap flag is set QRF_SW_Off, then long lines can be split in
+the middle of words.
 
 ### 2.9 Column width and alignment (nWidth and aWidth)
 
diff --git a/ext/qrf/qrf-tester.c b/ext/qrf/qrf-tester.c
deleted file mode 100644 (file)
index 8911882..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-/*
-** 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.
-**
-*************************************************************************
-** A simple command-line program for testing the Result-Format or "resfmt"
-** utility library for SQLite.
-*/
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
-#include "sqlite3.h"
-#include "qrf.h"
-
-#define COUNT(X)  (sizeof(X)/sizeof(X[0]))
-
-/* Report out-of-memory and die if the argument is NULL */
-static void checkOOM(void *p){
-  if( p==0 ){
-    fprintf(stdout, "out-of-memory\n");
-    exit(1);
-  }
-}
-
-/* A block of memory */
-typedef struct memblock memblock;
-struct memblock {
-  memblock *pNext;
-};
-
-/* List of all memory to be freed */
-static memblock *pToFree = 0;
-
-/* Free all memory at exit */
-static void tempFreeAll(void){
-  while( pToFree ){
-    memblock *pNext = pToFree->pNext;
-    sqlite3_free(pToFree);
-    pToFree = pNext;
-  }
-}
-
-/* Allocate memory that will be freed all at once by freeall() */
-static void *tempMalloc(size_t n){
-  memblock *p;
-  if( n>0x10000000 ) checkOOM(0);
-  p = sqlite3_malloc64( n+sizeof(memblock) );
-  checkOOM(p);
-  p->pNext = pToFree;
-  pToFree = p;
-  return (void*)&pToFree[1];
-}
-
-/* Make a copy of a string using tempMalloc() */
-static char *tempStrdup(char *zIn){
-  size_t n;
-  char *z;
-  if( zIn==0 ) zIn = "";
-  n  = strlen(zIn);
-  if( n>0x10000000 ) checkOOM(0);
-  z = tempMalloc( n+1 );
-  checkOOM(z);
-  memcpy(z, zIn, n+1);
-  return z;
-}
-
-/* Function used for writing to the console */
-static sqlite3_int64 testWriter(
-  void *pContext,
-  const unsigned char *p,
-  sqlite3_int64 n
-){
-  return fwrite(p,1,n,stdout);
-}
-
-/* Render BLOB values as "(%d-byte-blob)". */
-static char *testBlobRender(void *pNotUsed, sqlite3_value *pVal){
-  if( sqlite3_value_type(pVal)!=SQLITE_BLOB ) return 0;
-  return sqlite3_mprintf("(%d-byte-blob)",sqlite3_value_bytes(pVal));
-}
-
-int main(int argc, char **argv){
-  char *zSrc;
-  FILE *pSrc;
-  sqlite3_str *pBuf;
-  sqlite3 *db = 0;
-  sqlite3_stmt *pStmt;
-  int rc;
-  int lineNum = 0;
-  int bUseWriter = 1;
-  short int aWidth[100];
-  sqlite3_qrf_spec spec;
-  char zLine[1000];
-
-  if( argc<2 ){
-    zSrc = "<stdin>";
-    pSrc = stdin;
-  }else{
-    zSrc = argv[1];
-    pSrc = fopen(zSrc, "rb");
-    if( pSrc==0 ){
-      fprintf(stderr, "cannot open \"%s\" for reading\n", zSrc);
-      exit(1);
-    }
-  }
-  memset(&spec, 0, sizeof(spec));
-  spec.iVersion = 1;
-  spec.eStyle = QRF_STYLE_List;
-  spec.xWrite = testWriter;
-  pBuf = sqlite3_str_new(0);
-  rc = sqlite3_open(":memory:", &db);
-  if( rc ){
-    fprintf(stderr, "unable to open an in-memory database: %s\n",
-            sqlite3_errmsg(db));
-    exit(1);
-  }
-  while( fgets(zLine, sizeof(zLine), pSrc) ){
-    size_t n = strlen(zLine);
-    lineNum++;
-    while( n>0 && zLine[n-1]>0 && zLine[n-1]<=' ' ) n--;
-    zLine[n] = 0;
-    printf("%s\n", zLine);
-    if( strncmp(zLine, "--open=", 7)==0 ){
-      if( db ) sqlite3_close(db);
-      db = 0;
-      rc = sqlite3_open(&zLine[7], &db);
-      if( rc!=SQLITE_OK ){
-        fprintf(stderr, "%s:%d: cannot open \"%s\": %s\n",
-                zSrc, lineNum, &zLine[7],
-                sqlite3_errmsg(db));
-        exit(1);
-      }
-    }else
-    if( strcmp(zLine, "--go")==0 ){
-      const char *zSql, *zTail;
-      char *zErr = 0;
-      int n;
-      if( db==0 ){
-        fprintf(stderr, "%s:%d: database not open\n", zSrc, lineNum);
-        exit(1);
-      }
-      zSql = sqlite3_str_value(pBuf);
-      pStmt = 0;
-      while( zSql[0] ){
-        rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zTail);
-        if( rc || pStmt==0 ){
-          fprintf(stderr, "%s:%d: sqlite3_prepare() fails: %s\n",
-                  zSrc, lineNum, sqlite3_errmsg(db));
-          sqlite3_finalize(pStmt);
-          break;
-        }
-        zSql = sqlite3_sql(pStmt);
-        while( isspace(zSql[0]) ) zSql++;
-        n = (int)strlen(zSql);
-        while( n>0 && isspace(zSql[n-1]) ) n--;
-        if( n>0 ){
-          char *zOut = 0;
-          printf("/* %.*s */\n", n, zSql);
-          if( bUseWriter ){
-            spec.pzOutput = 0;
-            spec.xWrite = testWriter;
-          }else{
-            spec.pzOutput = &zOut;
-            spec.xWrite = 0;
-          }
-          rc = sqlite3_format_query_result(pStmt, &spec, &zErr);
-          if( rc!=SQLITE_OK ){
-            fprintf(stderr, "%s:%d: Error %d: %s\n", zSrc, lineNum,
-                            rc, zErr);
-          }else{
-            if( !bUseWriter && zOut ){
-              fputs(zOut, stdout);
-              sqlite3_free(zOut);
-            }
-          }
-          sqlite3_free(zErr);
-        }
-        sqlite3_finalize(pStmt);
-        pStmt = 0;
-        zSql = zTail;
-      }
-      sqlite3_str_reset(pBuf);
-    }else
-    if( strncmp(zLine, "--eStyle=", 9)==0 ){
-      const struct { const char *z; int e; } aStyle[] = {
-         { "box",      QRF_STYLE_Box,      },
-         { "csv",      QRF_STYLE_Csv,      },
-         { "column",   QRF_STYLE_Column,   },
-         { "count",    QRF_STYLE_Count,    },
-         { "eqp",      QRF_STYLE_EQP,      },
-         { "explain",  QRF_STYLE_Explain,  },
-         { "html",     QRF_STYLE_Html,     },
-         { "insert",   QRF_STYLE_Insert,   },
-         { "json",     QRF_STYLE_Json,     },
-         { "line",     QRF_STYLE_Line,     },
-         { "list",     QRF_STYLE_List,     },
-         { "markdown", QRF_STYLE_Markdown, },
-         { "off",      QRF_STYLE_Off,      },
-         { "quote",    QRF_STYLE_Quote,    },
-         { "table",    QRF_STYLE_Table,    },
-         { "scanexp",  QRF_STYLE_ScanExp,  },
-      };
-      int i;
-      for(i=0; i<COUNT(aStyle); i++){
-        if( strcmp(aStyle[i].z,&zLine[9])==0 ){
-          spec.eStyle = aStyle[i].e;
-          break;
-        }
-      }
-      if( i>=COUNT(aStyle) ){
-        sqlite3_str *pMsg = sqlite3_str_new(0);
-        for(i=0; i<COUNT(aStyle); i++){
-          sqlite3_str_appendf(pMsg, " %s", aStyle[i].z);
-        }
-        fprintf(stderr, "%s:%d: no such style: \"%s\"\nChoices: %s\n",
-                zSrc, lineNum, &zLine[9], sqlite3_str_value(pMsg));
-        sqlite3_free(sqlite3_str_finish(pMsg));
-      }
-    }else
-    if( strncmp(zLine, "--eText=", 8)==0 ){
-      const struct { const char *z; int e; } aQuote[] = {
-         { "csv",      QRF_TEXT_Csv     },
-         { "html",     QRF_TEXT_Html    },
-         { "json",     QRF_TEXT_Json    },
-         { "off",      QRF_TEXT_Off     },
-         { "sql",      QRF_TEXT_Sql     },
-         { "tcl",      QRF_TEXT_Tcl     },
-      };
-      int i;
-      for(i=0; i<COUNT(aQuote); i++){
-        if( strcmp(aQuote[i].z,&zLine[8])==0 ){
-          spec.eText = aQuote[i].e;
-          break;
-        }
-      }
-      if( i>=COUNT(aQuote) ){
-        sqlite3_str *pMsg = sqlite3_str_new(0);
-        for(i=0; i<COUNT(aQuote); i++){
-          sqlite3_str_appendf(pMsg, " %s", aQuote[i].z);
-        }
-        fprintf(stderr, "%s:%d: no such text-style: \"%s\"\nChoices: %s\n",
-                zSrc, lineNum, &zLine[8], sqlite3_str_value(pMsg));
-        sqlite3_free(sqlite3_str_finish(pMsg));
-      }
-    }else
-    if( strncmp(zLine, "--eBlob=", 8)==0 ){
-      const struct { const char *z; int e; } aBlob[] = {
-         { "auto",    QRF_BLOB_Auto    },
-         { "hex",     QRF_BLOB_Hex     },
-         { "json",    QRF_BLOB_Json    },
-         { "tcl",     QRF_BLOB_Tcl     },
-         { "text",    QRF_BLOB_Text    },
-         { "sql",     QRF_BLOB_Sql     },
-      };
-      int i;
-      for(i=0; i<COUNT(aBlob); i++){
-        if( strcmp(aBlob[i].z,&zLine[8])==0 ){
-          spec.eBlob = aBlob[i].e;
-          break;
-        }
-      }
-      if( i>=COUNT(aBlob) ){
-        sqlite3_str *pMsg = sqlite3_str_new(0);
-        for(i=0; i<COUNT(aBlob); i++){
-          sqlite3_str_appendf(pMsg, " %s", aBlob[i].z);
-        }
-        fprintf(stderr, "%s:%d: no such blob-style: \"%s\"\nChoices: %s\n",
-                zSrc, lineNum, &zLine[8], sqlite3_str_value(pMsg));
-        sqlite3_free(sqlite3_str_finish(pMsg));
-      }
-    }else
-    if( strncmp(zLine, "--eEsc=", 7)==0 ){
-      const struct { const char *z; int e; } aEscape[] = {
-         { "ascii",     QRF_ESC_Ascii   },
-         { "off",       QRF_ESC_Off     },
-         { "symbol",    QRF_ESC_Symbol  },
-      };
-      int i;
-      for(i=0; i<COUNT(aEscape); i++){
-        if( strcmp(aEscape[i].z,&zLine[7])==0 ){
-          spec.eEsc = aEscape[i].e;
-          break;
-        }
-      }
-      if( i>=COUNT(aEscape) ){
-        sqlite3_str *pMsg = sqlite3_str_new(0);
-        for(i=0; i<COUNT(aEscape); i++){
-          sqlite3_str_appendf(pMsg, " %s", aEscape[i].z);
-        }
-        fprintf(stderr, "%s:%d: no such escape mode: \"%s\"\nChoices: %s\n",
-                zSrc, lineNum, &zLine[10], sqlite3_str_value(pMsg));
-        sqlite3_free(sqlite3_str_finish(pMsg));
-      }
-    }else
-    if( strncmp(zLine, "--bColumnNames=", 15)==0 ){
-      spec.bColumnNames = atoi(&zLine[15])!=0;
-    }else
-    if( strncmp(zLine, "--bTextJsonb=", 13)==0 ){
-      spec.bTextJsonb = atoi(&zLine[13])!=0;
-    }else
-    if( strncmp(zLine, "--zNull=", 8)==0 ){
-      spec.zNull = tempStrdup(&zLine[8]);
-    }else
-    if( strncmp(zLine, "--zColumnSep=", 13)==0 ){
-      spec.zColumnSep = tempStrdup(&zLine[13]);
-    }else
-    if( strncmp(zLine, "--zRowSep=", 10)==0 ){
-      spec.zRowSep = tempStrdup(&zLine[10]);
-    }else
-    if( strncmp(zLine, "--mxWidth=", 10)==0 ){
-      spec.mxWidth = (short int)atoi(&zLine[10]);
-    }else
-    if( strncmp(zLine, "--aWidth=", 9)==0 ){
-      const char *zArg = &zLine[9];
-      int n = 0;
-      while( isspace(zArg[0]) ) zArg++;
-      while( zArg[0]!=0 && n<COUNT(aWidth) ){
-        int w = atoi(zArg);
-        if( w>QRF_MX_WIDTH ){
-          w = QRF_MX_WIDTH;
-        }else if( w<QRF_MN_WIDTH ){
-          w = QRF_MN_WIDTH;
-        }else if( w==0 && zArg[0]=='-' ){
-          w = QRF_MINUS_ZERO;
-        }
-        aWidth[n++] = w;
-        while( zArg[0] && !isspace(zArg[0]) ) zArg++;
-        while( isspace(zArg[0]) ) zArg++;
-      }
-      spec.aWidth = aWidth;
-      spec.nWidth = n;
-    }else
-    if( strcmp(zLine, "--exit")==0 ){
-      break;
-    }else
-    if( strncmp(zLine, "--use-writer=",13)==0 ){
-      bUseWriter = atoi(&zLine[13])!=0;
-    }else
-    if( strncmp(zLine, "--use-render=",13)==0 ){
-      spec.xRender = (atoi(&zLine[13])!=0) ? testBlobRender : 0;
-    }else
-    if( strncmp(zLine, "--",2)==0 && !isspace(zLine[2]) ){
-      fprintf(stderr, "%s:%d: Unrecognized command: \"%s\"\n", 
-              zSrc, lineNum, zLine);
-    }else      
-    {
-      if( sqlite3_str_length(pBuf) ) sqlite3_str_append(pBuf, "\n", 1);
-      sqlite3_str_appendall(pBuf, zLine);
-    }
-  }
-  if( db ) sqlite3_close(db);
-  sqlite3_free(sqlite3_str_finish(pBuf));
-  tempFreeAll();
-  if( pSrc!=stdin ) fclose(pSrc);
-  return 0;
-}
index e0574e21169b173bdc4d79db31a82aacb0ba4643..5f0dd801223635865de88f273e502d5cebe6e909 100644 (file)
@@ -646,7 +646,7 @@ static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
       break;
     }
     case SQLITE_BLOB: {
-      if( p->spec.bTextJsonb ){
+      if( p->spec.bTextJsonb==QRF_SW_On ){
         const char *zJson = qrfJsonbToJson(p, iCol);
         if( zJson ){
           qrfEncodeText(p, pOut, zJson);
@@ -1033,7 +1033,7 @@ static void qrfColumnar(Qrf *p){
   const unsigned char **azNextLine = 0;
   int bNextLine = 0;
   int bMultiLineRowExists = 0;
-  int bw = p->spec.bWordWrap;
+  int bw = p->spec.bWordWrap==QRF_SW_On;
   int nColumn = p->nCol;
 
   rc = sqlite3_step(p->pStmt);
@@ -1155,7 +1155,7 @@ static void qrfColumnar(Qrf *p){
     case QRF_STYLE_Column: {
       colSep = "  ";
       rowSep = "\n";
-      if( p->spec.bColumnNames ){
+      if( p->spec.bColumnNames==QRF_SW_On ){
         for(i=0; i<nColumn; i++){
           w = p->actualWidth[i];
           if( i<p->spec.nWidth && p->spec.aWidth[i]<0 ) w = -w;
@@ -1546,7 +1546,7 @@ qrf_reinit:
         ** not an EQP statement.
         */
         p->spec.eStyle = QRF_STYLE_Quote;
-        p->spec.bColumnNames = 1;
+        p->spec.bColumnNames = QRF_SW_On;
         p->spec.eText = QRF_TEXT_Sql;
         p->spec.eBlob = QRF_BLOB_Sql;
         p->spec.zColumnSep = ",";
@@ -1563,7 +1563,7 @@ qrf_reinit:
         ** statement is not an EXPLAIN statement.
         */
         p->spec.eStyle = QRF_STYLE_Quote;
-        p->spec.bColumnNames = 1;
+        p->spec.bColumnNames = QRF_SW_On;
         p->spec.eText = QRF_TEXT_Sql;
         p->spec.eBlob = QRF_BLOB_Sql;
         p->spec.zColumnSep = ",";
@@ -1587,6 +1587,46 @@ qrf_reinit:
       default:            p->spec.eBlob = QRF_BLOB_Text; break;
     }
   }
+  if( p->spec.bColumnNames==QRF_SW_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.bColumnNames = QRF_SW_On;
+        break;
+      default:
+        p->spec.bColumnNames = QRF_SW_Off;
+        break;
+    }
+  }
+  if( p->spec.bWordWrap==QRF_SW_Auto ){
+    p->spec.bWordWrap = QRF_SW_On;
+  }
+  if( p->spec.bTextJsonb==QRF_SW_Auto ){
+    p->spec.bTextJsonb = QRF_SW_Off;
+  }
+}
+
+/*
+** 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( !isalpha(z[0]) && z[0]!='_' ) return 1;
+  for(i=0; z[i]; i++){
+    if( !isalnum(z[i]) && z[i]!='_' ) return 1;
+  }
+  return sqlite3_keyword_check(zName, i)!=0;
 }
 
 /*
@@ -1617,7 +1657,7 @@ static void qrfOneSimpleRow(Qrf *p){
       break;
     }
     case QRF_STYLE_Html: {
-      if( p->nRow==0 && p->spec.bColumnNames ){
+      if( p->nRow==0 && p->spec.bColumnNames==QRF_SW_On ){
         sqlite3_str_append(p->pOut, "<TR>", 4);
         for(i=0; i<p->nCol; i++){
           const char *zCName = sqlite3_column_name(p->pStmt, i);
@@ -1636,7 +1676,25 @@ static void qrfOneSimpleRow(Qrf *p){
       break;
     }
     case QRF_STYLE_Insert: {
-      sqlite3_str_appendf(p->pOut,"INSERT INTO %s VALUES(",p->spec.zTableName);
+      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.bColumnNames==QRF_SW_On ){
+        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);
@@ -1685,7 +1743,7 @@ static void qrfOneSimpleRow(Qrf *p){
       break;
     }
     default: {  /* QRF_STYLE_List */
-      if( p->nRow==0 && p->spec.bColumnNames ){
+      if( p->nRow==0 && p->spec.bColumnNames==QRF_SW_On ){
         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);
index 8a81cb65000424ee8dad0dd470dd69d6d5a6356f..4b031b4b68dd4f7a8215de38b7cefccbe1caa380 100644 (file)
@@ -114,4 +114,13 @@ int sqlite3_format_query_result(
 #define QRF_ESC_Ascii   2 /* Unix-style escapes.  Ex: U+0007 shows ^G */
 #define QRF_ESC_Symbol  3 /* Unicode escapes. Ex: U+0007 shows U+2407 */
 
+/*
+** Allowed values for "boolean" fields, such as "bColumnNames", "bWordWrap",
+** and "bTextJsonb".  There is an extra "auto" variants so these are actually
+** tri-state settings, not booleans.
+*/
+#define QRF_SW_Auto     0 /* Let QRF choose the best value */
+#define QRF_SW_Off      1 /* This setting is forced off */
+#define QRF_SW_On       2 /* This setting is forced on */
+
 #endif /* !defined(SQLITE_QRF_H) */
index 658304e1104de2e09371fe7d6c5e078d0d7eea46..20680703a8513573f0c8be536c9c66dc1dae0475 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C A\sfew\ssimple\stest\scases\sadded.
-D 2025-11-05T16:29:06.224
+C The\sTCL\sinterface\sfor\sQRF\sis\snow\sfeature\scomplete\s(at\sleast\suntil\sI\sthink\nof\snew\sfeatures\sto\sadd).\s\sThere\sis\sstill\sa\slot\sof\stesting,\sbug-fixing,\nand\sdocumentation\sto\sbe\sdone,\showever.
+D 2025-11-05T18:27:18.151
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -416,10 +416,9 @@ F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f6
 F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c
 F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c
 F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
-F ext/qrf/README.md 6bfe3047478b8901694cab91ccc98b35a8e14678f38475dc726be485190d4433
-F ext/qrf/qrf-tester.c 3a733b25a25ba7390cd3df055edd76ac72f488a9c5d9eb523a7508b0b8ac8900
-F ext/qrf/qrf.c e45c181d031f71da73c7d605f56ff62640985a9bbb154f0221c5324bbc1b1167
-F ext/qrf/qrf.h 4542c723805550b48ca81ed53e3beea95e793ecc7d2b01d34a780fbea366323f
+F ext/qrf/README.md 5f12f91104d5df8ba8783e2240635698569ce54935e721b3a4e93abf4e00ddcb
+F ext/qrf/qrf.c 8dd8f3245f86686ddacdf92354655dd290e5ed7668f6cf7d6bcf1f9453160062
+F ext/qrf/qrf.h 98e02eded10848c42053d37e56bd15ab69cb6fe9fc1f1ca05823d9047e9c8509
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363
@@ -744,7 +743,7 @@ F src/sqliteInt.h 88f7fc9ce1630d9a5f7e0a8e1f3287cdc63882fba985c18e7eee1b9f457f59
 F src/sqliteLimit.h fe70bd8983e5d317a264f2ea97473b359faf3ebb0827877a76813f5cf0cdc364
 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
-F src/tclsqlite.c bb00869df54bdbb8f214d37e915eaa3033df4db7c758eed58e6f706a97714fd9
+F src/tclsqlite.c 45476ca0b3088504c7e296ce52b4ab0b6b9af57132abeca2867fbcf1a50f84af
 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a
 F src/test1.c f880ab766eeedf2c063662bd9538b923fd42c4341b7bfc2150a6d93ab8b9341c
 F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
@@ -1507,7 +1506,7 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c
 F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
 F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
 F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd
-F test/qrf01.test 6b912e5ceb34fb288d47ade071249b8dcd6df40735ee3a7733af99e77e504c25
+F test/qrf01.test 84699d3a41d371dda947a9ca994e8bc19cbc3e43b83d6716c6b43b1b6a7c1b6f
 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
 F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
 F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a
@@ -2173,8 +2172,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 468ba188f034b23398e1f07b915cf7c8b337dcf7f56a13511947c5322ae98722
-R 83d792277e49d25b00c0094481fa71d7
+P 7200372ed2424d1ac4c88644e0b17a224539f170b9111ced9c88774edbd75261
+R c08f61440d4d487375dc477b759daec9
 U drh
-Z 7f22080d0311b852cf62e1bdb7405410
+Z 6f4e493d857d3f3fb51f0ded7bd78aca
 # Remove this line to create a well-formed Fossil manifest.
index 2c029eefa250eee270acf5eea299c578bd178b2d..da59608a778d213eb72cf1e4bb41fa655209534e 100644 (file)
@@ -1 +1 @@
-7200372ed2424d1ac4c88644e0b17a224539f170b9111ced9c88774edbd75261
+633fe4fe584ae14ed6ced1ae137bf96a434a012423d70f0f93876737b0ca075c
index 73d7931c181be5bd26c7e6ca9d3729bf14bae5d5..bda4b8bd4059dc5b39cbd5a8af11613bf31c3238 100644 (file)
@@ -2067,16 +2067,26 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
   qrf.pzOutput = &zResult;
   for(i=2; i<objc; i++){
     const char *zArg = Tcl_GetString(objv[i]);
-    if( zArg[0]!='-' && zSql==0 ){
+    const char *azBool[] = { "auto", "off", "on", 0 };
+    if( zArg[0]!='-' ){
+      if( zSql ){
+        Tcl_AppendResult(pDb->interp, "unknown argument: ", zArg, (char*)0);
+        rc = TCL_ERROR;
+        goto format_failed;
+      }
       zSql  = zArg;
-    }else if( strcmp(zArg,"-style")==0 && i<objc-1 ){
+    }else if( i==objc-1 ){
+      Tcl_AppendResult(pDb->interp, "option has no argument: ", zArg, (char*)0);
+      rc = TCL_ERROR;
+      goto format_failed;
+    }else if( strcmp(zArg,"-style")==0 ){
       static const char *azStyles[] = {
         "auto",             "box",              "column",
         "count",            "csv",              "eqp",
         "explain",          "html",             "insert",
         "json",             "line",             "list",
         "markdown",         "quote",            "scanexp",
-        "table"
+        "table",            0
       };
       static unsigned char aStyleMap[] = {
         QRF_STYLE_Auto,     QRF_STYLE_Box,      QRF_STYLE_Column,
@@ -2087,13 +2097,92 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
         QRF_STYLE_Table
       };
       int style;
-      if( Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles,
-                              "format style", 0, &style) ) return SQLITE_ERROR;
+      rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles,
+                              "format style", 0, &style);
+      if( rc ) goto format_failed;
       qrf.eStyle = aStyleMap[style];
       i++;
+    }else if( strcmp(zArg,"-null")==0 ){
+      qrf.zNull = Tcl_GetString(objv[i+1]);
+      i++;
+    }else if( strcmp(zArg,"-rowsep")==0 ){
+      qrf.zRowSep = Tcl_GetString(objv[i+1]);
+      i++;
+    }else if( strcmp(zArg,"-columnsep")==0 ){
+      qrf.zColumnSep = Tcl_GetString(objv[i+1]);
+      i++;
+    }else if( strcmp(zArg,"-tablename")==0 ){
+      qrf.zTableName = Tcl_GetString(objv[i+1]);
+      i++;
+    }else if( strcmp(zArg,"-columnnames")==0 ){
+      int v = 0;
+      rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
+                              "-columnnames", 0, &v);
+      if( rc ) goto format_failed;
+      qrf.bColumnNames = v;
+      i++;
+    }else if( strcmp(zArg,"-wordwrap")==0 ){
+      int v = 0;
+      rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
+                              "-wordwrap", 0, &v);
+      if( rc ) goto format_failed;
+      qrf.bWordWrap = v;
+      i++;
+    }else if( strcmp(zArg,"-testjsonb")==0 ){
+      int v = 0;
+      rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
+                              "-testjsonb", 0, &v);
+      if( rc ) goto format_failed;
+      qrf.bTextJsonb = v;
+      i++;
+    }else if( strcmp(zArg,"-maxwidth")==0 ){
+      int v = 0;
+      rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v);
+      if( rc ) goto format_failed;
+      if( v<0 ){
+        v = 0;
+      }else if( v>QRF_MX_WIDTH ){
+        v = QRF_MX_WIDTH;
+      }
+      qrf.mxWidth = v;
+      i++;
+    }else if( strcmp(zArg,"-widths")==0 ){
+      int n = 0;
+      int jj;
+      rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n);
+      if( rc ) goto format_failed;
+      sqlite3_free(qrf.aWidth);
+      qrf.aWidth = sqlite3_malloc64( (n+1)*sizeof(qrf.aWidth[0]) );
+      if( qrf.aWidth==0 ){
+        Tcl_AppendResult(pDb->interp, "out of memory", (char*)0);
+        rc = TCL_ERROR;
+        goto format_failed;
+      }
+      memset(qrf.aWidth, 0, (n+1)*sizeof(qrf.aWidth[0]));
+      qrf.nWidth = n;
+      for(jj=0; jj<n; jj++){
+        Tcl_Obj *pTerm;
+        const char *zTerm;
+        rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm);
+        if( rc ) goto format_failed;
+        zTerm = Tcl_GetString(pTerm);
+        if( strcmp(zTerm, "-0")==0 ){
+          qrf.aWidth[jj] = QRF_MINUS_ZERO;
+        }else{
+          int v = atoi(zTerm);
+          if( v<QRF_MN_WIDTH ){
+            v = QRF_MN_WIDTH;
+          }else if( v>QRF_MX_WIDTH ){
+            v = QRF_MX_WIDTH;
+          }
+          qrf.aWidth[jj] = v;
+        }
+      }
+      i++;
     }else{
-      Tcl_AppendResult(pDb->interp, "unknown argument: ", zArg, (char*)0);
-      return TCL_ERROR;
+      Tcl_AppendResult(pDb->interp, "unknown option: ", zArg, (char*)0);
+      rc = TCL_ERROR;
+      goto format_failed;
     }
   }
   while( zSql && zSql[0] ){
@@ -2113,10 +2202,11 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
     }
   }
   Tcl_SetResult(pDb->interp, zResult, TCL_VOLATILE);
-  sqlite3_free(zResult);
-  return TCL_OK;
-
+  rc = TCL_OK;
+  /* Fall through...*/
+  
 format_failed:
+  sqlite3_free(qrf.aWidth);
   sqlite3_free(zResult);
   return rc;
 
@@ -3075,7 +3165,7 @@ deserialize_error:
   ** text and return that text.
   */
   case DB_FORMAT: {
-    dbQrf(pDb, objc, objv);
+    rc = dbQrf(pDb, objc, objv);
     break;
   }
 
index 490687f090d5dab6d57a82361451ae56a017b31a..64975331ca17eb807a0c03a9d8fa160e6d409330 100644 (file)
@@ -57,6 +57,14 @@ do_test 1.30 {
 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 -columnnames off {SELECT * FROM t1}]"
+} {
 1     2.5  three
 BLOB       Ἀμήν 
 }
@@ -68,6 +76,9 @@ do_test 1.50 {
 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 -columnnames on {SELECT * FROM t1}
+} "a,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}]"
@@ -83,6 +94,25 @@ do_test 1.70 {
 <TD>Ἀμήν
 </TR>
 }
+do_test 1.71 {
+  set result "\n[db format -style html -columnnames on {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}]"
@@ -90,6 +120,26 @@ do_test 1.80 {
 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 -columnnames on \
+      {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 -columnnames 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}]"
@@ -109,6 +159,17 @@ 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.110 {
   set result "\n[db format -style list {SELECT * FROM t1}]"
@@ -116,6 +177,18 @@ do_test 1.110 {
 1|2.5|three
 BLOB||Ἀμήν
 }
+do_test 1.111 {
+  set result "\n[db format -style list -columnnames on {SELECT * FROM t1}]"
+} {
+a|b|c
+1|2.5|three
+BLOB||Ἀμήν
+}
+do_test 1.112 {
+  set rc [catch {db format -style list -columnnames unk {SELECT * FROM t1}} res]
+  lappend rc $res
+} {1 {bad -columnnames "unk": must be auto, off, or on}}
+
 
 do_test 1.120 {
   set result "\n[db format -style markdown {SELECT * FROM t1}]"
@@ -132,5 +205,29 @@ do_test 1.130 {
 1,2.5,'three'
 x'424c4f42',NULL,'Ἀμήν'
 }
+do_test 1.131 {
+  set result "\n[db format -style quote -columnnames 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 {10 -10 22} -wordwrap off \
+                 {SELECT * FROM t1}]"
+} {
+┌────────────┬────────────┬────────────────────────┐
+│     a      │     b      │           c            │
+├────────────┼────────────┼────────────────────────┤
+│ 1          │          2 │ The quick fox jumps ov │
+│            │            │ er the lazy brown dog. │
+└────────────┴────────────┴────────────────────────┘
+}
 
 finish_test