]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
CLI comes up in legacy "list" mode if neither stdin or stdout are a tty, or
authordrh <>
Mon, 17 Nov 2025 16:44:13 +0000 (16:44 +0000)
committerdrh <>
Mon, 17 Nov 2025 16:44:13 +0000 (16:44 +0000)
if "--compat 20251114" or earlier is specified, or if "--batch" is used.
New modes "batch" and "tty" select either legacy "list" mode or the newer
"qbox" mode with limits.
Make CVS output compatible with legacy.  Break out ".import" into a separate
subroutine in anticipation of forthcoming improvements.  General code
cleanup.

FossilOrigin-Name: f6bfcea9a01493af182e9aa0d35df6f81bf9e36220df79139afa287fa43d9aa3

ext/qrf/README.md
ext/qrf/qrf.c
manifest
manifest.uuid
src/shell.c.in
test/qrf01.test
test/shell1.test
test/shell2.test
test/shell5.test

index b3acf5863c1efa1ece1ec8a1c3a28a3a37559c5e..a8d60f28ef1948ac3faa1f5ec5f59aa48b75b79b 100644 (file)
@@ -278,7 +278,7 @@ A value of QRF_BLOB_Sql means that BLOB values are shown as SQL BLOB
 literals: a prefix "`x'`" following by hexadecimal and ending with a
 final "`'`".
 
-A value of QRF_BLOB_Hex means that BLOB values are shown as pure
+A value of QRF_BLOB_Hex means that BLOB values are shown as
 hexadecimal text with no delimiters.
 
 A value of QRF_BLOB_Tcl means that BLOB values are shown as a
@@ -604,10 +604,16 @@ Except the eEsp mode defaults to `QRF_ESC_On`, so that control
 characters are escaped, for safety.
 
 The **Csv** and **Quote** styles are simply variations on **List**
-with different defaults.
-**Csv** sets things up to generate valid CSV file output.
-**Quote** displays a comma-separated list of SQL
-value literals.
+with hard-coded values for some of the sqlite3_qrf_spec settings:
+
+<table border=1 cellpadding=2 cellspacing=0>
+<tr><th>&nbsp;<th>Quote<th>Csv
+<tr><td>zColumnSep<td>","<td>","
+<tr><td>zRowSep<td>"\\n"<td>"\\r\\n"
+<tr><td>zNull<td>"NULL"<td>""
+<tr><td>eText<td>QRF_TEXT_Sql<td>QRF_TEXT_Csv
+<tr><td>eBlob<td>QRF_BLOB_Sql<td>QRF_BLOB_Text
+</table>
 
 The **Html** style generates HTML table content, just without
 the `<TABLE>..</TABLE>` around the outside.
index ca3eb7d8127be91bd66b3ece3ba63bca0d01b195..fdee93a86f0d08920ec40d49855a28908e185b3d 100644 (file)
@@ -2261,9 +2261,10 @@ qrf_reinit:
     case QRF_STYLE_Csv: {
       p->spec.eStyle = QRF_STYLE_List;
       p->spec.eText = QRF_TEXT_Csv;
-      p->spec.eBlob = QRF_BLOB_Tcl;
+      p->spec.eBlob = QRF_BLOB_Text;
       p->spec.zColumnSep = ",";
       p->spec.zRowSep = "\r\n";
+      p->spec.zNull = "";
       break;
     }
     case QRF_STYLE_Quote: {
index 7cb99c345f86143d53943a2c715f550056bf582a..6dc92154c262cc8a89a53f27280a3b96fef636be 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\stest\scases\simpacted\sby\sthe\suse\sof\s~/.sqliterc
-D 2025-11-17T00:15:14.616
+C CLI\scomes\sup\sin\slegacy\s"list"\smode\sif\sneither\sstdin\sor\sstdout\sare\sa\stty,\sor\nif\s"--compat\s20251114"\sor\searlier\sis\sspecified,\sor\sif\s"--batch"\sis\sused.\nNew\smodes\s"batch"\sand\s"tty"\sselect\seither\slegacy\s"list"\smode\sor\sthe\snewer\n"qbox"\smode\swith\slimits.\nMake\sCVS\soutput\scompatible\swith\slegacy.\s\sBreak\sout\s".import"\sinto\sa\sseparate\nsubroutine\sin\santicipation\sof\sforthcoming\simprovements.\s\sGeneral\scode\ncleanup.
+D 2025-11-17T16:44:13.271
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -416,8 +416,8 @@ 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 09dd538966d8ee32598fc010e7fe6755bd7190494953a02960a9c81197d20cf3
-F ext/qrf/qrf.c 290b95fa8613e11a90d5a5a92c32fa22ce415b01fa9578ff5070427b057ac02e
+F ext/qrf/README.md dd565fd1ca0c46ea37dbf4d496e368b9ecade768c92669640bc106e039629016
+F ext/qrf/qrf.c b62a16a4d380223c6fe90ae28c33aa27b44af128a2e1e39c496d3452ac6ae14f
 F ext/qrf/qrf.h b4b3489b3b3683523fd248d15cf5945830643b036943efacdb772a3e00367aa2
 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
@@ -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 64f777047c972c46df3f04597367980732a58aa0b58a21e4862812a225edee12
+F src/shell.c.in 4cae6e0560037d4916f2819e3ca576e71e4a40adee9fc273f320cd28644e99c6
 F src/sqlite.h.in 795ce84cc136b4e74d882cf4fab56d2927c20b9af9fd2fcea27760a6fe50851b
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 5d5330f5f8461f5ce74960436ddcfa53ecd09c2b8b23901e22ae38aec3243998
@@ -1507,7 +1507,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 f46f498e447d988b52dfa1cd45c683b7212bb2056741a4eff306df185b9c00b1
+F test/qrf01.test 1caa611b69e07c6c134c3749f633ef014a97640b8f57f03c8fc0ca4d064b6dd2
 F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
 F test/qrf03.test 9d88aeb5cdd53f050b7ab9bd203281f7c9d063c33f22f8808e441b7ac0874ccf
 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
@@ -1603,11 +1603,11 @@ 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 88acfa38c2ecfd5beb25825fdd3a93f4a81573a736c04de82e6e377efa6f6efd
-F test/shell2.test 38c394063f9398bc559fbe5b232c8e7f0c817dd33edd21ee8c4c4c1b650530db
+F test/shell1.test b88af002858e1923f90fccf07f841d853e8fba568cecf38efd29f5172c661735
+F test/shell2.test 103140814bdc7508aa41dd3462413cbc4aa84b4261112cb8d501d74275cb7d48
 F test/shell3.test 840192774cc4edf7653520c0434a311c7477b9bc324abbc7bd2887915792fa8c
 F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db
-F test/shell5.test ed3c2cf47330134ab91e5b16dbdc34a7fb955ff2999ff69bac908cc2238b2b7f
+F test/shell5.test 7a249400bb2af59ac8524f357f8cf2844a62b6ac5ff8ecd69b045ceb688700ae
 F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8
 F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3
 F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871
@@ -2176,8 +2176,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 2aebd7bfecaaf1f75b52b05a0d3009fc0dc61289ae666d24cb4e3ddfaf251645
-R 5f06e6da6beb48984fc94266490a7906
+P 8fc05faef91186429c6c710991fd736b1df9a9af946c29d207db2518d6436b38
+R bb02fe9715521dfd12626a15ebffae99
 U drh
-Z 490dbb4a529fbc5b5a8e8da8dff783f8
+Z 405f1a2cbb60052057d2e27df0bf6378
 # Remove this line to create a well-formed Fossil manifest.
index 891e6da3167ffeb3995b08cc371e1fbca43a5ee4..437b001ca5906d62193aaac822b921a9584a88a2 100644 (file)
@@ -1 +1 @@
-8fc05faef91186429c6c710991fd736b1df9a9af946c29d207db2518d6436b38
+f6bfcea9a01493af182e9aa0d35df6f81bf9e36220df79139afa287fa43d9aa3
index dc1ad60b872cabde341abe6e1f856cc56b72a775..0453c1bd00a64ad162148a1b12ba69e9c5dddd47 100644 (file)
@@ -534,12 +534,17 @@ static int bail_on_error = 0;
 static int stdin_is_interactive = 1;
 
 /*
-** On Windows systems we need to know if standard output is a console
-** in order to show that UTF-16 translation is done in the sign-on
-** banner. The following variable is true if it is the console.
+** Treat stdout like a TTY if true.
 */
 static int stdout_is_console = 1;
 
+/*
+** Use this value as the width of the output device.  Or, figure it
+** out at runtime if the value is negative.  Or use a default width
+** if this value is zero.
+*/
+static int stdout_tty_width = -1;
+
 /*
 ** The following is the open SQLite database.  We make a pointer
 ** to this database a static variable so that it can be accessed
@@ -1251,12 +1256,16 @@ typedef struct Mode {
   u8 autoEQPtrace;       /* autoEQP is in trace mode */
   u8 scanstatsOn;        /* True to display scan stats before each finalize */
   u8 bAutoScreenWidth;   /* Using the TTY to determine screen width */
-  u8 bEcho;              /* True to echo all SQL to output */
+  u8 mFlags;             /* MFLG_ECHO and/or MFLG_CRLF */
   u8 eMode;              /* One of the MODE_ values */
-  u8 crlfMode;           /* Do NL-to-CRLF translations when enabled (maybe) */
   sqlite3_qrf_spec spec; /* Spec to be passed into QRF */
 } Mode;
 
+/* Flags for Mode.mFlags */
+#define MFLG_ECHO  0x01  /* Echo inputs to output */
+#define MFLG_CRLF  0x02  /* Use CR/LF output line endings */
+
+
 /*
 ** State information about the database connection is contained in an
 ** instance of the following structure.
@@ -1426,8 +1435,14 @@ static const char *qrfQuoteNames[] =
 #define MODE_Tcl      19  /* Space-separated list of TCL strings */
 #define MODE_Www      20  /* Full web-page output */
 
+#define MODE_BUILTIN  20  /* Maximum built-in mode */
+#define MODE_BATCH    50  /* Default mode for batch processing */
+#define MODE_TTY      51  /* Default mode for interactive processing */
+#define MODE_USER     75  /* First user-defined mode */
+#define MODE_N_USER   25  /* Maximum number of user-defined modes */
+
 /*
-** Information about how each display-mode behaves.
+** Information about built-in display modes
 */
 typedef struct ModeInfo ModeInfo;
 struct ModeInfo {
@@ -1442,6 +1457,8 @@ struct ModeInfo {
   unsigned char eStyle;  /* Underlying QRF style */
   unsigned char eCx;     /* 0: other, 1: line, 2: columnar */
 };
+
+/* String constants used by built-in modes */
 static const char *aModeStr[] = 
   /* 0    1       2       3       4     5        6        7        8    */
    { 0,   "\n",   "|",    " ",    ",",  "\r\n",  "\036",  "\037",  "\t",
@@ -1455,7 +1472,7 @@ static const ModeInfo aModeInfo[] = {
   { "c",        4,     1,    10,   5,    5,    4,   1,   12,    0 },
   { "column",   0,     0,    9,    1,    1,    1,   2,   2,     2 },
   { "count",    0,     0,    0,    0,    0,    0,   0,   3,     0 },
-  { "csv",      4,     5,    9,    3,    3,    1,   1,   12,    0 },
+  { "csv",      4,     5,    9,    3,    3,    3,   1,   12,    0 },
   { "html",     0,     0,    9,    4,    4,    1,   2,   7,     0 },
   { "insert",   0,     0,    10,   2,    2,    2,   1,   8,     0 },
   { "jatom",    4,     1,    11,   6,    6,    5,   1,   12,    0 },
@@ -1593,9 +1610,22 @@ static void modeChange(ShellState *p, unsigned char eMode){
     pM->spec.eBlob = pI->eBlob;
     pM->spec.bTitles = pI->bHdr;
     pM->spec.eTitle = pI->eHdr;
-  }else if( eMode>=100 && eMode-100<p->nSavedModes ){
+  }else if( eMode>=MODE_USER && eMode-MODE_USER<p->nSavedModes ){
+    modeFree(&p->mode);
+    modeDup(&p->mode, &p->aSavedModes[eMode-MODE_USER].mode);
+  }else if( eMode==MODE_BATCH ){
+    u8 mFlags = p->mode.mFlags;
     modeFree(&p->mode);
-    modeDup(&p->mode, &p->aSavedModes[eMode-100].mode);
+    modeChange(p, MODE_List);
+    p->mode.mFlags = mFlags;
+  }else if( eMode==MODE_TTY ){
+    u8 mFlags = p->mode.mFlags;
+    modeFree(&p->mode);
+    modeChange(p, MODE_QBox);
+    p->mode.bAutoScreenWidth = 1;
+    p->mode.spec.nCharLimit = 300;
+    p->mode.spec.nLineLimit = 5;
+    p->mode.mFlags = mFlags;
   }
 }
 
@@ -1607,13 +1637,10 @@ static void modeChange(ShellState *p, unsigned char eMode){
 static void modeDefault(ShellState *p){
   p->mode.spec.iVersion = 1;
   p->mode.autoExplain = 1;
-  if( p->iCompat>=20251115 ){
-    modeChange(p, MODE_QBox);
-    p->mode.bAutoScreenWidth = 1;
-    p->mode.spec.nCharLimit = 300;
-    p->mode.spec.nLineLimit = 5;
+  if( p->iCompat>=20251115 && (stdin_is_interactive || stdout_is_console) ){
+    modeChange(p, MODE_TTY);
   }else{
-    modeChange(p, MODE_List);
+    modeChange(p, MODE_BATCH);
   }
 }
 
@@ -1622,7 +1649,12 @@ static void modeDefault(ShellState *p){
 ** the name does not match any mode.
 **
 ** Saved modes are also searched if p!=NULL.  The number returned
-** for a saved mode is the index into the p->aSavedModes[] array plus 100.
+** for a saved mode is the index into the p->aSavedModes[] array 
+** plus MODE_USER.
+**
+** Two special mode names are also available: "batch" and "tty".
+** evaluate to the default mode for batch operation and interactive
+** operation on a TTY, respectively.
 */
 static int modeFind(ShellState *p, const char *zName){
   int i;
@@ -1630,8 +1662,10 @@ static int modeFind(ShellState *p, const char *zName){
     if( cli_strcmp(aModeInfo[i].zName,zName)==0 ) return i;
   }
   for(i=0; i<p->nSavedModes; i++){
-    if( cli_strcmp(p->aSavedModes[i].zTag,zName)==0 ) return i+100;
+    if( cli_strcmp(p->aSavedModes[i].zTag,zName)==0 ) return i+MODE_USER;
   }
+  if( strcmp(zName,"batch")==0 ) return MODE_BATCH;
+  if( strcmp(zName,"tty")==0 ) return MODE_TTY;
   return -1;
 }
 
@@ -1901,7 +1935,7 @@ edit_func_end:
 */
 static void setCrlfMode(ShellState *p){
 #ifdef _WIN32
-  if( p->mode.crlfMode ){
+  if( p->mode.mFlags & MFLG_CRLF ){
     sqlite3_fsetmode(p->out, _O_TEXT);
   }else{
     sqlite3_fsetmode(p->out, _O_BINARY);
@@ -2065,7 +2099,8 @@ static void interrupt_handler(int NotUsed){
 
 /* Try to determine the screen width.  Use the default if unable.
 */
-int shellScreenWidth(int dfltWidth){
+int shellScreenWidth(void){
+  if( stdout_tty_width>0 ) return stdout_tty_width;
 #if defined(TIOCGSIZE)
   struct ttysize ts;
   if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)>=0
@@ -2091,7 +2126,8 @@ int shellScreenWidth(int dfltWidth){
     return csbi.srWindow.Right - csbi.srWindow.Left + 1;
   }
 #endif
-  return dfltWidth;
+#define DEFAULT_SCREEN_WIDTH 80
+  return DEFAULT_SCREEN_WIDTH;
 }
 
 #if (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
@@ -3142,7 +3178,7 @@ static int shell_exec(
   assert( pArg->mode.eMode>=0 && pArg->mode.eMode<ArraySize(aModeInfo) );
   eStyle = aModeInfo[pArg->mode.eMode].eStyle;
   if( pArg->mode.bAutoScreenWidth ){
-    spec.nScreenWidth = shellScreenWidth(80);
+    spec.nScreenWidth = shellScreenWidth();
   }
 
 #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION)
@@ -3660,19 +3696,6 @@ static const char *(azHelp[]) = {
   ".help ?-all? ?PATTERN?   Show help text for PATTERN",
 #ifndef SQLITE_SHELL_FIDDLE
   ".import FILE TABLE       Import data from FILE into TABLE",
-  "   Options:",
-  "     --ascii               Use \\037 and \\036 as column and row separators",
-  "     --csv                 Use , and \\n as column and row separators",
-  "     --skip N              Skip the first N rows of input",
-  "     --schema S            Target table to be S.TABLE",
-  "     -v                    \"Verbose\" - increase auxiliary output",
-  "   Notes:",
-  "     *  If TABLE does not exist, it is created.  The first row of input",
-  "        determines the column names.",
-  "     *  If neither --csv or --ascii are used, the input mode is derived",
-  "        from the \".mode\" output mode",
-  "     *  If FILE begins with \"|\" then it is a command that generates the",
-  "        input text.",
 #endif
 #ifndef SQLITE_OMIT_TEST_CONTROL
   ".imposter INDEX TABLE    Create imposter table TABLE on index INDEX",
@@ -7217,6 +7240,352 @@ static int pickStr(const char *zArg, char **pzErr, ...){
   return -1;
 }
 
+/*
+** DOT-COMMAND: .import
+**
+** USAGE: .import [OPTIONS] FILE TABLE
+**
+** Import CSV or similar text from FILE into TABLE.  If TABLE does
+** not exist, it is created using the first row of FILE as the column
+** names.  If FILE begins with "|" then it is a command that is run
+** and the output from the command is used as the input data.
+**
+** FILE is assumed to be in a CSV format, unless the current mode
+** is "ascii" or "tabs" or unless one of the options below specify
+** an alternative.
+**
+** Options:
+**   --ascii         Use \037 and \036 as column and row separators on input
+**   --csv           Input is standard RFC-4180 CSV.
+**   --schema S      When creating TABLE, put it in schema S
+**   --skip N        Ignore the first N rows of input
+**   -v              Verbose mode
+*/
+static int dotCmdImport(ShellState *p){
+  int nArg = p->dot.nArg;     /* Number of arguments */
+  char **azArg = p->dot.azArg;/* Argument list */
+  char *zTable = 0;           /* Insert data into this table */
+  char *zSchema = 0;          /* Schema of zTable */
+  char *zFile = 0;            /* Name of file to extra content from */
+  sqlite3_stmt *pStmt = NULL; /* A statement */
+  int nCol;                   /* Number of columns in the table */
+  i64 nByte;                  /* Number of bytes in an SQL string */
+  int i, j;                   /* Loop counters */
+  int needCommit;             /* True to COMMIT or ROLLBACK at end */
+  int nSep;                   /* Number of bytes in spec.zColumnSep */
+  char *zSql = 0;             /* An SQL statement */
+  ImportCtx sCtx;             /* Reader context */
+  char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
+  int eVerbose = 0;           /* Larger for more console output */
+  i64 nSkip = 0;              /* Initial lines to skip */
+  int useOutputMode = 1;      /* Use output mode to determine separators */
+  char *zCreate = 0;          /* CREATE TABLE statement text */
+  int rc;                     /* Result code */
+
+  failIfSafeMode(p, "cannot run .import in safe mode");
+  memset(&sCtx, 0, sizeof(sCtx));
+  if( p->mode.eMode==MODE_Ascii ){
+    xRead = ascii_read_one_field;
+  }else{
+    xRead = csv_read_one_field;
+  }
+  for(i=1; i<nArg; i++){
+    char *z = azArg[i];
+    if( z[0]=='-' && z[1]=='-' ) z++;
+    if( z[0]!='-' ){
+      if( zFile==0 ){
+        zFile = z;
+      }else if( zTable==0 ){
+        zTable = z;
+      }else{
+        dotCmdError(p, i, "unknown argument", 0);
+        return 1;
+      }
+    }else if( cli_strcmp(z,"-v")==0 ){
+      eVerbose++;
+    }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
+      zSchema = azArg[++i];
+    }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
+      nSkip = integerValue(azArg[++i]);
+    }else if( cli_strcmp(z,"-ascii")==0 ){
+      sCtx.cColSep = SEP_Unit[0];
+      sCtx.cRowSep = SEP_Record[0];
+      xRead = ascii_read_one_field;
+      useOutputMode = 0;
+    }else if( cli_strcmp(z,"-csv")==0 ){
+      sCtx.cColSep = ',';
+      sCtx.cRowSep = '\n';
+      xRead = csv_read_one_field;
+      useOutputMode = 0;
+    }else{
+      dotCmdError(p, i, "unknown option", 0);
+      return 1;
+    }
+  }
+  if( zTable==0 ){
+    cli_printf(p->out, "ERROR: missing %s argument\n",
+          zFile==0 ? "FILE" : "TABLE");
+    return 1;
+  }
+  seenInterrupt = 0;
+  open_db(p, 0);
+  if( useOutputMode ){
+    /* If neither the --csv or --ascii options are specified, then set
+    ** the column and row separator characters from the output mode. */
+    if( p->mode.spec.zColumnSep==0 ){
+      modeSetStr(&p->mode.spec.zColumnSep, ",");
+      nSep = 1;
+    }else if( (nSep = strlen30(p->mode.spec.zColumnSep))==0 ){
+      eputz("Error: non-null column separator required for import\n");
+      return 1;
+    }
+    if( nSep>1 ){
+      eputz("Error: multi-character column separators not allowed"
+            " for import\n");
+      return 1;
+    }
+    if( p->mode.spec.zRowSep==0 ){
+      modeSetStr(&p->mode.spec.zRowSep, "\n");
+      nSep = 1;
+    }else if( (nSep = strlen30(p->mode.spec.zRowSep))==0 ){
+      eputz("Error: non-null row separator required for import\n");
+      return 1;
+    }
+    if( nSep==2 && p->mode.eMode==MODE_Csv
+     && cli_strcmp(p->mode.spec.zRowSep,SEP_CrLf)==0
+    ){
+      /* When importing CSV (only), if the row separator is set to the
+      ** default output row separator, change it to the default input
+      ** row separator.  This avoids having to maintain different input
+      ** and output row separators. */
+      modeSetStr(&p->mode.spec.zRowSep, SEP_Row);
+      nSep = strlen30(p->mode.spec.zRowSep);
+    }
+    if( nSep>1 ){
+      eputz("Error: multi-character row separators not allowed"
+            " for import\n");
+      return 1;
+    }
+    sCtx.cColSep = (u8)p->mode.spec.zColumnSep[0];
+    sCtx.cRowSep = (u8)p->mode.spec.zRowSep[0];
+  }
+  sCtx.zFile = zFile;
+  sCtx.nLine = 1;
+  if( sCtx.zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+    eputz("Error: pipes are not supported in this OS\n");
+    return 1;
+#else
+    sCtx.in = sqlite3_popen(sCtx.zFile+1, "r");
+    sCtx.zFile = "<pipe>";
+    sCtx.xCloser = pclose;
+#endif
+  }else{
+    sCtx.in = sqlite3_fopen(sCtx.zFile, "rb");
+    sCtx.xCloser = fclose;
+  }
+  if( sCtx.in==0 ){
+    cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile);
+    return 1;
+  }
+  if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
+    char zSep[2];
+    zSep[1] = 0;
+    zSep[0] = sCtx.cColSep;
+    cli_puts("Column separator ", p->out);
+    output_c_string(p->out, zSep);
+    cli_puts(", row separator ", p->out);
+    zSep[0] = sCtx.cRowSep;
+    output_c_string(p->out, zSep);
+    cli_puts("\n", p->out);
+  }
+  sCtx.z = sqlite3_malloc64(120);
+  if( sCtx.z==0 ){
+    import_cleanup(&sCtx);
+    shell_out_of_memory();
+  }
+  /* Below, resources must be freed before exit. */
+  while( nSkip>0 ){
+    nSkip--;
+    while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
+  }
+  import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
+  if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) 
+   && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema"
+                       " WHERE name=%Q AND type='view'",
+                       zSchema ? zSchema : "main", zTable)
+  ){
+    /* Table does not exist.  Create it. */
+    sqlite3 *dbCols = 0;
+    char *zRenames = 0;
+    char *zColDefs;
+    zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", 
+                  zSchema ? zSchema : "main", zTable);
+    while( xRead(&sCtx) ){
+      zAutoColumn(sCtx.z, &dbCols, 0);
+      if( sCtx.cTerm!=sCtx.cColSep ) break;
+    }
+    zColDefs = zAutoColumn(0, &dbCols, &zRenames);
+    if( zRenames!=0 ){
+      cli_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
+            "Columns renamed during .import %s due to duplicates:\n"
+            "%s\n", sCtx.zFile, zRenames);
+      sqlite3_free(zRenames);
+    }
+    assert(dbCols==0);
+    if( zColDefs==0 ){
+      cli_printf(stderr,"%s: empty file\n", sCtx.zFile);
+      import_cleanup(&sCtx);
+      sqlite3_free(zCreate);
+      return 1;
+    }
+    zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
+    if( zCreate==0 ){
+      import_cleanup(&sCtx);
+      shell_out_of_memory();
+    }
+    if( eVerbose>=1 ){
+      cli_printf(p->out, "%s\n", zCreate);
+    }
+    rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
+    if( rc ){
+      cli_printf(stderr,
+           "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
+    }
+    sqlite3_free(zCreate);
+    zCreate = 0;
+    if( rc ){
+      import_cleanup(&sCtx);
+      return 1;
+    }
+  }
+  zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);",
+                         zTable, zSchema);
+  if( zSql==0 ){
+    import_cleanup(&sCtx);
+    shell_out_of_memory();
+  }
+  rc =  sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  sqlite3_free(zSql);
+  zSql = 0;
+  if( rc ){
+    if (pStmt) sqlite3_finalize(pStmt);
+    shellDatabaseError(p->db);
+    import_cleanup(&sCtx);
+    return 1;
+  }
+  if( sqlite3_step(pStmt)==SQLITE_ROW ){
+    nCol = sqlite3_column_int(pStmt, 0);
+  }else{
+    nCol = 0;
+  }
+  sqlite3_finalize(pStmt);
+  pStmt = 0;
+  if( nCol==0 ) return 0; /* no columns, no error */
+
+  nByte = 64                 /* space for "INSERT INTO", "VALUES(", ")\0" */
+        + (zSchema ? strlen(zSchema)*2 + 2: 0)  /* Quoted schema name */
+        + strlen(zTable)*2 + 2                  /* Quoted table name */
+        + nCol*2;            /* Space for ",?" for each column */
+  zSql = sqlite3_malloc64( nByte );
+  if( zSql==0 ){
+    import_cleanup(&sCtx);
+    shell_out_of_memory();
+  }
+  if( zSchema ){
+    sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", 
+                     zSchema, zTable);
+  }else{
+    sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
+  }
+  j = strlen30(zSql);
+  for(i=1; i<nCol; i++){
+    zSql[j++] = ',';
+    zSql[j++] = '?';
+  }
+  zSql[j++] = ')';
+  zSql[j] = 0;
+  assert( j<nByte );
+  if( eVerbose>=2 ){
+    cli_printf(p->out, "Insert using: %s\n", zSql);
+  }
+  rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+  sqlite3_free(zSql);
+  zSql = 0;
+  if( rc ){
+    shellDatabaseError(p->db);
+    if (pStmt) sqlite3_finalize(pStmt);
+    import_cleanup(&sCtx);
+    return 1;
+  }
+  needCommit = sqlite3_get_autocommit(p->db);
+  if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
+  do{
+    int startLine = sCtx.nLine;
+    for(i=0; i<nCol; i++){
+      char *z = xRead(&sCtx);
+      /*
+      ** Did we reach end-of-file before finding any columns?
+      ** If so, stop instead of NULL filling the remaining columns.
+      */
+      if( z==0 && i==0 ) break;
+      /*
+      ** Did we reach end-of-file OR end-of-line before finding any
+      ** columns in ASCII mode?  If so, stop instead of NULL filling
+      ** the remaining columns.
+      */
+      if( p->mode.eMode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
+      /*
+      ** For CSV mode, per RFC 4180, accept EOF in lieu of final
+      ** record terminator but only for last field of multi-field row.
+      ** (If there are too few fields, it's not valid CSV anyway.)
+      */
+      if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){
+        z = "";
+      }
+      sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
+      if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
+        cli_printf(stderr,"%s:%d: expected %d columns but found %d"
+              " - filling the rest with NULL\n",
+              sCtx.zFile, startLine, nCol, i+1);
+        i += 2;
+        while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
+      }
+    }
+    if( sCtx.cTerm==sCtx.cColSep ){
+      do{
+        xRead(&sCtx);
+        i++;
+      }while( sCtx.cTerm==sCtx.cColSep );
+      cli_printf(stderr,
+            "%s:%d: expected %d columns but found %d - extras ignored\n",
+            sCtx.zFile, startLine, nCol, i);
+    }
+    if( i>=nCol ){
+      sqlite3_step(pStmt);
+      rc = sqlite3_reset(pStmt);
+      if( rc!=SQLITE_OK ){
+        cli_printf(stderr,"%s:%d: INSERT failed: %s\n",
+              sCtx.zFile, startLine, sqlite3_errmsg(p->db));
+        sCtx.nErr++;
+      }else{
+        sCtx.nRow++;
+      }
+    }
+  }while( sCtx.cTerm!=EOF );
+
+  import_cleanup(&sCtx);
+  sqlite3_finalize(pStmt);
+  if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
+  if( eVerbose>0 ){
+    cli_printf(p->out,
+          "Added %d rows with %d errors using %d lines of input\n",
+          sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
+  }
+  return 0;
+}
+
+
 /*
 ** This function computes what to show the user about the configured
 ** titles (or column-names).  Output is an integer between 0 and 3:
@@ -7352,12 +7721,6 @@ static int dotCmdMode(ShellState *p){
         modeSetStr(&p->mode.spec.zTableName, azArg[i]);
       }
       chng = 1;
-    }else if( z[0]=='2' && strlen(z)==8 && atoi(z)>20000101 ){
-      modeFree(&p->mode);
-      p->iCompat = atoi(z);
-      memset(&p->mode, 0, sizeof(p->mode));
-      modeDefault(p);
-      chng = 1;
     }else if( optionMatch(z,"align") ){
       char *zAlign;
       int nAlign;
@@ -7442,7 +7805,7 @@ static int dotCmdMode(ShellState *p){
       for(ii=0; ii<p->nSavedModes; ii++){
         cli_printf(p->out, " %s", p->aSavedModes[ii].zTag);
       }
-      cli_puts("\n", p->out);
+      cli_puts(" batch tty\n", p->out);
     }else if( optionMatch(z,"quote") ){
       if( i+1<nArg
        && azArg[i+1][0]!='-'
@@ -7541,7 +7904,7 @@ static int dotCmdMode(ShellState *p){
         dotCmdError(p, i, "mode already exists", 0);
         return 1;
       }
-      if( p->nSavedModes > 100 ){
+      if( p->nSavedModes > MODE_N_USER ){
         dotCmdError(p, i-1, "cannot add more modes", 0);
         return 1;
       }
@@ -7983,7 +8346,7 @@ static int dotCmdOutput(ShellState *p){
     if( eMode=='x' ){
       /* spreadsheet mode.  Output as CSV. */
       newTempFile(p, "csv");
-      p->mode.bEcho = 0;
+      p->mode.mFlags &= ~MFLG_ECHO;
       p->mode.eMode = MODE_Csv;
       modeSetStr(&p->mode.spec.zColumnSep, SEP_Comma);
       modeSetStr(&p->mode.spec.zRowSep, SEP_CrLf);
@@ -8382,12 +8745,17 @@ static int do_meta_command(const char *zLine, ShellState *p){
   ){
     if( nArg==2 ){
 #ifdef _WIN32
-      p->mode.crlfMode = booleanValue(azArg[1]);
+      if( booleanValue(azArg[1]) ){
+        p->mode.mFlags |= MFLG_CRLF;
+      }else{
+        p->mode.mFlags &= ~MFLG_CRLF;
+      }
 #else
-      p->mode.crlfMode = 0;
+      p->mode.mFlags &= ~MFLG_CRLF;
 #endif
     }
-    cli_printf(stderr, "crlf is %s\n", p->mode.crlfMode ? "ON" : "OFF");
+    cli_printf(stderr, "crlf is %s\n", 
+       (p->mode.mFlags & MFLG_CRLF)!=0 ? "ON" : "OFF");
   }else
 
   if( c=='d' && n>1 && cli_strncmp(azArg[0], "databases", n)==0 ){
@@ -8602,7 +8970,11 @@ static int do_meta_command(const char *zLine, ShellState *p){
 
   if( c=='e' && cli_strncmp(azArg[0], "echo", n)==0 ){
     if( nArg==2 ){
-      p->mode.bEcho = booleanValue(azArg[1]);
+      if( booleanValue(azArg[1]) ){
+        p->mode.mFlags |= MFLG_ECHO;
+      }else{
+        p->mode.mFlags &= ~MFLG_ECHO;
+      }
     }else{
       eputz("Usage: .echo on|off\n");
       rc = 1;
@@ -8910,331 +9282,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
 
 #ifndef SQLITE_SHELL_FIDDLE
   if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
-    char *zTable = 0;           /* Insert data into this table */
-    char *zSchema = 0;          /* Schema of zTable */
-    char *zFile = 0;            /* Name of file to extra content from */
-    sqlite3_stmt *pStmt = NULL; /* A statement */
-    int nCol;                   /* Number of columns in the table */
-    i64 nByte;                  /* Number of bytes in an SQL string */
-    int i, j;                   /* Loop counters */
-    int needCommit;             /* True to COMMIT or ROLLBACK at end */
-    int nSep;                   /* Number of bytes in spec.zColumnSep */
-    char *zSql = 0;             /* An SQL statement */
-    ImportCtx sCtx;             /* Reader context */
-    char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
-    int eVerbose = 0;           /* Larger for more console output */
-    i64 nSkip = 0;              /* Initial lines to skip */
-    int useOutputMode = 1;      /* Use output mode to determine separators */
-    char *zCreate = 0;          /* CREATE TABLE statement text */
-
-    failIfSafeMode(p, "cannot run .import in safe mode");
-    memset(&sCtx, 0, sizeof(sCtx));
-    if( p->mode.eMode==MODE_Ascii ){
-      xRead = ascii_read_one_field;
-    }else{
-      xRead = csv_read_one_field;
-    }
-    rc = 1;
-    for(i=1; i<nArg; i++){
-      char *z = azArg[i];
-      if( z[0]=='-' && z[1]=='-' ) z++;
-      if( z[0]!='-' ){
-        if( zFile==0 ){
-          zFile = z;
-        }else if( zTable==0 ){
-          zTable = z;
-        }else{
-          cli_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n",z);
-          showHelp(p->out, "import");
-          goto meta_command_exit;
-        }
-      }else if( cli_strcmp(z,"-v")==0 ){
-        eVerbose++;
-      }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
-        zSchema = azArg[++i];
-      }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
-        nSkip = integerValue(azArg[++i]);
-      }else if( cli_strcmp(z,"-ascii")==0 ){
-        sCtx.cColSep = SEP_Unit[0];
-        sCtx.cRowSep = SEP_Record[0];
-        xRead = ascii_read_one_field;
-        useOutputMode = 0;
-      }else if( cli_strcmp(z,"-csv")==0 ){
-        sCtx.cColSep = ',';
-        sCtx.cRowSep = '\n';
-        xRead = csv_read_one_field;
-        useOutputMode = 0;
-      }else{
-        cli_printf(p->out, "ERROR: unknown option: \"%s\".  Usage:\n", z);
-        showHelp(p->out, "import");
-        goto meta_command_exit;
-      }
-    }
-    if( zTable==0 ){
-      cli_printf(p->out, "ERROR: missing %s argument. Usage:\n",
-            zFile==0 ? "FILE" : "TABLE");
-      showHelp(p->out, "import");
-      goto meta_command_exit;
-    }
-    seenInterrupt = 0;
-    open_db(p, 0);
-    if( useOutputMode ){
-      /* If neither the --csv or --ascii options are specified, then set
-      ** the column and row separator characters from the output mode. */
-      if( p->mode.spec.zColumnSep==0 ){
-        modeSetStr(&p->mode.spec.zColumnSep, ",");
-        nSep = 1;
-      }else if( (nSep = strlen30(p->mode.spec.zColumnSep))==0 ){
-        eputz("Error: non-null column separator required for import\n");
-        goto meta_command_exit;
-      }
-      if( nSep>1 ){
-        eputz("Error: multi-character column separators not allowed"
-              " for import\n");
-        goto meta_command_exit;
-      }
-      if( p->mode.spec.zRowSep==0 ){
-        modeSetStr(&p->mode.spec.zRowSep, "\n");
-        nSep = 1;
-      }else if( (nSep = strlen30(p->mode.spec.zRowSep))==0 ){
-        eputz("Error: non-null row separator required for import\n");
-        goto meta_command_exit;
-      }
-      if( nSep==2 && p->mode.eMode==MODE_Csv
-       && cli_strcmp(p->mode.spec.zRowSep,SEP_CrLf)==0
-      ){
-        /* When importing CSV (only), if the row separator is set to the
-        ** default output row separator, change it to the default input
-        ** row separator.  This avoids having to maintain different input
-        ** and output row separators. */
-        modeSetStr(&p->mode.spec.zRowSep, SEP_Row);
-        nSep = strlen30(p->mode.spec.zRowSep);
-      }
-      if( nSep>1 ){
-        eputz("Error: multi-character row separators not allowed"
-              " for import\n");
-        goto meta_command_exit;
-      }
-      sCtx.cColSep = (u8)p->mode.spec.zColumnSep[0];
-      sCtx.cRowSep = (u8)p->mode.spec.zRowSep[0];
-    }
-    sCtx.zFile = zFile;
-    sCtx.nLine = 1;
-    if( sCtx.zFile[0]=='|' ){
-#ifdef SQLITE_OMIT_POPEN
-      eputz("Error: pipes are not supported in this OS\n");
-      goto meta_command_exit;
-#else
-      sCtx.in = sqlite3_popen(sCtx.zFile+1, "r");
-      sCtx.zFile = "<pipe>";
-      sCtx.xCloser = pclose;
-#endif
-    }else{
-      sCtx.in = sqlite3_fopen(sCtx.zFile, "rb");
-      sCtx.xCloser = fclose;
-    }
-    if( sCtx.in==0 ){
-      cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile);
-      goto meta_command_exit;
-    }
-    if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
-      char zSep[2];
-      zSep[1] = 0;
-      zSep[0] = sCtx.cColSep;
-      cli_puts("Column separator ", p->out);
-      output_c_string(p->out, zSep);
-      cli_puts(", row separator ", p->out);
-      zSep[0] = sCtx.cRowSep;
-      output_c_string(p->out, zSep);
-      cli_puts("\n", p->out);
-    }
-    sCtx.z = sqlite3_malloc64(120);
-    if( sCtx.z==0 ){
-      import_cleanup(&sCtx);
-      shell_out_of_memory();
-    }
-    /* Below, resources must be freed before exit. */
-    while( nSkip>0 ){
-      nSkip--;
-      while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
-    }
-    import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
-    if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) 
-     && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema"
-                         " WHERE name=%Q AND type='view'",
-                         zSchema ? zSchema : "main", zTable)
-    ){
-      /* Table does not exist.  Create it. */
-      sqlite3 *dbCols = 0;
-      char *zRenames = 0;
-      char *zColDefs;
-      zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"", 
-                    zSchema ? zSchema : "main", zTable);
-      while( xRead(&sCtx) ){
-        zAutoColumn(sCtx.z, &dbCols, 0);
-        if( sCtx.cTerm!=sCtx.cColSep ) break;
-      }
-      zColDefs = zAutoColumn(0, &dbCols, &zRenames);
-      if( zRenames!=0 ){
-        cli_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
-              "Columns renamed during .import %s due to duplicates:\n"
-              "%s\n", sCtx.zFile, zRenames);
-        sqlite3_free(zRenames);
-      }
-      assert(dbCols==0);
-      if( zColDefs==0 ){
-        cli_printf(stderr,"%s: empty file\n", sCtx.zFile);
-        import_cleanup(&sCtx);
-        rc = 1;
-        sqlite3_free(zCreate);
-        goto meta_command_exit;
-      }
-      zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
-      if( zCreate==0 ){
-        import_cleanup(&sCtx);
-        shell_out_of_memory();
-      }
-      if( eVerbose>=1 ){
-        cli_printf(p->out, "%s\n", zCreate);
-      }
-      rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
-      if( rc ){
-        cli_printf(stderr,
-             "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
-      }
-      sqlite3_free(zCreate);
-      zCreate = 0;
-      if( rc ){
-        import_cleanup(&sCtx);
-        rc = 1;
-        goto meta_command_exit;
-      }
-    }
-    zSql = sqlite3_mprintf("SELECT count(*) FROM pragma_table_info(%Q,%Q);",
-                           zTable, zSchema);
-    if( zSql==0 ){
-      import_cleanup(&sCtx);
-      shell_out_of_memory();
-    }
-    rc =  sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
-    sqlite3_free(zSql);
-    zSql = 0;
-    if( rc ){
-      if (pStmt) sqlite3_finalize(pStmt);
-      shellDatabaseError(p->db);
-      import_cleanup(&sCtx);
-      rc = 1;
-      goto meta_command_exit;
-    }
-    if( sqlite3_step(pStmt)==SQLITE_ROW ){
-      nCol = sqlite3_column_int(pStmt, 0);
-    }else{
-      nCol = 0;
-    }
-    sqlite3_finalize(pStmt);
-    pStmt = 0;
-    if( nCol==0 ) return 0; /* no columns, no error */
-
-    nByte = 64                 /* space for "INSERT INTO", "VALUES(", ")\0" */
-          + (zSchema ? strlen(zSchema)*2 + 2: 0)  /* Quoted schema name */
-          + strlen(zTable)*2 + 2                  /* Quoted table name */
-          + nCol*2;            /* Space for ",?" for each column */
-    zSql = sqlite3_malloc64( nByte );
-    if( zSql==0 ){
-      import_cleanup(&sCtx);
-      shell_out_of_memory();
-    }
-    if( zSchema ){
-      sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?", 
-                       zSchema, zTable);
-    }else{
-      sqlite3_snprintf(nByte, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
-    }
-    j = strlen30(zSql);
-    for(i=1; i<nCol; i++){
-      zSql[j++] = ',';
-      zSql[j++] = '?';
-    }
-    zSql[j++] = ')';
-    zSql[j] = 0;
-    assert( j<nByte );
-    if( eVerbose>=2 ){
-      cli_printf(p->out, "Insert using: %s\n", zSql);
-    }
-    rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
-    sqlite3_free(zSql);
-    zSql = 0;
-    if( rc ){
-      shellDatabaseError(p->db);
-      if (pStmt) sqlite3_finalize(pStmt);
-      import_cleanup(&sCtx);
-      rc = 1;
-      goto meta_command_exit;
-    }
-    needCommit = sqlite3_get_autocommit(p->db);
-    if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
-    do{
-      int startLine = sCtx.nLine;
-      for(i=0; i<nCol; i++){
-        char *z = xRead(&sCtx);
-        /*
-        ** Did we reach end-of-file before finding any columns?
-        ** If so, stop instead of NULL filling the remaining columns.
-        */
-        if( z==0 && i==0 ) break;
-        /*
-        ** Did we reach end-of-file OR end-of-line before finding any
-        ** columns in ASCII mode?  If so, stop instead of NULL filling
-        ** the remaining columns.
-        */
-        if( p->mode.eMode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
-        /*
-        ** For CSV mode, per RFC 4180, accept EOF in lieu of final
-        ** record terminator but only for last field of multi-field row.
-        ** (If there are too few fields, it's not valid CSV anyway.)
-        */
-        if( z==0 && (xRead==csv_read_one_field) && i==nCol-1 && i>0 ){
-          z = "";
-        }
-        sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
-        if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
-          cli_printf(stderr,"%s:%d: expected %d columns but found %d"
-                " - filling the rest with NULL\n",
-                sCtx.zFile, startLine, nCol, i+1);
-          i += 2;
-          while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
-        }
-      }
-      if( sCtx.cTerm==sCtx.cColSep ){
-        do{
-          xRead(&sCtx);
-          i++;
-        }while( sCtx.cTerm==sCtx.cColSep );
-        cli_printf(stderr,
-              "%s:%d: expected %d columns but found %d - extras ignored\n",
-              sCtx.zFile, startLine, nCol, i);
-      }
-      if( i>=nCol ){
-        sqlite3_step(pStmt);
-        rc = sqlite3_reset(pStmt);
-        if( rc!=SQLITE_OK ){
-          cli_printf(stderr,"%s:%d: INSERT failed: %s\n",
-                sCtx.zFile, startLine, sqlite3_errmsg(p->db));
-          sCtx.nErr++;
-        }else{
-          sCtx.nRow++;
-        }
-      }
-    }while( sCtx.cTerm!=EOF );
-
-    import_cleanup(&sCtx);
-    sqlite3_finalize(pStmt);
-    if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
-    if( eVerbose>0 ){
-      cli_printf(p->out,
-            "Added %d rows with %d errors using %d lines of input\n",
-            sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
-    }
+    rc = dotCmdImport(p);
   }else
 #endif /* !defined(SQLITE_SHELL_FIDDLE) */
 
@@ -10629,7 +10677,8 @@ static int do_meta_command(const char *zLine, ShellState *p){
       rc = 1;
       goto meta_command_exit;
     }
-    cli_printf(p->out, "%12.12s: %s\n","echo", azBool[p->mode.bEcho!=0]);
+    cli_printf(p->out, "%12.12s: %s\n","echo", 
+            azBool[(p->mode.mFlags & MFLG_ECHO)!=0]);
     cli_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->mode.autoEQP&3]);
     cli_printf(p->out, "%12.12s: %s\n","explain",
                              p->mode.autoExplain ? "auto" : "off");
@@ -10784,7 +10833,7 @@ static int do_meta_command(const char *zLine, ShellState *p){
         len = strlen30(azResult[i]);
         if( len>maxlen ) maxlen = len;
       }
-      nPrintCol = 80/(maxlen+2);
+      nPrintCol = shellScreenWidth()/(maxlen+2);
       if( nPrintCol<1 ) nPrintCol = 1;
       nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
       for(i=0; i<nPrintRow; i++){
@@ -11771,7 +11820,7 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
 }
 
 static void echo_group_input(ShellState *p, const char *zDo){
-  if( p->mode.bEcho ){
+  if( p->mode.mFlags & MFLG_ECHO ){
     cli_printf(p->out, "%s\n", zDo);
     fflush(p->out);
   }
@@ -12175,6 +12224,7 @@ static const char zOptions[] =
   "   -quote               set output mode to 'quote'\n"
   "   -readonly            open the database read-only\n"
   "   -safe                enable safe-mode\n"
+  "   -screenwidth N       use N as the default screenwidth \n"
   "   -separator SEP       set output column separator. Default: '|'\n"
 #ifdef SQLITE_ENABLE_SORTER_REFERENCES
   "   -sorterref SIZE      sorter references threshold size\n"
@@ -12476,6 +12526,15 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       ** we do the actual processing of arguments later in a second pass.
       */
       stdin_is_interactive = 0;
+      stdout_is_console = 0;
+      modeChange(&data, MODE_BATCH);
+    }else if( cli_strcmp(z,"-screenwidth")==0 ){
+      int n = atoi(cmdline_option_value(argc, argv, ++i));
+      if( n<2 ){
+        sqlite3_fprintf(stderr,"minimum --screenwidth is 2\n");
+        exit(1);
+      }
+      stdout_tty_width = n;
     }else if( cli_strcmp(z,"-compat")==0 ){
       data.iCompat = atoi(cmdline_option_value(argc, argv, ++i));
       modeFree(&data.mode);
@@ -12761,7 +12820,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
      }else if( cli_strcmp(z,"-noheader")==0 ){
       data.mode.spec.bTitles = QRF_No;
     }else if( cli_strcmp(z,"-echo")==0 ){
-      data.mode.bEcho = 1;
+      data.mode.mFlags |= MFLG_ECHO;
     }else if( cli_strcmp(z,"-eqp")==0 ){
       data.mode.autoEQP = AUTOEQP_on;
     }else if( cli_strcmp(z,"-eqpfull")==0 ){
@@ -12790,6 +12849,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       stdin_is_interactive = 1;
     }else if( cli_strcmp(z,"-batch")==0 ){
       /* already handled */
+    }else if( cli_strcmp(z,"-screenwidth")==0 ){
+      i++;      
     }else if( cli_strcmp(z,"-compat")==0 ){
       i++;      
     }else if( cli_strcmp(z,"-utf8")==0 ){
index a9f6d4c83dbeebbed0a80c9c34a90df80542a382..c79a32defec9f5511520956432852cbdc2053bb1 100644 (file)
@@ -132,15 +132,24 @@ do_test 1.50 {
   db format -style count {SELECT * FROM t1}
 } 2
 
-do_test 1.60 {
-  db format -style csv {SELECT * FROM t1}
+do_test 1.60a {
+  db format -style list -columnsep , -rowsep \r\n -text csv -blob tcl {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}
+do_test 1.60b {
+  db format -style csv -columnsep xyz -rowsep pqr -text sql -blob sql {SELECT * FROM t1}
+} "1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n"
+do_test 1.61a {
+  db format -style list -columnsep , -rowsep \r\n -text csv -title auto -blob tcl {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}
+do_test 1.61b {
+  db format -style csv -title auto -blob tcl {SELECT * FROM t1}
+} "a,b,c\r\n1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n"
+do_test 1.62a {
+  db format -style list -columnsep , -rowsep \r\n -text csv -title csv -blob tcl {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.62b {
+  db format -style csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1}
+} "\"a x y\",b,c\r\n1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n"
 
 do_test 1.70 {
   set result "\n[db format -style html {SELECT * FROM t1}]"
index 3f0e19e7bee2791c2075c0da99e3ffdd89372e94..1a502336c3dbde961660392f9237a6f80b1cd088 100644 (file)
@@ -426,7 +426,8 @@ do_test shell1-3.11.2 {
 do_test shell1-3.11.3 {
   # too many arguments
   catchcmd "test.db" ".import FOO BAR BAD"
-} {/1 .ERROR: extra argument: "BAD".*./}
+} {1 {line 1: .import FOO BAR BAD
+line 1:                 ^--- unknown argument}}
 
 # .indexes ?TABLE?       Show names of all indexes
 #                          If TABLE specified, only show indexes for tables
@@ -456,7 +457,7 @@ do_test shell1-3.12.3 {
 #                          tabs     Tab-separated values
 #                          tcl      TCL list elements
 do_test shell1-3.13.1 {
-  catchcmd "test.db" ".mode 20250101\n.mode"
+  catchcmd "test.db" ".mode batch\n.mode"
 } {0 {current output mode: list}}
 do_test shell1-3.13.2 {
   catchcmd "test.db" ".mode FOO"
@@ -510,7 +511,7 @@ do_test shell1-3.15.1 {
 .print x"
 } {0 x}
 do_test shell1-3.15.2 {
-  catchcmd "test.db" ".mode 20250101\n.output FOO
+  catchcmd "test.db" ".mode batch\n.output FOO
 .print x
 .output
 SELECT readfile('FOO');"
@@ -632,7 +633,7 @@ do_test shell1-3.22.4 {
 
 # .show                  Show the current values for various settings
 do_test shell1-3.23.1 {
-  set res [catchcmd "test.db" ".mode 20250101\n.show"]
+  set res [catchcmd "test.db" ".mode batch\n.show"]
   list [regexp {echo:} $res] \
        [regexp {explain:} $res] \
        [regexp {headers:} $res] \
@@ -668,7 +669,7 @@ do_test shell1-3.23b.4 {
 # Adverse interaction between .stats and .eqp
 #
 do_test shell1-3.23b.5 {
-  catchcmd "test.db" [string map {"\n    " "\n"} {.mode 20250101
+  catchcmd "test.db" [string map {"\n    " "\n"} {.mode batch
     CREATE TEMP TABLE t1(x);
     INSERT INTO t1 VALUES(1),(2);
     .stats on
@@ -753,7 +754,7 @@ do_test shell1-3.27.4 {
 
 do_test shell1-3.28.1 {
   catchcmd test.db \
-     ".mode 20250101\n.log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');"
+     ".mode batch\n.log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');"
 } "0 {(123) hello\n456}"
 
 do_test shell1-3-29.1 {
@@ -1263,7 +1264,7 @@ do_test shell1-7.1.7 {
 # information.
 #
 do_test shell1-8.1 {
-  catchcmd ":memory:" {.mode 20250101
+  catchcmd ":memory:" {.mode batch
     -- The pow2 table will hold all the necessary powers of two.
     CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT);
     WITH RECURSIVE c(x,v) AS (
index dcf2942e180ea16df8f30151ebc1de80bfe74c02..274f758e2fd709a6cd293c66399eb281e980307b 100644 (file)
@@ -75,7 +75,7 @@ do_test shell2-1.3 {
 # NB. whitespace is important
 do_test shell2-1.4.1 {
   forcedelete foo.db
-  catchcmd "foo.db" {.mode 20250101
+  catchcmd "foo.db" {.mode batch
 CREATE TABLE foo(a);
 INSERT INTO foo(a) VALUES(1);
 SELECT * FROM foo;}
@@ -98,7 +98,7 @@ SELECT * FROM foo;
 do_test shell2-1.4.3 {
   forcedelete foo.db
   catchcmd "foo.db" {
-.mode 20250101
+.mode batch
 .echo ON
 CREATE TABLE foo(a);
 INSERT INTO foo(a) VALUES(1);
@@ -114,7 +114,7 @@ SELECT * FROM foo;
 do_test shell2-1.4.4 {
   forcedelete foo.db
   catchcmd "foo.db" {
-.mode 20250101
+.mode batch
 .echo ON
 CREATE TABLE foo(a);
 .echo OFF
@@ -130,7 +130,7 @@ SELECT * FROM foo;}
 do_test shell2-1.4.5 {
   forcedelete foo.db
   catchcmdex "foo.db" {
-.mode 20250101
+.mode batch
 .echo ON
 CREATE TABLE foo1(a);
 INSERT INTO foo1(a) VALUES(1);
@@ -161,7 +161,7 @@ SELECT * FROM foo1; SELECT * FROM foo2;
 do_test shell2-1.4.6 {
   forcedelete foo.db
   catchcmdex "foo.db" {
-.mode 20250101
+.mode batch
 .echo ON
 .headers ON
 CREATE TABLE foo1(a);
@@ -217,7 +217,7 @@ do_test shell2-1.4.9 {
 do_test shell2-1.4.9 {
   forcedelete clone.db
   set res [catchcmd :memory: [string trim {
-.mode 20250101
+.mode batch
  CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT);
  INSERT INTO t VALUES (1),(2);
 .clone clone.db
@@ -232,7 +232,7 @@ ifcapable vtab {
 # See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280
 do_test shell2-1.4.10 {
  set res [catchcmd :memory: [string trim {
- .mode 20250101
+ .mode batch
  SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1);
  SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1);
  SELECT avg(value),min(value),max(value) FROM generate_series(
@@ -261,7 +261,7 @@ do_test shell2-1.4.10 {
 2}}
 do_test shell2-1.4.10b {
  set res [catchcmd :memory: [string trim {
- .mode 20251116
+ .mode tty
 .print
  SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1);
  SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1);
@@ -340,7 +340,7 @@ do_test shell2-1.4.11 {
 # Bug from forum post 7cbe081746dd3803
 # Keywords as column names were producing an error message.
 do_test shell2-1.4.12 {
-  set res [catchcmd :memory: [string trim {.mode 20250101
+  set res [catchcmd :memory: [string trim {.mode batch
  CREATE TABLE "group"("order" text);
  INSERT INTO "group" VALUES ('ABC');
 .sha3sum}]]
index de64c3198ad1df388da3a351ac5094e140b434bd..2c8181579b73573bf8640df87c2cbd6387773457 100644 (file)
@@ -40,7 +40,8 @@ do_test shell5-1.1.2 {
 do_test shell5-1.1.3 {
   # too many arguments
   catchcmd "test.db" ".import FOO BAR BAD"
-} {/1 .ERROR: extra argument.*/}
+} {1 {line 1: .import FOO BAR BAD
+line 1:                 ^--- unknown argument}}
 
 # .separator STRING      Change separator used by output mode and .import
 do_test shell5-1.2.1 {