}
/*
- ** This routine reads a line of text from FILE in, stores it in
- ** memory obtained from malloc() and returns a pointer to it.
- ** NULL is returned at end of file, or if malloc() fails.
+ ** Arrange for shell input from either a FILE or a string.
+ ** For applicable invariants, see str_line_get(...) which is
+ ** the only modifier of this struct. (3rd and 4th members)
+ ** All other members are simply initialized to select the
+ ** input stream and track input sources, set by whatever
+ ** routines redirect the input.
+ */
+ typedef struct InSource {
+ FILE *inFile; /* Will be 0 when input is to be taken from string. */
+ char *zStrIn; /* Will be 0 when no input is available from string. */
+ int iReadOffset; /* Offset into zStrIn where next "read" to be done. */
+ int lineno; /* How many lines have been read from this source */
+ const char *zSourceSay; /* For complaints, keep a name for this source */
+ struct InSource *pFrom; /* and redirect tracking to aid unraveling. */
+ } InSource;
+ #define INSOURCE_STR_REDIR(str, tagTo, isFrom) {0, str, 0, 0, tagTo, isFrom}
+ #define INSOURCE_FILE_REDIR(fh, tagTo, isFrom) {fh, 0, 0, 0, tagTo, isFrom}
+ #define INSOURCE_IS_INTERACTIVE(pIS) \
+ ((pIS)==&termInSource && stdin_is_interactive )
+
+ /* This instance's address is taken as part of interactive input test. */
+ static InSource termInSource = { 0 /*stdin*/, 0, 0, 0, "<terminal>", 0};
+ static InSource stdInSource = { 0 /*stdin*/, 0, 0, 0, "<stdin>", 0};
+ static void init_std_inputs( FILE *pIn ){
+ termInSource.inFile = pIn;
+ stdInSource.inFile = pIn;
+ }
+
+ static char *str_line_get( char *zBuf, int ncMax, InSource *pInSrc){
+ if( pInSrc->inFile!=0 ){
+ char *zRet = fgets(zBuf, ncMax, pInSrc->inFile );
+ if( zRet!=0 ){
+ int iRead = strlen30(zRet);
+ if( iRead>0 && zRet[iRead-1]=='\n' ) ++pInSrc->lineno;
+ /* Consider: record line length to avoid rescan for it. */
+ return zRet;
+ }
+ }
+ else if( pInSrc->zStrIn!=0 ){
+ char *zBegin = pInSrc->zStrIn + pInSrc->iReadOffset;
+ if( *zBegin!=0 ){
+ int iTake = 0;
+ char c;
+ ncMax -= 1;
+ while( iTake<ncMax && (c=zBegin[iTake])!=0 ){
+ zBuf[iTake++] = c;
+ if( c=='\n' ){
+ ++pInSrc->lineno;
+ break;
+ }
+ }
+ if( ncMax>=0 ) zBuf[iTake] = 0;
+ pInSrc->iReadOffset += iTake;
+ /* Consider: record line length to avoid rescan for it. */
+ return zBuf;
+ }
+ }
+ return 0;
+ }
+
+ /*
+ ** This routine reads a line of text from designated stream source,
+ ** stores the text in memory obtained from malloc() and returns a
+ ** pointer to the text. NULL is returned at end of file.
+ ** There will be no return if malloc() fails.
**
- ** If zLine is not NULL then it is (and must be) a malloced buffer
- ** returned from a previous call to this routine that may be reused.
+ ** The trailing newline (or other line-end chars) are stripped.
+ **
+ ** If zLine is not NULL then it is a malloced buffer returned from
+ ** a previous call to this routine that may be reused.
*/
- static char *local_getline(char *zLine, FILE *in){
- #ifdef SQLITE_DEBUG
- static char *zLinePrior = 0;
- /* If zLine is passed in, not for the first time, it *must* be prior return,
- ** otherwise the assumption made here as to its allocated size is shaky. */
- assert(zLine==0 || zLinePrior==0 || zLine==zLinePrior);
- # define ZLINE_PRIOR(z) zLinePrior = z
- #else
- # define ZLINE_PRIOR(z)
- #endif
- int nLine = zLine==0 ? 0 : 100; /* Length now (or to be)allocated */
- int n = 0; /* Count of chars accumulated now */
-
+ static char *local_getline(char *zLine, InSource *pInSrc){
+ int nLine = zLine==0 ? 0 : 100;
+ int n = 0;
-
while( 1 ){
if( n+100>nLine ){
nLine = nLine*2 + 100;
return zLine;
}
-
/*
- ** Retrieve a single line of input text.
+ ** Retrieve a single line of input text from designated input source.
**
- ** If in==0 then read from standard input and prompt before each line.
- ** If isContinuation is true, then a continuation prompt is appropriate.
- ** If isContinuation is zero, then the main prompt should be used.
+ ** If the input is interactive, then issue either the continuation prompt
+ ** or the main prompt, per isContinuation, before reading a line from
+ ** standard input. Otherwise, just read a line from the specified source.
**
** If zPrior is not NULL then it is a buffer from a prior call to this
** routine that can be reused.
** The result is stored in space obtained from malloc() and must either
** be freed by the caller or else passed back into this routine via the
** zPrior argument for reuse.
+ **
+ ** If this function is called until it returns NULL, and the prior return
+ ** has been passed in for resuse, then the caller need/must not free it.
+ ** Otherwise, (in case of an early termination of reading from the given
+ ** input), the caller is responsible for freeing a prior, non-NULL return.
+ **
+ ** The trailing newline (or its ilk), if any, is trimmed.
+ ** The input line number is adjusted (via delegation or directly.)
*/
- static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
- char *zPrompt;
- char *zResult;
- if( in!=0 ){
- zResult = local_getline(zPrior, in);
+ static char *one_input_line(InSource *pInSrc, char *zPrior, int isContinuation){
+ if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
+ return local_getline(zPrior, pInSrc);
}else{
- zPrompt = isContinuation ? continuePrompt : mainPrompt;
+ char *zPrompt = isContinuation ? continuePrompt : mainPrompt;
#if SHELL_USE_LOCAL_GETLINE
- fprintf(STD_OUT, "%s", zPrompt);
- fflush(STD_OUT);
- zResult = local_getline(zPrior, STD_IN);
+ printf("%s", zPrompt);
+ fflush(stdout);
+ return local_getline(zPrior, pInSrc);
#else
+ char *zResult;
free(zPrior);
zResult = shell_readline(zPrompt);
+ ++pInSrc->lineno;
if( zResult && *zResult ) shell_add_history(zResult);
+ return zResult;
#endif
}
- return zResult;
}
-
/*
** Return the value of a hexadecimal digit. Return -1 if the input
** is not a hex digit.
char zPrefix[100]; /* Graph prefix */
};
-# define SHELL_EXTENSIONS (~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS
+ /* By default, omit the extension options that are not done yet. */
+ #ifndef SHELL_OMIT_EXTENSIONS
+ # define SHELL_OMIT_EXTENSIONS 6
+ #endif
+
+ /* Selectively omit features with one PP variable. Value is true iff
+ ** either x is not defined or defined with 0 in bitnum bit position.
+ */
+ #define NOT_IFDEF_BIT(x,bitnum) (x? (!(x & (1<<bitnum))) : !(x+0))
+
+ /* Whether build will include extended input parsing option */
+ #define SHEXT_PARSING_BIT 0
+ #define SHELL_EXTENDED_PARSING \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_PARSING_BIT)
+ /* Whether build will include dynamic dot-command extension */
+ #define SHEXT_DYNCMDS_BIT 1
+ #define SHELL_DYNAMIC_COMMANDS \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNCMDS_BIT)
+ /* Whether build will include expansion of variables in dot-commands */
+ #define SHEXT_VAREXP_BIT 2
+ #define SHELL_VARIABLE_EXPANSION \
+ NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_VAREXP_BIT)
+
+ #define SHELL_ALL_EXTENSIONS \
+ (1<<SHEXT_PARSING_BIT)+(1<<SHEXT_DYNCMDS_BIT)+(1<<SHEXT_VAREXP_BIT)
+ #if !defined(SHELL_OMIT_EXTENSIONS)
+ # define SHELL_EXTENSIONS SHELL_ALL_EXTENSIONS
+ #else
++# define SHELL_EXTENSIONS ((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS)
+ #endif
+
+ /* Runtime test for shell extended parsing, given ShellState pointer */
+ #if SHELL_EXTENDED_PARSING
+ # define SHEXT_PARSING(pSS) ((pSS->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
+ #else
+ # define SHEXT_PARSING(pSS) 0
+ #endif
+
+ /* Runtime test for shell variable expansion, given ShellState pointer */
+ #if SHELL_EXTENDED_PARSING
+ # define SHEXT_VAREXP(pSS) ((pSS->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
+ #else
+ # define SHEXT_VAREXP(pSS) 0
+ #endif
+
/* Parameters affecting columnar mode result display (defaulting together) */
typedef struct ColModeOpts {
int iWrap; /* In columnar modes, wrap lines reaching this limit */
typedef struct ShellState ShellState;
struct ShellState {
sqlite3 *db; /* The database */
- int abruptExit; /* Flag for immediate shell exit, exit code */
- int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
++ int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
+ u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
u8 autoExplain; /* Automatically turn on .explain mode */
u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */
u8 autoEQPtest; /* autoEQP is in test mode */
u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */
u8 nEqpLevel; /* Depth of the EQP output graph */
u8 eTraceType; /* SHELL_TRACE_* value for type of trace */
- u8 bSafeMode; /* True to prohibit unsafe operations */
- u8 bSafeModePersist; /* The long-term value of bSafeMode */
+ u8 bSafeMode; /* True when unsafe operations are prohibited */
+ u8 bSafeModeFuture; /* See updateSafeMode regarding use of this. */
+ u8 bAllowSysDump; /* Allow .dump use for sqlite_* tables. */
+ u8 bExtendedDotCmds; /* Bits set to enable various shell extensions */
+ /* Output mode state subject to systematic save/restore: (See OM_STATE.) */
+ u8 showHeader; /* True to show column names in List or Column mode */
+ u16 shellFlgs; /* Various flags */
+ u8 mode; /* An output mode setting */
ColModeOpts cmOpts; /* Option values affecting columnar mode output */
-
+ char colSeparator[20]; /* Column separator character for several modes */
+ char rowSeparator[20]; /* Row separator character for MODE_Ascii */
+ /* Output mode state-keep for systematic save/restore: (See OM_STATE.) */
+ u8 nSavedModes; /* number of valid items in next array */
+ OutputModeSave *pModeStack[MODE_STACK_MAX]; /* saved mode data buffers */
+ /* Output mode state-keep for certain ad-hoc save/restore ops: */
+ u8 cMode; /* temporary output mode for the current query */
+ u8 normalMode; /* Output mode before ".explain on" */
+ int *colWidth; /* Requested width of each column in columnar modes */
+ int *actualWidth; /* Actual width of each column */
+ int nWidth; /* Number of slots in colWidth[] and actualWidth[] */
+ char nullValue[20]; /* Text to print for NULL retrieved from database */
+
unsigned statsOn; /* True to display memory stats before each finalize */
unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */
-- int inputNesting; /* Track nesting level of .read and other redirects */
int outCount; /* Revert to stdout when reaching zero */
int cnt; /* Number of records displayed so far */
- int lineno; /* Line number of last line read from in */
- int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
- FILE *in; /* Read commands from this stream */
++
++ int inputNesting; /* Track nesting level of .read and other redirects */
+ InSource *pInSource; /* Read commands and SQL from this stream source */
++
FILE *out; /* Write results here */
FILE *traceOut; /* Output for sqlite3_trace() */
++ int abruptExit; /* Flag for immediate shell exit, exit code */
int nErr; /* Number of errors seen */
- int mode; /* An output mode setting */
- int modePrior; /* Saved mode */
- int cMode; /* temporary output mode for the current query */
- int normalMode; /* Output mode before ".explain on" */
int writableSchema; /* True if PRAGMA writable_schema=ON */
- int showHeader; /* True to show column names in List or Column mode */
int nCheck; /* Number of ".check" commands run */
unsigned nProgress; /* Number of progress callbacks encountered */
unsigned mxProgress; /* Maximum progress callbacks before failing */
unsigned flgProgress; /* Flags for the progress callback */
- unsigned shellFlgs; /* Various flags */
- unsigned priorShFlgs; /* Saved copy of flags */
++
sqlite3_int64 szMax; /* --maxsize argument to .open */
char *zDestTable; /* Name of destination table when MODE_Insert */
char *zTempFile; /* Temporary file that might need deleting */
ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */
};
- /*
+/*
+** Limit input nesting via .read or any other input redirect.
+** It's not too expensive, so a generous allowance can be made.
+*/
+#define MAX_INPUT_NESTING 25
+
++/*
+** This procedure updates the bSafeMode flag after completion of any
+** operation (meta-command or SQL submission) that counts as one for
+** which safe mode might be suspended.
+** bSafeModeFuture has 3 states salient here:
+** equal 0 => Safe mode is and will remain inactive.
+** equal 1 => Safe mode is and will remain active.
+** N > 1 => Safe mode is suspended for N-1 operations.
+*/
+static void updateSafeMode(ShellState *pSS){
+ switch( pSS->bSafeModeFuture ){
+ case 2:
+ default:
+ --pSS->bSafeModeFuture;
+ /* Fall thru, suspension is in effect. */
+ case 0:
+ pSS->bSafeMode = 0;
+ break;
+ case 1:
+ pSS->bSafeMode = 1;
+ }
+}
/* Allowed values for ShellState.autoEQP
*/
#define MODE_Line 0 /* One column per line. Blank line between records */
#define MODE_Column 1 /* One record per line in neat columns */
#define MODE_List 2 /* One record per line with a separator */
-#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */
-#define MODE_Html 4 /* Generate an XHTML table */
-#define MODE_Insert 5 /* Generate SQL "insert" statements */
-#define MODE_Quote 6 /* Quote values as for SQL */
-#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */
-#define MODE_Csv 8 /* Quote strings, numbers are plain */
-#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */
-#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */
-#define MODE_Pretty 11 /* Pretty-print schemas */
-#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */
-#define MODE_Json 13 /* Output JSON */
-#define MODE_Markdown 14 /* Markdown formatting */
-#define MODE_Table 15 /* MySQL-style table formatting */
-#define MODE_Box 16 /* Unicode box-drawing characters */
-#define MODE_Count 17 /* Output only a count of the rows of output */
-#define MODE_Off 18 /* No query output shown */
-
-static const char *modeDescr[] = {
- "line",
- "column",
- "list",
- "semi",
- "html",
- "insert",
- "quote",
- "tcl",
- "csv",
- "explain",
- "ascii",
- "prettyprint",
- "eqp",
- "json",
- "markdown",
- "table",
- "box",
- "count",
- "off"
+#define MODE_Html 3 /* Generate an XHTML table */
+#define MODE_Tcl 4 /* Generate ANSI-C or TCL quoted elements */
+#define MODE_Csv 5 /* Quote strings, numbers are plain */
- #define MODE_Tab 6 /* Transitory */
++#define MODE_Tab 6 /* Transitory, an alias for MODE_List with tabs */
+#define MODE_Insert 7 /* Generate SQL "insert" statements */
+#define MODE_Quote 8 /* Quote values as for SQL */
+#define MODE_Ascii 9 /* Use ASCII unit and record separators (0x1F/0x1E) */
+#define MODE_Markdown 10 /* Markdown formatting */
+#define MODE_Table 11 /* MySQL-style table formatting */
+#define MODE_Box 12 /* Unicode box-drawing characters */
+#define MODE_Count 13 /* Output only a count of the rows of output */
+#define MODE_Off 14 /* No query output shown */
+#define MODE_Json 15 /* Output JSON */
+#define MODE_EQP 16 /* Converts EXPLAIN QUERY PLAN output into a graph */
+#define MODE_Explain 17 /* Like MODE_Column, but do not truncate data */
+#define MODE_Pretty 18 /* Pretty-print schemas */
+#define MODE_Semi 19 /* Same as MODE_List but append ";" to each line */
- #define MODE_COUNT_OF 20
++#define MODE_COUNT_OF 20 /* also an invalid MODE_x value */
++
++/* Note that some of above ordering is assumed for this mode classification. */
++#define MODE_IS_COLUMNAR(m) \
++ ( (m)==MODE_Column||((m)>=MODE_Markdown&&(m)<=MODE_Box) )
++
+static struct {
+ const char *zModeName;
+ u8 bDepluralize;
+ u8 bUserBlocked;
+ u8 iAliasFor;
+} modeDescr[] = {
+ { "lines", 1, 0, MODE_Line },
+ { "columns", 1, 0, MODE_Column },
+ { "list", 0, 0, MODE_List },
+ { "html", 0, 0, MODE_Html },
+ { "tcl", 0, 0, MODE_Tcl },
+ { "csv", 0, 0, MODE_Csv },
+ { "tabs", 1, 0, MODE_List },
+ { "insert", 0, 0, MODE_Insert },
+ { "quote", 0, 0, MODE_Quote },
+ { "ascii", 0, 0, MODE_Ascii },
+ { "markdown", 0, 0, MODE_Markdown },
+ { "table", 0, 0, MODE_Table },
+ { "box", 0, 0, MODE_Box },
+ { "count", 0, 0, MODE_Count },
+ { "off", 0, 0, MODE_Off },
+ { "json", 0, 0, MODE_Json },
+ { "eqp", 0, 1, MODE_EQP },
+ { "explain", 0, 1, MODE_Explain },
+ { "prettyprint", 0, 1, MODE_Pretty },
+ { "semi", 0, 1, MODE_Semi }
};
/*
va_start(ap, zErrMsg);
zMsg = sqlite3_vmprintf(zErrMsg, ap);
va_end(ap);
- raw_printf(STD_ERR, "line %d: ", p->lineno);
- raw_printf(stderr, "line %d: ", p->pInSource->lineno);
- utf8_printf(stderr, "%s\n", zMsg);
- exit(1);
++ raw_printf(STD_ERR, "line %d: ", p->pInSource->lineno);
+ utf8_printf(STD_ERR, "%s\n", zMsg);
+ p->abruptExit = 3;
+ return 1;
}
+ return 0;
}
/*
**
** No bindings occur if this table does not exist. The name of the table
** begins with "sqlite_" so that it will not collide with ordinary application
- ** tables. The table must be in the TEMP schema.
+ ** tables. The table must be in the TEMP schema. Only rows with
+ ** uses=PTU_Binding are eligible for parameter binding.
*/
- static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
- int nVar;
+ static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){
- int nVar;
++ int nVar = sqlite3_bind_parameter_count(pStmt);
int i;
int rc;
sqlite3_stmt *pQ = 0;
-- nVar = sqlite3_bind_parameter_count(pStmt);
- if( nVar==0 ) return; /* Nothing to do */
- if( sqlite3_table_column_metadata(pArg->db, "TEMP", "sqlite_parameters",
- "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
- return; /* Parameter table does not exist */
- }
- rc = sqlite3_prepare_v2(pArg->db,
+ if( nVar==0 || !param_table_exists(db) ) return; /* Nothing to do */
+ rc = sqlite3_prepare_v2(db,
- "SELECT value FROM "PARAM_TABLE_SNAME
- " WHERE key=?1 AND uses=0", -1, &pQ, 0);
+ "SELECT value FROM temp.sqlite_parameters"
+ " WHERE key=?1", -1, &pQ, 0);
if( rc || pQ==0 ) return;
- assert( PTU_Binding==0 ); /* instead of working symbol value into query */
for(i=1; i<=nVar; i++){
char zNum[30];
const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
/*
** Run a prepared statement and output the result in one of the
--** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table,
--** or MODE_Box.
++** table-oriented formats, for which MODE_IS_COLUMNAR(m) is true:
++** MODE_Column, MODE_Markdown, MODE_Table, or MODE_Box.
**
** This is different from ordinary exec_prepared_stmt() in that
** it has to run the entire query and gather the results into memory
int rc;
sqlite3_uint64 nRow = 0;
-- if( pArg->cMode==MODE_Column
-- || pArg->cMode==MODE_Table
-- || pArg->cMode==MODE_Box
-- || pArg->cMode==MODE_Markdown
-- ){
++ if( MODE_IS_COLUMNAR(pArg->cMode) ){
exec_prepared_stmt_columnar(pArg, pStmt);
return;
}
int i;
int iSample = 0;
- raw_printf(stderr,
+ if( pState->bSafeMode ){
++ raw_printf(STD_ERR,
+ "Cannot run experimental commands such as \"%s\" in safe mode\n",
+ azArg[0]);
+ return 1;
+ }
assert( pState->expert.pExpert==0 );
memset(&pState->expert, 0, sizeof(ExpertInfo));
if( rc==SQLITE_OK ){
pState->expert.pExpert = sqlite3_expert_new(pState->db, &zErr);
if( pState->expert.pExpert==0 ){
- raw_printf(stderr, "sqlite3_expert_new: %s\n",
- raw_printf(stderr, "sqlite3_expert_new: %s\n", zErr ? zErr : "out of memory");
++ raw_printf(STD_ERR, "sqlite3_expert_new: %s\n",
+ zErr ? zErr : "out of memory");
rc = SQLITE_ERROR;
}else{
sqlite3_expert_config(
return rc;
}
+/* Configure help text generation to have coalesced secondary
+ * help lines with trailing newlines on all help lines.
+ */
+DISPATCH_CONFIG[
+ HELP_COALESCE=1
+];
+#define HELP_TEXT_FMT "%s"
+/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
+ * Alternative is 0 and "%s\n" .
+ */
+
+/*
+** Output primary (single-line) help for a known command.
+*/
+static void showPrimaryHelp(FILE *out, const char *zCmd){
+ const char **pzH;
+ int nc = strlen30(zCmd);
+ for(pzH = azHelp; *pzH != 0; ++pzH){
+ if( **pzH=='.' && strncmp(zCmd, (*pzH)+1, nc)==0 ){
+ utf8_printf(out, HELP_TEXT_FMT, *pzH);
+ break;
+ }
+ }
+}
+
/*
-** Text of help messages.
+** Output various subsets of help text. These 5 are defined:
+** 1. For all commands, primary help text only.
+** 2. For all commands, complete help text.
+** 3. For multiple commands matching a pattern, primary help text only.
+** 4. For a single matched command, complete help text.
+** 5. For commands whose help contains a pattern, complete help text.
+** These variations are indicated thusly:
+** 1. zPattern is NULL
+** 2. zPattern is ""
+** 3. zPattern is a prefix matching more than one command
+** 4. zPattern is a word or prefix matching just one command
+** 5. zPattern is neither case 3 or 4 but is found in complete help text
**
-** The help text for each individual command begins with a line that starts
-** with ".". Subsequent lines are supplimental information.
+** Return the number of matches.
+*/
+static int showHelp(FILE *out, const char *zPattern){
+ int npm = 0;
+ char *zPat = sqlite3_mprintf(".%s*", zPattern? zPattern : "");
+ const char **pzHxtra;
+ const char **pzH;
+ int nma = 0;
+ shell_check_oom(zPat);
+ for(pzH = azHelp; *pzH != 0; ++pzH){
+ /* Look for all commands or those for which zPattern is an exact prefix */
+ if( *pzH[0]=='.' ){
+ if ( sqlite3_strglob(zPat, *pzH)==0 ){
+ utf8_printf(out, HELP_TEXT_FMT, *pzH);
+ pzHxtra = pzH + 1;
+ ++npm;
+ }
+ }else if( zPattern && *zPattern==0 ){
+ utf8_printf(out, HELP_TEXT_FMT, *pzH);
+ }
+ }
+ sqlite3_free(zPat);
+ if( npm==1 ){
+ /* When zPattern is a prefix of exactly one command, then include
+ ** the secondary help of that command, (beginning at *pzHxtra.) */
+ while( *pzHxtra !=0 && *pzHxtra[0]!='.' ){
+ utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++);
+ }
+ }
+ if( npm>0 )
+ return npm;
+
+ /* Having failed to match a command, look for commands whose help contains
+ * zPattern anywhere. Show the complete text of all such commands.
+ */
+ zPat = sqlite3_mprintf("%%%s%%", zPattern);
+ if( zPat==0 ) shell_out_of_memory();
+ for(pzH = azHelp; *pzH != 0;){
+ if( *pzH[0]=='.' ){
+ pzHxtra = pzH;
+ nma = 0;
+ }
+ if( sqlite3_strlike(zPat, *pzH, 0)==0 )
+ ++nma;
+ ++pzH;
+ if( nma>0 && (*pzH==0 || *pzH[0]=='.') ){
+ ++npm;
+ while( pzHxtra < pzH )
+ utf8_printf(out, HELP_TEXT_FMT, *pzHxtra++);
+ }
+ }
+ sqlite3_free(zPat);
+
+ return npm;
+}
+
+/* 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.
**
-** There must be two or more spaces between the end of the command and the
-** start of the description of what that command does.
+** 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 const char *(azHelp[]) = {
-#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
- ".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
- ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
- " Options:",
- " --append Use the appendvfs",
- " --async Write to FILE without journal and fsync()",
- ".bail on|off Stop after hitting an error. Default OFF",
- ".binary on|off Turn binary output on or off. Default OFF",
- ".cd DIRECTORY Change the working directory to DIRECTORY",
- ".changes on|off Show number of rows changed by SQL",
- ".clone NEWDB Clone data into NEWDB from the existing database",
- ".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",
- ".dbinfo ?DB? Show status information about the database",
- ".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",
- " --schema SCHEMA Dump table(s) from given SCHEMA",
- " 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\"",
+static char *readFile(const char *zName, int *pnByte){
+ FILE *in = fopen(zName, "rb");
+ long nIn;
+ size_t nRead;
+ char *pBuf;
+ if( in==0 ) return 0;
+ fseek(in, 0, SEEK_END);
+ nIn = ftell(in);
+ rewind(in);
+ pBuf = sqlite3_malloc64( nIn+1 );
+ if( pBuf==0 ){ fclose(in); return 0; }
+ nRead = fread(pBuf, nIn, 1, in);
+ fclose(in);
+ if( nRead!=1 ){
+ sqlite3_free(pBuf);
+ 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
- " trigger Like \"full\" but also show trigger bytecode",
- ".excel Display the output of next command in spreadsheet",
- " --bom Put a UTF8 byte-order mark on intermediate file",
- ".exit ?CODE? Exit this program with return-code CODE",
- ".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 ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize",
- " Repeat -all to see undocumented commands",
- ".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.",
-#ifndef SQLITE_OMIT_TEST_CONTROL
- ".imposter INDEX TABLE Create imposter table TABLE on index INDEX",
+
+/*
+** 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
- ".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",
+
+/*
+** 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
- ".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",
-#ifndef SQLITE_OMIT_LOAD_EXTENSION
- ".load FILE ?ENTRY? Load an extension library",
-#endif
- ".log FILE|off Turn logging on or off. FILE can be stderr/stdout",
- ".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 --width 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",
- ".nonce STRING Suspend safe mode for one command if nonce matches",
- ".nullvalue STRING Use STRING in place of NULL values",
- ".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\")",
- ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
- " Options:",
- " --append Use appendvfs to append database to the end of FILE",
-#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",
- ".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",
- ".parameter CMD ... Manage SQL parameter bindings and scripts table",
- " clear ?NAMES? Erase all or only given named parameters",
-#ifndef SQLITE_NOHAVE_SYSTEM
- " edit ?OPT? NAME ... Use edit() to create or alter parameter NAME",
- " OPT may be -t or -e to use edited value as text or evaluate it first.",
-#endif
- " init Initialize TEMP table for bindings and scripts",
- " list ?PATTERNS? List parameters table binding and script values",
- " Alternatively, to list just some or all names: ls ?PATTERNS?",
- " load ?FILE? ?NAMES? Load some or all named parameters from FILE",
- " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
- " save ?FILE? ?NAMES? Save some or all named parameters into FILE",
- " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
- " set ?TOPT? NAME VALUE Give SQL parameter NAME a value of VALUE",
- " NAME must begin with one of $,:,@,? for bindings, or with a letter",
- " to be executable; the value is following argument list, space-joined.",
- " Option TOPT may be one of {-b -i -n -r -t} to cast effective value",
- " to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
- " unset ?NAMES? Remove named parameter(s) from parameters table",
- ".print STRING... Print literal STRING, then a newline",
-#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",
- ".quit Exit this program",
- ".read FILE Read input from FILE or command output",
- " If FILE begins with \"|\", it is a command that generates the input.",
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
- ".recover Recover as much data as possible from corrupt db.",
- " --freelist-corrupt Assume the freelist is corrupt",
- " --recovery-db NAME Store recovery metadata in database file NAME",
- " --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
- ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
- ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)",
- ".scanstats on|off 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_\"",
-#if 0 /* .script is soon to vanish, or be implemented. */
- ".script CMD ... Manage and use SQL statement store (some TBD)",
- " clear ?NAMES? Erase some or all stored SQL statements",
- " edit NAME Use edit() to create or alter statement NAME",
- " execute ?NAMES? Execute the named SQL statements in order",
- " init Initialize the TEMP table that holds statements",
- " list List the currently stored statements",
- " load FILE ?NAMES? Load some or all SQL statements from FILE",
- " save FILE ?NAMES? Save some or all SQL statements into FILE",
- " set NAME VALUE Give SQL statement NAME a value of VALUE",
- " NAME must start with a letter, and effective",
- " value is space-joined, following argument list",
- " unset ?NAMES? Remove NAMES from statement store",
-#endif
- ".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",
-#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",
-#ifndef SQLITE_NOHAVE_SYSTEM
- ".shell CMD ARGS... Run CMD ARGS... in a system shell",
-#endif
-#ifndef SHELL_OMIT_EXTENSIONS
- ".shxopts ?SIGNED_OPTS? Show or alter shell extension options",
- " Run without arguments to see their self-descriptive names",
-#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",
-#ifndef SQLITE_NOHAVE_SYSTEM
- ".system CMD ARGS... Run CMD ARGS... in a system shell",
-#endif
- ".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
- ".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",
-#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",
-#endif
- ".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",
- ".x NAMES ... Excecute content of some .param set variable(s)",
- " Only variables whose name begins with a letter are eligible for this."
-};
-
-/* Like azHelp[], but for undocumented commands. */
-static const char *(azHelpUndoc[]) = {
- "These undocumented commands are for internal testing.\n"
- "They are subject to change without notice.\n"
- ".check GLOB Fail if output since .testcase does not match",
- ".seeargs Echo arguments separated or terminated by |",
- ".selftest-boolean Check boolean translation",
- ".selftest-integer Check integer conversion",
- ".testcase NAME Begin output redirect to 'testcase-out.txt'",
- ".testctrl CMD ... Run various sqlite3_test_control() operations",
- " Run \".testctrl\" with no arguments for details",
-};
-
-/* This literal's value AND address are used for help's workings. */
-static const char *zHelpAll = "-all";
/*
-** 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.
*/
- int deduceDatabaseType(const char *zName, int dfltZip){
-static int showHelp(FILE *out, const char *zPattern){
- int i = 0;
- int j = 0;
- int n = 0;
- int nHelp = ArraySize(azHelp);
- const char **azHelpText = azHelp;
- char *zPat;
- if( zPattern==zHelpAll ){
- /* It's zHelpAll, show help for the undocumented commands. */
- nHelp = ArraySize(azHelpUndoc);
- azHelpText = azHelpUndoc;
- }
- if( zPattern==0
- || zPattern[0]=='0'
- || strcmp(zPattern,"-a")==0
- || strcmp(zPattern,"-all")==0
- || strcmp(zPattern,"--all")==0
- ){
- /* Show all commands.
- * If no pattern, only one line per command.
- * If any --all-like pattern, show all help.
- */
- if( zPattern==0 ) zPattern = "";
- for(i=0; i<nHelp; i++){
- if( azHelpText[i][0]=='.' || zPattern[0] ){
- utf8_printf(out, "%s\n", azHelpText[i]);
- n++;
- }
++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 ){
+ return SHELL_OPEN_ZIPFILE;
+ }else{
+ return SHELL_OPEN_NORMAL;
}
+ }
+ n = fread(zBuf, 16, 1, f);
+ if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
+ fclose(f);
+ return SHELL_OPEN_NORMAL;
+ }
+ fseek(f, -25, SEEK_END);
+ n = fread(zBuf, 25, 1, f);
+ if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
+ rc = SHELL_OPEN_APPENDVFS;
}else{
- /* Look for commands that for which zPattern is an exact prefix */
- zPat = sqlite3_mprintf(".%s*", zPattern);
- shell_check_oom(zPat);
- for(i=0; i<nHelp; i++){
- if( sqlite3_strglob(zPat, azHelpText[i])==0 ){
- utf8_printf(out, "%s\n", azHelpText[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<nHelp-1 && azHelpText[j][0]!='.' ){
- utf8_printf(out, "%s\n", azHelpText[j]);
- j++;
- }
- }
- return n;
- }
- /* Look for commands that contain zPattern anywhere. Show the complete
- ** text of all commands that match. */
- zPat = sqlite3_mprintf("%%%s%%", zPattern);
- shell_check_oom(zPat);
- for(i=0; i<nHelp; i++){
- if( azHelpText[i][0]=='.' ) j = i;
- if( sqlite3_strlike(zPat, azHelpText[i], 0)==0 ){
- utf8_printf(out, "%s\n", azHelpText[j]);
- while( j<nHelp-1 && azHelpText[j+1][0]!='.' ){
- j++;
- utf8_printf(out, "%s\n", azHelpText[j]);
- }
- i = j;
- n++;
- }
+ fseek(f, -22, SEEK_END);
+ n = fread(zBuf, 22, 1, f);
+ if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
+ && zBuf[3]==0x06 ){
+ rc = SHELL_OPEN_ZIPFILE;
+ }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
+ rc = SHELL_OPEN_ZIPFILE;
}
- sqlite3_free(zPat);
}
- return n;
+ fclose(f);
- return rc;
++ return rc;
}
-/* Forward reference */
-static int process_input(ShellState *p);
-
+#ifndef SQLITE_OMIT_DESERIALIZE
/*
-** 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;
- if( in==0 ) return 0;
- fseek(in, 0, SEEK_END);
- nIn = ftell(in);
- rewind(in);
- pBuf = sqlite3_malloc64( nIn+1 );
- if( pBuf==0 ){ fclose(in); return 0; }
- nRead = fread(pBuf, nIn, 1, in);
- fclose(in);
- if( nRead!=1 ){
- sqlite3_free(pBuf);
- 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.
-*/
-u8 deduceDatabaseType(const char *zName, int dfltZip){
- FILE *f = fopen(zName, "rb");
- size_t n;
- u8 rc = SHELL_OPEN_UNSPEC;
- char zBuf[100];
- if( f==0 ){
- if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
- return SHELL_OPEN_ZIPFILE;
- }else{
- return SHELL_OPEN_NORMAL;
- }
- }
- n = fread(zBuf, 16, 1, f);
- if( n==1 && memcmp(zBuf, "SQLite format 3", 16)==0 ){
- fclose(f);
- return SHELL_OPEN_NORMAL;
- }
- fseek(f, -25, SEEK_END);
- n = fread(zBuf, 25, 1, f);
- if( n==1 && memcmp(zBuf, "Start-Of-SQLite3-", 17)==0 ){
- rc = SHELL_OPEN_APPENDVFS;
- }else{
- fseek(f, -22, SEEK_END);
- n = fread(zBuf, 22, 1, f);
- if( n==1 && zBuf[0]==0x50 && zBuf[1]==0x4b && zBuf[2]==0x05
- && zBuf[3]==0x06 ){
- rc = SHELL_OPEN_ZIPFILE;
- }else if( n==0 && dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
- rc = SHELL_OPEN_ZIPFILE;
- }
- }
- fclose(f);
- return rc;
-}
-
-#ifndef SQLITE_OMIT_DESERIALIZE
-/*
-** 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 the present input.
+** 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){
unsigned char *a = 0;
unsigned int x[16];
char zLine[1000];
if( zDbFilename ){
- in = fopen(zDbFilename, "r");
- if( in==0 ){
+ inRedir.inFile = fopen(zDbFilename, "r");
+ if( inRedir.inFile==0 ){
- utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
+ utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
return 0;
}
- nLine = 0;
+ p->pInSource = &inRedir;
}else{
- in = p->in;
- nLine = p->lineno;
- if( in==0 ) in = STD_IN;
+ /* Will read hex DB lines inline from present input, without redirect. */
+ if( INSOURCE_IS_INTERACTIVE(p->pInSource) ){
+ printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
+ p->pInSource->zSourceSay, zEndMarker);
- fflush(stdout);
++ fflush(STD_OUT);
+ }
}
*pnData = 0;
- nLine++;
- if( fgets(zLine, sizeof(zLine), in)==0 ) goto readHexDb_error;
+ if( str_line_get(zLine,sizeof(zLine), p->pInSource)==0 ) goto readHexDb_error;
rc = sscanf(zLine, "| size %d pagesize %d", &n, &pgsz);
if( rc!=2 ) goto readHexDb_error;
if( n<0 ) goto readHexDb_error;
shell_check_oom(a);
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( str_line_get(zLine,sizeof(zLine), p->pInSource)!=0 ){
rc = sscanf(zLine, "| page %d offset %d", &j, &k);
if( rc==2 ){
iOffset = k;
}
return a;
- readHexDb_error:
- if( in!=p->in ){
- fclose(in);
- }else{
- while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
- nLine++;
- if(strncmp(zLine, "| end ", 6)==0 ) break;
+ readHexDb_error:
+ nlError = p->pInSource->lineno;
+ if( p->pInSource!=&inRedir ){
+ /* Since taking input inline, consume through its end marker. */
+ while( str_line_get(zLine, sizeof(zLine), p->pInSource)!=0 ){
++
+ if(strncmp(zLine, zEndMarker, 6)==0 ) break;
}
- p->lineno = nLine;
++
}
sqlite3_free(a);
- utf8_printf(STD_ERR,"Error on line %d of --hexdb input\n", nLine);
- return 0;
+ a = 0;
- utf8_printf(stderr,"Error on line %d within --hexdb input\n", nlError);
++ utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
+ goto readHexDb_cleanup;
}
#endif /* SQLITE_OMIT_DESERIALIZE */
if( zDbFilename==0 || zDbFilename[0]==0 ){
p->openMode = SHELL_OPEN_NORMAL;
}else{
- p->openMode = (u8)deduceDatabaseType(p->pAuxDb->zDbFilename,
- (openFlags & OPEN_DB_ZIPFILE)!=0);
- p->openMode = deduceDatabaseType(zDbFilename,
++ p->openMode = deduceDatabaseType(p->pAuxDb->zDbFilename,
+ (openFlags & OPEN_DB_ZIPFILE)!=0);
}
}
switch( p->openMode ){
** Attempt to close the databaes connection. Report errors.
*/
void close_db(sqlite3 *db){
- int rc = sqlite3_close(db);
+ int rc;
+ if( db==globalDb ){
+ sqlite3_mutex_enter(pGlobalDbLock);
+ globalDb = 0;
+ rc = sqlite3_close(db);
+ sqlite3_mutex_leave(pGlobalDbLock);
+ }else{
+ rc = sqlite3_close(db);
+ }
if( rc ){
- utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
+ utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
rc, sqlite3_errmsg(db));
- }
+ }
}
#if HAVE_READLINE || HAVE_EDITLINE
for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
}
if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
- if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
- return 1;
- }
- if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
- return 0;
+ for( i=0; zBoolNames[i]!=0; ++i ){
+ if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
}
- utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
+ utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
- zArg);
+ zArg);
return 0;
}
return res;
}
-** The return must be freed by caller.
+ /*
+ ** Run an SQL command and return the single text result,
+ ** Parameter binding is done iff bBind is true.
++** The return must be freed by caller using sqlite3_free().
+ */
+ static char *db_text(sqlite3 *db, const char *zSql, int bBind){
+ sqlite3_stmt *pStmt;
+ char *zRes = 0;
+ sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( pStmt ){
+ if( bBind ) bind_prepared_stmt(db, pStmt);
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ zRes = sqlite3_mprintf("%s", sqlite3_column_text(pStmt,0));
+ shell_check_oom(zRes);
+ }
+ }
+ sqlite3_finalize(pStmt);
+ return zRes;
+ }
+
/*
** Convert a 2-byte or 4-byte big-endian integer into a native integer
*/
if( c!=(*(z++)) ) return 0;
}
}
- while( IsSpace(*z) ){ z++; }
- return *z==0;
+ return *skipWhite(z)==0;
}
--
/*
** Compare the string as a command-line option with either one or two
** initial "-" characters.
if( *pRc==SQLITE_OK ){
int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
if( rc!=SQLITE_OK ){
- raw_printf(STD_ERR, "sql error: %s (%d)\n",
- raw_printf(stderr, "sql error: %s (%d)\n",
++ raw_printf(STD_ERR, "sql error: %s (%d)\n",
sqlite3_errmsg(db), sqlite3_errcode(db)
);
*pRc = rc;
** The caller is responsible for eventually calling sqlite3_free() on
** any non-NULL (*pzWhere) value. Here, "match" means strict equality
** when pAr->bGlob is false and GLOB match when pAr->bGlob is true.
+
*/
static void arWhereClause(
- int *pRc,
+ int *pRc,
ArCommand *pAr,
char **pzWhere /* OUT: New WHERE clause */
){
utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
}
- rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
+ rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
if( rc!=SQLITE_OK ){
- utf8_printf(STD_ERR, "cannot open file: %s (%s)\n",
- utf8_printf(stderr, "cannot open file: %s (%s)\n",
++ utf8_printf(STD_ERR, "cannot open file: %s (%s)\n",
cmd.zFile, sqlite3_errmsg(cmd.db)
);
goto end_ar_command;
}
/*
-** If *pRc is not SQLITE_OK when this function is called, it is a no-op.
-** Otherwise, zFmt is treated as a printf() style string. The result of
-** formatting it along with any trailing arguments is written into a
+** If pRc!=0 and *pRc is not SQLITE_OK when this function is called, it is a
+** no-op. Otherwise, zFmt is treated as a printf() style string. The result
- ** of formatting it along with any trailing arguments is written into a
++** of formatting it along with any trailing arguments is written into a
** buffer obtained from sqlite3_malloc(), and pointer to which is returned.
** It is the responsibility of the caller to eventually free this buffer
** using a call to sqlite3_free().
- **
+ **
-** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM and a NULL
-** pointer returned.
+** If an OOM error occurs, (*pRc) is set to SQLITE_NOMEM (if pRc!=0)
+** and a NULL pointer returned.
*/
static char *shellMPrintf(int *pRc, const char *zFmt, ...){
char *z = 0;
}
return pTab;
}
+#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
-/*
-** This function is called to recover data from the database. A script
-** to construct a new database containing all recovered data is output
-** on stream pState->out.
-*/
-static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){
- int rc = SQLITE_OK;
- sqlite3_stmt *pLoop = 0; /* Loop through all root pages */
- sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */
- sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */
- const char *zRecoveryDb = ""; /* Name of "recovery" database */
- const char *zLostAndFound = "lost_and_found";
- int i;
- int nOrphan = -1;
- RecoverTable *pOrphan = 0;
-
- int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
- int bRowids = 1; /* 0 if --no-rowids */
- for(i=1; i<nArg; i++){
- char *z = azArg[i];
- int n;
- if( z[0]=='-' && z[1]=='-' ) z++;
- n = strlen30(z);
- if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
- bFreelist = 0;
- }else
- if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
- i++;
- zRecoveryDb = azArg[i];
- }else
- if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
- i++;
- zLostAndFound = azArg[i];
- }else
- if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
- bRowids = 0;
- }
- else{
- utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
- showHelp(pState->out, azArg[0]);
- return 1;
+static int writeDb( char *azArg[], int nArg, ShellState *p, char **pzErr ){
+ int rc = 0;
+ const char *zDestFile = 0;
+ const char *zDb = 0;
+ sqlite3 *pDest;
+ sqlite3_backup *pBackup;
+ int j;
+ int bAsync = 0;
+ const char *zVfs = 0;
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ for(j=1; j<nArg; j++){
+ const char *z = azArg[j];
+ if( z[0]=='-' ){
+ if( z[1]=='-' ) z++;
+ if( strcmp(z, "-append")==0 ){
+ zVfs = "apndvfs";
+ }else
+ if( strcmp(z, "-async")==0 ){
+ bAsync = 1;
+ }else
+ {
+ utf8_printf(STD_ERR, "unknown option: %s\n", azArg[j]);
+ return SHELL_INVALID_ARGS;
+ }
+ }else if( zDestFile==0 ){
+ zDestFile = azArg[j];
+ }else if( zDb==0 ){
+ zDb = zDestFile;
+ zDestFile = azArg[j];
+ }else{
+ return SHELL_INVALID_ARGS;
}
}
-
- shellExecPrintf(pState->db, &rc,
- /* Attach an in-memory database named 'recovery'. Create an indexed
- ** cache of the sqlite_dbptr virtual table. */
- "PRAGMA writable_schema = on;"
- "ATTACH %Q AS recovery;"
- "DROP TABLE IF EXISTS recovery.dbptr;"
- "DROP TABLE IF EXISTS recovery.freelist;"
- "DROP TABLE IF EXISTS recovery.map;"
- "DROP TABLE IF EXISTS recovery.schema;"
- "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
- );
-
- if( bFreelist ){
- shellExec(pState->db, &rc,
- "WITH trunk(pgno) AS ("
- " SELECT shell_int32("
- " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
- " WHERE x>0"
- " UNION"
- " SELECT shell_int32("
- " (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
- " FROM trunk WHERE x>0"
- "),"
- "freelist(data, n, freepgno) AS ("
- " SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
- " FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
- " UNION ALL"
- " SELECT data, n-1, shell_int32(data, 2+n) "
- " FROM freelist WHERE n>=0"
- ")"
- "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
- );
+ if( zDestFile==0 ){
+ return SHELL_INVALID_ARGS;
+ }
+ if( zDb==0 ) zDb = "main";
- rc = sqlite3_open_v2(zDestFile, &pDest,
++ rc = sqlite3_open_v2(zDestFile, &pDest,
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zDestFile);
+ close_db(pDest);
+ return 1;
+ }
+ if( bAsync ){
+ sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
+ 0, 0, 0);
+ }
+ open_db(p, 0);
+ pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
+ if( pBackup==0 ){
+ utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(pDest));
+ close_db(pDest);
+ return 1;
+ }
+ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
+ sqlite3_backup_finish(pBackup);
+ if( rc==SQLITE_DONE ){
+ rc = 0;
+ }else{
+ utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(pDest));
+ rc = 1;
}
+ close_db(pDest);
+ return rc;
+}
- /* If this is an auto-vacuum database, add all pointer-map pages to
- ** the freelist table. Do this regardless of whether or not
- ** --freelist-corrupt was specified. */
- shellExec(pState->db, &rc,
- "WITH ptrmap(pgno) AS ("
- " SELECT 2 WHERE shell_int32("
- " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
- " )"
- " UNION ALL "
- " SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
- " FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
- ")"
- "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
- );
+/*
+ * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
+ * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
+ * close db and set it to 0, and return the columns spec, to later
+ * be sqlite3_free()'ed by the caller.
+ * The return is 0 when either:
+ * (a) The db was not initialized and zCol==0 (There are no columns.)
+ * (b) zCol!=0 (Column was added, db initialized as needed.)
+ * The 3rd argument, pRenamed, references an out parameter. If the
+ * pointer is non-zero, its referent will be set to a summary of renames
+ * done if renaming was necessary, or set to 0 if none was done. The out
+ * string (if any) must be sqlite3_free()'ed by the caller.
+ */
+#ifdef SHELL_DEBUG
+#define rc_err_oom_die(rc) \
+ if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
+ else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
- fprintf(stderr,"E:%d\n",rc), assert(0)
++ fprintf(STD_ERR,"E:%d\n",rc), assert(0)
+#else
+static void rc_err_oom_die(int rc){
+ if( rc==SQLITE_NOMEM ) shell_check_oom(0);
+ assert(rc==SQLITE_OK||rc==SQLITE_DONE);
+}
+#endif
- shellExec(pState->db, &rc,
- "CREATE TABLE recovery.dbptr("
- " pgno, child, PRIMARY KEY(child, pgno)"
- ") WITHOUT ROWID;"
- "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
- " SELECT * FROM sqlite_dbptr"
- " WHERE pgno NOT IN freelist AND child NOT IN freelist;"
-
- /* Delete any pointer to page 1. This ensures that page 1 is considered
- ** a root page, regardless of how corrupt the db is. */
- "DELETE FROM recovery.dbptr WHERE child = 1;"
-
- /* Delete all pointers to any pages that have more than one pointer
- ** to them. Such pages will be treated as root pages when recovering
- ** data. */
- "DELETE FROM recovery.dbptr WHERE child IN ("
- " SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
- ");"
-
- /* Create the "map" table that will (eventually) contain instructions
- ** for dealing with each page in the db that contains one or more
- ** records. */
- "CREATE TABLE recovery.map("
- "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
- ");"
-
- /* Populate table [map]. If there are circular loops of pages in the
- ** database, the following adds all pages in such a loop to the map
- ** as individual root pages. This could be handled better. */
- "WITH pages(i, maxlen) AS ("
- " SELECT page_count, ("
- " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
- " ) FROM pragma_page_count WHERE page_count>0"
- " UNION ALL"
- " SELECT i-1, ("
- " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
- " ) FROM pages WHERE i>=2"
- ")"
- "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
- " SELECT i, maxlen, NULL, ("
- " WITH p(orig, pgno, parent) AS ("
- " SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
- " UNION "
- " SELECT i, p.parent, "
- " (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
- " )"
- " SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
- ") "
- "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
- "UPDATE recovery.map AS o SET intkey = ("
- " SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
- ");"
-
- /* Extract data from page 1 and any linked pages into table
- ** recovery.schema. With the same schema as an sqlite_schema table. */
- "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
- "INSERT INTO recovery.schema SELECT "
- " max(CASE WHEN field=0 THEN value ELSE NULL END),"
- " max(CASE WHEN field=1 THEN value ELSE NULL END),"
- " max(CASE WHEN field=2 THEN value ELSE NULL END),"
- " max(CASE WHEN field=3 THEN value ELSE NULL END),"
- " max(CASE WHEN field=4 THEN value ELSE NULL END)"
- "FROM sqlite_dbdata WHERE pgno IN ("
- " SELECT pgno FROM recovery.map WHERE root=1"
- ")"
- "GROUP BY pgno, cell;"
- "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
- );
-
- /* Open a transaction, then print out all non-virtual, non-"sqlite_%"
- ** CREATE TABLE statements that extracted from the existing schema. */
- if( rc==SQLITE_OK ){
- sqlite3_stmt *pStmt = 0;
- /* ".recover" might output content in an order which causes immediate
- ** foreign key constraints to be violated. So disable foreign-key
- ** constraint enforcement to prevent problems when running the output
- ** script. */
- raw_printf(pState->out, "PRAGMA foreign_keys=OFF;\n");
- raw_printf(pState->out, "BEGIN;\n");
- raw_printf(pState->out, "PRAGMA writable_schema = on;\n");
- shellPrepare(pState->db, &rc,
- "SELECT sql FROM recovery.schema "
- "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
- );
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
- const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
- raw_printf(pState->out, "CREATE TABLE IF NOT EXISTS %s;\n",
- &zCreateTable[12]
- );
- }
- shellFinalize(&rc, pStmt);
- }
-
- /* Figure out if an orphan table will be required. And if so, how many
- ** user columns it should contain */
- shellPrepare(pState->db, &rc,
- "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
- , &pLoop
- );
- if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
- nOrphan = sqlite3_column_int(pLoop, 0);
- }
- shellFinalize(&rc, pLoop);
- pLoop = 0;
-
- shellPrepare(pState->db, &rc,
- "SELECT pgno FROM recovery.map WHERE root=?", &pPages
- );
-
- shellPrepare(pState->db, &rc,
- "SELECT max(field), group_concat(shell_escape_crnl(quote"
- "(case when (? AND field<0) then NULL else value end)"
- "), ', ')"
- ", min(field) "
- "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
- "GROUP BY cell", &pCells
- );
-
- /* Loop through each root page. */
- shellPrepare(pState->db, &rc,
- "SELECT root, intkey, max(maxlen) FROM recovery.map"
- " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
- " SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
- ")", &pLoop
- );
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
- int iRoot = sqlite3_column_int(pLoop, 0);
- int bIntkey = sqlite3_column_int(pLoop, 1);
- int nCol = sqlite3_column_int(pLoop, 2);
- int bNoop = 0;
- RecoverTable *pTab;
-
- assert( bIntkey==0 || bIntkey==1 );
- pTab = recoverFindTable(pState, &rc, iRoot, bIntkey, nCol, &bNoop);
- if( bNoop || rc ) continue;
- if( pTab==0 ){
- if( pOrphan==0 ){
- pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
- }
- pTab = pOrphan;
- if( pTab==0 ) break;
- }
-
- if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
- raw_printf(pState->out, "DELETE FROM sqlite_sequence;\n");
- }
- sqlite3_bind_int(pPages, 1, iRoot);
- if( bRowids==0 && pTab->iPk<0 ){
- sqlite3_bind_int(pCells, 1, 1);
- }else{
- sqlite3_bind_int(pCells, 1, 0);
- }
- sqlite3_bind_int(pCells, 3, pTab->iPk);
-
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
- int iPgno = sqlite3_column_int(pPages, 0);
- sqlite3_bind_int(pCells, 2, iPgno);
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
- int nField = sqlite3_column_int(pCells, 0);
- int iMin = sqlite3_column_int(pCells, 2);
- const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
-
- RecoverTable *pTab2 = pTab;
- if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
- if( pOrphan==0 ){
- pOrphan = recoverOrphanTable(pState, &rc, zLostAndFound, nOrphan);
- }
- pTab2 = pOrphan;
- if( pTab2==0 ) break;
- }
-
- nField = nField+1;
- if( pTab2==pOrphan ){
- raw_printf(pState->out,
- "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
- pTab2->zQuoted, iRoot, iPgno, nField,
- iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
- );
- }else{
- raw_printf(pState->out, "INSERT INTO %s(%s) VALUES( %s );\n",
- pTab2->zQuoted, pTab2->azlCol[nField], zVal
- );
- }
- }
- shellReset(&rc, pCells);
- }
- shellReset(&rc, pPages);
- if( pTab!=pOrphan ) recoverFreeTable(pTab);
- }
- shellFinalize(&rc, pLoop);
- shellFinalize(&rc, pPages);
- shellFinalize(&rc, pCells);
- recoverFreeTable(pOrphan);
-
- /* The rest of the schema */
- if( rc==SQLITE_OK ){
- sqlite3_stmt *pStmt = 0;
- shellPrepare(pState->db, &rc,
- "SELECT sql, name FROM recovery.schema "
- "WHERE sql NOT LIKE 'create table%'", &pStmt
- );
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
- const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
- if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
- const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
- char *zPrint = shellMPrintf(&rc,
- "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
- zName, zName, zSql
- );
- raw_printf(pState->out, "%s;\n", zPrint);
- sqlite3_free(zPrint);
- }else{
- raw_printf(pState->out, "%s;\n", zSql);
- }
- }
- shellFinalize(&rc, pStmt);
- }
-
- if( rc==SQLITE_OK ){
- raw_printf(pState->out, "PRAGMA writable_schema = off;\n");
- raw_printf(pState->out, "COMMIT;\n");
- }
- sqlite3_exec(pState->db, "DETACH recovery", 0, 0, 0);
- return rc;
-}
-#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
+#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
+static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
+#else /* Otherwise, memory is faster/better for the transient DB. */
+static const char *zCOL_DB = ":memory:";
+#endif
-/*
-** The .shxopts command, for setting or listing shell extension options.
+/* Define character (as C string) to separate generated column ordinal
+ * from protected part of incoming column names. This defaults to "_"
+ * so that incoming column identifiers that did not need not be quoted
+ * remain usable without being quoted. It must be one character.
*/
-#if SHELL_EXTENSIONS
-static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){
- static struct { const char *name; u8 mask; } shopts[] = {
-#if SHELL_DYNAMIC_COMMANDS
- {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
+#ifndef SHELL_AUTOCOLUMN_SEP
+# define AUTOCOLUMN_SEP "_"
+#else
+# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
#endif
-#if SHELL_EXTENDED_PARSING
- {"parsing", 1<<SHEXT_PARSING_BIT},
+
+static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
+ /* Queries and D{D,M}L used here */
+ static const char * const zTabMake = "\
+CREATE TABLE ColNames(\
+ cpos INTEGER PRIMARY KEY,\
+ name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
+CREATE VIEW RepeatedNames AS \
+SELECT DISTINCT t.name FROM ColNames t \
+WHERE t.name COLLATE NOCASE IN (\
+ SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
+);\
+";
+ static const char * const zTabFill = "\
+INSERT INTO ColNames(name,nlen,chop,reps,suff)\
+ VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
+";
+ static const char * const zHasDupes = "\
+SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
+ <count(name) FROM ColNames\
+";
+#ifdef SHELL_COLUMN_RENAME_CLEAN
+ static const char * const zDedoctor = "\
+UPDATE ColNames SET chop=iif(\
+ (substring(name,nlen,1) BETWEEN '0' AND '9')\
+ AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
+ nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
+ 0\
+)\
+";
#endif
-#if SHELL_VARIABLE_EXPANSION
- {"dot_vars", 1<<SHEXT_VAREXP_BIT},
+ static const char * const zSetReps = "\
+UPDATE ColNames AS t SET reps=\
+(SELECT count(*) FROM ColNames d \
+ WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
+ COLLATE NOCASE\
+)\
+";
+#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+ static const char * const zColDigits = "\
+SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
+";
#endif
- {"all_opts", SHELL_ALL_EXTENSIONS}
- };
- const char *zMoan = 0, *zAbout = 0;
- int ia, io;
- if( nArg>1 ){
- for( ia=1; ia<nArg; ++ia ){
- char cs = azArg[ia][0];
- if( cs!='+' && cs!='-' ){
- zMoan = "arguments must have a sign prefix.";
- zAbout = azArg[0];
- goto moan_error;
- }
- for( io=0; io<ArraySize(shopts); ++io ){
- if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
- if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
- else p->bExtendedDotCmds &= ~shopts[io].mask;
- break;
- }
- }
- if( io==ArraySize(shopts) ){
- zAbout = azArg[ia];
- zMoan = "is not a recognized option name";
- goto moan_error;
- }
+ static const char * const zRenameRank =
+#ifdef SHELL_COLUMN_RENAME_CLEAN
+ "UPDATE ColNames AS t SET suff="
+ "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
+#else /* ...RENAME_MINIMAL_ONE_PASS */
+"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
+" SELECT 0 AS nlz"
+" UNION"
+" SELECT nlz+1 AS nlz FROM Lzn"
+" WHERE EXISTS("
+" SELECT 1"
+" FROM ColNames t, ColNames o"
+" WHERE"
+" iif(t.name IN (SELECT * FROM RepeatedNames),"
+" printf('%s"AUTOCOLUMN_SEP"%s',"
+" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
+" t.name"
+" )"
+" ="
+" iif(o.name IN (SELECT * FROM RepeatedNames),"
+" printf('%s"AUTOCOLUMN_SEP"%s',"
+" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
+" o.name"
+" )"
+" COLLATE NOCASE"
+" AND o.cpos<>t.cpos"
+" GROUP BY t.cpos"
+" )"
+") UPDATE Colnames AS t SET"
+" chop = 0," /* No chopping, never touch incoming names. */
+" suff = iif(name IN (SELECT * FROM RepeatedNames),"
+" printf('"AUTOCOLUMN_SEP"%s', substring("
+" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
+" ''"
+" )"
+#endif
+ ;
+ static const char * const zCollectVar = "\
+SELECT\
+ '('||x'0a'\
+ || group_concat(\
+ cname||' TEXT',\
+ ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
+ ||')' AS ColsSpec \
+FROM (\
+ SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \
+ FROM ColNames ORDER BY cpos\
+)";
+ static const char * const zRenamesDone =
+ "SELECT group_concat("
+ " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff)),"
+ " ','||x'0a')"
+ "FROM ColNames WHERE suff<>'' OR chop!=0"
+ ;
+ int rc;
+ sqlite3_stmt *pStmt = 0;
+ assert(pDb!=0);
+ if( zColNew ){
+ /* Add initial or additional column. Init db if necessary. */
+ if( *pDb==0 ){
+ if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
+#ifdef SHELL_COLFIX_DB
+ if(*zCOL_DB!=':')
+ sqlite3_exec(*pDb,"drop table if exists ColNames;"
+ "drop view if exists RepeatedNames;",0,0,0);
+#endif
+ rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
+ rc_err_oom_die(rc);
}
+ assert(*pDb!=0);
+ rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_step(pStmt);
+ rc_err_oom_die(rc);
+ sqlite3_finalize(pStmt);
+ return 0;
+ }else if( *pDb==0 ){
+ return 0;
}else{
- raw_printf(p->out,
- " name value \"-shxopts set\"\n"
- " -------- ----- ---------------\n");
- for( io=0; io<ArraySize(shopts); ++io ){
- unsigned m = shopts[io].mask;
- unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
- raw_printf(p->out,
- " %9s %2d \"-shxopts 0x%02X\"\n",
- shopts[io].name, v, m);
- }
- }
- return 0;
- moan_error:
- raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan);
- return 1;
-}
+ /* Formulate the columns spec, close the DB, zero *pDb. */
+ char *zColsSpec = 0;
+ int hasDupes = db_int(*pDb, zHasDupes);
+#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+ int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
+#else
+# define nDigits 2
#endif
-
-static int execute_variables(char *azArg[], int nArg, ShellState *p){
- int ia, rc, nErrors = 0;
- sqlite3_stmt *pStmt = 0;
- open_db(p, 0);
- if( p->db==0 ){
- utf8_printf(stderr, ".x can only be done with a database open.\n");
- return 1;
- }
- if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
- PARAM_TABLE_NAME,
- "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
- utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n");
- return 1;
- }
- rc = sqlite3_prepare_v2
- (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
- " WHERE key=$1 AND uses=1",
- -1, &pStmt, 0);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n");
- return 1;
- }
- for( ia=1; ia < nArg; ++ia ){
- if( isalpha(azArg[ia][0]) ){
- rc = sqlite3_reset(pStmt);
- rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
+ if( hasDupes ){
+#ifdef SHELL_COLUMN_RENAME_CLEAN
+ rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
+ rc_err_oom_die(rc);
+#endif
+ rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
+ rc_err_oom_die(rc);
+ sqlite3_bind_int(pStmt, 1, nDigits);
rc = sqlite3_step(pStmt);
- if( rc==SQLITE_ROW ){
- const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
- int nb = sqlite3_column_bytes(pStmt, 0);
- while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
- if( nb>0 ){
- /* The trailing newline (or some other placeholder) is important
- * because one (or some other character) will likely be put in
- * its place during process_input() line/group handling, along
- * with a terminating NUL character. Without it, the NULL could
- * land past the end of the allocation made just below.
- */
- int nle = zValue[nb-1]=='\n';
- char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
- InSource inRedir
- = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
- shell_check_oom(zSubmit);
- p->pInSource = &inRedir;
- rc = process_input(p);
- sqlite3_free(zSubmit);
- p->pInSource = inRedir.pFrom;
- }else{
- continue; /* All white, ignore. */
- }
- }else{
- utf8_printf(stderr,
- "Skipping parameter '%s' (not set and executable.)\n",
- azArg[ia]);
- ++nErrors;
+ sqlite3_finalize(pStmt);
+ assert(rc==SQLITE_DONE);
+ }
+ assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
+ rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_ROW ){
+ zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
+ }else{
+ zColsSpec = 0;
+ }
+ if( pzRenamed!=0 ){
+ if( !hasDupes ) *pzRenamed = 0;
+ else{
+ sqlite3_finalize(pStmt);
+ if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
+ && SQLITE_ROW==sqlite3_step(pStmt) ){
+ *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
+ }else
+ *pzRenamed = 0;
}
- }else{
- utf8_printf(stderr,
- "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
- ++nErrors;
}
+ sqlite3_finalize(pStmt);
+ sqlite3_close(*pDb);
+ *pDb = 0;
+ return zColsSpec;
}
- sqlite3_finalize(pStmt);
- return (rc==2)? 2 : nErrors>0;
}
-struct param_row { char * value; int uses; int hits; };
-
-static int param_find_callback(void *pData, int nc, char **pV, char **pC){
- assert(nc>=1);
- assert(strcmp(pC[0],"value")==0);
- struct param_row *pParam = (struct param_row *)pData;
- assert(pParam->value==0); /* key values are supposedly unique. */
- if( pParam->value!=0 ) sqlite3_free( pParam->value );
- pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */
- if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
- ++pParam->hits;
+#ifndef OBJECTIFY_COMMANDS
+# define OBJECTIFY_COMMANDS 1
+#endif
+
+/* Meta-command implementation functions are defined in this section.
+COMMENT Define meta-commands and provide for their dispatch and .help text.
+COMMENT These should be kept in command name order for coding convenience
+COMMENT except where meta-commands share implementation. (The ordering
+COMMENT required for dispatch and help text is effected regardless.) The
+COMMENT effect of this configuration can be seen in generated output or by
+COMMENT executing tool/mkshellc.tcl --parameters (or --details or --help).
+COMMENT Generally, this section defines dispatchable functions inline and
+COMMENT causes collection of dispatch and help table entries, to be later
+COMMENT emitted by certain macros. (See EMIT_* further on.)
+** All dispatchable meta-command execute functions have this signature:
+static int someCommand(char *azArg[], int nArg, ShellState *p, char **pzErr);
+*/
+DISPATCH_CONFIG[
+ RETURN_TYPE=int
+ STORAGE_CLASS=static
+ ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellState *$arg6, char **$arg7
+ DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
+ CMD_CAPTURE_RE=^\s*{\s*"(\w+)"
+ DISPATCHEE_NAME=${cmd}Command
+ DC_ARG1_DEFAULT=[string length $cmd]
+ DC_ARG2_DEFAULT=0
+ DC_ARG3_DEFAULT=0
+ DC_ARG4_DEFAULT=azArg
+ DC_ARG5_DEFAULT=nArg
+ DC_ARG6_DEFAULT=p
+ DC_ARG7_DEFAULT=pzErr
+ DC_ARG_COUNT=8
+];
+
- CONDITION_COMMAND(seeargs defined(SQLITE_GIMME_SEEARGS));
++CONDITION_COMMAND(seeargs !defined(SHELL_OMIT_SEEARGS));
+/*****************
+ * The .seeargs command
+ */
+COLLECT_HELP_TEXT[
- ".seeargs Echo arguments separated by |",
- " A near-dummy command for use as a template (to vanish soon)",
++ ".seeargs Echo arguments suffixed with |",
+];
+DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
+ int rc = 0;
+ for (rc=1; rc<nArg; ++rc)
- raw_printf(p->out, "%s%s", azArg[rc], (rc==nArg-1)? "\n" : "|");
- return rc;
++ raw_printf(p->out, "%s%s", azArg[rc], (rc==nArg-1)? "|\n" : "|");
+ return 0;
}
-static void append_in_clause(sqlite3_str *pStr,
- const char **azBeg, const char **azLim);
-static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
- const char **azBeg, const char **azLim);
-static char *find_home_dir(int clearFlag);
+CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB));
+/*****************
+ * The .archive command
+ */
+COLLECT_HELP_TEXT[
+ ".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",
+];
+DISPATCHABLE_COMMAND( archive ? 0 0 azArg nArg p ){
+ open_db(p, 0);
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ return arDotCommand(p, 0, azArg, nArg);
+}
-/* Create a home-relative pathname from ~ prefixed path.
- * Return it, or 0 for any error.
- * Caller must sqlite3_free() it.
+/*****************
+ * The .auth command
*/
-static char *home_based_path( const char *zPath ){
- char *zHome = find_home_dir(0);
- char *zErr = 0;
- assert( zPath[0]=='~' );
- if( zHome==0 ){
- zErr = "Cannot find home directory.";
- }else if( zPath[0]==0 || (zPath[1]!='/'
+CONDITION_COMMAND(auth !defined(SQLITE_OMIT_AUTHORIZATION));
+COLLECT_HELP_TEXT[
+ ".auth ON|OFF Show authorizer callbacks",
+];
+DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){
+ int rc = 0;
+ open_db(p, 0);
+ if( booleanValue(azArg[1]) ){
+ sqlite3_set_authorizer(p->db, shellAuth, p);
+ }else if( p->bSafeModeFuture ){
+ sqlite3_set_authorizer(p->db, safeModeAuth, p);
+ }else{
+ sqlite3_set_authorizer(p->db, 0, 0);
+ }
+ return rc;
+}
+
+/*****************
+ * The .backup and .save commands (aliases for each other)
+ * These defer to writeDb in the dispatch table, so are not here.
+ */
+COLLECT_HELP_TEXT[
+ ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
+ " Options:",
+ " --append Use the appendvfs",
+ " --async Write the FILE without journal and fsync()",
+ ".save ?DB? FILE Write DB (default \"main\") to FILE",
+ " Options:",
+ " --append Use the appendvfs",
+ " --async Write the FILE without journal and fsync()",
+];
+COLLECT_DISPATCH( * )[
+ { "backup", writeDb, 4, 2, 5 },
+ { "save", writeDb, 3, 2, 5 },
+];
+
+/*****************
+ * The .bail command
+ */
+COLLECT_HELP_TEXT[
+ ".bail on|off Stop after hitting an error. Default OFF",
+];
+DISPATCHABLE_COMMAND( bail 3 2 2 ){
+ bail_on_error = booleanValue(azArg[1]);
+ return 0;
+}
+
+/*****************
+ * The .binary and .cd commands
+ */
+COLLECT_HELP_TEXT[
+ ".binary on|off Turn binary output on or off. Default OFF",
+ ".cd DIRECTORY Change the working directory to DIRECTORY",
+];
+DISPATCHABLE_COMMAND( binary 3 2 2 ){
+ if( booleanValue(azArg[1]) ){
+ setBinaryMode(p->out, 1);
+ }else{
+ setTextMode(p->out, 1);
+ }
+ return 0;
+}
+
+DISPATCHABLE_COMMAND( cd ? 2 2 ){
+ int rc=0;
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
#if defined(_WIN32) || defined(WIN32)
- && zPath[1]!='\\'
+ wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
+ rc = !SetCurrentDirectoryW(z);
+ sqlite3_free(z);
+#else
+ rc = chdir(azArg[1]);
#endif
- ) ){
- zErr = "Malformed pathname";
- }else{
- return sqlite3_mprintf("%s%s", zHome, zPath+1);
+ if( rc ){
+ utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
+ rc = 1;
}
- utf8_printf(stderr, "Error: %s\n", zErr);
+ return rc;
+}
+
+/* The undocumented ".breakpoint" command causes a call
+** to the no-op routine named test_breakpoint().
+*/
+DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
+ test_breakpoint();
return 0;
}
-/* Transfer selected parameters between two parameter tables, for save/load.
- * Argument bSaveNotLoad determines transfer direction and other actions.
- * If it is true, the store DB will be created if not existent, and its
- * table for keeping parameters will be created. Or failure is returned.
- * If it is false, the store DB will be opened for read and its presumed
- * table for keeping parameters will be read. Or failure is returned.
- *
- * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
- * If it is an empty list, all parameters will be saved or loaded.
- * Otherwise, only the named parameters are transferred, if they exist.
- * It is not an error to specify a name that cannot be transferred
- * because it does not exist in the source table.
- *
- * Returns are SQLITE_OK for success, or other codes for failure.
+/*****************
+ * The .changes, .check, .clone and .connection commands
*/
-static int param_xfr_table(sqlite3 *db, const char *zStoreDbName,
- int bSaveNotLoad, const char *azNames[], int nNames){
- int rc = 0;
- char *zSql = 0; /* to be sqlite3_free()'ed */
- sqlite3_str *sbCopy = 0;
- const char *zHere = PARAM_TABLE_SNAME;
- const char *zThere = PARAM_STORE_SNAME;
- const char *zTo = (bSaveNotLoad)? zThere : zHere;
- const char *zFrom = (bSaveNotLoad)? zHere : zThere;
- sqlite3 *dbStore = 0;
- int openFlags = (bSaveNotLoad)
- ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
- : SQLITE_OPEN_READONLY;
+COLLECT_HELP_TEXT[
+ ".changes on|off Show number of rows changed by SQL",
+ ".check GLOB Fail if output since .testcase does not match",
+ ".clone NEWDB Clone data into NEWDB from the existing database",
+ ".connection [close] [#] Open or close an auxiliary database connection",
+];
+DISPATCHABLE_COMMAND( changes 3 2 2 ){
+ setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
+ return 0;
+}
+DISPATCHABLE_COMMAND( check 3 0 0 ){
+ /* Cancel output redirection, if it is currently set (by .testcase)
+ ** Then read the content of the testcase-out.txt file and compare against
+ ** azArg[1]. If there are differences, report an error and exit.
+ */
+ char *zRes = 0;
+ int rc=0;
+ output_reset(p);
+ if( nArg!=2 ){
+ return SHELL_INVALID_ARGS;
+ }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
+ *pzErr = shellMPrintf(&rc, "Error: cannot read 'testcase-out.txt'");
+ rc = 2;
+ }else if( testcase_glob(azArg[1],zRes)==0 ){
+ *pzErr =
+ shellMPrintf(&rc,
+ "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n",
+ p->zTestcase, azArg[1], zRes);
+ rc = 1;
+ }else{
+ utf8_printf(STD_OUT, "testcase-%s ok\n", p->zTestcase);
+ p->nCheck++;
+ }
+ sqlite3_free(zRes);
+ return rc;
+}
+DISPATCHABLE_COMMAND( clone ? 2 2 ){
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ tryToClone(p, azArg[1]);
+ return 0;
+}
+DISPATCHABLE_COMMAND( connection ? 1 4 ){
+ if( nArg==1 ){
+ /* List available connections */
+ int i;
+ for(i=0; i<ArraySize(p->aAuxDb); i++){
+ const char *zFile = p->aAuxDb[i].zDbFilename;
+ if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
+ zFile = "(not open)";
+ }else if( zFile==0 ){
+ zFile = "(memory)";
+ }else if( zFile[0]==0 ){
+ zFile = "(temporary-file)";
+ }
+ if( p->pAuxDb == &p->aAuxDb[i] ){
+ utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
+ }else if( p->aAuxDb[i].db!=0 ){
+ utf8_printf(STD_OUT, " %d: %s\n", i, zFile);
+ }
+ }
+ }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
+ int i = azArg[1][0] - '0';
+ if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
+ p->pAuxDb->db = p->db;
+ p->pAuxDb = &p->aAuxDb[i];
+ globalDb = p->db = p->pAuxDb->db;
+ p->pAuxDb->db = 0;
+ }
+ }else if( nArg==3 && strcmp(azArg[1], "close")==0
+ && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
+ int i = azArg[2][0] - '0';
+ if( i<0 || i>=ArraySize(p->aAuxDb) ){
+ /* No-op */
+ }else if( p->pAuxDb == &p->aAuxDb[i] ){
+ raw_printf(STD_ERR, "cannot close the active database connection\n");
+ return 1;
+ }else if( p->aAuxDb[i].db ){
+ session_close_all(p, i);
+ close_db(p->aAuxDb[i].db);
+ p->aAuxDb[i].db = 0;
+ }
+ }else{
+ return SHELL_INVALID_ARGS;
+ }
+ return 0;
+}
- /* Ensure store DB can be opened and/or created appropriately. */
- rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "Error: Cannot %s parameter store DB %s\n",
- bSaveNotLoad? "open/create" : "read", zStoreDbName);
- return rc;
+/*****************
+ * The .databases, .dbconfig and .dbinfo commands
+ */
+COLLECT_HELP_TEXT[
+ ".databases List names and files of attached databases",
+ ".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
+ ".dbinfo ?DB? Show status information about the database",
+];
+/* Allow garbage arguments on this, to be ignored. */
+DISPATCHABLE_COMMAND( databases 2 1 0 ){
+ int rc;
+ char **azName = 0;
+ int nName = 0;
+ sqlite3_stmt *pStmt;
+ int i;
+ open_db(p, 0);
+ rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
+ if( rc ){
+ *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+ rc = 1;
+ }else{
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
+ const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
+ if( zSchema==0 || zFile==0 ) continue;
+ azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
+ shell_check_oom(azName);
+ azName[nName*2] = strdup(zSchema);
+ shell_check_oom(azName[nName*2]);
+ azName[nName*2+1] = strdup(zFile);
+ shell_check_oom(azName[nName*2+1]);
+ nName++;
+ }
}
- /* Ensure it has the parameter store table, or handle its absence. */
- assert(dbStore!=0);
- if( sqlite3_table_column_metadata
- (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
- if( !bSaveNotLoad ){
- utf8_printf(stderr, "Error: No parameters ever stored in DB %s\n",
- zStoreDbName);
- rc = 1;
+ sqlite3_finalize(pStmt);
+ for(i=0; i<nName; i++){
+ int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
+ int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
+ const char *z = azName[i*2+1];
+ utf8_printf(p->out, "%s: %s %s%s\n",
+ azName[i*2],
+ z && z[0] ? z : "\"\"",
+ bRdonly ? "r/o" : "r/w",
+ eTxn==SQLITE_TXN_NONE ? "" :
+ eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
+ free(azName[i*2]);
+ free(azName[i*2+1]);
+ }
+ sqlite3_free(azName);
+ return rc;
+}
+DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){
+ static const struct DbConfigChoices {
+ const char *zName;
+ int op;
+ } aDbConfig[] = {
+ { "defensive", SQLITE_DBCONFIG_DEFENSIVE },
+ { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
+ { "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
+ { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
+ { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
+ { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
+ { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW },
+ { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
+ { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE },
+ { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT },
+ { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
+ { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
+ { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
+ { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
+ { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA },
+ { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA },
+ };
+ int ii, v;
+ open_db(p, 0);
+ for(ii=0; ii<ArraySize(aDbConfig); ii++){
+ if( nArg>1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
+ if( nArg>=3 ){
+ sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
+ }
+ sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
+ utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
+ if( nArg>1 ) break;
+ }
+ if( nArg>1 && ii==ArraySize(aDbConfig) ){
+ *pzErr = sqlite3_mprintf
+ ("Error: unknown dbconfig \"%s\"\n"
+ "Enter \".dbconfig\" with no arguments for a list\n",
+ azArg[1]);
+ return 1;
- }
++ }
+ return 0;
+}
+DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
+ return shell_dbinfo_command(p, nArg, azArg);
+}
+
+/*****************
+ * The .dump, .echo and .eqp commands
+ */
+COLLECT_HELP_TEXT[
+ ".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",
++ " --schema SCHEMA Dump table(s) from given SCHEMA",
+ " 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",
+];
+DISPATCHABLE_COMMAND( dump ? 1 2 ){
+ char *zLike = 0;
++ char *zSchema = "main";
+ char *zSql;
+ int i;
+ int savedShowHeader = p->showHeader;
+ int savedShellFlags = p->shellFlgs;
- ShellClearFlag(p,
++ ShellClearFlag(p,
+ SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
+ |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
+ for(i=1; i<nArg; i++){
+ if( azArg[i][0]=='-' ){
+ const char *z = azArg[i]+1;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"preserve-rowids")==0 ){
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+ *pzErr = sqlite3_mprintf
+ ("The --preserve-rowids option is not compatible"
+ " with SQLITE_OMIT_VIRTUALTABLE\n");
+ sqlite3_free(zLike);
+ return 1;
+#else
+ ShellSetFlag(p, SHFLG_PreserveRowid);
+#endif
+ }else{
+ if( strcmp(z,"newlines")==0 ){
+ ShellSetFlag(p, SHFLG_Newlines);
+ }else if( strcmp(z,"data-only")==0 ){
+ ShellSetFlag(p, SHFLG_DumpDataOnly);
+ }else if( strcmp(z,"nosys")==0 ){
+ ShellSetFlag(p, SHFLG_DumpNoSys);
++ }else if( strcmp(z,"schema")==0 && ++i<nArg ){
++ zSchema = azArg[i];
+ }else{
+ *pzErr = sqlite3_mprintf
+ ("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
+ sqlite3_free(zLike);
+ return 1;
+ }
+ }
}else{
- /* The saved parameters table is not there yet; create it. */
- const char *zCT =
- "CREATE TABLE IF NOT EXISTS "PARAM_STORE_NAME"(\n"
- " key TEXT PRIMARY KEY,\n"
- " value,\n"
- " uses INT\n"
- ") WITHOUT ROWID;";
- rc = sqlite3_exec(dbStore, zCT, 0, 0, 0);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "Cannot create table %s. Nothing saved.", zThere);
+ /* azArg[i] contains a LIKE pattern. This ".dump" request should
+ ** only dump data for tables for which either the table name matches
+ ** the LIKE pattern, or the table appears to be a shadow table of
+ ** a virtual table for which the name matches the LIKE pattern.
+ */
+ char *zExpr = sqlite3_mprintf(
+ "name LIKE %Q ESCAPE '\\' OR EXISTS ("
- " SELECT 1 FROM sqlite_schema WHERE "
++ " SELECT 1 FROM %w.sqlite_schema WHERE "
+ " name LIKE %Q ESCAPE '\\' AND"
+ " sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
+ " substr(o.name, 1, length(name)+1) == (name||'_')"
- ")", azArg[i], azArg[i]
++ ")", azArg[i], zSchema, azArg[i]
+ );
-
++
+ if( zLike ){
+ zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
+ }else{
+ zLike = zExpr;
}
}
}
- sqlite3_close(dbStore);
- if( rc!=0 ) return rc;
- zSql = sqlite3_mprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA);
- shell_check_oom(zSql);
- rc = sqlite3_exec(db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
- if( rc!=SQLITE_OK ) return rc;
+ open_db(p, 0);
- sbCopy = sqlite3_str_new(db);
- sqlite3_str_appendf
- (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
- "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
- append_in_clause(sbCopy, azNames, azNames+nNames);
- zSql = sqlite3_str_finish(sbCopy);
- shell_check_oom(zSql);
- rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ /* When playing back a "dump", the content might appear in an order
+ ** which causes immediate foreign key constraints to be violated.
+ ** So disable foreign-key constraint enforcement to prevent problems. */
+ raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
+ raw_printf(p->out, "BEGIN TRANSACTION;\n");
+ }
+ p->writableSchema = 0;
+ p->showHeader = 0;
+ /* Set writable_schema=ON since doing so forces SQLite to initialize
+ ** as much of the schema as it can even if the sqlite_schema table is
+ ** corrupt. */
+ sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
+ p->nErr = 0;
+ if( zLike==0 ) zLike = sqlite3_mprintf("true");
+ zSql = sqlite3_mprintf(
- "SELECT name, type, sql FROM sqlite_schema AS o "
++ "SELECT name, type, sql FROM %w.sqlite_schema AS o "
+ "WHERE (%s) AND type=='table'"
+ " AND sql NOT NULL"
+ " ORDER BY tbl_name='sqlite_sequence', rowid",
- zLike
++ zSchema, zLike
+ );
+ run_schema_dump_query(p,zSql);
sqlite3_free(zSql);
+ if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ zSql = sqlite3_mprintf(
+ "SELECT sql FROM sqlite_schema AS o "
+ "WHERE (%s) AND sql NOT NULL"
+ " AND type IN ('index','trigger','view')",
+ zLike
+ );
+ run_table_dump_query(p, zSql);
+ sqlite3_free(zSql);
+ }
+ sqlite3_free(zLike);
+ if( p->writableSchema ){
+ raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
+ p->writableSchema = 0;
+ }
+ sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
+ sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
+ if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
+ }
+ p->showHeader = savedShowHeader;
+ p->shellFlgs = savedShellFlags;
- sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0);
- return rc;
+ return 0;
}
-
-/* Default location of parameters store DB for .parameters save/load. */
-static const char *zDefaultParamStore = "~/sqlite_params.sdb";
-
-/* Possibly generate a derived path from input spec, with defaulting
- * and conversion of leading (or only) tilde as home directory.
- * The above-set default is used for zSpec NULL, "" or "~".
- * When return is 0, there is an error; what needs doing cannnot be done.
- * If the return is exactly the input, it must not be sqlite3_free()'ed.
- * If the return differs from the input, it must be sqlite3_free()'ed.
- */
-static const char *params_store_path(const char *zSpec){
- if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){
- return home_based_path(zDefaultParamStore);
- }else if ( zSpec[0]=='~' ){
- return home_based_path(zSpec);
- }
- return zSpec;
+DISPATCHABLE_COMMAND( echo ? 2 2 ){
+ setOrClearFlag(p, SHFLG_Echo, azArg[1]);
+ return 0;
}
-
-/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */
-static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){
- const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
- if( zStore==0 ){
- utf8_printf(stderr, "Cannot form parameter load path. Nothing loaded.\n");
- return 1;
+DISPATCHABLE_COMMAND( eqp ? 0 0 ){
+ if( nArg==2 ){
+ p->autoEQPtest = 0;
+ if( p->autoEQPtrace ){
+ if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
+ p->autoEQPtrace = 0;
+ }
+ if( strcmp(azArg[1],"full")==0 ){
+ p->autoEQP = AUTOEQP_full;
+ }else if( strcmp(azArg[1],"trigger")==0 ){
+ p->autoEQP = AUTOEQP_trigger;
+#ifdef SQLITE_DEBUG
+ }else if( strcmp(azArg[1],"test")==0 ){
+ p->autoEQP = AUTOEQP_on;
+ p->autoEQPtest = 1;
+ }else if( strcmp(azArg[1],"trace")==0 ){
+ p->autoEQP = AUTOEQP_full;
+ p->autoEQPtrace = 1;
+ open_db(p, 0);
+ sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
+ sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
+#endif
+ }else{
+ p->autoEQP = (u8)booleanValue(azArg[1]);
+ }
}else{
- const char **pzFirst = (nArg>2)? azArg+2 : 0;
- int nNames = (nArg>2)? nArg-2 : 0;
- int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames);
- if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
- return rc;
+ return SHELL_INVALID_ARGS;
}
}
-/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
-static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){
- const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
- if( zStore==0 ){
- utf8_printf(stderr, "Cannot form parameter save path. Nothing saved.\n");
- return 1;
- }else{
- const char **pzFirst = (nArg>2)? azArg+2 : 0;
- int nNames = (nArg>2)? nArg-2 : 0;
- int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames);
- if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
- return rc;
+/*****************
+ * The .expert and .explain commands
+ */
+CONDITION_COMMAND( expert !defined(SQLITE_OMIT_VIRTUALTABLE) );
+COLLECT_HELP_TEXT[
+ ".expert Suggest indexes for queries",
+ ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
+];
+DISPATCHABLE_COMMAND( expert ? 1 1 ){
+ open_db(p, 0);
+ expertDotCommand(p, azArg, nArg);
+ return 0;
+}
+DISPATCHABLE_COMMAND( explain ? 1 2 ){
+ /* The ".explain" command is automatic now. It is largely
+ ** pointless, retained purely for backwards compatibility */
+ int val = 1;
+ if( nArg>1 ){
+ if( strcmp(azArg[1],"auto")==0 ){
+ val = 99;
+ }else{
+ val = booleanValue(azArg[1]);
+ }
}
+ if( val==1 && p->mode!=MODE_Explain ){
+ p->normalMode = p->mode;
+ p->mode = MODE_Explain;
+ p->autoExplain = 0;
+ }else if( val==0 ){
+ if( p->mode==MODE_Explain ) p->mode = p->normalMode;
+ p->autoExplain = 0;
+ }else if( val==99 ){
+ if( p->mode==MODE_Explain ) p->mode = p->normalMode;
+ p->autoExplain = 1;
+ }
+ return 0;
}
-#ifndef SQLITE_NOHAVE_SYSTEM
-/*
- * Edit one named parameter in the parameters table. If it does not
- * yet exist, create it. If eval is true, the value is treated as a
- * bare expression, otherwise it is a text value. The uses argument
- * sets the 3rd column in the parameters table, and may also serve
- * to partition the key namespace. (This is not done now.)
+/*****************
+ * The .excel, .once and .output commands
+ * These share much implementation, so they stick together.
*/
-static int edit_one_param(sqlite3 *db, char *name, int eval,
- ParamTableUse uses, const char * zEditor){
- struct param_row paramVU = {0,0,0};
- int rc;
- char * zVal = 0;
- char * zSql = sqlite3_mprintf
- ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name);
- shell_check_oom(zSql);
- sqlite3_exec(db, zSql, param_find_callback, ¶mVU, 0);
- sqlite3_free(zSql);
- assert(paramVU.hits<2);
- if( paramVU.hits==1 && paramVU.uses==uses){
- /* Editing an existing value of same kind. */
- sqlite3_free(paramVU.value);
- if( eval!=0 ){
- zSql = sqlite3_mprintf
- ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME
- " WHERE key=%Q AND uses=%d", zEditor, name, uses);
- zVal = db_text(db, zSql, 1);
- sqlite3_free(zSql);
- zSql = sqlite3_mprintf
- ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE"
- " key=%Q AND uses=%d", zVal, name, uses);
+COLLECT_HELP_TEXT[
+ ".excel Display the output of next command in spreadsheet",
+ " --bom Prefix the file with a UTF8 byte-order mark",
+ ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
+ " If FILE begins with '|' then open it as a command to be piped into.",
+ " 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 (same as \".excel\")",
+ ".output ?FILE? Send output to FILE or stdout if FILE is omitted",
+ " If FILE begins with '|' then open it as a command to be piped into.",
+ " 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 (same as \".excel\")",
+];
+static int outputRedirs(char *[], int, ShellState *,
+ char **pzErr, int bOnce, int eMode);
+DISPATCHABLE_COMMAND( excel ? 1 2 ){
+ return outputRedirs(azArg, nArg, p, pzErr, 2, 'x');
+}
+DISPATCHABLE_COMMAND( once ? 1 6 ){
+ return outputRedirs(azArg, nArg, p, pzErr, 1, 0);
+}
+DISPATCHABLE_COMMAND( output ? 1 6 ){
+ return outputRedirs(azArg, nArg, p, pzErr, 0, 0);
+}
+
+static int outputRedirs(char *azArg[], int nArg, ShellState *p,
+ char **pzErr, int bOnce, int eMode){
+ /* bOnce => 0: .output, 1: .once, 2: .excel */
+ /* eMode => 'x' for excel, else 0 */
+ int rc = 0;
+ char *zFile = 0;
+ int bTxtMode = 0;
+ int i;
+ int bBOM = 0;
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ if( z[0]=='-' ){
+ if( z[1]=='-' ) z++;
+ if( strcmp(z,"-bom")==0 ){
+ bBOM = 1;
+ }else if( bOnce!=2 && strcmp(z,"-x")==0 ){
+ eMode = 'x'; /* spreadsheet */
+ }else if( bOnce!=2 && strcmp(z,"-e")==0 ){
+ eMode = 'e'; /* text editor */
+ }else{
+ *pzErr = shellMPrintf(0," unknown option: \"%s\"\n",azArg[i]);
+ return SHELL_INVALID_ARGS;
+ }
+ }else if( zFile==0 && eMode!='e' && eMode!='x' ){
+ zFile = sqlite3_mprintf("%s", z);
+ shell_check_oom(zFile);
+ if( zFile[0]=='|' ){
+ while( i+1<nArg ){
+ zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
+ shell_check_oom(zFile);
+ }
+ break;
+ }
}else{
- zSql = sqlite3_mprintf
- ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE"
- " key=%Q AND uses=%d", zEditor, name, uses);
+ *pzErr = shellMPrintf(0," excess argument: \"%s\"\n", azArg[i]);
+ sqlite3_free(zFile);
+ return SHELL_INVALID_ARGS;
}
+ }
+ if( zFile==0 ){
+ zFile = sqlite3_mprintf("stdout");
+ shell_check_oom(zFile);
+ }
+ if( bOnce ){
+ p->outCount = 2;
}else{
- /* Editing a new value of same kind. */
- assert(paramVU.value==0 || paramVU.uses!=uses);
- if( eval!=0 ){
- zSql = sqlite3_mprintf
- ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
- zVal = db_text(db, zSql, 1);
- sqlite3_free(zSql);
- zSql = sqlite3_mprintf
- ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
- " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
- name, zVal, uses);
+ p->outCount = 0;
+ }
+ output_reset(p);
+#ifndef SQLITE_NOHAVE_SYSTEM
+ if( eMode=='e' || eMode=='x' ){
+ p->doXdgOpen = 1;
+ outputModePush(p);
+ if( eMode=='x' ){
+ /* spreadsheet mode. Output as CSV. */
+ newTempFile(p, "csv");
+ ShellClearFlag(p, SHFLG_Echo);
+ p->mode = MODE_Csv;
+ sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
}else{
- zSql = sqlite3_mprintf
- ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
- " VALUES (%Q,edit('-- %q%s', %Q),%d)",
- name, name, "\n", zEditor, uses);
+ /* text editor mode */
+ newTempFile(p, "txt");
+ bTxtMode = 1;
}
+ sqlite3_free(zFile);
+ zFile = sqlite3_mprintf("%s", p->zTempFile);
}
- shell_check_oom(zSql);
- if( eval!=0 ){
+#endif /* SQLITE_NOHAVE_SYSTEM */
+ shell_check_oom(zFile);
+ if( zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = shellMPrintf(&rc, "Error: pipes are not supported in this OS\n");
+ rc = 1;
+ p->out = STD_OUT;
+#else
+ p->out = popen(zFile + 1, "w");
+ if( p->out==0 ){
+ *pzErr = shellMPrintf(&rc, "Error: cannot open pipe \"%s\"\n", zFile + 1);
+ p->out = STD_OUT;
+ rc = 1;
+ }else{
+ if( bBOM ) fprintf(p->out,"\357\273\277");
+ sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
+ }
+#endif
+ }else{
+ p->out = output_file_open(zFile, bTxtMode);
+ if( p->out==0 ){
+ if( strcmp(zFile,"off")!=0 ){
+ *pzErr = shellMPrintf
+ (&rc, "Error: cannot write to \"%s\"\n", zFile);
+ }
+ p->out = STD_OUT;
+ rc = 1;
+ } else {
+ if( bBOM ) fprintf(p->out,"\357\273\277");
+ sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
+ }
}
- rc = sqlite3_exec(db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
- sqlite3_free(zVal);
- return rc!=SQLITE_OK;
+ sqlite3_free(zFile);
+ return rc;
}
-#endif
-/* Space-join values in an argument list. *valLim is not included. */
-char *values_join( char **valBeg, char **valLim ){
- char *z = 0;
- const char *zSep = 0;
- while( valBeg < valLim ){
- z = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg);
- zSep = " ";
- ++valBeg;
+
+/*****************
+ * The .filectrl and fullschema commands
+ */
+COLLECT_HELP_TEXT[
+ ".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",
+];
+DISPATCHABLE_COMMAND( filectrl ? 2 0 ){
+ static const struct {
+ const char *zCtrlName; /* Name of a test-control option */
+ int ctrlCode; /* Integer code for that option */
+ const char *zUsage; /* Usage notes */
+ } aCtrl[] = {
+ { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" },
+ { "data_version", SQLITE_FCNTL_DATA_VERSION, "" },
- { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
++ { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
+ { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
+ { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
+ /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
+ { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
+ { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" },
+ { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" },
+ { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
+ /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
+ };
+ int filectrl = -1;
+ int iCtrl = -1;
+ sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
+ int isOk = 0; /* 0: usage 1: %lld 2: no-result */
+ int n2, i;
+ const char *zCmd = 0;
+ const char *zSchema = 0;
+
+ open_db(p, 0);
+ zCmd = nArg>=2 ? azArg[1] : "help";
+
- if( zCmd[0]=='-'
++ if( zCmd[0]=='-'
+ && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
+ && nArg>=4
+ ){
+ zSchema = azArg[2];
+ for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
+ nArg -= 2;
+ zCmd = azArg[1];
}
- return z;
+
+ /* The argument can optionally begin with "-" or "--" */
+ if( zCmd[0]=='-' && zCmd[1] ){
+ zCmd++;
+ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+ }
+
+ /* --help lists all file-controls */
+ if( strcmp(zCmd,"help")==0 ){
+ utf8_printf(p->out, "Available file-controls:\n");
+ for(i=0; i<ArraySize(aCtrl); i++){
+ utf8_printf(p->out, " .filectrl %s %s\n",
+ aCtrl[i].zCtrlName, aCtrl[i].zUsage);
+ }
+ return 1;
+ }
+
+ /* Convert filectrl text option to value. Allow any
+ ** unique prefix of the option name, or a numerical value. */
+ n2 = strlen30(zCmd);
+ for(i=0; i<ArraySize(aCtrl); i++){
+ if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
+ if( filectrl<0 ){
+ filectrl = aCtrl[i].ctrlCode;
+ iCtrl = i;
+ }else{
+ utf8_printf(STD_ERR, "Error: ambiguous file-control: \"%s\"\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ return 1;
+ }
+ }
+ }
+ if( filectrl<0 ){
+ utf8_printf(STD_ERR,"Error: unknown file-control: %s\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ }else{
+ switch(filectrl){
+ case SQLITE_FCNTL_SIZE_LIMIT: {
+ if( nArg!=2 && nArg!=3 ) break;
+ iRes = nArg==3 ? integerValue(azArg[2]) : -1;
+ sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_LOCK_TIMEOUT:
+ case SQLITE_FCNTL_CHUNK_SIZE: {
+ int x;
+ if( nArg!=3 ) break;
+ x = (int)integerValue(azArg[2]);
+ sqlite3_file_control(p->db, zSchema, filectrl, &x);
+ isOk = 2;
+ break;
+ }
+ case SQLITE_FCNTL_PERSIST_WAL:
+ case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
+ int x;
+ if( nArg!=2 && nArg!=3 ) break;
+ x = nArg==3 ? booleanValue(azArg[2]) : -1;
+ sqlite3_file_control(p->db, zSchema, filectrl, &x);
+ iRes = x;
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_DATA_VERSION:
+ case SQLITE_FCNTL_HAS_MOVED: {
+ int x;
+ if( nArg!=2 ) break;
+ sqlite3_file_control(p->db, zSchema, filectrl, &x);
+ iRes = x;
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_TEMPFILENAME: {
+ char *z = 0;
+ if( nArg!=2 ) break;
+ sqlite3_file_control(p->db, zSchema, filectrl, &z);
+ if( z ){
+ utf8_printf(p->out, "%s\n", z);
+ sqlite3_free(z);
+ }
+ isOk = 2;
+ break;
+ }
+ case SQLITE_FCNTL_RESERVE_BYTES: {
+ int x;
+ if( nArg>=3 ){
+ x = atoi(azArg[2]);
+ sqlite3_file_control(p->db, zSchema, filectrl, &x);
+ }
+ x = -1;
+ sqlite3_file_control(p->db, zSchema, filectrl, &x);
+ utf8_printf(p->out,"%d\n", x);
+ isOk = 2;
+ break;
+ }
+ }
+ }
+ if( isOk==0 && iCtrl>=0 ){
+ utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+ return 1;
+ }else if( isOk==1 ){
+ char zBuf[100];
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
+ raw_printf(p->out, "%s\n", zBuf);
+ }
+ return 0;
}
-/* Get a named parameter value in form of stepped prepared statement,
- * ready to have its value taken from the 0th column. If the name
- * cannot be found for the given ParamTableUse, 0 is returned.
- * The caller is responsible for calling sqlite3_finalize(pStmt),
- * where pStmt is the return from this function.
- */
-static sqlite3_stmt *get_param_value(sqlite3 *db, char *name,
- ParamTableUse ptu){
- sqlite3_stmt *rv = 0;
+DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
int rc;
- char *zSql = sqlite3_mprintf
- ( "SELECT value FROM "PARAM_TABLE_SNAME
- " WHERE key=%Q AND uses=%d", name, ptu );
- shell_check_oom(zSql);
- rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0);
- sqlite3_free(zSql);
- if( SQLITE_OK==rc ){
- if( SQLITE_ROW==sqlite3_step(rv) ) return rv;
- sqlite3_finalize(rv);
+ ShellState data;
+ int doStats = 0;
+ memcpy(&data, p, sizeof(data));
+ data.showHeader = 0;
+ data.cMode = data.mode = MODE_Semi;
+ if( nArg==2 && optionMatch(azArg[1], "indent") ){
+ data.cMode = data.mode = MODE_Pretty;
+ nArg = 1;
+ }
+ if( nArg!=1 ){
+ return SHELL_INVALID_ARGS;
+ }
+ open_db(p, 0);
+ rc = sqlite3_exec(p->db,
+ "SELECT sql FROM"
+ " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
+ " FROM sqlite_schema UNION ALL"
+ " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
+ "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
+ "ORDER BY x",
+ callback, &data, 0
+ );
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pStmt;
+ rc = sqlite3_prepare_v2(p->db,
+ "SELECT rowid FROM sqlite_schema"
+ " WHERE name GLOB 'sqlite_stat[134]'",
+ -1, &pStmt, 0);
+ doStats = sqlite3_step(pStmt)==SQLITE_ROW;
+ sqlite3_finalize(pStmt);
+ }
+ if( doStats==0 ){
+ raw_printf(p->out, "/* No STAT tables available */\n");
+ }else{
+ raw_printf(p->out, "ANALYZE sqlite_schema;\n");
+ data.cMode = data.mode = MODE_Insert;
+ data.zDestTable = "sqlite_stat1";
+ shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
+ data.zDestTable = "sqlite_stat4";
+ shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
+ raw_printf(p->out, "ANALYZE sqlite_schema;\n");
}
- return 0;
+ return rc > 0;
}
-static struct ParamSetOpts {
- const char cCast;
- const char *zTypename;
- int evalKind;
-} param_set_opts[] = {
- /* { 'q', 0, 2 }, */
- /* { 'x', 0, 1 }, */
- { 'i', "INT", 1 },
- { 'r', "REAL", 1 },
- { 'b', "BLOB", 1 },
- { 't', "TEXT", 0 },
- { 'n', "NUMERIC", 1 }
-};
+/*****************
+ * The .headers command
+ */
+COLLECT_HELP_TEXT[
+ ".headers on|off Turn display of headers on or off",
+];
+DISPATCHABLE_COMMAND( headers 6 2 2 ){
+ p->showHeader = booleanValue(azArg[1]);
+ p->shellFlgs |= SHFLG_HeaderSet;
+ return 0;
+}
-/* Return an option character if it is single and prefixed by - or --,
- * else return 0.
+/*****************
+ * The .help command
*/
-static char option_char(char *zArg){
- if( zArg[0]=='-' ){
- ++zArg;
- if( zArg[0]=='-' ) ++zArg;
- if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
+COLLECT_HELP_TEXT[
- ".help ?(PATTERN|-all)? Show help text for some or all command(s)",
- " PATTERN Show help for matching command(s)",
- " -all Show all help for all commands",
++ ".help ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize",
++ " Repeat -all to see undocumented commands",
+];
+DISPATCHABLE_COMMAND( help 3 1 2 ){
+ const char *zPat = 0;
+ if( nArg>1 ){
+ char *z = azArg[1];
+ if( strcmp(z,"-a")==0
+ || strcmp(z,"-all")==0
+ || strcmp(z,"--all")==0 ){
+ zPat = "";
+ }else{
+ zPat = z;
+ }
+ }
+ if( showHelp(p->out, zPat)==0 ){
+ utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
}
+ /* Help pleas never fail! */
return 0;
}
-static int param_set(sqlite3 *db, char cCast,
- char *name, char **valBeg, char **valLim,
- ParamTableUse ptu){
- char *zSql = 0;
- int rc = SQLITE_OK, retries = 0, needsEval = 1;
- char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
- sqlite3_stmt *pStmtSet = 0;
- const char *zCastTo = 0;
- char *zValue = (zValGlom==0)? *valBeg : zValGlom;
- if( cCast ){
- struct ParamSetOpts *pSO = param_set_opts;
- for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){
- if( cCast==pSO->cCast ){
- zCastTo = pSO->zTypename;
- needsEval = pSO->evalKind > 0;
- break;
- }
- }
+/*****************
+ * The .import command
+ */
+COLLECT_HELP_TEXT[
+ ".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.",
+];
+DISPATCHABLE_COMMAND( import ? 3 7 ){
+ char *zTable = 0; /* Insert data into this table */
+ char *zSchema = "main"; /* within this schema */
+ char *zFile = 0; /* Name of file to extra content from */
+ sqlite3_stmt *pStmt = NULL; /* A statement */
+ int nCol; /* Number of columns in the table */
+ int nByte; /* Number of bytes in an SQL string */
+ int i, j; /* Loop counters */
+ int needCommit; /* True to COMMIT or ROLLBACK at end */
+ int nSep; /* Number of bytes in p->colSeparator[] */
+ char *zSql; /* An SQL statement */
+ ImportCtx sCtx; /* Reader context */
+ char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
+ int eVerbose = 0; /* Larger for more console output */
+ int nSkip = 0; /* Initial lines to skip */
+ int useOutputMode = 1; /* Use output mode to determine separators */
+ int rc = 0;
+
+ if(p->bSafeMode) return SHELL_FORBIDDEN_OP;
+ memset(&sCtx, 0, sizeof(sCtx));
+ if( 0==(sCtx.z = sqlite3_malloc64(120)) ){
+ shell_out_of_memory();
}
- if( needsEval ){
- if( zCastTo!=0 ){
- zSql = sqlite3_mprintf
- ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
- " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu );
+
+ if( p->mode==MODE_Ascii ){
+ xRead = ascii_read_one_field;
+ }else{
+ xRead = csv_read_one_field;
+ }
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ if( z[0]!='-' ){
+ if( zFile==0 ){
+ zFile = z;
+ }else if( zTable==0 ){
+ zTable = z;
+ }else{
+ *pzErr = shellMPrintf(0," surplus argument: \"%s\"\n", z);
+ return SHELL_INVALID_ARGS;
+ }
+ }else if( strcmp(z,"-v")==0 ){
+ eVerbose++;
+ }else if( strcmp(z,"-schema")==0 && i<nArg-1 ){
+ zSchema = azArg[++i];
+ }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
+ nSkip = integerValue(azArg[++i]);
+ }else if( strcmp(z,"-ascii")==0 ){
+ sCtx.cColSep = SEP_Unit[0];
+ sCtx.cRowSep = SEP_Record[0];
+ xRead = ascii_read_one_field;
+ useOutputMode = 0;
+ }else if( strcmp(z,"-csv")==0 ){
+ sCtx.cColSep = ',';
+ sCtx.cRowSep = '\n';
+ xRead = csv_read_one_field;
+ useOutputMode = 0;
}else{
- zSql = sqlite3_mprintf
- ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
- "VALUES(%Q,(%s),%d);", name, zValue, ptu );
+ *pzErr = shellMPrintf(0," unknown option: \"%s\"", z);
+ return SHELL_INVALID_ARGS;
}
- shell_check_oom(zSql);
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
- sqlite3_free(zSql);
}
- if( !needsEval || rc!=SQLITE_OK ){
- /* Reach here when value either requested to be cast to text, or must be. */
- sqlite3_finalize(pStmtSet);
- pStmtSet = 0;
- zSql = sqlite3_mprintf
- ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
- "VALUES(%Q,%Q,%d);", name, zValue, ptu );
- shell_check_oom(zSql);
- rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
- assert(rc==SQLITE_OK);
- sqlite3_free(zSql);
+ if( zTable==0 ){
+ *pzErr = shellMPrintf(0," missing %s argument.\n",
+ zFile==0 ? "FILE" : "TABLE");
+ return SHELL_INVALID_ARGS;
}
- sqlite3_step(pStmtSet);
- sqlite3_finalize(pStmtSet);
- sqlite3_free(zValGlom);
- return rc;
-}
-
-/* list or ls subcommand for .parameter dot-command */
-static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
- char **pzArgs, int nArg){
- sqlite3_stmt *pStmt = 0;
- sqlite3_str *sbList = sqlite3_str_new(p->db);
- int len = 0, rc;
- char *zFromWhere = 0;
- char *zSql = 0;
- sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME
- " WHERE (?1=3 OR uses=?1) AND ");
- append_glob_terms(sbList, "key",
- (const char **)pzArgs, (const char **)pzArgs+nArg);
- zFromWhere = sqlite3_str_finish(sbList);
- shell_check_oom(zFromWhere);
- zSql = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere);
- shell_check_oom(zSql);
+ seenInterrupt = 0;
+ open_db(p, 0);
+ if( useOutputMode ){
+ const char *zYap = 0;
+ /* If neither the --csv or --ascii options are specified, then set
+ ** the column and row separator characters from the output mode. */
+ nSep = strlen30(p->colSeparator);
+ if( nSep==0 ){
+ zYap = "Error: non-null column separator required for import";
+ }
+ if( nSep>1 ){
+ zYap = "Error: multi-character or multi-byte column separators"
+ " not allowed for import";
+ }
+ nSep = strlen30(p->rowSeparator);
+ if( nSep==0 ){
+ zYap = "Error: non-null row separator required for import";
+ }
+ if( zYap!=0 ){
+ *pzErr = shellMPrintf(0,"%s\n", zYap);
+ return 1;
+ }
+ if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
+ /* When importing CSV (only), if the row separator is set to the
+ ** default output row separator, change it to the default input
+ ** row separator. This avoids having to maintain different input
+ ** and output row separators. */
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
+ nSep = strlen30(p->rowSeparator);
+ }
+ if( nSep>1 ){
+ *pzErr = sqlite3_mprintf
+ ("Error: multi-character row separators not allowed for import\n");
+ return 1;
+ }
+ sCtx.cColSep = p->colSeparator[0];
+ sCtx.cRowSep = p->rowSeparator[0];
+ }
+ sCtx.zFile = zFile;
+ sCtx.nLine = 1;
+ if( sCtx.zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n");
+ return 1;
+#else
+ sCtx.in = popen(sCtx.zFile+1, "r");
+ sCtx.zFile = "<pipe>";
+ sCtx.xCloser = pclose;
+#endif
+ }else{
+ sCtx.in = fopen(sCtx.zFile, "rb");
+ sCtx.xCloser = fclose;
+ }
+ if( sCtx.in==0 ){
+ *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", zFile);
+ import_cleanup(&sCtx);
+ return 1;
+ }
+ /* Below, resources must be freed before exit. */
+ if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
+ char zSep[2];
+ zSep[1] = 0;
+ zSep[0] = sCtx.cColSep;
+ utf8_printf(p->out, "Column separator ");
+ output_c_string(p->out, zSep);
+ utf8_printf(p->out, ", row separator ");
+ zSep[0] = sCtx.cRowSep;
+ output_c_string(p->out, zSep);
+ utf8_printf(p->out, "\n");
+ }
+ while( (nSkip--)>0 ){
+ while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
+ }
+ zSql = sqlite3_mprintf("SELECT * FROM \"%w\".\"%w\"", zSchema, zTable);
+ if( zSql==0 ){
+ import_cleanup(&sCtx);
+ shell_out_of_memory();
+ }
+ nByte = strlen30(zSql);
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- if( rc==SQLITE_OK ){
- sqlite3_bind_int(pStmt, 1, ptu);
- if( sqlite3_step(pStmt)==SQLITE_ROW ){
- len = sqlite3_column_int(pStmt, 0);
- if( len>40 ) len = 40;
- if( len<4 ) len = 4;
+ import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
+ if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
+ char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
+ zSchema, zTable);
+ sqlite3 *dbCols = 0;
+ char *zRenames = 0;
+ char *zColDefs;
+ while( xRead(&sCtx) ){
+ zAutoColumn(sCtx.z, &dbCols, 0);
+
+ if( sCtx.cTerm!=sCtx.cColSep ) break;
+ }
+ zColDefs = zAutoColumn(0, &dbCols, &zRenames);
+ if( zRenames!=0 ){
- utf8_printf((stdin_is_interactive && p->in==stdin)? p->out : stderr,
- "Columns renamed during .import %s due to duplicates:\n"
++ FILE *fh = INSOURCE_IS_INTERACTIVE(p->pInSource)? p->out : STD_ERR;
++ utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
+ "%s\n", sCtx.zFile, zRenames);
+ sqlite3_free(zRenames);
+ }
+ assert(dbCols==0);
+ if( zColDefs==0 ){
+ sqlite3_free(zCreate);
+ import_cleanup(&sCtx);
+ *pzErr = shellMPrintf(0,"%s: empty file\n", sCtx.zFile);
+ return 1;
}
- utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
+ zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
+ if( eVerbose>=1 ){
+ utf8_printf(p->out, "%s\n", zCreate);
+ }
+ rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
+ if( rc ){
++ utf8_printf(STD_ERR, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
+ sqlite3_free(zCreate);
+ import_cleanup(&sCtx);
+ return 1;
+ }
+ sqlite3_free(zCreate);
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ }
+ sqlite3_free(zSql);
+ if( rc ){
+ if (pStmt) sqlite3_finalize(pStmt);
+ *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+ import_cleanup(&sCtx);
+ return 1;
}
+ nCol = sqlite3_column_count(pStmt);
sqlite3_finalize(pStmt);
pStmt = 0;
- if( len ){
- sqlite3_free(zSql);
- if( !bShort ){
- int nBindings = 0, nScripts = 0;
- zSql = sqlite3_mprintf("SELECT key, uses, iif(uses, value, quote(value))"
- " %z ORDER BY uses, key", zFromWhere);
- shell_check_oom(zSql);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_bind_int(pStmt, 1, ptu);
- while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
- ParamTableUse ptux = sqlite3_column_int(pStmt,1);
- switch( ptux ){
- case PTU_Binding:
- if( nBindings++ == 0 ){
- utf8_printf(p->out, "Binding Values:\n%-*s %s\n",
- len, "name", "value");
- }
- utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
- sqlite3_column_text(pStmt,2));
- break;
- case PTU_Script:
- if( nScripts++ == 0 ){
- utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value");
+ if( nCol==0 ) return 0; /* no columns, no error */
+ zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
+ if( zSql==0 ){
+ import_cleanup(&sCtx);
+ shell_out_of_memory();
+ }
+ sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?",
+ zSchema, zTable);
+ j = strlen30(zSql);
+ for(i=1; i<nCol; i++){
+ zSql[j++] = ',';
+ zSql[j++] = '?';
+ }
+ zSql[j++] = ')';
+ zSql[j] = 0;
+ if( eVerbose>=2 ){
+ utf8_printf(p->out, "Insert using: %s\n", zSql);
+ }
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( rc ){
+ *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+ if (pStmt) sqlite3_finalize(pStmt);
+ import_cleanup(&sCtx);
+ return 1;
+ }
+ needCommit = sqlite3_get_autocommit(p->db);
+ if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
+ do{
+ int startLine = sCtx.nLine;
+ for(i=0; i<nCol; i++){
+ char *z = xRead(&sCtx);
+ /*
+ ** Did we reach end-of-file before finding any columns?
+ ** If so, stop instead of NULL filling the remaining columns.
+ */
+ if( z==0 && i==0 ) break;
+ /*
+ ** Did we reach end-of-file OR end-of-line before finding any
+ ** columns in ASCII mode? If so, stop instead of NULL filling
+ ** the remaining columns.
+ */
+ if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
+ sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
+ if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
+ utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
+ "filling the rest with NULL\n",
+ sCtx.zFile, startLine, nCol, i+1);
+ i += 2;
+ while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
+ }
+ }
+ if( sCtx.cTerm==sCtx.cColSep ){
+ do{
+ xRead(&sCtx);
+ i++;
+ }while( sCtx.cTerm==sCtx.cColSep );
+ utf8_printf(STD_ERR, "%s:%d: expected %d columns but found %d - "
+ "extras ignored\n",
+ sCtx.zFile, startLine, nCol, i);
+ }
+ if( i>=nCol ){
+ sqlite3_step(pStmt);
+ rc = sqlite3_reset(pStmt);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
+ startLine, sqlite3_errmsg(p->db));
+ sCtx.nErr++;
+ }else{
+ sCtx.nRow++;
+ }
+ }
+ }while( sCtx.cTerm!=EOF );
+
+ import_cleanup(&sCtx);
+ sqlite3_finalize(pStmt);
+ if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
+ if( eVerbose>0 ){
+ utf8_printf(p->out,
+ "Added %d rows with %d errors using %d lines of input\n",
+ sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
+ }
+ return 0;
+}
+
+/*****************
+ * The .keyword command
+ */
+CONDITION_COMMAND( keyword !defined(NO_KEYWORD_COMMAND) );
+COLLECT_HELP_TEXT[
+ ".keyword ?KW? List keywords, or say whether KW is one.",
+];
+DISPATCHABLE_COMMAND( keyword ? 1 2 ){
+ if( nArg<2 ){
+ int i = 0;
+ int nk = sqlite3_keyword_count();
+ int nCol = 0;
+ int szKW;
+ while( i<nk ){
+ const char *zKW = 0;
+ if( SQLITE_OK==sqlite3_keyword_name(i++, &zKW, &szKW) ){
+ char kwBuf[50];
+ if( szKW < sizeof(kwBuf) ){
+ const char *zSep = " ";
+ if( (nCol += (1+szKW))>75){
+ zSep = "\n";
+ nCol = 0;
}
- utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
- sqlite3_column_text(pStmt,2));
- break;
- default: break; /* Ignore */
+ memcpy(kwBuf, zKW, szKW);
+ kwBuf[szKW] = 0;
+ utf8_printf(p->out, "%s%s", kwBuf, zSep);
}
}
- }else{
- int nc = 0, ncw = 78/(len+2);
- zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere);
- shell_check_oom(zSql);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_bind_int(pStmt, 1, ptu);
- while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
- utf8_printf(p->out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""),
- len, sqlite3_column_text(pStmt,0));
+ }
+ if( nCol>0 ) utf8_printf(p->out, "\n");
+ }else{
+ int szKW = strlen30(azArg[1]);
+ int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
+ utf8_printf(p->out, "%s is%s a keyword\n",
+ azArg[1], (isKeyword)? "" : " not");
+ }
+ return 0;
+}
+
+/*****************
+ * The .imposter, .iotrace, limit, lint, .load and .log commands
+ */
+CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
+CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
+CONDITION_COMMAND( load !defined(SQLITE_OMIT_LOAD_EXTENSION) );
+COLLECT_HELP_TEXT[
+ ".imposter INDEX TABLE Create imposter table TABLE on index INDEX",
+ ".iotrace FILE Enable I/O diagnostic logging to FILE",
+ ".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",
+ ".load FILE ?ENTRY? Load an extension library",
+ ".log FILE|off Turn logging on or off. FILE can be stderr/stdout",
+];
+DISPATCHABLE_COMMAND( imposter ? 3 3 ){
+ int rc = 0;
+ char *zSql;
+ char *zCollist = 0;
+ sqlite3_stmt *pStmt;
+ int tnum = 0;
+ int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */
+ int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
+ int i;
+ if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
+ *pzErr = shellMPrintf(0,"Usage: .imposter INDEX IMPOSTER\n"
+ " .imposter off\n");
+ /* Also allowed, but not documented:
+ **
+ ** .imposter TABLE IMPOSTER
+ **
+ ** where TABLE is a WITHOUT ROWID table. In that case, the
+ ** imposter is another WITHOUT ROWID table with the columns in
+ ** storage order. */
+ return 1;
+ }
+ open_db(p, 0);
+ if( nArg==2 ){
+ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
+ return 0;
+ }
+ zSql = sqlite3_mprintf(
+ "SELECT rootpage, 0 FROM sqlite_schema"
+ " WHERE name='%q' AND type='index'"
+ "UNION ALL "
+ "SELECT rootpage, 1 FROM sqlite_schema"
+ " WHERE name='%q' AND type='table'"
+ " AND sql LIKE '%%without%%rowid%%'",
+ azArg[1], azArg[1]
+ );
+ sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ tnum = sqlite3_column_int(pStmt, 0);
+ isWO = sqlite3_column_int(pStmt, 1);
+ }
+ sqlite3_finalize(pStmt);
+ zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
+ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ sqlite3_free(zSql);
+ i = 0;
+ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
+ char zLabel[20];
+ const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
+ i++;
+ if( zCol==0 ){
+ if( sqlite3_column_int(pStmt,1)==-1 ){
+ zCol = "_ROWID_";
+ }else{
+ sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
+ zCol = zLabel;
}
- if( nc>0 ) utf8_printf(p->out, "\n");
}
- sqlite3_finalize(pStmt);
+ if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
+ lenPK = (int)strlen(zCollist);
+ }
+ if( zCollist==0 ){
+ zCollist = sqlite3_mprintf("\"%w\"", zCol);
+ }else{
+ zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
+ }
+ }
+ sqlite3_finalize(pStmt);
+ if( i==0 || tnum==0 ){
+ *pzErr = shellMPrintf(0,"no such index: \"%s\"\n", azArg[1]);
+ sqlite3_free(zCollist);
+ return 1;
+ }
+ if( lenPK==0 ) lenPK = 100000;
+ zSql = sqlite3_mprintf(
+ "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
+ azArg[2], zCollist, lenPK, zCollist);
+ sqlite3_free(zCollist);
+ rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
+ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
+ if( rc ){
+ *pzErr = shellMPrintf(0,"Error in [%s]: %s\n",
+ zSql, sqlite3_errmsg(p->db));
+ }else{
+ utf8_printf(STD_OUT, "%s;\n", zSql);
+ raw_printf(STD_OUT,
+ "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
+ azArg[1], isWO ? "table" : "index"
+ );
+ }
}else{
- sqlite3_free(zFromWhere);
+ *pzErr = shellMPrintf(0,"SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
}
sqlite3_free(zSql);
-}
-
-/* Append an OR'ed series of GLOB terms comparing a given column
- * name to a series of patterns. Result is an appended expression.
- * For an empty pattern series, expression is true for non-NULL.
- */
-static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
- const char **azBeg, const char **azLim){
- if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName);
- else{
- char *zSep = "(";
- while( azBeg<azLim ){
- sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
- zSep = " OR ";
- ++azBeg;
+ return rc != 0;
+}
+DISPATCHABLE_COMMAND( iotrace ? 2 2 ){
+ SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
+ if( iotrace && iotrace!=STD_OUT ) fclose(iotrace);
+ iotrace = 0;
+ if( nArg<2 ){
+ sqlite3IoTrace = 0;
+ }else if( strcmp(azArg[1], "-")==0 ){
+ sqlite3IoTrace = iotracePrintf;
+ iotrace = STD_OUT;
+ }else{
+ iotrace = fopen(azArg[1], "w");
+ if( iotrace==0 ){
+ *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]);
+ sqlite3IoTrace = 0;
+ return 1;
+ }else{
+ sqlite3IoTrace = iotracePrintf;
}
- sqlite3_str_appendf(pStr, ")");
}
+ return 0;
}
-
-/* Append either an IN clause or an always true test to some SQL.
- *
- * An empty IN list is the same as always true (for non-NULL LHS)
- * for this clause, which assumes a trailing LHS operand and space.
- * If that is not the right result, guard the call against it.
- * This is used for ".parameter dostuff ?NAMES?" options,
- * where a missing list means all the qualifying entries.
- *
- * The empty list may be signified by azBeg and azLim both 0.
- */
-static void append_in_clause(sqlite3_str *pStr,
- const char **azBeg, const char **azLim){
- if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
- else{
- char cSep = '(';
- sqlite3_str_appendf(pStr, "IN");
- while( azBeg<azLim ){
- sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
- cSep = ',';
- ++azBeg;
+DISPATCHABLE_COMMAND( limits 5 1 3 ){
+ static const struct {
+ const char *zLimitName; /* Name of a limit */
+ int limitCode; /* Integer code for that limit */
+ } aLimit[] = {
+ { "length", SQLITE_LIMIT_LENGTH },
+ { "sql_length", SQLITE_LIMIT_SQL_LENGTH },
+ { "column", SQLITE_LIMIT_COLUMN },
+ { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH },
+ { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT },
+ { "vdbe_op", SQLITE_LIMIT_VDBE_OP },
+ { "function_arg", SQLITE_LIMIT_FUNCTION_ARG },
+ { "attached", SQLITE_LIMIT_ATTACHED },
+ { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH },
+ { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER },
+ { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH },
+ { "worker_threads", SQLITE_LIMIT_WORKER_THREADS },
+ };
+ int i, n2;
+ open_db(p, 0);
+ if( nArg==1 ){
+ for(i=0; i<ArraySize(aLimit); i++){
+ fprintf(STD_OUT, "%20s %d\n", aLimit[i].zLimitName,
+ sqlite3_limit(p->db, aLimit[i].limitCode, -1));
}
- sqlite3_str_appendf(pStr, ")");
+ }else if( nArg>3 ){
+ return SHELL_INVALID_ARGS;
+ }else{
+ int iLimit = -1;
+ n2 = strlen30(azArg[1]);
+ for(i=0; i<ArraySize(aLimit); i++){
+ if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
+ if( iLimit<0 ){
+ iLimit = i;
+ }else{
+ *pzErr = shellMPrintf(0,"ambiguous limit: \"%s\"\n", azArg[1]);
+ return 1;
+ }
+ }
+ }
+ if( iLimit<0 ){
+ *pzErr = sqlite3_mprintf
+ ("unknown limit: \"%s\"\n"
+ "enter \".limits\" with no arguments for a list.\n",
+ azArg[1]);
+ return 1;
+ }
+ if( nArg==3 ){
+ sqlite3_limit(p->db, aLimit[iLimit].limitCode,
+ (int)integerValue(azArg[2]));
+ }
+ fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
+ sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
}
+ return 0;
}
+DISPATCHABLE_COMMAND( lint 3 1 0 ){
+ open_db(p, 0);
+ int n = (nArg>=2 ? strlen30(azArg[1]) : 0);
+ if( n>0 && !sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ){
+ return lintFkeyIndexes(p, azArg, nArg);
+ }
+ *pzErr = sqlite3_mprintf
+ ("Usage %s sub-command ?switches...?\n"
+ "Where sub-commands are:\n"
+ " fkey-indexes\n", azArg[0]);
+ return 1;
+}
-/*
- * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
- * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
- * close db and set it to 0, and return the columns spec, to later
- * be sqlite3_free()'ed by the caller.
- * The return is 0 when either:
- * (a) The db was not initialized and zCol==0 (There are no columns.)
- * (b) zCol!=0 (Column was added, db initialized as needed.)
- * The 3rd argument, pRenamed, references an out parameter. If the
- * pointer is non-zero, its referent will be set to a summary of renames
- * done if renaming was necessary, or set to 0 if none was done. The out
- * string (if any) must be sqlite3_free()'ed by the caller.
- */
-#ifdef SHELL_DEBUG
-#define rc_err_oom_die(rc) \
- if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
- else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
- fprintf(stderr,"E:%d\n",rc), assert(0)
-#else
-static void rc_err_oom_die(int rc){
- if( rc==SQLITE_NOMEM ) shell_check_oom(0);
- assert(rc==SQLITE_OK||rc==SQLITE_DONE);
+DISPATCHABLE_COMMAND( load ? 2 3 ){
+ const char *zFile, *zProc;
+ char *zErrMsg = 0;
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ zFile = azArg[1];
+ zProc = nArg>=3 ? azArg[2] : 0;
+ open_db(p, 0);
+ if( SQLITE_OK!=sqlite3_load_extension(p->db, zFile, zProc, pzErr) ){
+ return 1;
+ }
+ return 0;
}
-#endif
-#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
-static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
-#else /* Otherwise, memory is faster/better for the transient DB. */
-static const char *zCOL_DB = ":memory:";
-#endif
+DISPATCHABLE_COMMAND( log ? 2 2 ){
+ const char *zFile = azArg[1];
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ output_file_close(p->pLog);
+ p->pLog = output_file_open(zFile, 0);
+ return 0;
+}
-/* Define character (as C string) to separate generated column ordinal
- * from protected part of incoming column names. This defaults to "_"
- * so that incoming column identifiers that did not need not be quoted
- * remain usable without being quoted. It must be one character.
- */
-#ifndef SHELL_AUTOCOLUMN_SEP
-# define AUTOCOLUMN_SEP "_"
-#else
-# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
-#endif
++static void effectMode(ShellState *p, u8 modeRequest, u8 modeNominal){
++ /* Effect the specified mode change. */
++ const char *zColSep = 0, *zRowSep = 0;
++ assert(modeNominal!=MODE_COUNT_OF);
++ switch( modeRequest ){
++ case MODE_Line:
++ zRowSep = SEP_Row;
++ break;
++ case MODE_Column:
++ if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
++ p->showHeader = 1;
++ }
++ zRowSep = SEP_Row;
++ break;
++ case MODE_List:
++ zColSep = SEP_Column;
++ zRowSep = SEP_Row;
++ break;
++ case MODE_Html:
++ break;
++ case MODE_Tcl:
++ zColSep = SEP_Space;
++ zRowSep = SEP_Row;
++ break;
++ case MODE_Csv:
++ zColSep = SEP_Comma;
++ zRowSep = SEP_CrLf;
++ break;
++ case MODE_Tab:
++ zColSep = SEP_Tab;
++ break;
++ case MODE_Insert:
++ break;
++ case MODE_Quote:
++ zColSep = SEP_Comma;
++ zRowSep = SEP_Row;
++ break;
++ case MODE_Ascii:
++ zColSep = SEP_Unit;
++ zRowSep = SEP_Record;
++ break;
++ case MODE_Markdown:
++ /* fall-thru */
++ case MODE_Table:
++ /* fall-thru */
++ case MODE_Box:
++ break;
++ case MODE_Count:
++ /* fall-thru */
++ case MODE_Off:
++ /* fall-thru */
++ case MODE_Json:
++ break;
++ case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
++ /* Modes used internally, not settable by .mode command. */
++ return;
++ }
++ if( zRowSep!=0 ){
++ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, zRowSep);
++ }
++ if( zColSep!=0 ){
++ sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, zColSep);
++ }
++ p->mode = modeNominal;
++}
+
-static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
- /* Queries and D{D,M}L used here */
- static const char * const zTabMake = "\
-CREATE TABLE ColNames(\
- cpos INTEGER PRIMARY KEY,\
- name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
-CREATE VIEW RepeatedNames AS \
-SELECT DISTINCT t.name FROM ColNames t \
-WHERE t.name COLLATE NOCASE IN (\
- SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
-);\
-";
- static const char * const zTabFill = "\
-INSERT INTO ColNames(name,nlen,chop,reps,suff)\
- VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
-";
- static const char * const zHasDupes = "\
-SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
- <count(name) FROM ColNames\
-";
-#ifdef SHELL_COLUMN_RENAME_CLEAN
- static const char * const zDedoctor = "\
-UPDATE ColNames SET chop=iif(\
- (substring(name,nlen,1) BETWEEN '0' AND '9')\
- AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
- nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
- 0\
-)\
-";
-#endif
- static const char * const zSetReps = "\
-UPDATE ColNames AS t SET reps=\
-(SELECT count(*) FROM ColNames d \
- WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
- COLLATE NOCASE\
-)\
-";
-#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
- static const char * const zColDigits = "\
-SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
-";
-#endif
- static const char * const zRenameRank =
-#ifdef SHELL_COLUMN_RENAME_CLEAN
- "UPDATE ColNames AS t SET suff="
- "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
-#else /* ...RENAME_MINIMAL_ONE_PASS */
-"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
-" SELECT 0 AS nlz"
-" UNION"
-" SELECT nlz+1 AS nlz FROM Lzn"
-" WHERE EXISTS("
-" SELECT 1"
-" FROM ColNames t, ColNames o"
-" WHERE"
-" iif(t.name IN (SELECT * FROM RepeatedNames),"
-" printf('%s"AUTOCOLUMN_SEP"%s',"
-" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
-" t.name"
-" )"
-" ="
-" iif(o.name IN (SELECT * FROM RepeatedNames),"
-" printf('%s"AUTOCOLUMN_SEP"%s',"
-" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
-" o.name"
-" )"
-" COLLATE NOCASE"
-" AND o.cpos<>t.cpos"
-" GROUP BY t.cpos"
-" )"
-") UPDATE Colnames AS t SET"
-" chop = 0," /* No chopping, never touch incoming names. */
-" suff = iif(name IN (SELECT * FROM RepeatedNames),"
-" printf('"AUTOCOLUMN_SEP"%s', substring("
-" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
-" ''"
-" )"
-#endif
- ;
- static const char * const zCollectVar = "\
-SELECT\
- '('||x'0a'\
- || group_concat(\
- cname||' TEXT',\
- ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
- ||')' AS ColsSpec \
-FROM (\
- SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \
- FROM ColNames ORDER BY cpos\
-)";
- static const char * const zRenamesDone =
- "SELECT group_concat("
- " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff)),"
- " ','||x'0a')"
- "FROM ColNames WHERE suff<>'' OR chop!=0"
- ;
- int rc;
- sqlite3_stmt *pStmt = 0;
- assert(pDb!=0);
- if( zColNew ){
- /* Add initial or additional column. Init db if necessary. */
- if( *pDb==0 ){
- if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
-#ifdef SHELL_COLFIX_DB
- if(*zCOL_DB!=':')
- sqlite3_exec(*pDb,"drop table if exists ColNames;"
- "drop view if exists RepeatedNames;",0,0,0);
-#endif
- rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
- rc_err_oom_die(rc);
+/*****************
+ * The .mode command
+ */
+COLLECT_HELP_TEXT[
- ".mode MODE ?TABLE? Set output mode",
++ ".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 --width 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",
+];
+DISPATCHABLE_COMMAND( mode ? 1 0 ){
+ const char *zTabname = 0;
+ const char *zArg;
+ int i;
+ u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
+ ColModeOpts cmOpts = ColModeOpts_default;
+ for(i=1; i<nArg; i++){
+ zArg = azArg[i];
+ if( optionMatch(zArg,"wrap") && i+1<nArg ){
+ cmOpts.iWrap = integerValue(azArg[++i]);
+ }else if( optionMatch(zArg,"ww") ){
+ cmOpts.bWordWrap = 1;
+ }else if( optionMatch(zArg,"wordwrap") && i+1<nArg ){
+ cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]);
+ }else if( optionMatch(zArg,"quote") ){
+ cmOpts.bQuote = 1;
+ }else if( optionMatch(zArg,"noquote") ){
+ cmOpts.bQuote = 0;
+ }else{
+ /* Not a known option. Check for known mode, or possibly a table name. */
+ if( foundMode==MODE_Insert && zTabname==0 ){
+ zTabname = zArg;
+ }else if( *zArg=='-' ){
+ goto flag_unknown;
+ }else{
+ int im, nza = strlen30(zArg);
+ if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
+ for( im=0; im<MODE_COUNT_OF; ++im ){
+ if( modeDescr[i].bUserBlocked ) continue;
+ if( strncmp(zArg,modeDescr[im].zModeName,nza)==0 ){
+ foundMode = (u8)im;
+ setMode = modeDescr[im].iAliasFor;
+ break;
+ }
+ }
+ if( strcmp(zArg, "qbox")==0 ){
+ ColModeOpts cmo = ColModeOpts_default_qbox;
+ foundMode = setMode = MODE_Box;
+ cmOpts = cmo;
+ }else if( im==MODE_COUNT_OF ) goto mode_unknown;
+ }
+ }
+ } /* Arg loop */
+ if( foundMode==MODE_COUNT_OF ){
+ const char *zMode;
+ int nms;
+ i = p->mode;
+ assert(i>=0 && i<MODE_COUNT_OF);
+ zMode = modeDescr[i].zModeName;
+ nms = strlen30(zMode)-modeDescr[i].bDepluralize;
+ /* Mode not specified. Show present mode (and toss any options set.) */
- if( p->mode==MODE_Column || (p->mode>=MODE_Markdown && p->mode<=MODE_Box) ){
++ if( MODE_IS_COLUMNAR(p->mode) ){
+ raw_printf
+ (p->out, "current output mode: %.*s --wrap %d --wordwrap %s --%squote\n",
+ nms, zMode, p->cmOpts.iWrap,
+ p->cmOpts.bWordWrap ? "on" : "off",
+ p->cmOpts.bQuote ? "" : "no");
+ }else{
+ raw_printf(p->out, "current output mode: %.*s\n", nms, zMode);
}
- assert(*pDb!=0);
- rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
- rc_err_oom_die(rc);
- rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
- rc_err_oom_die(rc);
- rc = sqlite3_step(pStmt);
- rc_err_oom_die(rc);
- sqlite3_finalize(pStmt);
- return 0;
- }else if( *pDb==0 ){
- return 0;
}else{
- /* Effect the specified mode change. */
- const char *zColSep = 0, *zRowSep = 0;
- assert(setMode!=MODE_COUNT_OF);
- switch( foundMode ){
- case MODE_Line:
- zRowSep = SEP_Row;
- break;
- case MODE_Column:
- if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
- p->showHeader = 1;
- }
- zRowSep = SEP_Row;
- p->cmOpts = cmOpts;
- break;
- case MODE_List:
- zColSep = SEP_Column;
- zRowSep = SEP_Row;
- break;
- case MODE_Html:
- break;
- case MODE_Tcl:
- zColSep = SEP_Space;
- zRowSep = SEP_Row;
- break;
- case MODE_Csv:
- zColSep = SEP_Comma;
- zRowSep = SEP_CrLf;
- break;
- case MODE_Tab:
- zColSep = SEP_Tab;
- break;
- case MODE_Insert:
- /* Formulate the columns spec, close the DB, zero *pDb. */
- char *zColsSpec = 0;
- int hasDupes = db_int(*pDb, zHasDupes);
-#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
- int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
-#else
-# define nDigits 2
-#endif
- if( hasDupes ){
-#ifdef SHELL_COLUMN_RENAME_CLEAN
- rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
- rc_err_oom_die(rc);
++ effectMode(p, foundMode, setMode);
++ if( MODE_IS_COLUMNAR(setMode) ) p->cmOpts = cmOpts;
++ else if( setMode==MODE_Insert ){
+ set_table_name(p, zTabname ? zTabname : "table");
- break;
- case MODE_Quote:
- zColSep = SEP_Comma;
- zRowSep = SEP_Row;
- break;
- case MODE_Ascii:
- zColSep = SEP_Unit;
- zRowSep = SEP_Record;
- break;
- case MODE_Markdown:
- /* fall-thru */
- case MODE_Table:
- /* fall-thru */
- case MODE_Box:
- p->cmOpts = cmOpts;
- break;
- case MODE_Count:
- /* fall-thru */
- case MODE_Off:
- /* fall-thru */
- case MODE_Json:
- break;
- case MODE_Explain: case MODE_Semi: case MODE_EQP: case MODE_Pretty: default:
- /* Modes used internally, not settable by .mode command. */
- return 0;
- }
- if( zRowSep!=0 ){
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, zRowSep);
- }
- if( zColSep!=0 ){
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, zColSep);
+ }
- p->mode = setMode;
+ }
+ p->cMode = p->mode;
+ return 0;
+ flag_unknown:
- utf8_printf(stderr, "Error: Unknown .mode option: %s\nValid options:\n%s",
++ utf8_printf(STD_ERR, "Error: Unknown .mode option: %s\nValid options:\n%s",
+ zArg,
+ " --noquote\n"
+ " --quote\n"
+ " --wordwrap on/off\n"
+ " --wrap N\n"
+ " --ww\n");
+ return 1;
+ mode_unknown:
- raw_printf(stderr, "Error: Mode should be one of: "
++ raw_printf(STD_ERR, "Error: Mode should be one of: "
+ "ascii box column csv html insert json line list markdown "
+ "qbox quote table tabs tcl\n");
+ return 1;
+ mode_badarg:
- utf8_printf(stderr, "Error: Invalid .mode argument: %s\n", zArg);
++ utf8_printf(STD_ERR, "Error: Invalid .mode argument: %s\n", zArg);
+ return 1;
+}
+
+/*****************
+ * The .open, .nonce and .nullvalue commands
+ */
+COLLECT_HELP_TEXT[
+ ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
+ " Options:",
+ " --append Use appendvfs to append database to the end of FILE",
+#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
- rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
- rc_err_oom_die(rc);
- rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
- rc_err_oom_die(rc);
- sqlite3_bind_int(pStmt, 1, nDigits);
- rc = sqlite3_step(pStmt);
- sqlite3_finalize(pStmt);
- assert(rc==SQLITE_DONE);
+ " --new Initialize FILE to an empty database",
+ " --nofollow Do not follow symbolic links",
+ " --readonly Open FILE readonly",
+ " --zip FILE is a ZIP archive",
+ ".nonce STRING Suspend safe mode for one command if nonce matches",
+ ".nullvalue STRING Use STRING in place of NULL values",
+];
+DISPATCHABLE_COMMAND( open 3 1 0 ){
+ const char *zFN = 0; /* Pointer to constant filename */
+ char *zNewFilename = 0; /* Name of the database file to open */
+ int iName = 1; /* Index in azArg[] of the filename */
+ int newFlag = 0; /* True to delete file before opening */
- int openMode = SHELL_OPEN_UNSPEC;
++ u8 openMode = SHELL_OPEN_UNSPEC;
+ int rc = 0;
+ /* Check for command-line arguments */
+ for(iName=1; iName<nArg; iName++){
+ const char *z = azArg[iName];
+ if( optionMatch(z,"new") ){
+ newFlag = 1;
+#ifdef SQLITE_HAVE_ZLIB
+ }else if( optionMatch(z, "zip") ){
+ openMode = SHELL_OPEN_ZIPFILE;
+#endif
+ }else if( optionMatch(z, "append") ){
+ openMode = SHELL_OPEN_APPENDVFS;
+ }else if( optionMatch(z, "readonly") ){
+ openMode = SHELL_OPEN_READONLY;
+ }else if( optionMatch(z, "nofollow") ){
+ p->openFlags |= SQLITE_OPEN_NOFOLLOW;
+#ifndef SQLITE_OMIT_DESERIALIZE
+ }else if( optionMatch(z, "deserialize") ){
+ openMode = SHELL_OPEN_DESERIALIZE;
+ }else if( optionMatch(z, "hexdb") ){
+ openMode = SHELL_OPEN_HEXDB;
+ }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
+ p->szMax = integerValue(azArg[++iName]);
+#endif /* SQLITE_OMIT_DESERIALIZE */
+ }else if( z[0]=='-' ){
+ *pzErr = shellMPrintf(0,"unknown option: %s\n", z);
+ return SHELL_INVALID_ARGS;
+ }else if( zFN ){
+ *pzErr = shellMPrintf(0,"extra argument: \"%s\"\n", z);
+ return SHELL_INVALID_ARGS;
+ }else{
+ zFN = z;
+ }
+ }
+
+ /* Close the existing database */
+ session_close_all(p, -1);
+ close_db(p->db);
+ p->db = 0;
+ p->pAuxDb->zDbFilename = 0;
+ sqlite3_free(p->pAuxDb->zFreeOnClose);
+ p->pAuxDb->zFreeOnClose = 0;
+ p->openMode = openMode;
+ p->openFlags = 0;
+ p->szMax = 0;
+
+ /* If a filename is specified, try to open it first */
+ if( zFN || p->openMode==SHELL_OPEN_HEXDB ){
+ if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN);
+ if( p->bSafeMode
+ && p->openMode!=SHELL_OPEN_HEXDB
+ && zFN
+ && strcmp(zFN,":memory:")!=0
+ ){
+ *pzErr = shellMPrintf(0,"cannot open database files in safe mode");
+ return SHELL_FORBIDDEN_OP;
}
- assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
- rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
- rc_err_oom_die(rc);
- rc = sqlite3_step(pStmt);
- if( rc==SQLITE_ROW ){
- zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
+ if( zFN ){
+ zNewFilename = sqlite3_mprintf("%s", zFN);
+ shell_check_oom(zNewFilename);
}else{
- zColsSpec = 0;
+ zNewFilename = 0;
}
- if( pzRenamed!=0 ){
- if( !hasDupes ) *pzRenamed = 0;
- else{
- sqlite3_finalize(pStmt);
- if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
- && SQLITE_ROW==sqlite3_step(pStmt) ){
- *pzRenamed = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
- }else
- *pzRenamed = 0;
- }
+ p->pAuxDb->zDbFilename = zNewFilename;
+ open_db(p, OPEN_DB_KEEPALIVE);
+ if( p->db==0 ){
+ *pzErr = shellMPrintf(0,"Error: cannot open '%s'\n", zNewFilename);
+ sqlite3_free(zNewFilename);
+ rc = 1;
+ }else{
+ p->pAuxDb->zFreeOnClose = zNewFilename;
}
- sqlite3_finalize(pStmt);
- sqlite3_close(*pDb);
- *pDb = 0;
- return zColsSpec;
}
+ if( p->db==0 ){
+ /* As a fall-back open a TEMP database */
+ p->pAuxDb->zDbFilename = 0;
+ open_db(p, 0);
+ }
+ return rc;
}
-/*
-** If an input line begins with "." then invoke this routine to
-** process that line.
-**
-** Return 1 on error, 2 to exit, and 0 otherwise.
-*/
-static int do_meta_command(char *zLine, ShellState *p){
- int h = 1;
- int nArg = 0;
- int n, c;
- int rc = 0;
- char *azArg[52];
-#if SHELL_VARIABLE_EXPANSION
- int ncLineIn = strlen30(zLine);
- u8 bExpVars = SHEXT_VAREXP(p);
-#endif
-
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( p->expert.pExpert ){
- expertFinish(p, 1, 0);
+DISPATCHABLE_COMMAND( nonce ? 2 2 ){
+ if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
+ raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
- p->lineno, azArg[1]);
++ p->pInSource->lineno, azArg[1]);
+ exit(1);
}
- /*****************
- * The .parameter command
+ /* Suspend safe mode for 1 meta-command after this. */
+ p->bSafeModeFuture = 2;
+ return 0;
+}
+
+DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
+ sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
+ "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
+ return 0;
+}
+
- COLLECT_HELP_TEXT[
- ".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",
- ];
- DISPATCHABLE_COMMAND( parameter 4 2 4 ){
- int rc = 0;
- open_db(p,0);
-
- /* .parameter clear
- ** Clear all bind parameters by dropping the TEMP table that holds them.
- */
- if( nArg==2 && strcmp(azArg[1],"clear")==0 ){
- sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;",
- 0, 0, 0);
- }else
-
- /* .parameter list
- ** List all bind parameters.
- */
- if( nArg==2 && strcmp(azArg[1],"list")==0 ){
- sqlite3_stmt *pStmt = 0;
- int rx;
- int len = 0;
- rx = sqlite3_prepare_v2(p->db,
- "SELECT max(length(key)) "
- "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
- if( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
- len = sqlite3_column_int(pStmt, 0);
- if( len>40 ) len = 40;
- }
- sqlite3_finalize(pStmt);
- pStmt = 0;
- if( len ){
- rx = sqlite3_prepare_v2(p->db,
- "SELECT key, quote(value) "
- "FROM temp.sqlite_parameters;", -1, &pStmt, 0);
- while( rx==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
- utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
- sqlite3_column_text(pStmt,1));
- }
- sqlite3_finalize(pStmt);
- }
- }else
-
- /* .parameter init
- ** Make sure the TEMP table used to hold bind parameters exists.
- ** Create it if necessary.
- */
- if( nArg==2 && strcmp(azArg[1],"init")==0 ){
- bind_table_init(p);
- }else
++/* Helper functions for .parameter command
+ */
- /* .parameter set NAME VALUE
- ** Set or reset a bind parameter. NAME should be the full parameter
- ** name exactly as it appears in the query. (ex: $abc, @def). The
- ** VALUE can be in either SQL literal notation, or if not it will be
- ** understood to be a text string.
- */
- if( nArg==4 && strcmp(azArg[1],"set")==0 ){
- int rx;
- char *zSql;
- sqlite3_stmt *pStmt;
- const char *zKey = azArg[2];
- const char *zValue = azArg[3];
- bind_table_init(p);
- zSql = sqlite3_mprintf(
- "REPLACE INTO temp.sqlite_parameters(key,value)"
- "VALUES(%Q,%s);", zKey, zValue);
- shell_check_oom(zSql);
- pStmt = 0;
- rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- if( rx!=SQLITE_OK ){
- sqlite3_finalize(pStmt);
- pStmt = 0;
- zSql = sqlite3_mprintf(
- "REPLACE INTO temp.sqlite_parameters(key,value)"
- "VALUES(%Q,%Q);", zKey, zValue);
- shell_check_oom(zSql);
- rx = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- if( rx!=SQLITE_OK ){
- utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
- sqlite3_finalize(pStmt);
- pStmt = 0;
- rc = 1;
- }
- }
- sqlite3_step(pStmt);
- sqlite3_finalize(pStmt);
- }else
+
- /* .parameter unset NAME
- ** Remove the NAME binding from the parameter binding table, if it
- ** exists.
- */
- if( nArg==3 && strcmp(azArg[1],"unset")==0 ){
- char *zSql = sqlite3_mprintf(
- "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]);
- if( zSql==0 ) shell_out_of_memory();
- sqlite3_exec(p->db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
- }else
++struct param_row { char * value; int uses; int hits; };
++
++static int param_find_callback(void *pData, int nc, char **pV, char **pC){
++ assert(nc>=1);
++ assert(strcmp(pC[0],"value")==0);
++ struct param_row *pParam = (struct param_row *)pData;
++ assert(pParam->value==0); /* key values are supposedly unique. */
++ if( pParam->value!=0 ) sqlite3_free( pParam->value );
++ pParam->value = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */
++ if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
++ ++pParam->hits;
++ return 0;
++}
+
- { /* If no command name and arg count matches, show a syntax error */
- showHelp(p->out, "parameter");
- return 1;
++static void append_in_clause(sqlite3_str *pStr,
++ const char **azBeg, const char **azLim);
++static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
++ const char **azBeg, const char **azLim);
++static char *find_home_dir(int clearFlag);
+
-
- return rc;
++/* Create a home-relative pathname from ~ prefixed path.
++ * Return it, or 0 for any error.
++ * Caller must sqlite3_free() it.
++ */
++static char *home_based_path( const char *zPath ){
++ char *zHome = find_home_dir(0);
++ char *zErr = 0;
++ assert( zPath[0]=='~' );
++ if( zHome==0 ){
++ zErr = "Cannot find home directory.";
++ }else if( zPath[0]==0 || (zPath[1]!='/'
++#if defined(_WIN32) || defined(WIN32)
++ && zPath[1]!='\\'
+ #endif
++ ) ){
++ zErr = "Malformed pathname";
++ }else{
++ return sqlite3_mprintf("%s%s", zHome, zPath+1);
+ }
++ utf8_printf(STD_ERR, "Error: %s\n", zErr);
++ return 0;
+}
- /*****************
- * The .print, .progress and .prompt commands
- /* Parse the input line into tokens.
- */
- while( zLine[h] && nArg<ArraySize(azArg)-1 ){
- while( IsSpace(zLine[h]) ){ h++; }
- if( zLine[h]==0 ) break;
- if( zLine[h]=='\'' || zLine[h]=='"' ){
- int delim = zLine[h++];
- azArg[nArg++] = &zLine[h];
- while( zLine[h] && zLine[h]!=delim ){
- if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
- h++;
- }
- if( zLine[h]==delim ){
- zLine[h++] = 0;
++/* Transfer selected parameters between two parameter tables, for save/load.
++ * Argument bSaveNotLoad determines transfer direction and other actions.
++ * If it is true, the store DB will be created if not existent, and its
++ * table for keeping parameters will be created. Or failure is returned.
++ * If it is false, the store DB will be opened for read and its presumed
++ * table for keeping parameters will be read. Or failure is returned.
++ *
++ * Arguments azNames and nNames reference the ?NAMES? save/load arguments.
++ * If it is an empty list, all parameters will be saved or loaded.
++ * Otherwise, only the named parameters are transferred, if they exist.
++ * It is not an error to specify a name that cannot be transferred
++ * because it does not exist in the source table.
++ *
++ * Returns are SQLITE_OK for success, or other codes for failure.
+ */
- CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
++static int param_xfr_table(sqlite3 *db, const char *zStoreDbName,
++ int bSaveNotLoad, const char *azNames[], int nNames){
++ int rc = 0;
++ char *zSql = 0; /* to be sqlite3_free()'ed */
++ sqlite3_str *sbCopy = 0;
++ const char *zHere = PARAM_TABLE_SNAME;
++ const char *zThere = PARAM_STORE_SNAME;
++ const char *zTo = (bSaveNotLoad)? zThere : zHere;
++ const char *zFrom = (bSaveNotLoad)? zHere : zThere;
++ sqlite3 *dbStore = 0;
++ int openFlags = (bSaveNotLoad)
++ ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
++ : SQLITE_OPEN_READONLY;
++
++ /* Ensure store DB can be opened and/or created appropriately. */
++ rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0);
++ if( rc!=SQLITE_OK ){
++ utf8_printf(STD_ERR, "Error: Cannot %s parameter store DB %s\n",
++ bSaveNotLoad? "open/create" : "read", zStoreDbName);
++ return rc;
++ }
++ /* Ensure it has the parameter store table, or handle its absence. */
++ assert(dbStore!=0);
++ if( sqlite3_table_column_metadata
++ (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
++ if( !bSaveNotLoad ){
++ utf8_printf(STD_ERR, "Error: No parameters ever stored in DB %s\n",
++ zStoreDbName);
++ rc = 1;
++ }else{
++ /* The saved parameters table is not there yet; create it. */
++ const char *zCT =
++ "CREATE TABLE IF NOT EXISTS "PARAM_STORE_NAME"(\n"
++ " key TEXT PRIMARY KEY,\n"
++ " value,\n"
++ " uses INT\n"
++ ") WITHOUT ROWID;";
++ rc = sqlite3_exec(dbStore, zCT, 0, 0, 0);
++ if( rc!=SQLITE_OK ){
++ utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
+ }
- if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
- }else{
- azArg[nArg++] = &zLine[h];
- while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
- if( zLine[h] ) zLine[h++] = 0;
- resolve_backslashes(azArg[nArg-1]);
+ }
+ }
- azArg[nArg] = 0;
++ sqlite3_close(dbStore);
++ if( rc!=0 ) return rc;
+
- /* Process the input line.
- */
- if( nArg==0 ) return 0; /* no tokens, no error */
- n = strlen30(azArg[0]);
- c = azArg[0][0];
- clearTempFile(p);
++ zSql = sqlite3_mprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA);
++ shell_check_oom(zSql);
++ rc = sqlite3_exec(db, zSql, 0, 0, 0);
++ sqlite3_free(zSql);
++ if( rc!=SQLITE_OK ) return rc;
+
-#ifndef SQLITE_OMIT_AUTHORIZATION
- if( c=='a' && strncmp(azArg[0], "auth", n)==0 ){
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .auth ON|OFF\n");
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- if( booleanValue(azArg[1]) ){
- sqlite3_set_authorizer(p->db, shellAuth, p);
- }else if( p->bSafeModePersist ){
- sqlite3_set_authorizer(p->db, safeModeAuth, p);
- }else{
- sqlite3_set_authorizer(p->db, 0, 0);
- }
- }else
-#endif
++ sbCopy = sqlite3_str_new(db);
++ sqlite3_str_appendf
++ (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)"
++ "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom);
++ append_in_clause(sbCopy, azNames, azNames+nNames);
++ zSql = sqlite3_str_finish(sbCopy);
++ shell_check_oom(zSql);
++ rc = sqlite3_exec(db, zSql, 0, 0, 0);
++ sqlite3_free(zSql);
+
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
- if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){
- open_db(p, 0);
- failIfSafeMode(p, "cannot run .archive in safe mode");
- rc = arDotCommand(p, 0, azArg, nArg);
- }else
-#endif
++ sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0);
++ return rc;
++}
+
- if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0)
- || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0)
- ){
- const char *zDestFile = 0;
- const char *zDb = 0;
- sqlite3 *pDest;
- sqlite3_backup *pBackup;
- int j;
- int bAsync = 0;
- const char *zVfs = 0;
- failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
- for(j=1; j<nArg; j++){
- const char *z = azArg[j];
- if( z[0]=='-' ){
- if( z[1]=='-' ) z++;
- if( strcmp(z, "-append")==0 ){
- zVfs = "apndvfs";
- }else
- if( strcmp(z, "-async")==0 ){
- bAsync = 1;
- }else
- {
- utf8_printf(stderr, "unknown option: %s\n", azArg[j]);
- return 1;
- }
- }else if( zDestFile==0 ){
- zDestFile = azArg[j];
- }else if( zDb==0 ){
- zDb = zDestFile;
- zDestFile = azArg[j];
- }else{
- raw_printf(stderr, "Usage: .backup ?DB? ?OPTIONS? FILENAME\n");
- return 1;
- }
- }
- if( zDestFile==0 ){
- raw_printf(stderr, "missing FILENAME argument on .backup\n");
- return 1;
- }
- if( zDb==0 ) zDb = "main";
- rc = sqlite3_open_v2(zDestFile, &pDest,
- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
- close_db(pDest);
- return 1;
- }
- if( bAsync ){
- sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
- 0, 0, 0);
- }
- open_db(p, 0);
- pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
- if( pBackup==0 ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
- close_db(pDest);
- return 1;
- }
- while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
- sqlite3_backup_finish(pBackup);
- if( rc==SQLITE_DONE ){
- rc = 0;
- }else{
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
- rc = 1;
- }
- close_db(pDest);
- }else
++/* Default location of parameters store DB for .parameters save/load. */
++static const char *zDefaultParamStore = "~/sqlite_params.sdb";
+
- if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){
- if( nArg==2 ){
- bail_on_error = booleanValue(azArg[1]);
- }else{
- raw_printf(stderr, "Usage: .bail on|off\n");
- rc = 1;
- }
- }else
++/* Possibly generate a derived path from input spec, with defaulting
++ * and conversion of leading (or only) tilde as home directory.
++ * The above-set default is used for zSpec NULL, "" or "~".
++ * When return is 0, there is an error; what needs doing cannnot be done.
++ * If the return is exactly the input, it must not be sqlite3_free()'ed.
++ * If the return differs from the input, it must be sqlite3_free()'ed.
++ */
++static const char *params_store_path(const char *zSpec){
++ if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){
++ return home_based_path(zDefaultParamStore);
++ }else if ( zSpec[0]=='~' ){
++ return home_based_path(zSpec);
++ }
++ return zSpec;
++}
+
- if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){
- if( nArg==2 ){
- if( booleanValue(azArg[1]) ){
- setBinaryMode(p->out, 1);
- }else{
- setTextMode(p->out, 1);
- }
- }else{
- raw_printf(stderr, "Usage: .binary on|off\n");
- rc = 1;
- }
- }else
++/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */
++static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){
++ const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
++ if( zStore==0 ){
++ utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
++ return 1;
++ }else{
++ const char **pzFirst = (nArg>2)? azArg+2 : 0;
++ int nNames = (nArg>2)? nArg-2 : 0;
++ int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames);
++ if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
++ return rc;
++ }
++}
+
- /* The undocumented ".breakpoint" command causes a call to the no-op
- ** routine named test_breakpoint().
- */
- if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){
- test_breakpoint();
- }else
++/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
++static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){
++ const char *zStore = params_store_path((nArg>1)? azArg[1] : 0);
++ if( zStore==0 ){
++ utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
++ return 1;
++ }else{
++ const char **pzFirst = (nArg>2)? azArg+2 : 0;
++ int nNames = (nArg>2)? nArg-2 : 0;
++ int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames);
++ if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore);
++ return rc;
++ }
++}
+
- if( c=='c' && strcmp(azArg[0],"cd")==0 ){
- failIfSafeMode(p, "cannot run .cd in safe mode");
- if( nArg==2 ){
-#if defined(_WIN32) || defined(WIN32)
- wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
- rc = !SetCurrentDirectoryW(z);
- sqlite3_free(z);
-#else
- rc = chdir(azArg[1]);
-#endif
- if( rc ){
- utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]);
- rc = 1;
- }
++#ifndef SQLITE_NOHAVE_SYSTEM
++/*
++ * Edit one named parameter in the parameters table. If it does not
++ * yet exist, create it. If eval is true, the value is treated as a
++ * bare expression, otherwise it is a text value. The uses argument
++ * sets the 3rd column in the parameters table, and may also serve
++ * to partition the key namespace. (This is not done now.)
++ */
++static int edit_one_param(sqlite3 *db, char *name, int eval,
++ ParamTableUse uses, const char * zEditor){
++ struct param_row paramVU = {0,0,0};
++ int rc;
++ char * zVal = 0;
++ char * zSql = sqlite3_mprintf
++ ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name);
++ shell_check_oom(zSql);
++ sqlite3_exec(db, zSql, param_find_callback, ¶mVU, 0);
++ sqlite3_free(zSql);
++ assert(paramVU.hits<2);
++ if( paramVU.hits==1 && paramVU.uses==uses){
++ /* Editing an existing value of same kind. */
++ sqlite3_free(paramVU.value);
++ if( eval!=0 ){
++ zSql = sqlite3_mprintf
++ ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME
++ " WHERE key=%Q AND uses=%d", zEditor, name, uses);
++ zVal = db_text(db, zSql, 1);
++ sqlite3_free(zSql);
++ zSql = sqlite3_mprintf
++ ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE"
++ " key=%Q AND uses=%d", zVal, name, uses);
+ }else{
- raw_printf(stderr, "Usage: .cd DIRECTORY\n");
- rc = 1;
++ zSql = sqlite3_mprintf
++ ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE"
++ " key=%Q AND uses=%d", zEditor, name, uses);
+ }
- }else
-
- if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){
- if( nArg==2 ){
- setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
++ }else{
++ /* Editing a new value of same kind. */
++ assert(paramVU.value==0 || paramVU.uses!=uses);
++ if( eval!=0 ){
++ zSql = sqlite3_mprintf
++ ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
++ zVal = db_text(db, zSql, 1);
++ sqlite3_free(zSql);
++ zSql = sqlite3_mprintf
++ ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
++ name, zVal, uses);
+ }else{
- raw_printf(stderr, "Usage: .changes on|off\n");
- rc = 1;
++ zSql = sqlite3_mprintf
++ ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ " VALUES (%Q,edit('-- %q%s', %Q),%d)",
++ name, name, "\n", zEditor, uses);
+ }
- }else
++ }
++ shell_check_oom(zSql);
++ if( eval!=0 ){
++ }
++ rc = sqlite3_exec(db, zSql, 0, 0, 0);
++ sqlite3_free(zSql);
++ sqlite3_free(zVal);
++ return rc!=SQLITE_OK;
++}
++#endif
+
- /* Cancel output redirection, if it is currently set (by .testcase)
- ** Then read the content of the testcase-out.txt file and compare against
- ** azArg[1]. If there are differences, report an error and exit.
- */
- if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){
- char *zRes = 0;
- output_reset(p);
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .check GLOB-PATTERN\n");
- rc = 2;
- }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
- raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n");
- rc = 2;
- }else if( testcase_glob(azArg[1],zRes)==0 ){
- utf8_printf(stderr,
- "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n",
- p->zTestcase, azArg[1], zRes);
- rc = 1;
- }else{
- utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
- p->nCheck++;
- }
- sqlite3_free(zRes);
- }else
++/* Space-join values in an argument list. *valLim is not included. */
++char *values_join( char **valBeg, char **valLim ){
++ char *z = 0;
++ const char *zSep = 0;
++ while( valBeg < valLim ){
++ z = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg);
++ zSep = " ";
++ ++valBeg;
++ }
++ return z;
++}
++
++/* Get a named parameter value in form of stepped prepared statement,
++ * ready to have its value taken from the 0th column. If the name
++ * cannot be found for the given ParamTableUse, 0 is returned.
++ * The caller is responsible for calling sqlite3_finalize(pStmt),
++ * where pStmt is the return from this function.
++ */
++static sqlite3_stmt *get_param_value(sqlite3 *db, char *name,
++ ParamTableUse ptu){
++ sqlite3_stmt *rv = 0;
++ int rc;
++ char *zSql = sqlite3_mprintf
++ ( "SELECT value FROM "PARAM_TABLE_SNAME
++ " WHERE key=%Q AND uses=%d", name, ptu );
++ shell_check_oom(zSql);
++ rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0);
++ sqlite3_free(zSql);
++ if( SQLITE_OK==rc ){
++ if( SQLITE_ROW==sqlite3_step(rv) ) return rv;
++ sqlite3_finalize(rv);
++ }
++ return 0;
++}
++
++static struct ParamSetOpts {
++ const char cCast;
++ const char *zTypename;
++ int evalKind;
++} param_set_opts[] = {
++ /* { 'q', 0, 2 }, */
++ /* { 'x', 0, 1 }, */
++ { 'i', "INT", 1 },
++ { 'r', "REAL", 1 },
++ { 'b', "BLOB", 1 },
++ { 't', "TEXT", 0 },
++ { 'n', "NUMERIC", 1 }
++};
+
- if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){
- failIfSafeMode(p, "cannot run .clone in safe mode");
- if( nArg==2 ){
- tryToClone(p, azArg[1]);
- }else{
- raw_printf(stderr, "Usage: .clone FILENAME\n");
- rc = 1;
- }
- }else
++/* Return an option character if it is single and prefixed by - or --,
++ * else return 0.
++ */
++static char option_char(char *zArg){
++ if( zArg[0]=='-' ){
++ ++zArg;
++ if( zArg[0]=='-' ) ++zArg;
++ if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0];
++ }
++ return 0;
++}
+
- if( c=='c' && strncmp(azArg[0], "connection", n)==0 ){
- if( nArg==1 ){
- /* List available connections */
- int i;
- for(i=0; i<ArraySize(p->aAuxDb); i++){
- const char *zFile = p->aAuxDb[i].zDbFilename;
- if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
- zFile = "(not open)";
- }else if( zFile==0 ){
- zFile = "(memory)";
- }else if( zFile[0]==0 ){
- zFile = "(temporary-file)";
- }
- if( p->pAuxDb == &p->aAuxDb[i] ){
- utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
- }else if( p->aAuxDb[i].db!=0 ){
- utf8_printf(stdout, " %d: %s\n", i, zFile);
- }
- }
- }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
- int i = azArg[1][0] - '0';
- if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
- p->pAuxDb->db = p->db;
- p->pAuxDb = &p->aAuxDb[i];
- globalDb = p->db = p->pAuxDb->db;
- p->pAuxDb->db = 0;
- }
- }else if( nArg==3 && strcmp(azArg[1], "close")==0
- && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
- int i = azArg[2][0] - '0';
- if( i<0 || i>=ArraySize(p->aAuxDb) ){
- /* No-op */
- }else if( p->pAuxDb == &p->aAuxDb[i] ){
- raw_printf(stderr, "cannot close the active database connection\n");
- rc = 1;
- }else if( p->aAuxDb[i].db ){
- session_close_all(p, i);
- close_db(p->aAuxDb[i].db);
- p->aAuxDb[i].db = 0;
++/* The set subcommand (per help text)
++ */
++static int param_set(sqlite3 *db, char cCast,
++ char *name, char **valBeg, char **valLim,
++ ParamTableUse ptu){
++ char *zSql = 0;
++ int rc = SQLITE_OK, retries = 0, needsEval = 1;
++ char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
++ sqlite3_stmt *pStmtSet = 0;
++ const char *zCastTo = 0;
++ char *zValue = (zValGlom==0)? *valBeg : zValGlom;
++ if( cCast ){
++ struct ParamSetOpts *pSO = param_set_opts;
++ for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){
++ if( cCast==pSO->cCast ){
++ zCastTo = pSO->zTypename;
++ needsEval = pSO->evalKind > 0;
++ break;
+ }
- }else{
- raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
- rc = 1;
+ }
- }else
-
- if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){
- char **azName = 0;
- int nName = 0;
- sqlite3_stmt *pStmt;
- int i;
- open_db(p, 0);
- rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
- if( rc ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
- rc = 1;
++ }
++ if( needsEval ){
++ if( zCastTo!=0 ){
++ zSql = sqlite3_mprintf
++ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu );
+ }else{
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
- const char *zSchema = (const char *)sqlite3_column_text(pStmt,1);
- const char *zFile = (const char*)sqlite3_column_text(pStmt,2);
- if( zSchema==0 || zFile==0 ) continue;
- azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*));
- shell_check_oom(azName);
- azName[nName*2] = strdup(zSchema);
- azName[nName*2+1] = strdup(zFile);
- nName++;
- }
++ zSql = sqlite3_mprintf
++ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ "VALUES(%Q,(%s),%d);", name, zValue, ptu );
+ }
- sqlite3_finalize(pStmt);
- for(i=0; i<nName; i++){
- int eTxn = sqlite3_txn_state(p->db, azName[i*2]);
- int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]);
- const char *z = azName[i*2+1];
- utf8_printf(p->out, "%s: %s %s%s\n",
- azName[i*2],
- z && z[0] ? z : "\"\"",
- bRdonly ? "r/o" : "r/w",
- eTxn==SQLITE_TXN_NONE ? "" :
- eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
- free(azName[i*2]);
- free(azName[i*2+1]);
- }
- sqlite3_free(azName);
- }else
++ shell_check_oom(zSql);
++ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
++ sqlite3_free(zSql);
++ }
++ if( !needsEval || rc!=SQLITE_OK ){
++ /* Reach here when value either requested to be cast to text, or must be. */
++ sqlite3_finalize(pStmtSet);
++ pStmtSet = 0;
++ zSql = sqlite3_mprintf
++ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ "VALUES(%Q,%Q,%d);", name, zValue, ptu );
++ shell_check_oom(zSql);
++ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0);
++ assert(rc==SQLITE_OK);
++ sqlite3_free(zSql);
++ }
++ sqlite3_step(pStmtSet);
++ sqlite3_finalize(pStmtSet);
++ sqlite3_free(zValGlom);
++ return rc;
++}
+
- if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){
- static const struct DbConfigChoices {
- const char *zName;
- int op;
- } aDbConfig[] = {
- { "defensive", SQLITE_DBCONFIG_DEFENSIVE },
- { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL },
- { "dqs_dml", SQLITE_DBCONFIG_DQS_DML },
- { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY },
- { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG },
- { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER },
- { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW },
- { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER },
- { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE },
- { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT },
- { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION },
- { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE },
- { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE },
- { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP },
- { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA },
- { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA },
- };
- int ii, v;
- open_db(p, 0);
- for(ii=0; ii<ArraySize(aDbConfig); ii++){
- if( nArg>1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
- if( nArg>=3 ){
- sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
- }
- sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v);
- utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off");
- if( nArg>1 ) break;
- }
- if( nArg>1 && ii==ArraySize(aDbConfig) ){
- utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]);
- utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n");
++/* list or ls subcommand for .parameter dot-command */
++static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort,
++ char **pzArgs, int nArg){
++ sqlite3_stmt *pStmt = 0;
++ sqlite3_str *sbList = sqlite3_str_new(p->db);
++ int len = 0, rc;
++ char *zFromWhere = 0;
++ char *zSql = 0;
++ sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME
++ " WHERE (?1=3 OR uses=?1) AND ");
++ append_glob_terms(sbList, "key",
++ (const char **)pzArgs, (const char **)pzArgs+nArg);
++ zFromWhere = sqlite3_str_finish(sbList);
++ shell_check_oom(zFromWhere);
++ zSql = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere);
++ shell_check_oom(zSql);
++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
++ if( rc==SQLITE_OK ){
++ sqlite3_bind_int(pStmt, 1, ptu);
++ if( sqlite3_step(pStmt)==SQLITE_ROW ){
++ len = sqlite3_column_int(pStmt, 0);
++ if( len>40 ) len = 40;
++ if( len<4 ) len = 4;
+ }
- }else
-
- if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){
- rc = shell_dbinfo_command(p, nArg, azArg);
- }else
-
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
- if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){
- open_db(p, 0);
- rc = recoverDatabaseCmd(p, nArg, azArg);
- }else
-#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
-
- if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){
- char *zLike = 0;
- char *zSchema = "main";
- char *zSql;
- int i;
- int savedShowHeader = p->showHeader;
- int savedShellFlags = p->shellFlgs;
- ShellClearFlag(p,
- SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo
- |SHFLG_DumpDataOnly|SHFLG_DumpNoSys);
- for(i=1; i<nArg; i++){
- if( azArg[i][0]=='-' ){
- const char *z = azArg[i]+1;
- if( z[0]=='-' ) z++;
- if( strcmp(z,"preserve-rowids")==0 ){
-#ifdef SQLITE_OMIT_VIRTUALTABLE
- raw_printf(stderr, "The --preserve-rowids option is not compatible"
- " with SQLITE_OMIT_VIRTUALTABLE\n");
- rc = 1;
- sqlite3_free(zLike);
- goto meta_command_exit;
-#else
- ShellSetFlag(p, SHFLG_PreserveRowid);
-#endif
- }else
- if( strcmp(z,"newlines")==0 ){
- ShellSetFlag(p, SHFLG_Newlines);
- }else
- if( strcmp(z,"data-only")==0 ){
- ShellSetFlag(p, SHFLG_DumpDataOnly);
- }else
- if( strcmp(z,"nosys")==0 ){
- ShellSetFlag(p, SHFLG_DumpNoSys);
- }else if( strcmp(z,"schema")==0 && ++i<nArg ){
- zSchema = azArg[i];
- }else
- {
- raw_printf(stderr, "Unknown option \"%s\" on \".dump\"\n", azArg[i]);
- rc = 1;
- sqlite3_free(zLike);
- goto meta_command_exit;
- }
- }else{
- /* azArg[i] contains a LIKE pattern. This ".dump" request should
- ** only dump data for tables for which either the table name matches
- ** the LIKE pattern, or the table appears to be a shadow table of
- ** a virtual table for which the name matches the LIKE pattern.
- */
- char *zExpr = sqlite3_mprintf(
- "name LIKE %Q ESCAPE '\\' OR EXISTS ("
- " SELECT 1 FROM %w.sqlite_schema WHERE "
- " name LIKE %Q ESCAPE '\\' AND"
- " sql LIKE 'CREATE VIRTUAL TABLE%%' AND"
- " substr(o.name, 1, length(name)+1) == (name||'_')"
- ")", azArg[i], zSchema, azArg[i]
- );
-
- if( zLike ){
- zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
- }else{
- zLike = zExpr;
++ }
++ sqlite3_finalize(pStmt);
++ pStmt = 0;
++ if( len ){
++ sqlite3_free(zSql);
++ if( !bShort ){
++ int nBindings = 0, nScripts = 0;
++ zSql = sqlite3_mprintf("SELECT key, uses, iif(uses, value, quote(value))"
++ " %z ORDER BY uses, key", zFromWhere);
++ shell_check_oom(zSql);
++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
++ sqlite3_bind_int(pStmt, 1, ptu);
++ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
++ ParamTableUse ptux = sqlite3_column_int(pStmt,1);
++ switch( ptux ){
++ case PTU_Binding:
++ if( nBindings++ == 0 ){
++ utf8_printf(p->out, "Binding Values:\n%-*s %s\n",
++ len, "name", "value");
++ }
++ utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
++ sqlite3_column_text(pStmt,2));
++ break;
++ case PTU_Script:
++ if( nScripts++ == 0 ){
++ utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value");
++ }
++ utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0),
++ sqlite3_column_text(pStmt,2));
++ break;
++ default: break; /* Ignore */
+ }
+ }
- }
-
- open_db(p, 0);
-
- if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
- /* When playing back a "dump", the content might appear in an order
- ** which causes immediate foreign key constraints to be violated.
- ** So disable foreign-key constraint enforcement to prevent problems. */
- raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
- raw_printf(p->out, "BEGIN TRANSACTION;\n");
- }
- p->writableSchema = 0;
- p->showHeader = 0;
- /* Set writable_schema=ON since doing so forces SQLite to initialize
- ** as much of the schema as it can even if the sqlite_schema table is
- ** corrupt. */
- sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
- p->nErr = 0;
- if( zLike==0 ) zLike = sqlite3_mprintf("true");
- zSql = sqlite3_mprintf(
- "SELECT name, type, sql FROM %w.sqlite_schema AS o "
- "WHERE (%s) AND type=='table'"
- " AND sql NOT NULL"
- " ORDER BY tbl_name='sqlite_sequence', rowid",
- zSchema, zLike
- );
- run_schema_dump_query(p,zSql);
- sqlite3_free(zSql);
- if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
- zSql = sqlite3_mprintf(
- "SELECT sql FROM %w.sqlite_schema AS o "
- "WHERE (%s) AND sql NOT NULL"
- " AND type IN ('index','trigger','view')",
- zSchema, zLike
- );
- run_table_dump_query(p, zSql);
- sqlite3_free(zSql);
- }
- sqlite3_free(zLike);
- if( p->writableSchema ){
- raw_printf(p->out, "PRAGMA writable_schema=OFF;\n");
- p->writableSchema = 0;
- }
- sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
- sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
- if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){
- raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
- }
- p->showHeader = savedShowHeader;
- p->shellFlgs = savedShellFlags;
- }else
-
- if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){
- if( nArg==2 ){
- setOrClearFlag(p, SHFLG_Echo, azArg[1]);
+ }else{
- raw_printf(stderr, "Usage: .echo on|off\n");
- rc = 1;
- }
- }else
-
- if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){
- if( nArg==2 ){
- p->autoEQPtest = 0;
- if( p->autoEQPtrace ){
- if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
- p->autoEQPtrace = 0;
- }
- if( strcmp(azArg[1],"full")==0 ){
- p->autoEQP = AUTOEQP_full;
- }else if( strcmp(azArg[1],"trigger")==0 ){
- p->autoEQP = AUTOEQP_trigger;
-#ifdef SQLITE_DEBUG
- }else if( strcmp(azArg[1],"test")==0 ){
- p->autoEQP = AUTOEQP_on;
- p->autoEQPtest = 1;
- }else if( strcmp(azArg[1],"trace")==0 ){
- p->autoEQP = AUTOEQP_full;
- p->autoEQPtrace = 1;
- open_db(p, 0);
- sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
- sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0);
-#endif
- }else{
- p->autoEQP = (u8)booleanValue(azArg[1]);
++ int nc = 0, ncw = 78/(len+2);
++ zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere);
++ shell_check_oom(zSql);
++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
++ sqlite3_bind_int(pStmt, 1, ptu);
++ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
++ utf8_printf(p->out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""),
++ len, sqlite3_column_text(pStmt,0));
+ }
- }else{
- raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n");
- rc = 1;
++ if( nc>0 ) utf8_printf(p->out, "\n");
+ }
- }else
-
- if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
- if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
- rc = 2;
- }else
++ sqlite3_finalize(pStmt);
++ }else{
++ sqlite3_free(zFromWhere);
++ }
++ sqlite3_free(zSql);
++}
+
- /* The ".explain" command is automatic now. It is largely pointless. It
- ** retained purely for backwards compatibility */
- if( c=='e' && strncmp(azArg[0], "explain", n)==0 ){
- int val = 1;
- if( nArg>=2 ){
- if( strcmp(azArg[1],"auto")==0 ){
- val = 99;
- }else{
- val = booleanValue(azArg[1]);
- }
++/* Append an OR'ed series of GLOB terms comparing a given column
++ * name to a series of patterns. Result is an appended expression.
++ * For an empty pattern series, expression is true for non-NULL.
++ */
++static void append_glob_terms(sqlite3_str *pStr, const char *zColName,
++ const char **azBeg, const char **azLim){
++ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName);
++ else{
++ char *zSep = "(";
++ while( azBeg<azLim ){
++ sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
++ zSep = " OR ";
++ ++azBeg;
+ }
- if( val==1 && p->mode!=MODE_Explain ){
- p->normalMode = p->mode;
- p->mode = MODE_Explain;
- p->autoExplain = 0;
- }else if( val==0 ){
- if( p->mode==MODE_Explain ) p->mode = p->normalMode;
- p->autoExplain = 0;
- }else if( val==99 ){
- if( p->mode==MODE_Explain ) p->mode = p->normalMode;
- p->autoExplain = 1;
++ sqlite3_str_appendf(pStr, ")");
++ }
++}
++
++/* Append either an IN clause or an always true test to some SQL.
++ *
++ * An empty IN list is the same as always true (for non-NULL LHS)
++ * for this clause, which assumes a trailing LHS operand and space.
++ * If that is not the right result, guard the call against it.
++ * This is used for ".parameter dostuff ?NAMES?" options,
++ * where a missing list means all the qualifying entries.
++ *
++ * The empty list may be signified by azBeg and azLim both 0.
++ */
++static void append_in_clause(sqlite3_str *pStr,
++ const char **azBeg, const char **azLim){
++ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
++ else{
++ char cSep = '(';
++ sqlite3_str_appendf(pStr, "IN");
++ while( azBeg<azLim ){
++ sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
++ cSep = ',';
++ ++azBeg;
+ }
- }else
++ sqlite3_str_appendf(pStr, ")");
++ }
++}
+
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( c=='e' && strncmp(azArg[0], "expert", n)==0 ){
- if( p->bSafeMode ){
- raw_printf(stderr,
- "Cannot run experimental commands such as \"%s\" in safe mode\n",
- azArg[0]);
- rc = 1;
- }else{
- open_db(p, 0);
- expertDotCommand(p, azArg, nArg);
- }
- }else
++/*****************
++ * The .parameter command
++ */
++COLLECT_HELP_TEXT[
++ ".parameter CMD ... Manage SQL parameter bindings and scripts table",
++ " clear ?NAMES? Erase all or only given named parameters",
++#ifndef SQLITE_NOHAVE_SYSTEM
++ " edit ?OPT? NAME ... Use edit() to create or alter parameter NAME",
++ " OPT may be -t or -e to use edited value as text or evaluate it first.",
+ #endif
++ " init Initialize TEMP table for bindings and scripts",
++ " list ?PATTERNS? List parameters table binding and script values",
++ " Alternatively, to list just some or all names: ls ?PATTERNS?",
++ " load ?FILE? ?NAMES? Load some or all named parameters from FILE",
++ " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
++ " save ?FILE? ?NAMES? Save some or all named parameters into FILE",
++ " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb",
++ " set ?TOPT? NAME VALUE Give SQL parameter NAME a value of VALUE",
++ " NAME must begin with one of $,:,@,? for bindings, or with a letter",
++ " to be executable; the value is following argument list, space-joined.",
++ " Option TOPT may be one of {-b -i -n -r -t} to cast effective value",
++ " to BLOB, INT, NUMERIC, REAL or TEXT respectively.",
++ " unset ?NAMES? Remove named parameter(s) from parameters table",
++];
++DISPATCHABLE_COMMAND( parameter 2 2 0 ){
++ int rc = 0;
++ open_db(p,0);
+
- if( c=='f' && strncmp(azArg[0], "filectrl", n)==0 ){
- static const struct {
- const char *zCtrlName; /* Name of a test-control option */
- int ctrlCode; /* Integer code for that option */
- const char *zUsage; /* Usage notes */
- } aCtrl[] = {
- { "chunk_size", SQLITE_FCNTL_CHUNK_SIZE, "SIZE" },
- { "data_version", SQLITE_FCNTL_DATA_VERSION, "" },
- { "has_moved", SQLITE_FCNTL_HAS_MOVED, "" },
- { "lock_timeout", SQLITE_FCNTL_LOCK_TIMEOUT, "MILLISEC" },
- { "persist_wal", SQLITE_FCNTL_PERSIST_WAL, "[BOOLEAN]" },
- /* { "pragma", SQLITE_FCNTL_PRAGMA, "NAME ARG" },*/
- { "psow", SQLITE_FCNTL_POWERSAFE_OVERWRITE, "[BOOLEAN]" },
- { "reserve_bytes", SQLITE_FCNTL_RESERVE_BYTES, "[N]" },
- { "size_limit", SQLITE_FCNTL_SIZE_LIMIT, "[LIMIT]" },
- { "tempfilename", SQLITE_FCNTL_TEMPFILENAME, "" },
- /* { "win32_av_retry", SQLITE_FCNTL_WIN32_AV_RETRY, "COUNT DELAY" },*/
- };
- int filectrl = -1;
- int iCtrl = -1;
- sqlite3_int64 iRes = 0; /* Integer result to display if rc2==1 */
- int isOk = 0; /* 0: usage 1: %lld 2: no-result */
- int n2, i;
- const char *zCmd = 0;
- const char *zSchema = 0;
-
- open_db(p, 0);
- zCmd = nArg>=2 ? azArg[1] : "help";
-
- if( zCmd[0]=='-'
- && (strcmp(zCmd,"--schema")==0 || strcmp(zCmd,"-schema")==0)
- && nArg>=4
- ){
- zSchema = azArg[2];
- for(i=3; i<nArg; i++) azArg[i-2] = azArg[i];
- nArg -= 2;
- zCmd = azArg[1];
++ /* .parameter clear and .parameter unset ?NAMES?
++ ** Delete some or all parameters from the TEMP table that holds them.
++ ** Without any arguments, clear deletes them all and unset does nothing.
++ */
++ if( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){
++ if( param_table_exists(p->db) && (nArg>2 || azArg[1][0]=='c') ){
++ sqlite3_str *sbZap = sqlite3_str_new(p->db);
++ char *zSql;
++ sqlite3_str_appendf
++ (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
++ append_in_clause(sbZap,
++ (const char **)&azArg[2], (const char **)&azArg[nArg]);
++ zSql = sqlite3_str_finish(sbZap);
++ shell_check_oom(zSql);
++ sqlite3_exec(p->db, zSql, 0, 0, 0);
++ sqlite3_free(zSql);
+ }
-
- /* The argument can optionally begin with "-" or "--" */
- if( zCmd[0]=='-' && zCmd[1] ){
- zCmd++;
- if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
++ }else
++#ifndef SQLITE_NOHAVE_SYSTEM
++ /* .parameter edit ?NAMES?
++ ** Edit the named parameters. Any that do not exist are created.
++ ** New ones get a uses tag auto-selected by their leading char.
++ */
++ if( strcmp(azArg[1],"edit")==0 ){
++ int ia = 2;
++ int eval = 0;
++ if( !INSOURCE_IS_INTERACTIVE(p->pInSource) ){
++ utf8_printf(STD_ERR, "Error: "
++ ".parameter edit can only be used interactively.\n");
++ return 1;
+ }
-
- /* --help lists all file-controls */
- if( strcmp(zCmd,"help")==0 ){
- utf8_printf(p->out, "Available file-controls:\n");
- for(i=0; i<ArraySize(aCtrl); i++){
- utf8_printf(p->out, " .filectrl %s %s\n",
- aCtrl[i].zCtrlName, aCtrl[i].zUsage);
++ param_table_init(p);
++ if( p->zEditor==0 ){
++ const char *zE = getenv("VISUAL");
++ if( zE!=0 ) p->zEditor = sqlite3_mprintf("%s", zE);
++ }
++ if( nArg>=3 && azArg[2][0]=='-' ){
++ char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1;
++ if( strncmp(zArg,"editor=",7)==0 ){
++ sqlite3_free(p->zEditor);
++ /* Accept an initial -editor=? option. */
++ p->zEditor = sqlite3_mprintf("%s", zArg+7);
++ ++ia;
+ }
- rc = 1;
- goto meta_command_exit;
+ }
-
- /* convert filectrl text option to value. allow any unique prefix
- ** of the option name, or a numerical value. */
- n2 = strlen30(zCmd);
- for(i=0; i<ArraySize(aCtrl); i++){
- if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
- if( filectrl<0 ){
- filectrl = aCtrl[i].ctrlCode;
- iCtrl = i;
- }else{
- utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
- "Use \".filectrl --help\" for help\n", zCmd);
- rc = 1;
- goto meta_command_exit;
- }
- }
++ if( p->zEditor==0 ){
++ utf8_printf(STD_ERR,
++ "Either set env-var VISUAL to name an"
++ " editor and restart, or rerun\n "
++ ".parameter edit with an initial "
++ "edit option, --editor=EDITOR_COMMAND .\n");
++ return 1;
+ }
- if( filectrl<0 ){
- utf8_printf(stderr,"Error: unknown file-control: %s\n"
- "Use \".filectrl --help\" for help\n", zCmd);
- }else{
- switch(filectrl){
- case SQLITE_FCNTL_SIZE_LIMIT: {
- if( nArg!=2 && nArg!=3 ) break;
- iRes = nArg==3 ? integerValue(azArg[2]) : -1;
- sqlite3_file_control(p->db, zSchema, SQLITE_FCNTL_SIZE_LIMIT, &iRes);
- isOk = 1;
- break;
- }
- case SQLITE_FCNTL_LOCK_TIMEOUT:
- case SQLITE_FCNTL_CHUNK_SIZE: {
- int x;
- if( nArg!=3 ) break;
- x = (int)integerValue(azArg[2]);
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- isOk = 2;
- break;
- }
- case SQLITE_FCNTL_PERSIST_WAL:
- case SQLITE_FCNTL_POWERSAFE_OVERWRITE: {
- int x;
- if( nArg!=2 && nArg!=3 ) break;
- x = nArg==3 ? booleanValue(azArg[2]) : -1;
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- iRes = x;
- isOk = 1;
- break;
- }
- case SQLITE_FCNTL_DATA_VERSION:
- case SQLITE_FCNTL_HAS_MOVED: {
- int x;
- if( nArg!=2 ) break;
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- iRes = x;
- isOk = 1;
- break;
- }
- case SQLITE_FCNTL_TEMPFILENAME: {
- char *z = 0;
- if( nArg!=2 ) break;
- sqlite3_file_control(p->db, zSchema, filectrl, &z);
- if( z ){
- utf8_printf(p->out, "%s\n", z);
- sqlite3_free(z);
- }
- isOk = 2;
- break;
- }
- case SQLITE_FCNTL_RESERVE_BYTES: {
- int x;
- if( nArg>=3 ){
- x = atoi(azArg[2]);
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- }
- x = -1;
- sqlite3_file_control(p->db, zSchema, filectrl, &x);
- utf8_printf(p->out,"%d\n", x);
- isOk = 2;
- break;
++ /* Future: Allow an option whereby new value can be evaluated
++ * the way that .parameter set ... does.
++ */
++ while( ia < nArg ){
++ ParamTableUse ptu;
++ char cf = (azArg[ia][0]=='-')? azArg[ia][1] : 0;
++ if( cf!=0 && azArg[ia][2]==0 ){
++ ++ia;
++ switch( cf ){
++ case 'e': eval = 1; continue;
++ case 't': eval = 0; continue;
++ default:
++ utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n",
++ azArg[--ia]);
++ return 1;
+ }
+ }
- }
- if( isOk==0 && iCtrl>=0 ){
- utf8_printf(p->out, "Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
- rc = 1;
- }else if( isOk==1 ){
- char zBuf[100];
- sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
- raw_printf(p->out, "%s\n", zBuf);
++ ptu = classify_param_name(azArg[ia]);
++ if( ptu==PTU_Nil ){
++ utf8_printf(STD_ERR, "Error: %s cannot be a binding or executable"
++ " parameter name.\n", azArg[ia]);
++ return 1;
++ }
++ rc = edit_one_param(p->db, azArg[ia], eval, ptu, p->zEditor);
++ ++ia;
++ if( rc!=0 ) return rc;
+ }
+ }else
++#endif
+
- if( c=='f' && strncmp(azArg[0], "fullschema", n)==0 ){
- ShellState data;
- int doStats = 0;
- memcpy(&data, p, sizeof(data));
- data.showHeader = 0;
- data.cMode = data.mode = MODE_Semi;
- if( nArg==2 && optionMatch(azArg[1], "indent") ){
- data.cMode = data.mode = MODE_Pretty;
- nArg = 1;
- }
- if( nArg!=1 ){
- raw_printf(stderr, "Usage: .fullschema ?--indent?\n");
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- rc = sqlite3_exec(p->db,
- "SELECT sql FROM"
- " (SELECT sql sql, type type, tbl_name tbl_name, name name, rowid x"
- " FROM sqlite_schema UNION ALL"
- " SELECT sql, type, tbl_name, name, rowid FROM sqlite_temp_schema) "
- "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' "
- "ORDER BY x",
- callback, &data, 0
- );
- if( rc==SQLITE_OK ){
- sqlite3_stmt *pStmt;
- rc = sqlite3_prepare_v2(p->db,
- "SELECT rowid FROM sqlite_schema"
- " WHERE name GLOB 'sqlite_stat[134]'",
- -1, &pStmt, 0);
- doStats = sqlite3_step(pStmt)==SQLITE_ROW;
- sqlite3_finalize(pStmt);
- }
- if( doStats==0 ){
- raw_printf(p->out, "/* No STAT tables available */\n");
- }else{
- raw_printf(p->out, "ANALYZE sqlite_schema;\n");
- data.cMode = data.mode = MODE_Insert;
- data.zDestTable = "sqlite_stat1";
- shell_exec(&data, "SELECT * FROM sqlite_stat1", 0);
- data.zDestTable = "sqlite_stat4";
- shell_exec(&data, "SELECT * FROM sqlite_stat4", 0);
- raw_printf(p->out, "ANALYZE sqlite_schema;\n");
- }
++ /* .parameter init
++ ** Make sure the TEMP table used to hold bind parameters exists.
++ ** Create it if necessary.
++ */
++ if( nArg==2 && strcmp(azArg[1],"init")==0 ){
++ param_table_init(p);
+ }else
+
- if( c=='h' && strncmp(azArg[0], "headers", n)==0 ){
- if( nArg==2 ){
- p->showHeader = booleanValue(azArg[1]);
- p->shellFlgs |= SHFLG_HeaderSet;
- }else{
- raw_printf(stderr, "Usage: .headers on|off\n");
- rc = 1;
- }
++ /* .parameter list|ls
++ ** List all or selected bind parameters.
++ ** list displays names, values and uses.
++ ** ls displays just the names.
++ */
++ if( nArg>=2 && ((strcmp(azArg[1],"list")==0)
++ || (strcmp(azArg[1],"ls")==0)) ){
++ list_params(p, PTU_Nil, azArg[1][1]=='s', azArg+2, nArg-2);
+ }else
+
- if( c=='h' && strncmp(azArg[0], "help", n)==0 ){
- if( nArg>=2 ){
- if( nArg==3
- && strcmp(azArg[1], zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){
- /* Show the undocumented command help */
- n = showHelp(p->out, zHelpAll);
- }else{
- /* Show such help as the pattern selects */
- n = showHelp(p->out, azArg[1]);
- }
- if( n==0 ){
- utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
- }
- }else{
- /* Show one-line summaries */
- showHelp(p->out, 0);
- }
++ /* .parameter load
++ ** Load all or named parameters from specified or default (DB) file.
++ */
++ if( strcmp(azArg[1],"load")==0 ){
++ param_table_init(p);
++ rc = parameters_load(p->db, (const char **)azArg+1, nArg-1);
+ }else
+
- if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
- char *zTable = 0; /* Insert data into this table */
- char *zSchema = "main"; /* within this schema */
- char *zFile = 0; /* Name of file to extra content from */
- sqlite3_stmt *pStmt = NULL; /* A statement */
- int nCol; /* Number of columns in the table */
- int nByte; /* Number of bytes in an SQL string */
- int i, j; /* Loop counters */
- int needCommit; /* True to COMMIT or ROLLBACK at end */
- int nSep; /* Number of bytes in p->colSeparator[] */
- char *zSql; /* An SQL statement */
- ImportCtx sCtx; /* Reader context */
- char *(SQLITE_CDECL *xRead)(ImportCtx*); /* Func to read one value */
- int eVerbose = 0; /* Larger for more console output */
- int nSkip = 0; /* Initial lines to skip */
- int useOutputMode = 1; /* Use output mode to determine separators */
-
- failIfSafeMode(p, "cannot run .import in safe mode");
- memset(&sCtx, 0, sizeof(sCtx));
- sCtx.z = sqlite3_malloc64(120);
- if( sCtx.z==0 ){
- import_cleanup(&sCtx);
- shell_out_of_memory();
- }
- if( p->mode==MODE_Ascii ){
- xRead = ascii_read_one_field;
- }else{
- xRead = csv_read_one_field;
- }
- for(i=1; i<nArg; i++){
- char *z = azArg[i];
- if( z[0]=='-' && z[1]=='-' ) z++;
- if( z[0]!='-' ){
- if( zFile==0 ){
- zFile = z;
- }else if( zTable==0 ){
- zTable = z;
- }else{
- utf8_printf(p->out, "ERROR: extra argument: \"%s\". Usage:\n", z);
- showHelp(p->out, "import");
- rc = 1;
- goto meta_command_exit;
- }
- }else if( strcmp(z,"-v")==0 ){
- eVerbose++;
- }else if( strcmp(z,"-schema")==0 && i<nArg-1 ){
- zSchema = azArg[++i];
- }else if( strcmp(z,"-skip")==0 && i<nArg-1 ){
- nSkip = integerValue(azArg[++i]);
- }else if( strcmp(z,"-ascii")==0 ){
- sCtx.cColSep = SEP_Unit[0];
- sCtx.cRowSep = SEP_Record[0];
- xRead = ascii_read_one_field;
- useOutputMode = 0;
- }else if( strcmp(z,"-csv")==0 ){
- sCtx.cColSep = ',';
- sCtx.cRowSep = '\n';
- xRead = csv_read_one_field;
- useOutputMode = 0;
- }else{
- utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n", z);
- showHelp(p->out, "import");
- rc = 1;
- goto meta_command_exit;
- }
- }
- if( zTable==0 ){
- utf8_printf(p->out, "ERROR: missing %s argument. Usage:\n",
- zFile==0 ? "FILE" : "TABLE");
- showHelp(p->out, "import");
- rc = 1;
- goto meta_command_exit;
- }
- seenInterrupt = 0;
- open_db(p, 0);
- if( useOutputMode ){
- /* If neither the --csv or --ascii options are specified, then set
- ** the column and row separator characters from the output mode. */
- nSep = strlen30(p->colSeparator);
- if( nSep==0 ){
- raw_printf(stderr,
- "Error: non-null column separator required for import\n");
- rc = 1;
- goto meta_command_exit;
- }
- if( nSep>1 ){
- raw_printf(stderr,
- "Error: multi-character column separators not allowed"
- " for import\n");
- rc = 1;
- goto meta_command_exit;
- }
- nSep = strlen30(p->rowSeparator);
- if( nSep==0 ){
- raw_printf(stderr,
- "Error: non-null row separator required for import\n");
- rc = 1;
- goto meta_command_exit;
- }
- if( nSep==2 && p->mode==MODE_Csv && strcmp(p->rowSeparator,SEP_CrLf)==0 ){
- /* When importing CSV (only), if the row separator is set to the
- ** default output row separator, change it to the default input
- ** row separator. This avoids having to maintain different input
- ** and output row separators. */
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
- nSep = strlen30(p->rowSeparator);
- }
- if( nSep>1 ){
- raw_printf(stderr, "Error: multi-character row separators not allowed"
- " for import\n");
- rc = 1;
- goto meta_command_exit;
- }
- sCtx.cColSep = p->colSeparator[0];
- sCtx.cRowSep = p->rowSeparator[0];
- }
- sCtx.zFile = zFile;
- sCtx.nLine = 1;
- if( sCtx.zFile[0]=='|' ){
-#ifdef SQLITE_OMIT_POPEN
- raw_printf(stderr, "Error: pipes are not supported in this OS\n");
- rc = 1;
- goto meta_command_exit;
-#else
- sCtx.in = popen(sCtx.zFile+1, "r");
- sCtx.zFile = "<pipe>";
- sCtx.xCloser = pclose;
-#endif
- }else{
- sCtx.in = fopen(sCtx.zFile, "rb");
- sCtx.xCloser = fclose;
- }
- if( sCtx.in==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
- rc = 1;
- import_cleanup(&sCtx);
- goto meta_command_exit;
- }
- /* Below, resources must be freed before exit. */
- if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
- char zSep[2];
- zSep[1] = 0;
- zSep[0] = sCtx.cColSep;
- utf8_printf(p->out, "Column separator ");
- output_c_string(p->out, zSep);
- utf8_printf(p->out, ", row separator ");
- zSep[0] = sCtx.cRowSep;
- output_c_string(p->out, zSep);
- utf8_printf(p->out, "\n");
- }
- while( (nSkip--)>0 ){
- while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
- }
- zSql = sqlite3_mprintf("SELECT * FROM \"%w\".\"%w\"", zSchema, zTable);
- if( zSql==0 ){
- import_cleanup(&sCtx);
- shell_out_of_memory();
- }
- nByte = strlen30(zSql);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
- if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(p->db))==0 ){
- char *zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
- zSchema, zTable);
- sqlite3 *dbCols = 0;
- char *zRenames = 0;
- char *zColDefs;
- while( xRead(&sCtx) ){
- zAutoColumn(sCtx.z, &dbCols, 0);
- if( sCtx.cTerm!=sCtx.cColSep ) break;
- }
- zColDefs = zAutoColumn(0, &dbCols, &zRenames);
- if( zRenames!=0 ){
- utf8_printf(INSOURCE_IS_INTERACTIVE(p->pInSource)? p->out : stderr,
- "Columns renamed during .import %s due to duplicates:\n"
- "%s\n", sCtx.zFile, zRenames);
- sqlite3_free(zRenames);
- }
- assert(dbCols==0);
- if( zColDefs==0 ){
- sqlite3_free(zCreate);
- import_cleanup(&sCtx);
- utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
- rc = 1;
- goto meta_command_exit;
- }
- zCreate = sqlite3_mprintf("%z%z\n", zCreate, zColDefs);
- if( eVerbose>=1 ){
- utf8_printf(p->out, "%s\n", zCreate);
- }
- rc = sqlite3_exec(p->db, zCreate, 0, 0, 0);
- if( rc ){
- utf8_printf(stderr, "%s failed:\n%s\n", zCreate, sqlite3_errmsg(p->db));
- sqlite3_free(zCreate);
- import_cleanup(&sCtx);
- rc = 1;
- goto meta_command_exit;
- }
- sqlite3_free(zCreate);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- }
- sqlite3_free(zSql);
- if( rc ){
- if (pStmt) sqlite3_finalize(pStmt);
- utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
- import_cleanup(&sCtx);
- rc = 1;
- goto meta_command_exit;
- }
- nCol = sqlite3_column_count(pStmt);
- sqlite3_finalize(pStmt);
- pStmt = 0;
- if( nCol==0 ) return 0; /* no columns, no error */
- zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
- if( zSql==0 ){
- import_cleanup(&sCtx);
- shell_out_of_memory();
- }
- sqlite3_snprintf(nByte+20, zSql, "INSERT INTO \"%w\".\"%w\" VALUES(?",
- zSchema, zTable);
- j = strlen30(zSql);
- for(i=1; i<nCol; i++){
- zSql[j++] = ',';
- zSql[j++] = '?';
- }
- zSql[j++] = ')';
- zSql[j] = 0;
- if( eVerbose>=2 ){
- utf8_printf(p->out, "Insert using: %s\n", zSql);
- }
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- if( rc ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
- if (pStmt) sqlite3_finalize(pStmt);
- import_cleanup(&sCtx);
- rc = 1;
- goto meta_command_exit;
- }
- needCommit = sqlite3_get_autocommit(p->db);
- if( needCommit ) sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
- do{
- int startLine = sCtx.nLine;
- for(i=0; i<nCol; i++){
- char *z = xRead(&sCtx);
- /*
- ** Did we reach end-of-file before finding any columns?
- ** If so, stop instead of NULL filling the remaining columns.
- */
- if( z==0 && i==0 ) break;
- /*
- ** Did we reach end-of-file OR end-of-line before finding any
- ** columns in ASCII mode? If so, stop instead of NULL filling
- ** the remaining columns.
- */
- if( p->mode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break;
- sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT);
- if( i<nCol-1 && sCtx.cTerm!=sCtx.cColSep ){
- utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
- "filling the rest with NULL\n",
- sCtx.zFile, startLine, nCol, i+1);
- i += 2;
- while( i<=nCol ){ sqlite3_bind_null(pStmt, i); i++; }
- }
- }
- if( sCtx.cTerm==sCtx.cColSep ){
- do{
- xRead(&sCtx);
- i++;
- }while( sCtx.cTerm==sCtx.cColSep );
- utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
- "extras ignored\n",
- sCtx.zFile, startLine, nCol, i);
- }
- if( i>=nCol ){
- sqlite3_step(pStmt);
- rc = sqlite3_reset(pStmt);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
- startLine, sqlite3_errmsg(p->db));
- sCtx.nErr++;
- }else{
- sCtx.nRow++;
- }
- }
- }while( sCtx.cTerm!=EOF );
-
- import_cleanup(&sCtx);
- sqlite3_finalize(pStmt);
- if( needCommit ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0);
- if( eVerbose>0 ){
- utf8_printf(p->out,
- "Added %d rows with %d errors using %d lines of input\n",
- sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
- }
++ /* .parameter save
++ ** Save all or named parameters into specified or default (DB) file.
++ */
++ if( strcmp(azArg[1],"save")==0 ){
++ rc = parameters_save(p->db, (const char **)azArg+1, nArg-1);
+ }else
+
-#ifndef SQLITE_UNTESTABLE
- if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){
- char *zSql;
- char *zCollist = 0;
- sqlite3_stmt *pStmt;
- int tnum = 0;
- int isWO = 0; /* True if making an imposter of a WITHOUT ROWID table */
- int lenPK = 0; /* Length of the PRIMARY KEY string for isWO tables */
- int i;
- if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
- utf8_printf(stderr, "Usage: .imposter INDEX IMPOSTER\n"
- " .imposter off\n");
- /* Also allowed, but not documented:
- **
- ** .imposter TABLE IMPOSTER
- **
- ** where TABLE is a WITHOUT ROWID table. In that case, the
- ** imposter is another WITHOUT ROWID table with the columns in
- ** storage order. */
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- if( nArg==2 ){
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
- goto meta_command_exit;
- }
- zSql = sqlite3_mprintf(
- "SELECT rootpage, 0 FROM sqlite_schema"
- " WHERE name='%q' AND type='index'"
- "UNION ALL "
- "SELECT rootpage, 1 FROM sqlite_schema"
- " WHERE name='%q' AND type='table'"
- " AND sql LIKE '%%without%%rowid%%'",
- azArg[1], azArg[1]
- );
- sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- if( sqlite3_step(pStmt)==SQLITE_ROW ){
- tnum = sqlite3_column_int(pStmt, 0);
- isWO = sqlite3_column_int(pStmt, 1);
- }
- sqlite3_finalize(pStmt);
- zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- i = 0;
- while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
- char zLabel[20];
- const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
- i++;
- if( zCol==0 ){
- if( sqlite3_column_int(pStmt,1)==-1 ){
- zCol = "_ROWID_";
- }else{
- sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
- zCol = zLabel;
- }
- }
- if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
- lenPK = (int)strlen(zCollist);
- }
- if( zCollist==0 ){
- zCollist = sqlite3_mprintf("\"%w\"", zCol);
- }else{
- zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
- }
- }
- sqlite3_finalize(pStmt);
- if( i==0 || tnum==0 ){
- utf8_printf(stderr, "no such index: \"%s\"\n", azArg[1]);
- rc = 1;
- sqlite3_free(zCollist);
- goto meta_command_exit;
- }
- if( lenPK==0 ) lenPK = 100000;
- zSql = sqlite3_mprintf(
- "CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))WITHOUT ROWID",
- azArg[2], zCollist, lenPK, zCollist);
- sqlite3_free(zCollist);
- rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 1, tnum);
- if( rc==SQLITE_OK ){
- rc = sqlite3_exec(p->db, zSql, 0, 0, 0);
- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 0);
- if( rc ){
- utf8_printf(stderr, "Error in [%s]: %s\n", zSql, sqlite3_errmsg(p->db));
- }else{
- utf8_printf(stdout, "%s;\n", zSql);
- raw_printf(stdout,
- "WARNING: writing to an imposter table will corrupt the \"%s\" %s!\n",
- azArg[1], isWO ? "table" : "index"
- );
- }
- }else{
- raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
++ /* .parameter set NAME VALUE
++ ** Set or reset a bind parameter. NAME should be the full parameter
++ ** name exactly as it appears in the query. (ex: $abc, @def). The
++ ** VALUE can be in either SQL literal notation, or if not it will be
++ ** understood to be a text string.
++ */
++ if( nArg>=4 && strcmp(azArg[1],"set")==0 ){
++ char cCast = option_char(azArg[2]);
++ int inv = 2 + (cCast != 0);
++ ParamTableUse ptu = classify_param_name(azArg[inv]);
++ if( ptu==PTU_Nil ){
++ utf8_printf(STD_ERR,
++ "Error: %s is not a usable parameter name.\n", azArg[inv]);
+ rc = 1;
- }
- sqlite3_free(zSql);
- }else
-#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
-
-#ifdef SQLITE_ENABLE_IOTRACE
- if( c=='i' && strncmp(azArg[0], "iotrace", n)==0 ){
- SQLITE_API extern void (SQLITE_CDECL *sqlite3IoTrace)(const char*, ...);
- if( iotrace && iotrace!=stdout ) fclose(iotrace);
- iotrace = 0;
- if( nArg<2 ){
- sqlite3IoTrace = 0;
- }else if( strcmp(azArg[1], "-")==0 ){
- sqlite3IoTrace = iotracePrintf;
- iotrace = stdout;
+ }else{
- iotrace = fopen(azArg[1], "w");
- if( iotrace==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
- sqlite3IoTrace = 0;
++ param_table_init(p);
++ rc = param_set(p->db, cCast, azArg[inv],
++ &azArg[inv+1], &azArg[nArg], ptu);
++ if( rc!=SQLITE_OK ){
++ utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
+ rc = 1;
- }else{
- sqlite3IoTrace = iotracePrintf;
+ }
+ }
+ }else
-#endif
+
- if( c=='l' && n>=5 && strncmp(azArg[0], "limits", n)==0 ){
- static const struct {
- const char *zLimitName; /* Name of a limit */
- int limitCode; /* Integer code for that limit */
- } aLimit[] = {
- { "length", SQLITE_LIMIT_LENGTH },
- { "sql_length", SQLITE_LIMIT_SQL_LENGTH },
- { "column", SQLITE_LIMIT_COLUMN },
- { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH },
- { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT },
- { "vdbe_op", SQLITE_LIMIT_VDBE_OP },
- { "function_arg", SQLITE_LIMIT_FUNCTION_ARG },
- { "attached", SQLITE_LIMIT_ATTACHED },
- { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH },
- { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER },
- { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH },
- { "worker_threads", SQLITE_LIMIT_WORKER_THREADS },
- };
- int i, n2;
- open_db(p, 0);
- if( nArg==1 ){
- for(i=0; i<ArraySize(aLimit); i++){
- printf("%20s %d\n", aLimit[i].zLimitName,
- sqlite3_limit(p->db, aLimit[i].limitCode, -1));
++ { /* If no command name and arg count matches, show a syntax error */
++ showHelp(p->out, "parameter");
++ return 1;
++ }
++
++ return rc;
++}
++
++/*****************
++ * The .print, .progress and .prompt commands
++ */
++CONDITION_COMMAND( progress !defined(SQLITE_OMIT_PROGRESS_CALLBACK) );
+COLLECT_HELP_TEXT[
+ ".print STRING... Print literal STRING",
+ ".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",
+ ".prompt MAIN CONTINUE Replace the standard prompts",
+];
+DISPATCHABLE_COMMAND( print 3 1 0 ){
+ int i;
+ for(i=1; i<nArg; i++){
+ if( i>1 ) raw_printf(p->out, " ");
+ utf8_printf(p->out, "%s", azArg[i]);
+ }
+ raw_printf(p->out, "\n");
+ return 0;
+}
+DISPATCHABLE_COMMAND( progress 3 2 0 ){
+ int i;
+ int nn = 0;
+ p->flgProgress = 0;
+ p->mxProgress = 0;
+ p->nProgress = 0;
+ for(i=1; i<nArg; i++){
+ const char *z = azArg[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
+ p->flgProgress |= SHELL_PROGRESS_QUIET;
+ continue;
}
- }else if( nArg>3 ){
- raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n");
- rc = 1;
- goto meta_command_exit;
- }else{
- int iLimit = -1;
- n2 = strlen30(azArg[1]);
- for(i=0; i<ArraySize(aLimit); i++){
- if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
- if( iLimit<0 ){
- iLimit = i;
- }else{
- utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]);
- rc = 1;
- goto meta_command_exit;
- }
- }
+ if( strcmp(z,"reset")==0 ){
+ p->flgProgress |= SHELL_PROGRESS_RESET;
+ continue;
}
- if( iLimit<0 ){
- utf8_printf(stderr, "unknown limit: \"%s\"\n"
- "enter \".limits\" with no arguments for a list.\n",
- azArg[1]);
- rc = 1;
- goto meta_command_exit;
+ if( strcmp(z,"once")==0 ){
+ p->flgProgress |= SHELL_PROGRESS_ONCE;
+ continue;
}
- if( nArg==3 ){
- sqlite3_limit(p->db, aLimit[iLimit].limitCode,
- (int)integerValue(azArg[2]));
+ if( strcmp(z,"limit")==0 ){
+ if( i+1>=nArg ){
+ *pzErr = shellMPrintf(0,"Error: missing argument on --limit\n");
+ return SHELL_INVALID_ARGS;
+ }else{
+ p->mxProgress = (int)integerValue(azArg[++i]);
+ }
+ continue;
}
- printf("%20s %d\n", aLimit[iLimit].zLimitName,
- sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
+ *pzErr = shellMPrintf(0,"Error: unknown option: \"%s\"\n", azArg[i]);
+ return SHELL_INVALID_ARGS;
+ }else{
+ nn = (int)integerValue(z);
}
- }else
-
- if( c=='l' && n>2 && strncmp(azArg[0], "lint", n)==0 ){
- open_db(p, 0);
- lintDotCommand(p, azArg, nArg);
- }else
+ }
+ open_db(p, 0);
+ sqlite3_progress_handler(p->db, nn, progress_handler, p);
+ return 0;
+}
+/* Allow too few arguments by tradition, (a form of no-op.) */
+DISPATCHABLE_COMMAND( prompt ? 1 3 ){
+ if( nArg >= 2) {
+ strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
+ }
+ if( nArg >= 3) {
+ strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
+ }
+ return 0;
+}
-#ifndef SQLITE_OMIT_LOAD_EXTENSION
- if( c=='l' && strncmp(azArg[0], "load", n)==0 ){
- const char *zFile, *zProc;
- char *zErrMsg = 0;
- failIfSafeMode(p, "cannot run .load in safe mode");
- if( nArg<2 ){
- raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n");
- rc = 1;
- goto meta_command_exit;
- }
- zFile = azArg[1];
- zProc = nArg>=3 ? azArg[2] : 0;
- open_db(p, 0);
- rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "Error: %s\n", zErrMsg);
- sqlite3_free(zErrMsg);
+/*****************
+ * The .read, .recover and .restore commands
+ */
+CONDITION_COMMAND( recover !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) );
+COLLECT_HELP_TEXT[
+ ".read FILE Read input from FILE",
+ " If FILE begins with \"|\", it is a command that generates the input.",
+ ".recover Recover as much data as possible from corrupt db.",
+ " --freelist-corrupt Assume the freelist is corrupt",
+ " --recovery-db NAME Store recovery metadata in database file NAME",
+ " --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",
+ ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
+];
+DISPATCHABLE_COMMAND( read 3 2 2 ){
+ int rc = 0;
- FILE *inSaved = p->in;
- int savedLineno = p->lineno;
++ FILE *inUse = 0;
++ int (*fCloser)(FILE *) = 0;
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ if( azArg[1][0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = shellMPrintf(0,"Error: pipes are not supported in this OS\n");
+ rc = 1;
- p->out = STD_OUT;
++ p->out = STD_OUT; /* This is likely not needed. To be investigated. */
+#else
- p->in = popen(azArg[1]+1, "r");
- if( p->in==0 ){
- *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]);
++ inUse = popen(azArg[1]+1, "r");
++ if( inUse==0 ){
++ utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", azArg[1]);
rc = 1;
- rc = process_input(p);
- pclose(p->in);
+ }else{
++ fCloser = pclose;
}
- }else
#endif
- }else if( (p->in = openChrSource(azArg[1]))==0 ){
++ }else if( (inUse = openChrSource(azArg[1]))==0 ){
+ *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", azArg[1]);
+ rc = 1;
+ }else{
++ fCloser = fclose;
++ }
++ if( inUse!=0 ){
++ InSource inSourceRedir
++ = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource);
++ p->pInSource = &inSourceRedir;
+ rc = process_input(p);
- fclose(p->in);
++ assert(fCloser!=0);
++ fCloser(inUse);
++ p->pInSource = p->pInSource->pFrom;
+ }
- p->in = inSaved;
- p->lineno = savedLineno;
+ return rc;
+}
- if( c=='l' && strncmp(azArg[0], "log", n)==0 ){
- failIfSafeMode(p, "cannot run .log in safe mode");
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .log FILENAME\n");
- rc = 1;
- }else{
- const char *zFile = azArg[1];
- output_file_close(p->pLog);
- p->pLog = output_file_open(zFile, 0);
- }
- }else
+/*
+** This command is invoked to recover data from the database. A script
+** to construct a new database containing all recovered data is output
+** on stream pState->out.
+*/
+DISPATCHABLE_COMMAND( recover ? 1 7 ){
+ open_db(p, 0);
+ int rc = SQLITE_OK;
+ sqlite3_stmt *pLoop = 0; /* Loop through all root pages */
+ sqlite3_stmt *pPages = 0; /* Loop through all pages in a group */
+ sqlite3_stmt *pCells = 0; /* Loop through all cells in a page */
+ const char *zRecoveryDb = ""; /* Name of "recovery" database */
+ const char *zLostAndFound = "lost_and_found";
+ int i;
+ int nOrphan = -1;
+ RecoverTable *pOrphan = 0;
- if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){
- const char *zMode = 0;
- const char *zTabname = 0;
- int i, n2;
- ColModeOpts cmOpts = ColModeOpts_default;
- for(i=1; i<nArg; i++){
- const char *z = azArg[i];
- if( optionMatch(z,"wrap") && i+1<nArg ){
- cmOpts.iWrap = integerValue(azArg[++i]);
- }else if( optionMatch(z,"ww") ){
- cmOpts.bWordWrap = 1;
- }else if( optionMatch(z,"wordwrap") && i+1<nArg ){
- cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]);
- }else if( optionMatch(z,"quote") ){
- cmOpts.bQuote = 1;
- }else if( optionMatch(z,"noquote") ){
- cmOpts.bQuote = 0;
- }else if( zMode==0 ){
- zMode = z;
- /* Apply defaults for qbox pseudo-mods. If that
- * overwrites already-set values, user was informed of this.
- */
- if( strcmp(z, "qbox")==0 ){
- ColModeOpts cmo = ColModeOpts_default_qbox;
- zMode = "box";
- cmOpts = cmo;
- }
- }else if( zTabname==0 ){
- zTabname = z;
- }else if( z[0]=='-' ){
- utf8_printf(stderr, "unknown option: %s\n", z);
- utf8_printf(stderr, "options:\n"
- " --noquote\n"
- " --quote\n"
- " --wordwrap on/off\n"
- " --wrap N\n"
- " --ww\n");
- rc = 1;
- goto meta_command_exit;
- }else{
- utf8_printf(stderr, "extra argument: \"%s\"\n", z);
- rc = 1;
- goto meta_command_exit;
- }
- }
- if( zMode==0 ){
- if( p->mode==MODE_Column
- || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
- ){
- raw_printf
- (p->out,
- "current output mode: %s --wrap %d --wordwrap %s --%squote\n",
- modeDescr[p->mode], p->cmOpts.iWrap,
- p->cmOpts.bWordWrap ? "on" : "off",
- p->cmOpts.bQuote ? "" : "no");
- }else{
- raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
- }
- zMode = modeDescr[p->mode];
+ int bFreelist = 1; /* 0 if --freelist-corrupt is specified */
+ int bRowids = 1; /* 0 if --no-rowids */
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ int n;
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ n = strlen30(z);
+ if( n<=17 && memcmp("-freelist-corrupt", z, n)==0 ){
+ bFreelist = 0;
+ }else
+ if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
+ i++;
+ zRecoveryDb = azArg[i];
+ }else
+ if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
+ i++;
+ zLostAndFound = azArg[i];
+ }else
+ if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
+ bRowids = 0;
}
- n2 = strlen30(zMode);
- if( strncmp(zMode,"lines",n2)==0 ){
- p->mode = MODE_Line;
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
- }else if( strncmp(zMode,"columns",n2)==0 ){
- p->mode = MODE_Column;
- if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
- p->showHeader = 1;
- }
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
- p->cmOpts = cmOpts;
- }else if( strncmp(zMode,"list",n2)==0 ){
- p->mode = MODE_List;
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
- }else if( strncmp(zMode,"html",n2)==0 ){
- p->mode = MODE_Html;
- }else if( strncmp(zMode,"tcl",n2)==0 ){
- p->mode = MODE_Tcl;
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space);
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
- }else if( strncmp(zMode,"csv",n2)==0 ){
- p->mode = MODE_Csv;
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
- }else if( strncmp(zMode,"tabs",n2)==0 ){
- p->mode = MODE_List;
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
- }else if( strncmp(zMode,"insert",n2)==0 ){
- p->mode = MODE_Insert;
- set_table_name(p, zTabname ? zTabname : "table");
- }else if( strncmp(zMode,"quote",n2)==0 ){
- p->mode = MODE_Quote;
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
- }else if( strncmp(zMode,"ascii",n2)==0 ){
- p->mode = MODE_Ascii;
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
- }else if( strncmp(zMode,"markdown",n2)==0 ){
- p->mode = MODE_Markdown;
- p->cmOpts = cmOpts;
- }else if( strncmp(zMode,"table",n2)==0 ){
- p->mode = MODE_Table;
- p->cmOpts = cmOpts;
- }else if( strncmp(zMode,"box",n2)==0 ){
- p->mode = MODE_Box;
- p->cmOpts = cmOpts;
- }else if( strncmp(zMode,"count",n2)==0 ){
- p->mode = MODE_Count;
- }else if( strncmp(zMode,"off",n2)==0 ){
- p->mode = MODE_Off;
- }else if( strncmp(zMode,"json",n2)==0 ){
- p->mode = MODE_Json;
- }else{
- raw_printf(stderr, "Error: mode should be one of: "
- "ascii box column csv html insert json line list markdown "
- "qbox quote table tabs tcl\n");
- rc = 1;
+ else{
- *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]);
++ *pzErr = shellMPrintf(0,"unexpected option: %s\n", azArg[i]);
+ showHelp(p->out, azArg[0]);
+ return 1;
}
- p->cMode = p->mode;
- }else
+ }
- if( c=='n' && strcmp(azArg[0], "nonce")==0 ){
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .nonce NONCE\n");
- rc = 1;
- }else if( p->zNonce==0 || strcmp(azArg[1],p->zNonce)!=0 ){
- raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n",
- p->pInSource->lineno, azArg[1]);
- exit(1);
- }else{
- p->bSafeMode = 0;
- return 0; /* Return immediately to bypass the safe mode reset
- ** at the end of this procedure */
+ shellExecPrintf(p->db, &rc,
- /* Attach an in-memory database named 'recovery'. Create an indexed
++ /* Attach an in-memory database named 'recovery'. Create an indexed
+ ** cache of the sqlite_dbptr virtual table. */
+ "PRAGMA writable_schema = on;"
+ "ATTACH %Q AS recovery;"
+ "DROP TABLE IF EXISTS recovery.dbptr;"
+ "DROP TABLE IF EXISTS recovery.freelist;"
+ "DROP TABLE IF EXISTS recovery.map;"
+ "DROP TABLE IF EXISTS recovery.schema;"
+ "CREATE TABLE recovery.freelist(pgno INTEGER PRIMARY KEY);", zRecoveryDb
+ );
+
+ if( bFreelist ){
+ shellExec(p->db, &rc,
+ "WITH trunk(pgno) AS ("
+ " SELECT shell_int32("
+ " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 8) AS x "
+ " WHERE x>0"
+ " UNION"
+ " SELECT shell_int32("
+ " (SELECT data FROM sqlite_dbpage WHERE pgno=trunk.pgno), 0) AS x "
+ " FROM trunk WHERE x>0"
+ "),"
+ "freelist(data, n, freepgno) AS ("
+ " SELECT data, min(16384, shell_int32(data, 1)-1), t.pgno "
+ " FROM trunk t, sqlite_dbpage s WHERE s.pgno=t.pgno"
+ " UNION ALL"
+ " SELECT data, n-1, shell_int32(data, 2+n) "
+ " FROM freelist WHERE n>=0"
+ ")"
+ "REPLACE INTO recovery.freelist SELECT freepgno FROM freelist;"
+ );
+ }
+
+ /* If this is an auto-vacuum database, add all pointer-map pages to
- ** the freelist table. Do this regardless of whether or not
++ ** the freelist table. Do this regardless of whether or not
+ ** --freelist-corrupt was specified. */
- shellExec(p->db, &rc,
++ shellExec(p->db, &rc,
+ "WITH ptrmap(pgno) AS ("
+ " SELECT 2 WHERE shell_int32("
+ " (SELECT data FROM sqlite_dbpage WHERE pgno=1), 13"
+ " )"
+ " UNION ALL "
+ " SELECT pgno+1+(SELECT page_size FROM pragma_page_size)/5 AS pp "
+ " FROM ptrmap WHERE pp<=(SELECT page_count FROM pragma_page_count)"
+ ")"
+ "REPLACE INTO recovery.freelist SELECT pgno FROM ptrmap"
+ );
+
- shellExec(p->db, &rc,
++ shellExec(p->db, &rc,
+ "CREATE TABLE recovery.dbptr("
+ " pgno, child, PRIMARY KEY(child, pgno)"
+ ") WITHOUT ROWID;"
+ "INSERT OR IGNORE INTO recovery.dbptr(pgno, child) "
+ " SELECT * FROM sqlite_dbptr"
+ " WHERE pgno NOT IN freelist AND child NOT IN freelist;"
+
+ /* Delete any pointer to page 1. This ensures that page 1 is considered
+ ** a root page, regardless of how corrupt the db is. */
+ "DELETE FROM recovery.dbptr WHERE child = 1;"
+
+ /* Delete all pointers to any pages that have more than one pointer
+ ** to them. Such pages will be treated as root pages when recovering
+ ** data. */
+ "DELETE FROM recovery.dbptr WHERE child IN ("
+ " SELECT child FROM recovery.dbptr GROUP BY child HAVING count(*)>1"
+ ");"
+
+ /* Create the "map" table that will (eventually) contain instructions
- ** for dealing with each page in the db that contains one or more
++ ** for dealing with each page in the db that contains one or more
+ ** records. */
+ "CREATE TABLE recovery.map("
+ "pgno INTEGER PRIMARY KEY, maxlen INT, intkey, root INT"
+ ");"
+
+ /* Populate table [map]. If there are circular loops of pages in the
+ ** database, the following adds all pages in such a loop to the map
+ ** as individual root pages. This could be handled better. */
+ "WITH pages(i, maxlen) AS ("
+ " SELECT page_count, ("
+ " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=page_count"
+ " ) FROM pragma_page_count WHERE page_count>0"
+ " UNION ALL"
+ " SELECT i-1, ("
+ " SELECT max(field+1) FROM sqlite_dbdata WHERE pgno=i-1"
+ " ) FROM pages WHERE i>=2"
+ ")"
+ "INSERT INTO recovery.map(pgno, maxlen, intkey, root) "
+ " SELECT i, maxlen, NULL, ("
+ " WITH p(orig, pgno, parent) AS ("
+ " SELECT 0, i, (SELECT pgno FROM recovery.dbptr WHERE child=i)"
+ " UNION "
+ " SELECT i, p.parent, "
+ " (SELECT pgno FROM recovery.dbptr WHERE child=p.parent) FROM p"
+ " )"
+ " SELECT pgno FROM p WHERE (parent IS NULL OR pgno = orig)"
+ ") "
+ "FROM pages WHERE maxlen IS NOT NULL AND i NOT IN freelist;"
+ "UPDATE recovery.map AS o SET intkey = ("
+ " SELECT substr(data, 1, 1)==X'0D' FROM sqlite_dbpage WHERE pgno=o.pgno"
+ ");"
+
+ /* Extract data from page 1 and any linked pages into table
+ ** recovery.schema. With the same schema as an sqlite_schema table. */
+ "CREATE TABLE recovery.schema(type, name, tbl_name, rootpage, sql);"
+ "INSERT INTO recovery.schema SELECT "
+ " max(CASE WHEN field=0 THEN value ELSE NULL END),"
+ " max(CASE WHEN field=1 THEN value ELSE NULL END),"
+ " max(CASE WHEN field=2 THEN value ELSE NULL END),"
+ " max(CASE WHEN field=3 THEN value ELSE NULL END),"
+ " max(CASE WHEN field=4 THEN value ELSE NULL END)"
+ "FROM sqlite_dbdata WHERE pgno IN ("
+ " SELECT pgno FROM recovery.map WHERE root=1"
+ ")"
+ "GROUP BY pgno, cell;"
+ "CREATE INDEX recovery.schema_rootpage ON schema(rootpage);"
+ );
+
- /* Open a transaction, then print out all non-virtual, non-"sqlite_%"
++ /* Open a transaction, then print out all non-virtual, non-"sqlite_%"
+ ** CREATE TABLE statements that extracted from the existing schema. */
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pStmt = 0;
+ /* ".recover" might output content in an order which causes immediate
+ ** foreign key constraints to be violated. So disable foreign-key
+ ** constraint enforcement to prevent problems when running the output
+ ** script. */
+ raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n");
+ raw_printf(p->out, "BEGIN;\n");
+ raw_printf(p->out, "PRAGMA writable_schema = on;\n");
+ shellPrepare(p->db, &rc,
+ "SELECT sql FROM recovery.schema "
+ "WHERE type='table' AND sql LIKE 'create table%'", &pStmt
+ );
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ const char *zCreateTable = (const char*)sqlite3_column_text(pStmt, 0);
- raw_printf(p->out, "CREATE TABLE IF NOT EXISTS %s;\n",
++ raw_printf(p->out, "CREATE TABLE IF NOT EXISTS %s;\n",
+ &zCreateTable[12]
+ );
}
- }else
+ shellFinalize(&rc, pStmt);
+ }
- if( c=='n' && strncmp(azArg[0], "nullvalue", n)==0 ){
- if( nArg==2 ){
- sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
- "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
- }else{
- raw_printf(stderr, "Usage: .nullvalue STRING\n");
- rc = 1;
- }
- }else
+ /* Figure out if an orphan table will be required. And if so, how many
+ ** user columns it should contain */
- shellPrepare(p->db, &rc,
++ shellPrepare(p->db, &rc,
+ "SELECT coalesce(max(maxlen), -2) FROM recovery.map WHERE root>1"
+ , &pLoop
+ );
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
+ nOrphan = sqlite3_column_int(pLoop, 0);
+ }
+ shellFinalize(&rc, pLoop);
+ pLoop = 0;
- if( c=='o' && strncmp(azArg[0], "open", n)==0 && n>=2 ){
- const char *zFN = 0; /* Pointer to constant filename */
- char *zNewFilename = 0; /* Name of the database file to open */
- int iName = 1; /* Index in azArg[] of the filename */
- int newFlag = 0; /* True to delete file before opening */
- u8 openMode = SHELL_OPEN_UNSPEC;
-
- /* Check for command-line arguments */
- for(iName=1; iName<nArg; iName++){
- const char *z = azArg[iName];
- if( optionMatch(z,"new") ){
- newFlag = 1;
-#ifdef SQLITE_HAVE_ZLIB
- }else if( optionMatch(z, "zip") ){
- openMode = SHELL_OPEN_ZIPFILE;
-#endif
- }else if( optionMatch(z, "append") ){
- openMode = SHELL_OPEN_APPENDVFS;
- }else if( optionMatch(z, "readonly") ){
- openMode = SHELL_OPEN_READONLY;
- }else if( optionMatch(z, "nofollow") ){
- p->openFlags |= SQLITE_OPEN_NOFOLLOW;
-#ifndef SQLITE_OMIT_DESERIALIZE
- }else if( optionMatch(z, "deserialize") ){
- openMode = SHELL_OPEN_DESERIALIZE;
- }else if( optionMatch(z, "hexdb") ){
- openMode = SHELL_OPEN_HEXDB;
- }else if( optionMatch(z, "maxsize") && iName+1<nArg ){
- p->szMax = integerValue(azArg[++iName]);
-#endif /* SQLITE_OMIT_DESERIALIZE */
- }else if( z[0]=='-' ){
- utf8_printf(stderr, "unknown option: %s\n", z);
- rc = 1;
- goto meta_command_exit;
- }else if( zFN ){
- utf8_printf(stderr, "extra argument: \"%s\"\n", z);
- rc = 1;
- goto meta_command_exit;
- }else{
- zFN = z;
- }
- }
+ shellPrepare(p->db, &rc,
+ "SELECT pgno FROM recovery.map WHERE root=?", &pPages
+ );
- /* Close the existing database */
- session_close_all(p, -1);
- close_db(p->db);
- p->db = 0;
- p->pAuxDb->zDbFilename = 0;
- sqlite3_free(p->pAuxDb->zFreeOnClose);
- p->pAuxDb->zFreeOnClose = 0;
- p->openMode = openMode;
- p->openFlags = 0;
- p->szMax = 0;
-
- /* If a filename is specified, try to open it first */
- if( zFN || p->openMode==SHELL_OPEN_HEXDB ){
- if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN);
- if( p->bSafeMode
- && p->openMode!=SHELL_OPEN_HEXDB
- && zFN
- && strcmp(zFN,":memory:")!=0
- ){
- failIfSafeMode(p, "cannot open disk-based database files in safe mode");
- }
- if( zFN ){
- zNewFilename = sqlite3_mprintf("%s", zFN);
- shell_check_oom(zNewFilename);
- }else{
- zNewFilename = 0;
- }
- p->pAuxDb->zDbFilename = zNewFilename;
- open_db(p, OPEN_DB_KEEPALIVE);
- if( p->db==0 ){
- utf8_printf(stderr, "Error: cannot open '%s'\n", zNewFilename);
- sqlite3_free(zNewFilename);
- }else{
- p->pAuxDb->zFreeOnClose = zNewFilename;
- }
- }
- if( p->db==0 ){
- /* As a fall-back open a TEMP database */
- p->pAuxDb->zDbFilename = 0;
- open_db(p, 0);
- }
- }else
+ shellPrepare(p->db, &rc,
+ "SELECT max(field), group_concat(shell_escape_crnl(quote"
+ "(case when (? AND field<0) then NULL else value end)"
+ "), ', ')"
+ ", min(field) "
+ "FROM sqlite_dbdata WHERE pgno = ? AND field != ?"
+ "GROUP BY cell", &pCells
+ );
- if( (c=='o'
- && (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
- || (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
- ){
- char *zFile = 0;
- int bTxtMode = 0;
- int i;
- int eMode = 0;
- int bBOM = 0;
- int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */
+ /* Loop through each root page. */
- shellPrepare(p->db, &rc,
- "SELECT root, intkey, max(maxlen) FROM recovery.map"
++ shellPrepare(p->db, &rc,
++ "SELECT root, intkey, max(maxlen) FROM recovery.map"
+ " WHERE root>1 GROUP BY root, intkey ORDER BY root=("
+ " SELECT rootpage FROM recovery.schema WHERE name='sqlite_sequence'"
+ ")", &pLoop
+ );
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pLoop) ){
+ int iRoot = sqlite3_column_int(pLoop, 0);
+ int bIntkey = sqlite3_column_int(pLoop, 1);
+ int nCol = sqlite3_column_int(pLoop, 2);
+ int bNoop = 0;
+ RecoverTable *pTab;
- failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
- if( c=='e' ){
- eMode = 'x';
- bOnce = 2;
- }else if( strncmp(azArg[0],"once",n)==0 ){
- bOnce = 1;
- }
- for(i=1; i<nArg; i++){
- char *z = azArg[i];
- if( z[0]=='-' ){
- if( z[1]=='-' ) z++;
- if( strcmp(z,"-bom")==0 ){
- bBOM = 1;
- }else if( c!='e' && strcmp(z,"-x")==0 ){
- eMode = 'x'; /* spreadsheet */
- }else if( c!='e' && strcmp(z,"-e")==0 ){
- eMode = 'e'; /* text editor */
- }else{
- utf8_printf(p->out, "ERROR: unknown option: \"%s\". Usage:\n",
- azArg[i]);
- showHelp(p->out, azArg[0]);
- rc = 1;
- goto meta_command_exit;
- }
- }else if( zFile==0 && eMode!='e' && eMode!='x' ){
- zFile = sqlite3_mprintf("%s", z);
- if( zFile && zFile[0]=='|' ){
- while( i+1<nArg ) zFile = sqlite3_mprintf("%z %s", zFile, azArg[++i]);
- break;
- }
- }else{
- utf8_printf(p->out,"ERROR: extra parameter: \"%s\". Usage:\n",
- azArg[i]);
- showHelp(p->out, azArg[0]);
- rc = 1;
- sqlite3_free(zFile);
- goto meta_command_exit;
+ assert( bIntkey==0 || bIntkey==1 );
+ pTab = recoverFindTable(p, &rc, iRoot, bIntkey, nCol, &bNoop);
+ if( bNoop || rc ) continue;
+ if( pTab==0 ){
+ if( pOrphan==0 ){
+ pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan);
}
+ pTab = pOrphan;
+ if( pTab==0 ) break;
}
- if( zFile==0 ){
- zFile = sqlite3_mprintf("stdout");
- }
- if( bOnce ){
- p->outCount = 2;
- }else{
- p->outCount = 0;
- }
- output_reset(p);
-#ifndef SQLITE_NOHAVE_SYSTEM
- if( eMode=='e' || eMode=='x' ){
- p->doXdgOpen = 1;
- outputModePush(p);
- if( eMode=='x' ){
- /* spreadsheet mode. Output as CSV. */
- newTempFile(p, "csv");
- ShellClearFlag(p, SHFLG_Echo);
- p->mode = MODE_Csv;
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
- }else{
- /* text editor mode */
- newTempFile(p, "txt");
- bTxtMode = 1;
- }
- sqlite3_free(zFile);
- zFile = sqlite3_mprintf("%s", p->zTempFile);
+
+ if( 0==sqlite3_stricmp(pTab->zQuoted, "\"sqlite_sequence\"") ){
+ raw_printf(p->out, "DELETE FROM sqlite_sequence;\n");
}
-#endif /* SQLITE_NOHAVE_SYSTEM */
- shell_check_oom(zFile);
- if( zFile[0]=='|' ){
-#ifdef SQLITE_OMIT_POPEN
- raw_printf(stderr, "Error: pipes are not supported in this OS\n");
- rc = 1;
- p->out = stdout;
-#else
- p->out = popen(zFile + 1, "w");
- if( p->out==0 ){
- utf8_printf(stderr,"Error: cannot open pipe \"%s\"\n", zFile + 1);
- p->out = stdout;
- rc = 1;
- }else{
- if( bBOM ) fprintf(p->out,"\357\273\277");
- sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
- }
-#endif
+ sqlite3_bind_int(pPages, 1, iRoot);
+ if( bRowids==0 && pTab->iPk<0 ){
+ sqlite3_bind_int(pCells, 1, 1);
}else{
- p->out = output_file_open(zFile, bTxtMode);
- if( p->out==0 ){
- if( strcmp(zFile,"off")!=0 ){
- utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
- }
- p->out = stdout;
- rc = 1;
- } else {
- if( bBOM ) fprintf(p->out,"\357\273\277");
- sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
- }
+ sqlite3_bind_int(pCells, 1, 0);
}
- sqlite3_free(zFile);
- }else
-
- if( c=='p' && n>=2 && strncmp(azArg[0], "parameter", n)==0 ){
- open_db(p,0);
- if( nArg<=1 ) goto parameter_syntax_error;
+ sqlite3_bind_int(pCells, 3, pTab->iPk);
- /* .parameter clear and .parameter unset ?NAMES?
- ** Delete some or all bind parameters from the TEMP table that holds them.
- ** Without any arguments, clear deletes them all and unset does nothing.
- */
- if( strcmp(azArg[1],"clear")==0 || strcmp(azArg[1],"unset")==0 ){
- if( param_table_exists(p->db) && (nArg>2 || azArg[1][0]=='c') ){
- sqlite3_str *sbZap = sqlite3_str_new(p->db);
- char *zSql;
- sqlite3_str_appendf
- (sbZap, "DELETE FROM "PARAM_TABLE_SNAME" WHERE key ");
- append_in_clause(sbZap,
- (const char **)&azArg[2], (const char **)&azArg[nArg]);
- zSql = sqlite3_str_finish(sbZap);
- shell_check_oom(zSql);
- sqlite3_exec(p->db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
- }
- }else
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPages) ){
+ int iPgno = sqlite3_column_int(pPages, 0);
+ sqlite3_bind_int(pCells, 2, iPgno);
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pCells) ){
+ int nField = sqlite3_column_int(pCells, 0);
+ int iMin = sqlite3_column_int(pCells, 2);
+ const char *zVal = (const char*)sqlite3_column_text(pCells, 1);
-#ifndef SQLITE_NOHAVE_SYSTEM
- /* .parameter edit ?NAMES?
- ** Edit the named parameters. Any that do not exist are created.
- ** New ones get a uses tag auto-selected by their leading char.
- */
- if( strcmp(azArg[1],"edit")==0 ){
- int ia = 2;
- int eval = 0;
- if( !INSOURCE_IS_INTERACTIVE(p->pInSource) ){
- utf8_printf(stderr, "Error: "
- ".parameter edit can only be used interactively.\n");
- rc = 1;
- goto meta_command_exit;
- }
- param_table_init(p);
- if( p->zEditor==0 ){
- const char *zE = getenv("VISUAL");
- if( zE!=0 ) p->zEditor = sqlite3_mprintf("%s", zE);
- }
- if( nArg>=3 && azArg[2][0]=='-' ){
- char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1;
- if( strncmp(zArg,"editor=",7)==0 ){
- sqlite3_free(p->zEditor);
- /* Accept an initial -editor=? option. */
- p->zEditor = sqlite3_mprintf("%s", zArg+7);
- ++ia;
- }
- }
- if( p->zEditor==0 ){
- /* This is klutzy, but edit is for interactive use. So this
- * problem, due to not heeding the .parameter doc, can be
- * fixed with a modest inconvenience to the casual user.
- */
- utf8_printf(stderr,
- "Either set env-var VISUAL to name an"
- " editor and restart, or rerun\n "
- ".parameter edit with an initial "
- "edit option, --editor=EDITOR_COMMAND .\n");
- rc = 1;
- goto meta_command_exit;
- }
- /* Future: Allow an option whereby new value can be evaluated
- * the way that .parameter set ... does.
- */
- while( ia < nArg ){
- ParamTableUse ptu;
- char cf = (azArg[ia][0]=='-')? azArg[ia][1] : 0;
- if( cf!=0 && azArg[ia][2]==0 ){
- ++ia;
- switch( cf ){
- case 'e': eval = 1; continue;
- case 't': eval = 0; continue;
- default:
- utf8_printf(stderr, "Error: bad .parameter name: %s\n",
- azArg[--ia]);
- rc = 1;
- goto meta_command_exit;
+ RecoverTable *pTab2 = pTab;
+ if( pTab!=pOrphan && (iMin<0)!=bIntkey ){
+ if( pOrphan==0 ){
+ pOrphan = recoverOrphanTable(p, &rc, zLostAndFound, nOrphan);
}
+ pTab2 = pOrphan;
+ if( pTab2==0 ) break;
}
- ptu = classify_param_name(azArg[ia]);
- if( ptu==PTU_Nil ){
- utf8_printf(stderr, "Error: %s cannot be a binding or executable"
- " parameter name.\n", azArg[ia]);
- rc = 1;
- goto meta_command_exit;
- }
- rc = edit_one_param(p->db, azArg[ia], eval, ptu, p->zEditor);
- ++ia;
- if( rc!=0 ) goto meta_command_exit;
- }
- }else
-#endif
- /* .parameter init
- ** Make sure the TEMP table used to hold bind parameters exists.
- ** Create it if necessary.
- */
- if( nArg==2 && strcmp(azArg[1],"init")==0 ){
- param_table_init(p);
- }else
-
- /* .parameter list|ls
- ** List all or selected bind parameters.
- ** list displays names, values and uses.
- ** ls displays just the names.
- */
- if( nArg>=2 && ((strcmp(azArg[1],"list")==0)
- || (strcmp(azArg[1],"ls")==0)) ){
- list_params(p, PTU_Nil, azArg[1][1]=='s', azArg+2, nArg-2);
- }else
-
- /* .parameter load
- ** Load all or named parameters from specified or default (DB) file.
- */
- if( strcmp(azArg[1],"load")==0 ){
- param_table_init(p);
- rc = parameters_load(p->db, (const char **)azArg+1, nArg-1);
- }else
-
- /* .parameter save
- ** Save all or named parameters into specified or default (DB) file.
- */
- if( strcmp(azArg[1],"save")==0 ){
- rc = parameters_save(p->db, (const char **)azArg+1, nArg-1);
- }else
-
- /* .parameter set NAME VALUE
- ** Set or reset a bind parameter. NAME should be the full parameter
- ** name exactly as it appears in the query. (ex: $abc, @def). The
- ** VALUE can be in either SQL literal notation, or if not it will be
- ** understood to be a text string.
- */
- if( nArg>=4 && strcmp(azArg[1],"set")==0 ){
- char cCast = option_char(azArg[2]);
- int inv = 2 + (cCast != 0);
- ParamTableUse ptu = classify_param_name(azArg[inv]);
- if( ptu==PTU_Nil ){
- utf8_printf(stderr,
- "Error: %s is not a usable parameter name.\n", azArg[inv]);
- rc = 1;
- }else{
- param_table_init(p);
- rc = param_set(p->db, cCast, azArg[inv],
- &azArg[inv+1], &azArg[nArg], ptu);
- if( rc!=SQLITE_OK ){
- utf8_printf(p->out, "Error: %s\n", sqlite3_errmsg(p->db));
- rc = 1;
- }
- }
- }else
-
- /* If no command name matches, show a syntax error */
- parameter_syntax_error:
- showHelp(p->out, "parameter");
- }else
-
- if( c=='p' && n>=3 && strncmp(azArg[0], "print", n)==0 ){
- int i;
- for(i=1; i<nArg; i++){
- if( i>1 ) raw_printf(p->out, " ");
- utf8_printf(p->out, "%s", azArg[i]);
- }
- raw_printf(p->out, "\n");
- }else
-#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
- if( c=='p' && n>=3 && strncmp(azArg[0], "progress", n)==0 ){
- int i;
- int nn = 0;
- p->flgProgress = 0;
- p->mxProgress = 0;
- p->nProgress = 0;
- for(i=1; i<nArg; i++){
- const char *z = azArg[i];
- if( z[0]=='-' ){
- z++;
- if( z[0]=='-' ) z++;
- if( strcmp(z,"quiet")==0 || strcmp(z,"q")==0 ){
- p->flgProgress |= SHELL_PROGRESS_QUIET;
- continue;
- }
- if( strcmp(z,"reset")==0 ){
- p->flgProgress |= SHELL_PROGRESS_RESET;
- continue;
- }
- if( strcmp(z,"once")==0 ){
- p->flgProgress |= SHELL_PROGRESS_ONCE;
- continue;
- }
- if( strcmp(z,"limit")==0 ){
- if( i+1>=nArg ){
- utf8_printf(stderr, "Error: missing argument on --limit\n");
- rc = 1;
- goto meta_command_exit;
- }else{
- p->mxProgress = (int)integerValue(azArg[++i]);
- }
- continue;
+ nField = nField+1;
+ if( pTab2==pOrphan ){
- raw_printf(p->out,
++ raw_printf(p->out,
+ "INSERT INTO %s VALUES(%d, %d, %d, %s%s%s);\n",
+ pTab2->zQuoted, iRoot, iPgno, nField,
+ iMin<0 ? "" : "NULL, ", zVal, pTab2->azlCol[nField]
+ );
+ }else{
- raw_printf(p->out, "INSERT INTO %s(%s) VALUES( %s );\n",
++ raw_printf(p->out, "INSERT INTO %s(%s) VALUES( %s );\n",
+ pTab2->zQuoted, pTab2->azlCol[nField], zVal
+ );
}
- utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]);
- rc = 1;
- goto meta_command_exit;
- }else{
- nn = (int)integerValue(z);
}
+ shellReset(&rc, pCells);
}
- open_db(p, 0);
- sqlite3_progress_handler(p->db, nn, progress_handler, p);
- }else
-#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
-
- if( c=='p' && strncmp(azArg[0], "prompt", n)==0 ){
- if( nArg >= 2) {
- strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
- }
- if( nArg >= 3) {
- strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
- }
- }else
-
- if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
- rc = 2;
- }else
+ shellReset(&rc, pPages);
+ if( pTab!=pOrphan ) recoverFreeTable(pTab);
+ }
+ shellFinalize(&rc, pLoop);
+ shellFinalize(&rc, pPages);
+ shellFinalize(&rc, pCells);
+ recoverFreeTable(pOrphan);
- if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){
- FILE *inUse = 0;
- int (*fCloser)(FILE *) = 0;
- failIfSafeMode(p, "cannot run .read in safe mode");
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .read FILE\n");
- rc = 1;
- goto meta_command_exit;
- }
- if( azArg[1][0]=='|' ){
-#ifdef SQLITE_OMIT_POPEN
- raw_printf(stderr, "Error: pipes are not supported in this OS\n");
- rc = 1;
- p->out = stdout; /* This is likely not needed. To be investigated. */
-#else
- inUse = popen(azArg[1]+1, "r");
- if( inUse==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
- rc = 1;
+ /* The rest of the schema */
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pStmt = 0;
- shellPrepare(p->db, &rc,
++ shellPrepare(p->db, &rc,
+ "SELECT sql, name FROM recovery.schema "
+ "WHERE sql NOT LIKE 'create table%'", &pStmt
+ );
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ const char *zSql = (const char*)sqlite3_column_text(pStmt, 0);
+ if( sqlite3_strnicmp(zSql, "create virt", 11)==0 ){
+ const char *zName = (const char*)sqlite3_column_text(pStmt, 1);
- char *zPrint = shellMPrintf(&rc,
++ char *zPrint = shellMPrintf(&rc,
+ "INSERT INTO sqlite_schema VALUES('table', %Q, %Q, 0, %Q)",
+ zName, zName, zSql
+ );
+ raw_printf(p->out, "%s;\n", zPrint);
+ sqlite3_free(zPrint);
}else{
- fCloser = pclose;
+ raw_printf(p->out, "%s;\n", zSql);
}
-#endif
- }else if( (inUse = openChrSource(azArg[1]))==0 ){
- utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
- rc = 1;
- }else{
- fCloser = fclose;
}
- if( inUse!=0 ){
- InSource inSourceRedir
- = INSOURCE_FILE_REDIR(inUse, azArg[1], p->pInSource);
- p->pInSource = &inSourceRedir;
- rc = process_input(p);
- assert(fCloser!=0);
- fCloser(inUse);
- p->pInSource = p->pInSource->pFrom;
- }
- }else
+ shellFinalize(&rc, pStmt);
+ }
- if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){
- const char *zSrcFile;
- const char *zDb;
- sqlite3 *pSrc;
- sqlite3_backup *pBackup;
- int nTimeout = 0;
-
- failIfSafeMode(p, "cannot run .restore in safe mode");
- if( nArg==2 ){
- zSrcFile = azArg[1];
- zDb = "main";
- }else if( nArg==3 ){
- zSrcFile = azArg[2];
- zDb = azArg[1];
- }else{
- raw_printf(stderr, "Usage: .restore ?DB? FILE\n");
- rc = 1;
- goto meta_command_exit;
- }
- rc = sqlite3_open(zSrcFile, &pSrc);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zSrcFile);
- close_db(pSrc);
- return 1;
- }
- open_db(p, 0);
- pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
- if( pBackup==0 ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
- close_db(pSrc);
- return 1;
- }
- while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
- || rc==SQLITE_BUSY ){
- if( rc==SQLITE_BUSY ){
- if( nTimeout++ >= 3 ) break;
- sqlite3_sleep(100);
- }
- }
- sqlite3_backup_finish(pBackup);
- if( rc==SQLITE_DONE ){
- rc = 0;
- }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
- raw_printf(stderr, "Error: source database is busy\n");
- rc = 1;
- }else{
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
- rc = 1;
- }
+ if( rc==SQLITE_OK ){
+ raw_printf(p->out, "PRAGMA writable_schema = off;\n");
+ raw_printf(p->out, "COMMIT;\n");
+ }
+ sqlite3_exec(p->db, "DETACH recovery", 0, 0, 0);
+ return rc;
+}
+
+DISPATCHABLE_COMMAND( restore ? 2 3 ){
+ int rc;
+ const char *zSrcFile;
+ const char *zDb;
+ sqlite3 *pSrc;
+ sqlite3_backup *pBackup;
+ int nTimeout = 0;
+
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ if( nArg==2 ){
+ zSrcFile = azArg[1];
+ zDb = "main";
+ }else if( nArg==3 ){
+ zSrcFile = azArg[2];
+ zDb = azArg[1];
+ }else{
+ return SHELL_INVALID_ARGS;
+ }
+ rc = sqlite3_open(zSrcFile, &pSrc);
+ if( rc!=SQLITE_OK ){
+ *pzErr = shellMPrintf(0,"Error: cannot open \"%s\"\n", zSrcFile);
close_db(pSrc);
- }else
+ return 1;
+ }
+ open_db(p, 0);
+ pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
+ if( pBackup==0 ){
+ *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+ close_db(pSrc);
+ return 1;
+ }
+ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
+ || rc==SQLITE_BUSY ){
+ if( rc==SQLITE_BUSY ){
+ if( nTimeout++ >= 3 ) break;
+ sqlite3_sleep(100);
+ }
+ }
+ sqlite3_backup_finish(pBackup);
+ if( rc==SQLITE_DONE ){
+ rc = 0;
+ }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
+ *pzErr = shellMPrintf(0,"Error: source database is busy\n");
+ rc = 1;
+ }else{
+ *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+ rc = 1;
+ }
+ close_db(pSrc);
+ return rc;
+}
- if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){
- if( nArg==2 ){
- p->scanstatsOn = (u8)booleanValue(azArg[1]);
+/*****************
+ * The .scanstats and .schema commands
+ */
+COLLECT_HELP_TEXT[
+ ".scanstats on|off 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_\"",
+];
+DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
+ p->scanstatsOn = (u8)booleanValue(azArg[1]);
#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
- raw_printf(stderr, "Warning: .scanstats not available in this build.\n");
+ raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
#endif
+ return 0;
+}
+DISPATCHABLE_COMMAND( schema ? 1 2 ){
+ int rc;
+ ShellText sSelect;
+ ShellState data;
+ char *zErrMsg = 0;
+ const char *zDiv = "(";
+ const char *zName = 0;
+ int iSchema = 0;
+ int bDebug = 0;
+ int bNoSystemTabs = 0;
+ int ii;
+
+ open_db(p, 0);
+ memcpy(&data, p, sizeof(data));
+ data.showHeader = 0;
+ data.cMode = data.mode = MODE_Semi;
+ initText(&sSelect);
+ for(ii=1; ii<nArg; ii++){
+ if( optionMatch(azArg[ii],"indent") ){
+ data.cMode = data.mode = MODE_Pretty;
+ }else if( optionMatch(azArg[ii],"debug") ){
+ bDebug = 1;
+ }else if( optionMatch(azArg[ii],"nosys") ){
+ bNoSystemTabs = 1;
+ }else if( azArg[ii][0]=='-' ){
+ *pzErr = shellMPrintf(0,"Unknown option: \"%s\"\n", azArg[ii]);
+ return SHELL_INVALID_ARGS;
+ }else if( zName==0 ){
+ zName = azArg[ii];
}else{
- raw_printf(stderr, "Usage: .scanstats on|off\n");
- rc = 1;
+ return SHELL_INVALID_ARGS;
+ }
+ }
+ if( zName!=0 ){
+ int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
+ || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
+ || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
+ || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
+ if( isSchema ){
+ char *new_argv[2], *new_colv[2];
+ new_argv[0] = sqlite3_mprintf(
+ "CREATE TABLE %s (\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")", zName);
+ shell_check_oom(new_argv[0]);
+ new_argv[1] = 0;
+ new_colv[0] = "sql";
+ new_colv[1] = 0;
+ callback(&data, 1, new_argv, new_colv);
+ sqlite3_free(new_argv[0]);
+ }
+ }
+ if( zDiv ){
+ sqlite3_stmt *pStmt = 0;
+ rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
+ -1, &pStmt, 0);
+ if( rc ){
+ *pzErr = shellMPrintf(0,"Error: %s\n", sqlite3_errmsg(p->db));
+ sqlite3_finalize(pStmt);
+ return 1;
}
- }else
-
- if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){
- ShellText sSelect;
- ShellState data;
- char *zErrMsg = 0;
- const char *zDiv = "(";
- const char *zName = 0;
- int iSchema = 0;
- int bDebug = 0;
- int bNoSystemTabs = 0;
- int ii;
-
- open_db(p, 0);
- memcpy(&data, p, sizeof(data));
- data.showHeader = 0;
- data.cMode = data.mode = MODE_Semi;
- initText(&sSelect);
- for(ii=1; ii<nArg; ii++){
- if( optionMatch(azArg[ii],"indent") ){
- data.cMode = data.mode = MODE_Pretty;
- }else if( optionMatch(azArg[ii],"debug") ){
- bDebug = 1;
- }else if( optionMatch(azArg[ii],"nosys") ){
- bNoSystemTabs = 1;
- }else if( azArg[ii][0]=='-' ){
- utf8_printf(stderr, "Unknown option: \"%s\"\n", azArg[ii]);
- rc = 1;
- goto meta_command_exit;
- }else if( zName==0 ){
- zName = azArg[ii];
- }else{
- raw_printf(stderr,
- "Usage: .schema ?--indent? ?--nosys? ?LIKE-PATTERN?\n");
- rc = 1;
- goto meta_command_exit;
- }
- }
- if( zName!=0 ){
- int isSchema = sqlite3_strlike(zName, "sqlite_master", '\\')==0
- || sqlite3_strlike(zName, "sqlite_schema", '\\')==0
- || sqlite3_strlike(zName,"sqlite_temp_master", '\\')==0
- || sqlite3_strlike(zName,"sqlite_temp_schema", '\\')==0;
- if( isSchema ){
- char *new_argv[2], *new_colv[2];
- new_argv[0] = sqlite3_mprintf(
- "CREATE TABLE %s (\n"
- " type text,\n"
- " name text,\n"
- " tbl_name text,\n"
- " rootpage integer,\n"
- " sql text\n"
- ")", zName);
- shell_check_oom(new_argv[0]);
- new_argv[1] = 0;
- new_colv[0] = "sql";
- new_colv[1] = 0;
- callback(&data, 1, new_argv, new_colv);
- sqlite3_free(new_argv[0]);
- }
- }
- if( zDiv ){
- sqlite3_stmt *pStmt = 0;
- rc = sqlite3_prepare_v2(p->db, "SELECT name FROM pragma_database_list",
- -1, &pStmt, 0);
- if( rc ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
- sqlite3_finalize(pStmt);
- rc = 1;
- goto meta_command_exit;
- }
- appendText(&sSelect, "SELECT sql FROM", 0);
- iSchema = 0;
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
- const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
- char zScNum[30];
- sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
- appendText(&sSelect, zDiv, 0);
- zDiv = " UNION ALL ";
- appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
- if( sqlite3_stricmp(zDb, "main")!=0 ){
- appendText(&sSelect, zDb, '\'');
- }else{
- appendText(&sSelect, "NULL", 0);
- }
- appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
- appendText(&sSelect, zScNum, 0);
- appendText(&sSelect, " AS snum, ", 0);
+ appendText(&sSelect, "SELECT sql FROM", 0);
+ iSchema = 0;
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zDb = (const char*)sqlite3_column_text(pStmt, 0);
+ char zScNum[30];
+ sqlite3_snprintf(sizeof(zScNum), zScNum, "%d", ++iSchema);
+ appendText(&sSelect, zDiv, 0);
+ zDiv = " UNION ALL ";
+ appendText(&sSelect, "SELECT shell_add_schema(sql,", 0);
+ if( sqlite3_stricmp(zDb, "main")!=0 ){
appendText(&sSelect, zDb, '\'');
- appendText(&sSelect, " AS sname FROM ", 0);
- appendText(&sSelect, zDb, quoteChar(zDb));
- appendText(&sSelect, ".sqlite_schema", 0);
+ }else{
+ appendText(&sSelect, "NULL", 0);
}
- sqlite3_finalize(pStmt);
+ appendText(&sSelect, ",name) AS sql, type, tbl_name, name, rowid,", 0);
+ appendText(&sSelect, zScNum, 0);
+ appendText(&sSelect, " AS snum, ", 0);
+ appendText(&sSelect, zDb, '\'');
+ appendText(&sSelect, " AS sname FROM ", 0);
+ appendText(&sSelect, zDb, quoteChar(zDb));
+ appendText(&sSelect, ".sqlite_schema", 0);
+ }
+ sqlite3_finalize(pStmt);
#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS
- if( zName ){
- appendText(&sSelect,
- " UNION ALL SELECT shell_module_schema(name),"
- " 'table', name, name, name, 9e+99, 'main'"
- " FROM pragma_module_list", 0);
- }
-#endif
- appendText(&sSelect, ") WHERE ", 0);
- if( zName ){
- char *zQarg = sqlite3_mprintf("%Q", zName);
- int bGlob;
- shell_check_oom(zQarg);
- bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0 ||
- strchr(zName, '[') != 0;
- if( strchr(zName, '.') ){
- appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
- }else{
- appendText(&sSelect, "lower(tbl_name)", 0);
- }
- appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
- appendText(&sSelect, zQarg, 0);
- if( !bGlob ){
- appendText(&sSelect, " ESCAPE '\\' ", 0);
- }
- appendText(&sSelect, " AND ", 0);
- sqlite3_free(zQarg);
- }
- if( bNoSystemTabs ){
- appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
- }
- appendText(&sSelect, "sql IS NOT NULL"
- " ORDER BY snum, rowid", 0);
- if( bDebug ){
- utf8_printf(p->out, "SQL: %s;\n", sSelect.z);
+ if( zName ){
+ appendText(&sSelect,
+ " UNION ALL SELECT shell_module_schema(name),"
- " 'table', name, name, name, 9e+99, 'main' FROM pragma_module_list",
++ " 'table', name, name, name, 9e+99, 'main'"
++ " FROM pragma_module_list",
+ 0);
+ }
+#endif
+ appendText(&sSelect, ") WHERE ", 0);
+ if( zName ){
+ char *zQarg = sqlite3_mprintf("%Q", zName);
+ int bGlob;
+ shell_check_oom(zQarg);
+ bGlob = strchr(zName, '*') != 0 || strchr(zName, '?') != 0
+ || strchr(zName, '[') != 0;
+ if( strchr(zName, '.') ){
+ appendText(&sSelect, "lower(printf('%s.%s',sname,tbl_name))", 0);
}else{
- rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
+ appendText(&sSelect, "lower(tbl_name)", 0);
+ }
+ appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
+ appendText(&sSelect, zQarg, 0);
+ if( !bGlob ){
+ appendText(&sSelect, " ESCAPE '\\' ", 0);
}
- freeText(&sSelect);
+ appendText(&sSelect, " AND ", 0);
+ sqlite3_free(zQarg);
}
- if( zErrMsg ){
- utf8_printf(stderr,"Error: %s\n", zErrMsg);
- sqlite3_free(zErrMsg);
- rc = 1;
- }else if( rc != SQLITE_OK ){
- raw_printf(stderr,"Error: querying schema information\n");
- rc = 1;
+ if( bNoSystemTabs ){
+ appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
+ }
+ appendText(&sSelect, "sql IS NOT NULL"
+ " ORDER BY snum, rowid", 0);
+ if( bDebug ){
+ utf8_printf(p->out, "SQL: %s;\n", sSelect.z);
}else{
- rc = 0;
+ rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
}
- }else
+ freeText(&sSelect);
+ }
+ if( zErrMsg ){
+ *pzErr = zErrMsg;
+ rc = 1;
+ }else if( rc != SQLITE_OK ){
+ *pzErr = shellMPrintf(0,"Error: querying schema information\n");
+ rc = 1;
+ }else{
+ rc = 0;
+ }
+ return rc;
+}
- if( c=='s' && n==7 && strncmp(azArg[0], "seeargs", n)==0 ){
- for( n=1; n<nArg; ++n ){
- raw_printf(p->out, "%s%s", azArg[n], (n==nArg-1)? "|\n" : "|");
+/*****************
+ * The .selecttrace, .separator, .session and .sha3sum commands
+ */
+CONDITION_COMMAND( session defined(SQLITE_ENABLE_SESSION) );
+COLLECT_HELP_TEXT[
+ ".separator COL ?ROW? Change the column and row separators",
+ ".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.",
+ ".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",
+];
+DISPATCHABLE_COMMAND( selecttrace ? 1 0 ){
+ unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
+ sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
+ return 0;
+}
+DISPATCHABLE_COMMAND( separator ? 2 3 ){
+ if( nArg>=2 ){
+ sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
+ "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
+ }
+ if( nArg>=3 ){
+ sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
+ "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
+ }
+ return 0;
+}
+DISPATCHABLE_COMMAND( session 3 2 0 ){
+ int rc = 0;
+ struct AuxDb *pAuxDb = p->pAuxDb;
+ OpenSession *pSession = &pAuxDb->aSession[0];
+ char **azCmd = &azArg[1];
+ int iSes = 0;
+ int nCmd = nArg - 1;
+ int i;
+ open_db(p, 0);
+ if( nArg>=3 ){
+ for(iSes=0; iSes<pAuxDb->nSession; iSes++){
+ if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
+ }
+ if( iSes<pAuxDb->nSession ){
+ pSession = &pAuxDb->aSession[iSes];
+ azCmd++;
+ nCmd--;
+ }else{
+ pSession = &pAuxDb->aSession[0];
+ iSes = 0;
}
- }else
-
- if( c=='s' && n==11 && strncmp(azArg[0], "selecttrace", n)==0 ){
- unsigned int x
- = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
- sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
- }else
+ }
-#if defined(SQLITE_ENABLE_SESSION)
- if( c=='s' && strncmp(azArg[0],"session",n)==0 && n>=3 ){
- struct AuxDb *pAuxDb = p->pAuxDb;
- OpenSession *pSession = &pAuxDb->aSession[0];
- char **azCmd = &azArg[1];
- int iSes = 0;
- int nCmd = nArg - 1;
- int i;
- if( nArg<=1 ) goto session_syntax_error;
- open_db(p, 0);
- if( nArg>=3 ){
- for(iSes=0; iSes<pAuxDb->nSession; iSes++){
- if( strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
- }
- if( iSes<pAuxDb->nSession ){
- pSession = &pAuxDb->aSession[iSes];
- azCmd++;
- nCmd--;
- }else{
- pSession = &pAuxDb->aSession[0];
- iSes = 0;
+ /* .session attach TABLE
+ ** Invoke the sqlite3session_attach() interface to attach a particular
+ ** table so that it is never filtered.
+ */
+ if( strcmp(azCmd[0],"attach")==0 ){
+ if( nCmd!=2 ) goto session_syntax_error;
+ if( pSession->p==0 ){
+ session_not_open:
+ raw_printf(STD_ERR, "ERROR: No sessions are open\n");
+ }else{
+ rc = sqlite3session_attach(pSession->p, azCmd[1]);
+ if( rc ){
+ raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
+ rc = 0;
}
}
+ }else
- /* .session attach TABLE
- ** Invoke the sqlite3session_attach() interface to attach a particular
- ** table so that it is never filtered.
- */
- if( strcmp(azCmd[0],"attach")==0 ){
- if( nCmd!=2 ) goto session_syntax_error;
- if( pSession->p==0 ){
- session_not_open:
- raw_printf(stderr, "ERROR: No sessions are open\n");
- }else{
- rc = sqlite3session_attach(pSession->p, azCmd[1]);
- if( rc ){
- raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
- rc = 0;
- }
- }
- }else
-
-- /* .session changeset FILE
-- ** .session patchset FILE
-- ** Write a changeset or patchset into a file. The file is overwritten.
-- */
-- if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
-- FILE *out = 0;
- if( failIfSafeMode
- (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
- rc = SHELL_FORBIDDEN_OP;
- failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]);
++ /* .session changeset FILE
++ ** .session patchset FILE
++ ** Write a changeset or patchset into a file. The file is overwritten.
++ */
++ if( strcmp(azCmd[0],"changeset")==0 || strcmp(azCmd[0],"patchset")==0 ){
++ FILE *out = 0;
++ if( failIfSafeMode
++ (p, "cannot run \".session %s\" in safe mode", azCmd[0]) ){
++ rc = SHELL_FORBIDDEN_OP;
++ }else{
+ if( nCmd!=2 ) goto session_syntax_error;
+ if( pSession->p==0 ) goto session_not_open;
+ out = fopen(azCmd[1], "wb");
+ if( out==0 ){
- utf8_printf(stderr, "ERROR: cannot open \"%s\" for writing\n",
- azCmd[1]);
++ *pzErr = sqlite3_mprintf
++ ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
++ rc = 1;
}else{
- if( nCmd!=2 ) goto session_syntax_error;
- if( pSession->p==0 ) goto session_not_open;
- out = fopen(azCmd[1], "wb");
- if( out==0 ){
- *pzErr = sqlite3_mprintf
- ("ERROR: cannot open \"%s\" for writing\n", azCmd[1]);
- rc = 1;
+ int szChng;
+ void *pChng;
+ if( azCmd[0][0]=='c' ){
+ rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
}else{
- int szChng;
- void *pChng;
- if( azCmd[0][0]=='c' ){
- rc = sqlite3session_changeset(pSession->p, &szChng, &pChng);
- }else{
- rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
- }
- if( rc ){
- fprintf(STD_OUT, "Error: error code %d\n", rc);
- rc = 0;
- }
- if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){
- raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
- szChng);
- }
- sqlite3_free(pChng);
- fclose(out);
+ rc = sqlite3session_patchset(pSession->p, &szChng, &pChng);
+ }
+ if( rc ){
- printf("Error: error code %d\n", rc);
++ fprintf(STD_OUT, "Error: error code %d\n", rc);
+ rc = 0;
}
- if( pChng
- && fwrite(pChng, szChng, 1, out)!=1 ){
- raw_printf(stderr, "ERROR: Failed to write entire %d-byte output\n",
- szChng);
++ if( pChng && fwrite(pChng, szChng, 1, out)!=1 ){
++ raw_printf(STD_ERR, "ERROR: Failed to write entire %d-byte output\n",
++ szChng);
+ }
+ sqlite3_free(pChng);
+ fclose(out);
}
- }else
-
- /* .session close
- ** Close the identified session
- */
- if( strcmp(azCmd[0], "close")==0 ){
- if( nCmd!=1 ) goto session_syntax_error;
- if( pAuxDb->nSession ){
- session_close(pSession);
- pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
- }
-- }else
++ }
++ }else
- /* .session close
- ** Close the identified session
- */
- if( strcmp(azCmd[0], "close")==0 ){
- if( nCmd!=1 ) goto session_syntax_error;
- if( pAuxDb->nSession ){
- session_close(pSession);
- pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
- }
- }else
- /* .session enable ?BOOLEAN?
- ** Query or set the enable flag
- */
- if( strcmp(azCmd[0], "enable")==0 ){
- int ii;
- if( nCmd>2 ) goto session_syntax_error;
- ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
- if( pAuxDb->nSession ){
- ii = sqlite3session_enable(pSession->p, ii);
- utf8_printf(p->out, "session %s enable flag = %d\n",
- pSession->zName, ii);
- }
- }else
++ /* .session close
++ ** Close the identified session
++ */
++ if( strcmp(azCmd[0], "close")==0 ){
++ if( nCmd!=1 ) goto session_syntax_error;
++ if( pAuxDb->nSession ){
++ session_close(pSession);
++ pAuxDb->aSession[iSes] = pAuxDb->aSession[--pAuxDb->nSession];
++ }
++ }else
- /* .session enable ?BOOLEAN?
- ** Query or set the enable flag
- */
- if( strcmp(azCmd[0], "enable")==0 ){
- int ii;
- if( nCmd>2 ) goto session_syntax_error;
- ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
- if( pAuxDb->nSession ){
- ii = sqlite3session_enable(pSession->p, ii);
- utf8_printf(p->out, "session %s enable flag = %d\n",
- pSession->zName, ii);
- }
- }else
- /* .session filter GLOB ....
- ** Set a list of GLOB patterns of table names to be excluded.
- */
- if( strcmp(azCmd[0], "filter")==0 ){
- int ii, nByte;
- if( nCmd<2 ) goto session_syntax_error;
- if( pAuxDb->nSession ){
- for(ii=0; ii<pSession->nFilter; ii++){
- sqlite3_free(pSession->azFilter[ii]);
- }
- sqlite3_free(pSession->azFilter);
- nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
- pSession->azFilter = sqlite3_malloc( nByte );
- if( pSession->azFilter==0 ){
- raw_printf(stderr, "Error: out or memory\n");
- exit(1);
- }
- for(ii=1; ii<nCmd; ii++){
- char *x = pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
- shell_check_oom(x);
- }
- pSession->nFilter = ii-1;
- }
- }else
++ /* .session enable ?BOOLEAN?
++ ** Query or set the enable flag
++ */
++ if( strcmp(azCmd[0], "enable")==0 ){
++ int ii;
++ if( nCmd>2 ) goto session_syntax_error;
++ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
++ if( pAuxDb->nSession ){
++ ii = sqlite3session_enable(pSession->p, ii);
++ utf8_printf(p->out, "session %s enable flag = %d\n",
++ pSession->zName, ii);
++ }
++ }else
- /* .session filter GLOB ....
- ** Set a list of GLOB patterns of table names to be excluded.
- */
- if( strcmp(azCmd[0], "filter")==0 ){
- int ii, nByte;
- if( nCmd<2 ) goto session_syntax_error;
- if( pAuxDb->nSession ){
- for(ii=0; ii<pSession->nFilter; ii++){
- sqlite3_free(pSession->azFilter[ii]);
- }
- sqlite3_free(pSession->azFilter);
- nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
- pSession->azFilter = sqlite3_malloc( nByte );
- if( pSession->azFilter==0 ){
- shell_out_of_memory();
- }
- for(ii=1; ii<nCmd; ii++){
- pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
- shell_check_oom(pSession->azFilter[ii-1]);
- }
- pSession->nFilter = ii-1;
- }
- }else
- /* .session indirect ?BOOLEAN?
- ** Query or set the indirect flag
- */
- if( strcmp(azCmd[0], "indirect")==0 ){
- int ii;
- if( nCmd>2 ) goto session_syntax_error;
- ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
- if( pAuxDb->nSession ){
- ii = sqlite3session_indirect(pSession->p, ii);
- utf8_printf(p->out, "session %s indirect flag = %d\n",
- pSession->zName, ii);
++ /* .session filter GLOB ....
++ ** Set a list of GLOB patterns of table names to be excluded.
++ */
++ if( strcmp(azCmd[0], "filter")==0 ){
++ int ii, nByte;
++ if( nCmd<2 ) goto session_syntax_error;
++ if( pAuxDb->nSession ){
++ for(ii=0; ii<pSession->nFilter; ii++){
++ sqlite3_free(pSession->azFilter[ii]);
+ }
- }else
-
- /* .session isempty
- ** Determine if the session is empty
- */
- if( strcmp(azCmd[0], "isempty")==0 ){
- int ii;
- if( nCmd!=1 ) goto session_syntax_error;
- if( pAuxDb->nSession ){
- ii = sqlite3session_isempty(pSession->p);
- utf8_printf(p->out, "session %s isempty flag = %d\n",
- pSession->zName, ii);
++ sqlite3_free(pSession->azFilter);
++ nByte = sizeof(pSession->azFilter[0])*(nCmd-1);
++ pSession->azFilter = sqlite3_malloc( nByte );
++ if( pSession->azFilter==0 ){
++ shell_out_of_memory();
+ }
- }else
-
- /* .session list
- ** List all currently open sessions
- */
- if( strcmp(azCmd[0],"list")==0 ){
- for(i=0; i<pAuxDb->nSession; i++){
- utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
++ for(ii=1; ii<nCmd; ii++){
++ pSession->azFilter[ii-1] = sqlite3_mprintf("%s", azCmd[ii]);
++ shell_check_oom(pSession->azFilter[ii-1]);
+ }
- }else
++ pSession->nFilter = ii-1;
++ }
++ }else
- /* .session indirect ?BOOLEAN?
- ** Query or set the indirect flag
- */
- if( strcmp(azCmd[0], "indirect")==0 ){
- int ii;
- if( nCmd>2 ) goto session_syntax_error;
- ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
- if( pAuxDb->nSession ){
- ii = sqlite3session_indirect(pSession->p, ii);
- utf8_printf(p->out, "session %s indirect flag = %d\n",
- pSession->zName, ii);
- }
- }else
-
- /* .session isempty
- ** Determine if the session is empty
- */
- if( strcmp(azCmd[0], "isempty")==0 ){
- int ii;
- if( nCmd!=1 ) goto session_syntax_error;
- if( pAuxDb->nSession ){
- ii = sqlite3session_isempty(pSession->p);
- utf8_printf(p->out, "session %s isempty flag = %d\n",
- pSession->zName, ii);
- }
- }else
-
- /* .session list
- ** List all currently open sessions
- */
- if( strcmp(azCmd[0],"list")==0 ){
- for(i=0; i<pAuxDb->nSession; i++){
- utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
- }
- }else
-
- /* .session open DB NAME
- ** Open a new session called NAME on the attached database DB.
- ** DB is normally "main".
- */
- if( strcmp(azCmd[0],"open")==0 ){
- char *zName;
- if( nCmd!=3 ) goto session_syntax_error;
- zName = azCmd[2];
- if( zName[0]==0 ) goto session_syntax_error;
- for(i=0; i<pAuxDb->nSession; i++){
- if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
- utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
- return rc;
- }
- }
- if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
- raw_printf
- (STD_ERR, "Maximum of %d sessions\n",
- ArraySize(pAuxDb->aSession));
- return rc;
- }
- pSession = &pAuxDb->aSession[pAuxDb->nSession];
- rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
- if( rc ){
- *pzErr = sqlite3_mprintf
- ("Cannot open session: error code=%d\n", rc);
- return rc;
- }
- pSession->nFilter = 0;
- sqlite3session_table_filter(pSession->p, session_filter,
- pSession);
- pAuxDb->nSession++;
- shell_newstr_assign(&pSession->zName,
- sqlite3_mprintf("%s", zName));
- }else{
- /* If no command name matches, show a syntax error */
- session_syntax_error:
- showHelp(p->out, "session");
- return 1;
- }
- /* .session open DB NAME
- ** Open a new session called NAME on the attached database DB.
- ** DB is normally "main".
- */
- if( strcmp(azCmd[0],"open")==0 ){
- char *zName;
- if( nCmd!=3 ) goto session_syntax_error;
- zName = azCmd[2];
- if( zName[0]==0 ) goto session_syntax_error;
- for(i=0; i<pAuxDb->nSession; i++){
- if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
- utf8_printf(stderr, "Session \"%s\" already exists\n", zName);
- goto meta_command_exit;
- }
- }
- if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
- raw_printf(stderr, "Maximum of %d sessions\n",
- ArraySize(pAuxDb->aSession));
- goto meta_command_exit;
- }
- pSession = &pAuxDb->aSession[pAuxDb->nSession];
- rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
- if( rc ){
- raw_printf(stderr, "Cannot open session: error code=%d\n", rc);
- rc = 0;
- goto meta_command_exit;
- }
- pSession->nFilter = 0;
- sqlite3session_table_filter(pSession->p, session_filter, pSession);
- pAuxDb->nSession++;
- pSession->zName = sqlite3_mprintf("%s", zName);
- shell_check_oom(pSession->zName);
- }else
- /* If no command name matches, show a syntax error */
- session_syntax_error:
- showHelp(p->out, "session");
++ /* .session indirect ?BOOLEAN?
++ ** Query or set the indirect flag
++ */
++ if( strcmp(azCmd[0], "indirect")==0 ){
++ int ii;
++ if( nCmd>2 ) goto session_syntax_error;
++ ii = nCmd==1 ? -1 : booleanValue(azCmd[1]);
++ if( pAuxDb->nSession ){
++ ii = sqlite3session_indirect(pSession->p, ii);
++ utf8_printf(p->out, "session %s indirect flag = %d\n",
++ pSession->zName, ii);
++ }
+ }else
-#endif
+
-#ifdef SQLITE_DEBUG
- /* Undocumented commands for internal testing. Subject to change
- ** without notice. */
- if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
- if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
- int i, v;
- for(i=1; i<nArg; i++){
- v = booleanValue(azArg[i]);
- utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
- }
++ /* .session isempty
++ ** Determine if the session is empty
++ */
++ if( strcmp(azCmd[0], "isempty")==0 ){
++ int ii;
++ if( nCmd!=1 ) goto session_syntax_error;
++ if( pAuxDb->nSession ){
++ ii = sqlite3session_isempty(pSession->p);
++ utf8_printf(p->out, "session %s isempty flag = %d\n",
++ pSession->zName, ii);
+ }
- if( strncmp(azArg[0]+9, "integer", n-9)==0 ){
- int i; sqlite3_int64 v;
- for(i=1; i<nArg; i++){
- char zBuf[200];
- v = integerValue(azArg[i]);
- sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
- utf8_printf(p->out, "%s", zBuf);
- }
++ }else
++
++ /* .session list
++ ** List all currently open sessions
++ */
++ if( strcmp(azCmd[0],"list")==0 ){
++ for(i=0; i<pAuxDb->nSession; i++){
++ utf8_printf(p->out, "%d %s\n", i, pAuxDb->aSession[i].zName);
+ }
+ }else
-#endif
+
- if( c=='s' && n>=4 && strncmp(azArg[0],"selftest",n)==0 ){
- int bIsInit = 0; /* True to initialize the SELFTEST table */
- int bVerbose = 0; /* Verbose output */
- int bSelftestExists; /* True if SELFTEST already exists */
- int i, k; /* Loop counters */
- int nTest = 0; /* Number of tests runs */
- int nErr = 0; /* Number of errors seen */
- ShellText str; /* Answer for a query */
- sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
++ /* .session open DB NAME
++ ** Open a new session called NAME on the attached database DB.
++ ** DB is normally "main".
++ */
++ if( strcmp(azCmd[0],"open")==0 ){
++ char *zName;
++ if( nCmd!=3 ) goto session_syntax_error;
++ zName = azCmd[2];
++ if( zName[0]==0 ) goto session_syntax_error;
++ for(i=0; i<pAuxDb->nSession; i++){
++ if( strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
++ utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
++ return rc;
++ }
++ }
++ if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
++ raw_printf
++ (STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
++ return rc;
++ }
++ pSession = &pAuxDb->aSession[pAuxDb->nSession];
++ rc = sqlite3session_create(p->db, azCmd[1], &pSession->p);
++ if( rc ){
++ *pzErr = sqlite3_mprintf
++ ("Cannot open session: error code=%d\n", rc);
++ return rc;
++ }
++ pSession->nFilter = 0;
++ sqlite3session_table_filter(pSession->p, session_filter, pSession);
++ pAuxDb->nSession++;
++ shell_newstr_assign(&pSession->zName, sqlite3_mprintf("%s", zName));
++ }else{
+
- open_db(p,0);
- for(i=1; i<nArg; i++){
- const char *z = azArg[i];
- if( z[0]=='-' && z[1]=='-' ) z++;
- if( strcmp(z,"-init")==0 ){
- bIsInit = 1;
++ /* If no command name matches, show a syntax error */
++ session_syntax_error:
++ showHelp(p->out, "session");
++ return 1;
++ }
+ return rc;
+}
+DISPATCHABLE_COMMAND( sha3sum 4 1 1 ){
+ const char *zLike = 0; /* Which table to checksum. 0 means everything */
+ int i; /* Loop counter */
+ int bSchema = 0; /* Also hash the schema */
+ int bSeparate = 0; /* Hash each table separately */
+ int iSize = 224; /* Hash algorithm to use */
+ int bDebug = 0; /* Only show the query that would have run */
+ sqlite3_stmt *pStmt; /* For querying tables names */
+ char *zSql; /* SQL to be run */
+ char *zSep; /* Separator */
+ ShellText sSql; /* Complete SQL for the query to run the hash */
+ ShellText sQuery; /* Set of queries used to read all content */
+ open_db(p, 0);
+ for(i=1; i<nArg; i++){
+ const char *z = azArg[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( strcmp(z,"schema")==0 ){
+ bSchema = 1;
}else
- * The .selftest, .shell, .show, .stats and .system commands
+ if( strcmp(z,"sha3-224")==0 || strcmp(z,"sha3-256")==0
+ || strcmp(z,"sha3-384")==0 || strcmp(z,"sha3-512")==0
+ ){
+ iSize = atoi(&z[5]);
+ }else
+ if( strcmp(z,"debug")==0 ){
+ bDebug = 1;
+ }else
+ {
+ *pzErr = sqlite3_mprintf
+ ("Unknown option \"%s\" on \"%s\"\n", azArg[i], azArg[0]);
+ return SHELL_INVALID_ARGS;
+ }
+ }else if( zLike ){
+ return SHELL_INVALID_ARGS;
+ }else{
+ zLike = z;
+ bSeparate = 1;
+ if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
+ }
+ }
+ if( bSchema ){
+ zSql = "SELECT lower(name) FROM sqlite_schema"
+ " WHERE type='table' AND coalesce(rootpage,0)>1"
+ " UNION ALL SELECT 'sqlite_schema'"
+ " ORDER BY 1 collate nocase";
+ }else{
+ zSql = "SELECT lower(name) FROM sqlite_schema"
+ " WHERE type='table' AND coalesce(rootpage,0)>1"
+ " AND name NOT LIKE 'sqlite_%'"
+ " ORDER BY 1 collate nocase";
+ }
+ sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
+ initText(&sQuery);
+ initText(&sSql);
+ appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
+ zSep = "VALUES(";
+ while( SQLITE_ROW==sqlite3_step(pStmt) ){
+ const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
+ if( zTab==0 ) continue;
+ if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
+ if( strncmp(zTab, "sqlite_",7)!=0 ){
+ appendText(&sQuery,"SELECT * FROM ", 0);
+ appendText(&sQuery,zTab,'"');
+ appendText(&sQuery," NOT INDEXED;", 0);
+ }else if( strcmp(zTab, "sqlite_schema")==0 ){
+ appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
+ " ORDER BY name;", 0);
+ }else if( strcmp(zTab, "sqlite_sequence")==0 ){
+ appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
+ " ORDER BY name;", 0);
+ }else if( strcmp(zTab, "sqlite_stat1")==0 ){
+ appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
+ " ORDER BY tbl,idx;", 0);
+ }else if( strcmp(zTab, "sqlite_stat4")==0 ){
+ appendText(&sQuery, "SELECT * FROM ", 0);
+ appendText(&sQuery, zTab, 0);
+ appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
+ }
+ appendText(&sSql, zSep, 0);
+ appendText(&sSql, sQuery.z, '\'');
+ sQuery.n = 0;
+ appendText(&sSql, ",", 0);
+ appendText(&sSql, zTab, '\'');
+ zSep = "),(";
+ }
+ sqlite3_finalize(pStmt);
+ if( bSeparate ){
+ zSql = sqlite3_mprintf(
+ "%s))"
+ " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
+ " FROM [sha3sum$query]",
+ sSql.z, iSize);
+ }else{
+ zSql = sqlite3_mprintf(
+ "%s))"
+ " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
+ " FROM [sha3sum$query]",
+ sSql.z, iSize);
+ }
+ shell_check_oom(zSql);
+ freeText(&sQuery);
+ freeText(&sSql);
+ if( bDebug ){
+ utf8_printf(p->out, "%s\n", zSql);
+ }else{
+ shell_exec(p, zSql, 0);
+ }
+ sqlite3_free(zSql);
+ return 0;
+}
+
+/*****************
++ * The .selftest, .shell, .show, .shxopts, .stats and .system commands
+ */
+CONDITION_COMMAND( shell !defined(SQLITE_NOHAVE_SYSTEM) );
++CONDITION_COMMAND( shxopts (SHELL_EXTENSIONS)!=0 );
+CONDITION_COMMAND( system !defined(SQLITE_NOHAVE_SYSTEM) );
+COLLECT_HELP_TEXT[
+ ".selftest ?OPTIONS? Run tests defined in the SELFTEST table",
+ " Options:",
+ " --init Create a new SELFTEST table",
+ " -v Verbose output",
+ ".shell CMD ARGS... Run CMD ARGS... in a system shell",
+ ".show Show the current values for various settings",
++ ".shxopts ?SIGNED_OPTS? Show or alter shell extension options",
++ " Run without arguments to see their self-descriptive names",
+ ".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",
+ ".system CMD ARGS... Run CMD ARGS... in a system shell",
+];
+DISPATCHABLE_COMMAND( selftest 4 0 0 ){
+ int rc;
+ int bIsInit = 0; /* True to initialize the SELFTEST table */
+ int bVerbose = 0; /* Verbose output */
+ int bSelftestExists; /* True if SELFTEST already exists */
+ int i, k; /* Loop counters */
+ int nTest = 0; /* Number of tests runs */
+ int nErr = 0; /* Number of errors seen */
+ ShellText str; /* Answer for a query */
+ sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
+
+ open_db(p,0);
+ for(i=1; i<nArg; i++){
+ const char *z = azArg[i];
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ if( strcmp(z,"-init")==0 ){
+ bIsInit = 1;
+ }else
if( strcmp(z,"-v")==0 ){
bVerbose++;
}else
- {
- utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
- azArg[i], azArg[0]);
- raw_printf(stderr, "Should be one of: --init -v\n");
- rc = 1;
- goto meta_command_exit;
- }
- }
- if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
- != SQLITE_OK ){
- bSelftestExists = 0;
- }else{
- bSelftestExists = 1;
- }
- if( bIsInit ){
- createSelftestTable(p);
- bSelftestExists = 1;
- }
- initText(&str);
- appendText(&str, "x", 0);
- for(k=bSelftestExists; k>=0; k--){
- if( k==1 ){
- rc = sqlite3_prepare_v2(p->db,
- "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
- -1, &pStmt, 0);
- }else{
- rc = sqlite3_prepare_v2(p->db,
- "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
- " (1,'run','PRAGMA integrity_check','ok')",
- -1, &pStmt, 0);
- }
- if( rc ){
- raw_printf(stderr, "Error querying the selftest table\n");
- rc = 1;
- sqlite3_finalize(pStmt);
- goto meta_command_exit;
- }
- for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
- int tno = sqlite3_column_int(pStmt, 0);
- const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
- const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
- const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
-
- if( zOp==0 ) continue;
- if( zSql==0 ) continue;
- if( zAns==0 ) continue;
- k = 0;
- if( bVerbose>0 ){
- printf("%d: %s %s\n", tno, zOp, zSql);
- }
- if( strcmp(zOp,"memo")==0 ){
- utf8_printf(p->out, "%s\n", zSql);
- }else
- if( strcmp(zOp,"run")==0 ){
- char *zErrMsg = 0;
- str.n = 0;
- str.z[0] = 0;
- rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
- nTest++;
- if( bVerbose ){
- utf8_printf(p->out, "Result: %s\n", str.z);
- }
- if( rc || zErrMsg ){
- nErr++;
- rc = 1;
- utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
- sqlite3_free(zErrMsg);
- }else if( strcmp(zAns,str.z)!=0 ){
- nErr++;
- rc = 1;
- utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
- utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z);
- }
- }else
{
- utf8_printf(stderr,
- "Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
- rc = 1;
- break;
+ *pzErr = sqlite3_mprintf
+ ("Unknown option \"%s\" on \"%s\"\n"
+ "Should be one of: --init -v\n", azArg[i], azArg[0]);
+ return 1;
}
- } /* End loop over rows of content from SELFTEST */
- sqlite3_finalize(pStmt);
- } /* End loop over k */
- freeText(&str);
- utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
- }else
-
- if( c=='s' && strncmp(azArg[0], "separator", n)==0 ){
- if( nArg<2 || nArg>3 ){
- raw_printf(stderr, "Usage: .separator COL ?ROW?\n");
- rc = 1;
- }
- if( nArg>=2 ){
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
- "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
+ }
+ if( sqlite3_table_column_metadata(p->db,"main","selftest",0,0,0,0,0,0)
+ != SQLITE_OK ){
+ bSelftestExists = 0;
+ }else{
+ bSelftestExists = 1;
+ }
+ if( bIsInit ){
+ createSelftestTable(p);
+ bSelftestExists = 1;
+ }
+ initText(&str);
+ appendText(&str, "x", 0);
+ for(k=bSelftestExists; k>=0; k--){
+ if( k==1 ){
+ rc = sqlite3_prepare_v2(p->db,
+ "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
+ -1, &pStmt, 0);
+ }else{
+ rc = sqlite3_prepare_v2(p->db,
+ "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
+ " (1,'run','PRAGMA integrity_check','ok')",
+ -1, &pStmt, 0);
}
- if( nArg>=3 ){
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
- "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
+ if( rc ){
+ *pzErr = shellMPrintf(0,"Error querying the selftest table\n");
+ sqlite3_finalize(pStmt);
+ return 1;
}
- }else
-
- if( c=='s' && n>=4 && strncmp(azArg[0],"sha3sum",n)==0 ){
- const char *zLike = 0; /* Which table to checksum. 0 means everything */
- int i; /* Loop counter */
- int bSchema = 0; /* Also hash the schema */
- int bSeparate = 0; /* Hash each table separately */
- int iSize = 224; /* Hash algorithm to use */
- int bDebug = 0; /* Only show the query that would have run */
- sqlite3_stmt *pStmt; /* For querying tables names */
- char *zSql; /* SQL to be run */
- char *zSep; /* Separator */
- ShellText sSql; /* Complete SQL for the query to run the hash */
- ShellText sQuery; /* Set of queries used to read all content */
- open_db(p, 0);
- for(i=1; i<nArg; i++){
- const char *z = azArg[i];
- if( z[0]=='-' ){
- z++;
- if( z[0]=='-' ) z++;
- if( strcmp(z,"schema")==0 ){
- bSchema = 1;
- }else
- if( strcmp(z,"sha3-224")==0 || strcmp(z,"sha3-256")==0
- || strcmp(z,"sha3-384")==0 || strcmp(z,"sha3-512")==0
- ){
- iSize = atoi(&z[5]);
- }else
- if( strcmp(z,"debug")==0 ){
- bDebug = 1;
- }else
- {
- utf8_printf(stderr, "Unknown option \"%s\" on \"%s\"\n",
- azArg[i], azArg[0]);
- showHelp(p->out, azArg[0]);
+ for(i=1; sqlite3_step(pStmt)==SQLITE_ROW; i++){
+ int tno = sqlite3_column_int(pStmt, 0);
+ const char *zOp = (const char*)sqlite3_column_text(pStmt, 1);
+ const char *zSql = (const char*)sqlite3_column_text(pStmt, 2);
+ const char *zAns = (const char*)sqlite3_column_text(pStmt, 3);
+
+ if( zOp==0 || zSql==0 || zAns==0 ) continue;
+ k = 0;
+ if( bVerbose>0 ){
+ fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql);
+ }
+ if( strcmp(zOp,"memo")==0 ){
+ utf8_printf(p->out, "%s\n", zSql);
- }else
- if( strcmp(zOp,"run")==0 ){
- char *zErrMsg = 0;
- str.n = 0;
- str.z[0] = 0;
- rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
- nTest++;
- if( bVerbose ){
- utf8_printf(p->out, "Result: %s\n", str.z);
- }
- if( rc || zErrMsg ){
- nErr++;
- rc = 1;
- utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
- sqlite3_free(zErrMsg);
- }else if( strcmp(zAns,str.z)!=0 ){
- nErr++;
- rc = 1;
- utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
- utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z);
- }
- }else
- {
- *pzErr = sqlite3_mprintf
- ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
- rc = 1;
- break;
- }
++ }else if( strcmp(zOp,"run")==0 ){
++ char *zErrMsg = 0;
++ str.n = 0;
++ str.z[0] = 0;
++ rc = sqlite3_exec(p->db, zSql, captureOutputCallback, &str, &zErrMsg);
++ nTest++;
++ if( bVerbose ){
++ utf8_printf(p->out, "Result: %s\n", str.z);
++ }
++ if( rc || zErrMsg ){
++ nErr++;
++ rc = 1;
++ utf8_printf(p->out, "%d: error-code-%d: %s\n", tno, rc, zErrMsg);
++ sqlite3_free(zErrMsg);
++ }else if( strcmp(zAns,str.z)!=0 ){
++ nErr++;
+ rc = 1;
- goto meta_command_exit;
++ utf8_printf(p->out, "%d: Expected: [%s]\n", tno, zAns);
++ utf8_printf(p->out, "%d: Got: [%s]\n", tno, str.z);
+ }
- }else if( zLike ){
- raw_printf(stderr, "Usage: .sha3sum ?OPTIONS? ?LIKE-PATTERN?\n");
- rc = 1;
- goto meta_command_exit;
+ }else{
- zLike = z;
- bSeparate = 1;
- if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
++ *pzErr = sqlite3_mprintf
++ ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
++ rc = 1;
++ break;
+ }
- }
- if( bSchema ){
- zSql = "SELECT lower(name) FROM sqlite_schema"
- " WHERE type='table' AND coalesce(rootpage,0)>1"
- " UNION ALL SELECT 'sqlite_schema'"
- " ORDER BY 1 collate nocase";
- }else{
- zSql = "SELECT lower(name) FROM sqlite_schema"
- " WHERE type='table' AND coalesce(rootpage,0)>1"
- " AND name NOT LIKE 'sqlite_%'"
- " ORDER BY 1 collate nocase";
- }
- sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- initText(&sQuery);
- initText(&sSql);
- appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
- zSep = "VALUES(";
- while( SQLITE_ROW==sqlite3_step(pStmt) ){
- const char *zTab = (const char*)sqlite3_column_text(pStmt,0);
- if( zTab==0 ) continue;
- if( zLike && sqlite3_strlike(zLike, zTab, 0)!=0 ) continue;
- if( strncmp(zTab, "sqlite_",7)!=0 ){
- appendText(&sQuery,"SELECT * FROM ", 0);
- appendText(&sQuery,zTab,'"');
- appendText(&sQuery," NOT INDEXED;", 0);
- }else if( strcmp(zTab, "sqlite_schema")==0 ){
- appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
- " ORDER BY name;", 0);
- }else if( strcmp(zTab, "sqlite_sequence")==0 ){
- appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
- " ORDER BY name;", 0);
- }else if( strcmp(zTab, "sqlite_stat1")==0 ){
- appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
- " ORDER BY tbl,idx;", 0);
- }else if( strcmp(zTab, "sqlite_stat4")==0 ){
- appendText(&sQuery, "SELECT * FROM ", 0);
- appendText(&sQuery, zTab, 0);
- appendText(&sQuery, " ORDER BY tbl, idx, rowid;\n", 0);
- }
- appendText(&sSql, zSep, 0);
- appendText(&sSql, sQuery.z, '\'');
- sQuery.n = 0;
- appendText(&sSql, ",", 0);
- appendText(&sSql, zTab, '\'');
- zSep = "),(";
- }
+ } /* End loop over rows of content from SELFTEST */
sqlite3_finalize(pStmt);
- if( bSeparate ){
- zSql = sqlite3_mprintf(
- "%s))"
- " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
- " FROM [sha3sum$query]",
- sSql.z, iSize);
- }else{
- zSql = sqlite3_mprintf(
- "%s))"
- " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
- " FROM [sha3sum$query]",
- sSql.z, iSize);
- }
- shell_check_oom(zSql);
- freeText(&sQuery);
- freeText(&sSql);
- if( bDebug ){
- utf8_printf(p->out, "%s\n", zSql);
- }else{
- shell_exec(p, zSql, 0);
- }
- sqlite3_free(zSql);
- }else
+ } /* End loop over k */
+ freeText(&str);
+ utf8_printf(p->out, "%d errors out of %d tests\n", nErr, nTest);
+ return rc > 0;
+}
#ifndef SQLITE_NOHAVE_SYSTEM
- if( c=='s'
- && (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0)
- ){
- char *zCmd;
- int i, x;
- failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
- if( nArg<2 ){
- raw_printf(stderr, "Usage: .system COMMAND\n");
- rc = 1;
- goto meta_command_exit;
- }
- zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
- for(i=2; i<nArg && zCmd!=0; i++){
- zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
- zCmd, azArg[i]);
- }
- x = zCmd!=0 ? system(zCmd) : 1;
- sqlite3_free(zCmd);
- if( x ) raw_printf(stderr, "System command returns %d\n", x);
- }else
-#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
-
- if( c=='s' && strncmp(azArg[0], "show", n)==0 ){
- static const char *azBool[] = { "off", "on", "trigger", "full"};
- const char *zOut;
- int i;
- if( nArg!=1 ){
- raw_printf(stderr, "Usage: .show\n");
- rc = 1;
- goto meta_command_exit;
- }
- utf8_printf(p->out, "%12.12s: %s\n","echo",
- azBool[ShellHasFlag(p, SHFLG_Echo)]);
- utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]);
- utf8_printf(p->out, "%12.12s: %s\n","explain",
- p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
- utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
- if( p->mode==MODE_Column
- || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
- ){
- utf8_printf
- (p->out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
- modeDescr[p->mode], p->cmOpts.iWrap,
- p->cmOpts.bWordWrap ? "on" : "off",
- p->cmOpts.bQuote ? "" : "no");
- }else{
- utf8_printf(p->out, "%12.12s: %s\n","mode", modeDescr[p->mode]);
- }
- utf8_printf(p->out, "%12.12s: ", "nullvalue");
- output_c_string(p->out, p->nullValue);
- raw_printf(p->out, "\n");
- utf8_printf(p->out,"%12.12s: %s\n","output",
- strlen30(p->outfile) ? p->outfile : "stdout");
- utf8_printf(p->out,"%12.12s: ", "colseparator");
- output_c_string(p->out, p->colSeparator);
- raw_printf(p->out, "\n");
- utf8_printf(p->out,"%12.12s: ", "rowseparator");
- output_c_string(p->out, p->rowSeparator);
- raw_printf(p->out, "\n");
- switch( p->statsOn ){
- case 0: zOut = "off"; break;
- default: zOut = "on"; break;
- case 2: zOut = "stmt"; break;
- case 3: zOut = "vmstep"; break;
- }
- utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
- utf8_printf(p->out, "%12.12s: ", "width");
- for (i=0;i<p->nWidth;i++) {
- raw_printf(p->out, "%d ", p->colWidth[i]);
- }
- raw_printf(p->out, "\n");
- utf8_printf(p->out, "%12.12s: %s\n", "filename",
- p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
- }else
-#if SHELL_EXTENSIONS
- if( c=='s' && strncmp(azArg[0], "shxopts", n)==0 ){
- rc = shxoptsCommand(azArg, nArg, p, 0);
- }else
+static int shellOut(char *azArg[], int nArg, ShellState *p, char **pzErr){
+ char *zCmd;
+ int i, x;
+ if( p->bSafeMode ) return SHELL_FORBIDDEN_OP;
+ zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
+ shell_check_oom(zCmd);
+ for(i=2; i<nArg; i++){
+ zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
+ zCmd, azArg[i]);
+ shell_check_oom(zCmd);
+ }
+ x = system(zCmd);
+ sqlite3_free(zCmd);
+ if( x ) raw_printf(STD_ERR, "%s command returns %d\n", azArg[0], x);
+ return 0;
+}
#endif
- if( c=='s' && strncmp(azArg[0], "stats", n)==0 ){
- if( nArg==2 ){
- if( strcmp(azArg[1],"stmt")==0 ){
- p->statsOn = 2;
- }else if( strcmp(azArg[1],"vmstep")==0 ){
- p->statsOn = 3;
- }else{
- p->statsOn = (u8)booleanValue(azArg[1]);
+DISPATCHABLE_COMMAND( shell ? 2 0 ){
+ return shellOut(azArg, nArg, p, pzErr);
+}
++DISPATCHABLE_COMMAND( shxopts 3 0 0 ){
++ static struct { const char *name; u8 mask; } shopts[] = {
++#if SHELL_DYNAMIC_COMMANDS
++ {"dyn_cmds", 1<<SHEXT_DYNCMDS_BIT},
++#endif
++#if SHELL_EXTENDED_PARSING
++ {"parsing", 1<<SHEXT_PARSING_BIT},
++#endif
++#if SHELL_VARIABLE_EXPANSION
++ {"dot_vars", 1<<SHEXT_VAREXP_BIT},
++#endif
++ {"all_opts", SHELL_ALL_EXTENSIONS}
++ };
++ const char *zMoan = 0, *zAbout = 0;
++ int ia, io;
++ if( nArg>1 ){
++ for( ia=1; ia<nArg; ++ia ){
++ char cs = azArg[ia][0];
++ if( cs!='+' && cs!='-' ){
++ zMoan = "arguments must have a sign prefix.";
++ zAbout = azArg[0];
++ goto moan_error;
+ }
- }else if( nArg==1 ){
- display_stats(p->db, p, 0);
++ for( io=0; io<ArraySize(shopts); ++io ){
++ if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
++ if( cs=='+' ) p->bExtendedDotCmds |= shopts[io].mask;
++ else p->bExtendedDotCmds &= ~shopts[io].mask;
++ break;
++ }
++ }
++ if( io==ArraySize(shopts) ){
++ zAbout = azArg[ia];
++ zMoan = "is not a recognized option name";
++ goto moan_error;
++ }
++ }
++ }else{
++ raw_printf(p->out,
++ " name value \"-shxopts set\"\n"
++ " -------- ----- ---------------\n");
++ for( io=0; io<ArraySize(shopts); ++io ){
++ unsigned m = shopts[io].mask;
++ unsigned v = ((p->bExtendedDotCmds & m) == m)? 1 : 0;
++ raw_printf(p->out,
++ " %9s %2d \"-shxopts 0x%02X\"\n",
++ shopts[io].name, v, m);
++ }
++ }
++ return 0;
++ moan_error:
++ raw_printf(STD_ERR, "Error: %s %s\n", zAbout, zMoan);
++ return 1;
++}
+DISPATCHABLE_COMMAND( system ? 2 0 ){
+ return shellOut(azArg, nArg, p, pzErr);
+}
+DISPATCHABLE_COMMAND( show ? 1 1 ){
+ static const char *azBool[] = { "off", "on", "trigger", "full"};
+ const char *zOut;
+ int i;
+ utf8_printf(p->out, "%12.12s: %s\n","echo",
+ azBool[ShellHasFlag(p, SHFLG_Echo)]);
+ utf8_printf(p->out, "%12.12s: %s\n","eqp", azBool[p->autoEQP&3]);
+ utf8_printf(p->out, "%12.12s: %s\n","explain",
+ p->mode==MODE_Explain ? "on" : p->autoExplain ? "auto" : "off");
+ utf8_printf(p->out,"%12.12s: %s\n","headers", azBool[p->showHeader!=0]);
+ zOut = modeDescr[p->mode].zModeName;
+ i = strlen30(zOut) - modeDescr[p->mode].bDepluralize;
- if( p->mode==MODE_Column
- || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
- ){
++ if( MODE_IS_COLUMNAR(p->mode) ){
+ utf8_printf
+ (p->out, "%12.12s: %.*s --wrap %d --wordwrap %s --%squote\n", "mode",
+ i, zOut, p->cmOpts.iWrap,
+ p->cmOpts.bWordWrap ? "on" : "off",
+ p->cmOpts.bQuote ? "" : "no");
+ }else{
+ utf8_printf(p->out, "%12.12s: %.*s\n","mode", i, zOut);
+ }
+ utf8_printf(p->out, "%12.12s: ", "nullvalue");
+ output_c_string(p->out, p->nullValue);
+ raw_printf(p->out, "\n");
+ utf8_printf(p->out,"%12.12s: %s\n","output",
+ strlen30(p->outfile) ? p->outfile : "stdout");
+ utf8_printf(p->out,"%12.12s: ", "colseparator");
+ output_c_string(p->out, p->colSeparator);
+ raw_printf(p->out, "\n");
+ utf8_printf(p->out,"%12.12s: ", "rowseparator");
+ output_c_string(p->out, p->rowSeparator);
+ raw_printf(p->out, "\n");
+ switch( p->statsOn ){
+ case 0: zOut = "off"; break;
+ default: zOut = "on"; break;
+ case 2: zOut = "stmt"; break;
+ case 3: zOut = "vmstep"; break;
+ }
+ utf8_printf(p->out, "%12.12s: %s\n","stats", zOut);
+ utf8_printf(p->out, "%12.12s: ", "width");
+ for (i=0;i<p->nWidth;i++) {
+ raw_printf(p->out, "%d ", p->colWidth[i]);
+ }
+ raw_printf(p->out, "\n");
+ utf8_printf(p->out, "%12.12s: %s\n", "filename",
+ p->pAuxDb->zDbFilename ? p->pAuxDb->zDbFilename : "");
+ return 0;
+}
+DISPATCHABLE_COMMAND( stats ? 0 0 ){
+ if( nArg==2 ){
+ if( strcmp(azArg[1],"stmt")==0 ){
+ p->statsOn = 2;
+ }else if( strcmp(azArg[1],"vmstep")==0 ){
+ p->statsOn = 3;
}else{
- raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
- rc = 1;
+ p->statsOn = (u8)booleanValue(azArg[1]);
}
- }else
+ }else if( nArg==1 ){
+ display_stats(p->db, p, 0);
+ }else{
+ *pzErr = shellMPrintf(0,"Usage: .stats ?on|off|stmt|vmstep?\n");
+ return 1;
+ }
+ return 0;
+}
- if( (c=='t' && n>1 && strncmp(azArg[0], "tables", n)==0)
- || (c=='i' && (strncmp(azArg[0], "indices", n)==0
- || strncmp(azArg[0], "indexes", n)==0) )
- ){
- sqlite3_stmt *pStmt;
- char **azResult;
- int nRow, nAlloc;
- int ii;
- ShellText s;
- initText(&s);
- open_db(p, 0);
- rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
- if( rc ){
- sqlite3_finalize(pStmt);
- return shellDatabaseError(p->db);
- }
+/*****************
+ * The .tables, .views, .indices and .indexes command
+ * These are together because they share implementation or are aliases.
+ */
+COLLECT_HELP_TEXT[
+ ".indexes ?TABLE? Show names of indexes",
+ " If TABLE is specified, only show indexes for",
+ " tables matching TABLE using the LIKE operator.",
+];
+static int showTableLike(char *azArg[], int nArg, ShellState *p,
+ char **pzErr, char ot){
+ int rc;
+ sqlite3_stmt *pStmt;
+ char **azResult;
+ int nRow, nAlloc;
+ int ii;
+ ShellText s;
+ initText(&s);
+ open_db(p, 0);
+ rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0);
+ if( rc ){
+ sqlite3_finalize(pStmt);
+ return shellDatabaseError(p->db);
+ }
- if( nArg>2 && c=='i' ){
- /* It is an historical accident that the .indexes command shows an error
- ** when called with the wrong number of arguments whereas the .tables
- ** command does not. */
- raw_printf(stderr, "Usage: .indexes ?LIKE-PATTERN?\n");
- rc = 1;
- sqlite3_finalize(pStmt);
- goto meta_command_exit;
- }
- for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
- const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
- if( zDbName==0 ) continue;
- if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
- if( sqlite3_stricmp(zDbName, "main")==0 ){
- appendText(&s, "SELECT name FROM ", 0);
- }else{
- appendText(&s, "SELECT ", 0);
- appendText(&s, zDbName, '\'');
- appendText(&s, "||'.'||name FROM ", 0);
- }
- appendText(&s, zDbName, '"');
- appendText(&s, ".sqlite_schema ", 0);
- if( c=='t' ){
- appendText(&s," WHERE type IN ('table','view')"
- " AND name NOT LIKE 'sqlite_%'"
- " AND name LIKE ?1", 0);
- }else{
- appendText(&s," WHERE type='index'"
- " AND tbl_name LIKE ?1", 0);
- }
- }
- rc = sqlite3_finalize(pStmt);
- if( rc==SQLITE_OK ){
- appendText(&s, " ORDER BY 1", 0);
- rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
- }
- freeText(&s);
- if( rc ) return shellDatabaseError(p->db);
-
- /* Run the SQL statement prepared by the above block. Store the results
- ** as an array of nul-terminated strings in azResult[]. */
- nRow = nAlloc = 0;
- azResult = 0;
- if( nArg>1 ){
- sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
+ if( nArg>2 && ot=='i' ){
+ /* It is an historical accident that the .indexes command shows an error
+ ** when called with the wrong number of arguments whereas the .tables
+ ** command does not. */
+ *pzErr = shellMPrintf(0,"Usage: .indexes ?LIKE-PATTERN?\n");
+ sqlite3_finalize(pStmt);
+ return 1;
+ }
+ for(ii=0; sqlite3_step(pStmt)==SQLITE_ROW; ii++){
+ const char *zDbName = (const char*)sqlite3_column_text(pStmt, 1);
+ const char *zFilter = "";
+ const char *zSystem = " AND name NOT LIKE 'sqlite_%'";
+ if( zDbName==0 ) continue;
+ if( s.z && s.z[0] ) appendText(&s, " UNION ALL ", 0);
+ if( sqlite3_stricmp(zDbName, "main")==0 ){
+ appendText(&s, "SELECT name FROM ", 0);
}else{
- sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
+ appendText(&s, "SELECT ", 0);
+ appendText(&s, zDbName, '\'');
+ appendText(&s, "||'.'||name FROM ", 0);
+ }
+ appendText(&s, zDbName, '"');
+ appendText(&s, ".sqlite_schema ", 0);
+ switch (ot) {
+ case 'i':
+ zFilter = "'index'";
+ break;
+#ifndef LEGACY_TABLES_LISTING
+ case 't':
+ zFilter = "'table'";
+ break;
+ case 'v':
+ zFilter = "'view'";
+ break;
+#endif
+ case 's':
+ zSystem = " AND name LIKE 'sqlite_%'";
+ /* fall thru */
+ case 'T':
+ zFilter = "'table','view'";
+ break;
+ default:
+ assert(0);
}
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
- if( nRow>=nAlloc ){
- char **azNew;
- int n2 = nAlloc*2 + 10;
- azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
- shell_check_oom(azNew);
- nAlloc = n2;
- azResult = azNew;
- }
- azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
- shell_check_oom(azResult[nRow]);
- nRow++;
- }
- if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
- rc = shellDatabaseError(p->db);
- }
-
- /* Pretty-print the contents of array azResult[] to the output */
- if( rc==0 && nRow>0 ){
- int len, maxlen = 0;
- int i, j;
- int nPrintCol, nPrintRow;
- for(i=0; i<nRow; i++){
- len = strlen30(azResult[i]);
- if( len>maxlen ) maxlen = len;
- }
- nPrintCol = 80/(maxlen+2);
- if( nPrintCol<1 ) nPrintCol = 1;
- nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
- for(i=0; i<nPrintRow; i++){
- for(j=i; j<nRow; j+=nPrintRow){
- char *zSp = j<nPrintRow ? "" : " ";
- utf8_printf(p->out, "%s%-*s", zSp, maxlen,
- azResult[j] ? azResult[j]:"");
- }
- raw_printf(p->out, "\n");
+ appendText(&s, " WHERE type IN(", 0);
+ appendText(&s, zFilter, 0);
+ appendText(&s, ") AND name LIKE ?1", 0);
+ appendText(&s, zSystem, 0);
+ }
+ rc = sqlite3_finalize(pStmt);
+ if( rc==SQLITE_OK ){
+ appendText(&s, " ORDER BY 1", 0);
+ rc = sqlite3_prepare_v2(p->db, s.z, -1, &pStmt, 0);
+ }
+ freeText(&s);
+ if( rc ) return shellDatabaseError(p->db);
+
+ /* Run the SQL statement prepared by the above block. Store the results
+ ** as an array of nul-terminated strings in azResult[]. */
+ nRow = nAlloc = 0;
+ azResult = 0;
+ if( nArg>1 ){
+ sqlite3_bind_text(pStmt, 1, azArg[1], -1, SQLITE_TRANSIENT);
+ }else{
+ sqlite3_bind_text(pStmt, 1, "%", -1, SQLITE_STATIC);
+ }
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ if( nRow>=nAlloc ){
+ char **azNew;
+ int n2 = nAlloc*2 + 10;
+ azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
+ shell_check_oom(azNew);
+ nAlloc = n2;
+ azResult = azNew;
+ }
+ azResult[nRow] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
+ shell_check_oom(azResult[nRow]);
+ nRow++;
+ }
+ if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
+ rc = shellDatabaseError(p->db);
+ }
+
+ /* Pretty-print the contents of array azResult[] to the output */
+ if( rc==0 && nRow>0 ){
+ int len, maxlen = 0;
+ int i, j;
+ int nPrintCol, nPrintRow;
+ for(i=0; i<nRow; i++){
+ len = strlen30(azResult[i]);
+ if( len>maxlen ) maxlen = len;
+ }
+ nPrintCol = 80/(maxlen+2);
+ if( nPrintCol<1 ) nPrintCol = 1;
+ nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
+ for(i=0; i<nPrintRow; i++){
+ for(j=i; j<nRow; j+=nPrintRow){
+ char *zSp = j<nPrintRow ? "" : " ";
+ utf8_printf(p->out, "%s%-*s", zSp, maxlen,
+ azResult[j] ? azResult[j]:"");
}
+ raw_printf(p->out, "\n");
}
+ }
- for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
- sqlite3_free(azResult);
- }else
+ for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
+ sqlite3_free(azResult);
+ return 0;
+}
- /* Begin redirecting output to the file "testcase-out.txt" */
- if( c=='t' && strcmp(azArg[0],"testcase")==0 ){
- output_reset(p);
- p->out = output_file_open("testcase-out.txt", 0);
- if( p->out==0 ){
- raw_printf(stderr, "Error: cannot open 'testcase-out.txt'\n");
+COLLECT_HELP_TEXT[
+#ifndef LEGACY_TABLES_LISTING
+ ".tables ?FLAG? ?TVLIKE? List names of tables and/or views",
+ " FLAG may be -t, -v or -s to list only tables, views or system tables",
+ " TVLIKE may restrict the listing to names matching given LIKE pattern",
+#else
+ ".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
+#endif
+];
+DISPATCHABLE_COMMAND( tables 2 1 3 ){
+ char objType = 'T';
+#ifndef LEGACY_TABLES_LISTING
+ if( nArg>1 && azArg[1][0]=='-' && azArg[1][1]!=0 && azArg[1][2]==0 ){
+ char c = azArg[1][1];
+ switch (c){
+ case 's':
+ case 't':
+ case 'v':
+ objType = c;
+ ++azArg;
+ --nArg;
+ break;
+ default:
+ return SHELL_INVALID_ARGS;
}
- if( nArg>=2 ){
- sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
- }else{
- sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
+ }
+#endif
+ return showTableLike(azArg, nArg, p, pzErr, objType);
+}
+DISPATCHABLE_COMMAND( indexes 3 1 2 ){
+ return showTableLike(azArg, nArg, p, pzErr, 'i');
+}
+DISPATCHABLE_COMMAND( indices 3 1 2 ){
+ return showTableLike(azArg, nArg, p, pzErr, 'i');
+}
+
+/*****************
+ * The .unmodule command
+ */
+CONDITION_COMMAND( unmodule defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE) );
+COLLECT_HELP_TEXT[
+ ".unmodule NAME ... Unregister virtual table modules",
+ " --allexcept Unregister everything except those named",
+];
+DISPATCHABLE_COMMAND( unmodule ? 2 0 ){
+ int ii;
+ int lenOpt;
+ char *zOpt;
+ open_db(p, 0);
+ zOpt = azArg[1];
+ if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
+ lenOpt = (int)strlen(zOpt);
+ if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){
+ assert( azArg[nArg]==0 );
+ sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0);
+ }else{
+ for(ii=1; ii<nArg; ii++){
+ sqlite3_create_module(p->db, azArg[ii], 0, 0);
}
- }else
+ }
+ return 0;
+}
+
+/*****************
+ * The .testcase, .testctrl, .timeout, .timer and .trace commands
+ */
+CONDITION_COMMAND( testctrl !defined(SQLITE_UNTESTABLE) );
+CONDITION_COMMAND( trace !defined(SQLITE_OMIT_TRACE) );
+COLLECT_HELP_TEXT[
+ ".testcase NAME Begin redirecting output to 'testcase-out.txt'",
+ ".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",
+ ".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",
+#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)",
+];
-#ifndef SQLITE_UNTESTABLE
- if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){
- static const struct {
- const char *zCtrlName; /* Name of a test-control option */
- int ctrlCode; /* Integer code for that option */
- int unSafe; /* Not valid for --safe mode */
- const char *zUsage; /* Usage notes */
- } aCtrl[] = {
- { "always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" },
- { "assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" },
- /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/
- /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/
- { "byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" },
- { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" },
- /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/
- { "imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
- { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" },
- { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" },
- { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" },
- { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" },
+/* Begin redirecting output to the file "testcase-out.txt" */
+DISPATCHABLE_COMMAND( testcase ? 0 0 ){
+ output_reset(p);
+ p->out = output_file_open("testcase-out.txt", 0);
+ if( p->out==0 ){
+ raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n");
+ }
+ if( nArg>=2 ){
+ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
+ }else{
+ sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
+ }
+}
+DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
+ static const struct {
+ const char *zCtrlName; /* Name of a test-control option */
+ int ctrlCode; /* Integer code for that option */
+ int unSafe; /* Not valid for --safe mode */
+ const char *zUsage; /* Usage notes */
+ } aCtrl[] = {
+ { "always", SQLITE_TESTCTRL_ALWAYS, 1, "BOOLEAN" },
+ { "assert", SQLITE_TESTCTRL_ASSERT, 1, "BOOLEAN" },
+ /*{ "benign_malloc_hooks",SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS,1, "" },*/
+ /*{ "bitvec_test", SQLITE_TESTCTRL_BITVEC_TEST, 1, "" },*/
+ { "byteorder", SQLITE_TESTCTRL_BYTEORDER, 0, "" },
+ { "extra_schema_checks",SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS,0,"BOOLEAN" },
+ /*{ "fault_install", SQLITE_TESTCTRL_FAULT_INSTALL, 1,"" },*/
+ { "imposter", SQLITE_TESTCTRL_IMPOSTER,1,"SCHEMA ON/OFF ROOTPAGE"},
+ { "internal_functions", SQLITE_TESTCTRL_INTERNAL_FUNCTIONS,0,"" },
+ { "localtime_fault", SQLITE_TESTCTRL_LOCALTIME_FAULT,0,"BOOLEAN" },
+ { "never_corrupt", SQLITE_TESTCTRL_NEVER_CORRUPT,1, "BOOLEAN" },
+ { "optimizations", SQLITE_TESTCTRL_OPTIMIZATIONS,0,"DISABLE-MASK" },
#ifdef YYCOVERAGE
- { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" },
-#endif
- { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " },
- { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" },
- { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" },
- { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" },
- { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" },
- { "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" },
- { "tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" },
- };
- int testctrl = -1;
- int iCtrl = -1;
- int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */
- int isOk = 0;
- int i, n2;
- const char *zCmd = 0;
+ { "parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" },
+#endif
+ { "pending_byte", SQLITE_TESTCTRL_PENDING_BYTE,0, "OFFSET " },
+ { "prng_restore", SQLITE_TESTCTRL_PRNG_RESTORE,0, "" },
+ { "prng_save", SQLITE_TESTCTRL_PRNG_SAVE, 0, "" },
+ { "prng_seed", SQLITE_TESTCTRL_PRNG_SEED, 0, "SEED ?db?" },
+ { "seek_count", SQLITE_TESTCTRL_SEEK_COUNT, 0, "" },
+ { "sorter_mmap", SQLITE_TESTCTRL_SORTER_MMAP, 0, "NMAX" },
+ { "tune", SQLITE_TESTCTRL_TUNE, 1, "ID VALUE" },
+ };
+ int testctrl = -1;
+ int iCtrl = -1;
+ int rc2 = 0; /* 0: usage. 1: %d 2: %x 3: no-output */
+ int isOk = 0;
+ int i, n2;
+ const char *zCmd = 0;
- open_db(p, 0);
- zCmd = nArg>=2 ? azArg[1] : "help";
+ open_db(p, 0);
+ zCmd = nArg>=2 ? azArg[1] : "help";
+
+ /* The argument can optionally begin with "-" or "--" */
+ if( zCmd[0]=='-' && zCmd[1] ){
+ zCmd++;
+ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+ }
- /* The argument can optionally begin with "-" or "--" */
- if( zCmd[0]=='-' && zCmd[1] ){
- zCmd++;
- if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+ /* --help lists all test-controls */
+ if( strcmp(zCmd,"help")==0 ){
+ utf8_printf(p->out, "Available test-controls:\n");
+ for(i=0; i<ArraySize(aCtrl); i++){
+ utf8_printf(p->out, " .testctrl %s %s\n",
+ aCtrl[i].zCtrlName, aCtrl[i].zUsage);
}
+ return 1;
+ }
- /* --help lists all test-controls */
- if( strcmp(zCmd,"help")==0 ){
- utf8_printf(p->out, "Available test-controls:\n");
- for(i=0; i<ArraySize(aCtrl); i++){
- utf8_printf(p->out, " .testctrl %s %s\n",
- aCtrl[i].zCtrlName, aCtrl[i].zUsage);
+ /* convert testctrl text option to value. allow any unique prefix
+ ** of the option name, or a numerical value. */
+ n2 = strlen30(zCmd);
+ for(i=0; i<ArraySize(aCtrl); i++){
+ if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
+ if( testctrl<0 ){
+ testctrl = aCtrl[i].ctrlCode;
+ iCtrl = i;
+ }else{
+ *pzErr = sqlite3_mprintf
+ ("Error: ambiguous test-control: \"%s\"\n"
+ "Use \".testctrl --help\" for help\n", zCmd);
+ return 1;
}
- rc = 1;
- goto meta_command_exit;
}
- p->lineno, aCtrl[iCtrl].zCtrlName);
+ }
+ if( testctrl<0 ){
+ utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
+ "Use \".testctrl --help\" for help\n", zCmd);
+ }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
+ utf8_printf(STD_ERR,
+ "line %d: \".testctrl %s\" may not be used in safe mode\n",
++ p->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
+ exit(1);
+ }else{
+ switch(testctrl){
- /* convert testctrl text option to value. allow any unique prefix
- ** of the option name, or a numerical value. */
- n2 = strlen30(zCmd);
- for(i=0; i<ArraySize(aCtrl); i++){
- if( strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
- if( testctrl<0 ){
- testctrl = aCtrl[i].ctrlCode;
- iCtrl = i;
- }else{
- utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n"
- "Use \".testctrl --help\" for help\n", zCmd);
- rc = 1;
- goto meta_command_exit;
- }
+ /* sqlite3_test_control(int, db, int) */
+ case SQLITE_TESTCTRL_OPTIMIZATIONS:
+ if( nArg==3 ){
+ unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
+ rc2 = sqlite3_test_control(testctrl, p->db, opt);
+ isOk = 3;
}
- }
- if( testctrl<0 ){
- utf8_printf(stderr,"Error: unknown test-control: %s\n"
- "Use \".testctrl --help\" for help\n", zCmd);
- }else if( aCtrl[iCtrl].unSafe && p->bSafeMode ){
- utf8_printf(stderr,
- "line %d: \".testctrl %s\" may not be used in safe mode\n",
- p->pInSource->lineno, aCtrl[iCtrl].zCtrlName);
- exit(1);
- }else{
- switch(testctrl){
-
- /* sqlite3_test_control(int, db, int) */
- case SQLITE_TESTCTRL_OPTIMIZATIONS:
- if( nArg==3 ){
- unsigned int opt = (unsigned int)strtol(azArg[2], 0, 0);
- rc2 = sqlite3_test_control(testctrl, p->db, opt);
- isOk = 3;
- }
- break;
+ break;
- /* sqlite3_test_control(int) */
- case SQLITE_TESTCTRL_PRNG_SAVE:
- case SQLITE_TESTCTRL_PRNG_RESTORE:
- case SQLITE_TESTCTRL_BYTEORDER:
- if( nArg==2 ){
- rc2 = sqlite3_test_control(testctrl);
- isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
- }
- break;
+ /* sqlite3_test_control(int) */
+ case SQLITE_TESTCTRL_PRNG_SAVE:
+ case SQLITE_TESTCTRL_PRNG_RESTORE:
+ case SQLITE_TESTCTRL_BYTEORDER:
+ if( nArg==2 ){
+ rc2 = sqlite3_test_control(testctrl);
+ isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
+ }
+ break;
- /* sqlite3_test_control(int, uint) */
- case SQLITE_TESTCTRL_PENDING_BYTE:
- if( nArg==3 ){
- unsigned int opt = (unsigned int)integerValue(azArg[2]);
- rc2 = sqlite3_test_control(testctrl, opt);
- isOk = 3;
- }
- break;
+ /* sqlite3_test_control(int, uint) */
+ case SQLITE_TESTCTRL_PENDING_BYTE:
+ if( nArg==3 ){
+ unsigned int opt = (unsigned int)integerValue(azArg[2]);
+ rc2 = sqlite3_test_control(testctrl, opt);
+ isOk = 3;
+ }
+ break;
- /* sqlite3_test_control(int, int, sqlite3*) */
- case SQLITE_TESTCTRL_PRNG_SEED:
- if( nArg==3 || nArg==4 ){
- int ii = (int)integerValue(azArg[2]);
- sqlite3 *db;
- if( ii==0 && strcmp(azArg[2],"random")==0 ){
- sqlite3_randomness(sizeof(ii),&ii);
- printf("-- random seed: %d\n", ii);
- }
- if( nArg==3 ){
- db = 0;
- }else{
- db = p->db;
- /* Make sure the schema has been loaded */
- sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
- }
- rc2 = sqlite3_test_control(testctrl, ii, db);
- isOk = 3;
- }
- break;
+ /* sqlite3_test_control(int, int, sqlite3*) */
+ case SQLITE_TESTCTRL_PRNG_SEED:
+ if( nArg==3 || nArg==4 ){
+ int ii = (int)integerValue(azArg[2]);
+ sqlite3 *db;
+ if( ii==0 && strcmp(azArg[2],"random")==0 ){
+ sqlite3_randomness(sizeof(ii),&ii);
+ fprintf(STD_OUT, "-- random seed: %d\n", ii);
+ }
+ if( nArg==3 ){
+ db = 0;
+ }else{
+ db = p->db;
+ /* Make sure the schema has been loaded */
+ sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
+ }
+ rc2 = sqlite3_test_control(testctrl, ii, db);
+ isOk = 3;
+ }
+ break;
- /* sqlite3_test_control(int, int) */
- case SQLITE_TESTCTRL_ASSERT:
- case SQLITE_TESTCTRL_ALWAYS:
- if( nArg==3 ){
- int opt = booleanValue(azArg[2]);
- rc2 = sqlite3_test_control(testctrl, opt);
- isOk = 1;
- }
- break;
+ /* sqlite3_test_control(int, int) */
+ case SQLITE_TESTCTRL_ASSERT:
+ case SQLITE_TESTCTRL_ALWAYS:
+ if( nArg==3 ){
+ int opt = booleanValue(azArg[2]);
+ rc2 = sqlite3_test_control(testctrl, opt);
+ isOk = 1;
+ }
+ break;
- /* sqlite3_test_control(int, int) */
- case SQLITE_TESTCTRL_LOCALTIME_FAULT:
- case SQLITE_TESTCTRL_NEVER_CORRUPT:
- if( nArg==3 ){
- int opt = booleanValue(azArg[2]);
- rc2 = sqlite3_test_control(testctrl, opt);
- isOk = 3;
- }
- break;
+ /* sqlite3_test_control(int, int) */
+ case SQLITE_TESTCTRL_LOCALTIME_FAULT:
+ case SQLITE_TESTCTRL_NEVER_CORRUPT:
+ if( nArg==3 ){
+ int opt = booleanValue(azArg[2]);
+ rc2 = sqlite3_test_control(testctrl, opt);
+ isOk = 3;
+ }
+ break;
- /* sqlite3_test_control(sqlite3*) */
- case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
- rc2 = sqlite3_test_control(testctrl, p->db);
- isOk = 3;
- break;
+ /* sqlite3_test_control(sqlite3*) */
+ case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
+ rc2 = sqlite3_test_control(testctrl, p->db);
+ isOk = 3;
+ break;
- case SQLITE_TESTCTRL_IMPOSTER:
- if( nArg==5 ){
- rc2 = sqlite3_test_control(testctrl, p->db,
- azArg[2],
- integerValue(azArg[3]),
- integerValue(azArg[4]));
- isOk = 3;
- }
- break;
+ case SQLITE_TESTCTRL_IMPOSTER:
+ if( nArg==5 ){
+ rc2 = sqlite3_test_control(testctrl, p->db,
+ azArg[2],
+ integerValue(azArg[3]),
+ integerValue(azArg[4]));
+ isOk = 3;
+ }
+ break;
- case SQLITE_TESTCTRL_SEEK_COUNT: {
- u64 x = 0;
- rc2 = sqlite3_test_control(testctrl, p->db, &x);
- utf8_printf(p->out, "%llu\n", x);
- isOk = 3;
- break;
- }
+ case SQLITE_TESTCTRL_SEEK_COUNT: {
+ u64 x = 0;
+ rc2 = sqlite3_test_control(testctrl, p->db, &x);
+ utf8_printf(p->out, "%llu\n", x);
+ isOk = 3;
+ break;
+ }
#ifdef YYCOVERAGE
- case SQLITE_TESTCTRL_PARSER_COVERAGE: {
- if( nArg==2 ){
- sqlite3_test_control(testctrl, p->out);
- isOk = 3;
- }
- break;
- }
+ case SQLITE_TESTCTRL_PARSER_COVERAGE: {
+ if( nArg==2 ){
+ sqlite3_test_control(testctrl, p->out);
+ isOk = 3;
+ }
+ break;
+ }
#endif
#ifdef SQLITE_DEBUG
- case SQLITE_TESTCTRL_TUNE: {
- if( nArg==4 ){
- int id = (int)integerValue(azArg[2]);
- int val = (int)integerValue(azArg[3]);
- sqlite3_test_control(testctrl, id, &val);
- isOk = 3;
- }else if( nArg==3 ){
- int id = (int)integerValue(azArg[2]);
- sqlite3_test_control(testctrl, -id, &rc2);
- isOk = 1;
- }else if( nArg==2 ){
- int id = 1;
- while(1){
- int val = 0;
- rc2 = sqlite3_test_control(testctrl, -id, &val);
- if( rc2!=SQLITE_OK ) break;
- if( id>1 ) utf8_printf(p->out, " ");
- utf8_printf(p->out, "%d: %d", id, val);
- id++;
- }
- if( id>1 ) utf8_printf(p->out, "\n");
- isOk = 3;
- }
- break;
- }
-#endif
- case SQLITE_TESTCTRL_SORTER_MMAP:
- if( nArg==3 ){
- int opt = (unsigned int)integerValue(azArg[2]);
- rc2 = sqlite3_test_control(testctrl, p->db, opt);
- isOk = 3;
- }
- break;
+ case SQLITE_TESTCTRL_TUNE: {
+ if( nArg==4 ){
+ int id = (int)integerValue(azArg[2]);
+ int val = (int)integerValue(azArg[3]);
+ sqlite3_test_control(testctrl, id, &val);
+ isOk = 3;
+ }else if( nArg==3 ){
+ int id = (int)integerValue(azArg[2]);
+ sqlite3_test_control(testctrl, -id, &rc2);
+ isOk = 1;
+ }else if( nArg==2 ){
+ int id = 1;
+ while(1){
+ int val = 0;
+ rc2 = sqlite3_test_control(testctrl, -id, &val);
+ if( rc2!=SQLITE_OK ) break;
+ if( id>1 ) utf8_printf(p->out, " ");
+ utf8_printf(p->out, "%d: %d", id, val);
+ id++;
+ }
+ if( id>1 ) utf8_printf(p->out, "\n");
+ isOk = 3;
}
+ break;
}
- if( isOk==0 && iCtrl>=0 ){
- utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
- rc = 1;
- }else if( isOk==1 ){
- raw_printf(p->out, "%d\n", rc2);
- }else if( isOk==2 ){
- raw_printf(p->out, "0x%08x\n", rc2);
+#endif
}
- }else
-#endif /* !defined(SQLITE_UNTESTABLE) */
-
- if( c=='t' && n>4 && strncmp(azArg[0], "timeout", n)==0 ){
- open_db(p, 0);
- sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
- }else
-
- if( c=='t' && n>=5 && strncmp(azArg[0], "timer", n)==0 ){
- if( nArg==2 ){
- enableTimer = booleanValue(azArg[1]);
- if( enableTimer && !HAS_TIMER ){
- raw_printf(stderr, "Error: timer not available on this system.\n");
- enableTimer = 0;
+ }
+ if( isOk==0 && iCtrl>=0 ){
+ utf8_printf(p->out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+ return 1;
+ }else if( isOk==1 ){
+ raw_printf(p->out, "%d\n", rc2);
+ }else if( isOk==2 ){
+ raw_printf(p->out, "0x%08x\n", rc2);
+ }
+ return 0;
+}
+DISPATCHABLE_COMMAND( timeout 4 1 2 ){
+ open_db(p, 0);
+ sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
+ return 0;
+}
+DISPATCHABLE_COMMAND( timer ? 2 2 ){
+ enableTimer = booleanValue(azArg[1]);
+ if( enableTimer && !HAS_TIMER ){
+ raw_printf(STD_ERR, "Error: timer not available on this system.\n");
+ enableTimer = 0;
+ }
+ return 0;
+}
+DISPATCHABLE_COMMAND( trace ? 0 0 ){
+ int mType = 0;
+ int jj;
+ open_db(p, 0);
+ for(jj=1; jj<nArg; jj++){
+ const char *z = azArg[jj];
+ if( z[0]=='-' ){
+ if( optionMatch(z, "expanded") ){
+ p->eTraceType = SHELL_TRACE_EXPANDED;
+ }
+#ifdef SQLITE_ENABLE_NORMALIZE
+ else if( optionMatch(z, "normalized") ){
+ p->eTraceType = SHELL_TRACE_NORMALIZED;
+ }
+#endif
+ else if( optionMatch(z, "plain") ){
+ p->eTraceType = SHELL_TRACE_PLAIN;
+ }
+ else if( optionMatch(z, "profile") ){
+ mType |= SQLITE_TRACE_PROFILE;
+ }
+ else if( optionMatch(z, "row") ){
+ mType |= SQLITE_TRACE_ROW;
+ }
+ else if( optionMatch(z, "stmt") ){
+ mType |= SQLITE_TRACE_STMT;
+ }
+ else if( optionMatch(z, "close") ){
+ mType |= SQLITE_TRACE_CLOSE;
+ }
+ else {
+ *pzErr = shellMPrintf(0,"Unknown option \"%s\" on \".trace\"\n", z);
+ return 1;
}
}else{
- raw_printf(stderr, "Usage: .timer on|off\n");
- rc = 1;
+ output_file_close(p->traceOut);
+ p->traceOut = output_file_open(azArg[1], 0);
}
- }else
+ }
+ if( p->traceOut==0 ){
+ sqlite3_trace_v2(p->db, 0, 0, 0);
+ }else{
+ if( mType==0 ) mType = SQLITE_TRACE_STMT;
+ sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
+ }
+ return 0;
+}
-#ifndef SQLITE_OMIT_TRACE
- if( c=='t' && strncmp(azArg[0], "trace", n)==0 ){
- int mType = 0;
- int jj;
- open_db(p, 0);
- for(jj=1; jj<nArg; jj++){
- const char *z = azArg[jj];
- if( z[0]=='-' ){
- if( optionMatch(z, "expanded") ){
- p->eTraceType = SHELL_TRACE_EXPANDED;
- }
-#ifdef SQLITE_ENABLE_NORMALIZE
- else if( optionMatch(z, "normalized") ){
- p->eTraceType = SHELL_TRACE_NORMALIZED;
- }
+/*****************
+ * The .user command
+ * Because there is no help text for .user, it does its own argument validation.
+ */
+CONDITION_COMMAND( user SQLITE_USER_AUTHENTICATION );
+DISPATCHABLE_COMMAND( user ? 0 0 ){
+ int rc;
+ const char *usage
+ = "Usage: .user SUBCOMMAND ...\n"
+ "Subcommands are:\n"
+ " login USER PASSWORD\n"
+ " delete USER\n"
+ " add USER PASSWORD ISADMIN\n"
+ " edit USER PASSWORD ISADMIN\n"
+ ;
+ if( nArg<2 ){
+ teach_fail:
+ *pzErr = shellMPrintf(0,usage);
+ return 1;
+ }
+ open_db(p, 0);
+ if( strcmp(azArg[1],"login")==0 ){
+ if( nArg!=4 ){
+ goto teach_fail;
+ }
+ rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
+ strlen30(azArg[3]));
+ if( rc ){
+ *pzErr = shellMPrintf(0,"Authentication failed for user %s\n", azArg[2]);
+ return 1;
+ }
+ }else if( strcmp(azArg[1],"add")==0 ){
+ if( nArg!=5 ){
+ goto teach_fail;
+ }
+ rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
+ booleanValue(azArg[4]));
+ if( rc ){
+ *pzErr = shellMPrintf(0,"User-Add failed: %d\n", rc);
+ return 1;
+ }
+ }else if( strcmp(azArg[1],"edit")==0 ){
+ if( nArg!=5 ){
+ goto teach_fail;
+ }
+ rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
+ booleanValue(azArg[4]));
+ if( rc ){
+ *pzErr = shellMPrintf(0,"User-Edit failed: %d\n", rc);
+ return 1;
+ }
+ }else if( strcmp(azArg[1],"delete")==0 ){
+ if( nArg!=3 ){
+ goto teach_fail;
+ }
+ rc = sqlite3_user_delete(p->db, azArg[2]);
+ if( rc ){
+ *pzErr = shellMPrintf(0,"User-Delete failed: %d\n", rc);
+ return 1;
+ }
+ }else{
+ goto teach_fail;
+ }
+ return 0;
+}
+
+/*****************
+ * The .vfsinfo, .vfslist, .vfsname and .version commands
+ */
+COLLECT_HELP_TEXT[
+ ".version Show a variety of version info",
+ ".vfsinfo ?AUX? Information about the top-level VFS",
+ ".vfslist List all available VFSes",
+ ".vfsname ?AUX? Print the name of the VFS stack",
+];
+DISPATCHABLE_COMMAND( version ? 1 1 ){
+ utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
+ sqlite3_libversion(), sqlite3_sourceid());
+#if SQLITE_HAVE_ZLIB
+ utf8_printf(p->out, "zlib version %s\n", zlibVersion());
#endif
- else if( optionMatch(z, "plain") ){
- p->eTraceType = SHELL_TRACE_PLAIN;
- }
- else if( optionMatch(z, "profile") ){
- mType |= SQLITE_TRACE_PROFILE;
- }
- else if( optionMatch(z, "row") ){
- mType |= SQLITE_TRACE_ROW;
- }
- else if( optionMatch(z, "stmt") ){
- mType |= SQLITE_TRACE_STMT;
- }
- else if( optionMatch(z, "close") ){
- mType |= SQLITE_TRACE_CLOSE;
- }
- else {
- raw_printf(stderr, "Unknown option \"%s\" on \".trace\"\n", z);
- rc = 1;
- goto meta_command_exit;
+#define CTIMEOPT_VAL_(opt) #opt
+#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
+#if defined(__clang__) && defined(__clang_major__)
+ utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
+ CTIMEOPT_VAL(__clang_minor__) "."
+ CTIMEOPT_VAL(__clang_patchlevel__) "\n");
+#elif defined(_MSC_VER)
+ utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
+#elif defined(__GNUC__) && defined(__VERSION__)
+ utf8_printf(p->out, "gcc-" __VERSION__ "\n");
+#endif
+ return 0;
+}
+DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
+ const char *zDbName = nArg==2 ? azArg[1] : "main";
+ sqlite3_vfs *pVfs = 0;
+ if( p->db ){
+ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
+ if( pVfs ){
+ utf8_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName);
+ raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion);
+ raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
+ raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
+ }
+ }
+ return 0;
+}
+DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
+ sqlite3_vfs *pVfs;
+ sqlite3_vfs *pCurrent = 0;
+ if( p->db ){
+ sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
+ }
+ for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
+ utf8_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName,
+ pVfs==pCurrent ? " <--- CURRENT" : "");
+ raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion);
+ raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
+ raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
+ if( pVfs->pNext ){
+ raw_printf(p->out, "-----------------------------------\n");
+ }
+ }
+ return 0;
+}
+DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
+ const char *zDbName = nArg==2 ? azArg[1] : "main";
+ char *zVfsName = 0;
+ if( p->db ){
+ sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
+ if( zVfsName ){
+ utf8_printf(p->out, "%s\n", zVfsName);
+ sqlite3_free(zVfsName);
+ }
+ }
+ return 0;
+}
+
+/*****************
+ * The .width and .wheretrace commands
+ * The .wheretrace command has no help.
+ */
+COLLECT_HELP_TEXT[
+ ".width NUM1 NUM2 ... Set minimum column widths for columnar output",
- " Negative values right-justify",
++ " Negative values right-justify",
+];
+DISPATCHABLE_COMMAND( width ? 1 0 ){
+ int j;
+ p->nWidth = nArg-1;
+ p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
+ if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
+ if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
+ for(j=1; j<nArg; j++){
+ p->colWidth[j-1] = (int)integerValue(azArg[j]);
+ }
+ return 0;
+}
+DISPATCHABLE_COMMAND( wheretrace ? 1 2 ){
+ unsigned int x = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
+ sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
+ return 0;
+}
+
++/*****************
++ * The .x command
++ */
++COLLECT_HELP_TEXT[
++ ".x NAMES ... Excecute content of some .parameter set variable(s)",
++ " Only variables whose name begins with a letter are eligible for this."
++];
++DISPATCHABLE_COMMAND( x ? 1 0 ){
++ int ia, rc, nErrors = 0;
++ sqlite3_stmt *pStmt = 0;
++ open_db(p, 0);
++ if( p->db==0 ){
++ utf8_printf(STD_ERR, ".x can only be done with a database open.\n");
++ return 1;
++ }
++ if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA,
++ PARAM_TABLE_NAME,
++ "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
++ utf8_printf(STD_ERR, "No "PARAM_TABLE_SNAME" table exists.\n");
++ return 1;
++ }
++ rc = sqlite3_prepare_v2
++ (p->db, "SELECT value FROM "PARAM_TABLE_SNAME
++ " WHERE key=$1 AND uses=1",
++ -1, &pStmt, 0);
++ if( rc!=SQLITE_OK ){
++ utf8_printf(STD_ERR, PARAM_TABLE_SNAME" is wrongly created.\n");
++ return 1;
++ }
++ for( ia=1; ia < nArg; ++ia ){
++ if( isalpha(azArg[ia][0]) ){
++ rc = sqlite3_reset(pStmt);
++ rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
++ rc = sqlite3_step(pStmt);
++ if( rc==SQLITE_ROW ){
++ const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
++ int nb = sqlite3_column_bytes(pStmt, 0);
++ while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
++ if( nb>0 ){
++ /* The trailing newline (or some other placeholder) is important
++ * because one (or some other character) will likely be put in
++ * its place during process_input() line/group handling, along
++ * with a terminating NUL character. Without it, the NULL could
++ * land past the end of the allocation made just below.
++ */
++ int nle = zValue[nb-1]=='\n';
++ char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle );
++ InSource inRedir
++ = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource);
++ shell_check_oom(zSubmit);
++ p->pInSource = &inRedir;
++ rc = process_input(p);
++ sqlite3_free(zSubmit);
++ p->pInSource = inRedir.pFrom;
++ }else{
++ continue; /* All white, ignore. */
+ }
+ }else{
- output_file_close(p->traceOut);
- p->traceOut = output_file_open(azArg[1], 0);
++ utf8_printf(STD_ERR,
++ "Skipping parameter '%s' (not set and executable.)\n",
++ azArg[ia]);
++ ++nErrors;
+ }
- }
- if( p->traceOut==0 ){
- sqlite3_trace_v2(p->db, 0, 0, 0);
+ }else{
- if( mType==0 ) mType = SQLITE_TRACE_STMT;
- sqlite3_trace_v2(p->db, mType, sql_trace_callback, p);
++ utf8_printf(STD_ERR,
++ "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
++ ++nErrors;
+ }
- }else
-#endif /* !defined(SQLITE_OMIT_TRACE) */
++ }
++ sqlite3_finalize(pStmt);
++ return (rc==2)? 2 : nErrors>0;
++}
+
-#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
- if( c=='u' && strncmp(azArg[0], "unmodule", n)==0 ){
- int ii;
- int lenOpt;
- char *zOpt;
- if( nArg<2 ){
- raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n");
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- zOpt = azArg[1];
- if( zOpt[0]=='-' && zOpt[1]=='-' && zOpt[2]!=0 ) zOpt++;
- lenOpt = (int)strlen(zOpt);
- if( lenOpt>=3 && strncmp(zOpt, "-allexcept",lenOpt)==0 ){
- assert( azArg[nArg]==0 );
- sqlite3_drop_modules(p->db, nArg>2 ? (const char**)(azArg+2) : 0);
+/* End of published, standard meta-command implementation functions
+COMMENT Build-time overrides of above meta-commands or new meta-commands may be
+COMMENT incorporated into shell.c via: -it COMMAND_CUSTOMIZE=<customize source>
+COMMENT where <customize source> names a file using the methodology of the above
+COMMENT section to define new or altered meta-commands and their help text.
+*/
+INCLUDE( COMMAND_CUSTOMIZE );
+
+typedef struct MetaCommand MetaCommand;
+
+/* Define and populate command dispatch table. */
+static struct CommandInfo {
+ const char * cmdName;
+ int (*cmdDoer)(char *azArg[], int nArg, ShellState *, char **pzErr);
+ unsigned char minLen, minArgs, maxArgs;
+#if OBJECTIFY_COMMANDS
+ const char *azHelp[2]; /* primary and secondary help text */
+ void * pCmdData;
+#endif
+} command_table[] = {
+ COMMENT Emit the dispatch table entries generated and collected above.
+ EMIT_DISPATCH(2);
+ { 0, 0, 0, -1, -1 }
+};
+static unsigned numCommands
+ = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
+
+COMMENT This help text is set seperately from meta-command definition section
+COMMENT for the always-built-in, non-customizable commands with visible help.
+COLLECT_HELP_TEXT[
+ ".exit ?CODE? Exit this program with return-code CODE or 0",
+ ".quit Exit this program",
+];
+
+/*
+** Text of help messages.
+**
+** The help text for each individual command begins with a line that starts
+** with ".". Subsequent lines are supplimental information.
+**
+** There must be two or more spaces between the end of the command and the
+** start of the description of what that command does.
+*/
+static const char *(azHelp[]) = {
+/* Template for help text indents and length:
+ ".whatever ?arg? ... Summary of effects (limited to this line's length)",
+ " ^ ^ ^ ^ ",
+*/
+ COMMENT Emit the help text fragments collected above via COLLECT_HELP_TEXT.
+ EMIT_HELP_TEXT(2);
+ 0 /* Sentinel */
+};
+
+
+#define NO_SUCH_COMMAND SQLITE_NOTFOUND
+/* SHELL_INVALID_ARGS defined as SQLITE_MISUSE in shext_linkage.h */
+
+/*****************
+** Command dispatcher
+** For the non-extended or non-extensible shell, this function does
+** a binary search of the fixed list of meta-command info structs.
+** For an extended shell, it may (TBD) query the shell's DB. Either
+** way, this function retains its interface.
+** After successful command lookup and (simple) argument checking,
+** it calls the found meta-command with the input arguments (except
+** that azArg[0] is replaced with the properly spelled command name.)
+** The return is either a dispatch error or whatever the dispatched
+** meta-command returns.
+*/
+int dispatchCommand(char *azArg[], int nArg, ShellState *pSS, char **pzErr){
+ const char *cmdName = azArg[0];
+ int cmdLen = strlen30(cmdName);
+ struct CommandInfo *pci = 0;
+ int ixb = 0, ixe = numCommands-1;
+ while( ixb <= ixe ){
+ int ixm = (ixb+ixe)/2;
+ int md = strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
+ if( md>0 ){
+ ixb = ixm+1;
+ }else if( md<0 ){
+ ixe = ixm-1;
}else{
- for(ii=1; ii<nArg; ii++){
- sqlite3_create_module(p->db, azArg[ii], 0, 0);
+ if( command_table[ixm].minLen > cmdLen ){
+ return NO_SUCH_COMMAND;
}
+ pci = &command_table[ixm];
+ break;
}
- }else
+ }
+ if( 0==pci ){
+ return NO_SUCH_COMMAND;
+ }
+ if( pci->minArgs > nArg||(pci->maxArgs > 0 && pci->maxArgs < nArg) ){
+ return SHELL_INVALID_ARGS;
+ }
+ /* Replace any user-shortened command name with its whole name. */
+ azArg[0] = (char *)pci->cmdName;
+ return (pci->cmdDoer)(azArg, nArg, pSS, pzErr);
+}
+
+/*
+** If an input line begins with "." then invoke this routine to
+** process that line.
+**
+** Return 1 on error, 2 to exit, and 0 otherwise.
+*/
+static int do_meta_command(char *zLine, ShellState *p){
+ int h = 1;
+ int nArg = 0;
+ int n, c;
+ int rc = 0;
+ char *azArg[52];
++#if SHELL_VARIABLE_EXPANSION
++ int ncLineIn = strlen30(zLine);
++ u8 bExpVars = SHEXT_VAREXP(p);
+ #endif
-#if SQLITE_USER_AUTHENTICATION
- if( c=='u' && strncmp(azArg[0], "user", n)==0 ){
- if( nArg<2 ){
- raw_printf(stderr, "Usage: .user SUBCOMMAND ...\n");
- rc = 1;
- goto meta_command_exit;
- }
- open_db(p, 0);
- if( strcmp(azArg[1],"login")==0 ){
- if( nArg!=4 ){
- raw_printf(stderr, "Usage: .user login USER PASSWORD\n");
- rc = 1;
- goto meta_command_exit;
- }
- rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
- strlen30(azArg[3]));
- if( rc ){
- utf8_printf(stderr, "Authentication failed for user %s\n", azArg[2]);
- rc = 1;
- }
- }else if( strcmp(azArg[1],"add")==0 ){
- if( nArg!=5 ){
- raw_printf(stderr, "Usage: .user add USER PASSWORD ISADMIN\n");
- rc = 1;
- goto meta_command_exit;
- }
- rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
- booleanValue(azArg[4]));
- if( rc ){
- raw_printf(stderr, "User-Add failed: %d\n", rc);
- rc = 1;
- }
- }else if( strcmp(azArg[1],"edit")==0 ){
- if( nArg!=5 ){
- raw_printf(stderr, "Usage: .user edit USER PASSWORD ISADMIN\n");
- rc = 1;
- goto meta_command_exit;
- }
- rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
- booleanValue(azArg[4]));
- if( rc ){
- raw_printf(stderr, "User-Edit failed: %d\n", rc);
- rc = 1;
- }
- }else if( strcmp(azArg[1],"delete")==0 ){
- if( nArg!=3 ){
- raw_printf(stderr, "Usage: .user delete USER\n");
- rc = 1;
- goto meta_command_exit;
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( p->expert.pExpert ){
+ expertFinish(p, 1, 0);
+ }
+#endif
+
+ /* Parse the input line into tokens.
+ */
+ while( zLine[h] && nArg<ArraySize(azArg)-1 ){
+ while( IsSpace(zLine[h]) ){ h++; }
+ if( zLine[h]==0 ) break;
+ if( zLine[h]=='\'' || zLine[h]=='"' ){
+ int delim = zLine[h++];
+ azArg[nArg++] = &zLine[h];
+ while( zLine[h] && zLine[h]!=delim ){
+ if( zLine[h]=='\\' && delim=='"' && zLine[h+1]!=0 ) h++;
+ h++;
}
- rc = sqlite3_user_delete(p->db, azArg[2]);
- if( rc ){
- raw_printf(stderr, "User-Delete failed: %d\n", rc);
- rc = 1;
+ if( zLine[h]==delim ){
+ zLine[h++] = 0;
}
+ if( delim=='"' ) resolve_backslashes(azArg[nArg-1]);
}else{
- raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n");
- rc = 1;
- goto meta_command_exit;
+ azArg[nArg++] = &zLine[h];
+ while( zLine[h] && !IsSpace(zLine[h]) ){ h++; }
+ if( zLine[h] ) zLine[h++] = 0;
+ resolve_backslashes(azArg[nArg-1]);
}
- }else
-#endif /* SQLITE_USER_AUTHENTICATION */
+ }
+ azArg[nArg] = 0;
- if( c=='v' && strncmp(azArg[0], "version", n)==0 ){
- utf8_printf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
- sqlite3_libversion(), sqlite3_sourceid());
-#if SQLITE_HAVE_ZLIB
- utf8_printf(p->out, "zlib version %s\n", zlibVersion());
-#endif
-#define CTIMEOPT_VAL_(opt) #opt
-#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt)
-#if defined(__clang__) && defined(__clang_major__)
- utf8_printf(p->out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
- CTIMEOPT_VAL(__clang_minor__) "."
- CTIMEOPT_VAL(__clang_patchlevel__) "\n");
-#elif defined(_MSC_VER)
- utf8_printf(p->out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
-#elif defined(__GNUC__) && defined(__VERSION__)
- utf8_printf(p->out, "gcc-" __VERSION__ "\n");
-#endif
- }else
+ /* Process the input line.
+ */
+ if( nArg==0 ) return 0; /* no tokens, no error */
+ n = strlen30(azArg[0]);
+ c = azArg[0][0];
+ clearTempFile(p);
- if( c=='v' && strncmp(azArg[0], "vfsinfo", n)==0 ){
- const char *zDbName = nArg==2 ? azArg[1] : "main";
- sqlite3_vfs *pVfs = 0;
- if( p->db ){
- sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
- if( pVfs ){
- utf8_printf(p->out, "vfs.zName = \"%s\"\n", pVfs->zName);
- raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion);
- raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
- raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
- }
- }
+ /* Check for the special, non-dispatched meta-commands.
+ */
+
+ if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
+ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 )
+ p->abruptExit = rc;
+ rc = 2;
}else
- if( c=='v' && strncmp(azArg[0], "vfslist", n)==0 ){
- sqlite3_vfs *pVfs;
- sqlite3_vfs *pCurrent = 0;
- if( p->db ){
- sqlite3_file_control(p->db, "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
- }
- for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
- utf8_printf(p->out, "vfs.zName = \"%s\"%s\n", pVfs->zName,
- pVfs==pCurrent ? " <--- CURRENT" : "");
- raw_printf(p->out, "vfs.iVersion = %d\n", pVfs->iVersion);
- raw_printf(p->out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
- raw_printf(p->out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
- if( pVfs->pNext ){
- raw_printf(p->out, "-----------------------------------\n");
- }
- }
+ if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
+ rc = 2;
}else
- if( c=='v' && strncmp(azArg[0], "vfsname", n)==0 ){
- const char *zDbName = nArg==2 ? azArg[1] : "main";
- char *zVfsName = 0;
- if( p->db ){
- sqlite3_file_control(p->db, zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
- if( zVfsName ){
- utf8_printf(p->out, "%s\n", zVfsName);
- sqlite3_free(zVfsName);
+#ifdef SQLITE_DEBUG
+ /* Undocumented commands for internal testing.
+ * Subject to change without notice.
+ * These are not dispatched via lookup because the command word varies.
+ */
+ if( c=='s' && n>=10 && strncmp(azArg[0], "selftest-", 9)==0 ){
+ if( strncmp(azArg[0]+9, "boolean", n-9)==0 ){
+ int i, v;
+ for(i=1; i<nArg; i++){
+ v = booleanValue(azArg[i]);
+ utf8_printf(p->out, "%s: %d 0x%x\n", azArg[i], v, v);
}
}
- }else
-
- if( c=='w' && strncmp(azArg[0], "wheretrace", n)==0 ){
- unsigned int x
- = nArg>=2 ? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
- sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &x);
- }else
-
- if( c=='w' && strncmp(azArg[0], "width", n)==0 ){
- int j;
- assert( nArg<=ArraySize(azArg) );
- p->nWidth = nArg-1;
- p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(int)*2);
- if( p->colWidth==0 && p->nWidth>0 ) shell_out_of_memory();
- if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth];
- for(j=1; j<nArg; j++){
- p->colWidth[j-1] = (int)integerValue(azArg[j]);
+ if( strncmp(azArg[0]+9, "integer", n-9)==0 ){
+ int i; sqlite3_int64 v;
+ for(i=1; i<nArg; i++){
+ char zBuf[200];
+ v = integerValue(azArg[i]);
+ sqlite3_snprintf(sizeof(zBuf),zBuf,"%s: %lld 0x%llx\n", azArg[i],v,v);
+ utf8_printf(p->out, "%s", zBuf);
+ }
}
}else
-
- if( c=='x' && n==1 ){ /* "x" */
- rc = execute_variables(azArg, nArg, p);
- }else
-
+#endif
+ /* The meta-command is not among the specially handled ones. Dispatch it. */
{
- utf8_printf(stderr, "Error: unknown command or invalid arguments: "
- " \"%s\". Enter \".help\" for help\n", azArg[0]);
- rc = 1;
+ char *zErr = 0;
+ int dispatchResult = dispatchCommand(azArg, nArg, p, &zErr);
+ if( p->abruptExit!=0 ){
+ dispatchResult = SHELL_FORBIDDEN_OP;
+ }
+ switch( dispatchResult ){
+ case NO_SUCH_COMMAND:
+ utf8_printf(STD_ERR, "Error: unknown command: \"%s\"\n", azArg[0]);
+ if( stdin_is_interactive )
+ utf8_printf(STD_ERR, " Enter \".help\" for a list of commands.\n");
+ rc = 1;
+ break;
+ case SHELL_INVALID_ARGS:
+ utf8_printf(STD_ERR, "Error: invalid arguments for \".%s\"\n", azArg[0]);
+ if( stdin_is_interactive ){
+ if( zErr!=0 ){
+ utf8_printf(STD_ERR, " %s\n", zErr);
+ }else{
+ utf8_printf(STD_ERR, "Usage: ");
+ showPrimaryHelp(STD_ERR, azArg[0]);
+ }
+ }
+ rc = 1;
+ break;
+ case SHELL_FORBIDDEN_OP:
+ if( zErr!=0 ){
+ utf8_printf
+ (STD_ERR,
+ "Error: \".%s\" may not %s in --safe mode\n", azArg[0], zErr);
+ sqlite3_free(zErr);
+ }else {
+ utf8_printf(STD_ERR,
+ "Error: \".%s\" forbidden in --safe mode\n", azArg[0]);
+ }
+ p->abruptExit = 3;
+ rc = 2;
+ default:
+ if( 0!=dispatchResult ) rc = 1;
+ if( zErr!=0 ){
+ utf8_printf(STD_ERR, "%s", zErr);
+ sqlite3_free(zErr);
+ }
+ }
}
meta_command_exit:
p->outCount--;
if( p->outCount==0 ) output_reset(p);
}
- p->bSafeMode = p->bSafeModePersist;
+ updateSafeMode(p);
+ #if SHELL_VARIABLE_EXPANSION
+ if( bExpVars ){
+ /* Free any arguments that are allocated rather than tokenized in place. */
+ for( n=1; n<nArg; ++n ){
+ int iArgOffset = azArg[n]-zLine;
+ u8 bInPlace = iArgOffset>0 && iArgOffset<ncLineIn;
+ if( !bInPlace ) sqlite3_free(azArg[n]);
+ }
+ }
+ #endif
return rc;
}
#ifndef CHAR_BIT
# define CHAR_BIT 8
#endif
+
typedef enum {
- QSS_HasDark = 1<<CHAR_BIT, QSS_EndingSemi = 2<<CHAR_BIT,
- QSS_CharMask = (1<<CHAR_BIT)-1, QSS_ScanMask = 3<<CHAR_BIT,
- QSS_Start = 0
- } QuickScanState;
- #define QSS_SETV(qss, newst) ((newst) | ((qss) & QSS_ScanMask))
- #define QSS_INPLAIN(qss) (((qss)&QSS_CharMask)==QSS_Start)
- #define QSS_PLAINWHITE(qss) (((qss)&~QSS_EndingSemi)==QSS_Start)
- #define QSS_PLAINDARK(qss) (((qss)&~QSS_EndingSemi)==QSS_HasDark)
- #define QSS_SEMITERM(qss) (((qss)&~QSS_HasDark)==QSS_EndingSemi)
+ SSS_HasDark = 1<<CHAR_BIT, SSS_EndingSemi = 2<<CHAR_BIT,
+ SSS_CharMask = (1<<CHAR_BIT)-1, SSS_ScanMask = 3<<CHAR_BIT,
+ SSS_Start = 0
+ } SqlScanState;
+ #define SSS_SETV(qss, newst) ((newst) | ((qss) & SSS_ScanMask))
+ #define SSS_INPLAIN(qss) (((qss)&SSS_CharMask)==SSS_Start)
+ #define SSS_PLAINWHITE(qss) (((qss)&~SSS_EndingSemi)==SSS_Start)
+ #define SSS_PLAINDARK(qss) (((qss)&~SSS_EndingSemi)==SSS_HasDark)
+ #define SSS_SEMITERM(qss) (((qss)&~SSS_HasDark)==SSS_EndingSemi)
/*
** Scan line for classification to guide shell's handling.
continue;
++zLine;
cWait = 0;
- qss = QSS_SETV(qss, 0);
+
+ sss = SSS_SETV(sss, 0);
goto PlainScan;
case '`': case '\'': case '"':
if(*zLine==cWait){
}
/*
- ** Return TRUE if the line typed in is an SQL command terminator other
- ** than a semi-colon. The SQL Server style "go" command is understood
- ** as is the Oracle "/".
+ ** If the line typed in is an SQL command terminator other than ';',
+ ** return a pointer to the terminator. Otherwise return 0.
+ ** The SQL Server style "go" command and Oracle "/" are understood.
*/
- static int line_is_command_terminator(char *zLine){
+ static char *line_is_command_terminator(char *zLine){
+ int iSkip = 0;
while( IsSpace(zLine[0]) ){ zLine++; };
if( zLine[0]=='/' )
- iSkip = 1; /* Oracle */
- else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
- iSkip = 2; /* SQL Server */
+ zLine += 1; /* Oracle */
- else if ( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
++ else if( ToLower(zLine[0])=='g' && ToLower(zLine[1])=='o' )
+ zLine += 2; /* SQL Server */
- else
- return 0;
- return quickscan(zLine,QSS_Start)==QSS_Start;
+ else if( iSkip>0 ){
+ SqlScanState sss = SSS_Start;
+ sql_prescan(zLine+iSkip,&sss);
+ if( sss==SSS_Start ) return zLine;
+ }
+ return 0;
}
/*
}else{
sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
}
-- utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail);
++ utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail);
sqlite3_free(zErrMsg);
- zErrMsg = 0;
return 1;
}else if( ShellHasFlag(p, SHFLG_CountChanges) ){
char zLineBuf[2000];
return 0;
}
+ #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){
+ DCmd_ScanState ss = *pScanState & ~endEscaped;
+ char c = (ss&isOpenMask)? 1 : *zCmd++;
+ while( c!=0 ){
+ switch( ss ){
+ case twixtArgs:
+ while( IsSpace(c) ){
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ switch( c ){
+ case '\\':
+ if( *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }else goto inDark;
+ case '\'': ss = inSqArg; goto inSq;
+ case '"': ss = inDqArg; goto inDq;
+ default: ss = inDarkArg; goto inDark;
+ }
+ inSq:
+ case inSqArg:
+ while( (c=*zCmd++)!='\'' ){
+ if( c==0 ) goto atEnd;
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ }
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ inDq:
+ case inDqArg:
+ do {
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ if( c=='\\' ){
+ if( (c=*zCmd++)==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ } while( c!='"' );
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ inDark:
+ case inDarkArg:
+ while( !IsSpace(c) ){
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ }
+ }
+ atEnd:
+ *pScanState = ss;
+ }
+ #else
+ # define dot_command_scan(x,y)
+ #endif
+
+ /* Utility functions for process_input. */
+
+ #if SHELL_EXTENDED_PARSING
+ /*
+ ** Process dot-command line with its scan state to:
+ ** 1. Setup for requested line-splicing; and
+ ** 2. Say whether it is complete.
+ ** The last two out parameters are the line's length, which may be
+ ** adjusted, and the char to be used for joining a subsequent line.
+ ** This is broken out of process_input() mainly for readability.
+ ** The return is TRUE for dot-command ready to run, else false.
+ */
+ static int line_join_ends(DCmd_ScanState dcss, char *zLine,
+ int *pnLength, char *pcLE){
+ /* It is ready only if has no open argument or escaped newline. */
+ int bOpen = DCSS_IsOpen(dcss);
+ if( !DCSS_EndEscaped(dcss) ){
+ *pcLE = '\n';
+ return !bOpen;
+ }else{
+ *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
+ /* Swallow the trailing escape character. */
+ zLine[--*pnLength] = 0;
+ return 0;
+ }
+ }
+ #endif
/*
- ** 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.
+ ** Grow the accumulation line buffer to accommodate ncNeed chars.
+ ** In/out parameters pz and pna reference the buffer and its size.
+ ** The buffer must eventually be sqlite3_free()'ed by the caller.
+ */
+ static void grow_line_buffer(char **pz, int *pna, int ncNeed){
++
+ if( ncNeed > *pna ){
+ *pna += *pna + (*pna>>1) + 100;
+ *pz = sqlite3_realloc(*pz, *pna);
+ shell_check_oom(*pz);
+ }
+ }
+
+ /*
+ ** Read input from designated source (p->pInSource) and process it.
+ ** If pInSource==0 then input is interactive - the user is typing it.
+ ** Otherwise, input is coming from a file, stream device or string.
+ ** Prompts issue and history is saved only for interactive input.
+ ** An interrupt signal will cause this routine to exit immediately,
+ ** with "exit demanded" code returned, unless input is interactive.
**
- ** Normally return (number_of_errors > 0),
- ** but return SHELL_FORBIDDEN_OP for immediate shell exit.
+ ** Returns:
+ ** 0 => no errors
+ ** 1 => errors>0
+ ** 2 => exit demanded, no errors.
+ ** 3 => exit demanded, errors>0
*/
static int process_input(ShellState *p){
- char *zLine = 0; /* A single input line */
- char *zSql = 0; /* Accumulated SQL text */
- int nLine; /* Length of current line */
- int nSql = 0; /* Bytes of zSql[] used */
- int nAlloc = 0; /* Allocated zSql[] space */
- int rc = 0; /* Error or exit code */
- int errCnt = 0; /* Number of errors seen */
- int startline = 0; /* Line number for start of current input */
- QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
-
+ char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */
+ char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */
+ /* Above two pointers could be local to the group handling loop, but are
+ * not so that the number of memory allocations can be reduced. They are
+ * reused from one incoming group to another, realloc()'ed as needed. */
+ int naAccum = 0; /* tracking how big zLineAccum buffer has become */
+ /* Some flags for ending the overall group processing loop, always 1 or 0 */
+ u8 bErrorBail=0, bInputEnd=0, bExitDemand=0, bInterrupted=0;
+ /* Flag to affect prompting and interrupt action */
+ u8 bInteractive = (p->pInSource==&stdInSource && stdin_is_interactive);
+ int nErrors = 0; /* count of errors during execution or its prep */
+
+ /* Block overly-recursive or absurdly nested input redirects. */
if( p->inputNesting==MAX_INPUT_NESTING ){
- /* 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);
+ InSource *pInSrc = p->pInSource->pFrom;
+ const char *zLead = "Input nesting limit ("
+ SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
+ int i = 3;
+ assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
+ while( i-->0 && pInSrc!=0 ){
- utf8_printf(stderr,
++ 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(stderr, " ...\nERROR: Check recursion.\n");
++ utf8_printf(STD_ERR, " ...\nERROR: Check recursion.\n");
return 1;
}
++p->inputNesting;
- p->lineno = 0;
- while( rc<2
- &&
- (errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive)) ){
+
+ /* line-group processing loop (per SQL block, dot-command or comment) */
+ while( !bErrorBail && !bInputEnd && !bExitDemand && !bInterrupted ){
+ int nGroupLines = 0; /* count of lines belonging to this group */
+ int ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */
+ int ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */
+ int iLastLine = 0; /* index of last accumulated line start */
+ /* Initialize resumable scanner(s). */
+ SqlScanState sqScanState = SSS_Start; /* for SQL scan */
+ #if SHELL_EXTENDED_PARSING
+ DCmd_ScanState dcScanState = DCSS_Start; /* for dot-command scan */
+ int ndcLeadWhite = 0; /* for skip over initial whitespace to . or # */
+ char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
+ #else
+ # define ndcLeadWhite 0 /* For legacy parsing, no white before . or # . */
+ # define cLineEnd '\n' /* For legacy parsing, this always joins lines. */
+ #endif
+ /* An ordered enum to record kind of incoming line group. Its ordering
+ * means than a value greater than Comment implies something runnable.
+ */
+ enum { Tbd = 0, Eof, Comment, Sql, Cmd /*, Tcl */ } inKind = Tbd;
+ /* An enum signifying the group disposition state */
+ enum {
+ Incoming, Runnable, Dumpable, Erroneous, Ignore
+ } disposition = Incoming;
+ char **pzLineUse = &zLineInput; /* ref line to be processed */
+ int *pncLineUse = &ncLineIn; /* ref that line's char count */
+ int iStartline = 0; /* starting line number of group */
+
fflush(p->out);
- zLine = one_input_line(p->in, zLine, nSql>0);
- if( zLine==0 ){
- /* End of input */
- if( p->in==0 && stdin_is_interactive ) fprintf(STD_OUT, "\n");
- break;
- }
- if( seenInterrupt ){
- if( p->in!=0 ) break;
- seenInterrupt = 0;
- }
- p->lineno++;
- if( QSS_INPLAIN(qss)
- && line_is_command_terminator(zLine)
- && line_is_complete(zSql, nSql) ){
- memcpy(zLine,";",2);
- }
- qss = quickscan(zLine, qss);
- if( QSS_PLAINWHITE(qss) && nSql==0 ){
- if( ShellHasFlag(p, SHFLG_Echo) )
- fprintf(STD_OUT, "%s\n", zLine);
- /* Just swallow single-line whitespace */
- qss = QSS_Start;
- continue;
- }
- if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
- if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zLine);
- if( zLine[0]=='.' ){
- rc = do_meta_command(zLine, p);
- if( rc==2 || p->abruptExit!=0 ){ /* exit requested */
+ zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
+ inKind = Eof;
+ disposition = Ignore;
+ if( bInteractive ) printf("\n");
+ }else{
+ ++nGroupLines;
+ iStartline = p->pInSource->lineno;
+ ncLineIn = strlen30(zLineInput);
+ if( seenInterrupt ){
+ if( p->pInSource!=0 ) break;
+ bInterrupted = 1; /* This will be honored, or not, later. */
+ seenInterrupt = 0;
+ disposition = Dumpable;
+ }
+ /* Classify and check for single-line dispositions, prep for more. */
+ #if SHELL_EXTENDED_PARSING
+ ndcLeadWhite = (SHEXT_PARSING(p))
+ ? skipWhite(zLineInput)-zLineInput
+ : 0; /* Disallow leading whitespace for . or # in legacy mode. */
+ #endif
+ switch( zLineInput[ndcLeadWhite] ){
+ case '.':
+ inKind = Cmd;
+ dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState);
+ break;
+ case '#':
+ inKind = Comment;
+ break;
+ default:
+ /* Might be SQL, or a swallowable whole SQL comment. */
+ sql_prescan(zLineInput, &sqScanState);
+ if( SSS_PLAINWHITE(sqScanState) ){
+ /* It's either all blank or a whole SQL comment. Swallowable. */
+ inKind = Comment;
+ }else{
+ /* Something dark, not a # comment or dot-command. Must be SQL. */
+ inKind = Sql;
+ }
+ break;
+ } /* end classification switch */
+ } /* end read/classify initial group input line */
+
+ /* Here, if not at end of input, the initial line of group is in, and
+ * it has been scanned and classified. Next, do the processing needed
+ * to recognize whether the initial line or accumulated group so far
+ * is complete such that it may be run, and perform joining of more
+ * lines into the group while it is not so complete. This loop ends
+ * with the input group line(s) ready to be run, or if the input ends
+ * before it is ready, with the group marked as erroneous.
+ */
+ while( disposition==Incoming ){
+ /* Check whether more to accumulate, or ready for final disposition. */
+ switch( inKind ){
+ case Comment:
+ disposition = Dumpable;
+ case Cmd:
+ #if SHELL_EXTENDED_PARSING
+ if( SHEXT_PARSING(p) ){
+ if( line_join_ends(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
+ disposition = Runnable;
+ }
+ }else
+ #endif
+ disposition = Runnable; /* Legacy, any dot-command line is ready. */
+ break;
+ case Sql:
+ /* Check to see if it is complete and ready to run. */
+ if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){
+ disposition = Runnable;
+ }else if( SSS_PLAINWHITE(sqScanState) ){
+ /* It is a leading single-line or multi-line comment. */
+ disposition = Runnable;
+ inKind = Comment;
+ }else{
+ char *zT = line_is_command_terminator(zLineInput);
+ if( zT!=0 ){
+ /* Last line is a lone go or / -- prep for running it. */
+ if( nGroupLines>1 ){
+ disposition = Runnable;
+ memcpy(*pzLineUse+iLastLine,";\n",3);
+ *pncLineUse = iLastLine + 2;
+ }else{
+ /* Unless nothing preceded it, then dump it. */
+ disposition = Dumpable;
+ }
+ }
+ }
+ break;
+ } /* end switch on inKind */
+ /* Collect and accumulate more input if group not yet complete. */
+ if( disposition==Incoming ){
+ if( nGroupLines==1 ){
+ grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
+ /* Copy line just input */
+ memcpy(zLineAccum, zLineInput, ncLineIn);
+ ncLineAcc = ncLineIn;
+ pzLineUse = &zLineAccum;
+ pncLineUse = &ncLineAcc;
+ }
+ /* Read in next line of group, (if available.) */
+ zLineInput = one_input_line(p->pInSource, zLineInput, nGroupLines>0);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
+ inKind = Eof;
+ disposition = Erroneous;
+ if( bInteractive ) printf("\n");
+ continue;
+ }
+ ++nGroupLines;
+ ncLineIn = strlen30(zLineInput);
+ /* Scan line just input (if needed) and append to accumulation. */
+ switch( inKind ){
+ case Cmd:
+ dot_command_scan(zLineInput, &dcScanState);
+ break;
+ case Sql:
+ sql_prescan(zLineInput, &sqScanState);
+ break;
+ default:
break;
- }else if( rc ){
- errCnt++;
}
+ grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+ncLineIn+2);
+ /* Join lines as setup by exam of previous line(s). */
+ if( cLineEnd!=0 ) zLineAccum[ncLineAcc++] = cLineEnd;
+ cLineEnd = '\n'; /* reset to default after use */
+ memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
+ iLastLine = ncLineAcc;
+ ncLineAcc += ncLineIn;
+ zLineAccum[ncLineAcc] = 0;
+ } /* end glom another line */
+ } /* end group collection loop */
+ /* Here, the group is fully collected or known to be incomplete forever. */
+ switch( disposition ){
+ case Dumpable:
+ echo_group_input(p, *pzLineUse);
+ break;
+ case Runnable:
+ switch( inKind ){
+ case Sql:
+ /* runOneSqlLine() does its own echo when requested. */
+ nErrors += runOneSqlLine(p, *pzLineUse,
+ INSOURCE_IS_INTERACTIVE(p->pInSource),
+ iStartline);
+ if( bail_on_error && nErrors>0 ) bErrorBail = 1;
+ break;
+ case Cmd:
+ echo_group_input(p, *pzLineUse);
+ switch( do_meta_command(*pzLineUse+ndcLeadWhite, p) ){
+ default: ++nErrors; /* fall thru */
+ case 0: break;
+ case 2: bExitDemand = 1; break;
+ }
+ break;
+ default:
+ assert(inKind!=Tbd);
+ break;
}
- qss = QSS_Start;
- continue;
- }
- /* No single-line dispositions remain; accumulate line(s). */
- nLine = strlen30(zLine);
- if( nSql+nLine+2>=nAlloc ){
- /* Grow buffer by half-again increments when big. */
- nAlloc = nSql+(nSql>>1)+nLine+100;
- zSql = realloc(zSql, nAlloc);
- shell_check_oom(zSql);
- }
- if( nSql==0 ){
- int i;
- for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
- assert( nAlloc>0 && zSql!=0 );
- memcpy(zSql, zLine+i, nLine+1-i);
- startline = p->lineno;
- nSql = nLine-i;
- }else{
- zSql[nSql++] = '\n';
- memcpy(zSql+nSql, zLine, nLine+1);
- nSql += nLine;
- }
- if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- nSql = 0;
- if( p->outCount ){
- output_reset(p);
- p->outCount = 0;
- }else{
- clearTempFile(p);
- }
- updateSafeMode(p);
- qss = QSS_Start;
- }else if( nSql && QSS_PLAINWHITE(qss) ){
- if( ShellHasFlag(p, SHFLG_Echo) ) fprintf(STD_OUT, "%s\n", zSql);
- nSql = 0;
- qss = QSS_Start;
++ if( p->abruptExit!=0 ) bExitDemand = 1;
+ break;
+ case Erroneous:
- utf8_printf(stderr, "Error: Input incomplete at line %d of \"%s\"\n",
++ utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
+ p->pInSource->lineno, p->pInSource->zSourceSay);
+ ++nErrors;
+ break;
+ case Ignore:
+ break;
+ default: assert(0);
}
- }
- if( nSql && QSS_PLAINDARK(qss) ){
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- updateSafeMode(p);
- }
- free(zSql);
- free(zLine);
- --p->inputNesting;
- return (p->abruptExit)? SHELL_FORBIDDEN_OP : errCnt>0;
+ if( nErrors>0 && bail_on_error ) bErrorBail = 1;
+ } /* end group consume/prep/(run, dump or complain) loop */
+
+ /* Cleanup and determine return value based on flags and error count. */
+ free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
+ sqlite3_free(zLineAccum);
+
+ return ((bErrorBail | bExitDemand)<<1) + (nErrors>0);
}
/*
/*
** Read input from the file given by sqliterc_override. Or if that
** parameter is NULL, take input from ~/.sqliterc
-**
-** Returns the number of errors.
+** The return is similar to process_input() (0 success, 1 error, x abort)
*/
- static int process_sqliterc(
+ static void process_sqliterc(
ShellState *p, /* Configuration data */
const char *sqliterc_override /* Name of config file. NULL to use default */
){
if (sqliterc == NULL) {
home_dir = find_home_dir(0);
if( home_dir==0 ){
- raw_printf(stderr, "-- warning: cannot find home directory;"
+ raw_printf(STD_ERR, "-- warning: cannot find home directory;"
" cannot read ~/.sqliterc\n");
- return 1;
+ return;
}
zBuf = sqlite3_mprintf("%s/.sqliterc",home_dir);
shell_check_oom(zBuf);
sqliterc = zBuf;
}
- p->in = fopen(sqliterc,"rb");
- if( p->in ){
+ inUse = fopen(sqliterc,"rb");
+ if( inUse!=0 ){
+ InSource inSourceRedir
+ = INSOURCE_FILE_REDIR(inUse, sqliterc, p->pInSource);
+ int rc;
+ p->pInSource = &inSourceRedir;
if( stdin_is_interactive ){
- utf8_printf(stderr,"-- Loading resources from %s\n",sqliterc);
+ utf8_printf(STD_ERR,"-- Loading resources from %s\n",sqliterc);
}
rc = process_input(p);
- fclose(p->in);
+ fclose(inUse);
- p->pInSource = inSourceRedir.pFrom; /* 0 when called by main() */
- if( rc!=0 && bail_on_error ) exit(1);
++ p->pInSource = inSourceRedir.pFrom;
++ if( rc!=0 && bail_on_error ) p->abruptExit = rc;
}else if( sqliterc_override!=0 ){
- utf8_printf(stderr,"cannot open: \"%s\"\n", sqliterc);
+ utf8_printf(STD_ERR,"cannot open: \"%s\"\n", sqliterc);
- rc = 1;
- }else{
- rc = 0;
+ if( bail_on_error ) exit(1);
}
- p->in = inSaved;
- p->lineno = savedLineno;
sqlite3_free(zBuf);
- return rc;
}
/*
" -readonly open the database read-only\n"
" -safe enable safe-mode\n"
" -separator SEP set output column separator. Default: '|'\n"
- " -shxopts BMASK enable shell extensions and options\n"
+ #if SHELL_EXTENSIONS
++ " -shxopts BMASK enable shell extensions and options (7 for all)\n"
+ #endif
#ifdef SQLITE_ENABLE_SORTER_REFERENCES
" -sorterref SIZE sorter references threshold size\n"
#endif
sqlite3_config(SQLITE_CONFIG_URI, 1);
sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
- sqlite3_snprintf(sizeof(mainPrompt), mainPrompt, "sqlite> ");
+ sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> ");
+ init_std_inputs(stdin);
}
/*
# endif
#endif
+#ifndef SHELL_MAIN
+# if SQLITE_SHELL_IS_UTF8
+# define SHELL_MAIN main
+# else
+# define SHELL_MAIN wmain
+# endif
+#endif
+
#if SQLITE_SHELL_IS_UTF8
-int SQLITE_CDECL main(int argc, char **argv){
+int SQLITE_CDECL SHELL_MAIN(int argc, char **argv){
#else
-int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
+int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
char **argv;
#endif
- char *zErrMsg = 0;
ShellState data;
const char *zInitFile = 0;
+ int bQuiet = 0; /* for testing, to suppress banner and history actions */
int i;
int rc = 0;
int warnInmemoryDb = 0;
break;
#endif
}else if( strcmp(z, "-memtrace")==0 ){
- sqlite3MemTraceActivate(stderr);
+ sqlite3MemTraceActivate(STD_ERR);
}else if( strcmp(z,"-bail")==0 ){
bail_on_error = 1;
+ #if SHELL_EXTENSIONS
+ }else if( strcmp(z,"-shxopts")==0 ){
+ data.bExtendedDotCmds = (u8)integerValue(argv[++i]);
+ #endif
}else if( strcmp(z,"-nonce")==0 ){
free(data.zNonce);
data.zNonce = strdup(argv[++i]);
+ }else if( strcmp(z,"-quiet")==0 ){
+ bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
}else if( strcmp(z,"-safe")==0 ){
- /* no-op - catch this on the second pass */
+ /* catch this on the second pass (Unsafe is fine on invocation.) */
}
}
verify_uninitialized();
** file is processed so that the command-line arguments will override
** settings in the initialization file.
*/
- for(i=1; i<argc; i++){
+ for(i=1; i<argc && rc<2; i++){
char *z = argv[i];
++ char *zModeSet = 0;
if( z[0]!='-' ) continue;
if( z[1]=='-' ){ z++; }
if( strcmp(z,"-init")==0 ){
i++;
}else if( strcmp(z,"-html")==0 ){
-- data.mode = MODE_Html;
++ zModeSet = z;
}else if( strcmp(z,"-list")==0 ){
-- data.mode = MODE_List;
++ zModeSet = z;
}else if( 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);
++ zModeSet = z;
}else if( strcmp(z,"-line")==0 ){
-- data.mode = MODE_Line;
++ zModeSet = z;
}else if( strcmp(z,"-column")==0 ){
-- data.mode = MODE_Column;
++ zModeSet = z;
}else if( strcmp(z,"-json")==0 ){
-- data.mode = MODE_Json;
++ zModeSet = z;
}else if( strcmp(z,"-markdown")==0 ){
-- data.mode = MODE_Markdown;
++ zModeSet = z;
}else if( strcmp(z,"-table")==0 ){
-- data.mode = MODE_Table;
++ zModeSet = z;
}else if( strcmp(z,"-box")==0 ){
-- data.mode = MODE_Box;
++ zModeSet = z;
}else if( strcmp(z,"-csv")==0 ){
-- data.mode = MODE_Csv;
-- memcpy(data.colSeparator,",",2);
++ zModeSet = z;
++ }else if( strcmp(z,"-ascii")==0 ){
++ zModeSet = z;
++ }else if( strcmp(z,"-tabs")==0 ){
++ zModeSet = z;
#ifdef SQLITE_HAVE_ZLIB
}else if( strcmp(z,"-zip")==0 ){
data.openMode = SHELL_OPEN_ZIPFILE;
data.openMode = SHELL_OPEN_READONLY;
}else if( strcmp(z,"-nofollow")==0 ){
data.openFlags |= SQLITE_OPEN_NOFOLLOW;
-- }else if( strcmp(z,"-ascii")==0 ){
-- data.mode = MODE_Ascii;
-- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Unit);
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Record);
- sqlite3_snprintf(sizeof(data.rowSeparator),data.rowSeparator, SEP_Record);
-- }else if( strcmp(z,"-tabs")==0 ){
-- data.mode = MODE_List;
-- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Tab);
-- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
}else if( strcmp(z,"-separator")==0 ){
sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
"%s",cmdline_option_value(argc,argv,++i));
ShellSetFlag(&data, SHFLG_Backslash);
}else if( strcmp(z,"-bail")==0 ){
/* No-op. The bail_on_error flag should already be set. */
+ #if SHELL_EXTENSIONS
+ }else if( strcmp(z,"-shxopts")==0 ){
+ i++; /* Handled on first pass. */
+ #endif
}else if( strcmp(z,"-version")==0 ){
- printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
- return 0;
+ fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
+ rc = 2;
}else if( strcmp(z,"-interactive")==0 ){
stdin_is_interactive = 1;
}else if( strcmp(z,"-batch")==0 ){
z = cmdline_option_value(argc,argv,++i);
if( z[0]=='.' ){
rc = do_meta_command(z, &data);
- if( rc && bail_on_error ){
- data.abruptExit = rc;
- rc = 2;
- goto shell_bail;
- switch( rc ){
- case 2: return 0;
- case 3: return 1;
- default: return 2;
- case 0: break;
- case 1: if( bail_on_error ) return 1;
- break;
-- }
}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;
- }
+ rc = run_single_query(&data, z);
+ }
++ if( rc && bail_on_error ){
++ if( rc==2 ) rc = 0;
++ goto shell_bail;
+ }
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
}else if( strncmp(z, "-A", 2)==0 ){
if( nCmd>0 ){
break;
#endif
}else if( strcmp(z,"-safe")==0 ){
- data.bSafeMode = data.bSafeModePersist = 1;
+ data.bSafeMode = data.bSafeModeFuture = 1;
+ }else if( strcmp(z,"-quiet")==0 ){
+ ++i;
}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;
+ 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;
+ }
- data.cMode = data.mode;
++ if( zModeSet!=0 ){
++ char *azModeCmd[] = { ".mode", zModeSet+1 };
++ modeCommand(azModeCmd, 2, &data, 0);
++ data.cMode = data.mode;
+ }
- data.cMode = data.mode;
}
if( !readStdin ){
** command-line inputs, except for the argToSkip argument which contains
** the database filename.
*/
- for(i=0; i<nCmd; i++){
+ for(i=0; i<nCmd && rc<2; i++){
if( azCmd[i][0]=='.' ){
rc = do_meta_command(azCmd[i], &data);
- if( rc && bail_on_error ){
- goto shell_bail;
- if( rc ){
- free(azCmd);
- return rc==2 ? 0 : rc;
-- }
}else{
- open_db(&data, 0);
- 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;
- }
+ rc = run_single_query(&data, azCmd[i]);
+ }
++ if( rc && bail_on_error ){
++ goto shell_bail;
+ }
}
}else{
/* Run commands received from standard input
*/
if( stdin_is_interactive ){
char *zHome;
- char *zHistory = 0;
+ char *zHistory;
int nHistory;
- 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 ){
- nHistory = strlen30(zHome) + 20;
- if( (zHistory = malloc(nHistory))!=0 ){
- sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
+ if( bQuiet ){
+ /* bQuiet is almost like normal interactive, but quieter. */
+ mainPrompt[0] = 0;
+ continuePrompt[0] = 0;
+ }else{
- printf(
++ fprintf(STD_OUT,
+ "SQLite version %s %.19s\n" /*extra-version-info*/
+ "Enter \".help\" for usage hints.\n",
+ sqlite3_libversion(), sqlite3_sourceid()
+ );
+ if( warnInmemoryDb ){
- printf("Connected to a ");
++ fprintf(STD_OUT, "Connected to a ");
+ printBold("transient in-memory database");
- printf(".\nUse \".open FILENAME\" to reopen on a "
++ fprintf(STD_OUT, ".\nUse \".open FILENAME\" to reopen on a "
+ "persistent database.\n");
}
- }
- if( zHistory ){ shell_read_history(zHistory); }
+ 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( 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;
+ }
+ data.pInSource = &termInSource; /* read from stdin interactively */
rc = process_input(&data);
- if( zHistory ){
- shell_stifle_history(2000);
- shell_write_history(zHistory);
- free(zHistory);
+ if( !bQuiet ){
+ if( zHistory ){
+ shell_stifle_history(2000);
+ shell_write_history(zHistory);
+ free(zHistory);
+ }
}
}else{
- data.in = STD_IN;
+ data.pInSource = &stdInSource; /* read from stdin without prompts */
rc = process_input(&data);
}
}
#endif
free(data.colWidth);
free(data.zNonce);
- /* Freed ShellState objects so that valgrind detects real memory leaks. */
+ for(i=0; i<data.nSavedModes; ++i) sqlite3_free(data.pModeStack[i]);
- /* Clear the global data structure so that valgrind will detect memory
- ** leaks */
+ free(azCmd);
++ /* Clear ShellState objects so that valgrind detects real memory leaks. */
+ memset(&data, 0, sizeof(data));
- return rc & ~2; /* Clear the "quit" bit. */
+ /* Process exit codes to yield single shell exit code.
+ * rc == 2 is a quit signal, resulting in no error by itself.
+ * data.bAbruptExit conveys either an normal (success or error) exit
+ * code or an abnormal exit code. Its abnormal values take priority.
+ */
+ /* Check for an abnormal exit, and issue error if so. */
+ if( rc>2 || data.abruptExit>1 ){
+ rc = (rc<data.abruptExit)? data.abruptExit : rc;
+ raw_printf(STD_ERR, "Abnormal exit (%d)\n", data.abruptExit);
+ }else{
+ /* rc is one of 0,1,2, mapping to 0,1,0 shellexit codes. */
- rc &= ~1;
++ rc &= ~2;
+ }
- memset(&data, 0, sizeof(data));
+ return rc;
}