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
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.
#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 {
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",
{ "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 },
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;
}
}
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);
}
}
** 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;
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;
}
*/
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);
/* 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
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)
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)
".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",
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:
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;
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]!='-'
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;
}
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);
){
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 ){
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;
#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) */
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");
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++){
}
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);
}
" -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"
** 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);
}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 ){
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 ){