]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Improvements to the .import command of the CLI.
authordrh <>
Sun, 28 Dec 2025 14:01:52 +0000 (14:01 +0000)
committerdrh <>
Sun, 28 Dec 2025 14:01:52 +0000 (14:01 +0000)
FossilOrigin-Name: 436ed7937bcd3b5781539d883ff2957b81d74abccb75f65a2ffb7446a5944522

manifest
manifest.uuid
src/shell.c.in
test/shell2.test

index fe92ac26c2bce0cfafa989e5241c6af352e41ce5..2748076d94b735cca70545515dc930ac23f46bbe 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Change\sa\ssingle\sinteger\svariable\sto\s64-bit\sin\sthe\stokenizer\sto\savoid\nany\spossibility\sof\san\sinteger\soverflow.
-D 2025-12-27T19:57:59.356
+C Improvements\sto\sthe\s.import\scommand\sof\sthe\sCLI.
+D 2025-12-28T14:01:52.978
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -737,7 +737,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 47aa7fdc9ec4c19b103ac5e79d7887d30119b5675309facf5eed1118391c868b
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c 85852256d860f3ba5be4a9edc1238e68dbea082a0167f31b7345c821ae45775d
-F src/shell.c.in 8aa6b2bc490271bcbb39b650b120d28976c2de0c0ae80f356a64b2971ba85023
+F src/shell.c.in b30c49e8b58c51ee12034b14960a9097eec7fc2053231a31fe6271aa7e6c319f
 F src/sqlite.h.in b6599377f02ef9d545a8da48959213928b63291ad83ff65e5f3a72bf4fec595d
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 5d5330f5f8461f5ce74960436ddcfa53ecd09c2b8b23901e22ae38aec3243998
@@ -1614,7 +1614,7 @@ F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8e
 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
 F test/shell1.test da4901c88f3a1db2acd39381ed260a0fe3c706bbc170b695fcbe6cb3eb6f7ab0
-F test/shell2.test f563798603950345a98f2a630d4ea746fc0063919a3dac917475d4e2166acbd7
+F test/shell2.test dc541d2681503e55466a24d35a4cbf8ca5b90b8fcdef37fc4db07373a67d31d3
 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e
 F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db
 F test/shell5.test 145a9474bab6d80b6ee452b83135507b25275d53b0936aab76520f007ac4d9dc
@@ -2188,8 +2188,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c f40bccf0236f8bcc34b299781b7d34cb269ace23afe5c1b8a9d966e2fa1ce9e5
-P f41851587328f8b434e66987765324b7bf5746ecdf3a9f2869000276e0f79401
-R 0318fcb6944164fd4ebe079bcdee2867
+P 69cf692a24d714305b2e4c8c4c40f70dc6510c26b8db7e5249f32a53e44c7e5c
+R 3af252d77a85ba7bba900e37cdf35e34
 U drh
-Z cb66f3935c2321391ca778a118ec4b1c
+Z 298454a73cfd919f12c3b1fa1a6bf676
 # Remove this line to create a well-formed Fossil manifest.
index 71e418aa8199a4d06de3027ea0e8c286c25fa6be..bf21efe34e8bed6702507b8c4bf051dda522c313 100644 (file)
@@ -1 +1 @@
-69cf692a24d714305b2e4c8c4c40f70dc6510c26b8db7e5249f32a53e44c7e5c
+436ed7937bcd3b5781539d883ff2957b81d74abccb75f65a2ffb7446a5944522
index ce6026330a5ec1827c6089df1c67558bfe9f12dd..9b74c0166bd3a9610d3ab68d12e28bbcac637ad1 100644 (file)
@@ -4930,8 +4930,8 @@ static void import_append_char(ImportCtx *p, int c){
 **   +  Input comes from p->in.
 **   +  Store results in p->z of length p->n.  Space to hold p->z comes
 **      from sqlite3_malloc64().
-**   +  Use p->cSep as the column separator.  The default is ",".
-**   +  Use p->rSep as the row separator.  The default is "\n".
+**   +  Use p->cColSep as the column separator.  The default is ",".
+**   +  Use p->cRowSep as the row separator.  The default is "\n".
 **   +  Keep track of the line number in p->nLine.
 **   +  Store the character that terminates the field in p->cTerm.  Store
 **      EOF on end-of-file.
@@ -5020,8 +5020,8 @@ static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
 **   +  Input comes from p->in.
 **   +  Store results in p->z of length p->n.  Space to hold p->z comes
 **      from sqlite3_malloc64().
-**   +  Use p->cSep as the column separator.  The default is "\x1F".
-**   +  Use p->rSep as the row separator.  The default is "\x1E".
+**   +  Use p->cColSep as the column separator.  The default is "\x1F".
+**   +  Use p->cRowSep as the row separator.  The default is "\x1E".
 **   +  Keep track of the row number in p->nLine.
 **   +  Store the character that terminates the field in p->cTerm.  Store
 **      EOF on end-of-file.
@@ -7346,13 +7346,23 @@ static int pickStr(const char *zArg, char **pzErr, ...){
 ** 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.
+** The content of FILE is interpreted using RFC-4180 ("CSV") quoting
+** rules unless the current mode is "ascii" or "tabs" or unless one
+** the --ascii option is used.
+**
+** The column and row separators must be single ASCII characters.  If
+** multiple characters or a Unicode character are specified for the
+** separators, then only the first byte of the separator is used.  Except,
+** if the row separator is \n and the mode is not --ascii, then \r\n is
+** understood as a row separator too.
 **
 ** Options:
-**   --ascii         Use \037 and \036 as column and row separators on input
+**   --ascii         Do not use RFC-4180 quoting.  Use \037 and \036
+**                   as column and row separators on input, unless other
+**                   delimiters are specified using --colsep and/or --rowsep
+**   --colsep CHAR   Use CHAR as the column separator.
 **   --csv           Input is standard RFC-4180 CSV.
+**   --rowsep CHAR   Use CHAR as the row separator.
 **   --schema S      When creating TABLE, put it in schema S
 **   --skip N        Ignore the first N rows of input
 **   -v              Verbose mode
@@ -7368,13 +7378,11 @@ static int dotCmdImport(ShellState *p){
   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 */
 
@@ -7404,15 +7412,27 @@ static int dotCmdImport(ShellState *p){
     }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];
+      if( sCtx.cColSep==0 ) sCtx.cColSep = SEP_Unit[0];
+      if( sCtx.cRowSep==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';
+      if( sCtx.cColSep==0 ) sCtx.cColSep = ',';
+      if( sCtx.cRowSep==0 ) sCtx.cRowSep = '\n';
       xRead = csv_read_one_field;
-      useOutputMode = 0;
+    }else if( cli_strcmp(z,"-colsep")==0 ){
+      if( i==nArg-1 ){
+        dotCmdError(p, i, "missing argument", 0);
+        return 1;
+      }
+      i++;
+      sCtx.cColSep = azArg[i][0];
+    }else if( cli_strcmp(z,"-rowsep")==0 ){
+      if( i==nArg-1 ){
+        dotCmdError(p, i, "missing argument", 0);
+        return 1;
+      }
+      i++;
+      sCtx.cRowSep = azArg[i][0];
     }else{
       dotCmdError(p, i, "unknown option", 0);
       return 1;
@@ -7425,45 +7445,30 @@ static int dotCmdImport(ShellState *p){
   }
   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( sCtx.cColSep==0 ){
+    if( p->mode.spec.zColumnSep && p->mode.spec.zColumnSep[0]!=0 ){
+      sCtx.cColSep = p->mode.spec.zColumnSep[0];
+    }else{
+      sCtx.cColSep = ',';
     }
-    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;
+  }
+  if( (sCtx.cColSep & 0x80)!=0 ){
+    eputz("Error: .import column separator must be ASCII\n");
+    return 1;
+  }
+  if( sCtx.cRowSep==0 ){
+    if( p->mode.spec.zRowSep && p->mode.spec.zRowSep[0]!=0 ){
+      sCtx.cRowSep = p->mode.spec.zRowSep[0];
+    }else{
+      sCtx.cRowSep = '\n';
     }
-    sCtx.cColSep = (u8)p->mode.spec.zColumnSep[0];
-    sCtx.cRowSep = (u8)p->mode.spec.zRowSep[0];
+  }
+  if( sCtx.cRowSep=='\r' && xRead!=ascii_read_one_field ){
+    sCtx.cRowSep = '\n';
+  }
+  if( (sCtx.cRowSep & 0x80)!=0 ){
+    eputz("Error: .import row separator must be ASCII\n");
+    return 1;
   }
   sCtx.zFile = zFile;
   sCtx.nLine = 1;
@@ -7484,7 +7489,7 @@ static int dotCmdImport(ShellState *p){
     cli_printf(stderr,"Error: cannot open \"%s\"\n", zFile);
     return 1;
   }
-  if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
+  if( eVerbose>=1 ){
     char zSep[2];
     zSep[1] = 0;
     zSep[0] = sCtx.cColSep;
index 3f6004176999ac4525158c2d4ede1e195a3b0426..7141c4d497313292f3d0253ba485044a6335a86e 100644 (file)
@@ -330,11 +330,11 @@ do_test shell2-1.4.11 {
   close $df
   set res [catchcmd :memory: [string trim {
  CREATE TABLE t(line text);
-.mode ascii
-.separator "\377" "\n"
+.mode ascii -colsep "\377" -rowsep "\n"
 .import dummy.csv t
  SELECT count(*) FROM t;}]]
-} {0 1}
+} {1 {0
+Error: .import column separator must be ASCII}}
 } ;# ifcapable vtab
 
 # Bug from forum post 7cbe081746dd3803