*/
static int stdout_is_console = 1;
++/*
++** This statically allocated variable is used to strip a resource
++** stack upon abrupt exits (involving OOM or -safe mode violations.
++*/
++static ResourceMark main_resource_mark = 0;
++
++/* Avoid redundant atexit() registration. */
++static u8 atexit_registered = 0;
++
/*
** The following is the open SQLite database. We make a pointer
** to this database a static variable so that it can be accessed
** by the SIGINT handler to interrupt database processing.
*/
static sqlite3 *globalDb = 0;
+/*
+** Mutex used to access *globalDb from main thread or ^C handler.
+*/
+static sqlite3_mutex *pGlobalDbLock = 0;
+
+
/*
-** True if an interrupt (Control-C) has been received.
+** Greater than 0 if an interrupt (Control-C) has been received.
*/
static volatile int seenInterrupt = 0;
# define raw_printf fprintf
#endif
-/* Indicate out-of-memory and exit. */
-static void shell_out_of_memory(void){
- raw_printf(stderr,"Error: out of memory\n");
+/*
- ** Provide a way for embedding apps to handle OOM condition their way.
- ** This is crude, and using it will undoubtedly leak memory and handles.
- ** The alternative is extensive recoding of the shell, (not for today.)
- ** This could become a longjump(...) (paired with app's setjump(...).)
++** Provide a way for embedding apps to handle OOM or other shell-fatal
++** conditions their way. A plain exit() call will undoubtedly leak
++** memory and handles. The default scheme, when SHELL_TERMINATE is
++** not defined, uses setjmp()/longjmp() execution stack ripping
++** together with a resource stack which can be popped to release
++** resources automatically. One replacement could be:
++# define SHELL_TERMINATE(why) \
++ fprintf(stderr, "Error: Terminating due to %s\n", why);\
+ exit(1);
+*/
- #ifndef SHELL_OOM_EXIT
- # define SHELL_OOM_EXIT exit(1)
++
++/*
++** Provide an abrupt exit/termination for routines that cannot proceed
++** or whose failure means the CLI cannot usefully continue.
++*/
++static void shell_terminate(const char *zWhy){
++#ifndef SHELL_TERMINATE
++ quit_moan(zWhy, 2);
++#else
++ holder_free(exit_mark);
++ SHELL_TERMINATE(zWhy);
+#endif
- /* Indicate out-of-memory and exit. */
++}
++
++/* Indicate out-of-memory and terminate. */
+static void shell_out_of_memory(void){
- raw_printf(STD_ERR,"Error: out of memory\n");
- sqlite3_mutex_free(pGlobalDbLock);
- pGlobalDbLock = 0;
- SHELL_OOM_EXIT;
++ shell_terminate("out of memory");
}
--/* Check a pointer to see if it is NULL. If it is NULL, exit with an
++/* Check a pointer to see if it is NULL. If so, terminate with an
** out-of-memory error.
*/
static void shell_check_oom(const void *p){
}
/*
-** 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 strLineGet(...) 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 )
-
++#define INSOURCE_IS_INVOKEARG(pIS) \
++ ((pIS)==&cmdInSource)
+/* This instance's address is taken as part of interactive input test. */
+static InSource termInSource = { 0, 0, 0, 0, "<terminal>", 0};
+static InSource stdInSource = { 0, 0, 0, 0, "<stdin>", 0};
+static InSource cmdInSource = { 0, 0, 0, 0, "<cmdLine>", 0};
+static void init_std_inputs(FILE *pIn ){
+ termInSource.inFile = pIn;
+ stdInSource.inFile = pIn;
+ cmdInSource.lineno = 0;
+}
+static void set_invocation_cmd(char *zDo){
+ cmdInSource.iReadOffset = 0;
+ cmdInSource.zStrIn = zDo;
+ ++cmdInSource.lineno;
+}
+
+static char *strLineGet(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.
**
** If the third argument, quote, is not '\0', then it is used as a
** quote character for zAppend.
++**
++** This routine can abruptly exit under OOM conditions.
*/
static void appendText(ShellText *p, const char *zAppend, char quote){
i64 len;
char cQuote;
char *zDiv = "(";
int nRow = 0;
-
- zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;",
- zSchema ? zSchema : "main", zName);
+ int rc;
+ char *rv = 0;
+ ResourceMark rm_mark = holder_mark();
+ char *zSql = smprintf("PRAGMA \"%w\".table_info=%Q;",
+ zSchema ? zSchema : "main", zName);
shell_check_oom(zSql);
- sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
+ if( rc==SQLITE_NOMEM ) shell_out_of_memory();
+ stmt_holder(pStmt);
initText(&s);
- text_holder(&s);
++ text_ref_holder(&s);
if( zSchema ){
cQuote = quoteChar(zSchema);
if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0;
/*
** If in safe mode, print an error message described by the arguments
- ** and "exit" without returning to the caller. This "exit" will occur
- ** immediately, as a process exit, for normal shell builds. When the
- ** shell is built with options to use it embedded, control returns to
- ** the caller of the shell's main, "do shell things" entry point.
-** and exit immediately.
++** and arrange for a semi-abrupt exit. Unsafe actions are blocked
++** by their doers calling this and not acting if 1 is returned.
++** The process_input() routine detects the semi-abrupt exit, (which
++** is indicated by the external shell state member, shellAbruptExit
++** being set non-zero), and aborts any further input processing.
+**
- ** It is an error, (perhaps with only minor effect such as memory leak),
- ** for a dot-command to call this function while it holds resources in
- ** need of freeing. Instead, it should be called before acquiring them.
- **
- ** The return is true if failing, 0 otherwise.
++** The return is 1 if failing for a forbidded unsafe act, 0 otherwise.
*/
-static void failIfSafeMode(
- ShellState *p,
+static int failIfSafeMode(
+ ShellExState *psx,
const char *zErrMsg,
...
){
if( argc==2 ){
zEditor = (const char*)sqlite3_value_text(argv[1]);
+ /* If that failed for OOM, just pretend it is not there. */
}else{
zEditor = getenv("VISUAL");
- /* Note that this code NOT threadsafe due to use of the getenv()
++ /* Note that this code is NOT threadsafe due to use of the getenv()
+ ** result. Because the shell is single-threaded, this is fine.
+ ** But if the CLI is called as a subroutine in a multi-threaded
+ ** program, adjustments may have to be made. */
}
if( zEditor==0 ){
sqlite3_result_error(context, "no editor for edit()", -1);
}
#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( pArg->expert.pExpert ){
- rc = expertHandleSQL(pArg, zSql, pzErrMsg);
- return expertFinish(pArg, (rc!=SQLITE_OK), pzErrMsg);
+ if( psi->expert.pExpert ){
+ rc = expertHandleSQL(psi, zSql, pzErrMsg);
+ return expertFinish(psi, (rc!=SQLITE_OK), pzErrMsg);
}
#endif
- stmt_holder(pStmt); /* offset 0 */
- stmt_holder(pExplain); /* offset 1 */
- sstr_holder(zEQP); /* offset 2 */
-
++ stmt_ptr_holder(&pStmt); /* offset 0 */
++ stmt_ptr_holder(&pExplain); /* offset 1 */
++ sstr_ptr_holder(&zEQP); /* offset 2 */
while( zSql[0] && (SQLITE_OK == rc) ){
static const char *zStmtSql;
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
if( SQLITE_OK != rc ){
+ if( rc==SQLITE_NOMEM ) shell_out_of_memory();
if( pzErrMsg ){
-- *pzErrMsg = save_err_msg(db, "in prepare", rc, zSql);
- shell_check_oom(*pzErrMsg);
++ shell_check_oom(*pzErrMsg = save_err_msg(db, "in prepare", rc, zSql));
}
}else{
if( !pStmt ){
/* this happens for a comment or white-space */
- zSql = zLeftover;
- while( IsSpace(zSql[0]) ) zSql++;
+ zSql = skipWhite(zLeftover);
continue;
}
- swap_held(mark, 0, pStmt);
zStmtSql = sqlite3_sql(pStmt);
if( zStmtSql==0 ) zStmtSql = "";
- while( IsSpace(zStmtSql[0]) ) zStmtSql++;
+ else zStmtSql = skipWhite(zStmtSql);
/* save off the prepared statment handle and reset row count */
- if( pArg ){
- pArg->pStmt = pStmt;
- pArg->cnt = 0;
+ if( psx ){
+ psi->pStmt = pStmt;
+ psx->resultCount = 0;
}
/* Show the EXPLAIN QUERY PLAN if .eqp is on */
int triggerEQP = 0;
disable_debug_trace_modes();
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP);
- if( pArg->autoEQP>=AUTOEQP_trigger ){
+ if( psi->autoEQP>=AUTOEQP_trigger ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 1, 0);
}
- zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
+ zEQP = smprintf("EXPLAIN QUERY PLAN %s", zStmtSql);
shell_check_oom(zEQP);
- swap_held(mark, 2, zEQP);
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
if( rc==SQLITE_OK ){
- swap_held(mark, 1, pExplain);
while( sqlite3_step(pExplain)==SQLITE_ROW ){
const char *zEQPLine = (const char*)sqlite3_column_text(pExplain,3);
int iEqpId = sqlite3_column_int(pExplain, 0);
int iParentId = sqlite3_column_int(pExplain, 1);
if( zEQPLine==0 ) zEQPLine = "";
- if( zEQPLine[0]=='-' ) eqp_render(pArg, 0);
- eqp_append(pArg, iEqpId, iParentId, zEQPLine);
+ if( zEQPLine[0]=='-' ) eqp_render(psi, 0);
+ eqp_append(psi, iEqpId, iParentId, zEQPLine);
}
- eqp_render(pArg, 0);
+ eqp_render(psi, 0);
}
sqlite3_finalize(pExplain);
- swap_held(mark, 1, 0);
++ pExplain = 0;
sqlite3_free(zEQP);
- swap_held(mark, 2, 0);
- if( pArg->autoEQP>=AUTOEQP_full ){
++ zEQP = 0;
+ if( psi->autoEQP>=AUTOEQP_full ){
/* Also do an EXPLAIN for ".eqp full" mode */
- zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql);
+ zEQP = smprintf("EXPLAIN %s", zStmtSql);
shell_check_oom(zEQP);
- swap_held(mark, 2, zEQP);
rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
if( rc==SQLITE_OK ){
- swap_held(mark, 1, pExplain);
- pArg->cMode = MODE_Explain;
- explain_data_prepare(pArg, pExplain);
- exec_prepared_stmt(pArg, pExplain);
- explain_data_delete(pArg);
+ explain_data_prepare(psi, pExplain);
+ psi->cMode = MODE_Explain;
+#if SHELL_DATAIO_EXT
+ {
+ ExportHandler *pexSave = psi->pActiveExporter;
+ psi->pActiveExporter = psi->pFreeformExporter;
+ exec_prepared_stmt(psx, pExplain);
+ psi->pActiveExporter = pexSave;
+ }
+#else
+ exec_prepared_stmt(psx, pExplain);
+#endif
+ explain_data_delete(psi);
}
sqlite3_finalize(pExplain);
- swap_held(mark, 1, 0);
++ pExplain = 0;
sqlite3_free(zEQP);
- swap_held(mark, 2, 0);
++ zEQP = 0;
}
- if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
+ if( psi->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
/* Reprepare pStmt before reactiving trace modes */
sqlite3_finalize(pStmt);
++ pStmt = 0;
sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- swap_held(mark, 0, pStmt);
- if( pArg ) pArg->pStmt = pStmt;
+ if( psx ) psi->pStmt = pStmt;
}
restore_debug_trace_modes();
}
** copy of the error message. Otherwise, set zSql to point to the
** next statement to execute. */
rc2 = sqlite3_finalize(pStmt);
- swap_held(mark, 0, 0);
++ pStmt = 0;
if( rc!=SQLITE_NOMEM ) rc = rc2;
if( rc==SQLITE_OK ){
- zSql = zLeftover;
- while( IsSpace(zSql[0]) ) zSql++;
+ zSql = skipWhite(zLeftover);
}else if( pzErrMsg ){
-- *pzErrMsg = save_err_msg(db, "stepping", rc, 0);
++ shell_check_oom(*pzErrMsg = save_err_msg(db, "stepping", rc, 0));
}
/* clear saved stmt handle */
*/
static void freeColumnList(char **azCol){
int i;
-- for(i=1; azCol[i]; i++){
-- sqlite3_free(azCol[i]);
++ if( azCol!=0 ){
++ for(i=1; azCol[i]; i++){
++ sqlite3_free(azCol[i]);
++ }
++ /* azCol[0] is a static string */
++ sqlite3_free(azCol);
}
-- /* azCol[0] is a static string */
-- sqlite3_free(azCol);
}
/*
** name of the rowid column.
**
** The first regular column in the table is azCol[1]. The list is terminated
--** by an entry with azCol[i]==0.
++** by an entry with azCol[i]==0 for i>0. This is an invariant, maintained
++** as the list is grown so that freeColumnList() can always deal with it.
++**
++** This function can exit abruptly under OOM conditions.
*/
-static char **tableColumnList(ShellState *p, const char *zTab){
+static char **tableColumnList(sqlite3 *db, const char *zTab, int preserveRowid){
char **azCol = 0;
sqlite3_stmt *pStmt;
char *zSql;
int nAlloc = 0;
int nPK = 0; /* Number of PRIMARY KEY columns seen */
int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */
- int preserveRowid = ShellHasFlag(p, SHFLG_PreserveRowid);
++ ResourceMark mark = holder_mark();
++ AnyResourceHolder arh = { 0, (GenericFreer)freeColumnList };
int rc;
- zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab);
+ zSql = smprintf("PRAGMA table_info=%Q", zTab);
shell_check_oom(zSql);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc ) return 0;
++ stmt_holder(pStmt); /* offset 0 */
++ any_ref_holder(&arh); /* offset 1 */
while( sqlite3_step(pStmt)==SQLITE_ROW ){
if( nCol>=nAlloc-2 ){
++ int nAllocPrev = nAlloc;
nAlloc = nAlloc*2 + nCol + 10;
azCol = sqlite3_realloc(azCol, nAlloc*sizeof(azCol[0]));
shell_check_oom(azCol);
++ memset(azCol+nAllocPrev, 0, (nAlloc-nAllocPrev)*sizeof(azCol[0]));
++ arh.pAny = azCol;
}
- azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
+ azCol[++nCol] = smprintf("%s", sqlite3_column_text(pStmt, 1));
shell_check_oom(azCol[nCol]);
if( sqlite3_column_int(pStmt, 5) ){
nPK++;
}
}
}
-- sqlite3_finalize(pStmt);
++ swap_held(mark, 1, 0); /* Now that it's built, save it from takedown. */
++ holder_free(mark);
if( azCol==0 ) return 0;
-- azCol[0] = 0;
-- azCol[nCol+1] = 0;
++ any_ref_holder(&arh); /* offset 0 */
++ /* azCol[0] = 0; azCol[nCol+1] = 0; -- Done by memset() above. */
/* The decision of whether or not a rowid really needs to be preserved
** is tricky. We never need to preserve a rowid for a WITHOUT ROWID table
** there is a "pk" entry in "PRAGMA index_list". There will be
** no "pk" index if the PRIMARY KEY really is an alias for the ROWID.
*/
- zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)"
- " WHERE origin='pk'", zTab);
+ zSql = smprintf("SELECT 1 FROM pragma_index_list(%Q)"
+ " WHERE origin='pk'", zTab);
shell_check_oom(zSql);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
sqlite3_free(zSql);
if( rc ){
-- freeColumnList(azCol);
++ holder_free(mark);
return 0;
}
++ stmt_holder(pStmt);
rc = sqlite3_step(pStmt);
-- sqlite3_finalize(pStmt);
preserveRowid = rc==SQLITE_ROW;
}
if( preserveRowid ){
}
}
}
++ swap_held(mark, 0, 0); /* Save built list from takedown (again.) */
++ holder_free(mark);
return azCol;
}
const char *zTable;
const char *zType;
const char *zSql;
- ShellState *p = (ShellState *)pArg;
+ ShellInState *psi = (ShellInState *)pArg;
+ ShellExState *psx = XSS(psi);
int dataOnly;
int noSys;
++ ResourceMark mark = holder_mark();
UNUSED_PARAMETER(azNotUsed);
if( nArg!=3 || azArg==0 ) return 0;
}
if( cli_strcmp(zType, "table")==0 ){
++ AnyResourceHolder arh = { 0, (GenericFreer)freeColumnList };
ShellText sSelect;
ShellText sTable;
char **azCol;
int i;
- char *savedDestTable;
+ const char *savedDestTable;
int savedMode;
+ int preserveRowid = (psi->shellFlgs & SHFLG_PreserveRowid)!=0;
- azCol = tableColumnList(p, zTable);
+ azCol = tableColumnList(DBI(psi), zTable, preserveRowid);
if( azCol==0 ){
- p->nErr++;
+ psi->nErr++;
return 0;
}
++ /* Hereafter, resource management is used (for a column list.) */
++ arh.pAny = azCol;
++ any_ref_holder(&arh);
/* Always quote the table name, even if it appears to be pure ascii,
** in case it is a keyword. Ex: INSERT INTO "table" ... */
initText(&sTable);
++ text_ref_holder(&sTable);
appendText(&sTable, zTable, quoteChar(zTable));
/* If preserving the rowid, add a column list after the table name.
** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)"
/* Build an appropriate SELECT statement */
initText(&sSelect);
++ text_ref_holder(&sSelect);
appendText(&sSelect, "SELECT ", 0);
if( azCol[0] ){
appendText(&sSelect, azCol[0], 0);
appendText(&sSelect, ",", 0);
}
}
-- freeColumnList(azCol);
appendText(&sSelect, " FROM ", 0);
appendText(&sSelect, zTable, quoteChar(zTable));
- savedDestTable = p->zDestTable;
- savedMode = p->mode;
- p->zDestTable = sTable.z;
- p->mode = p->cMode = MODE_Insert;
- rc = shell_exec(p, sSelect.z, 0);
+ savedDestTable = psx->zDestTable;
+ savedMode = psi->mode;
+ psx->zDestTable = sTable.z;
+ psi->mode = psi->cMode = MODE_Insert;
+ rc = shell_exec(psx, sSelect.z, 0);
if( (rc&0xff)==SQLITE_CORRUPT ){
- raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n");
- toggleSelectOrder(p->db);
- shell_exec(p, sSelect.z, 0);
- toggleSelectOrder(p->db);
- }
- p->zDestTable = savedDestTable;
- p->mode = savedMode;
- freeText(&sTable);
- freeText(&sSelect);
- if( rc ) p->nErr++;
+ raw_printf(psi->out, "/****** CORRUPTION ERROR *******/\n");
+ toggleSelectOrder(DBX(psx));
+ shell_exec(psx, sSelect.z, 0);
+ toggleSelectOrder(DBX(psx));
+ }
+ psx->zDestTable = savedDestTable;
+ psi->mode = savedMode;
- freeText(&sTable);
- freeText(&sSelect);
+ if( rc ) psi->nErr++;
++ holder_free(mark);
}
return 0;
}
const char *zQuery
){
int rc;
++ ResourceMark mark = holder_mark();
char *zErr = 0;
- rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr);
++
++ sstr_ptr_holder(&zErr);
+ rc = sqlite3_exec(DBI(psi), zQuery, dump_callback, psi, &zErr);
if( rc==SQLITE_CORRUPT ){
char *zQ2;
int len = strlen30(zQuery);
sqlite3_free(zErr);
zErr = 0;
}
-- zQ2 = malloc( len+100 );
-- if( zQ2==0 ) return rc;
++ shell_check_oom(zQ2 = malloc( len+100 ));
++ mmem_holder(zQ2);
sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
- rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
+ rc = sqlite3_exec(DBI(psi), zQ2, dump_callback, psi, &zErr);
if( rc ){
- utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr);
+ utf8_printf(psi->out, "/****** ERROR: %s ******/\n", zErr);
}else{
rc = SQLITE_CORRUPT;
}
-- sqlite3_free(zErr);
-- free(zQ2);
}
++ bail:
++ holder_free(mark);
return rc;
}
+/* Configure help text generation to have coalesced secondary help lines
+ * with trailing newlines on all help lines. This allow help text to be
+ * representable as an array of two C-strings per dot-command.
+ */
+DISPATCH_CONFIG[
+ HELP_COALESCE=1
+];
+#define HELP_TEXT_FMTP ".%s"
+#define HELP_TEXT_FMTS "%s"
+/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
+ * Alternative is 0, ".%s\n" and "%s\n" .
+ */
+
+/* Forward references */
+static int showHelp(FILE *out, const char *zPattern, ShellExState *);
+static DotCmdRC process_input(ShellInState *psx);
+static DotCommand *builtInCommand(int ix);
+
/*
-** Text of help messages.
+** Read the content of file zName into memory obtained from sqlite3_malloc64()
+** and return a pointer to the buffer. The caller is responsible for freeing
+** the memory.
+**
+** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
+** read.
**
-** The help text for each individual command begins with a line that starts
-** with ".". Subsequent lines are supplemental information.
+** For convenience, a nul-terminator byte is always appended to the data read
+** from the file before the buffer is returned. This byte is not included in
+** the final value of (*pnByte), if applicable.
+**
+** NULL is returned if any error is encountered. The final value of *pnByte
+** is undefined in this case.
+ **
-** There must be two or more spaces between the end of the command and the
-** start of the description of what that command does.
++** This function always returns; no abrubt OOM exits are taken.
*/
-static const char *(azHelp[]) = {
-#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
- && !defined(SQLITE_SHELL_FIDDLE)
- ".archive ... Manage SQL archives",
- " Each command must have exactly one of the following options:",
- " -c, --create Create a new archive",
- " -u, --update Add or update files with changed mtime",
- " -i, --insert Like -u but always add even if unchanged",
- " -r, --remove Remove files from archive",
- " -t, --list List contents of archive",
- " -x, --extract Extract files from archive",
- " Optional arguments:",
- " -v, --verbose Print each filename as it is processed",
- " -f FILE, --file FILE Use archive FILE (default is current db)",
- " -a FILE, --append FILE Open FILE using the apndvfs VFS",
- " -C DIR, --directory DIR Read/extract files from directory DIR",
- " -g, --glob Use glob matching for names in archive",
- " -n, --dryrun Show the SQL that would have occurred",
- " Examples:",
- " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar",
- " .ar -tf ARCHIVE # List members of ARCHIVE",
- " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE",
- " See also:",
- " http://sqlite.org/cli.html#sqlite_archive_support",
-#endif
-#ifndef SQLITE_OMIT_AUTHORIZATION
- ".auth ON|OFF Show authorizer callbacks",
-#endif
-#ifndef SQLITE_SHELL_FIDDLE
- ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
- " Options:",
- " --append Use the appendvfs",
- " --async Write to FILE without journal and fsync()",
-#endif
- ".bail on|off Stop after hitting an error. Default OFF",
- ".binary on|off Turn binary output on or off. Default OFF",
-#ifndef SQLITE_SHELL_FIDDLE
- ".cd DIRECTORY Change the working directory to DIRECTORY",
+static char *readFile(const char *zName, int *pnByte){
+ FILE *in = fopen(zName, "rb");
+ long nIn;
+ size_t nRead;
+ char *pBuf;
+ int rc;
+ if( in==0 ) return 0;
+ rc = fseek(in, 0, SEEK_END);
+ if( rc!=0 ){
+ raw_printf(stderr, "Error: '%s' not seekable\n", zName);
+ fclose(in);
+ return 0;
+ }
+ nIn = ftell(in);
+ rewind(in);
+ pBuf = sqlite3_malloc64( nIn+1 );
+ if( pBuf==0 ){
+ raw_printf(stderr, "Error: out of memory\n");
+ fclose(in);
+ return 0;
+ }
+ nRead = fread(pBuf, nIn, 1, in);
+ fclose(in);
+ if( nRead!=1 ){
+ sqlite3_free(pBuf);
+ raw_printf(stderr, "Error: cannot read '%s'\n", zName);
+ return 0;
+ }
+ pBuf[nIn] = 0;
+ if( pnByte ) *pnByte = nIn;
+ return pBuf;
+}
+
+#if defined(SQLITE_ENABLE_SESSION)
+/*
+** Close a single OpenSession object and release all of its associated
+** resources.
+*/
+static void session_close(OpenSession *pSession){
+ int i;
+ sqlite3session_delete(pSession->p);
+ sqlite3_free(pSession->zName);
+ for(i=0; i<pSession->nFilter; i++){
+ sqlite3_free(pSession->azFilter[i]);
+ }
+ sqlite3_free(pSession->azFilter);
+ memset(pSession, 0, sizeof(OpenSession));
+}
#endif
- ".changes on|off Show number of rows changed by SQL",
-#ifndef SQLITE_SHELL_FIDDLE
- ".check GLOB Fail if output since .testcase does not match",
- ".clone NEWDB Clone data into NEWDB from the existing database",
-#endif
- ".connection [close] [#] Open or close an auxiliary database connection",
- ".databases List names and files of attached databases",
- ".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
-#if SQLITE_SHELL_HAVE_RECOVER
- ".dbinfo ?DB? Show status information about the database",
-#endif
- ".dump ?OBJECTS? Render database content as SQL",
- " Options:",
- " --data-only Output only INSERT statements",
- " --newlines Allow unescaped newline characters in output",
- " --nosys Omit system tables (ex: \"sqlite_stat1\")",
- " --preserve-rowids Include ROWID values in the output",
- " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
- " Additional LIKE patterns can be given in subsequent arguments",
- ".echo on|off Turn command echo on or off",
- ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
- " Other Modes:",
-#ifdef SQLITE_DEBUG
- " test Show raw EXPLAIN QUERY PLAN output",
- " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
-#endif
- " trigger Like \"full\" but also show trigger bytecode",
-#ifndef SQLITE_SHELL_FIDDLE
- ".excel Display the output of next command in spreadsheet",
- " --bom Put a UTF8 byte-order mark on intermediate file",
-#endif
-#ifndef SQLITE_SHELL_FIDDLE
- ".exit ?CODE? Exit this program with return-code CODE",
-#endif
- ".expert EXPERIMENTAL. Suggest indexes for queries",
- ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
- ".filectrl CMD ... Run various sqlite3_file_control() operations",
- " --schema SCHEMA Use SCHEMA instead of \"main\"",
- " --help Show CMD details",
- ".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
- ".headers on|off Turn display of headers on or off",
- ".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",
-#endif
- ".indexes ?TABLE? Show names of indexes",
- " If TABLE is specified, only show indexes for",
- " tables matching TABLE using the LIKE operator.",
-#ifdef SQLITE_ENABLE_IOTRACE
- ",iotrace FILE Enable I/O diagnostic logging to FILE",
-#endif
- ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT",
- ".lint OPTIONS Report potential schema issues.",
- " Options:",
- " fkey-indexes Find missing foreign key indexes",
-#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
- ".load FILE ?ENTRY? Load an extension library",
-#endif
-#if !defined(SQLITE_SHELL_FIDDLE)
- ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout",
+
+/*
+** Close all OpenSession objects and release all associated resources.
+*/
+#if defined(SQLITE_ENABLE_SESSION)
+static void session_close_all(ShellInState *psi, int i){
+ int j;
+ struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
+ for(j=0; j<pAuxDb->nSession; j++){
+ session_close(&pAuxDb->aSession[j]);
+ }
+ pAuxDb->nSession = 0;
+}
#else
- ".log on|off Turn logging on or off.",
-#endif
- ".mode MODE ?OPTIONS? Set output mode",
- " MODE is one of:",
- " ascii Columns/rows delimited by 0x1F and 0x1E",
- " box Tables using unicode box-drawing characters",
- " csv Comma-separated values",
- " column Output in columns. (See .width)",
- " html HTML <table> code",
- " insert SQL insert statements for TABLE",
- " json Results in a JSON array",
- " line One value per line",
- " list Values delimited by \"|\"",
- " markdown Markdown table format",
- " qbox Shorthand for \"box --wrap 60 --quote\"",
- " quote Escape answers as for SQL",
- " table ASCII-art table",
- " tabs Tab-separated values",
- " tcl TCL list elements",
- " OPTIONS: (for columnar modes or insert mode):",
- " --wrap N Wrap output lines to no longer than N characters",
- " --wordwrap B Wrap or not at word boundaries per B (on/off)",
- " --ww Shorthand for \"--wordwrap 1\"",
- " --quote Quote output text as SQL literals",
- " --noquote Do not quote output text",
- " TABLE The name of SQL table used for \"insert\" mode",
-#ifndef SQLITE_SHELL_FIDDLE
- ".nonce STRING Suspend safe mode for one command if nonce matches",
-#endif
- ".nullvalue STRING Use STRING in place of NULL values",
-#ifndef SQLITE_SHELL_FIDDLE
- ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
- " If FILE begins with '|' then open as a pipe",
- " --bom Put a UTF8 byte-order mark at the beginning",
- " -e Send output to the system text editor",
- " -x Send output as CSV to a spreadsheet (same as \".excel\")",
- /* Note that .open is (partially) available in WASM builds but is
- ** currently only intended to be used by the fiddle tool, not
- ** end users, so is "undocumented." */
- ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
- " Options:",
- " --append Use appendvfs to append database to the end of FILE",
-#endif
-#ifndef SQLITE_OMIT_DESERIALIZE
- " --deserialize Load into memory using sqlite3_deserialize()",
- " --hexdb Load the output of \"dbtotxt\" as an in-memory db",
- " --maxsize N Maximum size for --hexdb or --deserialized database",
-#endif
- " --new Initialize FILE to an empty database",
- " --nofollow Do not follow symbolic links",
- " --readonly Open FILE readonly",
- " --zip FILE is a ZIP archive",
-#ifndef SQLITE_SHELL_FIDDLE
- ".output ?FILE? Send output to FILE or stdout if FILE is omitted",
- " If FILE begins with '|' then open it as a pipe.",
- " Options:",
- " --bom Prefix output with a UTF8 byte-order mark",
- " -e Send output to the system text editor",
- " -x Send output as CSV to a spreadsheet",
-#endif
- ".parameter CMD ... Manage SQL parameter bindings",
- " clear Erase all bindings",
- " init Initialize the TEMP table that holds bindings",
- " list List the current parameter bindings",
- " set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE",
- " PARAMETER should start with one of: $ : @ ?",
- " unset PARAMETER Remove PARAMETER from the binding table",
- ".print STRING... Print literal STRING",
-#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
- ".progress N Invoke progress handler after every N opcodes",
- " --limit N Interrupt after N progress callbacks",
- " --once Do no more than one progress interrupt",
- " --quiet|-q No output except at interrupts",
- " --reset Reset the count for each input and interrupt",
-#endif
- ".prompt MAIN CONTINUE Replace the standard prompts",
-#ifndef SQLITE_SHELL_FIDDLE
- ".quit Stop interpreting input stream, exit if primary.",
- ".read FILE Read input from FILE or command output",
- " If FILE begins with \"|\", it is a command that generates the input.",
-#endif
-#if SQLITE_SHELL_HAVE_RECOVER
- ".recover Recover as much data as possible from corrupt db.",
- " --ignore-freelist Ignore pages that appear to be on db freelist",
- " --lost-and-found TABLE Alternative name for the lost-and-found table",
- " --no-rowids Do not attempt to recover rowid values",
- " that are not also INTEGER PRIMARY KEYs",
-#endif
-#ifndef SQLITE_SHELL_FIDDLE
- ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
- ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)",
+# define session_close_all(X,Y)
#endif
- ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off",
- ".schema ?PATTERN? Show the CREATE statements matching PATTERN",
- " Options:",
- " --indent Try to pretty-print the schema",
- " --nosys Omit objects whose names start with \"sqlite_\"",
- ",selftest ?OPTIONS? Run tests defined in the SELFTEST table",
- " Options:",
- " --init Create a new SELFTEST table",
- " -v Verbose output",
- ".separator COL ?ROW? Change the column and row separators",
+
+/*
+** Implementation of the xFilter function for an open session. Omit
+** any tables named by ".session filter" but let all other table through.
+*/
#if defined(SQLITE_ENABLE_SESSION)
- ".session ?NAME? CMD ... Create or control sessions",
- " Subcommands:",
- " attach TABLE Attach TABLE",
- " changeset FILE Write a changeset into FILE",
- " close Close one session",
- " enable ?BOOLEAN? Set or query the enable bit",
- " filter GLOB... Reject tables matching GLOBs",
- " indirect ?BOOLEAN? Mark or query the indirect status",
- " isempty Query whether the session is empty",
- " list List currently open session names",
- " open DB NAME Open a new session on DB",
- " patchset FILE Write a patchset into FILE",
- " If ?NAME? is omitted, the first defined session is used.",
-#endif
- ".sha3sum ... Compute a SHA3 hash of database content",
- " Options:",
- " --schema Also hash the sqlite_schema table",
- " --sha3-224 Use the sha3-224 algorithm",
- " --sha3-256 Use the sha3-256 algorithm (default)",
- " --sha3-384 Use the sha3-384 algorithm",
- " --sha3-512 Use the sha3-512 algorithm",
- " Any other argument is a LIKE pattern for tables to hash",
-#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
- ".shell CMD ARGS... Run CMD ARGS... in a system shell",
-#endif
- ".show Show the current values for various settings",
- ".stats ?ARG? Show stats or turn stats on or off",
- " off Turn off automatic stat display",
- " on Turn on automatic stat display",
- " stmt Show statement stats",
- " vmstep Show the virtual machine step count only",
-#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
- ".system CMD ARGS... Run CMD ARGS... in a system shell",
-#endif
- ".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
-#ifndef SQLITE_SHELL_FIDDLE
- ",testcase NAME Begin redirecting output to 'testcase-out.txt'",
-#endif
- ",testctrl CMD ... Run various sqlite3_test_control() operations",
- " Run \".testctrl\" with no arguments for details",
- ".timeout MS Try opening locked tables for MS milliseconds",
- ".timer on|off Turn SQL timer on or off",
-#ifndef SQLITE_OMIT_TRACE
- ".trace ?OPTIONS? Output each SQL statement as it is run",
- " FILE Send output to FILE",
- " stdout Send output to stdout",
- " stderr Send output to stderr",
- " off Disable tracing",
- " --expanded Expand query parameters",
-#ifdef SQLITE_ENABLE_NORMALIZE
- " --normalized Normal the SQL statements",
+static int session_filter(void *pCtx, const char *zTab){
+ OpenSession *pSession = (OpenSession*)pCtx;
+ int i;
+ for(i=0; i<pSession->nFilter; i++){
+ if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
+ }
+ return 1;
+}
#endif
- " --plain Show SQL as it is input",
- " --stmt Trace statement execution (SQLITE_TRACE_STMT)",
- " --profile Profile statements (SQLITE_TRACE_PROFILE)",
- " --row Trace each row (SQLITE_TRACE_ROW)",
- " --close Trace connection close (SQLITE_TRACE_CLOSE)",
-#endif /* SQLITE_OMIT_TRACE */
-#ifdef SQLITE_DEBUG
- ".unmodule NAME ... Unregister virtual table modules",
- " --allexcept Unregister everything except those named",
+
+#if SHELL_DYNAMIC_EXTENSION
+static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
+ int six = 0;
+ int rcFlags = 0;
+ ShellExState *psx = XSS(psi);
+ while( six < psi->numSubscriptions ){
+ struct EventSubscription *pes = psi->pSubscriptions + six++;
+ rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
+ }
+ return rcFlags;
+}
#endif
- ".version Show source, library and compiler versions",
- ".vfsinfo ?AUX? Information about the top-level VFS",
- ".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",
-};
/*
-** Output help text.
-**
-** zPattern describes the set of commands for which help text is provided.
-** If zPattern is NULL, then show all commands, but only give a one-line
-** description of each.
+** Try to deduce the type of file for zName based on its content. Return
+** one of the SHELL_OPEN_* constants.
**
-** Return the number of matches.
+** If the file does not exist or is empty but its name looks like a ZIP
+** archive and the dfltZip flag is true, then assume it is a ZIP archive.
+** Otherwise, assume an ordinary database regardless of the filename if
+** the type cannot be determined from content.
*/
-static int showHelp(FILE *out, const char *zPattern){
- int i = 0;
- int j = 0;
- int n = 0;
- char *zPat;
- if( zPattern==0
- || zPattern[0]=='0'
- || cli_strcmp(zPattern,"-a")==0
- || cli_strcmp(zPattern,"-all")==0
- || cli_strcmp(zPattern,"--all")==0
- ){
- enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 };
- enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 };
- /* Show all or most commands
- ** *zPattern==0 => summary of documented commands only
- ** *zPattern=='0' => whole help for undocumented commands
- ** Otherwise => whole help for documented commands
- */
- enum HelpWanted hw = HW_SummaryOnly;
- enum HelpHave hh = HH_More;
- if( zPattern!=0 ){
- hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull;
- }
- for(i=0; i<ArraySize(azHelp); i++){
- switch( azHelp[i][0] ){
- case ',':
- hh = HH_Summary|HH_Undoc;
- break;
- case '.':
- hh = HH_Summary;
- break;
- default:
- hh &= ~HH_Summary;
- break;
- }
- if( ((hw^hh)&HH_Undoc)==0 ){
- if( (hh&HH_Summary)!=0 ){
- utf8_printf(out, ".%s\n", azHelp[i]+1);
- ++n;
- }else if( (hw&HW_SummaryOnly)==0 ){
- utf8_printf(out, "%s\n", azHelp[i]);
- }
- }
- }
- }else{
- /* Seek documented commands for which zPattern is an exact prefix */
- zPat = sqlite3_mprintf(".%s*", zPattern);
- shell_check_oom(zPat);
- for(i=0; i<ArraySize(azHelp); i++){
- if( sqlite3_strglob(zPat, azHelp[i])==0 ){
- utf8_printf(out, "%s\n", azHelp[i]);
- j = i+1;
- n++;
- }
- }
- sqlite3_free(zPat);
- if( n ){
- if( n==1 ){
- /* when zPattern is a prefix of exactly one command, then include
- ** the details of that command, which should begin at offset j */
- while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
- utf8_printf(out, "%s\n", azHelp[j]);
- j++;
- }
- }
- return n;
- }
- /* Look for documented commands that contain zPattern anywhere.
- ** Show complete text of all documented commands that match. */
- zPat = sqlite3_mprintf("%%%s%%", zPattern);
- shell_check_oom(zPat);
- for(i=0; i<ArraySize(azHelp); i++){
- if( azHelp[i][0]==',' ){
- while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
- continue;
- }
- if( azHelp[i][0]=='.' ) j = i;
- if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
- utf8_printf(out, "%s\n", azHelp[j]);
- while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
- j++;
- utf8_printf(out, "%s\n", azHelp[j]);
- }
- i = j;
- n++;
- }
- }
- sqlite3_free(zPat);
- }
- return n;
-}
-
-/* Forward reference */
-static int process_input(ShellState *p);
-
-/*
-** Read the content of file zName into memory obtained from sqlite3_malloc64()
-** and return a pointer to the buffer. The caller is responsible for freeing
-** the memory.
-**
-** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
-** read.
-**
-** For convenience, a nul-terminator byte is always appended to the data read
-** from the file before the buffer is returned. This byte is not included in
-** the final value of (*pnByte), if applicable.
-**
-** NULL is returned if any error is encountered. The final value of *pnByte
-** is undefined in this case.
-*/
-static char *readFile(const char *zName, int *pnByte){
- FILE *in = fopen(zName, "rb");
- long nIn;
- size_t nRead;
- char *pBuf;
- int rc;
- if( in==0 ) return 0;
- rc = fseek(in, 0, SEEK_END);
- if( rc!=0 ){
- raw_printf(stderr, "Error: '%s' not seekable\n", zName);
- fclose(in);
- return 0;
- }
- nIn = ftell(in);
- rewind(in);
- pBuf = sqlite3_malloc64( nIn+1 );
- if( pBuf==0 ){
- raw_printf(stderr, "Error: out of memory\n");
- fclose(in);
- return 0;
- }
- nRead = fread(pBuf, nIn, 1, in);
- fclose(in);
- if( nRead!=1 ){
- sqlite3_free(pBuf);
- raw_printf(stderr, "Error: cannot read '%s'\n", zName);
- return 0;
- }
- pBuf[nIn] = 0;
- if( pnByte ) *pnByte = nIn;
- return pBuf;
-}
-
-#if defined(SQLITE_ENABLE_SESSION)
-/*
-** Close a single OpenSession object and release all of its associated
-** resources.
-*/
-static void session_close(OpenSession *pSession){
- int i;
- sqlite3session_delete(pSession->p);
- sqlite3_free(pSession->zName);
- for(i=0; i<pSession->nFilter; i++){
- sqlite3_free(pSession->azFilter[i]);
- }
- sqlite3_free(pSession->azFilter);
- memset(pSession, 0, sizeof(OpenSession));
-}
-#endif
-
-/*
-** Close all OpenSession objects and release all associated resources.
-*/
-#if defined(SQLITE_ENABLE_SESSION)
-static void session_close_all(ShellState *p, int i){
- int j;
- struct AuxDb *pAuxDb = i<0 ? p->pAuxDb : &p->aAuxDb[i];
- for(j=0; j<pAuxDb->nSession; j++){
- session_close(&pAuxDb->aSession[j]);
- }
- pAuxDb->nSession = 0;
-}
-#else
-# define session_close_all(X,Y)
-#endif
-
-/*
-** Implementation of the xFilter function for an open session. Omit
-** any tables named by ".session filter" but let all other table through.
-*/
-#if defined(SQLITE_ENABLE_SESSION)
-static int session_filter(void *pCtx, const char *zTab){
- OpenSession *pSession = (OpenSession*)pCtx;
- int i;
- for(i=0; i<pSession->nFilter; i++){
- if( sqlite3_strglob(pSession->azFilter[i], zTab)==0 ) return 0;
- }
- return 1;
-}
-#endif
-
-/*
-** Try to deduce the type of file for zName based on its content. Return
-** one of the SHELL_OPEN_* constants.
-**
-** If the file does not exist or is empty but its name looks like a ZIP
-** archive and the dfltZip flag is true, then assume it is a ZIP archive.
-** Otherwise, assume an ordinary database regardless of the filename if
-** the type cannot be determined from content.
-*/
-int deduceDatabaseType(const char *zName, int dfltZip){
+u8 deduceDatabaseType(const char *zName, int dfltZip){
FILE *f = fopen(zName, "rb");
size_t n;
- int rc = SHELL_OPEN_UNSPEC;
+ u8 rc = SHELL_OPEN_UNSPEC;
char zBuf[100];
if( f==0 ){
if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
/*
** 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 standard input.
+** If p->aAuxDb[].zDbFilename is 0, then read from the present input.
*/
-static unsigned char *readHexDb(ShellState *p, int *pnData){
+static unsigned char *readHexDb(ShellInState *psi, int *pnData){
++ ResourceMark mark = holder_mark();
unsigned char *a = 0;
- int nLine;
int n = 0;
int pgsz = 0;
int iOffset = 0;
n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */
a = sqlite3_malloc( n ? n : 1 );
shell_check_oom(a);
++ smem_holder(a); /* offset 0 */
memset(a, 0, n);
if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
- utf8_printf(stderr, "invalid pagesize\n");
+ utf8_printf(STD_ERR, "invalid pagesize\n");
goto readHexDb_error;
}
- for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
+ while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
rc = sscanf(zLine, "| page %d offset %d", &j, &k);
if( rc==2 ){
iOffset = k;
}
}
}
- *pnData = n;
- if( in!=p->in ){
- fclose(in);
- }else{
- p->lineno = nLine;
+ *pnData = n; /* Record success and size. */
++ swap_held(mark, 0, 0);
+ readHexDb_cleanup:
+ if( psi->pInSource==&inRedir ){
+ fclose( inRedir.inFile );
+ psi->pInSource = inRedir.pFrom;
}
++ holder_free(mark);
return a;
-readHexDb_error:
- if( in!=p->in ){
- fclose(in);
- }else{
- while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
- nLine++;
- if(cli_strncmp(zLine, "| end ", 6)==0 ) break;
+ readHexDb_error:
+ nlError = psi->pInSource->lineno;
+ if( psi->pInSource!=&inRedir ){
+ /* Since taking input inline, consume through its end marker. */
+ while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
+ if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break;
}
- p->lineno = nLine;
}
-- sqlite3_free(a);
- utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
- return 0;
+ a = 0;
+ utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
+ goto readHexDb_cleanup;
}
#endif /* SQLITE_OMIT_DESERIALIZE */
sqlite3_result_int(context, sleep);
}
--/* Flags for open_db().
++/* Flags for open_db(). ToDo: Conform comments to code or vice-versa.
**
** The default behavior of open_db() is to exit(1) if the database fails to
** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
break;
}
}
- globalDb = p->db;
- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
- utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n",
- zDbFilename, sqlite3_errmsg(p->db));
+ globalDb = DBX(psx);
+ if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
+ const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
+ utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
+ zDbFilename, zWhy);
++ sqlite3_close(DBX(psx));
++ if( bail_on_error && !stdin_is_interactive ){
++ shell_terminate("-bail used in batch mode.");
++ }
if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
-- exit(1);
++ shell_terminate("with OPEN_DB_KEEPALIVE.");
}
- sqlite3_close(DBX(psx));
- sqlite3_close(p->db);
- sqlite3_open(":memory:", &p->db);
- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
- utf8_printf(stderr,
- "Also: unable to open substitute in-memory database.\n"
- );
- exit(1);
+ sqlite3_open(":memory:", &DBX(psx));
+ if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
- utf8_printf(stderr,
- "Also: unable to open substitute in-memory database.\n"
- );
- exit(1);
++ shell_terminate("Also: unable to open substitute in-memory database.");
}else{
utf8_printf(stderr,
"Notice: using substitute in-memory database instead of \"%s\"\n",
return 0;
}
-static void echo_group_input(ShellState *p, const char *zDo){
- if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo);
+#if SHELL_EXTENDED_PARSING
+/* Resumable line classsifier for dot-commands
+**
+** 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,
+ SCAN_TRACKER_REFTYPE pst){
+ DCmd_ScanState ss = *pScanState & ~endEscaped;
+ char c = (ss&isOpenMask)? 1 : *zCmd++;
+ while( c!=0 ){
+ switch( ss ){
+ case twixtArgs:
+ CONTINUE_PROMPT_AWAITC(pst, 0);
+ 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:
+ CONTINUE_PROMPT_AWAITC(pst, '\'');
+ while( (c=*zCmd++)!='\'' ){
+ if( c==0 ) goto atEnd;
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ }
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ inDq:
+ case inDqArg:
+ CONTINUE_PROMPT_AWAITC(pst, '"');
+ 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:
+ CONTINUE_PROMPT_AWAITC(pst, 0);
+ while( !IsSpace(c) ){
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ case endEscaped: case isOpenMask: default:
+ ; /* Not reachable, but quiet compilers unable to see this. */
+ }
+ }
+ atEnd:
+ *pScanState = ss;
}
+#else
+# define dot_command_scan(x,y,z)
+#endif
-#ifdef SQLITE_SHELL_FIDDLE
+/* Utility functions for process_input. */
+
+#if SHELL_EXTENDED_PARSING
/*
-** Alternate one_input_line() impl for wasm mode. This is not in the primary
-** impl because we need the global shellState and cannot access it from that
-** function without moving lots of code around (creating a larger/messier diff).
+** 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 char *one_input_line(FILE *in, char *zPrior, int isContinuation){
- /* Parse the next line from shellState.wasm.zInput. */
- const char *zBegin = shellState.wasm.zPos;
- const char *z = zBegin;
- char *zLine = 0;
- i64 nZ = 0;
-
- UNUSED_PARAMETER(in);
- UNUSED_PARAMETER(isContinuation);
- if(!z || !*z){
+static int line_join_done(DCmd_ScanState dcss, char *zLine,
+ i64 *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;
}
- while(*z && isspace(*z)) ++z;
- zBegin = z;
- for(; *z && '\n'!=*z; ++nZ, ++z){}
- if(nZ>0 && '\r'==zBegin[nZ-1]){
- --nZ;
+}
+#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, i64 *pna, int ncNeed){
+
+ if( ncNeed > *pna ){
+ *pna += *pna + (*pna>>1) + 100;
+ *pz = sqlite3_realloc(*pz, *pna);
+ shell_check_oom(*pz);
}
- shellState.wasm.zPos = z;
- zLine = realloc(zPrior, nZ+1);
- shell_check_oom(zLine);
- memcpy(zLine, zBegin, nZ);
- zLine[nZ] = 0;
- return zLine;
}
-#endif /* SQLITE_SHELL_FIDDLE */
/*
-** 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.
+** 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.
**
-** Return the number of errors.
-*/
-static int process_input(ShellState *p){
- char *zLine = 0; /* A single input line */
- char *zSql = 0; /* Accumulated SQL text */
- i64 nLine; /* Length of current line */
- i64 nSql = 0; /* Bytes of zSql[] used */
- i64 nAlloc = 0; /* Allocated zSql[] space */
- int rc; /* Error code */
- 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) */
-
- if( p->inputNesting==MAX_INPUT_NESTING ){
- /* This will be more informative in a later version. */
- utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
- " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
- return 1;
- }
- ++p->inputNesting;
- p->lineno = 0;
- CONTINUE_PROMPT_RESET;
- 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, CONTINUE_PROMPT_PSTATE);
- if( QSS_PLAINWHITE(qss) && nSql==0 ){
- /* Just swallow single-line whitespace */
- echo_group_input(p, zLine);
- qss = QSS_Start;
- continue;
- }
- if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
- CONTINUE_PROMPT_RESET;
- echo_group_input(p, zLine);
- if( zLine[0]=='.' ){
- rc = do_meta_command(zLine, p);
- if( rc==2 ){ /* exit requested */
+** Returns are the post-execute values of enum DotCmdRC:
+** DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort
+** each of which may be bit-wise or'ed with DCR_Error.
+*/
+static DotCmdRC process_input(ShellInState *psi){
+ 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. */
+ i64 naAccum = 0; /* tracking how big zLineAccum buffer has become */
+ /* Some flags for ending the overall group processing loop, always 1 or 0 */
+ u8 bInputEnd=0, bInterrupted=0;
+ /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort,
+ * the greatest of whichever is applicable */
- u8 termKind = DCR_Ok;
++ u8 termKind = (XSS(psi)->shellAbruptExit==0)? DCR_Ok : DCR_Exit;
++ /* Flag to indicate input from shell invocation argument. */
++ u8 bInvokeArg = INSOURCE_IS_INVOKEARG(psi->pInSource);
+ /* Flag to affect prompting and interrupt action */
+ u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource);
+ int nErrors = 0; /* count of errors during execution or its prep */
+
+ /* Block overly-recursive or absurdly nested input redirects. */
+ if( psi->inputNesting>=MAX_INPUT_NESTING ){
+ InSource *pInSrc = psi->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(STD_ERR,
+ "%s from line %d of \"%s\"",
+ zLead, pInSrc->lineno, pInSrc->zSourceSay);
+ zLead = (i%2==0)? "\n" : "";
+ pInSrc=pInSrc->pFrom;
+ }
+ utf8_printf(STD_ERR, " ...\nError: Check recursion.\n");
+ return DCR_Error;
+ }
+ ++psi->inputNesting;
+
+ /* line-group processing loop (per SQL block, dot-command or comment) */
+ while( !bInputEnd && termKind==DCR_Ok && !bInterrupted ){
+#if SHELL_DYNAMIC_EXTENSION
+ ScriptSupport *pSS = psi->script;
+#endif
+ int nGroupLines = 0; /* count of lines belonging to this group */
+ i64 ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */
+ i64 ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */
+ i64 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 nLeadWhite = 0; /* skips over initial whitespace to . or # */
+ char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
+#else
+# define nLeadWhite 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
+#if SHELL_DYNAMIC_EXTENSION
+ , Script
+#endif
+ } 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 */
+ i64 *pncLineUse = &ncLineIn; /* ref that line's char count */
+ int iStartline = 0; /* starting line number of group */
+
+ seenInterrupt = 0;
+ fflush(psi->out);
+ CONTINUE_PROMPT_RESET;
+ zLineInput = one_input_line(psi->pInSource, zLineInput,
+ nGroupLines>0, &shellPrompts);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
+ inKind = Eof;
+ disposition = Ignore;
+ if( bInteractive ) printf("\n");
+ }else{
+ ++nGroupLines;
+ iStartline = psi->pInSource->lineno;
+ ncLineIn = strlen30(zLineInput);
+ if( seenInterrupt ){
+ if( psi->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
+ nLeadWhite = (SHEXT_PARSING(psi))
+ ? skipWhite(zLineInput)-zLineInput
+ : 0; /* Disallow leading whitespace for . or # in legacy mode. */
+#endif
+#if SHELL_DYNAMIC_EXTENSION
+ if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
+ inKind = Script;
+ }else
+#endif
+ {
+ switch( zLineInput[nLeadWhite] ){
+ case '.':
+ inKind = Cmd;
+ dot_command_scan(zLineInput+nLeadWhite, &dcScanState,
+ CONTINUE_PROMPT_PSTATE);
+ break;
+ case '#':
+ inKind = Comment;
+ break;
+ default:
+ /* Might be SQL, or a swallowable whole SQL comment. */
+ sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
+ 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;
- }else if( rc ){
- errCnt++;
}
}
- qss = QSS_Start;
- continue;
- }
- /* No single-line dispositions remain; accumulate line(s). */
- nLine = strlen(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);
- }
- if( nSql==0 ){
- i64 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;
- }else{
- zSql[nSql++] = '\n';
- memcpy(zSql+nSql, zLine, nLine+1);
- nSql += nLine;
- }
- if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
- echo_group_input(p, zSql);
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- CONTINUE_PROMPT_RESET;
- nSql = 0;
- if( p->outCount ){
- output_reset(p);
- p->outCount = 0;
- }else{
- clearTempFile(p);
+ } /* 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 ){
+ PROMPTS_UPDATE(inKind == Sql || inKind == Cmd);
+ /* 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(psi) ){
+ if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
+ disposition = Runnable;
+ }
+ }else
+#endif
+ disposition = Runnable; /* Legacy, any dot-command line is ready. */
+ break;
+#if SHELL_DYNAMIC_EXTENSION
+ case Script:
+ if( pSS==0
+ || pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){
+ disposition = Runnable;
+ }
+ break;
+#endif
+ case Sql:
+ /* Check to see if it is complete and ready to run. */
+ if( SSS_SEMITERM(sqScanState) && 1==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;
+ case Tbd: case Eof: default: assert(0); /* Not reachable */
+ } /* 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);
+ zLineAccum[ncLineIn] = 0;
+ ncLineAcc = ncLineIn;
+ pzLineUse = &zLineAccum;
+ pncLineUse = &ncLineAcc;
+ }
+ /* Read in next line of group, (if available.) */
+ zLineInput = one_input_line(psi->pInSource, zLineInput,
+ nGroupLines>0, &shellPrompts);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
- if( inKind==Sql && psi->pInSource==&cmdInSource ){
++ if( inKind==Sql && bInvokeArg ){
+ /* As a special dispensation, SQL arguments on the command line
+ ** do not need to end with ';' (or a lone go.) */
+ if( nGroupLines>1 ){
+ grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+2);
+ }
+ strcpy( zLineAccum+ncLineAcc, ";" );
+ if( 1==sqlite3_complete(*pzLineUse) ){
+ zLineAccum[ncLineAcc] = 0;
+ disposition = Runnable;
+ continue;
+ }
+ }
+ disposition = Erroneous;
+ inKind = Eof;
+ if( bInteractive ) printf("\n");
+ continue;
+ }
+ ++nGroupLines;
+ ncLineIn = strlen30(zLineInput);
+ /* Scan line just input (if needed) and append to accumulation. */
+ switch( inKind ){
+ case Cmd:
+ dot_command_scan(zLineInput, &dcScanState, CONTINUE_PROMPT_PSTATE);
+ break;
+ case Sql:
+ sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
+ 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;
+#if SHELL_EXTENDED_PARSING
+ cLineEnd = '\n'; /* reset to default after use */
+#endif
+ 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. */
+ CONTINUE_PROMPT_RESET;
+ switch( disposition ){
+ case Dumpable:
+ echo_group_input(psi, *pzLineUse);
+#if SHELL_DYNAMIC_EXTENSION
+ if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
+#endif
+ break;
+ case Runnable:
+ switch( inKind ){
+ case Sql:
+ echo_group_input(psi, *pzLineUse);
+ nErrors += runOneSqlLine(XSS(psi), *pzLineUse,
+ INSOURCE_IS_INTERACTIVE(psi->pInSource),
+ iStartline);
+ break;
+ case Cmd: {
+ DotCmdRC dcr;
+ echo_group_input(psi, *pzLineUse);
+ dcr = do_dot_command(*pzLineUse+nLeadWhite, XSS(psi));
+ nErrors += (dcr & DCR_Error);
+ dcr &= ~DCR_Error;
+ if( dcr > termKind ) termKind = dcr;
+ break;
+ }
+#if SHELL_DYNAMIC_EXTENSION
+ case Script: {
+ char *zErr = 0;
+ DotCmdRC dcr;
+ assert(pSS!=0);
+ /* Consider: Should echo flag be honored here? */
+ pSS->pMethods->resetCompletionScan(pSS);
+ dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite,
+ XSS(psi), &zErr);
+ if( dcr!=DCR_Ok || zErr!=0 ){
+ /* Future: Handle errors more informatively and like dot commands. */
+ nErrors += (dcr!=DCR_Ok);
+ if( zErr!=0 ){
+ utf8_printf(STD_ERR, "Error: %s\n", zErr);
+ sqlite3_free(zErr);
+ }
+ }
+ break;
+ }
+#endif
+ default:
+ assert(inKind!=Tbd);
+ break;
+ }
+ if( XSS(psi)->shellAbruptExit!=0 ){
+ termKind = DCR_Exit;
}
- p->bSafeMode = p->bSafeModePersist;
- qss = QSS_Start;
- }else if( nSql && QSS_PLAINWHITE(qss) ){
- echo_group_input(p, zSql);
- nSql = 0;
- qss = QSS_Start;
+ break;
+ case Erroneous:
+ utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
+ psi->pInSource->lineno, psi->pInSource->zSourceSay);
+#if SHELL_DYNAMIC_EXTENSION
+ if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
+#endif
+ ++nErrors;
+ break;
+ case Ignore:
+ break;
+ default: assert(0);
}
+ if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error;
+ } /* 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);
+
+ /* Translate DCR_Return because it has been done here, not to propagate
+ * unless input is from shell invocation argument. */
+ if( termKind==DCR_Return && psi->pInSource!=&cmdInSource ){
+ termKind = DCR_Ok;
}
- if( nSql ){
- /* This may be incomplete. Let the SQL parser deal with that. */
- echo_group_input(p, zSql);
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- CONTINUE_PROMPT_RESET;
- }
- free(zSql);
- free(zLine);
- --p->inputNesting;
- return errCnt>0;
+ return termKind|(nErrors>0);
}
/*
if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n");
}
- ResourceMark buffMark; /* where to grab held array pointer */
+/* A vector of command strings collected from . */
+typedef struct CmdArgs {
+ /* Array is malloc'ed, but not its elements except by wmain()'s futzing. */
+ char **azCmd; /* the strings */
+ int nCmd; /* how many collected */
- void *vaz;
- if( pca->nCmd == 0 ){
- pca->buffMark = holder_mark();
- more_holders(1);
- }
- vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*(pca->nCmd+1));
+} CmdArgs;
+/* Data collected during args scanning. */
+typedef struct ArgsData {
+ int readStdin; /* whether stdin will be read */
+ int nOptsEnd; /* where -- seen, else argc */
+ const char *zInitFile; /* specified init file */
+ const char *zVfs; /* -vfs command-line option */
+ short bQuiet; /* -quiet option */
+} ArgsData;
+/*
+** Perform CLI invocation argument processing.
+** This code is collected here for convenience, to declutter main()
+** and to make this processing a little simpler to understand.
+*/
+static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi,
+ CmdArgs *pca, ArgsData *pad){
+ int rc = 0;
+ DotCmdRC drc;
+ int i;
+ if( pass==1 ){
+ for(i=1; i<argc && rc<2; i++){
+ char *z = argv[i];
+ if( z[0]!='-' || i>pad->nOptsEnd ){
+ if( psi->aAuxDb->zDbFilename==0 ){
+ psi->aAuxDb->zDbFilename = z;
+ }else{
- if( pca->nCmd == 0 ) mmem_holder(vaz);
- else swap_held(pca->buffMark, 0, vaz);
++ void *vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*(pca->nCmd+1));
+ shell_check_oom(vaz);
+ pca->azCmd = (char**)vaz;
+ pca->azCmd[pca->nCmd++] = z;
+ /* Excesss arguments are interpreted as SQL (or dot-commands)
+ ** and mean that nothing is to be read from stdin. */
+ pad->readStdin = 0;
+ }
+ continue;
+ }
+ if( z[1]=='-' ) z++;
+ if( cli_strcmp(z, "-")==0 ){
+ pad->nOptsEnd = i;
+ continue;
+ }else if( cli_strcmp(z,"-separator")==0
+ || cli_strcmp(z,"-nullvalue")==0
+ || cli_strcmp(z,"-newline")==0
+ || cli_strcmp(z,"-cmd")==0
+ ){
+ (void)cmdline_option_value(argc, argv, ++i);
+ /* Will pickup value on next pass. */
+ }else if( cli_strcmp(z,"-init")==0 ){
+ pad->zInitFile = cmdline_option_value(argc, argv, ++i);
+ }else if( cli_strcmp(z,"-batch")==0 ){
+ /* Need to check for batch mode here to so we can avoid printing
+ ** informational messages (like from process_sqliterc) before
+ ** we do the actual processing of arguments later in a second pass.
+ */
+ stdin_is_interactive = 0;
+ }else if( cli_strcmp(z,"-heap")==0 ){
+#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
+ const char *zSize;
+ sqlite3_int64 szHeap;
+
+ zSize = cmdline_option_value(argc, argv, ++i);
+ szHeap = integerValue(zSize);
+ if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_HEAP,malloc((int)szHeap),(int)szHeap, 64);
+#else
+ (void)cmdline_option_value(argc, argv, ++i);
+#endif
+ }else if( cli_strcmp(z,"-pagecache")==0 ){
+ sqlite3_int64 n, sz;
+ void *pvCache = 0;
+ sz = integerValue(cmdline_option_value(argc,argv,++i));
+ if( sz>70000 ) sz = 70000;
+ if( sz<0 ) sz = 0;
+ n = integerValue(cmdline_option_value(argc,argv,++i));
+ if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
+ n = 0xffffffffffffLL/sz;
+ }
+ verify_uninitialized();
+ if( n>0 && sz>0 ) pvCache = malloc(n*sz);
+ shell_check_oom(pvCache);
+ sqlite3_config(SQLITE_CONFIG_PAGECACHE, pvCache, sz, n);
+ psi->shellFlgs |= SHFLG_Pagecache;
+ }else if( cli_strcmp(z,"-lookaside")==0 ){
+ int n, sz;
+ sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ if( sz<0 ) sz = 0;
+ n = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ if( n<0 ) n = 0;
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
+ if( sz*n==0 ) psi->shellFlgs &= ~SHFLG_Lookaside;
+ }else if( cli_strcmp(z,"-threadsafe")==0 ){
+ int n;
+ n = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ verify_uninitialized();
+ switch( n ){
+ case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
+ case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
+ default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
+ }
+#ifdef SQLITE_ENABLE_VFSTRACE
+ }else if( cli_strcmp(z,"-vfstrace")==0 ){
+ extern int vfstrace_register(
+ const char *zTraceName,
+ const char *zOldVfsName,
+ int (*xOut)(const char*,void*),
+ void *pOutArg,
+ int makeDefault
+ );
+ vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,STD_ERR,1);
+#endif
+#ifdef SQLITE_ENABLE_MULTIPLEX
+ }else if( cli_strcmp(z,"-multiplex")==0 ){
+ extern int sqlite3_multiple_initialize(const char*,int);
+ sqlite3_multiplex_initialize(0, 1);
+#endif
+ }else if( cli_strcmp(z,"-mmap")==0 ){
+ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ }else if( cli_strcmp(z,"-sorterref")==0 ){
+ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
+#endif
+ }else if( cli_strcmp(z,"-vfs")==0 ){
+ pad->zVfs = cmdline_option_value(argc, argv, ++i);
+#ifdef SQLITE_HAVE_ZLIB
+ }else if( cli_strcmp(z,"-zip")==0 ){
+ psi->openMode = SHELL_OPEN_ZIPFILE;
+#endif
+ }else if( cli_strcmp(z,"-append")==0 ){
+ psi->openMode = SHELL_OPEN_APPENDVFS;
+#ifndef SQLITE_OMIT_DESERIALIZE
+ }else if( cli_strcmp(z,"-deserialize")==0 ){
+ psi->openMode = SHELL_OPEN_DESERIALIZE;
+ }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
+ psi->szMax = integerValue(argv[++i]);
+#endif
+ }else if( cli_strcmp(z,"-readonly")==0 ){
+ psi->openMode = SHELL_OPEN_READONLY;
+ }else if( cli_strcmp(z,"-nofollow")==0 ){
+ psi->openFlags = SQLITE_OPEN_NOFOLLOW;
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+ }else if( cli_strncmp(z, "-A",2)==0 ){
+ /* All remaining command-line arguments are passed to the ".archive"
+ ** command, so ignore them */
+ break;
+#endif
+ }else if( cli_strcmp(z, "-memtrace")==0 ){
+ sqlite3MemTraceActivate(STD_ERR);
+ }else if( cli_strcmp(z,"-bail")==0 ){
+ bail_on_error = 1;
+#if SHELL_EXTENSIONS
+ }else if( cli_strcmp(z,"-shxopts")==0 ){
+ psi->bExtendedDotCmds = (u8)integerValue(argv[++i]);
+#endif
+ }else if( cli_strcmp(z,"-nonce")==0 ){
+ free(psi->zNonce);
+ psi->zNonce = strdup(argv[++i]);
+ shell_check_oom(psi->zNonce);
+ }else if( cli_strcmp(z,"-quiet")==0 ){
+ pad->bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
+ psi->shellFlgs |= SHFLG_TestingMode;
+ }else if( cli_strcmp(z,"-safe")==0 ){
+ /* catch this on the second pass (Unsafe is fine on invocation.) */
+ }
+ }
+ }else if( pass==2 ){
+ for(i=1; i<argc && rc<2; i++){
+ char *z = argv[i];
+ char *zModeSet = 0;
+ if( z[0]!='-' || i>=pad->nOptsEnd ) continue;
+ if( z[1]=='-' ){ z++; }
+ if( cli_strcmp(z,"-init")==0 ){
+ i++;
+ }else if( cli_strcmp(z,"-html")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-list")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-quote")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-line")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-column")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-json")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-markdown")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-table")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-box")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-csv")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-ascii")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-tabs")==0 ){
+ zModeSet = z;
+#ifdef SQLITE_HAVE_ZLIB
+ }else if( cli_strcmp(z,"-zip")==0 ){
+ psi->openMode = SHELL_OPEN_ZIPFILE;
+#endif
+ }else if( cli_strcmp(z,"-append")==0 ){
+ psi->openMode = SHELL_OPEN_APPENDVFS;
+#ifndef SQLITE_OMIT_DESERIALIZE
+ }else if( cli_strcmp(z,"-deserialize")==0 ){
+ psi->openMode = SHELL_OPEN_DESERIALIZE;
+ }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
+ psi->szMax = integerValue(argv[++i]);
+#endif
+ }else if( cli_strcmp(z,"-readonly")==0 ){
+ psi->openMode = SHELL_OPEN_READONLY;
+ }else if( cli_strcmp(z,"-nofollow")==0 ){
+ psi->openFlags |= SQLITE_OPEN_NOFOLLOW;
+ }else if( cli_strcmp(z,"-separator")==0 ){
+ sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator,
+ "%s",cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-newline")==0 ){
+ sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator,
+ "%s",cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-nullvalue")==0 ){
+ sqlite3_snprintf(sizeof(psi->nullValue), psi->nullValue,
+ "%s",cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-header")==0 ){
+ psi->showHeader = 1;
+ ShellSetFlagI(psi, SHFLG_HeaderSet);
+ }else if( cli_strcmp(z,"-noheader")==0 ){
+ psi->showHeader = 0;
+ ShellSetFlagI(psi, SHFLG_HeaderSet);
+ }else if( cli_strcmp(z,"-echo")==0 ){
+ ShellSetFlagI(psi, SHFLG_Echo);
+ }else if( cli_strcmp(z,"-eqp")==0 ){
+ psi->autoEQP = AUTOEQP_on;
+ }else if( cli_strcmp(z,"-eqpfull")==0 ){
+ psi->autoEQP = AUTOEQP_full;
+ }else if( cli_strcmp(z,"-stats")==0 ){
+ psi->statsOn = 1;
+ }else if( cli_strcmp(z,"-scanstats")==0 ){
+ psi->scanstatsOn = 1;
+ }else if( cli_strcmp(z,"-backslash")==0 ){
+ /* Undocumented command-line option: -backslash
+ ** Causes C-style backslash escapes to be evaluated in SQL statements
+ ** prior to sending the SQL into SQLite. Useful for injecting crazy
+ ** bytes in the middle of SQL statements for testing and debugging.
+ */
+ ShellSetFlagI(psi, SHFLG_Backslash);
+ }else if( cli_strcmp(z,"-bail")==0 ){
+ /* No-op. The bail_on_error flag should already be set. */
+#if SHELL_EXTENSIONS
+ }else if( cli_strcmp(z,"-shxopts")==0 ){
+ i++; /* Handled on first pass. */
+#endif
+ }else if( cli_strcmp(z,"-version")==0 ){
+ fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
+ rc = 2;
+ }else if( cli_strcmp(z,"-interactive")==0 ){
+ stdin_is_interactive = 1;
+ }else if( cli_strcmp(z,"-batch")==0 ){
+ stdin_is_interactive = 0;
+ }else if( cli_strcmp(z,"-utf8")==0 ){
+#if SHELL_WIN_UTF8_OPT
+ console_utf8 = 1;
+#endif /* SHELL_WIN_UTF8_OPT */
+ }else if( cli_strcmp(z,"-heap")==0 ){
+ i++;
+ }else if( cli_strcmp(z,"-pagecache")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-lookaside")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-threadsafe")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-nonce")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-mmap")==0 ){
+ i++;
+ }else if( cli_strcmp(z,"-memtrace")==0 ){
+ i++;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ }else if( cli_strcmp(z,"-sorterref")==0 ){
+ i++;
+#endif
+ }else if( cli_strcmp(z,"-vfs")==0 ){
+ i++;
+#ifdef SQLITE_ENABLE_VFSTRACE
+ }else if( cli_strcmp(z,"-vfstrace")==0 ){
+ i++;
+#endif
+#ifdef SQLITE_ENABLE_MULTIPLEX
+ }else if( cli_strcmp(z,"-multiplex")==0 ){
+ i++;
+#endif
+ }else if( cli_strcmp(z,"-help")==0 ){
+ usage(1);
+ }else if( cli_strcmp(z,"-cmd")==0 ){
+ /* Run commands that follow -cmd first and separately from commands
+ ** that simply appear on the command-line. This seems goofy. It would
+ ** be better if all commands ran in the order that they appear. But
+ ** we retain the goofy behavior for historical compatibility. */
+ if( i==argc-1 ) break; /* Pretend specified command is empty. */
+ set_invocation_cmd(cmdline_option_value(argc,argv,++i));
+ drc = process_input(psi);
+ rc = (drc>2)? 2 : drc;
+ if( rc>0 ){
+ break;
+ }
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+ }else if( cli_strncmp(z, "-A", 2)==0 ){
+ if( pca->nCmd>0 ){
+ utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands"
+ " with \"%s\"\n", z);
+ rc = 1;
+ break;
+ }
+ open_db(XSS(psi), OPEN_DB_ZIPFILE);
+ if( z[2] ){
+ argv[i] = &z[2];
+ drc = arDotCommand(XSS(psi), 1, argv+(i-1), argc-(i-1));
+ }else{
+ drc = arDotCommand(XSS(psi), 1, argv+i, argc-i);
+ }
+ rc = (drc>2)? 2 : drc;
+ pad->readStdin = 0;
+ break;
+#endif
+ }else if( cli_strcmp(z,"-safe")==0 ){
+ psi->bSafeMode = psi->bSafeModeFuture = 1;
+ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
+ /* Acted upon in first pass. */
+ }else if( cli_strcmp(z,"-quiet")==0 ){
+ ++i;
+ }else{
+ utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
+ raw_printf(STD_ERR,"Use -help for a list of options.\n");
+ rc = 2;
+ }
+ if( zModeSet!=0 ){
+ char *azModeCmd[] = { ".mode", zModeSet+1 };
+ modeCommand(azModeCmd, 2, XSS(psi), 0);
+ psi->cMode = psi->mode;
+ }
+ }
+ }
+ return rc;
+}
+
#ifndef SQLITE_SHELL_IS_UTF8
# if (defined(_WIN32) || defined(WIN32)) \
&& (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__)))
#ifdef SQLITE_DEBUG
sqlite3_int64 mem_main_enter = 0;
#endif
- char *zErrMsg = 0;
#ifdef SQLITE_SHELL_FIDDLE
-# define data shellState
+# define datai shellStateI
+# define datax shellStateX
#else
- ShellState data;
+ ShellInState datai;
+ ShellExState datax;
#endif
+#if SHELL_DATAIO_EXT
+ BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( &datai );
+ BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( &datai );
+#endif
+ RIP_STATE(exit_jb);
const char *zInitFile = 0;
- int i;
+ int bQuiet = 0; /* for testing, to suppress banner and history actions */
+ int i, aec;
int rc = 0;
+ DotCmdRC drc = DCR_Ok;
int warnInmemoryDb = 0;
- /* azCmd, nCmd, buffMark */
- CmdArgs cmdArgs = {0,0,0};
- int readStdin = 1;
- int nCmd = 0;
++ /* azCmd, nCmd */
++ CmdArgs cmdArgs = {0,0};
+ /* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */
+ ArgsData argsData = { 1, argc, 0,0,0 };
int nOptsEnd = argc;
- char **azCmd = 0;
- const char *zVfs = 0; /* Value of -vfs command-line option */
#if !SQLITE_SHELL_IS_UTF8
char **argvToFree = 0;
int argcToFree = 0;
#endif
- ResourceMark entry_mark = holder_mark();
- setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
++ main_resource_mark = holder_mark();
+ setvbuf(STD_ERR, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
#ifdef SQLITE_SHELL_FIDDLE
stdin_is_interactive = 0;
stdout_is_console = 1;
stdin_is_interactive = isatty(0);
stdout_is_console = isatty(1);
#endif
++ if( atexit_registered==0 ){
#if SHELL_WIN_UTF8_OPT
-- atexit(console_restore); /* Needs revision for CLI as library call */
++ atexit(console_restore); /* Needs revision for CLI as library call */
#endif
-- atexit(sayAbnormalExit);
++ atexit(sayAbnormalExit);
++ }
#ifdef SQLITE_DEBUG
mem_main_enter = sqlite3_memory_used();
#endif
exit(1);
}
#endif
- main_init(&data);
+ main_init(&datai,&datax);
+#if SHELL_DATAIO_EXT
+ datai.pFreeformExporter = (ExportHandler*)&ffExporter;
+ datai.pColumnarExporter = (ExportHandler*)&cmExporter;
+ datai.pActiveExporter = (ExportHandler*)&ffExporter;
+#endif
- /* On Windows, we must translate command-line arguments into UTF-8.
- ** The SQLite memory allocator subsystem has to be enabled in order to
- ** do this. But we want to run an sqlite3_shutdown() afterwards so that
- ** subsequent sqlite3_config() calls will work. So copy all results into
- ** memory that does not come from the SQLite memory allocator.
+ /* From here on, within the true clause of this next test, various
+ ** heap allocations are made which may fail, resulting in an abrupt
+ ** shell exit. Such an exit happens in 1 of 2 ways: A held resource
+ ** stack and the call stack are ripped back to this point; or just
+ ** the held resource stack is ripped back and a process exit occurs.
*/
- register_exit_ripper(&exit_jb, entry_mark);
++ register_exit_ripper(&exit_jb, main_resource_mark);
+ if( 0==RIP_TO_HERE(exit_jb) ){
+
+ /* On Windows, we must translate command-line arguments into UTF-8.
+ ** The SQLite memory allocator subsystem has to be enabled in order to
+ ** do this. But we want to run an sqlite3_shutdown() afterwards so that
+ ** subsequent sqlite3_config() calls will work. So copy all results into
+ ** memory that does not come from the SQLite memory allocator.
+ */
#if !SQLITE_SHELL_IS_UTF8
- sqlite3_initialize();
- argvToFree = malloc(sizeof(argv[0])*argc*2);
- shell_check_oom(argvToFree);
- argcToFree = argc;
- argv = argvToFree + argc;
- for(i=0; i<argc; i++){
- char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
- i64 n;
- shell_check_oom(z);
- n = strlen(z);
- argv[i] = malloc( n+1 );
- shell_check_oom(argv[i]);
- memcpy(argv[i], z, n+1);
- argvToFree[i] = argv[i];
- sqlite3_free(z);
- }
- sqlite3_shutdown();
+ sqlite3_initialize();
+ argvToFree = malloc(sizeof(argv[0])*argc*2);
+ shell_check_oom(argvToFree);
+ argcToFree = argc;
+ argv = argvToFree + argc;
+ for(i=0; i<argc; i++){
+ char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
+ i64 n;
+ shell_check_oom(z);
+ n = strlen(z);
+ argv[i] = malloc( n+1 );
+ shell_check_oom(argv[i]);
+ memcpy(argv[i], z, n+1);
+ argvToFree[i] = argv[i];
+ sqlite3_free(z);
+ }
+ sqlite3_shutdown();
#endif
- assert( argc>=1 && argv && argv[0] );
- Argv0 = argv[0];
+ assert( argc>=1 && argv && argv[0] );
+ Argv0 = argv[0];
+#if SHELL_DYNAMIC_EXTENSION
+ initStartupDir();
+ if( isExtendedBasename(Argv0) ){
+ datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS;
+ }
+#endif
#ifdef SQLITE_SHELL_DBNAME_PROC
- {
- /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
- ** of a C-function that will provide the name of the database file. Use
- ** this compile-time option to embed this shell program in larger
- ** applications. */
- extern void SQLITE_SHELL_DBNAME_PROC(const char**);
- SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename);
- warnInmemoryDb = 0;
- }
+ {
+ /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
+ ** of a C-function that will provide the name of the database file. Use
+ ** this compile-time option to embed this shell program in larger
+ ** applications. */
+ extern void SQLITE_SHELL_DBNAME_PROC(const char**);
+ SQLITE_SHELL_DBNAME_PROC(&datai.pAuxDb->zDbFilename);
+ warnInmemoryDb = 0;
+ }
#endif
- /* Do an initial pass through the command-line argument to locate
- ** the name of the database file, the name of the initialization file,
- ** the size of the alternative malloc heap,
- ** and the first command to execute.
- */
+ /* Do an initial pass through the command-line argument to locate
+ ** the name of the database file, the name of the initialization file,
+ ** the size of the alternative malloc heap,
+ ** and the first command to execute.
+ */
#ifndef SQLITE_SHELL_FIDDLE
- verify_uninitialized();
-#endif
- for(i=1; i<argc; i++){
- char *z;
- z = argv[i];
- if( z[0]!='-' || i>nOptsEnd ){
- if( data.aAuxDb->zDbFilename==0 ){
- data.aAuxDb->zDbFilename = z;
- }else{
- /* Excesss arguments are interpreted as SQL (or dot-commands) and
- ** mean that nothing is read from stdin */
- readStdin = 0;
- nCmd++;
- azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd);
- shell_check_oom(azCmd);
- azCmd[nCmd-1] = z;
- }
- continue;
- }
- if( z[1]=='-' ) z++;
- if( cli_strcmp(z, "-")==0 ){
- nOptsEnd = i;
- continue;
- }else if( cli_strcmp(z,"-separator")==0
- || cli_strcmp(z,"-nullvalue")==0
- || cli_strcmp(z,"-newline")==0
- || cli_strcmp(z,"-cmd")==0
- ){
- (void)cmdline_option_value(argc, argv, ++i);
- }else if( cli_strcmp(z,"-init")==0 ){
- zInitFile = cmdline_option_value(argc, argv, ++i);
- }else if( cli_strcmp(z,"-batch")==0 ){
- /* Need to check for batch mode here to so we can avoid printing
- ** informational messages (like from process_sqliterc) before
- ** we do the actual processing of arguments later in a second pass.
- */
- stdin_is_interactive = 0;
- }else if( cli_strcmp(z,"-heap")==0 ){
-#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
- const char *zSize;
- sqlite3_int64 szHeap;
-
- zSize = cmdline_option_value(argc, argv, ++i);
- szHeap = integerValue(zSize);
- if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
-#else
- (void)cmdline_option_value(argc, argv, ++i);
-#endif
- }else if( cli_strcmp(z,"-pagecache")==0 ){
- sqlite3_int64 n, sz;
- sz = integerValue(cmdline_option_value(argc,argv,++i));
- if( sz>70000 ) sz = 70000;
- if( sz<0 ) sz = 0;
- n = integerValue(cmdline_option_value(argc,argv,++i));
- if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
- n = 0xffffffffffffLL/sz;
- }
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_PAGECACHE,
- (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n);
- data.shellFlgs |= SHFLG_Pagecache;
- }else if( cli_strcmp(z,"-lookaside")==0 ){
- int n, sz;
- sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
- if( sz<0 ) sz = 0;
- n = (int)integerValue(cmdline_option_value(argc,argv,++i));
- if( n<0 ) n = 0;
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
- if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside;
- }else if( cli_strcmp(z,"-threadsafe")==0 ){
- int n;
- n = (int)integerValue(cmdline_option_value(argc,argv,++i));
- verify_uninitialized();
- switch( n ){
- case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
- case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
- default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
- }
-#ifdef SQLITE_ENABLE_VFSTRACE
- }else if( cli_strcmp(z,"-vfstrace")==0 ){
- extern int vfstrace_register(
- const char *zTraceName,
- const char *zOldVfsName,
- int (*xOut)(const char*,void*),
- void *pOutArg,
- int makeDefault
- );
- vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
-#endif
-#ifdef SQLITE_ENABLE_MULTIPLEX
- }else if( cli_strcmp(z,"-multiplex")==0 ){
- extern int sqlite3_multiple_initialize(const char*,int);
- sqlite3_multiplex_initialize(0, 1);
-#endif
- }else if( cli_strcmp(z,"-mmap")==0 ){
- sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
-#if defined(SQLITE_ENABLE_SORTER_REFERENCES)
- }else if( cli_strcmp(z,"-sorterref")==0 ){
- sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
-#endif
- }else if( cli_strcmp(z,"-vfs")==0 ){
- zVfs = cmdline_option_value(argc, argv, ++i);
-#ifdef SQLITE_HAVE_ZLIB
- }else if( cli_strcmp(z,"-zip")==0 ){
- data.openMode = SHELL_OPEN_ZIPFILE;
-#endif
- }else if( cli_strcmp(z,"-append")==0 ){
- data.openMode = SHELL_OPEN_APPENDVFS;
-#ifndef SQLITE_OMIT_DESERIALIZE
- }else if( cli_strcmp(z,"-deserialize")==0 ){
- data.openMode = SHELL_OPEN_DESERIALIZE;
- }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
- data.szMax = integerValue(argv[++i]);
-#endif
- }else if( cli_strcmp(z,"-readonly")==0 ){
- data.openMode = SHELL_OPEN_READONLY;
- }else if( cli_strcmp(z,"-nofollow")==0 ){
- data.openFlags = SQLITE_OPEN_NOFOLLOW;
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
- }else if( cli_strncmp(z, "-A",2)==0 ){
- /* All remaining command-line arguments are passed to the ".archive"
- ** command, so ignore them */
- break;
+ verify_uninitialized();
#endif
- }else if( cli_strcmp(z, "-memtrace")==0 ){
- sqlite3MemTraceActivate(stderr);
- }else if( cli_strcmp(z,"-bail")==0 ){
- bail_on_error = 1;
- }else if( cli_strcmp(z,"-nonce")==0 ){
- free(data.zNonce);
- data.zNonce = strdup(argv[++i]);
- }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
- ShellSetFlag(&data,SHFLG_TestingMode);
- }else if( cli_strcmp(z,"-safe")==0 ){
- /* no-op - catch this on the second pass */
- }
- }
+ i = scanInvokeArgs(argc, argv, 1, &datai, &cmdArgs, &argsData);
#ifndef SQLITE_SHELL_FIDDLE
- verify_uninitialized();
+ verify_uninitialized();
#endif
-
#ifdef SQLITE_SHELL_INIT_PROC
- {
- /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
- ** of a C-function that will perform initialization actions on SQLite that
- ** occur just before or after sqlite3_initialize(). Use this compile-time
- ** option to embed this shell program in larger applications. */
- extern void SQLITE_SHELL_INIT_PROC(void);
- SQLITE_SHELL_INIT_PROC();
- }
+ {
+ /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
+ ** of a C-function that will perform initialization actions on SQLite that
+ ** occur just before or after sqlite3_initialize(). Use this compile-time
+ ** option to embed this shell program in larger applications. */
+ extern void SQLITE_SHELL_INIT_PROC(void);
+ SQLITE_SHELL_INIT_PROC();
+ }
#else
- /* All the sqlite3_config() calls have now been made. So it is safe
- ** to call sqlite3_initialize() and process any command line -vfs option. */
- sqlite3_initialize();
+ /* All the sqlite3_config() calls have now been made. So it is safe
+ ** to call sqlite3_initialize() and process any command line -vfs option. */
+ sqlite3_initialize();
#endif
- if( zVfs ){
- sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs);
- if( pVfs ){
- sqlite3_vfs_register(pVfs, 1);
- }else{
- utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs);
- exit(1);
+ /* Create a mutex for thread-safe query execution interruption. */
- pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
- atexit(zapGlobalDbLock);
++ if( pGlobalDbLock==0 ){
++ pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
+ }
- }
++ if( atexit_registered==0 ){
++ atexit(zapGlobalDbLock);
++ ++atexit_registered;
++ }
+ /* Register the control-C (SIGINT) handler.
+ ** Make sure we have a valid signal handler early, before anything
+ ** is done that might take long. */
+#ifdef SIGINT
+ signal(SIGINT, interrupt_handler);
+#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
+ SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
+#endif
- if( data.pAuxDb->zDbFilename==0 ){
+ if( argsData.zVfs ){
+ sqlite3_vfs *pVfs = sqlite3_vfs_find(argsData.zVfs);
+ if( pVfs ){
+ sqlite3_vfs_register(pVfs, 1);
+ }else{
+ utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argsData.zVfs);
+ rc = 1;
+ goto shell_bail;
+ }
+ }
+
+ if( datai.pAuxDb->zDbFilename==0 ){
#ifndef SQLITE_OMIT_MEMORYDB
- data.pAuxDb->zDbFilename = ":memory:";
- warnInmemoryDb = argc==1;
+ datai.pAuxDb->zDbFilename = ":memory:";
+ warnInmemoryDb = argc==1;
#else
- utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0);
- return 1;
+ utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0);
+ rc = 1;
+ goto shell_bail;
#endif
- }
- data.out = stdout;
+ }
+ datai.out = STD_OUT;
#ifndef SQLITE_SHELL_FIDDLE
- sqlite3_appendvfs_init(0,0,0);
+ sqlite3_appendvfs_init(0,0,0);
#endif
- /* Go ahead and open the database file if it already exists. If the
- ** file does not exist, delay opening it. This prevents empty database
- ** files from being created if a user mistypes the database name argument
- ** to the sqlite command-line tool.
- */
- if( access(data.pAuxDb->zDbFilename, 0)==0 ){
- open_db(&data, 0);
- }
+ /* Go ahead and open the database file if it already exists. If the
+ ** file does not exist, delay opening it. This prevents empty database
+ ** files from being created if a user mistypes the database name argument
+ ** to the sqlite command-line tool.
+ */
+ if( access(datai.pAuxDb->zDbFilename, 0)==0 ){
+ open_db(&datax, 0);
+ }
- /* 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.
- */
- process_sqliterc(&data,zInitFile);
+ /* 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, without any quitting or bail-on-error.
+ */
+ process_sqliterc(&datai,argsData.zInitFile);
+
+ /* Make a second pass through the command-line argument and set
+ ** options. This second pass is delayed until after the initialization
+ ** file is processed so that the command-line arguments will override
+ ** settings in the initialization file.
+ */
+ rc = scanInvokeArgs(argc, argv, 2, &datai, &cmdArgs, &argsData);
+ if( rc>0 ){
+ goto shell_bail;
+ }
- /* Make a second pass through the command-line argument and set
- ** options. This second pass is delayed until after the initialization
- ** file is processed so that the command-line arguments will override
- ** settings in the initialization file.
- */
- for(i=1; i<argc; i++){
- char *z = argv[i];
- if( z[0]!='-' || i>=nOptsEnd ) continue;
- if( z[1]=='-' ){ z++; }
- if( cli_strcmp(z,"-init")==0 ){
- i++;
- }else if( cli_strcmp(z,"-html")==0 ){
- data.mode = MODE_Html;
- }else if( cli_strcmp(z,"-list")==0 ){
- data.mode = MODE_List;
- }else if( cli_strcmp(z,"-quote")==0 ){
- data.mode = MODE_Quote;
- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma);
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
- }else if( cli_strcmp(z,"-line")==0 ){
- data.mode = MODE_Line;
- }else if( cli_strcmp(z,"-column")==0 ){
- data.mode = MODE_Column;
- }else if( cli_strcmp(z,"-json")==0 ){
- data.mode = MODE_Json;
- }else if( cli_strcmp(z,"-markdown")==0 ){
- data.mode = MODE_Markdown;
- }else if( cli_strcmp(z,"-table")==0 ){
- data.mode = MODE_Table;
- }else if( cli_strcmp(z,"-box")==0 ){
- data.mode = MODE_Box;
- }else if( cli_strcmp(z,"-csv")==0 ){
- data.mode = MODE_Csv;
- memcpy(data.colSeparator,",",2);
-#ifdef SQLITE_HAVE_ZLIB
- }else if( cli_strcmp(z,"-zip")==0 ){
- data.openMode = SHELL_OPEN_ZIPFILE;
-#endif
- }else if( cli_strcmp(z,"-append")==0 ){
- data.openMode = SHELL_OPEN_APPENDVFS;
-#ifndef SQLITE_OMIT_DESERIALIZE
- }else if( cli_strcmp(z,"-deserialize")==0 ){
- data.openMode = SHELL_OPEN_DESERIALIZE;
- }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
- data.szMax = integerValue(argv[++i]);
-#endif
- }else if( cli_strcmp(z,"-readonly")==0 ){
- data.openMode = SHELL_OPEN_READONLY;
- }else if( cli_strcmp(z,"-nofollow")==0 ){
- data.openFlags |= SQLITE_OPEN_NOFOLLOW;
- }else if( cli_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);
- }else if( cli_strcmp(z,"-tabs")==0 ){
- data.mode = MODE_List;
- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab);
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row);
- }else if( cli_strcmp(z,"-separator")==0 ){
- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
- "%s",cmdline_option_value(argc,argv,++i));
- }else if( cli_strcmp(z,"-newline")==0 ){
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
- "%s",cmdline_option_value(argc,argv,++i));
- }else if( cli_strcmp(z,"-nullvalue")==0 ){
- sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
- "%s",cmdline_option_value(argc,argv,++i));
- }else if( cli_strcmp(z,"-header")==0 ){
- data.showHeader = 1;
- ShellSetFlag(&data, SHFLG_HeaderSet);
- }else if( cli_strcmp(z,"-noheader")==0 ){
- data.showHeader = 0;
- ShellSetFlag(&data, SHFLG_HeaderSet);
- }else if( cli_strcmp(z,"-echo")==0 ){
- ShellSetFlag(&data, SHFLG_Echo);
- }else if( cli_strcmp(z,"-eqp")==0 ){
- data.autoEQP = AUTOEQP_on;
- }else if( cli_strcmp(z,"-eqpfull")==0 ){
- data.autoEQP = AUTOEQP_full;
- }else if( cli_strcmp(z,"-stats")==0 ){
- data.statsOn = 1;
- }else if( cli_strcmp(z,"-scanstats")==0 ){
- data.scanstatsOn = 1;
- }else if( cli_strcmp(z,"-backslash")==0 ){
- /* Undocumented command-line option: -backslash
- ** Causes C-style backslash escapes to be evaluated in SQL statements
- ** prior to sending the SQL into SQLite. Useful for injecting
- ** crazy bytes in the middle of SQL statements for testing and debugging.
- */
- ShellSetFlag(&data, SHFLG_Backslash);
- }else if( cli_strcmp(z,"-bail")==0 ){
- /* No-op. The bail_on_error flag should already be set. */
- }else if( cli_strcmp(z,"-version")==0 ){
- printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
- return 0;
- }else if( cli_strcmp(z,"-interactive")==0 ){
- stdin_is_interactive = 1;
- }else if( cli_strcmp(z,"-batch")==0 ){
- stdin_is_interactive = 0;
- }else if( cli_strcmp(z,"-utf8")==0 ){
#if SHELL_WIN_UTF8_OPT
- console_utf8 = 1;
-#endif /* SHELL_WIN_UTF8_OPT */
- }else if( cli_strcmp(z,"-heap")==0 ){
- i++;
- }else if( cli_strcmp(z,"-pagecache")==0 ){
- i+=2;
- }else if( cli_strcmp(z,"-lookaside")==0 ){
- i+=2;
- }else if( cli_strcmp(z,"-threadsafe")==0 ){
- i+=2;
- }else if( cli_strcmp(z,"-nonce")==0 ){
- i += 2;
- }else if( cli_strcmp(z,"-mmap")==0 ){
- i++;
- }else if( cli_strcmp(z,"-memtrace")==0 ){
- i++;
-#ifdef SQLITE_ENABLE_SORTER_REFERENCES
- }else if( cli_strcmp(z,"-sorterref")==0 ){
- i++;
-#endif
- }else if( cli_strcmp(z,"-vfs")==0 ){
- i++;
-#ifdef SQLITE_ENABLE_VFSTRACE
- }else if( cli_strcmp(z,"-vfstrace")==0 ){
- i++;
-#endif
-#ifdef SQLITE_ENABLE_MULTIPLEX
- }else if( cli_strcmp(z,"-multiplex")==0 ){
- i++;
-#endif
- }else if( cli_strcmp(z,"-help")==0 ){
- usage(1);
- }else if( cli_strcmp(z,"-cmd")==0 ){
- /* Run commands that follow -cmd first and separately from commands
- ** that simply appear on the command-line. This seems goofy. It would
- ** be better if all commands ran in the order that they appear. But
- ** we retain the goofy behavior for historical compatibility. */
- if( i==argc-1 ) break;
- z = cmdline_option_value(argc,argv,++i);
- if( z[0]=='.' ){
- rc = do_meta_command(z, &data);
- if( rc && bail_on_error ) return rc==2 ? 0 : rc;
- }else{
- open_db(&data, 0);
- rc = shell_exec(&data, z, &zErrMsg);
- if( zErrMsg!=0 ){
- utf8_printf(stderr,"Error: %s\n", zErrMsg);
- if( bail_on_error ) return rc!=0 ? rc : 1;
- }else if( rc!=0 ){
- utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
- if( bail_on_error ) return rc;
- }
- }
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
- }else if( cli_strncmp(z, "-A", 2)==0 ){
- if( nCmd>0 ){
- utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands"
- " with \"%s\"\n", z);
- return 1;
- }
- open_db(&data, OPEN_DB_ZIPFILE);
- if( z[2] ){
- argv[i] = &z[2];
- arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
- }else{
- arDotCommand(&data, 1, argv+i, argc-i);
- }
- readStdin = 0;
- break;
-#endif
- }else if( cli_strcmp(z,"-safe")==0 ){
- data.bSafeMode = data.bSafeModePersist = 1;
- }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
- /* Acted upon in first pass. */
+ if( console_utf8 && stdin_is_interactive ){
+ console_prepare();
}else{
- utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
- raw_printf(stderr,"Use -help for a list of options.\n");
- return 1;
+ setBinaryMode(stdin, 0);
+ console_utf8 = 0;
}
- data.cMode = data.mode;
- }
-#if SHELL_WIN_UTF8_OPT
- if( console_utf8 && stdin_is_interactive ){
- console_prepare();
- }else{
- setBinaryMode(stdin, 0);
- console_utf8 = 0;
- }
#endif
- if( !readStdin ){
- /* Run all arguments that do not begin with '-' as if they were separate
- ** command-line inputs, except for the argToSkip argument which contains
- ** the database filename.
- */
- for(i=0; i<nCmd; i++){
- if( azCmd[i][0]=='.' ){
- rc = do_meta_command(azCmd[i], &data);
- if( rc ){
- free(azCmd);
- return rc==2 ? 0 : rc;
- }
- }else{
- open_db(&data, 0);
- echo_group_input(&data, azCmd[i]);
- rc = shell_exec(&data, azCmd[i], &zErrMsg);
- if( zErrMsg || rc ){
- if( zErrMsg!=0 ){
- utf8_printf(stderr,"Error: %s\n", zErrMsg);
- }else{
- utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]);
- }
- sqlite3_free(zErrMsg);
- free(azCmd);
- return rc!=0 ? rc : 1;
- }
- }
- }
- }else{
- /* Run commands received from standard input
- */
- if( stdin_is_interactive ){
- char *zHome;
- char *zHistory;
- int nHistory;
- printf(
- "SQLite version %s %.19s\n" /*extra-version-info*/
- "Enter \".help\" for usage hints.\n",
- sqlite3_libversion(), sqlite3_sourceid()
- );
- if( warnInmemoryDb ){
- printf("Connected to a ");
- printBold("transient in-memory database");
- printf(".\nUse \".open FILENAME\" to reopen on a "
- "persistent database.\n");
- }
- zHistory = getenv("SQLITE_HISTORY");
- if( zHistory ){
- zHistory = strdup(zHistory);
- }else if( (zHome = find_home_dir(0))!=0 ){
- nHistory = strlen30(zHome) + 20;
- if( (zHistory = malloc(nHistory))!=0 ){
- sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
+ if( !argsData.readStdin ){
+ /* Run all arguments that are not the DB name or do not begin with '-'
+ ** as if they were separate command-line inputs. */
+ for(i=0; i<cmdArgs.nCmd && rc<2; i++){
+ set_invocation_cmd(cmdArgs.azCmd[i]);
+ drc = process_input(&datai);
+ rc = (drc>2)? 2 : drc;
+ if( rc>0 ){
+ goto shell_bail;
}
}
- if( zHistory ){ shell_read_history(zHistory); }
+ }else{
+ /* Run commands received from standard input
+ */
+ if( stdin_is_interactive ){
+ char *zHome;
+ char *zHistory = 0;
+ if( argsData.bQuiet ){
+ /* bQuiet is almost like normal interactive, but quieter
+ ** and avoids history keeping and line editor completions. */
+ mainPrompt[0] = 0;
+ continuePrompt[0] = 0;
+ }else{
+ fprintf(STD_OUT,
+ "SQLite version %s %.19s\n" /*extra-version-info*/
+ "Enter \".help\" for usage hints.\n",
+ sqlite3_libversion(), sqlite3_sourceid()
+ );
+ if( warnInmemoryDb ){
+ fprintf(STD_OUT, "Connected to a ");
+ printBold("transient in-memory database");
+ fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
+ "persistent database.\n");
+ }
+ zHistory = getenv("SQLITE_HISTORY");
+ if( zHistory ){
+ zHistory = strdup(zHistory);
+ }else if( (zHome = find_home_dir(0))!=0 ){
+ int nHistory = strlen30(zHome) + 20;
+ if( (zHistory = malloc(nHistory))!=0 ){
+ sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
+ }
+ }
+ if( zHistory ){ shell_read_history(zHistory); }
#if HAVE_READLINE || HAVE_EDITLINE
- rl_attempted_completion_function = readline_completion;
+ rl_attempted_completion_function = readline_completion;
#elif HAVE_LINENOISE
- linenoiseSetCompletionCallback(linenoise_completion);
+ linenoiseSetCompletionCallback(linenoise_completion);
#endif
- data.in = 0;
- rc = process_input(&data);
- if( zHistory ){
- shell_stifle_history(2000);
- shell_write_history(zHistory);
- free(zHistory);
+ }
+ datai.pInSource = &termInSource; /* read from stdin interactively */
+ drc = process_input(&datai);
+ rc = (drc>2)? 2 : drc;
+ if( !bQuiet ){
+ if( zHistory ){
+ shell_stifle_history(2000);
+ shell_write_history(zHistory);
+ free(zHistory);
+ }
+ }
+ }else{
+ datai.pInSource = &stdInSource; /* read from stdin without prompts */
+ drc = process_input(&datai);
+ rc = (drc>2)? 2 : drc;
}
- }else{
- data.in = stdin;
- rc = process_input(&data);
}
- holder_free(entry_mark);
+ }else{
+ /* An abrupt, stack-ripping exit arrives here. */
+ }
+ shell_bail:
++ /* All users of resource managment should have left its stack as
++ ** it was near the beginning of shell execution. Verify this. */
++ assert(main_resource_mark==holder_mark());
++ if( cmdArgs.azCmd!=0 ){
++ free(cmdArgs.azCmd);
++ cmdArgs.azCmd = 0;
+ }
#ifndef SQLITE_SHELL_FIDDLE
/* In WASM mode we have to leave the db state in place so that
- ** client code can "push" SQL into it after this call returns. */
- free(azCmd);
- set_table_name(&data, 0);
- if( data.db ){
- session_close_all(&data, -1);
- close_db(data.db);
+ ** client code can "push" SQL into it after this call returns.
+ ** For that build, just bypass freeing all acquired resources.
+ */
+ set_table_name(&datax, 0);
+ if( datax.dbUser ){
+ session_close_all(&datai, -1);
+# if SHELL_DYNAMIC_EXTENSION
+ notify_subscribers(&datai, NK_DbAboutToClose, datax.dbUser);
+# endif
+ close_db(datax.dbUser);
}
- # ifdef SQLITE_DEBUG
- /* Do this redundantly with atexit() to aid memory leak reporting. */
- for(i=0; i<ArraySize(data.aAuxDb); i++){
- sqlite3_free(data.aAuxDb[i].zFreeOnClose);
- if( data.aAuxDb[i].db ){
- session_close_all(&data, i);
- close_db(data.aAuxDb[i].db);
++ /* Do this redundantly with atexit() to aid memory leak reporting,
++ ** or if CLI is embedded, to get it done before return. */
+ sqlite3_mutex_free(pGlobalDbLock);
+ pGlobalDbLock = 0;
- # endif
+ for(i=0; i<ArraySize(datai.aAuxDb); i++){
+ sqlite3_free(datai.aAuxDb[i].zFreeOnClose);
+ if( datai.aAuxDb[i].db ){
+ session_close_all(&datai, i);
+ close_db(datai.aAuxDb[i].db);
}
}
find_home_dir(1);