]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add new ASCII mode to the shell capable of importing and exporting using the official...
authormistachkin <mistachkin@noemail.net>
Sat, 19 Jul 2014 20:15:16 +0000 (20:15 +0000)
committermistachkin <mistachkin@noemail.net>
Sat, 19 Jul 2014 20:15:16 +0000 (20:15 +0000)
FossilOrigin-Name: 7fe601ead0d0ae26cb09d0dbc7d6367785376567

manifest
manifest.uuid
src/shell.c
test/shell1.test
test/shell5.test

index 9c16ebe0d53f9afdfb6bf13f89f8349322c95be8..2bcc380e41418260eb6050653218cd1b97b28b15 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Update\sthe\ssqlite3_stmt_busy()\sfunction\sso\sthat\sit\scorrectly\sreturns\strue\sfor\s"ROLLBACK"\sstatements\sthat\shave\sbeen\sstepped\sbut\snot\syet\sreset.
-D 2014-07-19T17:57:10.785
+C Add\snew\sASCII\smode\sto\sthe\sshell\scapable\sof\simporting\sand\sexporting\susing\sthe\sofficial\sunit\sand\srecord\sseparators\s(i.e.\s0x1F\sand\s0x1E,\srespectively).
+D 2014-07-19T20:15:16.631
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
 F Makefile.in 5eb79e334a5de69c87740edd56af6527dd219308
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -223,7 +223,7 @@ F src/random.c d10c1f85b6709ca97278428fd5db5bbb9c74eece
 F src/resolve.c 5fc110baeacf120a73fe34e103f052632ff11a02
 F src/rowset.c a9c9aae3234b44a6d7c6f5a3cadf90dce1e627be
 F src/select.c 6762c62e11b504aa014edceab8886495165e3a77
-F src/shell.c 566aee8213372a2e81ba0eb34e9759f7b2574009
+F src/shell.c f1524cdcf12af7eeff98c7846518ce5424d38d60
 F src/sqlite.h.in fd8e3a36b0aded082dc93a4b89c1e85324b4cf75
 F src/sqlite3.rc 11094cc6a157a028b301a9f06b3d03089ea37c3e
 F src/sqlite3ext.h 886f5a34de171002ad46fae8c36a7d8051c190fc
@@ -818,11 +818,11 @@ F test/shared9.test 5f2a8f79b4d6c7d107a01ffa1ed05ae7e6333e21
 F test/sharedA.test 0cdf1a76dfa00e6beee66af5b534b1e8df2720f5
 F test/shared_err.test 0079c05c97d88cfa03989b7c20a8b266983087aa
 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
-F test/shell1.test fb080d67c81e8a80a79ea04b36f127209b5bd112
+F test/shell1.test 4e4f8e6be18384f0bde93acc01947d7217e81774
 F test/shell2.test c57da3a381c099b02c813ba156298d5c2f5c93a3
 F test/shell3.test 5e8545ec72c4413a0e8d4c6be56496e3c257ca29
 F test/shell4.test 8a9c08976291e6c6c808b4d718f4a8b299f339f5
-F test/shell5.test ef0c52952a4a96dc1d9ec3b1fa81ec897ca48154
+F test/shell5.test 3c9264ddf171d778d7d93bda5eea0bafe8a65069
 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
 F test/shrink.test 8c70f62b6e8eb4d54533de6d65bd06b1b9a17868
@@ -1182,7 +1182,10 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 574cc8eb1448cff67390f2e16cc9b7c3ddd4658b
-R 3e17c7c287ebc0f7c8d4dc519939fd8f
-U dan
-Z 2190a4b1d8dc0a3facc078eb683578eb
+P 61cee3c0678f5abd9131a29ab946a5e71f55643e
+R 2620c0e44150895c6e6dda7db94cda7b
+T *branch * asciiMode
+T *sym-asciiMode *
+T -sym-trunk *
+U mistachkin
+Z 0122ab3028becc4213d4d02208439f07
index 5e45c34d1e6bea1ef669b7a5590a7052d0c3a6ab..7ba436b0177cd57ae0aad331ef4bc593605cdbbc 100644 (file)
@@ -1 +1 @@
-61cee3c0678f5abd9131a29ab946a5e71f55643e
\ No newline at end of file
+7fe601ead0d0ae26cb09d0dbc7d6367785376567
\ No newline at end of file
index 97167dc877c13310667294c266aaff0d2ac24f86..bae22d56a692ae5af3060f38d27ac83141da9769 100644 (file)
@@ -457,7 +457,8 @@ struct callback_data {
   int writableSchema;    /* True if PRAGMA writable_schema=ON */
   int showHeader;        /* True to show column names in List or Column mode */
   char *zDestTable;      /* Name of destination table when MODE_Insert */
-  char separator[20];    /* Separator character for MODE_List */
+  char colSeparator[20]; /* Column separator character for several modes */
+  char rowSeparator[20]; /* Row separator character for MODE_Ascii */
   int colWidth[100];     /* Requested width of each column when in column mode*/
   int actualWidth[100];  /* Actual width of each column */
   char nullvalue[20];    /* The text to print when a NULL comes back from
@@ -488,6 +489,7 @@ struct callback_data {
 #define MODE_Tcl      6  /* Generate ANSI-C or TCL quoted elements */
 #define MODE_Csv      7  /* Quote strings, numbers are plain */
 #define MODE_Explain  8  /* Like MODE_Column, but do not truncate data */
+#define MODE_Ascii    9  /* Use ASCII unit and record separators (0x1F/0x1E) */
 
 static const char *modeDescr[] = {
   "line",
@@ -499,8 +501,16 @@ static const char *modeDescr[] = {
   "tcl",
   "csv",
   "explain",
+  "ascii",
 };
 
+/*
+** These are the column/row separators used by the ASCII mode.
+*/
+#define SEP_Line      "\n"
+#define SEP_Column    "\x1F"
+#define SEP_Row       "\x1E"
+
 /*
 ** Number of elements in an array
 */
@@ -667,11 +677,11 @@ static void output_csv(struct callback_data *p, const char *z, int bSep){
     fprintf(out,"%s",p->nullvalue);
   }else{
     int i;
-    int nSep = strlen30(p->separator);
+    int nSep = strlen30(p->colSeparator);
     for(i=0; z[i]; i++){
       if( needCsvQuote[((unsigned char*)z)[i]] 
-         || (z[i]==p->separator[0] && 
-             (nSep==1 || memcmp(z, p->separator, nSep)==0)) ){
+         || (z[i]==p->colSeparator[0] && 
+             (nSep==1 || memcmp(z, p->colSeparator, nSep)==0)) ){
         i = 0;
         break;
       }
@@ -688,7 +698,7 @@ static void output_csv(struct callback_data *p, const char *z, int bSep){
     }
   }
   if( bSep ){
-    fprintf(p->out, "%s", p->separator);
+    fprintf(p->out, "%s", p->colSeparator);
   }
 }
 
@@ -720,10 +730,10 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
         int len = strlen30(azCol[i] ? azCol[i] : "");
         if( len>w ) w = len;
       }
-      if( p->cnt++>0 ) fprintf(p->out,"\n");
+      if( p->cnt++>0 ) fprintf(p->out, "%s", p->rowSeparator);
       for(i=0; i<nArg; i++){
-        fprintf(p->out,"%*s = %s\n", w, azCol[i],
-                azArg[i] ? azArg[i] : p->nullvalue);
+        fprintf(p->out,"%*s = %s%s", w, azCol[i],
+                azArg[i] ? azArg[i] : p->nullvalue, p->rowSeparator);
       }
       break;
     }
@@ -748,9 +758,11 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
           }
           if( p->showHeader ){
             if( w<0 ){
-              fprintf(p->out,"%*.*s%s",-w,-w,azCol[i], i==nArg-1 ? "\n": "  ");
+              fprintf(p->out,"%*.*s%s",-w,-w,azCol[i],
+                      i==nArg-1 ? p->rowSeparator : "  ");
             }else{
-              fprintf(p->out,"%-*.*s%s",w,w,azCol[i], i==nArg-1 ? "\n": "  ");
+              fprintf(p->out,"%-*.*s%s",w,w,azCol[i],
+                      i==nArg-1 ? p->rowSeparator : "  ");
             }
           }
         }
@@ -765,7 +777,7 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
             }
             fprintf(p->out,"%-*.*s%s",w,w,"-----------------------------------"
                    "----------------------------------------------------------",
-                    i==nArg-1 ? "\n": "  ");
+                    i==nArg-1 ? p->rowSeparator : "  ");
           }
         }
       }
@@ -788,10 +800,12 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
         }
         if( w<0 ){
           fprintf(p->out,"%*.*s%s",-w,-w,
-              azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": "  ");
+              azArg[i] ? azArg[i] : p->nullvalue,
+              i==nArg-1 ? p->rowSeparator : "  ");
         }else{
           fprintf(p->out,"%-*.*s%s",w,w,
-              azArg[i] ? azArg[i] : p->nullvalue, i==nArg-1 ? "\n": "  ");
+              azArg[i] ? azArg[i] : p->nullvalue,
+              i==nArg-1 ? p->rowSeparator : "  ");
         }
       }
       break;
@@ -800,7 +814,8 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
     case MODE_List: {
       if( p->cnt++==0 && p->showHeader ){
         for(i=0; i<nArg; i++){
-          fprintf(p->out,"%s%s",azCol[i], i==nArg-1 ? "\n" : p->separator);
+          fprintf(p->out,"%s%s",azCol[i],
+                  i==nArg-1 ? p->rowSeparator : p->colSeparator);
         }
       }
       if( azArg==0 ) break;
@@ -809,11 +824,11 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
         if( z==0 ) z = p->nullvalue;
         fprintf(p->out, "%s", z);
         if( i<nArg-1 ){
-          fprintf(p->out, "%s", p->separator);
+          fprintf(p->out, "%s", p->colSeparator);
         }else if( p->mode==MODE_Semi ){
-          fprintf(p->out, ";\n");
+          fprintf(p->out, ";%s", p->rowSeparator);
         }else{
-          fprintf(p->out, "\n");
+          fprintf(p->out, "%s", p->rowSeparator);
         }
       }
       break;
@@ -842,16 +857,16 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
       if( p->cnt++==0 && p->showHeader ){
         for(i=0; i<nArg; i++){
           output_c_string(p->out,azCol[i] ? azCol[i] : "");
-          if(i<nArg-1) fprintf(p->out, "%s", p->separator);
+          if(i<nArg-1) fprintf(p->out, "%s", p->colSeparator);
         }
-        fprintf(p->out,"\n");
+        fprintf(p->out, "%s", p->rowSeparator);
       }
       if( azArg==0 ) break;
       for(i=0; i<nArg; i++){
         output_c_string(p->out, azArg[i] ? azArg[i] : p->nullvalue);
-        if(i<nArg-1) fprintf(p->out, "%s", p->separator);
+        if(i<nArg-1) fprintf(p->out, "%s", p->colSeparator);
       }
-      fprintf(p->out,"\n");
+      fprintf(p->out, "%s", p->rowSeparator);
       break;
     }
     case MODE_Csv: {
@@ -859,13 +874,13 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
         for(i=0; i<nArg; i++){
           output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1);
         }
-        fprintf(p->out,"\n");
+        fprintf(p->out, "%s", p->rowSeparator);
       }
       if( azArg==0 ) break;
       for(i=0; i<nArg; i++){
         output_csv(p, azArg[i], i<nArg-1);
       }
-      fprintf(p->out,"\n");
+      fprintf(p->out, "%s", p->rowSeparator);
       break;
     }
     case MODE_Insert: {
@@ -897,6 +912,22 @@ static int shell_callback(void *pArg, int nArg, char **azArg, char **azCol, int
       fprintf(p->out,");\n");
       break;
     }
+    case MODE_Ascii: {
+      if( p->cnt++==0 && p->showHeader ){
+        for(i=0; i<nArg; i++){
+          if( i>0 ) fprintf(p->out, "%s", p->colSeparator);
+          fprintf(p->out,"%s",azCol[i] ? azCol[i] : "");
+        }
+        fprintf(p->out, "%s", p->rowSeparator);
+      }
+      if( azArg==0 ) break;
+      for(i=0; i<nArg; i++){
+        if( i>0 ) fprintf(p->out, "%s", p->colSeparator);
+        fprintf(p->out,"%s",azArg[i] ? azArg[i] : p->nullvalue);
+      }
+      fprintf(p->out, "%s", p->rowSeparator);
+      break;
+    }
   }
   return 0;
 }
@@ -1574,6 +1605,7 @@ static char zHelp[] =
   ".backup ?DB? FILE      Backup DB (default \"main\") to FILE\n"
   ".bail on|off           Stop after hitting an error.  Default OFF\n"
   ".clone NEWDB           Clone data into NEWDB from the existing database\n"
+  ".colseparator STRING   This is an alias for .separator\n"
   ".databases             List names and files of attached databases\n"
   ".dump ?TABLE? ...      Dump the database in an SQL text format\n"
   "                         If TABLE specified, only dump tables matching\n"
@@ -1598,6 +1630,7 @@ static char zHelp[] =
 #endif
   ".log FILE|off          Turn logging on or off.  FILE can be stderr/stdout\n"
   ".mode MODE ?TABLE?     Set output mode where MODE is one of:\n"
+  "                         ascii    Columns/rows delimited with 0x1F and 0x1E\n"
   "                         csv      Comma-separated values\n"
   "                         column   Left-aligned columns.  (See .width)\n"
   "                         html     HTML <table> code\n"
@@ -1615,11 +1648,12 @@ static char zHelp[] =
   ".quit                  Exit this program\n"
   ".read FILENAME         Execute SQL in FILENAME\n"
   ".restore ?DB? FILE     Restore content of DB (default \"main\") from FILE\n"
+  ".rowseparator STRING   Change row separator for output mode and .import\n"
   ".save FILE             Write in-memory database into FILE\n"
   ".schema ?TABLE?        Show the CREATE statements\n"
   "                         If TABLE specified, only show tables matching\n"
   "                         LIKE pattern TABLE.\n"
-  ".separator STRING      Change separator used by output mode and .import\n"
+  ".separator STRING      Change column separator for output mode and .import\n"
   ".shell CMD ARGS...     Run CMD ARGS... in a system shell\n"
   ".show                  Show the current values for various settings\n"
   ".stats on|off          Turn stats on or off\n"
@@ -1832,10 +1866,10 @@ static void test_breakpoint(void){
 }
 
 /*
-** An object used to read a CSV file
+** An object used to read a CSV and other files for import.
 */
-typedef struct CSVReader CSVReader;
-struct CSVReader {
+typedef struct ImportCtx ImportCtx;
+struct ImportCtx {
   const char *zFile;  /* Name of the input file */
   FILE *in;           /* Read the CSV text from this input stream */
   char *z;            /* Accumulated text for a field */
@@ -1843,11 +1877,12 @@ struct CSVReader {
   int nAlloc;         /* Space allocated for z[] */
   int nLine;          /* Current line number */
   int cTerm;          /* Character that terminated the most recent field */
-  int cSeparator;     /* The separator character.  (Usually ",") */
+  int cColSep;        /* The column separator character.  (Usually ",") */
+  int cRowSep;        /* The row separator character.  (Usually "\n") */
 };
 
 /* Append a single byte to z[] */
-static void csv_append_char(CSVReader *p, int c){
+static void import_append_char(ImportCtx *p, int c){
   if( p->n+1>=p->nAlloc ){
     p->nAlloc += p->nAlloc + 100;
     p->z = sqlite3_realloc(p->z, p->nAlloc);
@@ -1865,15 +1900,17 @@ static void csv_append_char(CSVReader *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_malloc().
-**   +  Use p->cSep as the separator.  The default is ",".
+**   +  Use p->cSep as the column separator.  The default is ",".
+**   +  Use p->rSep 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.
 **   +  Report syntax errors on stderr
 */
-static char *csv_read_one_field(CSVReader *p){
-  int c, pc, ppc;
-  int cSep = p->cSeparator;
+static char *csv_read_one_field(ImportCtx *p){
+  int c;
+  int cSep = p->cColSep;
+  int rSep = p->cRowSep;
   p->n = 0;
   c = fgetc(p->in);
   if( c==EOF || seenInterrupt ){
@@ -1881,12 +1918,13 @@ static char *csv_read_one_field(CSVReader *p){
     return 0;
   }
   if( c=='"' ){
+    int pc, ppc;
     int startLine = p->nLine;
     int cQuote = c;
     pc = ppc = 0;
     while( 1 ){
       c = fgetc(p->in);
-      if( c=='\n' ) p->nLine++;
+      if( c==rSep ) p->nLine++;
       if( c==cQuote ){
         if( pc==cQuote ){
           pc = 0;
@@ -1894,8 +1932,8 @@ static char *csv_read_one_field(CSVReader *p){
         }
       }
       if( (c==cSep && pc==cQuote)
-       || (c=='\n' && pc==cQuote)
-       || (c=='\n' && pc=='\r' && ppc==cQuote)
+       || (c==rSep && pc==cQuote)
+       || (c==rSep && pc=='\r' && ppc==cQuote)
        || (c==EOF && pc==cQuote)
       ){
         do{ p->n--; }while( p->z[p->n]!=cQuote );
@@ -1909,19 +1947,19 @@ static char *csv_read_one_field(CSVReader *p){
       if( c==EOF ){
         fprintf(stderr, "%s:%d: unterminated %c-quoted field\n",
                 p->zFile, startLine, cQuote);
-        p->cTerm = EOF;
+        p->cTerm = c;
         break;
       }
-      csv_append_char(p, c);
+      import_append_char(p, c);
       ppc = pc;
       pc = c;
     }
   }else{
-    while( c!=EOF && c!=cSep && c!='\n' ){
-      csv_append_char(p, c);
+    while( c!=EOF && c!=cSep && c!=rSep ){
+      import_append_char(p, c);
       c = fgetc(p->in);
     }
-    if( c=='\n' ){
+    if( c==rSep ){
       p->nLine++;
       if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
     }
@@ -1931,6 +1969,40 @@ static char *csv_read_one_field(CSVReader *p){
   return p->z;
 }
 
+/* Read a single field of ASCII delimited text.
+**
+**   +  Input comes from p->in.
+**   +  Store results in p->z of length p->n.  Space to hold p->z comes
+**      from sqlite3_malloc().
+**   +  Use p->cSep as the column separator.  The default is "\x1F".
+**   +  Use p->rSep 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.
+**   +  Report syntax errors on stderr
+*/
+static char *ascii_read_one_field(ImportCtx *p){
+  int c;
+  int cSep = p->cColSep;
+  int rSep = p->cRowSep;
+  p->n = 0;
+  c = fgetc(p->in);
+  if( c==EOF || seenInterrupt ){
+    p->cTerm = EOF;
+    return 0;
+  }
+  while( c!=EOF && c!=cSep && c!=rSep ){
+    import_append_char(p, c);
+    c = fgetc(p->in);
+  }
+  if( c==rSep ){
+    p->nLine++;
+  }
+  p->cTerm = c;
+  if( p->z ) p->z[p->n] = 0;
+  return p->z;
+}
+
 /*
 ** Try to transfer data for table zTable.  If an error is seen while
 ** moving forward, try to go backwards.  The backwards movement won't
@@ -2485,9 +2557,10 @@ static int do_meta_command(char *zLine, struct callback_data *p){
     int 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 p->separator[] */
+    int nSep;                   /* Number of bytes in p->colSeparator[] */
     char *zSql;                 /* An SQL statement */
-    CSVReader sCsv;             /* Reader context */
+    ImportCtx sCtx;             /* Reader context */
+    char *(*xRead)(ImportCtx*); /* Procecure to read one value */
     int (*xCloser)(FILE*);      /* Procedure to close th3 connection */
 
     if( nArg!=3 ){
@@ -2497,55 +2570,71 @@ static int do_meta_command(char *zLine, struct callback_data *p){
     zFile = azArg[1];
     zTable = azArg[2];
     seenInterrupt = 0;
-    memset(&sCsv, 0, sizeof(sCsv));
+    memset(&sCtx, 0, sizeof(sCtx));
     open_db(p, 0);
-    nSep = strlen30(p->separator);
+    nSep = strlen30(p->colSeparator);
+    if( nSep==0 ){
+      fprintf(stderr, "Error: non-null column separator required for import\n");
+      return 1;
+    }
+    if( nSep>1 ){
+      fprintf(stderr, "Error: multi-character column separators not allowed"
+                      " for import\n");
+      return 1;
+    }
+    nSep = strlen30(p->rowSeparator);
     if( nSep==0 ){
-      fprintf(stderr, "Error: non-null separator required for import\n");
+      fprintf(stderr, "Error: non-null row separator required for import\n");
       return 1;
     }
     if( nSep>1 ){
-      fprintf(stderr, "Error: multi-character separators not allowed"
+      fprintf(stderr, "Error: multi-character row separators not allowed"
                       " for import\n");
       return 1;
     }
-    sCsv.zFile = zFile;
-    sCsv.nLine = 1;
-    if( sCsv.zFile[0]=='|' ){
-      sCsv.in = popen(sCsv.zFile+1, "r");
-      sCsv.zFile = "<pipe>";
+    sCtx.zFile = zFile;
+    sCtx.nLine = 1;
+    if( sCtx.zFile[0]=='|' ){
+      sCtx.in = popen(sCtx.zFile+1, "r");
+      sCtx.zFile = "<pipe>";
       xCloser = pclose;
     }else{
-      sCsv.in = fopen(sCsv.zFile, "rb");
+      sCtx.in = fopen(sCtx.zFile, "rb");
       xCloser = fclose;
     }
-    if( sCsv.in==0 ){
+    if( p->mode==MODE_Ascii ){
+      xRead = ascii_read_one_field;
+    }else{
+      xRead = csv_read_one_field;
+    }
+    if( sCtx.in==0 ){
       fprintf(stderr, "Error: cannot open \"%s\"\n", zFile);
       return 1;
     }
-    sCsv.cSeparator = p->separator[0];
+    sCtx.cColSep = p->colSeparator[0];
+    sCtx.cRowSep = p->rowSeparator[0];
     zSql = sqlite3_mprintf("SELECT * FROM %s", zTable);
     if( zSql==0 ){
       fprintf(stderr, "Error: out of memory\n");
-      xCloser(sCsv.in);
+      xCloser(sCtx.in);
       return 1;
     }
     nByte = strlen30(zSql);
     rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
-    csv_append_char(&sCsv, 0);    /* To ensure sCsv.z is allocated */
+    import_append_char(&sCtx, 0);    /* To ensure sCtx.z is allocated */
     if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(db))==0 ){
       char *zCreate = sqlite3_mprintf("CREATE TABLE %s", zTable);
       char cSep = '(';
-      while( csv_read_one_field(&sCsv) ){
-        zCreate = sqlite3_mprintf("%z%c\n  \"%s\" TEXT", zCreate, cSep, sCsv.z);
+      while( xRead(&sCtx) ){
+        zCreate = sqlite3_mprintf("%z%c\n  \"%s\" TEXT", zCreate, cSep, sCtx.z);
         cSep = ',';
-        if( sCsv.cTerm!=sCsv.cSeparator ) break;
+        if( sCtx.cTerm!=sCtx.cColSep ) break;
       }
       if( cSep=='(' ){
         sqlite3_free(zCreate);
-        sqlite3_free(sCsv.z);
-        xCloser(sCsv.in);
-        fprintf(stderr,"%s: empty file\n", sCsv.zFile);
+        sqlite3_free(sCtx.z);
+        xCloser(sCtx.in);
+        fprintf(stderr,"%s: empty file\n", sCtx.zFile);
         return 1;
       }
       zCreate = sqlite3_mprintf("%z\n)", zCreate);
@@ -2554,8 +2643,8 @@ static int do_meta_command(char *zLine, struct callback_data *p){
       if( rc ){
         fprintf(stderr, "CREATE TABLE %s(...) failed: %s\n", zTable,
                 sqlite3_errmsg(db));
-        sqlite3_free(sCsv.z);
-        xCloser(sCsv.in);
+        sqlite3_free(sCtx.z);
+        xCloser(sCtx.in);
         return 1;
       }
       rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
@@ -2564,7 +2653,7 @@ static int do_meta_command(char *zLine, struct callback_data *p){
     if( rc ){
       if (pStmt) sqlite3_finalize(pStmt);
       fprintf(stderr,"Error: %s\n", sqlite3_errmsg(db));
-      xCloser(sCsv.in);
+      xCloser(sCtx.in);
       return 1;
     }
     nCol = sqlite3_column_count(pStmt);
@@ -2574,7 +2663,7 @@ static int do_meta_command(char *zLine, struct callback_data *p){
     zSql = sqlite3_malloc( nByte*2 + 20 + nCol*2 );
     if( zSql==0 ){
       fprintf(stderr, "Error: out of memory\n");
-      xCloser(sCsv.in);
+      xCloser(sCtx.in);
       return 1;
     }
     sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\" VALUES(?", zTable);
@@ -2590,46 +2679,56 @@ static int do_meta_command(char *zLine, struct callback_data *p){
     if( rc ){
       fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
       if (pStmt) sqlite3_finalize(pStmt);
-      xCloser(sCsv.in);
+      xCloser(sCtx.in);
       return 1;
     }
     needCommit = sqlite3_get_autocommit(db);
     if( needCommit ) sqlite3_exec(db, "BEGIN", 0, 0, 0);
     do{
-      int startLine = sCsv.nLine;
+      int startLine = sCtx.nLine;
       for(i=0; i<nCol; i++){
-        char *z = csv_read_one_field(&sCsv);
+        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==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
         sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
-        if( i<nCol-1 && sCsv.cTerm!=sCsv.cSeparator ){
+        if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
           fprintf(stderr, "%s:%d: expected %d columns but found %d - "
                           "filling the rest with NULL\n",
-                          sCsv.zFile, startLine, nCol, i+1);
+                          sCtx.zFile, startLine, nCol, i+1);
           i++;
           while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
         }
       }
-      if( sCsv.cTerm==sCsv.cSeparator ){
+      if( sCtx.cTerm==sCtx.cColSep ){
         do{
-          csv_read_one_field(&sCsv);
+          xRead(&sCtx);
           i++;
-        }while( sCsv.cTerm==sCsv.cSeparator );
+        }while( sCtx.cTerm==sCtx.cColSep );
         fprintf(stderr, "%s:%d: expected %d columns but found %d - "
                         "extras ignored\n",
-                        sCsv.zFile, startLine, nCol, i);
+                        sCtx.zFile, startLine, nCol, i);
       }
       if( i>=nCol ){
         sqlite3_step(pStmt);
         rc = sqlite3_reset(pStmt);
         if( rc!=SQLITE_OK ){
-          fprintf(stderr, "%s:%d: INSERT failed: %s\n", sCsv.zFile, startLine,
+          fprintf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile, startLine,
                   sqlite3_errmsg(db));
         }
       }
-    }while( sCsv.cTerm!=EOF );
+    }while( sCtx.cTerm!=EOF );
 
-    xCloser(sCsv.in);
-    sqlite3_free(sCsv.z);
+    xCloser(sCtx.in);
+    sqlite3_free(sCtx.z);
     sqlite3_finalize(pStmt);
     if( needCommit ) sqlite3_exec(db, "COMMIT", 0, 0, 0);
   }else
@@ -2747,19 +2846,26 @@ static int do_meta_command(char *zLine, struct callback_data *p){
       p->mode = MODE_Html;
     }else if( c2=='t' && strncmp(azArg[1],"tcl",n2)==0 ){
       p->mode = MODE_Tcl;
-      sqlite3_snprintf(sizeof(p->separator), p->separator, " ");
+      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, " ");
+      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Line);
     }else if( c2=='c' && strncmp(azArg[1],"csv",n2)==0 ){
       p->mode = MODE_Csv;
-      sqlite3_snprintf(sizeof(p->separator), p->separator, ",");
+      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, ",");
+      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Line);
     }else if( c2=='t' && strncmp(azArg[1],"tabs",n2)==0 ){
       p->mode = MODE_List;
-      sqlite3_snprintf(sizeof(p->separator), p->separator, "\t");
+      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, "\t");
+      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Line);
     }else if( c2=='i' && strncmp(azArg[1],"insert",n2)==0 ){
       p->mode = MODE_Insert;
       set_table_name(p, nArg>=3 ? azArg[2] : "table");
+    }else if( c2=='a' && strncmp(azArg[1],"ascii",n2)==0 ){
+      p->mode = MODE_Ascii;
+      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
+      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
     }else {
       fprintf(stderr,"Error: mode should be one of: "
-         "column csv html insert line list tabs tcl\n");
+         "ascii column csv html insert line list tabs tcl\n");
       rc = 1;
     }
   }else
@@ -3028,10 +3134,30 @@ static int do_meta_command(char *zLine, struct callback_data *p){
   }else
 #endif
 
+  if( c=='r' && strncmp(azArg[0], "rowseparator", n)==0 ){
+    if( nArg==2 ){
+      sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
+                       "%.*s", (int)sizeof(p->rowSeparator)-1, azArg[1]);
+    }else{
+      fprintf(stderr, "Usage: .rowseparator STRING\n");
+      rc = 1;
+    }
+  }else
+
+  if( c=='c' && strncmp(azArg[0], "colseparator", n)==0 ){
+    if( nArg==2 ){
+      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
+                       "%.*s", (int)sizeof(p->colSeparator)-1, azArg[1]);
+    }else{
+      fprintf(stderr, "Usage: .colseparator STRING\n");
+      rc = 1;
+    }
+  }else
+
   if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){
     if( nArg==2 ){
-      sqlite3_snprintf(sizeof(p->separator), p->separator,
-                       "%.*s", (int)sizeof(p->separator)-1, azArg[1]);
+      sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
+                       "%.*s", (int)sizeof(p->colSeparator)-1, azArg[1]);
     }else{
       fprintf(stderr, "Usage: .separator STRING\n");
       rc = 1;
@@ -3064,21 +3190,24 @@ static int do_meta_command(char *zLine, struct callback_data *p){
       rc = 1;
       goto meta_command_exit;
     }
-    fprintf(p->out,"%9.9s: %s\n","echo", p->echoOn ? "on" : "off");
-    fprintf(p->out,"%9.9s: %s\n","eqp", p->autoEQP ? "on" : "off");
-    fprintf(p->out,"%9.9s: %s\n","explain", p->explainPrev.valid ? "on" :"off");
-    fprintf(p->out,"%9.9s: %s\n","headers", p->showHeader ? "on" : "off");
-    fprintf(p->out,"%9.9s: %s\n","mode", modeDescr[p->mode]);
-    fprintf(p->out,"%9.9s: ", "nullvalue");
+    fprintf(p->out,"%12.12s: %s\n","echo", p->echoOn ? "on" : "off");
+    fprintf(p->out,"%12.12s: %s\n","eqp", p->autoEQP ? "on" : "off");
+    fprintf(p->out,"%12.12s: %s\n","explain", p->explainPrev.valid ? "on" :"off");
+    fprintf(p->out,"%12.12s: %s\n","headers", p->showHeader ? "on" : "off");
+    fprintf(p->out,"%12.12s: %s\n","mode", modeDescr[p->mode]);
+    fprintf(p->out,"%12.12s: ", "nullvalue");
       output_c_string(p->out, p->nullvalue);
       fprintf(p->out, "\n");
-    fprintf(p->out,"%9.9s: %s\n","output",
+    fprintf(p->out,"%12.12s: %s\n","output",
             strlen30(p->outfile) ? p->outfile : "stdout");
-    fprintf(p->out,"%9.9s: ", "separator");
-      output_c_string(p->out, p->separator);
+    fprintf(p->out,"%12.12s: ", "colseparator");
+      output_c_string(p->out, p->colSeparator);
+      fprintf(p->out, "\n");
+    fprintf(p->out,"%12.12s: ", "rowseparator");
+      output_c_string(p->out, p->rowSeparator);
       fprintf(p->out, "\n");
-    fprintf(p->out,"%9.9s: %s\n","stats", p->statsOn ? "on" : "off");
-    fprintf(p->out,"%9.9s: ","width");
+    fprintf(p->out,"%12.12s: %s\n","stats", p->statsOn ? "on" : "off");
+    fprintf(p->out,"%12.12s: ","width");
     for (i=0;i<(int)ArraySize(p->colWidth) && p->colWidth[i] != 0;i++) {
       fprintf(p->out,"%d ",p->colWidth[i]);
     }
@@ -3672,8 +3801,10 @@ static int process_sqliterc(
 ** Show available command line options
 */
 static const char zOptions[] = 
+  "   -ascii               set output mode to 'ascii'\n"
   "   -bail                stop after hitting an error\n"
   "   -batch               force batch I/O\n"
+  "   -colseparator SEP    same as -separator\n"
   "   -column              set output mode to 'column'\n"
   "   -cmd COMMAND         run \"COMMAND\" before reading stdin\n"
   "   -csv                 set output mode to 'csv'\n"
@@ -3693,6 +3824,7 @@ static const char zOptions[] =
   "   -multiplex           enable the multiplexor VFS\n"
 #endif
   "   -nullvalue TEXT      set text string for NULL values. Default ''\n"
+  "   -rowseparator SEP    set output line separator. Default: '\\n'\n"
   "   -separator SEP       set output field separator. Default: '|'\n"
   "   -stats               print memory stats before each finalize\n"
   "   -version             show SQLite version\n"
@@ -3720,7 +3852,8 @@ static void usage(int showDetail){
 static void main_init(struct callback_data *data) {
   memset(data, 0, sizeof(*data));
   data->mode = MODE_List;
-  memcpy(data->separator,"|", 2);
+  memcpy(data->colSeparator,"|", 2);
+  memcpy(data->rowSeparator,"\n", 2);
   data->showHeader = 0;
   sqlite3_config(SQLITE_CONFIG_URI, 1);
   sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
@@ -3918,9 +4051,18 @@ int main(int argc, char **argv){
       data.mode = MODE_Column;
     }else if( strcmp(z,"-csv")==0 ){
       data.mode = MODE_Csv;
-      memcpy(data.separator,",",2);
-    }else if( strcmp(z,"-separator")==0 ){
-      sqlite3_snprintf(sizeof(data.separator), data.separator,
+      memcpy(data.colSeparator,",",2);
+    }else if( strcmp(z,"-ascii")==0 ){
+      data.mode = MODE_Ascii;
+      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
+                       SEP_Column);
+      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
+                       SEP_Row);
+    }else if( strcmp(z,"-separator")==0 || strcmp(z,"-colseparator")==0 ){
+      sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
+                       "%s",cmdline_option_value(argc,argv,++i));
+    }else if( strcmp(z,"-rowseparator")==0 ){
+      sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
                        "%s",cmdline_option_value(argc,argv,++i));
     }else if( strcmp(z,"-nullvalue")==0 ){
       sqlite3_snprintf(sizeof(data.nullvalue), data.nullvalue,
index e6fb0c28d26c0107ae155384f942c5df843eba1b..9c7190e007502714d23289e0c3d40222c772152f 100644 (file)
@@ -206,10 +206,10 @@ 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: column csv html insert line list tabs tcl}}
+} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}}
 do_test shell1-2.2.6 {
   catchcmd "test.db" ".mode \'insert FOO"
-} {1 {Error: mode should be one of: column csv html insert line list tabs tcl}}
+} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}}
 
 # check multiple tokens, and quoted tokens
 do_test shell1-2.3.1 {
@@ -237,7 +237,7 @@ 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: column csv html insert line list tabs tcl}}
+} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}}
 do_test shell1-2.4.2 {
   catchcmd "test.db" ".mode csv"
 } {0 {}}
@@ -420,6 +420,7 @@ do_test shell1-3.12.3 {
 } {1 {Usage: .indices ?LIKE-PATTERN?}}
 
 # .mode MODE ?TABLE?     Set output mode where MODE is one of:
+#                          ascii    Columns/rows delimited with 0x1F and 0x1E
 #                          csv      Comma-separated values
 #                          column   Left-aligned columns.  (See .width)
 #                          html     HTML <table> code
@@ -430,10 +431,10 @@ do_test shell1-3.12.3 {
 #                          tcl      TCL list elements
 do_test shell1-3.13.1 {
   catchcmd "test.db" ".mode"
-} {1 {Error: mode should be one of: column csv html insert line list tabs tcl}}
+} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}}
 do_test shell1-3.13.2 {
   catchcmd "test.db" ".mode FOO"
-} {1 {Error: mode should be one of: column csv html insert line list tabs tcl}}
+} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}}
 do_test shell1-3.13.3 {
   catchcmd "test.db" ".mode csv"
 } {0 {}}
@@ -466,10 +467,10 @@ do_test shell1-3.13.11 {
 # don't allow partial mode type matches
 do_test shell1-3.13.12 {
   catchcmd "test.db" ".mode l"
-} {1 {Error: mode should be one of: column csv html insert line list tabs tcl}}
+} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}}
 do_test shell1-3.13.13 {
   catchcmd "test.db" ".mode li"
-} {1 {Error: mode should be one of: column csv html insert line list tabs tcl}}
+} {1 {Error: mode should be one of: ascii column csv html insert line list tabs tcl}}
 do_test shell1-3.13.14 {
   catchcmd "test.db" ".mode lin"
 } {0 {}}
@@ -585,7 +586,7 @@ CREATE VIEW v2 AS SELECT x+1 AS y FROM t1;
 CREATE VIEW v1 AS SELECT y+1 FROM v2;}}
 db eval {DROP VIEW v1; DROP VIEW v2; DROP TABLE t1;}
 
-# .separator STRING      Change separator used by output mode and .import
+# .separator STRING  Change column separator used by output and .import
 do_test shell1-3.22.1 {
   catchcmd "test.db" ".separator"
 } {1 {Usage: .separator STRING}}
@@ -606,10 +607,11 @@ do_test shell1-3.23.1 {
        [regexp {mode:} $res] \
        [regexp {nullvalue:} $res] \
        [regexp {output:} $res] \
-       [regexp {separator:} $res] \
+       [regexp {colseparator:} $res] \
+       [regexp {rowseparator:} $res] \
        [regexp {stats:} $res] \
        [regexp {width:} $res]
-} {1 1 1 1 1 1 1 1 1}
+} {1 1 1 1 1 1 1 1 1 1}
 do_test shell1-3.23.2 {
   # too many arguments
   catchcmd "test.db" ".show BAD"
@@ -809,4 +811,28 @@ do_test shell1-4.6 {
 ";"
 "$"} 7}
 
+# .colseparator STRING  Change column separator used by output and .import
+do_test shell1-5.1.1 {
+  catchcmd "test.db" ".colseparator"
+} {1 {Usage: .colseparator STRING}}
+do_test shell1-5.1.2 {
+  catchcmd "test.db" ".colseparator FOO"
+} {0 {}}
+do_test shell1-5.1.3 {
+  # too many arguments
+  catchcmd "test.db" ".colseparator FOO BAD"
+} {1 {Usage: .colseparator STRING}}
+
+# .rowseparator STRING  Change row separator used by output and .import
+do_test shell1-6.1.1 {
+  catchcmd "test.db" ".rowseparator"
+} {1 {Usage: .rowseparator STRING}}
+do_test shell1-6.1.2 {
+  catchcmd "test.db" ".rowseparator FOO"
+} {0 {}}
+do_test shell1-6.1.3 {
+  # too many arguments
+  catchcmd "test.db" ".rowseparator FOO BAD"
+} {1 {Usage: .rowseparator STRING}}
+
 finish_test
index 6e9dd2063968fd7f507715f820c9de948496a411..4e11e8798b0d9f174385877fbd724d53967a0371 100644 (file)
@@ -64,10 +64,16 @@ do_test shell5-1.2.3 {
   catchcmd "test.db" ".separator FOO BAD"
 } {1 {Usage: .separator STRING}}
 
-# separator should default to "|"
-do_test shell5-1.3.1 {
+# column separator should default to "|"
+do_test shell5-1.3.1.1 {
   set res [catchcmd "test.db" ".show"]
-  list [regexp {separator: \"\|\"} $res]
+  list [regexp {colseparator: \"\|\"} $res]
+} {1}
+
+# row separator should default to "\n"
+do_test shell5-1.3.1.2 {
+  set res [catchcmd "test.db" ".show"]
+  list [regexp {rowseparator: \"\\n\"} $res]
 } {1}
 
 # set separator to different value.
@@ -369,5 +375,32 @@ CREATE TABLE t4(a, b);
   db eval { SELECT * FROM t4 }
 } {xy\" hello one 2 {} {}}
 
+#----------------------------------------------------------------------------
+# Tests for the shell "ascii" import/export mode.
+#
+do_test shell5-3.1 {
+  set fd [open shell5.csv w]
+  fconfigure $fd -encoding binary -translation binary
+  puts -nonewline $fd "\"test 1\"\x1F,test 2\r\n\x1E"
+  puts -nonewline $fd "test 3\x1Ftest 4\n"
+  close $fd
+  catchcmd test.db {
+.mode ascii
+CREATE TABLE t5(a, b);
+.import shell5.csv t5
+  }
+  db eval { SELECT * FROM t5 }
+} "\{\"test 1\"} \{,test 2\r\n\} \{test 3\} \{test 4\n\}"
+
+#
+# NOTE: This test ends up converting the "\r\n" to "\n\n" due
+#       to end-of-line translation on the "stdout" channel.
+#
+do_test shell5-3.2 {
+  catchcmd test.db {
+.mode ascii
+SELECT * FROM t5;
+  }
+} "0 \{\"test 1\"\x1F,test 2\n\n\x1Etest 3\x1Ftest 4\n\x1E\}"
 
 finish_test