]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Continuing code cleanup. Various minor bug fixes. All legacy tests now
authordrh <>
Wed, 12 Nov 2025 18:54:09 +0000 (18:54 +0000)
committerdrh <>
Wed, 12 Nov 2025 18:54:09 +0000 (18:54 +0000)
pass.  All planned features for ".mode" are implemented, at least at the
interface level, though some features are still no-ops.

FossilOrigin-Name: e5a81711d0076b447e5bd3206bc04d755a6229b9f4926f42260fcd01ecf3e5a2

manifest
manifest.uuid
src/shell.c.in
test/modeA.clitest
test/shell1.test
test/shellA.test
test/shellB.test

index ebd49ac4a923d484eb74b2ce69f7baee2a485870..e59807f3fb6fb4f074fe7d6ce8b21b2068eb126c 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sa\snew\sdirect\sCLI\stesting\sscript\sin\stest/modeA.clitest\sto\sdemonstration\nthat\scan\sbe\sdone.\s\sRig\sthis\sthis\sto\sbe\srun\sby\stest/shellB.test\sduring\snormal\ntesting.
-D 2025-11-12T13:46:11.794
+C Continuing\scode\scleanup.\s\sVarious\sminor\sbug\sfixes.\s\sAll\slegacy\stests\snow\npass.\s\sAll\splanned\sfeatures\sfor\s".mode"\sare\simplemented,\sat\sleast\sat\sthe\ninterface\slevel,\sthough\ssome\sfeatures\sare\sstill\sno-ops.
+D 2025-11-12T18:54:09.684
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -735,7 +735,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a
-F src/shell.c.in 68396182b0d542eb223fb93e41fcfdb98ffa2caac8ce06868ec1e1da962badba
+F src/shell.c.in 7fda2605823e01dbd3bde96d3189eb38bd3ed838070be73aeeb0ccfe89af8db8
 F src/sqlite.h.in 7403a952a8f1239de7525b73c4e3a0f9540ec0607ed24fec887f5832642d44b8
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52
@@ -1437,7 +1437,7 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93
 F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465
 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3
 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3
-F test/modeA.clitest 7d6a81c5fe60ba07b5b68c9fb624fd7cb7c75be3fc364e17c75c47d8efaaa54d
+F test/modeA.clitest 73d091c785671f629af0900f0654a28468bbba5685a545c1513049c57f1b8cbc
 F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08
 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a
 F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7
@@ -1602,7 +1602,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21
 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
-F test/shell1.test 4d44fb7d08a62b6c6424185e519d59fff9c93d9f9cbf4c773f30908bf60cb3de
+F test/shell1.test cdc63d6badd34aef37cd31d92a30b3d3c38fe1386a9c98ac047a7ecec5bfc6a3
 F test/shell2.test d8da6a06dcce1d8f04f776f918d4d57c28ddc28c54f3a44f95429794892e3a91
 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e
 F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d
@@ -1611,8 +1611,8 @@ F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bd
 F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3
 F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871
 F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209
-F test/shellA.test 1de183849ddb6b49d9f21a10a2f6f22bc66f962c0bfaed5baa5ecc3dc6d8f2d5
-F test/shellB.test d2b2f6256560145b8c027dbfc41cd2ac50b2ef25d5853f3ae80c9407f441c9d2
+F test/shellA.test 3a3ccbac90367d51f12c3d762c90e84723492e3bc5d228cfefd4c6ebfc72394d
+F test/shellB.test 714c13ba4d964277ab236fa210d8c8e05b5f224ce342981548ef4929794d3768
 F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c
 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@ -2175,8 +2175,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 4c8109682e3a224fd8e015e73a9c1c7b6d56ad747d7ca7bc387bb92e1c98f731
-R e7fc6b191c336123ef02d62bdd856b8f
+P 3107ebb9e1602fe5bed644c6f69426bebf307772e581b17a8fa3e50bd522e566
+R 8dad54c9d16986934ba268d5971ec78d
 U drh
-Z 90d2468da1196b69530a9ff9957b86e3
+Z 3adf1517a92d999df985d59d4ee4e85f
 # Remove this line to create a well-formed Fossil manifest.
index 3e790b0dacf36324e3b227a37f7ecbb09218e230..d2ff45567adec1fced70dda2fc33305aaabfae7b 100644 (file)
@@ -1 +1 @@
-3107ebb9e1602fe5bed644c6f69426bebf307772e581b17a8fa3e50bd522e566
+e5a81711d0076b447e5bd3206bc04d755a6229b9f4926f42260fcd01ecf3e5a2
index b7d02ca1f765ade05c5e90030565011ad8a3775c..0e9f0d8ead4137964a92d5e82667ba6b33869f6e 100644 (file)
@@ -1415,8 +1415,8 @@ static const char *qrfQuoteNames[] = { "off","off","sql","hex", "tcl", "json"};
 #define MODE_QBox     15  /* BOX with SQL-quoted content */
 #define MODE_Quote    16  /* Quote values as for SQL */
 #define MODE_Table    17  /* MySQL-style table formatting */
-#define MODE_Tcl      18  /* Space-separated list of TCL strings */
-#define MODE_Tsv      19  /* Tab-separated values */
+#define MODE_Tabs     18  /* Tab-separated values */
+#define MODE_Tcl      19  /* Space-separated list of TCL strings */
 #define MODE_Www      20  /* Full web-page output */
 
 /*
@@ -1439,7 +1439,7 @@ static const char *aModeStr[] =
    { 0, "\n", "|", " ", ",", "\r\n", "\036", "\037", "\t", "", "NULL", "null" };
 static const ModeInfo aModeInfo[] = {
 /*   zName      eCSep  eRSep eNull eText eBlob bHdr eTitle eStyle eCx */
-  { "ascii",    7,     8,    9,    3,    1,    1,   3,     12,    0  },
+  { "ascii",    7,     6,    9,    1,    1,    1,   1,     12,    0  },
   { "box",      0,     0,    9,    1,    1,    2,   1,     1,     2  },
   { "c",        4,     1,    2,    5,    4,    1,   5,     12,    0  },
   { "column",   0,     0,    9,    1,    1,    2,   1,     2,     2  },
@@ -1457,8 +1457,8 @@ static const ModeInfo aModeInfo[] = {
   { "qbox",     0,     0,    9,    2,    2,    2,   1,     1,     2  },
   { "quote",    4,     1,    10,   2,    2,    1,   2,     12,    0  },
   { "table",    0,     0,    9,    1,    1,    2,   1,     19,    2  },
+  { "tabs",     8,     1,    9,    3,    1,    1,   3,     12,    0  },
   { "tcl",      3,     1,    9,    5,    4,    1,   5,     12,    0  },
-  { "tsv",      8,     5,    9,    3,    1,    1,   3,     12,    0  },
   { "www",      0,     0,    9,    4,    1,    2,   4,     7,     0  },
 };     /*       |     /     /       \      \   |   /      /
        **       |    /     /         \      \  |  /      /
@@ -1975,6 +1975,51 @@ static void output_c_string(FILE *out, const char *z){
   cli_puts(zq, out);
 }
 
+/* Encode input string z[] as a C-language string literal and
+** append it to the sqlite3_str.  If z is NULL render and empty string.
+*/
+static void append_c_string(sqlite3_str *out, const char *z){
+  char c;
+  static const char *zq = "\"";
+  static long ctrlMask = ~0L;
+  static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */
+  char ace[3] = "\\?";
+  char cbsSay;
+  if( z==0 ) z = "";
+  sqlite3_str_appendall(out,zq);
+  while( *z!=0 ){
+    const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0);
+    const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask);
+    const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast;
+    if( pcEnd > z ){
+      sqlite3_str_appendf(out, "%.*s", (int)(pcEnd-z), z);
+    }
+    if( (c = *pcEnd)==0 ) break;
+    ++pcEnd;
+    switch( c ){
+    case '\\': case '"':
+      cbsSay = (char)c;
+      break;
+    case '\t': cbsSay = 't'; break;
+    case '\n': cbsSay = 'n'; break;
+    case '\r': cbsSay = 'r'; break;
+    case '\f': cbsSay = 'f'; break;
+    default: cbsSay = 0; break;
+    }
+    if( cbsSay ){
+      ace[1] = cbsSay;
+      sqlite3_str_appendall(out,ace);
+    }else if( !isprint(c&0xff) ){
+      sqlite3_str_appendf(out, "\\%03o", c&0xff);
+    }else{
+      ace[1] = (char)c;
+      sqlite3_str_appendall(out, ace+1);
+    }
+    z = pcEnd;
+  }
+  sqlite3_str_appendall(out, zq);
+}
+
 /*
 ** This routine runs when the user presses Ctrl-C
 */
@@ -7144,9 +7189,68 @@ static int pickStr(const char *zArg, char **pzErr, ...){
 **
 ** 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.
+** 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.
+**
+**   -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 */
@@ -7156,24 +7260,100 @@ static int dotCmdMode(ShellState *p){
   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( optionMatch(z,"wrap") && i+1<nArg ){
-      int 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;
+    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,"ww") ){
-      p->mode.spec.bWordWrap = QRF_Yes;
+    }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;
-    }else if( optionMatch(z,"wordwrap") ){
+      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;
       }
-      p->mode.spec.bWordWrap = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No;
+      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
@@ -7232,63 +7412,77 @@ static int dotCmdMode(ShellState *p){
       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;
+      modeFree(&p->mode);
+      modeChange(&p->mode, eMode);
     }else if( optionMatch(z,"textjsonb") ){
       if( i+1>=nArg ){
         dotCmdError(p, i, "missing argument", 0);
         return 1;
       }
-      p->mode.spec.bTextJsonb = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No;
+      p->mode.spec.bTextJsonb = booleanValue(azArg[++i]) ? QRF_Yes : QRF_No;
       chng = 1;
-    }else if( optionMatch(z,"escape") ){
-      /* See similar code at tag-20250224-1 */
-      char *zErr = 0;
+    }else if( optionMatch(z,"widths") ){
+      int nWidth = 0;
+      short int *aWidth;
+      const char *z;
       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;
+      z = 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
+      ** result. */
+      aWidth = malloc( strlen(z) );
+      while( isspace(z[0]) ) z++;
+      while( z[0] ){
+        int w = 0;
+        k = z[0]=='-' && isdigit(z[1]);
+        while( isdigit(z[k]) ){
+          w = w*10 + z[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;
+        aWidth[nWidth++] = w;
+        z += k;
+        if( z[0]==',' ) z++;
+        while( isspace(z[0]) ) z++;
+      }
+      free(p->mode.spec.aWidth);
+      p->mode.spec.aWidth = aWidth;
+      p->mode.spec.nWidth = nWidth;
       chng = 1;
-    }else if( 0<=(k=pickStr(z,0,"-tablename","-rowsep","-colsep","-null","")) ){
-                            /*   0            1         2         3 */
+    }else if( optionMatch(z,"wrap") ){
+      int w;
       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;
-      }
+      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( 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]);
-      }
+    }else if( optionMatch(z,"ww") ){
+      p->mode.spec.bWordWrap = QRF_Yes;
       chng = 1;
-    }else if( optionMatch(z,"error-prefix") ){
+    }else if( optionMatch(z,"wordwrap") ){
       if( i+1>=nArg ){
         dotCmdError(p, i, "missing argument", 0);
         return 1;
       }
-      free(p->zErrPrefix);
-      p->zErrPrefix = strdup(azArg[++i]);
+      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;
@@ -7298,22 +7492,87 @@ static int dotCmdMode(ShellState *p){
       return 1;
     }
   }
-  if( !chng ){
+  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( pI->eCx>0 && p->mode.spec.bWordWrap ){
-      if( p->mode.spec.nWrap ){
-        sqlite3_str_appendf(pDesc, " --wrap %d", p->mode.spec.nWrap);
+    if( bAll || (p->mode.spec.nAlign && pI->eCx==2) ){
+      int i;
+      sqlite3_str_appendall(pDesc, " --align \"");
+      for(i=0; i<p->mode.spec.nAlign; i++){
+        unsigned char a = p->mode.spec.aAlign[i];
+        sqlite3_str_append(pDesc, "LLCR"+(a&3), 1);
       }
-      sqlite3_str_append(pDesc, " --ww", 5);
+      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);
     }
-    if( pI->eText!=p->mode.spec.eText ){
+    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]);
     }
-    if( p->mode.spec.eEsc!=QRF_Auto ){
-      sqlite3_str_appendf(pDesc, " --escape %s", qrfEscNames[p->mode.spec.eEsc]);
+    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.nLineLimit>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");
+    }
+    if( p->mode.spec.nWidth>0 && (bAll || pI->eCx==2) ){
+      int i;
+      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]);
+        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);
@@ -7340,6 +7599,9 @@ static int dotCmdMode(ShellState *p){
 **   -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.
 **
@@ -7463,6 +7725,14 @@ static int dotCmdOutput(ShellState *p){
         }
         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);
@@ -7517,6 +7787,7 @@ static int dotCmdOutput(ShellState *p){
       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++;
@@ -7527,7 +7798,7 @@ static int dotCmdOutput(ShellState *p){
       if( cli_strcmp(zPattern,zTest)!=0 ){
         sqlite3_fprintf(stderr,
             "%s:%lld: --verify does matches prior output\n",
-          p->zInFile, p->lineno);
+          p->zInFile, iStart);
         p->nTestErr++;
       }
       sqlite3_free(zPattern);
@@ -11870,6 +12141,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
   int nOptsEnd = argc;
   int bEnableVfstrace = 0;
   char **azCmd = 0;
+  int *aiCmd = 0;
   const char *zVfs = 0;           /* Value of -vfs command-line option */
 #if !SQLITE_SHELL_IS_UTF8
   char **argvToFree = 0;
@@ -11995,7 +12267,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
         nCmd++;
         azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd);
         shell_check_oom(azCmd);
+        aiCmd = realloc(aiCmd, sizeof(aiCmd[0])*nCmd);
+        shell_check_oom(azCmd);
         azCmd[nCmd-1] = z;
+        aiCmd[nCmd-1] = i;
       }
       continue;
     }
@@ -12216,28 +12491,25 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     if( cli_strcmp(z,"-init")==0 ){
       i++;
     }else if( cli_strcmp(z,"-html")==0 ){
-      data.mode.eMode = MODE_Html;
+      modeChange(&data.mode, MODE_Html);
     }else if( cli_strcmp(z,"-list")==0 ){
-      data.mode.eMode = MODE_List;
+      modeChange(&data.mode, MODE_List);
     }else if( cli_strcmp(z,"-quote")==0 ){
-      data.mode.eMode = MODE_Quote;
-      modeSetStr(&data.mode.spec.zColumnSep, SEP_Comma);
-      modeSetStr(&data.mode.spec.zRowSep, SEP_Row);
+      modeChange(&data.mode, MODE_Quote);
     }else if( cli_strcmp(z,"-line")==0 ){
-      data.mode.eMode = MODE_Line;
+      modeChange(&data.mode, MODE_Line);
     }else if( cli_strcmp(z,"-column")==0 ){
-      data.mode.eMode = MODE_Column;
+      modeChange(&data.mode, MODE_Column);
     }else if( cli_strcmp(z,"-json")==0 ){
-      data.mode.eMode = MODE_Json;
+      modeChange(&data.mode, MODE_Json);
     }else if( cli_strcmp(z,"-markdown")==0 ){
-      data.mode.eMode = MODE_Markdown;
+      modeChange(&data.mode, MODE_Markdown);
     }else if( cli_strcmp(z,"-table")==0 ){
-      data.mode.eMode = MODE_Table;
+      modeChange(&data.mode, MODE_Table);
     }else if( cli_strcmp(z,"-box")==0 ){
-      data.mode.eMode = MODE_Box;
+      modeChange(&data.mode, MODE_Box);
     }else if( cli_strcmp(z,"-csv")==0 ){
-      data.mode.eMode = MODE_Csv;
-      modeSetStr(&data.mode.spec.zColumnSep, SEP_Comma);
+      modeChange(&data.mode, MODE_Csv);
     }else if( cli_strcmp(z,"-escape")==0 && i+1<argc ){
       /* See similar code at tag-20250224-1 */
       const char *zEsc = argv[++i];
@@ -12280,13 +12552,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       data.openFlags &= ~(SQLITE_OPEN_CREATE);
       if( data.openFlags==0 ) data.openFlags = SQLITE_OPEN_READWRITE;
     }else if( cli_strcmp(z,"-ascii")==0 ){
-      data.mode.eMode = MODE_Ascii;
-      modeSetStr(&data.mode.spec.zColumnSep, SEP_Unit);
-      modeSetStr(&data.mode.spec.zRowSep, SEP_Record);
+      modeChange(&data.mode, MODE_Ascii);
     }else if( cli_strcmp(z,"-tabs")==0 ){
-      data.mode.eMode = MODE_List;
-      modeSetStr(&data.mode.spec.zColumnSep, SEP_Tab);
-      modeSetStr(&data.mode.spec.zRowSep, SEP_Row);
+      modeChange(&data.mode, MODE_Tabs);
     }else if( cli_strcmp(z,"-separator")==0 ){
       modeSetStr(&data.mode.spec.zColumnSep, 
                        cmdline_option_value(argc,argv,++i));
@@ -12424,8 +12692,14 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
     for(i=0; i<nCmd; i++){
       echo_group_input(&data, azCmd[i]);
       if( azCmd[i][0]=='.' ){
+        char *zErrCtx = malloc( 64 );
+        shell_check_oom(zErrCtx);
+        sqlite3_snprintf(64,zErrCtx,"argv[%i]:",aiCmd[i]);
         data.zInFile = "<cmdline>";
+        data.zErrPrefix = zErrCtx;
         rc = do_meta_command(azCmd[i], &data);
+        free(data.zErrPrefix);
+        data.zErrPrefix = 0;
         if( rc ){
           if( rc==2 ) rc = 0;
           goto shell_main_exit;
@@ -12505,6 +12779,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 #endif
  shell_main_exit:
   free(azCmd);
+  free(aiCmd);
   set_table_name(&data, 0);
   if( data.db ){
     session_close_all(&data, -1);
index 2c6c60bfa336c68e387ffa061894be48dff34328..35661cc08bc8bf42fd4a9913453af4613a5af53b 100644 (file)
@@ -15,7 +15,6 @@
 #   ./sqlite3 <test/modeA.clitest
 #
 #
-.mode --error-prefix "Error:"
 CREATE TABLE t1(a,b,c,d,e);
 INSERT INTO t1 VALUES(1,2.5,'three',x'4444',NULL);
 INSERT INTO t1 SELECT b,c,d,e,a FROM t1;
@@ -34,9 +33,9 @@ SELECT * FROM t1;
 │     │ 1     │ 2.5   │ three │ DD    │
 └─────┴───────┴───────┴───────┴───────┘
 END
-.null xyz
 
 .output memory
+.mode --null xyz
 SELECT * FROM t1;
 .output --verify END
 ┌─────┬───────┬───────┬───────┬───────┐
@@ -49,10 +48,17 @@ SELECT * FROM t1;
 └─────┴───────┴───────┴───────┴───────┘
 END
 
-.output memory
+.output memory --error-prefix "Error:"
 .mode foo
 .output --verify END
 Error: .mode foo
 Error:       ^--- unknown mode
 Error: Use ".help .mode" for more info
 END
+
+.output memory
+.mode --null xyzzy -v
+.output -glob ' --null "xyzzy"'
+.output memory
+.mode -null abcde -v
+.output -glob ' --null "abcde"'
index 95b44ffd898f3269e19183ec2008976992160ab1..07cb68465b8cf127a7af4ba319474b28db150732 100644 (file)
@@ -216,10 +216,14 @@ do_test shell1-2.2.4 {
 } {0 {}}
 do_test shell1-2.2.5 {
   catchcmd "test.db" ".mode \"insert FOO"
-} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
+} {1 {line 1: .mode "insert FOO
+line 1:        ^--- unknown mode
+line 1: Use ".help .mode" for more info}}
 do_test shell1-2.2.6 {
   catchcmd "test.db" ".mode \'insert FOO"
-} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
+} {1 {line 1: .mode 'insert FOO
+line 1:        ^--- unknown mode
+line 1: Use ".help .mode" for more info}}
 
 # check multiple tokens, and quoted tokens
 do_test shell1-2.3.1 {
@@ -247,7 +251,9 @@ do_test shell1-2.3.7 {
 # check quoted args are unquoted
 do_test shell1-2.4.1 {
   catchcmd "test.db" ".mode FOO"
-} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
+} {1 {line 1: .mode FOO
+line 1:       ^--- unknown mode
+line 1: Use ".help .mode" for more info}}
 do_test shell1-2.4.2 {
   catchcmd "test.db" ".mode csv"
 } {0 {}}
@@ -451,10 +457,12 @@ do_test shell1-3.12.3 {
 #                          tcl      TCL list elements
 do_test shell1-3.13.1 {
   catchcmd "test.db" ".mode"
-} {0 {current output mode: list --escape ascii}}
+} {0 {current output mode: list}}
 do_test shell1-3.13.2 {
   catchcmd "test.db" ".mode FOO"
-} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
+} {1 {line 1: .mode FOO
+line 1:       ^--- unknown mode
+line 1: Use ".help .mode" for more info}}
 do_test shell1-3.13.3 {
   catchcmd "test.db" ".mode csv"
 } {0 {}}
index ad3eecc55b07590189a02625a49d335d86afd28b..fac9ea25344a95f90947b366bc69f889573ee31c 100644 (file)
@@ -101,7 +101,9 @@ do_test shellA-1.9 {
      exec {*}$CLI test.db {.mode test --escape xyz}
   } msg]
   lappend rc $msg
-} {1 {unknown control character escape mode "xyz" - choices: auto off ascii symbol}}
+} {1 {argv[2]: .mode test --escape xyz
+argv[2]:       ^--- unknown mode
+argv[2]: Use ".help .mode" for more info}}
 do_test shellA-1.10 {
   set rc [catch {
      exec {*}$CLI --escape abc test.db .q
@@ -121,8 +123,8 @@ do_test shellA-2.1 {
 8,'last line'
 }
 do_test shellA-2.2 {
-  exec {*}$CLI test.db --quote {.mode}
-} {current output mode: quote --escape ascii}
+  exec {*}$CLI test.db --quote {.mode -v}
+} {/*current output mode: quote* --escape auto*/}
 do_test shellA-2.3 {
   exec {*}$CLI test.db --quote --escape SYMBOL {.mode}
 } {current output mode: quote --escape symbol}
index 5697af7c6c13988900f459a2e7b132f550f01d71..ed0a20cef439956513c910ed58529cf282794031 100644 (file)
@@ -28,14 +28,14 @@ proc do_clitest {name} {
   set script [string map $mapping {
     catch {exec {*}$::CLI :memory: ".read $::testdir/<NAME>" 2>@stdout} res
     set ntest 0
-    set nerr 0
-    regexp {(\d+) tests run with (\d+) errors} $res all ntest nerr
+    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}]
-    set answer "$nerr errors"
+    set answer "error count: $nerr"
   }]
   # puts $script
-  do_test shellB-$name $script {0 errors}
+  do_test shellB-$name $script {error count: 0}
 }
 
 do_clitest modeA.clitest