#define _CRT_SECURE_NO_WARNINGS
#endif
++/* Define stringification of a bare word (or anything without commas.) */
++# define SHELL_STRINGIFY_(f) #f
++# define SHELL_STRINGIFY(f) SHELL_STRINGIFY_(f)
++
/*
** Optionally #include a user-defined header, whereby compilation options
** may be set prior to where they take effect, but after platform setup.
** file. Note that this macro has a like effect on sqlite3.c compilation.
*/
#ifdef SQLITE_CUSTOM_INCLUDE
--# define INC_STRINGIFY_(f) #f
--# define INC_STRINGIFY(f) INC_STRINGIFY_(f)
--# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE)
++# include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE)
#endif
/*
static int bail_on_error = 0;
/*
--** Threat stdin as an interactive input if the following variable
++** Treat 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;
}
/*
--** This routine reads a line of text from FILE in, stores
--** the text in memory obtained from malloc() and returns a pointer
--** to the text. NULL is returned at end of file, or if malloc()
--** fails.
++** Arrange for shell input from either a FILE or a string.
++** For applicable invariants, see str_line_get(...) which is
++** the only modifier of this struct. (3rd and 4th members)
++** All other members are simply initialized to select the
++** input stream and track input sources, set by whatever
++** routines redirect the input.
++*/
++typedef struct InSource {
++ FILE *inFile; /* Will be 0 when input is to be taken from string. */
++ char *zStrIn; /* Will be 0 when no input is available from string. */
++ int iReadOffset; /* Offset into zStrIn where next "read" to be done. */
++ int lineno; /* How many lines have been read from this source */
++ const char *zSourceSay; /* For complaints, keep a name for this source */
++ struct InSource *pFrom; /* and redirect tracking to aid unraveling. */
++} InSource;
++#define INSOURCE_STR_REDIR(str, tagTo, isFrom) {0, str, 0, 0, tagTo, isFrom}
++#define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {fh, 0, 0, 0, tagTo, isFrom}
++#define INSOURCE_IS_INTERACTIVE(pIS) \
++ ((pIS)==&termInSource && stdin_is_interactive )
++
++/* This instance's address is taken as part of interactive input test. */
++static InSource termInSource = { 0 /*stdin*/, 0, 0, 0, "<terminal>", 0};
++static InSource stdInSource = { 0 /*stdin*/, 0, 0, 0, "<stdin>", 0};
++static void init_std_inputs( FILE *pIn ){
++ termInSource.inFile = pIn;
++ stdInSource.inFile = pIn;
++}
++
++static char *str_line_get( char *zBuf, int ncMax, InSource *pInSrc){
++ if( pInSrc->inFile!=0 ){
++ char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
++ if( zRet!=0 ){
++ int iRead = strlen30(zRet);
++ if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno;
++ /* Consider: record line length to avoid rescan for it. */
++ return zRet;
++ }
++ }
++ else if( pInSrc->zStrIn!=0 ){
++ char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset;
++ if( *zBegin!=0 ){
++ int iTake = 0;
++ char c;
++ ncMax -= 1;
++ while( iTake<ncMax && (c=zBegin[iTake])!=0 ){
++ zBuf[iTake++] = c;
++ if( c=='\n' ){
++ ++pInSrc->lineno;
++ break;
++ }
++ }
++ if( ncMax>=0 ) zBuf[iTake] = 0;
++ pInSrc->iReadOffset += iTake;
++ /* Consider: record line length to avoid rescan for it. */
++ return zBuf;
++ }
++ }
++ return 0;
++}
++
++/*
++** This routine reads a line of text from designated stream source,
++** stores the text in memory obtained from malloc() and returns a
++** pointer to the text. NULL is returned at end of file.
++** There will be no return if malloc() fails.
+**
+** The trailing newline (or other line-end chars) are stripped.
**
** If zLine is not NULL then it is a malloced buffer returned from
** a previous call to this routine that may be reused.
*/
--static char *local_getline(char *zLine, FILE *in){
++static char *local_getline(char *zLine, InSource *pInSrc){
int nLine = zLine==0 ? 0 : 100;
int n = 0;
zLine = realloc(zLine, nLine);
shell_check_oom(zLine);
}
-- if( fgets(&zLine[n], nLine - n, in)==0 ){
++ if( str_line_get(&zLine[n], nLine - n, pInSrc)==0 ){
if( n==0 ){
free(zLine);
return 0;
#if defined(_WIN32) || defined(WIN32)
/* For interactive input on Windows systems, translate the
** multi-byte characterset characters into UTF-8. */
-- if( stdin_is_interactive && in==stdin ){
++ if( stdin_is_interactive && pInSrc==&stdInSource) ){
char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0);
if( zTrans ){
int nTrans = strlen30(zTrans)+1;
return zLine;
}
- /*
- ** Arrange for shell input from either a FILE or a string.
- ** See getline_from(...) for applicable invariants. It is
- ** the only modifier of data within this struct, except for
- ** initialization which is either {0, zS} or {pF, 0}, (with
- ** the iReadOffset always 0-initialized), left to whatever
- ** routine is switching input sources.
- */
- typedef struct InSource {
- FILE *inFile; /* Will be 0 when input is to be taken from string. */
- char *zStrIn; /* Will be 0 when no input is available from string. */
- int iReadOffset; /* Offset into ZStrIn where next "read" to be done. */
- const char *zSourceSay; /* For complaints, keep a name for this source */
- struct InSource *pFrom; /* and redirect tracking to aid unraveling. */
- } InSource;
+
/*
- ** Read single lines of input text from source selected by pInSource.
- ** This function presents an interface similar to local_getline(...),
- ** to which it defers for FILE input. (This function may be considered
- ** to be an adapter except for the source parameter. With a rewrite,
- ** it and local_getline can merge, saving most of 26 LOC. )
- */
- static char *getline_from( char *zLine, InSource *pSource ){
- if( pSource->inFile!=0 ){
- return local_getline(zLine, pSource->inFile);
- }else if( pSource->zStrIn!=0
- && pSource->zStrIn[pSource->iReadOffset]!=0 ){
- /* This code mostly copied from local_getline(); It begs for refactor. */
- int nLine = zLine==0 ? 0 : 100;
- int n = 0;
-
- while( 1 ){
- if( n+100>nLine ){
- nLine = nLine*2 + 100;
- zLine = realloc(zLine, nLine);
- shell_check_oom(zLine);
- }
- {
- /* This block is where fgets() became "read string". */
- char *zBegin = pSource->zStrIn + pSource->iReadOffset;
- int iTake = 0;
- char c = zBegin[iTake];
- if( c==0 ){
- if( n==0 ){
- free(zLine);
- return 0;
- }
- zLine[n] = 0;
- break;
- }else{
- while( iTake<nLine-n-1 && c!=0 ){
- zLine[n+iTake++] = c;
- if( c=='\n' ) break;
- c = zBegin[iTake];
- }
- zLine[n+iTake] = 0;
- pSource->iReadOffset += iTake;
- }
- }
- /* Above block replaced this commented code: */
- /* if( fgets(&zLine[n], nLine - n, in)==0 ){ */
- /* if( n==0 ){ */
- /* free(zLine); */
- /* return 0; */
- /* } */
- /* zLine[n] = 0; */
- /* break; */
- /* } */
- while( zLine[n] ) n++;
- if( n>0 && zLine[n-1]=='\n' ){
- n--;
- if( n>0 && zLine[n-1]=='\r' ) n--;
- zLine[n] = 0;
- break;
- }
- }
- return zLine;
- }else{
- return 0;
- }
- }
-
- /*
--** Retrieve a single line of input text.
++** Retrieve a single line of input text from designated input source.
**
--** If in==0 then read from standard input and prompt before each line.
--** If isContinuation is true, then a continuation prompt is appropriate.
--** If isContinuation is zero, then the main prompt should be used.
++** If the input is interactive, then issue either the continuation prompt
++** or the main prompt, per isContinuation, before reading a line from
++** standard input. Otherwise, just read a line from the specified source.
**
** If zPrior is not NULL then it is a buffer from a prior call to this
** routine that can be reused.
** The result is stored in space obtained from malloc() and must either
** be freed by the caller or else passed back into this routine via the
** zPrior argument for reuse.
- ** has been passed in for resuse, then the caller need not free it.
+**
+** If this function is called until it returns NULL, and the prior return
++** has been passed in for resuse, then the caller need/must not free it.
+** Otherwise, (in case of an early termination of reading from the given
+** input), the caller is responsible for freeing a prior, non-NULL return.
+**
+** The trailing newline (or its ilk), if any, is trimmed.
++** The input line number is adjusted (via delegation or directly.)
*/
-static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
- char *zPrompt;
- char *zResult;
- if( in!=0 ){
- zResult = local_getline(zPrior, in);
+static char *one_input_line(InSource *pInSrc, char *zPrior, int isContinuation){
- char *zPrompt;
- char *zResult;
- if( pInSrc!=0 ){
- zResult = getline_from(zPrior, pInSrc);
++ if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
++ return local_getline(zPrior, pInSrc);
}else{
-- zPrompt = isContinuation ? continuePrompt : mainPrompt;
++ char *zPrompt = isContinuation ? continuePrompt : mainPrompt;
#if SHELL_USE_LOCAL_GETLINE
printf("%s", zPrompt);
fflush(stdout);
-- zResult = local_getline(zPrior, stdin);
++ return local_getline(zPrior, pInSrc);
#else
++ char *zResult;
free(zPrior);
zResult = shell_readline(zPrompt);
++ ++pInSrc->lineno;
if( zResult && *zResult ) shell_add_history(zResult);
++ return zResult;
#endif
}
-- return zResult;
}
char *zFake;
UNUSED_PARAMETER(nVal);
zName = (const char*)sqlite3_value_text(apVal[0]);
-- zFake = zName ? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
++ zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0;
if( zFake ){
sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake),
-1, sqlite3_free);
char zPrefix[100]; /* Graph prefix */
};
- /* Input source switching is done through one of these (defined below) */
- typedef struct InSource InSource;
-
+/* Selectively omit features with one PP variable. Value is true iff
+** either x is not defined or defined with 0 in bitnum bit position.
+*/
+#define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<<bitnum))) : !(x+0))
+
+/* Whether build will include extended input parsing option */
+#define SHEXT_PARSING_BIT 0
+#define SHELL_EXTENDED_PARSING \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
+/* Whether build will include dynamic dot-command extension */
+#define SHEXT_DYNCMDS_BIT 1
+#define SHELL_DYNAMIC_COMMANDS \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNCMDS_BIT)
+/* Whether build will include expansion of variables in dot-commands */
+#define SHEXT_VAREXP_BIT 2
+#define SHELL_VARIABLE_EXPANSION \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
+
+#define SHELL_ALL_EXTENSIONS \
+ (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNCMDS_BIT)+(1<<SHEXT_VAREXP_BIT)
+#if !defined(SHELL_OMIT_EXTENSIONS)
+# define SHELL_EXTENSIONS SHELL_ALL_EXTENSIONS
+#else
+# define SHELL_EXTENSIONS (~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS
+#endif
+
+/* Runtime test for shell extended parsing, given ShellState pointer */
+#if SHELL_EXTENDED_PARSING
+# define SHEXT_PARSING(pSS) (pSS->bExtendedDotCmds & (1<<SHEXT_PARSING_BIT) !=0)
+#else
+# define SHEXT_PARSING(pSS) 0
+#endif
+
+/* Runtime test for shell variable expansion, given ShellState pointer */
+#if SHELL_EXTENDED_PARSING
+# define SHEXT_VAREXP(pSS) (pSS->bExtendedDotCmds & (1<<SHEXT_VAREXP_BIT) !=0)
+#else
+# define SHEXT_VAREXP(pSS) 0
+#endif
+
+/* Parameters affecting columnar mode result display (defaulting together) */
+typedef struct ColModeOpts {
+ int iWrap; /* In columnar modes, wrap lines reaching this limit */
+ u8 bQuote; /* Quote results for .mode box and table */
+ u8 bWordWrap; /* In columnar modes, wrap at word boundaries */
+} ColModeOpts;
+#define ColModeOpts_default { 60, 0, 0 }
+#define ColModeOpts_default_qbox { 60, 1, 0 }
+
/*
** State information about the database connection is contained in an
** instance of the following structure.
u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
u8 bSafeMode; /* True to prohibit unsafe operations */
u8 bSafeModePersist; /* The long-term value of bSafeMode */
+ u8 bExtendedDotCmds; /* Bits set to enable various shell extensions */
+ ColModeOpts cmOpts; /* Option values affecting columnar mode output */
unsigned statsOn; /* True to display memory stats before each finalize */
+ u8 bQuote; /* Quote results for .mode box and table */
+ int iWrap; /* In columnar modes, wrap lines reaching this limit */
unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */
+ int inputNesting; /* Track nesting level of .read and other redirects */
int outCount; /* Revert to stdout when reaching zero */
int cnt; /* Number of records displayed so far */
-- int lineno; /* Line number of last line read from in */
int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
- FILE *in; /* Read commands from this stream */
+ InSource *pInSource; /* Read commands and SQL from this stream source */
FILE *out; /* Write results here */
FILE *traceOut; /* Output for sqlite3_trace() */
int nErr; /* Number of errors seen */
va_start(ap, zErrMsg);
zMsg = sqlite3_vmprintf(zErrMsg, ap);
va_end(ap);
-- raw_printf(stderr, "line %d: ", p->lineno);
++ raw_printf(stderr, "line %d: ", p->pInSource->lineno);
utf8_printf(stderr, "%s\n", zMsg);
exit(1);
}
zCode = sqlite3_mprintf("%.*s", len, zSql);
for(i=0; zCode[i]; i++){ if( IsSpace(zSql[i]) ) zCode[i] = ' '; }
if( iOffset<25 ){
-- zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here", zCode, iOffset, "");
++ zMsg = sqlite3_mprintf("\n %z\n %*s^--- error here",
++ zCode, iOffset, "");
}else{
-- zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^", zCode, iOffset-14, "");
++ zMsg = sqlite3_mprintf("\n %z\n %*serror here ---^",
++ zCode, iOffset-14, "");
}
return zMsg;
}
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace);
}
- /* Create the TEMP table used to store parameter bindings */
- static void bind_table_init(ShellState *p){
+/* DB schema protection for use by next two utility functions */
+typedef struct DbProtectState {
+ int wrSchema;
+ int defensiveMode;
+} DbProtectState;
+
+/* Allow system (sqlite_*) schema changes, return prior protection state. */
+static DbProtectState allow_sys_schema_change(sqlite3 *db){
+ DbProtectState rv;
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &rv.defensiveMode);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &rv.wrSchema);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
+ return rv;
+}
+
+/* Restore protection state using allow_sys_schema_change() return. */
+static void restore_sys_schema_protection( sqlite3 *db, DbProtectState *pPS ){
+ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, pPS->wrSchema, 0);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, pPS->defensiveMode, 0);
+}
+
-enum ParamTableUses { PTU_Binding = 0, PTU_Script = 1 };
+ /* Partition the temp.sqlite_parameters key namespace according to use. */
- int wrSchema = 0;
- int defensiveMode = 0;
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
++typedef enum { PTU_Binding = 0, PTU_Script, PTU_Source } ParamTableUse;
+ #define PARAM_TABLE_NAME "sqlite_parameters"
+ #define PARAM_TABLE_SCHEMA "temp"
+ #define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME
+
+ /* Create the TEMP table used to store parameter bindings and SQL statements */
+ static void param_table_init(ShellState *p){
+ DbProtectState dps = allow_sys_schema_change(p->db);
sqlite3_exec(p->db,
-- "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n"
++ "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n"
" key TEXT PRIMARY KEY,\n"
- " value\n"
+ " value,\n"
+ " uses INT DEFAULT (0)" /* aka PTU_Binding */
") WITHOUT ROWID;",
0, 0, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0);
+ restore_sys_schema_protection( p->db, &dps );
}
/*
nVar = sqlite3_bind_parameter_count(pStmt);
if( nVar==0 ) return; /* Nothing to do */
-- if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters",
++ if( sqlite3_table_column_metadata(pArg->db, PARAM_TABLE_SCHEMA,
++ PARAM_TABLE_NAME,
"key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
return; /* Parameter table does not exist */
}
rc = sqlite3_prepare_v2(pArg->db,
-- "SELECT value FROM temp.sqlite_parameters"
- " WHERE key=?1", -1, &pQ, 0);
++ "SELECT value FROM "PARAM_TABLE_SNAME
+ " WHERE key=?1 AND uses=0", -1, &pQ, 0);
if( rc || pQ==0 ) return;
+ assert( PTU_Binding==0 ); /* instead of working symbol value into query */
for(i=1; i<=nVar; i++){
char zNum[30];
const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
** |
** 3
**
--** Each box characters has between 2 and 4 of the lines leading from
++** Each box character has between 2 and 4 of the lines leading from
** the center. The characters are here identified by the numbers of
** their corresponding lines.
*/
" Options:",
" --indent Try to pretty-print the schema",
" --nosys Omit objects whose names start with \"sqlite_\"",
- ".script CMD ... Manage SQL statement store",
++ ".script CMD ... Manage and use SQL statement store (some TBD)",
+ " clear ?NAMES? Erase some or all stored SQL statements",
+ " edit NAME Use edit() to create or alter statement NAME",
+ " execute ?NAMES? Execute the named SQL statements in order",
+ " init Initialize the TEMP table that holds statements",
+ " list List the currently stored statements",
+ " load FILE ?NAMES? Load some or all SQL statements from FILE",
+ " save FILE ?NAMES? Save some or all SQL statements into FILE",
+ " set NAME VALUE Give SQL statement NAME a value of VALUE",
+ " NAME must start with a letter, and effective",
+ " value is space-joined, following argument list",
+ " unset ?NAMES? Remove NAMES from statement store",
".selftest ?OPTIONS? Run tests defined in the SELFTEST table",
" Options:",
" --init Create a new SELFTEST table",
".vfslist List all available VFSes",
".vfsname ?AUX? Print the name of the VFS stack",
".width NUM1 NUM2 ... Set minimum column widths for columnar output",
- " Negative values right-justify",
+ " Negative values right-justify",
+ ".x NAME ... Excecute content of some .param set variable(s)",
+ " Only variables whose name begins with a letter are eligible for this."
+};
+
+/* Like azHelp[], but for undocumented commands. */
+static const char *(azHelpUndoc[]) = {
+ "These undocumented commands are for internal testing.\n"
+ "They are subject to change without notice.\n"
+ ".check GLOB Fail if output since .testcase does not match",
+ ".seeargs Echo arguments separated or terminated by |",
+ ".selftest-boolean Check boolean translation",
+ ".selftest-integer Check integer conversion",
+ ".testcase NAME Begin output redirect to 'testcase-out.txt'",
+ ".testctrl CMD ... Run various sqlite3_test_control() operations",
+ " Run \".testctrl\" with no arguments for details",
+ ".x ?NAMES? Execute the named stored SQL statements in order",
+ " This is merely a shorter alias for .script execute ?NAMES?"
};
- /* This literal's value and address is used for help's workings. */
++/* This literal's value AND address are used for help's workings. */
+static const char *zHelpAll = "-all";
+
/*
** Output help text.
**
/*
** Reconstruct an in-memory database using the output from the "dbtotxt"
** program. Read content from the file in p->aAuxDb[].zDbFilename.
- ** If p->aAuxDb[].zDbFilename is 0, then read from either the present
- ** input file, or standard input if there is no file open for input.
-** If p->aAuxDb[].zDbFilename is 0, then read from standard input.
++** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
*/
static unsigned char *readHexDb(ShellState *p, int *pnData){
unsigned char *a = 0;
-- int nLine;
int n = 0;
int pgsz = 0;
int iOffset = 0;
-- int j, k;
++ int j, k, nlError;
int rc;
- FILE *in = 0;
- FILE *in;
++ static const char *zEndMarker = "| end ";
const char *zDbFilename = p->pAuxDb->zDbFilename;
++ /* Need next two objects only if redirecting input to get the hex. */
++ InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, p->pInSource);
unsigned int x[16];
char zLine[1000];
if( zDbFilename ){
-- in = fopen(zDbFilename, "r");
-- if( in==0 ){
++ inRedir.inFile = fopen(zDbFilename, "r");
++ if( inRedir.inFile==0 ){
utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
return 0;
}
-- nLine = 0;
++ p->pInSource = &inRedir;
}else{
- if( p->pInSource!=0 ) in = p->pInSource->inFile;
- in = p->in;
-- nLine = p->lineno;
-- if( in==0 ) in = stdin;
++ /* Will read hex DB lines inline from present input, without redirect. */
++ if( INSOURCE_IS_INTERACTIVE(p->pInSource) ){
++ printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
++ p->pInSource->zSourceSay, zEndMarker);
++ fflush(stdout);
++ }
}
*pnData = 0;
-- nLine++;
-- if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
++ if( str_line_get(zLine,sizeof(zLine), p->pInSource)==0 ) goto readHexDb_error;
rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
if( rc!=2 ) goto readHexDb_error;
if( n<0 ) goto readHexDb_error;
utf8_printf(stderr, "invalid pagesize\n");
goto readHexDb_error;
}
-- for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
++ while( str_line_get(zLine,sizeof(zLine), p->pInSource)!=0 ){
rc = sscanf(zLine, "| page %d offset %d", &j, &k);
if( rc==2 ){
iOffset = k;
continue;
}
-- if( strncmp(zLine, "| end ", 6)==0 ){
++ if( strncmp(zLine, zEndMarker, 6)==0 ){
break;
}
rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
}
}
}
-- *pnData = n;
- if( zDbFilename ) fclose(in);
- else p->lineno = nLine;
- if( in!=p->in ){
- fclose(in);
- }else{
- p->lineno = nLine;
++ *pnData = n; /* Record success and size. */
++ readHexDb_cleanup:
++ if( p->pInSource==&inRedir ){
++ fclose( inRedir.inFile );
++ p->pInSource = inRedir.pFrom;
+ }
return a;
--readHexDb_error:
- if( zDbFilename ){
- if( in!=p->in ){
-- fclose(in);
-- }else{
- int nLinePass = nLine;
- while( fgets(zLine, sizeof(zLine), in)!=0 ){
- nLinePass++;
- while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
- nLine++;
-- if(strncmp(zLine, "| end ", 6)==0 ) break;
++ readHexDb_error:
++ nlError = p->pInSource->lineno;
++ if( p->pInSource!=&inRedir ){
++ /* Since taking input inline, consume through its end marker. */
++ while( str_line_get(zLine, sizeof(zLine), p->pInSource)!=0 ){
++ if(strncmp(zLine, zEndMarker, 6)==0 ) break;
}
- p->lineno = nLinePass;
- p->lineno = nLine;
}
sqlite3_free(a);
-- utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
-- return 0;
++ a = 0;
++ utf8_printf(stderr,"Error on line %d within --hexdb input\n", nlError);
++ goto readHexDb_cleanup;
}
#endif /* SQLITE_OMIT_DESERIALIZE */
for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
}
if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
- if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
- return 1;
- }
- if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
- return 0;
+ for( i=0; zBoolNames[i]!=0; ++i ){
+ if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
}
utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
-- zArg);
++ zArg);
return 0;
}
/*
** Create a prepared statement using printf-style arguments for the SQL.
**
--** This routine is could be marked "static". But it is not always used,
++** This routine could be marked "static". But it is not always used,
** depending on compile-time options. By omitting the "static", we avoid
** nuisance compiler warnings about "defined but not used".
*/
}
#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
- if( sqlite3_table_column_metadata(p->db, "TEMP", "sqlite_parameters",
++/*
++** The .shxopts command, for setting or listing shell extension options.
++ */
+#if SHELL_EXTENSIONS
+static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){
+ static struct { const char *name; u8 mask; } shopts[] = {
+#if SHELL_DYNAMIC_COMMANDS
+ {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
+#endif
+#if SHELL_EXTENDED_PARSING
+ {"parsing", 1<<SHEXT_PARSING_BIT},
+#endif
+#if SHELL_VARIABLE_EXPANSION
+ {"dot_vars", 1<<SHEXT_VAREXP_BIT},
+#endif
+ {"all_opts", SHELL_ALL_EXTENSIONS}
+ };
+ const char *zMoan = 0, *zAbout = 0;
+ int ia, io;
+ if( nArg>1 ){
+ for( ia=1; ia<nArg; ++ia ){
+ char cs = azArg[ia][0];
+ if( cs!='+' && cs!='-' ){
+ zMoan = "arguments must have a sign prefix.";
+ zAbout = azArg[0];
+ goto moan_error;
+ }
+ for( io=0; io<ArraySize(shopts); ++io ){
+ if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
+ if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
+ else p->bExtendedDotCmds &= ~shopts[io].mask;
+ break;
+ }
+ }
+ if( io==ArraySize(shopts) ){
+ zAbout = azArg[ia];
+ zMoan = "is not a recognized option name";
+ goto moan_error;
+ }
+ }
+ }else{
+ raw_printf(p->out,
+ " name value \"-shxopts set\"\n"
+ " -------- ----- ---------------\n");
+ for( io=0; io<ArraySize(shopts); ++io ){
+ unsigned m = shopts[io].mask;
+ unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
+ raw_printf(p->out,
+ " %9s %2d \"-shxopts 0x%02X\"\n",
+ shopts[io].name, v, m);
+ }
+ }
+ return 0;
+ moan_error:
+ raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan);
+ return 1;
+}
+#endif
+
+static int execute_variables(char *azArg[], int nArg, ShellState *p){
+ int ia, rc, nErrors = 0;
+ sqlite3_stmt *pStmt = 0;
+ open_db(p, 0);
+ if( p->db==0 ){
+ utf8_printf(stderr, ".x can only be done with a database open.\n");
+ return 1;
+ }
- utf8_printf(stderr, "No temp.sqlite_parameters table exists.\n");
++ if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
++ PARAM_TABLE_NAME,
+ "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
- (p->db, "SELECT value FROM temp.sqlite_parameters WHERE key=$1",
++ utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n");
+ return 1;
+ }
+ rc = sqlite3_prepare_v2
- utf8_printf(stderr, "temp.sqlite_parameters is wrongly created.\n");
++ (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
++ " WHERE key=$1 AND uses=1",
+ -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
- * because it or some other character is likely to be put in its
- * place during process_input() line or group handling, along
++ utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n");
+ return 1;
+ }
+ for( ia=1; ia < nArg; ++ia ){
+ if( isalpha(azArg[ia][0]) ){
+ rc = sqlite3_reset(pStmt);
+ rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_ROW ){
+ const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
+ int nb = sqlite3_column_bytes(pStmt, 0);
+ while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
+ if( nb>0 ){
+ /* The trailing newline (or some other placeholder) is important
- * land past the end of the allocation made at this next line.
++ * because one (or some other character) will likely be put in
++ * its place during process_input() line/group handling, along
+ * with a terminating NUL character. Without it, the NULL could
- InSource inSourceDivert =
- {0, zSubmit, 0, azArg[ia], p->pInSource };
- InSource *pInSrcSave = p->pInSource;
- int savedLineno = p->lineno;
++ * land past the end of the allocation made just below.
+ */
+ int nle = zValue[nb-1]=='\n';
+ char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
- p->pInSource = &inSourceDivert;
++ InSource inRedir
++ = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
+ shell_check_oom(zSubmit);
- p->pInSource = pInSrcSave;
- p->lineno = savedLineno;
++ p->pInSource = &inRedir;
+ rc = process_input(p);
+ sqlite3_free(zSubmit);
-/* Edit one named parameter in the parameters table. If it does not
++ p->pInSource = inRedir.pFrom;
+ }else{
+ continue; /* All white, ignore. */
+ }
+ }else{
+ utf8_printf(stderr,
+ "Skipping parameter '%s' (not set.)\n", azArg[ia]);
+ ++nErrors;
+ }
+ }else{
+ utf8_printf(stderr,
+ "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
+ ++nErrors;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ return (rc==2)? 2 : nErrors>0;
+}
+
+ struct param_row { char * value; int uses; int hits; };
+
+ static int param_find_callback(void *pData, int nc, char **pV, char **pC){
+ assert(nc>=1);
+ assert(strcmp(pC[0],"value")==0);
+ struct param_row *pParam = (struct param_row *)pData;
+ assert(pParam->value==0); /* key values are supposedly unique. */
+ if( pParam->value!=0 ) sqlite3_free( pParam->value );
+ pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */
+ if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
+ ++pParam->hits;
+ return 0;
+ }
-
++
++/*
++ * Edit one named parameter in the parameters table. If it does not
+ * yet exist, create it. If eval is true, the value is treated as a
+ * bare expression, otherwise it is a text value. The uses argument
+ * sets the 3rd column in the parameters table, and may also serve
+ * to partition the key namespace. (This is not done now.)
+ */
+ static int edit_one_param(sqlite3 *db, char *name, int eval,
+ int uses, const char * zEditor){
+ struct param_row paramVU = {0,0,0};
+ char * zSql = sqlite3_mprintf
+ ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name);
+ shell_check_oom(zSql);
+ sqlite3_exec(db, zSql, param_find_callback, ¶mVU, 0);
+ sqlite3_free(zSql);
+ assert(paramVU.hits<2);
+ if( eval!=0 ){
+ /* ToDo: Run an eval-like update, leaving as-is if it's a bad expr. */
+ // int rv = edit_one_param(db, name, 0, uses);
+ }
+ if( paramVU.hits==1 && paramVU.uses==uses){
+ /* Editing an existing value of same kind. */
+ sqlite3_free(paramVU.value);
+ zSql = sqlite3_mprintf
+ ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE"
+ " key=%Q AND uses=%d", zEditor, name, uses);
+ }else{
+ /* Editing a new value of same kind. */
+ assert(paramVU.value==0);
+ zSql = sqlite3_mprintf
+ ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+ " VALUES (%Q,edit('-- %q%s', %Q),%d)",
+ name, name, "\n", zEditor, uses);
+ }
+ shell_check_oom(zSql);
+ sqlite3_exec(db, zSql, 0, 0, 0);
+ sqlite3_free(zSql);
+ return 0; /* ToDo: add some error checks */
+ }
+
+ static void append_in_clause(sqlite3_str *pStr, char **azBeg, char **azLim){
+ /* An empty IN list is the same as always true (for non-NULL LHS)
+ * for this clause, which assumes a trailing LHS operand and space.
+ * If that is not the right result, guard the call against it.
+ * This is used for .parameter and .script ?NAMES? options,
+ * where a missing list means all the qualifying entries.
+ */
+ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
+ else{
+ char cSep = '(';
+ sqlite3_str_appendf(pStr, "IN");
+ while( azBeg<azLim ){
+ sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
+ cSep = ',';
+ ++azBeg;
+ }
+ sqlite3_str_appendf(pStr, ")");
+ }
+ }
+
/*
** If an input line begins with "." then invoke this routine to
** process that line.
rc = 1;
}else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n",
-- p->lineno, azArg[1]);
++ p->pInSource->lineno, azArg[1]);
exit(1);
}else{
p->bSafeMode = 0;
}else
if( (c=='o'
-- && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
-- || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
++ && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
++ || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
){
char *zFile = 0;
int bTxtMode = 0;
int len = 0;
rx = sqlite3_prepare_v2(p->db,
"SELECT max(length(key)) "
-- "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
++ "FROM "PARAM_TABLE_SNAME, -1, &pStmt, 0);
if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
len = sqlite3_column_int(pStmt, 0);
if( len>40 ) len = 40;
pStmt = 0;
if( len ){
rx = sqlite3_prepare_v2(p->db,
-- "SELECT key, quote(value) "
-- "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
++ "SELECT key, quote(value), uses "
++ "FROM "PARAM_TABLE_SNAME, -1, &pStmt, 0);
while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-- utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
-- sqlite3_column_text(pStmt,1));
++ utf8_printf(p->out, "%-*s %s %d\n", len,
++ sqlite3_column_text(pStmt,0),
++ sqlite3_column_text(pStmt,1),
++ sqlite3_column_int(pStmt,2));
}
sqlite3_finalize(pStmt);
}
sqlite3_stmt *pStmt;
const char *zKey = azArg[2];
const char *zValue = azArg[3];
- bind_table_init(p);
++ u8 ptu = isalpha(*zKey)? PTU_Script : PTU_Binding;
+ param_table_init(p);
zSql = sqlite3_mprintf(
-- "REPLACE INTO temp.sqlite_parameters(key,value)"
-- "VALUES(%Q,%s);", zKey, zValue);
++ "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ "VALUES(%Q,%s,%d);", zKey, zValue, ptu);
shell_check_oom(zSql);
pStmt = 0;
rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_finalize(pStmt);
pStmt = 0;
zSql = sqlite3_mprintf(
-- "REPLACE INTO temp.sqlite_parameters(key,value)"
-- "VALUES(%Q,%Q);", zKey, zValue);
++ "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ "VALUES(%Q,%Q,%d);", zKey, zValue, ptu);
shell_check_oom(zSql);
rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
*/
if( nArg==3 && strcmp(azArg[1],"unset")==0 ){
char *zSql = sqlite3_mprintf(
-- "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]);
++ "DELETE FROM "PARAM_TABLE_SNAME" WHERE key=%Q", azArg[2]);
shell_check_oom(zSql);
sqlite3_exec(p->db, zSql, 0, 0, 0);
sqlite3_free(zSql);
utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
rc = 1;
}else{
- InSource *pInSrcSave = p->pInSource;
- int savedLineno = p->lineno;
- InSource inSourceDivert =
- {inUse, 0, 0, azArg[1], p->pInSource};
- p->pInSource = &inSourceDivert;
+ fCloser = fclose;
+ }
+ if( inUse!=0 ){
++ InSource inSourceRedir
++ = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource);
++ p->pInSource = &inSourceRedir;
rc = process_input(p);
- fclose(p->in);
+ assert(fCloser!=0);
+ fCloser(inUse);
- p->pInSource = pInSrcSave;
- p->lineno = savedLineno;
++ p->pInSource = p->pInSource->pFrom;
}
- p->in = inSaved;
- p->lineno = savedLineno;
}else
if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){
}else if( zName==0 ){
zName = azArg[ii];
}else{
-- raw_printf(stderr, "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
++ raw_printf(stderr,
++ "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
rc = 1;
goto meta_command_exit;
}
#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
if( zName ){
appendText(&sSelect,
-- " UNION ALL SELECT shell_module_schema(name),"
-- " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list",
-- 0);
++ " UNION ALL SELECT shell_module_schema(name),"
++ " 'table', name, name, name, 9e+99, 'main'"
++ " FROM pragma_module_list", 0);
}
#endif
appendText(&sSelect, ") WHERE ", 0);
}
}else
+ if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){
+ for( n=1; n<nArg; ++n ){
+ raw_printf(p->out, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|");
+ }
+ }else
+
if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){
-- unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
++ unsigned int x
++ = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
}else
}
}
if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
-- raw_printf(stderr, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
++ raw_printf(stderr, "Maximum of %d sessions\n",
++ ArraySize(pAuxDb->aSession));
goto meta_command_exit;
}
pSession = &pAuxDb->aSession[pAuxDb->nSession];
}else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
utf8_printf(stderr,
"line %d: \".testctrl %s\" may not be used in safe mode\n",
-- p->lineno, aCtrl[iCtrl].zCtrlName);
++ p->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
exit(1);
}else{
switch(testctrl){
}else
if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){
-- unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
++ unsigned int x
++ = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
}else
if( p->outCount==0 ) output_reset(p);
}
p->bSafeMode = p->bSafeModePersist;
- /* Free any arguments that had to be allocated rather than tokenized in place. */
+#if SHELL_VARIABLE_EXPANSION
+ if( bExpVars ){
++ /* Free any arguments that are allocated rather than tokenized in place. */
+ for( n=1; n<nArg; ++n ){
+ int iArgOffset = azArg[n]-zLine;
+ u8 bInPlace = iArgOffset>0 && iArgOffset<ncLineIn;
+ if( !bInPlace ) sqlite3_free(azArg[n]);
+ }
+ }
+#endif
return rc;
}
return 0;
}
-
-/*
-** Read input from *in and process it. If *in==0 then input
-** is interactive - the user is typing it it. Otherwise, input
-** 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.
+#if SHELL_EXTENDED_PARSING
+/* Resumable line classsifier for dot-commands
**
-** Return the number of errors.
-*/
-static int process_input(ShellState *p){
- char *zLine = 0; /* A single input line */
- char *zSql = 0; /* Accumulated SQL text */
- int nLine; /* Length of current line */
- int nSql = 0; /* Bytes of zSql[] used */
- int nAlloc = 0; /* Allocated zSql[] space */
- int rc; /* Error code */
- int errCnt = 0; /* Number of errors seen */
- int startline = 0; /* Line number for start of current input */
- QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
-
- p->lineno = 0;
- while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
- fflush(p->out);
- zLine = one_input_line(p->in, zLine, nSql>0);
- if( zLine==0 ){
- /* End of input */
- if( p->in==0 && stdin_is_interactive ) printf("\n");
- break;
- }
- if( seenInterrupt ){
- if( p->in!=0 ) break;
- seenInterrupt = 0;
- }
- p->lineno++;
- if( QSS_INPLAIN(qss)
- && line_is_command_terminator(zLine)
- && line_is_complete(zSql, nSql) ){
- memcpy(zLine,";",2);
- }
- qss = quickscan(zLine, qss);
- if( QSS_PLAINWHITE(qss) && nSql==0 ){
- if( ShellHasFlag(p, SHFLG_Echo) )
- printf("%s\n", zLine);
- /* Just swallow single-line whitespace */
- qss = QSS_Start;
+** Determines if a dot-command is open, having either an unclosed
+** quoted argument or an escape sequence opener ('\') at its end.
+**
+** The FSM design/behavior assumes/requires that a terminating '\'
+** is not part of the character sequence being classified -- that
+** it represents an escaped newline which is removed as physical
+** lines are spliced to accumulate logical lines.
+**
+** The line or added line-portion is passed as zCmd.
+** The pScanState pointer must reference an (opaque) DCmd_ScanState,
+** which must be set to DCSS_Start to initialize the scanner state.
+** Resumed scanning should always be done with zCmd logically just
+** past the last non-0 char of the text previously passed in, with
+** any previously scanned, trailing newline escape first trimmed.
+** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
+** The following macros may be applied to the scan state:
+*/
+#define DCSS_InDarkArg(dcss) ((dcss)&argPosMask==inDqArg)
+#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
+#define DCSS_IsOpen(dcss) (((dcss) & isOpenMask)!=0)
+typedef enum {
+ DCSS_Start = 0,
+ twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
+ endEscaped = 4, /* bit used */
+ argPosMask = 3, /* bits used */
+ isOpenMask = 1|4 /* bit test */
+} DCmd_ScanState;
+
+static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState){
+ DCmd_ScanState ss = *pScanState & ~endEscaped;
+ char c = (ss&isOpenMask)? 1 : *zCmd++;
+ while( c!=0 ){
+ switch( ss ){
+ twixt:
+ case twixtArgs:
+ while( IsSpace(c) ){
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ switch( c ){
+ case '\\':
+ if( *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }else goto inDark;
+ case '\'': ss = inSqArg; goto inSq;
+ case '"': ss = inDqArg; goto inDq;
+ default: ss = inDarkArg; goto inDark;
+ }
+ inSq:
+ case inSqArg:
+ while( (c=*zCmd++)!='\'' ){
+ if( c==0 ) goto atEnd;
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ }
+ ss = twixtArgs;
+ c = *zCmd++;
continue;
- }
- if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
- if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zLine);
- if( zLine[0]=='.' ){
- rc = do_meta_command(zLine, p);
- if( rc==2 ){ /* exit requested */
- break;
- }else if( rc ){
- errCnt++;
+ inDq:
+ case inDqArg:
+ do {
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ if( c=='\\' ){
+ if( (c=*zCmd++)==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ if( (c=*zCmd++)==0 ) goto atEnd;
}
+ } while( c!='"' );
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ inDark:
+ case inDarkArg:
+ while( !IsSpace(c) ){
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ if( (c=*zCmd++)==0 ) goto atEnd;
}
- qss = QSS_Start;
+ ss = twixtArgs;
+ c = *zCmd++;
continue;
}
- /* No single-line dispositions remain; accumulate line(s). */
- nLine = strlen30(zLine);
- if( nSql+nLine+2>=nAlloc ){
- /* Grow buffer by half-again increments when big. */
- nAlloc = nSql+(nSql>>1)+nLine+100;
- zSql = realloc(zSql, nAlloc);
- shell_check_oom(zSql);
+ }
+ atEnd:
+ *pScanState = ss;
+}
+#else
+# define dot_command_scan(x,y)
+#endif
+
+/* Utility functions for process_input. */
+
+#if SHELL_EXTENDED_PARSING
+/*
+** Process dot-command line with its scan state to:
+** 1. Setup for requested line-splicing; and
+** 2. Say whether it is complete.
+** The last two out parameters are the line's length, which may be
+** adjusted, and the char to be used for joining a subsequent line.
+** This is broken out of process_input() mainly for readability.
+** The return is TRUE for dot-command ready to run, else false.
+*/
+static int line_join_ends(DCmd_ScanState dcss, char *zLine,
+ int *pnLength, char *pcLE){
+ /* It is ready only if has no open argument or escaped newline. */
+ int bOpen = DCSS_IsOpen(dcss);
+ if( !DCSS_EndEscaped(dcss) ){
+ *pcLE = '\n';
+ return !bOpen;
+ }else{
+ *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
+ /* Swallow the trailing escape character. */
+ zLine[--*pnLength] = 0;
+ return 0;
+ }
+}
+#endif
+
+/*
+** Grow the accumulation line buffer to accommodate ncNeed chars.
+** In/out parameters pz and pna reference the buffer and its size.
+** The buffer must eventually be sqlite3_free()'ed by the caller.
+*/
+static void grow_line_buffer(char **pz, int *pna, int ncNeed){
+ if( ncNeed > *pna ){
+ *pna += *pna + (*pna>>1) + 100;
+ *pz = sqlite3_realloc(*pz, *pna);
+ shell_check_oom(*pz);
+ }
+}
+
+/*
+** Read input from designated source (p->pInSource) and process it.
+** If pInSource==0 then input is interactive - the user is typing it.
+** Otherwise, input is coming from a file, stream device or string.
+** Prompts issue and history is saved only for interactive input.
+** An interrupt signal will cause this routine to exit immediately,
+** with "exit demanded" code returned, unless input is interactive.
+**
+** Returns:
+** 0 => no errors
+** 1 => errors>0
+** 2 => exit demanded, no errors.
+** 3 => exit demanded, errors>0
+*/
+static int process_input(ShellState *p){
+ char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */
+ char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */
+ /* Above two pointers could be local to the group handling loop, but are
+ * not so that the number of memory allocations can be reduced. They are
+ * reused from one incoming group to another, realloc()'ed as needed. */
+ int naAccum = 0; /* tracking how big zLineAccum buffer has become */
+ /* Some flags for ending the overall group processing loop, always 1 or 0 */
+ u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0;
+ /* Flag to affect prompting and interrupt action */
- u8 bInteractive = (p->pInSource==0 && stdin_is_interactive);
++ u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive);
+ int nErrors = 0; /* count of errors during execution or its prep */
+
+ /* Block overly-recursive or absurdly nested input redirects. */
+ if( p->inputNesting==MAX_INPUT_NESTING ){
- InSource *pInSrc = p->pInSource;
- int i;
- utf8_printf
- (stderr, "Input nesting limit (%d) reached, from line %d of \"%s\",\n",
- MAX_INPUT_NESTING, p->lineno, pInSrc->zSourceSay);
- for( i=0; i<3 && (pInSrc=pInSrc->pFrom)!=0; ++i )
- utf8_printf(stderr, " from \"%s\"", pInSrc->zSourceSay);
++ InSource *pInSrc = p->pInSource->pFrom;
++ const char *zLead = "Input nesting limit ("
++ SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
++ int i = 3;
++ assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
++ while( i-->0 && pInSrc!=0 ){
++ utf8_printf(stderr,
++ "%s from line %d of \"%s\"",
++ zLead, pInSrc->lineno, pInSrc->zSourceSay);
++ zLead = (i%2==0)? "\n" : "";
++ pInSrc=pInSrc->pFrom;
+ }
- if( nSql==0 ){
- int i;
- for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
- assert( nAlloc>0 && zSql!=0 );
- memcpy(zSql, zLine+i, nLine+1-i);
- startline = p->lineno;
- nSql = nLine-i;
+ utf8_printf(stderr, " ...\nERROR: Check recursion.\n");
+ return 1;
+ }
+ ++p->inputNesting;
- p->lineno = 0;
+
+ /* line-group processing loop (per SQL block, dot-command or comment) */
+ while( !bErrorBail && !bInputEnd && !bExitDemand && !bInterrupted ){
+ int nGroupLines = 0; /* count of lines belonging to this group */
+ int ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */
+ int ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */
+ int iLastLine = 0; /* index of last accumulated line start */
+ /* Initialize resumable scanner(s). */
+ SqlScanState sqScanState = SSS_Start; /* for SQL scan */
+#if SHELL_EXTENDED_PARSING
+ DCmd_ScanState dcScanState = DCSS_Start; /* for dot-command scan */
+ int ndcLeadWhite = 0; /* for skip over initial whitespace to . or # */
+ char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
+#else
+# define ndcLeadWhite 0 /* For legacy parsing, no white before . or # . */
+# define cLineEnd '\n' /* For legacy parsing, this always joins lines. */
+#endif
+ /* An ordered enum to record kind of incoming line group. Its ordering
+ * means than a value greater than Comment implies something runnable.
+ */
+ enum { Tbd = 0, Eof, Comment, Sql, Cmd /*, Tcl */ } inKind = Tbd;
+ /* An enum signifying the group disposition state */
+ enum {
+ Incoming, Runnable, Dumpable, Erroneous, Ignore
+ } disposition = Incoming;
+ char **pzLineUse = &zLineInput; /* ref line to be processed */
+ int *pncLineUse = &ncLineIn; /* ref that line's char count */
+ int iStartline = 0; /* starting line number of group */
+
+ fflush(p->out);
+ zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
+ inKind = Eof;
+ disposition = Ignore;
+ if( bInteractive ) printf("\n");
}else{
- zSql[nSql++] = '\n';
- memcpy(zSql+nSql, zLine, nLine+1);
- nSql += nLine;
- }
- if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- nSql = 0;
- if( p->outCount ){
- output_reset(p);
- p->outCount = 0;
- }else{
- clearTempFile(p);
+ ++nGroupLines;
- p->lineno++;
- iStartline = p->lineno;
++ iStartline = p->pInSource->lineno;
+ ncLineIn = strlen30(zLineInput);
+ if( seenInterrupt ){
+ if( p->pInSource!=0 ) break;
+ bInterrupted = 1; /* This will be honored, or not, later. */
+ seenInterrupt = 0;
+ disposition = Dumpable;
+ }
+ /* Classify and check for single-line dispositions, prep for more. */
+#if SHELL_EXTENDED_PARSING
+ ndcLeadWhite = (SHEXT_PARSING(p))
+ ? skipWhite(zLineInput)-zLineInput
+ : 0; /* Disallow leading whitespace for . or # in legacy mode. */
+#endif
+ switch( zLineInput[ndcLeadWhite] ){
+ case '.':
+ inKind = Cmd;
+ dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState);
+ break;
+ case '#':
+ inKind = Comment;
+ break;
+ default:
+ /* Might be SQL, or a swallowable whole SQL comment. */
+ sql_prescan(zLineInput, &sqScanState);
+ if( SSS_PLAINWHITE(sqScanState) ){
+ /* It's either all blank or a whole SQL comment. Swallowable. */
+ inKind = Comment;
+ }else{
+ /* Something dark, not a # comment or dot-command. Must be SQL. */
+ inKind = Sql;
+ }
+ break;
+ } /* end classification switch */
+ } /* end read/classify initial group input line */
+
+ /* Here, if not at end of input, the initial line of group is in, and
+ * it has been scanned and classified. Next, do the processing needed
+ * to recognize whether the initial line or accumulated group so far
+ * is complete such that it may be run, and perform joining of more
+ * lines into the group while it is not so complete. This loop ends
+ * with the input group line(s) ready to be run, or if the input ends
+ * before it is ready, with the group marked as erroneous.
+ */
+ while( disposition==Incoming ){
+ /* Check whether more to accumulate, or ready for final disposition. */
+ switch( inKind ){
+ case Comment:
+ disposition = Dumpable;
+ case Cmd:
+#if SHELL_EXTENDED_PARSING
+ if( SHEXT_PARSING(p) ){
+ if( line_join_ends(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
+ disposition = Runnable;
+ }
+ }else
+#endif
+ disposition = Runnable; /* Legacy, any dot-command line is ready. */
+ break;
+ case Sql:
+ /* Check to see if it is complete and ready to run. */
+ if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){
+ disposition = Runnable;
+ }else if( SSS_PLAINWHITE(sqScanState) ){
+ /* It is a leading single-line or multi-line comment. */
+ disposition = Runnable;
+ inKind = Comment;
+ }else{
+ char *zT = line_is_command_terminator(zLineInput);
+ if( zT!=0 ){
+ /* Last line is a lone go or / -- prep for running it. */
+ if( nGroupLines>1 ){
+ disposition = Runnable;
+ memcpy(*pzLineUse+iLastLine,";\n",3);
+ *pncLineUse = iLastLine + 2;
+ }else{
+ /* Unless nothing preceded it, then dump it. */
+ disposition = Dumpable;
+ }
+ }
+ }
+ break;
+ } /* end switch on inKind */
+ /* Collect and accumulate more input if group not yet complete. */
+ if( disposition==Incoming ){
+ if( nGroupLines==1 ){
+ grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
+ /* Copy line just input */
+ memcpy(zLineAccum, zLineInput, ncLineIn);
+ ncLineAcc = ncLineIn;
+ pzLineUse = &zLineAccum;
+ pncLineUse = &ncLineAcc;
+ }
+ /* Read in next line of group, (if available.) */
+ zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
+ inKind = Eof;
+ disposition = Erroneous;
+ if( bInteractive ) printf("\n");
+ continue;
+ }
+ ++nGroupLines;
- p->lineno++;
+ ncLineIn = strlen30(zLineInput);
+ /* Scan line just input (if needed) and append to accumulation. */
+ switch( inKind ){
+ case Cmd:
+ dot_command_scan(zLineInput, &dcScanState);
+ break;
+ case Sql:
+ sql_prescan(zLineInput, &sqScanState);
+ break;
+ default:
+ break;
+ }
+ grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
+ /* Join lines as setup by exam of previous line(s). */
+ if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
+ cLineEnd = '\n'; /* reset to default after use */
+ memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
+ iLastLine = ncLineAcc;
+ ncLineAcc += ncLineIn;
+ zLineAccum[ncLineAcc] = 0;
+ } /* end glom another line */
+ } /* end group collection loop */
+ /* Here, the group is fully collected or known to be incomplete forever. */
+ switch( disposition ){
+ case Dumpable:
+ echo_group_input(p, *pzLineUse);
+ break;
+ case Runnable:
+ switch( inKind ){
+ case Sql:
+ /* runOneSqlLine() does its own echo when requested. */
- nErrors += runOneSqlLine(p, *pzLineUse, p->pInSource!=0, iStartline);
++ nErrors += runOneSqlLine(p, *pzLineUse,
++ INSOURCE_IS_INTERACTIVE(p->pInSource),
++ iStartline);
+ if( bail_on_error && nErrors>0 ) bErrorBail = 1;
+ break;
+ case Cmd:
+ echo_group_input(p, *pzLineUse);
+ switch( do_meta_command(*pzLineUse, p) ){
+ default: ++nErrors; /* fall thru */
+ case 0: break;
+ case 2: bExitDemand = 1; break;
+ }
+ break;
+ default:
+ assert(inKind!=Tbd);
+ break;
}
- p->bSafeMode = p->bSafeModePersist;
- qss = QSS_Start;
- }else if( nSql && QSS_PLAINWHITE(qss) ){
- if( ShellHasFlag(p, SHFLG_Echo) ) printf("%s\n", zSql);
- nSql = 0;
- qss = QSS_Start;
+ break;
+ case Erroneous:
+ utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n",
- p->lineno,
- (p->pInSource!=0)? p->pInSource->zSourceSay : "<stdin>");
++ p->pInSource->lineno, p->pInSource->zSourceSay);
+ ++nErrors;
+ break;
+ case Ignore:
+ break;
+ default: assert(0);
}
- }
- if( nSql && QSS_PLAINDARK(qss) ){
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- }
- free(zSql);
- free(zLine);
- return errCnt>0;
+ if( nErrors>0 && bail_on_error ) bErrorBail = 1;
+ } /* end group consume/prep/(run, dump or complain) loop */
+
+ /* Cleanup and determine return value based on flags and error count. */
+ free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
+ sqlite3_free(zLineAccum);
+
+ return ((bErrorBail | bExitDemand)<<1) + (nErrors>0);
}
/*
shell_check_oom(zBuf);
sqliterc = zBuf;
}
- p->in = fopen(sqliterc,"rb");
- if( p->in ){
+ inUse = fopen(sqliterc,"rb");
+ if( inUse!=0 ){
- InSource *pInSrcSave = p->pInSource;
- int savedLineno = p->lineno;
- InSource inSourceDivert = {inUse, 0, 0, sqliterc, p->pInSource};
++ InSource inSourceRedir
++ = INSOURCE_FILE_REDIR(inUse, sqliterc, p->pInSource);
+ int rc;
- p->pInSource = &inSourceDivert;
++ p->pInSource = &inSourceRedir;
if( stdin_is_interactive ){
utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc);
}
- if( process_input(p) && bail_on_error ) exit(1);
- fclose(p->in);
+ rc = process_input(p);
+ fclose(inUse);
- p->pInSource = pInSrcSave;
- p->lineno = savedLineno;
++ p->pInSource = inSourceRedir.pFrom; /* 0 when called by main() */
+ if( rc!=0 && bail_on_error ) exit(1);
}else if( sqliterc_override!=0 ){
utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc);
if( bail_on_error ) exit(1);
sqlite3_config(SQLITE_CONFIG_URI, 1);
sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
-- sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
++ sqlite3_snprintf(sizeof(mainPrompt), mainPrompt, "sqlite> ");
sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> ");
++ init_std_inputs(stdin);
}
/*
/* Process the initialization file if there is one. If no -init option
** is given on the command line, look for a file named ~/.sqliterc and
-- ** try to process it.
++ ** try to process it, without any quitting or bail-on-error.
*/
process_sqliterc(&data,zInitFile);
}else if( strcmp(z,"-ascii")==0 ){
data.mode = MODE_Ascii;
sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Unit);
-- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Record);
++ sqlite3_snprintf(sizeof(data.rowSeparator),data.rowSeparator, SEP_Record);
}else if( strcmp(z,"-tabs")==0 ){
data.mode = MODE_List;
sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Tab);
#elif HAVE_LINENOISE
linenoiseSetCompletionCallback(linenoise_completion);
#endif
- data.pInSource = 0; /* read from stdin interactively */
- data.in = 0;
++ data.pInSource = &termInSource; /* read from stdin interactively */
rc = process_input(&data);
if( zHistory ){
shell_stifle_history(2000);
free(zHistory);
}
}else{
- data.pInSource = &inputSource; /* read from stdin without prompts */
- data.in = stdin;
++ data.pInSource = &stdInSource; /* read from stdin without prompts */
rc = process_input(&data);
}
}