From: drh <> Date: Mon, 10 Nov 2025 18:39:33 +0000 (+0000) Subject: Improved argument parsing and error message infrastructure for X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a04f5335f91c29f3cfb03e3e3c5e34a224063da3;p=thirdparty%2Fsqlite.git Improved argument parsing and error message infrastructure for dot-commands in the CLI. FossilOrigin-Name: 23d5d09db8eae33b250cb8c86b6e6790fc9d5a62ca16df77d8aa881405da66fa --- diff --git a/manifest b/manifest index d120cd91ff..8ea9a39df0 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C The\s".www"\scommand\sis\snow\shandled\sby\sQRF.\s\sSo\sat\sthis\spoint,\sQRF\shandles\nall\squery\sresult\sformatting\sin\sthe\sCLI\sand\sthe\slegacy\sformatter\shas\sbeen\nremoved\sfrom\sthe\scode. -D 2025-11-10T15:56:18.937 +C Improved\sargument\sparsing\sand\serror\smessage\sinfrastructure\sfor\ndot-commands\sin\sthe\sCLI. +D 2025-11-10T18:39:33.947 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -735,7 +735,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a -F src/shell.c.in 2de811229c16b13c38c019d9ce449ba40f93734681a4d5c6d194c44fe9c1f75c +F src/shell.c.in be014464de2877b434ccd0c65757b07be23641674fce92fe31260d6808500cb4 F src/sqlite.h.in 7403a952a8f1239de7525b73c4e3a0f9540ec0607ed24fec887f5832642d44b8 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52 @@ -2173,8 +2173,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 0eb0410f725eed44973cf8712ab2d24c16fb5cbb249b5780f8fe5d41b2193d79 -R 00bfc0527e98b76f591e01157abf37f0 +P 35d4c7151e63c3f105a11dddc853666ae19cfca190204847a42f2b2a5641e95d +R 4ca7ce922316e03e64949b78f2940fba U drh -Z a342d7d49c0ab55f3ea5c8cac28ceec8 +Z 5e2631e91d82ea9477e2d7d3a822726d # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index e5fb996386..e0d2d993c5 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -35d4c7151e63c3f105a11dddc853666ae19cfca190204847a42f2b2a5641e95d +23d5d09db8eae33b250cb8c86b6e6790fc9d5a62ca16df77d8aa881405da66fa diff --git a/src/shell.c.in b/src/shell.c.in index 088b6d25d8..73720da401 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -1200,7 +1200,6 @@ struct ShellState { sqlite3 *db; /* The database */ u8 autoExplain; /* Automatically turn on .explain mode */ u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ - u8 autoEQPtest; /* autoEQP is in test mode */ u8 autoEQPtrace; /* autoEQP is in trace mode */ u8 scanstatsOn; /* True to display scan stats before each finalize */ u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */ @@ -1219,6 +1218,7 @@ struct ShellState { int outCount; /* Revert to stdout when reaching zero */ int cnt; /* Number of records displayed so far */ i64 lineno; /* Line number of last line read from in */ + const char *zInFile; /* Name of the input file */ int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */ FILE *in; /* Read commands from this stream */ FILE *out; /* Write results here */ @@ -1266,6 +1266,15 @@ struct ShellState { #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ #endif + struct DotCmdLine { /* Info about arguments to a dot-command */ + const char *zOrig; /* Original text of the dot-command */ + char *zCopy; /* Copy of zOrig, from malloc() */ + int nAlloc; /* Size of allocates for arrays below */ + int nArg; /* Number of argument slots actually used */ + char **azArg; /* Pointer to each argument, dequoted */ + int *aiOfst; /* Offset into zOrig[] for start of each arg */ + char *abQuot; /* True if the argment was originally quoted */ + } dot; #ifdef SQLITE_SHELL_FIDDLE struct { const char * zInput; /* Input string from wasm/JS proxy */ @@ -1445,6 +1454,20 @@ static void shellPutsFunc( sqlite3_result_value(pCtx, apVal[0]); } +/* +** Compute the name of the location of an input error in memory +** obtained from sqlite3_malloc(). +*/ +static char *shellErrorLocation(ShellState *p){ + char *zLoc; + if( p->zInFile==0 || strcmp(p->zInFile,"")==0){ + zLoc = sqlite3_mprintf("line %lld:", p->lineno); + }else{ + zLoc = sqlite3_mprintf("%s:%lld:", p->zInFile, p->lineno); + } + return zLoc; +} + /* ** If in safe mode, print an error message described by the arguments ** and exit immediately. @@ -1457,14 +1480,50 @@ static void failIfSafeMode( if( p->bSafeMode ){ va_list ap; char *zMsg; + char *zLoc = shellErrorLocation(p); va_start(ap, zErrMsg); zMsg = sqlite3_vmprintf(zErrMsg, ap); va_end(ap); - sqlite3_fprintf(stderr, "line %lld: %s\n", p->lineno, zMsg); + sqlite3_fprintf(stderr, "%s %s\n", zLoc, zMsg); exit(1); } } +/* +** Issue an error message from a dot-command. +*/ +static void dotCmdError( + ShellState *p, /* Shell state */ + int iArg, /* Index of argument on which error occurred */ + const char *zBrief, /* Brief (<20 character) error description */ + const char *zDetail, /* Error details */ + ... +){ + FILE *out = stderr; + char *zLoc = shellErrorLocation(p); + if( zBrief!=0 && iArg>=0 && iArgdot.nArg ){ + int i = p->dot.aiOfst[iArg]; + int nPrompt = strlen(zBrief) + 5; + sqlite3_fprintf(out, "%s %s\n", zLoc, p->dot.zOrig); + if( i > nPrompt ){ + sqlite3_fprintf(out, "%s %*s%s ---^\n", zLoc, 1+i-nPrompt, "", zBrief); + }else{ + sqlite3_fprintf(out, "%s %*s^--- %s\n", zLoc, i, "", zBrief); + } + } + if( zDetail ){ + char *zMsg; + va_list ap; + va_start(ap, zDetail); + zMsg = sqlite3_vmprintf(zDetail,ap); + va_end(ap); + sqlite3_fprintf(out,"%s %s\n", zLoc, zMsg); + sqlite3_free(zMsg); + } + sqlite3_free(zLoc); +} + + /* ** SQL function: edit(VALUE) ** edit(VALUE,EDITOR) @@ -6884,12 +6943,13 @@ static int faultsim_callback(int iArg){ ** ** Return 1 on error, 2 to exit, and 0 otherwise. */ -static int do_meta_command(char *zLine, ShellState *p){ +static int do_meta_command(const char *zLine, ShellState *p){ int h = 1; int nArg = 0; int n, c; int rc = 0; - char *azArg[52]; + char *z; + char **azArg; #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_AUTHORIZATION) if( p->expert.pExpert ){ @@ -6899,27 +6959,51 @@ static int do_meta_command(char *zLine, ShellState *p){ /* Parse the input line into tokens. */ - while( zLine[h] && nArgdot.zOrig = zLine; + free(p->dot.zCopy); + z = p->dot.zCopy = strdup(zLine); + shell_check_oom(z); + nArg = 0; + while( z[h] ){ + while( IsSpace(z[h]) ){ h++; } + if( z[h]==0 ) break; + if( nArg+2>p->dot.nAlloc ){ + p->dot.nAlloc = nArg+22; + p->dot.azArg = realloc(p->dot.azArg,p->dot.nAlloc*sizeof(char*)); + shell_check_oom(p->dot.azArg); + p->dot.aiOfst = realloc(p->dot.aiOfst,p->dot.nAlloc*sizeof(int)); + shell_check_oom(p->dot.aiOfst); + p->dot.abQuot = realloc(p->dot.abQuot,p->dot.nAlloc); + shell_check_oom(p->dot.abQuot); + } + if( z[h]=='\'' || z[h]=='"' ){ + int delim = z[h++]; + p->dot.abQuot[nArg] = 1; + p->dot.azArg[nArg] = &z[h]; + p->dot.aiOfst[nArg] = h; + while( z[h] && z[h]!=delim ){ + if( z[h]=='\\' && delim=='"' && z[h+1]!=0 ) h++; h++; } - if( zLine[h]==delim ){ - zLine[h++] = 0; + if( z[h]==delim ){ + z[h++] = 0; } - if( delim=='"' ) resolve_backslashes(azArg[nArg-1]); + if( delim=='"' ) resolve_backslashes(p->dot.azArg[nArg]); }else{ - azArg[nArg++] = &zLine[h]; - while( zLine[h] && !IsSpace(zLine[h]) ){ h++; } - if( zLine[h] ) zLine[h++] = 0; + p->dot.abQuot[nArg] = 0; + p->dot.azArg[nArg] = &z[h]; + p->dot.aiOfst[nArg] = h; + while( z[h] && !IsSpace(z[h]) ){ h++; } + if( z[h] ) z[h++] = 0; } + nArg++; } - azArg[nArg] = 0; + p->dot.nArg = nArg; + if( p->dot.nAlloc==0 ){ + return 0; /* No input tokens */ + } + p->dot.azArg[nArg] = 0; + azArg = p->dot.azArg; /* Process the input line. */ @@ -6978,7 +7062,7 @@ static int do_meta_command(char *zLine, ShellState *p){ bAsync = 1; }else { - sqlite3_fprintf(stderr,"unknown option: %s\n", azArg[j]); + dotCmdError(p, j, "unknown option", "should be -append or -async"); return 1; } }else if( zDestFile==0 ){ @@ -7254,8 +7338,8 @@ static int do_meta_command(char *zLine, ShellState *p){ if( nArg>1 ) break; } if( nArg>1 && ii==ArraySize(aDbConfig) ){ - sqlite3_fprintf(stderr,"Error: unknown dbconfig \"%s\"\n", azArg[1]); - eputz("Enter \".dbconfig\" with no arguments for a list\n"); + dotCmdError(p, 1, "unknown dbconfig", + "Enter \".dbconfig\" with no arguments for a list"); } }else @@ -7285,8 +7369,9 @@ static int do_meta_command(char *zLine, ShellState *p){ if( z[0]=='-' ) z++; if( cli_strcmp(z,"preserve-rowids")==0 ){ #ifdef SQLITE_OMIT_VIRTUALTABLE - eputz("The --preserve-rowids option is not compatible" - " with SQLITE_OMIT_VIRTUALTABLE\n"); + dotCmdError(p, i, "unable", + "The --preserve-rowids option is not compatible" + " with SQLITE_OMIT_VIRTUALTABLE"); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -7304,8 +7389,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ShellSetFlag(p, SHFLG_DumpNoSys); }else { - sqlite3_fprintf(stderr, - "Unknown option \"%s\" on \".dump\"\n", azArg[i]); + dotCmdError(p, i, "unknown option", 0); rc = 1; sqlite3_free(zLike); goto meta_command_exit; @@ -7402,7 +7486,6 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){ if( nArg==2 ){ - p->autoEQPtest = 0; if( p->autoEQPtrace ){ if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); p->autoEQPtrace = 0; @@ -7412,9 +7495,6 @@ static int do_meta_command(char *zLine, ShellState *p){ }else if( cli_strcmp(azArg[1],"trigger")==0 ){ p->autoEQP = AUTOEQP_trigger; #ifdef SQLITE_DEBUG - }else if( cli_strcmp(azArg[1],"test")==0 ){ - p->autoEQP = AUTOEQP_on; - p->autoEQPtest = 1; }else if( cli_strcmp(azArg[1],"trace")==0 ){ p->autoEQP = AUTOEQP_full; p->autoEQPtrace = 1; @@ -8342,7 +8422,9 @@ static int do_meta_command(char *zLine, ShellState *p){ } if( !chng ){ if( p->mode==MODE_Column - || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) + || p->mode==MODE_Box + || p->mode==MODE_Table + || p->mode>=MODE_Markdown ){ sqlite3_fprintf(p->out, "current output mode: %s --wrap %d --wordwrap %s " @@ -8922,7 +9004,9 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_fprintf(stderr,"Error: cannot open \"%s\"\n", azArg[1]); rc = 1; }else{ - rc = process_input(p, azArg[1]); + char *zFilename = strdup(azArg[1]); + rc = process_input(p, zFilename); + free(zFilename); fclose(p->in); } p->in = inSaved; @@ -10551,7 +10635,6 @@ static int do_meta_command(char *zLine, ShellState *p){ if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){ int j; - assert( nArg<=ArraySize(azArg) ); p->nWidth = nArg-1; p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(short int)*2); if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory(); @@ -10913,6 +10996,8 @@ static int process_input(ShellState *p, const char *zSrc){ int errCnt = 0; /* Number of errors seen */ i64 startline = 0; /* Line number for start of current input */ QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */ + const char *saved_zInFile; /* Prior value of p->zInFile */ + i64 saved_lineno; /* Prior value of p->lineno */ if( p->inputNesting==MAX_INPUT_NESTING ){ /* This will be more informative in a later version. */ @@ -10921,6 +11006,9 @@ static int process_input(ShellState *p, const char *zSrc){ return 1; } ++p->inputNesting; + saved_zInFile = p->zInFile; + p->zInFile = zSrc; + saved_lineno = p->lineno; p->lineno = 0; CONTINUE_PROMPT_RESET; while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){ @@ -11018,6 +11106,8 @@ static int process_input(ShellState *p, const char *zSrc){ free(zSql); free(zLine); --p->inputNesting; + p->zInFile = saved_zInFile; + p->lineno = saved_lineno; return errCnt>0; } @@ -11956,6 +12046,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ for(i=0; i