From c28490c05652d5b28e94094dd30f3910d82948de Mon Sep 17 00:00:00 2001 From: drh Date: Thu, 26 Oct 2006 14:25:58 +0000 Subject: [PATCH] Command-line shell enhancements. Bail out when errors are seen in non-interactive mode. Override isatty() using -interactive or -batch command-line options. Report line number in error messages. Tickets #2009, #2045. (CVS 3490) FossilOrigin-Name: 3baa04cfb91039e27f642f6f78ef761b5770cb08 --- manifest | 14 +++--- manifest.uuid | 2 +- src/shell.c | 121 +++++++++++++++++++++++++++----------------------- 3 files changed, 74 insertions(+), 63 deletions(-) diff --git a/manifest b/manifest index a7ec8a7796..291b03701f 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Empty\squeries\sshould\sget\sno\sresults.\s\sMy\srecent\schange\n(\shttp://www.sqlite.org/cvstrac/chngview?cn=3486\s)\sbroke\stest\sfts2a-5.3.\nThis\schange\sshould\smake\sthe\sexpected\sresult\smore\sobvious.\s(CVS\s3489) -D 2006-10-26T00:41:51 +C Command-line\sshell\senhancements.\s\sBail\sout\swhen\serrors\sare\sseen\sin\nnon-interactive\smode.\s\sOverride\sisatty()\susing\s-interactive\sor\s-batch\ncommand-line\soptions.\s\sReport\sline\snumber\sin\serror\smessages.\nTickets\s#2009,\s#2045.\s(CVS\s3490) +D 2006-10-26T14:25:58 F Makefile.in 4379c909d46b38b8c5db3533084601621d4f14b2 F Makefile.linux-gcc 2d8574d1ba75f129aba2019f0b959db380a90935 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028 @@ -94,7 +94,7 @@ F src/printf.c b179b6ed12f793e028dd169e2e2e2b2a37eedc63 F src/random.c d40f8d356cecbd351ccfab6eaedd7ec1b54f5261 F src/select.c 6ba6d8ead43d0575ce1f8b418cc039f8f301389a F src/server.c 087b92a39d883e3fa113cae259d64e4c7438bc96 -F src/shell.c 1c17cd03c9cc2ff618435156038f508c159ecf12 +F src/shell.c 73cbdcb3cdc63bca7630dc1dfe41ab9ac90d98db F src/sqlite.h.in bf935004029631fd93d119bcf2f7259b9cb9ad5e F src/sqlite3ext.h 2c2156cc32a158e2b7bd9042d42accf94bff2e40 F src/sqliteInt.h 637ef229c3d8e0f98096ab31c496efdf5361d678 @@ -419,7 +419,7 @@ F www/tclsqlite.tcl bb0d1357328a42b1993d78573e587c6dcbc964b9 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b F www/whentouse.tcl 97e2b5cd296f7d8057e11f44427dea8a4c2db513 -P 5878add0839f9c5bec77caae2361ec20cb60b48b -R 3d9ce122beb09a00cd3fd7137dd66557 -U shess -Z 4dba001cc239d0747f6caf0a916f48ed +P cde383eb467de0d752e94a22cd2f890c2dc599cc +R 91d1f3065e90bb2f41c9cbd193a3305f +U drh +Z 74126c296022fab53cfd540120f95a5c diff --git a/manifest.uuid b/manifest.uuid index 725fdfedc8..6d04ae7584 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -cde383eb467de0d752e94a22cd2f890c2dc599cc \ No newline at end of file +3baa04cfb91039e27f642f6f78ef761b5770cb08 \ No newline at end of file diff --git a/src/shell.c b/src/shell.c index 43ea506637..747100a8f1 100644 --- a/src/shell.c +++ b/src/shell.c @@ -12,7 +12,7 @@ ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. ** -** $Id: shell.c,v 1.150 2006/09/25 13:09:23 drh Exp $ +** $Id: shell.c,v 1.151 2006/10/26 14:25:58 drh Exp $ */ #include #include @@ -60,6 +60,12 @@ extern int isatty(); #endif +/* +** Threat stdin as an interactive input if the following variable +** is true. Otherwise, assume stdin is connected to a file or pipe. +*/ +static int stdin_is_interactive = 1; + /* ** The following is the open SQLite database. We make a pointer ** to this database a static variable so that it can be accessed @@ -184,10 +190,7 @@ static char *local_getline(char *zPrompt, FILE *in){ } /* -** Retrieve a single line of input text. "isatty" is true if text -** is coming from a terminal. In that case, we issue a prompt and -** attempt to use "readline" for command-line editing. If "isatty" -** is false, use "local_getline" instead of "readline" and issue no prompt. +** Retrieve a single line of input text. ** ** zPrior is a string of prior text retrieved. If not the empty ** string, then issue a continuation prompt. @@ -587,7 +590,7 @@ static void set_table_name(struct callback_data *p, const char *zName){ ** If the third argument, quote, is not '\0', then it is used as a ** quote character for zAppend. */ -static char * appendText(char *zIn, char const *zAppend, char quote){ +static char *appendText(char *zIn, char const *zAppend, char quote){ int len; int i; int nAppend = strlen(zAppend); @@ -793,7 +796,7 @@ static char zHelp[] = ; /* Forward reference */ -static void process_input(struct callback_data *p, FILE *in); +static int process_input(struct callback_data *p, FILE *in); /* ** Make sure the database is open. If it is not, then open it. If @@ -853,6 +856,23 @@ static void resolve_backslashes(char *z){ z[j] = 0; } +/* +** Interpret zArg as a boolean value. Return either 0 or 1. +*/ +static int booleanValue(char *zArg){ + int val = atoi(zArg); + int j; + for(j=0; zArg[j]; j++){ + zArg[j] = tolower(zArg[j]); + } + if( strcmp(zArg,"on")==0 ){ + val = 1; + }else if( strcmp(zArg,"yes")==0 ){ + val = 1; + } + return val; +} + /* ** If an input line begins with "." then invoke this routine to ** process that line. @@ -957,18 +977,7 @@ static int do_meta_command(char *zLine, struct callback_data *p){ }else if( c=='e' && strncmp(azArg[0], "echo", n)==0 && nArg>1 ){ - int j; - char *z = azArg[1]; - int val = atoi(azArg[1]); - for(j=0; z[j]; j++){ - z[j] = tolower((unsigned char)z[j]); - } - if( strcmp(z,"on")==0 ){ - val = 1; - }else if( strcmp(z,"yes")==0 ){ - val = 1; - } - p->echoOn = val; + p->echoOn = booleanValue(azArg[1]); }else if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ @@ -976,18 +985,7 @@ static int do_meta_command(char *zLine, struct callback_data *p){ }else if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){ - int j; - static char zOne[] = "1"; - char *z = nArg>=2 ? azArg[1] : zOne; - int val = atoi(z); - for(j=0; z[j]; j++){ - z[j] = tolower((unsigned char)z[j]); - } - if( strcmp(z,"on")==0 ){ - val = 1; - }else if( strcmp(z,"yes")==0 ){ - val = 1; - } + int val = nArg>=2 ? booleanValue(azArg[1]) : 1; if(val == 1) { if(!p->explainPrev.valid) { p->explainPrev.valid = 1; @@ -1018,21 +1016,9 @@ static int do_meta_command(char *zLine, struct callback_data *p){ } }else - if( c=='h' && (strncmp(azArg[0], "header", n)==0 - || + if( c=='h' && (strncmp(azArg[0], "header", n)==0 || strncmp(azArg[0], "headers", n)==0 )&& nArg>1 ){ - int j; - char *z = azArg[1]; - int val = atoi(azArg[1]); - for(j=0; z[j]; j++){ - z[j] = tolower((unsigned char)z[j]); - } - if( strcmp(z,"on")==0 ){ - val = 1; - }else if( strcmp(z,"yes")==0 ){ - val = 1; - } - p->showHeader = val; + p->showHeader = booleanValue(azArg[1]); }else if( c=='h' && strncmp(azArg[0], "help", n)==0 ){ @@ -1482,18 +1468,26 @@ static int _is_command_terminator(const char *zLine){ ** is coming from a file or device. A prompt is issued and history ** is saved only if input is interactive. An interrupt signal will ** cause this routine to exit immediately, unless input is interactive. +** +** Return the number of errors. */ -static void process_input(struct callback_data *p, FILE *in){ +static int process_input(struct callback_data *p, FILE *in){ char *zLine; char *zSql = 0; int nSql = 0; char *zErrMsg; - int rc; + int rc = 0; + int lineno = 0; + int startline = 0; while( fflush(p->out), (zLine = one_input_line(zSql, in))!=0 ){ if( seenInterrupt ){ if( in!=0 ) break; seenInterrupt = 0; } + if( rc && (in!=0 || !stdin_is_interactive) ){ + break; + } + lineno++; if( p->echoOn ) printf("%s\n", zLine); if( (zSql==0 || zSql[0]==0) && _all_whitespace(zLine) ) continue; if( zLine && zLine[0]=='.' && nSql==0 ){ @@ -1516,6 +1510,7 @@ static void process_input(struct callback_data *p, FILE *in){ exit(1); } strcpy(zSql, zLine); + startline = lineno; } }else{ int len = strlen(zLine); @@ -1534,13 +1529,18 @@ static void process_input(struct callback_data *p, FILE *in){ open_db(p); rc = sqlite3_exec(p->db, zSql, callback, p, &zErrMsg); if( rc || zErrMsg ){ - /* if( in!=0 && !p->echoOn ) printf("%s\n",zSql); */ + char zPrefix[100]; + if( in!=0 || !stdin_is_interactive ){ + sprintf(zPrefix, "SQL error near line %d:", startline); + }else{ + sprintf(zPrefix, "SQL error:"); + } if( zErrMsg!=0 ){ - printf("SQL error: %s\n", zErrMsg); + printf("%s %s\n", zPrefix, zErrMsg); sqlite3_free(zErrMsg); zErrMsg = 0; }else{ - printf("SQL error: %s\n", sqlite3_errmsg(p->db)); + printf("%s %s\n", zPrefix, sqlite3_errmsg(p->db)); } } free(zSql); @@ -1552,6 +1552,7 @@ static void process_input(struct callback_data *p, FILE *in){ if( !_all_whitespace(zSql) ) printf("Incomplete SQL: %s\n", zSql); free(zSql); } + return rc; } /* @@ -1642,7 +1643,7 @@ static void process_sqliterc( } in = fopen(sqliterc,"rb"); if( in ){ - if( isatty(fileno(stdout)) ){ + if( stdin_is_interactive ){ printf("Loading resources from %s\n",sqliterc); } process_input(p,in); @@ -1698,6 +1699,7 @@ int main(int argc, char **argv){ const char *zInitFile = 0; char *zFirstCmd = 0; int i; + int rc = 0; #ifdef __MACOS__ argc = ccommand(&argv); @@ -1705,6 +1707,7 @@ int main(int argc, char **argv){ Argv0 = argv[0]; main_init(&data); + stdin_is_interactive = isatty(0); /* Make sure we have a valid signal handler early, before anything ** else is done. @@ -1718,7 +1721,10 @@ int main(int argc, char **argv){ ** and the first command to execute. */ for(i=1; i