Add new ASCII mode to the shell capable of importing and exporting using the official unit and record separators (i.e. 0x1F and 0x1E, respectively).

FossilOrigin-Name: 7fe601ead0d0ae26cb09d0dbc7d6367785376567
This commit is contained in:
mistachkin 2014-07-19 20:15:16 +00:00
parent 857745c089
commit 636bf9f768
5 changed files with 328 additions and 124 deletions

View File

@ -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

View File

@ -1 +1 @@
61cee3c0678f5abd9131a29ab946a5e71f55643e
7fe601ead0d0ae26cb09d0dbc7d6367785376567

View 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 separator required for import\n");
fprintf(stderr, "Error: non-null column separator required for import\n");
return 1;
}
if( nSep>1 ){
fprintf(stderr, "Error: multi-character separators not allowed"
fprintf(stderr, "Error: multi-character column 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>";
nSep = strlen30(p->rowSeparator);
if( nSep==0 ){
fprintf(stderr, "Error: non-null row separator required for import\n");
return 1;
}
if( nSep>1 ){
fprintf(stderr, "Error: multi-character row separators not allowed"
" for import\n");
return 1;
}
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,"%9.9s: %s\n","stats", p->statsOn ? "on" : "off");
fprintf(p->out,"%9.9s: ","width");
fprintf(p->out,"%12.12s: ", "rowseparator");
output_c_string(p->out, p->rowSeparator);
fprintf(p->out, "\n");
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,

View 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

View 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