** May you share freely, never taking more than you give.
**
*************************************************************************
- ** This file contains code to implement the "sqlite" or "sqlitex"
-** This file contains code to implement the "sqlite" command line
-** utility for accessing SQLite databases.
++** This file contains code to implement the "sqlite3" or "sqlite3x"
+** command line utilities for accessing SQLite databases.
*/
+
#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS)
/* This needs to come before any includes for MSVC compiler */
-#define _CRT_SECURE_NO_WARNINGS
+# define _CRT_SECURE_NO_WARNINGS
#endif
-typedef unsigned int u32;
-typedef unsigned short int u16;
/*
** Optionally #include a user-defined header, whereby compilation options
extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText);
#endif
- /* Forward declarations for use by resmanage. */
+/* Get the shell extension interfaces and structs. (ShellExState ...) */
+INCLUDE shext_linkage.h
+
++/* Forward declarations for use by resmanage.c package. */
+typedef struct ShellText ShellText;
+static void freeText(ShellText *p);
+#define SHELL_MANAGE_TEXT /* Cause text_holder() to be available. */
+/* Get resource management package, supporting OOM and safe mode exits. */
+INCLUDE resmanage.c
+
+/* For an embedded shell, allow the 3 standard streams to be specified.
+** If used, these names will have to refer to something globally reachable
+** from the same thread which called the shell's main().
+**/
+#ifndef STD_IN
+# define STD_IN stdin
+#endif
+#ifndef STD_OUT
+# define STD_OUT stdout
+#endif
+#ifndef STD_ERR
+# define STD_ERR stderr
+#endif
+
/* On Windows, we normally run with output mode of TEXT so that \n characters
** are automatically translated into \r\n. However, this behavior needs
** to be disabled in some cases (ex: when generating CSV output and when
** to this database a static variable so that it can be accessed
** by the SIGINT handler to interrupt database processing.
*/
-static sqlite3 *globalDb = 0;
+static volatile sqlite3 *globalDb = 0;
- /* Shorten voltatile cast-away for this. */
++/* Shorten volatile cast-away for this. */
+#define GLOBAL_DB ((sqlite3 *)globalDb)
+
+/*
+** Mutex used to access *globalDb from main thread or ^C handler and to
+** guard against referenced DB race where it is closed and interrupted.
+*/
+static sqlite3_mutex *pGlobalDbLock = 0;
/*
-** True if an interrupt (Control-C) has been received.
+** Greater than 0 if an interrupt (Control-C) has been received.
*/
static volatile int seenInterrupt = 0;
}
/*
-** 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.
+** routine that can be reused. If not used, it must be passed to free().
**
** 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.
+** be freed by the caller, using the same allocator[a], or else passed
+** back into this routine via the zPrior argument for reuse.
+**
+** [a. This function is exposed for use by shell extensions which may
+** have no access to "the same allocator". This is why the function
+** following this one exists, also exposed to shell extensions. ]
+**
+** 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.
++** has been passed in for reuse, 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.)
*/
-#ifndef SQLITE_SHELL_FIDDLE
-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, Prompts *pCue){
+ if( !INSOURCE_IS_INTERACTIVE(pInSrc) ){
+ return local_getline(zPrior, pInSrc);
}else{
- zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt;
+ static Prompts cueDefault = { "$ ","> " };
+ const char *zPrompt;
+ char *zResult;
+ if( pCue==0 ) pCue = &cueDefault;
+ zPrompt = isContinuation ? pCue->zContinue : pCue->zMain;
#if SHELL_USE_LOCAL_GETLINE
printf("%s", zPrompt);
fflush(stdout);
char zPrefix[100]; /* Graph prefix */
};
- /* ixe is just before the basename with extension(s) */
+/* By default, omit the extension options that are not done yet.
+ * "SHELL_EXTENSIONS" is short for "Some shell extensions are built in." */
+#ifndef SHELL_OMIT_EXTENSIONS
+# define SHELL_OMIT_EXTENSIONS 4
+# define SHELL_EXTENSIONS 1
+#else
+# define SHELL_EXTENSIONS \
+ (0!=((~SHELL_OMIT_EXTENSIONS) & SHELL_ALL_EXTENSIONS))
+#endif
+
+#ifdef SQLITE_OMIT_LOAD_EXTENSION
+# define SHELL_OMIT_LOAD_EXTENSION 1
+#else
+# define SHELL_OMIT_LOAD_EXTENSION 0
+#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 runtime extension via .shxload */
+#define SHEXT_DYNEXT_BIT 1
+#define SHELL_DYNAMIC_EXTENSION ( !SHELL_OMIT_LOAD_EXTENSION \
+ && NOT_IFDEF_BIT(SHELL_OMIT_EXTENSIONS, SHEXT_DYNEXT_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_DYNEXT_BIT)+(1<<SHEXT_VAREXP_BIT)
+
+/* Runtime test for shell extended parsing, given ShellInState pointer */
+#if SHELL_EXTENDED_PARSING
+# define SHEXT_PARSING(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_PARSING_BIT))!=0)
+#else
+# define SHEXT_PARSING(psi) 0
+#endif
+
+/* Runtime test for shell variable expansion, given ShellInState pointer */
+#if SHELL_EXTENDED_PARSING
+# define SHEXT_VAREXP(psi) ((psi->bExtendedDotCmds&(1<<SHEXT_VAREXP_BIT))!=0)
+#else
+# define SHEXT_VAREXP(psi) 0
+#endif
+
+/* Enable use of ExportHandler and ImportHandler interfaces for built-in I/O.
+** (This deactivates "shuffled" older code which can be deleted some day. The
+** code has been partitioned and moved as part of built-in exporters.) */
+#define SHELL_DATAIO_EXT 1
+
+#if SHELL_DYNAMIC_EXTENSION
+
+/* This is only used to support extensions that need this information.
+ * For example, they might need to locate and load related files. */
+# if defined(_WIN32) || defined(WIN32)
+static char startupDir[MAX_PATH+1] = {0};
+# define initStartupDir() (_getcwd(startupDir,MAX_PATH)!=0)
+# define IS_PATH_SEP(c) ((c)=='/'||(c)=='\\')
+# else
+static char startupDir[PATH_MAX+1] = {0};
+# define initStartupDir() (getcwd(startupDir, sizeof(startupDir))!=0)
+ /* Above useless expression avoids an "unused result" warning. */
+# define IS_PATH_SEP(c) ((c)=='/')
+# endif
+
+# ifndef SHELL_OMIT_EXTBYNAME
+/* Is a program invocation name one used for a shell to start as extensible? */
+static int isExtendedBasename(const char *zPgm){
+ int ixe = (zPgm)? (int)strlen(zPgm)-1 : 0;
+ if( ixe==0 ) return 0;
+ while( ixe>=0 && !IS_PATH_SEP(zPgm[ixe]) ) --ixe;
- * an array of this struct's instances to facilitate lookup by name
++ /* index is just before the basename with extension(s) */
+ return sqlite3_strnicmp(&zPgm[ixe+1], "sqlite3x", 8)==0;
+}
+# else
+# define isExtendedBasename(pathname) 0
+# endif
+
+/* Tracking and use info for loaded shell extensions
+ * An instance is kept for each shell extension that is currently loaded.
+ * They are kept in a simple list (aka dynamic array), index into which
+ * is used internally to get the extension's object. These indices are
+ * kept in the dbShell and updated there as the list content changes.
+ */
+typedef struct ShExtInfo {
+ ExtensionId extId; /* The xInit function pointer */
+ void (*extDtor)(void *); /* Extension shutdown on exit or unload */
+ void *pvExtObj; /* Passed to extDtor(...) at shutdown */
+ /* Each shell extension library registers 0 or more of its extension
+ * implementations, interfaces to which are kept in below dynamic.
+ * arrays. The dbShell DB keeps indices into these arrays and into
++ * an array of instances of this struct to facilitate lookup by name
+ * of pointers to the implementations. */
+ int numDotCommands;
+ DotCommand **ppDotCommands;
+ int numExportHandlers;
+ ExportHandler **ppExportHandlers;
+ int numImportHandlers;
+ ImportHandler **ppImportHandlers;
+ DotCommand *pUnknown; /* .unknown registered for this extension (unowned) */
+} ShExtInfo;
+#define SHEXT_INFO_INIT {0,0,NULL, 0,NULL, 0,NULL, 0,NULL, NULL}
+#endif
+
/* Parameters affecting columnar mode result display (defaulting together) */
typedef struct ColModeOpts {
int iWrap; /* In columnar modes, wrap lines reaching this limit */
#define ColModeOpts_default_qbox { 60, 1, 0 }
/*
-** State information about the database connection is contained in an
-** instance of the following structure.
+** Stored output mode state, for partial save and later restore.
+** Returned by:
- ** outputModeSave *outputModeSave(ShellInState *p, SaveModeWhat ws).
++** outputModeSave *outputModeSave(ShellInState *p, SaveModeWhat what).
+** Accepted by:
+** outputModeRestore(ShellInState *p, OutputModeSave *pSaved).
+** See enum SaveWhatMode regarding what to save and restore.
+** Also see outputModePush(...), outputModePushSome(...) and
+** outputModePop(...) for usages spanning more than one call.
*/
-typedef struct ShellState ShellState;
-struct ShellState {
- sqlite3 *db; /* The database */
+typedef struct OutputModeSave{
+ u16 what; /* Set upon creation. See SaveWhatMode for values. */
+ char itsValues[1]; /* This size is inaccurate unless nothing is saved. */
+} OutputModeSave;
+#define MODE_STACK_MAX 3 /* How many levels of saved output mode to allow. */
+
+/*
+** Shell state information is contained in an instance of the following struct
- ** and in a ShellExState struct defined in shext_linkage.h, partitioned thusly:
++** and in a ShellExState struct defined in shext_linkage.h, partitioned thus:
+**
+** Data not exposed for shell extension use, (and which need not be stable),
+** are kept in a ShellInState instance. These include facts about the database
+** connection, specialized display mode setup, safe mode control, and other
+** data associated with the shell's means of operation.
+**
+** Data which is exposed for shell extension use, (and which should be stable
+** or grown only with new members at the end, to preserve layout), is kept
+** in the ShellExState instance, which contains a ShellInState pointer, pSIS.
+*/
+
+/* Oft-used macros for transition to internal/external shell state access
+ * ISS(psx) : internal shell state (pointer)
+ * XSS(psi) : external shell state (pointer)
+ * DBI(psi) : DB from internal pointer
+ * DBX(psx) : DB from external pointer
+ */
+#define ISS(psx) (psx)->pSIS
+#define XSS(psi) (psi)->pSXS
+#define DBI(psi) (psi)->pSXS->dbUser
+#define DBX(psx) (psx)->dbUser
+
+typedef struct ShellInState {
+ int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */
+ u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */
+ sqlite3_int64 szMax; /* --maxsize argument to .open */
u8 autoExplain; /* Automatically turn on .explain mode */
- u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */
+ u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */
u8 autoEQPtest; /* autoEQP is in test mode */
u8 autoEQPtrace; /* autoEQP is in trace mode */
u8 scanstatsOn; /* True to display scan stats before each finalize */
u8 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 */
- /* Output mode state-keep for certain ad-hoc save/restore ops: */
+ 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 save/restore operations: */
+ u8 cMode; /* temporary output mode for the current query */
+ u8 normalMode; /* Output mode before ".explain on" */
+ 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 */
+ unsigned mEqpLines; /* Mask of vertical 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 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 */
int *aiIndent; /* Array of indents used in MODE_Explain */
int nIndent; /* Size of array aiIndent[] */
int iIndent; /* Index of current op in aiIndent[] */
- char *zNonce; /* Nonce for temporary safe-mode escapes */
+ char *zNonce; /* Nonce for temporary safe-mode suspension */
EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */
ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */
+
#ifdef SQLITE_SHELL_FIDDLE
- struct {
- const char * zInput; /* Input string from wasm/JS proxy */
- const char * zPos; /* Cursor pos into zInput */
- const char * zDefaultDbName; /* Default name for db file */
- } wasm;
+ const char * zDefaultDbName; /* Default name for db file (? not used here) */
#endif
-};
+
+#if SHELL_DYNAMIC_EXTENSION
+ /* extension management */
+ int numExtLoaded; /* Number of extensions presently loaded or emulated */
+ ShExtInfo *pShxLoaded; /* Tracking and use info for loaded shell extensions */
+ int ixExtPending; /* Index of pending extension load operations if !0 */
+ /* scripting integration */
+ ScriptSupport *script; /* Scripting support, if any, from loaded extension */
+ ExtensionId scriptXid; /* Id of extension which is supporting scripting */
+ /* shell event subscription list */
+ int numSubscriptions; /* Number of active entries in below list */
+ struct EventSubscription {
+ ExtensionId eid;
+ void *pvUserData;
+ ShellEventNotify eventHandler;
+ } *pSubscriptions; /* The current shell event subscriptions */
+ u8 bDbDispatch; /* Cache fact of dbShell dispatch table */
+ DotCommand *pUnknown; /* Last registered "unknown" dot command */
+#endif
+
+#if SHELL_DATAIO_EXT
- ExportHandler *pFreeformExporter; /* Default freeform mode exporter */
++ ExportHandler *pFreeformExporter; /* Default free-form mode exporter */
+ ExportHandler *pColumnarExporter; /* Default columnar mode exporter */
+ ExportHandler *pActiveExporter; /* Presently active exporter */
+#endif
+
+ ShellExState *pSXS; /* Pointer to companion, exposed shell state */
+} ShellInState;
#ifdef SQLITE_SHELL_FIDDLE
-static ShellState shellState;
+/* For WASM, keep a static instance so pseudo-main can be called repeatedly. */
+static ShellInState shellStateI;
+static ShellExState shellStateX;
#endif
+/*
+** 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 (dot-command, SQL submission, or script execution) 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(ShellInState *psi){
+ switch( psi->bSafeModeFuture ){
+ case 2:
+ default:
+ --psi->bSafeModeFuture;
+ deliberate_fall_through;
+ case 0:
+ psi->bSafeMode = 0;
+ break;
+ case 1:
+ psi->bSafeMode = 1;
+ }
+}
-/* Allowed values for ShellState.autoEQP
+/* Allowed values for ShellInState.autoEQP
*/
#define AUTOEQP_off 0 /* Automatic EXPLAIN QUERY PLAN is off */
#define AUTOEQP_on 1 /* Automatic EQP is on */
#define SHELL_TRACE_EXPANDED 1 /* Show expanded SQL text */
#define SHELL_TRACE_NORMALIZED 2 /* Show normalized SQL text */
-/* Bits in the ShellState.flgProgress variable */
+/* Bits in the ShellInState.flgProgress variable */
#define SHELL_PROGRESS_QUIET 0x01 /* Omit announcing every progress callback */
- #define SHELL_PROGRESS_RESET 0x02 /* Reset the count when the progres
+ #define SHELL_PROGRESS_RESET 0x02 /* Reset the count when the progress
** callback limit is reached, and for each
** top-level SQL statement */
#define SHELL_PROGRESS_ONCE 0x04 /* Cancel the --limit after firing once */
/*
** If in safe mode, print an error message described by the arguments
-** and exit immediately.
+** and arrange for a semi-abrupt exit. Unsafe actions are blocked
+** by their doers calling this and not acting if 1 is returned.
+** The process_input() routine detects the semi-abrupt exit, (which
+** is indicated by the external shell state member, shellAbruptExit
+** being set non-zero), and aborts any further input processing.
+**
- ** The return is 1 if failing for a forbidded unsafe act, 0 otherwise.
++** The return is 1 if failing for a forbidden unsafe act, 0 otherwise.
*/
-static void failIfSafeMode(
- ShellState *p,
+static int failIfSafeMode(
+ ShellExState *psx,
const char *zErrMsg,
...
){
/*
** This routine runs when the user presses Ctrl-C
- ** defined action when/if DB operation becomes uninterruptable.
+** This may need some changes for embedability, perhaps a client-
++** defined action when/if DB operation becomes uninterruptible.
*/
static void interrupt_handler(int NotUsed){
UNUSED_PARAMETER(NotUsed);
sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace);
}
- PTU_Nil = 3 /* Unspecified */
+/* DB schema protection for use by next two utility functions */
+typedef struct DbProtectState {
+ int wrSchema;
+ int defensiveMode;
+} DbProtectState;
+
+/* Allow system (sqlite_*) schema changes, return prior protection state. */
+static DbProtectState allow_sys_schema_change(sqlite3 *db){
+ DbProtectState rv;
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, -1, &rv.defensiveMode);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &rv.wrSchema);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
+ return rv;
+}
+
+/* Restore protection state using allow_sys_schema_change() return. */
+static void restore_sys_schema_protection( sqlite3 *db, DbProtectState *pPS ){
+ sqlite3_db_config(db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, pPS->wrSchema, 0);
+ sqlite3_db_config(db, SQLITE_DBCONFIG_DEFENSIVE, pPS->defensiveMode, 0);
+}
+
+/* Partition the temp.sqlite_parameters and main.sqlite_variables
+ * key namespace according to use.
+ */
+typedef enum {
+ PTU_Binding = 0, /* Binding parameter, value to be bound */
+#define SPTU_Binding SHELL_STRINGIFY(0)
+ PTU_Script = 1, /* A value containing shell SQL and dot-commands */
+#define SPTU_Script SHELL_STRINGIFY(1)
+ PTU_Entry = 2, /* Value as entered for non-text binding parameters */
+#define SPTU_Entry SHELL_STRINGIFY(2)
- /* Possibly using a -editor=X argument and env-var VISUAL, attempt
- * to get the zEditor shell state member set iff not already set.
- * If there is no such argument, the env-var is retrieved if set.
++ PTU_Ref = 3, /* Name was recently referenced */
++#define SPTU_Ref SHELL_STRINGIFY(3)
++ PTU_Nil = 4 /* Unspecified */
+} ParamTableUse;
+
+static ParamTableUse classify_param_name( const char *zName ){
+ char c = *zName;
+ switch( c ){
+ case '$': case ':': case '@': case '?': return PTU_Binding;
+ default: return isalpha(c)? PTU_Script : PTU_Nil;
+ }
+}
+
+#ifndef SQLITE_NOHAVE_SYSTEM
++/* Possibly using a -editor=X argument and environment variable VISUAL,
++ * attempt to get the zEditor shell state member set iff not already set.
++ * If there is no such argument, said variable is retrieved if set.
+ * If the argument is -editor=X or --editor=X, use that and leave
+ * the zEditor member set accordingly. Returns are:
+ * 0 => editor set, zEd was not the -editor option
+ * 1 => editor set, zEd consumed as -editor option
+ * -1 => editor not set, and error/advice message issued.
+ *
+ * This implements an undocumented fall-back for the .vars and
+ * .parameters edit subcommands, so that users need not restart
+ * a shell session to get an editor specified upon need for it. */
+int attempt_editor_set(ShellInState *psi, char *zDot, const char *zEd){
+ if( psi->zEditor==0 ){
+ const char *zE = getenv("VISUAL");
+ if( zE!=0 ) psi->zEditor = smprintf("%s", zE);
+ }
+ if( zEd && zEd[0]=='-' ){
+ zEd += 1 + (zEd[1]=='-');
+ if( cli_strncmp(zEd,"editor=",7)==0 ){
+ sqlite3_free(psi->zEditor);
+ /* Accept an initial -editor=? option. */
+ psi->zEditor = smprintf("%s", zEd+7);
+ return 1;
+ }
+ }
+ if( psi->zEditor==0 ){
+ utf8_printf(STD_ERR,
+ "Either set env-var VISUAL to name an"
+ " editor and restart, or rerun\n "
+ ".%s edit with an initial edit option,"
+ " --editor=EDITOR_COMMAND .\n", zDot);
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+/* The table kept for user DBs if .parameter command is used usefully. */
+#define PARAM_TABLE_NAME "sqlite_parameters"
+#define PARAM_TABLE_SCHEMA "temp"
+#define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME
+
+/* The table kept for the shell DB if .vars command is used usefully. */
+#define SHVAR_TABLE_NAME "sqlite_variables"
+#define SHVAR_TABLE_SCHEMA "main"
+#define SHVAR_TABLE_SNAME SHVAR_TABLE_SCHEMA"."SHVAR_TABLE_NAME
+
+#ifndef SH_KV_STORE_NAME
+/* Name for table keeping user's saved parameters */
+# define SH_KV_STORE_NAME "SQLiteShell_KeyValuePairs"
+#endif
+#ifndef SH_KV_STORE_SCHEMA
+/* Schema name used to attach saved parameters DB during load/save */
+# define SH_KV_STORE_SCHEMA "SQLiteShell"
+#endif
+#define SH_KV_STORE_SNAME SH_KV_STORE_SCHEMA"."SH_KV_STORE_NAME
+
/* Create the TEMP table used to store parameter bindings */
-static void bind_table_init(ShellState *p){
- int wrSchema = 0;
- int defensiveMode = 0;
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, -1, &wrSchema);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
- sqlite3_exec(p->db,
- "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n"
+static void param_table_init(sqlite3 *db){
+ DbProtectState dps = allow_sys_schema_change(db);
+ s3_exec_noom(db,
+ "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n"
- " key TEXT PRIMARY KEY,\n"
++ " key TEXT,\n"
+ " value,\n"
- " uses INT DEFAULT ("SPTU_Binding")"
- ") WITHOUT ROWID;",
++ " uses INT DEFAULT ("SPTU_Binding"),"
++ " UNIQUE(key,uses) ON CONFLICT REPLACE"
++ ")",
+ 0, 0, 0);
+ restore_sys_schema_protection( db, &dps );
+}
+
+/* Tell whether the above-created table exists, return true iff exists. */
+static int param_table_exists( sqlite3 *db ){
+ return sqlite3_table_column_metadata
+ (db, PARAM_TABLE_SCHEMA, PARAM_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
+}
+
+/* Create the shell DB table used to store shell variables or scripts */
+static int shvars_table_init(sqlite3 *db){
+ DbProtectState dps = allow_sys_schema_change(db);
+ int rc = s3_exec_noom(db,
+ "CREATE TABLE IF NOT EXISTS "SHVAR_TABLE_SNAME"(\n"
" key TEXT PRIMARY KEY,\n"
- " value\n"
- ") WITHOUT ROWID;",
+ " value,\n"
+ " uses INT DEFAULT ("SPTU_Script")"
- ") WITHOUT ROWID;",
++ ") WITHOUT ROWID",
0, 0, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, defensiveMode, 0);
+ restore_sys_schema_protection( db, &dps );
+ return rc!=SQLITE_OK;
+}
+
+/* Tell whether the above-created table exists, return true iff exists. */
+static int shvars_table_exists( sqlite3 *db ){
+ return sqlite3_table_column_metadata
+ (db, SHVAR_TABLE_SCHEMA, SHVAR_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK;
+}
+
+/* Make shell vars table exist. */
+static int ensure_shvars_table(sqlite3 *dbs){
+ if( shvars_table_exists(dbs) ) return SQLITE_OK;
+ else return shvars_table_init(dbs);
}
/*
** Bind parameters on a prepared statement.
**
-** Parameter bindings are taken from a TEMP table of the form:
+** Normal parameter bindings are taken from a TEMP table of the form:
**
-** CREATE TEMP TABLE sqlite_parameters(key TEXT PRIMARY KEY, value)
+** CREATE TEMP TABLE
+** sqlite_parameters(key TEXT PRIMARY KEY, value, uses INT)
** WITHOUT ROWID;
**
-** 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.
+** Normal bindings do not 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. Only rows with
+** uses=PTU_Binding are eligible for parameter binding.
+** Whether the table exists or not, parameter names like _NAN or _INF are
+** bound to the similarly named floating point "values".
++**
++** If noteUses is non-zero, binding attempts and success are recorded in the
++** sqlite_parameters table with uses = PTU_Binding. Failed attempts get value
++** 0 and successful findings get value 1.
*/
- static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt){
-static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
- int nVar;
++static void bind_prepared_stmt(sqlite3 *db, sqlite3_stmt *pStmt, u8 noteUses){
+ int nVar = sqlite3_bind_parameter_count(pStmt);
int i;
int rc;
+ int haveParams = param_table_exists(db);
sqlite3_stmt *pQ = 0;
++ sqlite3_stmt *pU = 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 ){
- rc = SQLITE_NOTFOUND;
- pQ = 0;
- }else{
- rc = sqlite3_prepare_v2(pArg->db,
- "SELECT value FROM temp.sqlite_parameters"
- " WHERE key=?1", -1, &pQ, 0);
- }
+ if( haveParams ){
+ rc = s3_prepare_v2_noom(db,
- "SELECT value FROM temp.sqlite_parameters"
- " WHERE key=?1", -1, &pQ, 0);
++ "SELECT value FROM temp."PARAM_TABLE_NAME
++ " WHERE key=?1 AND uses="SPTU_Binding, -1, &pQ, 0);
+ if( rc!=SQLITE_OK || pQ==0 ) haveParams = 0;
++ else if( noteUses ){
++ rc = s3_prepare_v2_noom(db,
++ "INSERT INTO temp."PARAM_TABLE_NAME
++ "(key, value, uses) VALUES (?1,?2,"SPTU_Ref")",
++ -1, &pU, 0);
++ s3_exec_noom(db,
++ "DELETE FROM temp."PARAM_TABLE_NAME" WHERE uses="SPTU_Ref,
++ 0,0,0);
++ if( rc!=SQLITE_OK ) noteUses = 0;
++ }
+ }
+ stmt_holder(pQ);
++ stmt_holder(pU);
for(i=1; i<=nVar; i++){
char zNum[30];
const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
zVar = zNum;
}
sqlite3_bind_text(pQ, 1, zVar, -1, SQLITE_STATIC);
- if( rc==SQLITE_OK && pQ && sqlite3_step(pQ)==SQLITE_ROW ){
++ if( haveParams && noteUses ){
++ sqlite3_reset(pU);
++ sqlite3_bind_text(pU, 1, zVar, -1, SQLITE_STATIC);
++ sqlite3_bind_int(pU, 2, 0);
++ }
+ if( haveParams && s3_step_noom(pQ)==SQLITE_ROW ){
sqlite3_bind_value(pStmt, i, sqlite3_column_value(pQ, 0));
++ if( noteUses ){
++ sqlite3_bind_int(pU, 2, 1);
++ s3_step_noom(pU);
++ }
#ifdef NAN
}else if( sqlite3_strlike("_NAN", zVar, 0)==0 ){
sqlite3_bind_double(pStmt, i, NAN);
sqlite3_bind_double(pStmt, i, INFINITY);
#endif
}else{
++ if( haveParams && noteUses ) s3_step_noom(pU);
sqlite3_bind_null(pStmt, i);
}
- sqlite3_reset(pQ);
+ sqlite3_reset(pQ); /* Undocumented: NULL pQ is ok. */
}
- release_holder();
- sqlite3_finalize(pQ);
++ release_holders(2);
}
/*
return 0; /* Not reached */
}
- sqlite3_stmt *pStmt /* Statment to run */
+#if SHELL_DATAIO_EXT
+/*
+** Run a prepared statement with output as determined by ExportHandler.
+*/
+static void exec_prepared_stmt(
+ ShellExState *psx, /* Pointer to shell state */
++ sqlite3_stmt *pStmt /* Statement to run */
+){
+ ShellInState *psi = ISS(psx);
+ ExportHandler *pExporter = psi->pActiveExporter;
+ char *zErr = 0;
+ int rc;
+
+ rc = pExporter->pMethods->prependResultsOut(pExporter, psx, &zErr, pStmt);
+ if( rc==SQLITE_OK ){
+ do{
+ rc = pExporter->pMethods->rowResultsOut(pExporter, psx, &zErr, pStmt);
+ }while( rc==SQLITE_ROW );
+ rc = pExporter->pMethods->appendResultsOut(pExporter, psx, &zErr, pStmt);
+ }
+}
+
+#else /* !SHELL_DATAIO_EXT */
+
/*
** 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
** any output.
*/
static void exec_prepared_stmt_columnar(
- ShellState *p, /* Pointer to ShellState */
+ ShellExState *psx, /* Pointer to shell state */
- sqlite3_stmt *pStmt /* Statment to run */
+ sqlite3_stmt *pStmt /* Statement to run */
){
+ ShellInState *psi = ISS(psx);
sqlite3_int64 nRow = 0;
int nColumn = 0;
char **azData = 0;
** Run a prepared statement
*/
static void exec_prepared_stmt(
- ShellState *pArg, /* Pointer to ShellState */
+ ShellExState *pArg, /* Pointer to shell state */
- sqlite3_stmt *pStmt /* Statment to run */
+ sqlite3_stmt *pStmt /* Statement to run */
){
int rc;
+ ShellInState *psi = ISS(pArg);
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(psi->cMode) ){
exec_prepared_stmt_columnar(pArg, pStmt);
return;
}
}
zStmtSql = sqlite3_sql(pStmt);
if( zStmtSql==0 ) zStmtSql = "";
- while( IsSpace(zStmtSql[0]) ) zStmtSql++;
+ else zStmtSql = skipWhite(zStmtSql);
- /* save off the prepared statment handle and reset row count */
+ /* save off the prepared statement handle and reset row count */
- if( pArg ){
- pArg->pStmt = pStmt;
- pArg->cnt = 0;
+ if( psx ){
+ psi->pStmt = pStmt;
+ psx->resultCount = 0;
}
/* Show the EXPLAIN QUERY PLAN if .eqp is on */
int iEqpId = sqlite3_column_int(pExplain, 0);
int iParentId = sqlite3_column_int(pExplain, 1);
if( zEQPLine==0 ) zEQPLine = "";
- if( zEQPLine[0]=='-' ) eqp_render(pArg, 0);
- eqp_append(pArg, iEqpId, iParentId, zEQPLine);
+ if( zEQPLine[0]=='-' ) eqp_render(psi, 0);
+ eqp_append(psi, iEqpId, iParentId, zEQPLine);
}
- eqp_render(pArg, 0);
+ eqp_render(psi, 0);
}
sqlite3_finalize(pExplain);
- sqlite3_free(zEQP);
- if( pArg->autoEQP>=AUTOEQP_full ){
+ pExplain = 0;
+ if( psi->autoEQP>=AUTOEQP_full ){
/* Also do an EXPLAIN for ".eqp full" mode */
- zEQP = sqlite3_mprintf("EXPLAIN %s", zStmtSql);
- shell_check_oom(zEQP);
- rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
+ zEQP = smprintf("EXPLAIN %s", zStmtSql);
+ rc = s3_prep_noom_free(db, &zEQP, &pExplain);
if( rc==SQLITE_OK ){
- pArg->cMode = MODE_Explain;
- explain_data_prepare(pArg, pExplain);
- exec_prepared_stmt(pArg, pExplain);
- explain_data_delete(pArg);
+ explain_data_prepare(psi, pExplain);
+ psi->cMode = MODE_Explain;
+ {
+#if SHELL_DATAIO_EXT
+ ExportHandler *pexSave = psi->pActiveExporter;
+ psi->pActiveExporter = psi->pFreeformExporter;
+#endif
+ exec_prepared_stmt(psx, pExplain);
+#if SHELL_DATAIO_EXT
+ psi->pActiveExporter = pexSave;
+#endif
+ }
+ explain_data_delete(psi);
}
sqlite3_finalize(pExplain);
- sqlite3_free(zEQP);
+ pExplain = 0;
}
- if( pArg->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
+ if( psi->autoEQP>=AUTOEQP_trigger && triggerEQP==0 ){
sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, 0, 0);
- /* Reprepare pStmt before reactiving trace modes */
+ /* Reprepare pStmt before reactivating trace modes */
sqlite3_finalize(pStmt);
- sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- if( pArg ) pArg->pStmt = pStmt;
+ pStmt = 0;
+ s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0);
+ if( psx ) psi->pStmt = pStmt;
}
restore_debug_trace_modes();
}
}
}
- bind_prepared_stmt(DBX(psx), pStmt);
- bind_prepared_stmt(pArg, pStmt);
- exec_prepared_stmt(pArg, pStmt);
- explain_data_delete(pArg);
- eqp_render(pArg, 0);
++ bind_prepared_stmt(DBX(psx), pStmt, 1);
+ exec_prepared_stmt(psx, pStmt);
+ explain_data_delete(psi);
+ eqp_render(psi, 0);
/* print usage stats if stats on */
- if( pArg && pArg->statsOn ){
- display_stats(db, pArg, 0);
+ if( psx && psi->statsOn ){
+ display_stats(db, psi, 0);
}
/* print loop-counters if required */
return rc;
}
+/* Configure help text generation to have coalesced secondary help lines
+ * with trailing newlines on all help lines. This allow help text to be
+ * representable as an array of two C-strings per dot-command.
+ */
+DISPATCH_CONFIG[
+ HELP_COALESCE=1
+];
+#define HELP_TEXT_FMTP ".%s"
+#define HELP_TEXT_FMTS "%s"
+/* Above HELP_COALESCE config and HELP_TEXT_FMT PP vars must track.
+ * Alternative is 0, ".%s\n" and "%s\n" .
+ */
+
+/* Forward references */
+static int showHelp(FILE *out, const char *zPattern, ShellExState *);
+static DotCmdRC process_input(ShellInState *psx);
+static DotCommand *builtInCommand(int ix);
+
/*
-** Text of help messages.
+** Read the content of file zName into memory obtained from sqlite3_malloc64()
+** and return a pointer to the buffer. The caller is responsible for freeing
+** the memory.
+**
+** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
+** read.
+**
+** 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.
**
-** The help text for each individual command begins with a line that starts
-** with ".". Subsequent lines are supplemental information.
+** NULL is returned if any error is encountered. The final value of *pnByte
+** is undefined in this case.
**
- ** This function always returns; no abrubt OOM exits are taken.
-** There must be two or more spaces between the end of the command and the
-** start of the description of what that command does.
++** This function always returns; no abrupt OOM exits are taken.
*/
-static const char *(azHelp[]) = {
-#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
- && !defined(SQLITE_SHELL_FIDDLE)
- ".archive ... Manage SQL archives",
- " Each command must have exactly one of the following options:",
- " -c, --create Create a new archive",
- " -u, --update Add or update files with changed mtime",
- " -i, --insert Like -u but always add even if unchanged",
- " -r, --remove Remove files from archive",
- " -t, --list List contents of archive",
- " -x, --extract Extract files from archive",
- " Optional arguments:",
- " -v, --verbose Print each filename as it is processed",
- " -f FILE, --file FILE Use archive FILE (default is current db)",
- " -a FILE, --append FILE Open FILE using the apndvfs VFS",
- " -C DIR, --directory DIR Read/extract files from directory DIR",
- " -g, --glob Use glob matching for names in archive",
- " -n, --dryrun Show the SQL that would have occurred",
- " Examples:",
- " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar",
- " .ar -tf ARCHIVE # List members of ARCHIVE",
- " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE",
- " See also:",
- " http://sqlite.org/cli.html#sqlite_archive_support",
-#endif
-#ifndef SQLITE_OMIT_AUTHORIZATION
- ".auth ON|OFF Show authorizer callbacks",
-#endif
-#ifndef SQLITE_SHELL_FIDDLE
- ".backup ?DB? FILE Backup DB (default \"main\") to FILE",
- " Options:",
- " --append Use the appendvfs",
- " --async Write to FILE without journal and fsync()",
-#endif
- ".bail on|off Stop after hitting an error. Default OFF",
- ".binary on|off Turn binary output on or off. Default OFF",
-#ifndef SQLITE_SHELL_FIDDLE
- ".cd DIRECTORY Change the working directory to DIRECTORY",
-#endif
- ".changes on|off Show number of rows changed by SQL",
-#ifndef SQLITE_SHELL_FIDDLE
- ".check GLOB Fail if output since .testcase does not match",
- ".clone NEWDB Clone data into NEWDB from the existing database",
-#endif
- ".connection [close] [#] Open or close an auxiliary database connection",
- ".databases List names and files of attached databases",
- ".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
-#if SQLITE_SHELL_HAVE_RECOVER
- ".dbinfo ?DB? Show status information about the database",
-#endif
- ".dump ?OBJECTS? Render database content as SQL",
- " Options:",
- " --data-only Output only INSERT statements",
- " --newlines Allow unescaped newline characters in output",
- " --nosys Omit system tables (ex: \"sqlite_stat1\")",
- " --preserve-rowids Include ROWID values in the output",
- " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump",
- " Additional LIKE patterns can be given in subsequent arguments",
- ".echo on|off Turn command echo on or off",
- ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN",
- " Other Modes:",
-#ifdef SQLITE_DEBUG
- " test Show raw EXPLAIN QUERY PLAN output",
- " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
-#endif
- " trigger Like \"full\" but also show trigger bytecode",
-#ifndef SQLITE_SHELL_FIDDLE
- ".excel Display the output of next command in spreadsheet",
- " --bom Put a UTF8 byte-order mark on intermediate file",
-#endif
-#ifndef SQLITE_SHELL_FIDDLE
- ".exit ?CODE? Exit this program with return-code CODE",
-#endif
- ".expert EXPERIMENTAL. Suggest indexes for queries",
- ".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
- ".filectrl CMD ... Run various sqlite3_file_control() operations",
- " --schema SCHEMA Use SCHEMA instead of \"main\"",
- " --help Show CMD details",
- ".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
- ".headers on|off Turn display of headers on or off",
- ".help ?-all? ?PATTERN? Show help text for PATTERN",
-#ifndef SQLITE_SHELL_FIDDLE
- ".import FILE TABLE Import data from FILE into TABLE",
- " Options:",
- " --ascii Use \\037 and \\036 as column and row separators",
- " --csv Use , and \\n as column and row separators",
- " --skip N Skip the first N rows of input",
- " --schema S Target table to be S.TABLE",
- " -v \"Verbose\" - increase auxiliary output",
- " Notes:",
- " * If TABLE does not exist, it is created. The first row of input",
- " determines the column names.",
- " * If neither --csv or --ascii are used, the input mode is derived",
- " from the \".mode\" output mode",
- " * If FILE begins with \"|\" then it is a command that generates the",
- " input text.",
-#endif
-#ifndef SQLITE_OMIT_TEST_CONTROL
- ",imposter INDEX TABLE Create imposter table TABLE on index INDEX",
-#endif
- ".indexes ?TABLE? Show names of indexes",
- " If TABLE is specified, only show indexes for",
- " tables matching TABLE using the LIKE operator.",
-#ifdef SQLITE_ENABLE_IOTRACE
- ",iotrace FILE Enable I/O diagnostic logging to FILE",
-#endif
- ".limit ?LIMIT? ?VAL? Display or change the value of an SQLITE_LIMIT",
- ".lint OPTIONS Report potential schema issues.",
- " Options:",
- " fkey-indexes Find missing foreign key indexes",
-#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
- ".load FILE ?ENTRY? Load an extension library",
+static char *readFile(const char *zName, int *pnByte){
+ FILE *in = fopen(zName, "rb");
+ long nIn;
+ size_t nRead;
+ char *pBuf;
+ int rc;
+ if( in==0 ) return 0;
+ rc = fseek(in, 0, SEEK_END);
+ if( rc!=0 ){
+ raw_printf(stderr, "Error: '%s' not seekable\n", zName);
+ fclose(in);
+ return 0;
+ }
+ nIn = ftell(in);
+ rewind(in);
+ pBuf = sqlite3_malloc64( nIn+1 );
+ if( pBuf==0 ){
+ raw_printf(stderr, "Error: out of memory\n");
+ fclose(in);
+ return 0;
+ }
+ nRead = fread(pBuf, nIn, 1, in);
+ fclose(in);
+ if( nRead!=1 ){
+ sqlite3_free(pBuf);
+ raw_printf(stderr, "Error: cannot read '%s'\n", zName);
+ return 0;
+ }
+ pBuf[nIn] = 0;
+ if( pnByte ) *pnByte = nIn;
+ return pBuf;
+}
+
+#if defined(SQLITE_ENABLE_SESSION)
+/*
+** Close a single OpenSession object and release all of its associated
+** resources.
+*/
+static void session_close(OpenSession *pSession){
+ int i;
+ sqlite3session_delete(pSession->p);
+ sqlite3_free(pSession->zName);
+ for(i=0; i<pSession->nFilter; i++){
+ sqlite3_free(pSession->azFilter[i]);
+ }
+ sqlite3_free(pSession->azFilter);
+ memset(pSession, 0, sizeof(OpenSession));
+}
#endif
-#if !defined(SQLITE_SHELL_FIDDLE)
- ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout",
+
+/*
+** Close all OpenSession objects and release all associated resources.
+*/
+#if defined(SQLITE_ENABLE_SESSION)
+static void session_close_all(ShellInState *psi, int i){
+ int j;
+ struct AuxDb *pAuxDb = i<0 ? psi->pAuxDb : &psi->aAuxDb[i];
+ for(j=0; j<pAuxDb->nSession; j++){
+ session_close(&pAuxDb->aSession[j]);
+ }
+ pAuxDb->nSession = 0;
+}
#else
- ".log on|off Turn logging on or off.",
-#endif
- ".mode MODE ?OPTIONS? Set output mode",
- " MODE is one of:",
- " ascii Columns/rows delimited by 0x1F and 0x1E",
- " box Tables using unicode box-drawing characters",
- " csv Comma-separated values",
- " column Output in columns. (See .width)",
- " html HTML <table> code",
- " insert SQL insert statements for TABLE",
- " json Results in a JSON array",
- " line One value per line",
- " list Values delimited by \"|\"",
- " markdown Markdown table format",
- " qbox Shorthand for \"box --wrap 60 --quote\"",
- " quote Escape answers as for SQL",
- " table ASCII-art table",
- " tabs Tab-separated values",
- " tcl TCL list elements",
- " OPTIONS: (for columnar modes or insert mode):",
- " --wrap N Wrap output lines to no longer than N characters",
- " --wordwrap B Wrap or not at word boundaries per B (on/off)",
- " --ww Shorthand for \"--wordwrap 1\"",
- " --quote Quote output text as SQL literals",
- " --noquote Do not quote output text",
- " TABLE The name of SQL table used for \"insert\" mode",
-#ifndef SQLITE_SHELL_FIDDLE
- ".nonce STRING Suspend safe mode for one command if nonce matches",
-#endif
- ".nullvalue STRING Use STRING in place of NULL values",
-#ifndef SQLITE_SHELL_FIDDLE
- ".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
- " If FILE begins with '|' then open as a pipe",
- " --bom Put a UTF8 byte-order mark at the beginning",
- " -e Send output to the system text editor",
- " -x Send output as CSV to a spreadsheet (same as \".excel\")",
- /* Note that .open is (partially) available in WASM builds but is
- ** currently only intended to be used by the fiddle tool, not
- ** end users, so is "undocumented." */
- ".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
- " Options:",
- " --append Use appendvfs to append database to the end of FILE",
-#endif
-#ifndef SQLITE_OMIT_DESERIALIZE
- " --deserialize Load into memory using sqlite3_deserialize()",
- " --hexdb Load the output of \"dbtotxt\" as an in-memory db",
- " --maxsize N Maximum size for --hexdb or --deserialized database",
-#endif
- " --new Initialize FILE to an empty database",
- " --nofollow Do not follow symbolic links",
- " --readonly Open FILE readonly",
- " --zip FILE is a ZIP archive",
-#ifndef SQLITE_SHELL_FIDDLE
- ".output ?FILE? Send output to FILE or stdout if FILE is omitted",
- " If FILE begins with '|' then open it as a pipe.",
- " Options:",
- " --bom Prefix output with a UTF8 byte-order mark",
- " -e Send output to the system text editor",
- " -x Send output as CSV to a spreadsheet",
-#endif
- ".parameter CMD ... Manage SQL parameter bindings",
- " clear Erase all bindings",
- " init Initialize the TEMP table that holds bindings",
- " list List the current parameter bindings",
- " set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE",
- " PARAMETER should start with one of: $ : @ ?",
- " unset PARAMETER Remove PARAMETER from the binding table",
- ".print STRING... Print literal STRING",
-#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
- ".progress N Invoke progress handler after every N opcodes",
- " --limit N Interrupt after N progress callbacks",
- " --once Do no more than one progress interrupt",
- " --quiet|-q No output except at interrupts",
- " --reset Reset the count for each input and interrupt",
-#endif
- ".prompt MAIN CONTINUE Replace the standard prompts",
-#ifndef SQLITE_SHELL_FIDDLE
- ".quit Stop interpreting input stream, exit if primary.",
- ".read FILE Read input from FILE or command output",
- " If FILE begins with \"|\", it is a command that generates the input.",
-#endif
-#if SQLITE_SHELL_HAVE_RECOVER
- ".recover Recover as much data as possible from corrupt db.",
- " --ignore-freelist Ignore pages that appear to be on db freelist",
- " --lost-and-found TABLE Alternative name for the lost-and-found table",
- " --no-rowids Do not attempt to recover rowid values",
- " that are not also INTEGER PRIMARY KEYs",
-#endif
-#ifndef SQLITE_SHELL_FIDDLE
- ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
- ".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)",
+# define session_close_all(X,Y)
#endif
- ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off",
- ".schema ?PATTERN? Show the CREATE statements matching PATTERN",
- " Options:",
- " --indent Try to pretty-print the schema",
- " --nosys Omit objects whose names start with \"sqlite_\"",
- ",selftest ?OPTIONS? Run tests defined in the SELFTEST table",
- " Options:",
- " --init Create a new SELFTEST table",
- " -v Verbose output",
- ".separator COL ?ROW? Change the column and row separators",
+
+/*
+** Implementation of the xFilter function for an open session. Omit
+** any tables named by ".session filter" but let all other table through.
+*/
#if defined(SQLITE_ENABLE_SESSION)
- ".session ?NAME? CMD ... Create or control sessions",
- " Subcommands:",
- " attach TABLE Attach TABLE",
- " changeset FILE Write a changeset into FILE",
- " close Close one session",
- " enable ?BOOLEAN? Set or query the enable bit",
- " filter GLOB... Reject tables matching GLOBs",
- " indirect ?BOOLEAN? Mark or query the indirect status",
- " isempty Query whether the session is empty",
- " list List currently open session names",
- " open DB NAME Open a new session on DB",
- " patchset FILE Write a patchset into FILE",
- " If ?NAME? is omitted, the first defined session is used.",
-#endif
- ".sha3sum ... Compute a SHA3 hash of database content",
- " Options:",
- " --schema Also hash the sqlite_schema table",
- " --sha3-224 Use the sha3-224 algorithm",
- " --sha3-256 Use the sha3-256 algorithm (default)",
- " --sha3-384 Use the sha3-384 algorithm",
- " --sha3-512 Use the sha3-512 algorithm",
- " Any other argument is a LIKE pattern for tables to hash",
-#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
- ".shell CMD ARGS... Run CMD ARGS... in a system shell",
-#endif
- ".show Show the current values for various settings",
- ".stats ?ARG? Show stats or turn stats on or off",
- " off Turn off automatic stat display",
- " on Turn on automatic stat display",
- " stmt Show statement stats",
- " vmstep Show the virtual machine step count only",
-#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
- ".system CMD ARGS... Run CMD ARGS... in a system shell",
+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
- ".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
-#ifndef SQLITE_SHELL_FIDDLE
- ",testcase NAME Begin redirecting output to 'testcase-out.txt'",
+
+#if SHELL_DYNAMIC_EXTENSION
+static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
+ int six = 0;
+ int rcFlags = 0;
+ ShellExState *psx = XSS(psi);
+ while( six < psi->numSubscriptions ){
+ struct EventSubscription *pes = psi->pSubscriptions + six++;
+ rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
+ }
+ return rcFlags;
+}
#endif
- ",testctrl CMD ... Run various sqlite3_test_control() operations",
- " Run \".testctrl\" with no arguments for details",
- ".timeout MS Try opening locked tables for MS milliseconds",
- ".timer on|off Turn SQL timer on or off",
-#ifndef SQLITE_OMIT_TRACE
- ".trace ?OPTIONS? Output each SQL statement as it is run",
- " FILE Send output to FILE",
- " stdout Send output to stdout",
- " stderr Send output to stderr",
- " off Disable tracing",
- " --expanded Expand query parameters",
+
+/*
+** 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.
+*/
+static unsigned char *readHexDb(ShellInState *psi, int *pnData){
+ ResourceMark mark = holder_mark();
+ unsigned char *a = 0;
+ int n = 0;
+ int pgsz = 0;
+ int iOffset = 0;
+ int j, k, nlError;
+ int rc;
+ static const char *zEndMarker = "| end ";
+ const char *zDbFilename = psi->pAuxDb->zDbFilename;
+ /* Need next two objects only if redirecting input to get the hex. */
+ InSource inRedir = INSOURCE_FILE_REDIR(0, zDbFilename, psi->pInSource);
+ AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource };
+ unsigned int x[16];
+ char zLine[1000];
+ if( zDbFilename ){
+ inRedir.inFile = fopen(zDbFilename, "r");
+ if( inRedir.inFile==0 ){
+ utf8_printf(STD_ERR, "cannot open \"%s\" for reading\n", zDbFilename);
+ return 0;
+ }
+ psi->pInSource = &inRedir;
+ inRedir.closer.stream = fclose;
+ any_ref_holder(&arh);
+ }else{
+ /* Will read hex DB lines inline from present input, without redirect. */
+ if( INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
+ printf("Reading hex DB from \"%s\", until end-marker input like:\n%s\n",
+ psi->pInSource->zSourceSay, zEndMarker);
+ fflush(STD_OUT);
+ }
+ }
+ *pnData = 0;
+ if( strLineGet(zLine,sizeof(zLine), psi->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;
+ if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
+ n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */
+ a = sqlite3_malloc( n ? n : 1 );
+ shell_check_ooms(a);
+ smem_holder(a);
+ memset(a, 0, n);
+ if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
+ utf8_printf(STD_ERR, "invalid pagesize\n");
+ goto readHexDb_error;
+ }
+ while( strLineGet(zLine,sizeof(zLine), psi->pInSource)!=0 ){
+ rc = sscanf(zLine, "| page %d offset %d", &j, &k);
+ if( rc==2 ){
+ iOffset = k;
+ continue;
+ }
+ if( cli_strncmp(zLine, zEndMarker, 6)==0 ){
+ break;
+ }
+ rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
+ &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
+ &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
+ if( rc==17 ){
+ k = iOffset+j;
+ if( k+16<=n && k>=0 ){
+ int ii;
+ for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
+ }
+ }
+ }
+ *pnData = n; /* Record success and size. */
+ drop_holder();
+ readHexDb_cleanup:
+ RESOURCE_FREE(mark);
+ return a;
+
+ readHexDb_error:
+ nlError = psi->pInSource->lineno;
+ if( psi->pInSource!=&inRedir ){
+ /* Since taking input inline, consume through its end marker. */
+ while( strLineGet(zLine, sizeof(zLine), psi->pInSource)!=0 ){
+ if(cli_strncmp(zLine, zEndMarker, 6)==0 ) break;
+ }
+ }
+ a = 0;
+ utf8_printf(STD_ERR,"Error on line %d within --hexdb input\n", nlError);
+ goto readHexDb_cleanup;
+}
+#endif /* SQLITE_OMIT_DESERIALIZE */
+
+/*
+** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
+*/
+static void shellUSleepFunc(
+ sqlite3_context *context,
+ int argcUnused,
+ sqlite3_value **argv
+){
+ int sleep = sqlite3_value_int(argv[0]);
+ (void)argcUnused;
+ sqlite3_sleep(sleep/1000);
+ sqlite3_result_int(context, sleep);
+}
+
+/*
+** Attempt to close the database connection. Report errors.
+** The close is done under mutex protection for globalDb.
+*/
+static void close_db(sqlite3 *db){
+ int rc;
+ if( db==globalDb ){
+ /* This should only block for the time needed to handle ^C interrupt. */
+ sqlite3_mutex_enter(pGlobalDbLock);
+ globalDb = 0;
+ rc = sqlite3_close(db);
+ sqlite3_mutex_leave(pGlobalDbLock);
+ }else{
+ rc = sqlite3_close(db);
+ }
+ if( rc ){
+ utf8_printf(STD_ERR, "Error: sqlite3_close() returns %d: %s\n",
+ rc, sqlite3_errmsg(db));
+ }
+}
+
+/* Flags for open_db(). ToDo: Conform comments to code or vice-versa.
+**
+** The default behavior of open_db() is to exit(1) if the database fails to
+** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
+** but still returns without calling exit.
+**
+** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
+** ZIP archive if the file does not exist or is empty and its name matches
+** the *.zip pattern.
+*/
+#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */
+#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */
+
+/*
+** Make sure the database is open. If it is not, then open it. If
+** the database fails to open, print an error message and exit.
+*/
+static void open_db(ShellExState *psx, int openFlags){
+ ShellInState *psi = ISS(psx);
+ if( DBX(psx)==0 ){
+ sqlite3 **pDb = &DBX(psx);
+ const char *zDbFilename = psi->pAuxDb->zDbFilename;
+ if( psi->openMode==SHELL_OPEN_UNSPEC ){
+ if( zDbFilename==0 || zDbFilename[0]==0 ){
+ psi->openMode = SHELL_OPEN_NORMAL;
+ }else{
+ psi->openMode = deduceDatabaseType(zDbFilename,
+ (openFlags & OPEN_DB_ZIPFILE)!=0);
+ }
+ }
+ switch( psi->openMode ){
+ case SHELL_OPEN_APPENDVFS: {
+ sqlite3_open_v2
+ (zDbFilename, pDb,
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags,
+ "apndvfs");
+ break;
+ }
+ case SHELL_OPEN_HEXDB:
+ case SHELL_OPEN_DESERIALIZE: {
+ sqlite3_open(0, pDb);
+ break;
+ }
+ case SHELL_OPEN_ZIPFILE: {
+ sqlite3_open(":memory:", pDb);
+ break;
+ }
+ case SHELL_OPEN_READONLY: {
+ sqlite3_open_v2(zDbFilename, pDb,
+ SQLITE_OPEN_READONLY|psi->openFlags, 0);
+ break;
+ }
+ case SHELL_OPEN_UNSPEC:
+ case SHELL_OPEN_NORMAL: {
+ sqlite3_open_v2(zDbFilename, pDb,
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|psi->openFlags, 0);
+ break;
+ }
+ }
+ globalDb = DBX(psx);
+ if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
+ const char *zWhy = (DBX(psx)==0)? "(?)" : sqlite3_errmsg(DBX(psx));
+ utf8_printf(STD_ERR,"Error: unable to open database \"%s\": %s\n",
+ zDbFilename, zWhy);
+ close_db(DBX(psx));
+ if( bail_on_error && !stdin_is_interactive ){
+ shell_terminate("-bail used in batch mode.");
+ }
+ if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
+ shell_terminate("with OPEN_DB_KEEPALIVE.");
+ }
+ sqlite3_open(":memory:", &DBX(psx));
+ if( DBX(psx)==0 || SQLITE_OK!=sqlite3_errcode(DBX(psx)) ){
+ shell_terminate("Also: unable to open substitute in-memory database.");
+ }else{
+ utf8_printf(stderr,
+ "Notice: using substitute in-memory database instead of \"%s\"\n",
+ zDbFilename);
+ }
+ }
+ sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_STMT_SCANSTATUS,(int)0,(int*)0);
+
+ /* Reflect the use or absence of --unsafe-testing invocation. */
+ {
+ int testmode_on = ShellHasFlag(psx,SHFLG_TestingMode);
+ sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_TRUSTED_SCHEMA,testmode_on,0);
+ sqlite3_db_config(GLOBAL_DB,SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
+ }
+#ifndef SQLITE_OMIT_LOAD_EXTENSION
+ sqlite3_enable_load_extension(GLOBAL_DB, 1);
+#endif
+ sqlite3_shathree_init(GLOBAL_DB, 0, 0);
+ sqlite3_uint_init(GLOBAL_DB, 0, 0);
+ sqlite3_decimal_init(GLOBAL_DB, 0, 0);
+ sqlite3_base64_init(GLOBAL_DB, 0, 0);
+ sqlite3_base85_init(GLOBAL_DB, 0, 0);
+ sqlite3_regexp_init(GLOBAL_DB, 0, 0);
+ sqlite3_ieee_init(GLOBAL_DB, 0, 0);
+ sqlite3_series_init(GLOBAL_DB, 0, 0);
+#ifndef SQLITE_SHELL_FIDDLE
+ sqlite3_fileio_init(GLOBAL_DB, 0, 0);
+ sqlite3_completion_init(GLOBAL_DB, 0, 0);
+#endif
+#ifdef SQLITE_HAVE_ZLIB
+ if( !psi->bSafeModeFuture ){
+ sqlite3_zipfile_init(GLOBAL_DB, 0, 0);
+ sqlite3_sqlar_init(GLOBAL_DB, 0, 0);
+ }
+#endif
+
+#ifdef SQLITE_SHELL_EXTFUNCS
+ /* Create a preprocessing mechanism for extensions to make
+ * their own provisions for being built into the shell.
+ * This is a short-span macro. See further below for usage.
+ */
+#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
+#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
+ /* Let custom-included extensions get their ..._init() called.
+ * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
+ * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
- * inititialization routine to be called.
++ * initialization routine to be called.
+ */
+ {
+ int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
+ /* Let custom-included extensions expose their functionality.
+ * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
+ * the SQL functions, virtual tables, collating sequences or
+ * VFS's implemented by the extension to be registered.
+ */
+ if( irc==SQLITE_OK
+ || irc==SQLITE_OK_LOAD_PERMANENTLY ){
+ SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
+ }
+#undef SHELL_SUB_MACRO
+#undef SHELL_SUBMACRO
+ }
+#endif
+
+ sqlite3_create_function(GLOBAL_DB, "shell_add_schema", 3,SQLITE_UTF8,0,
+ shellAddSchemaName, 0, 0);
+ sqlite3_create_function(GLOBAL_DB, "shell_module_schema", 1,SQLITE_UTF8,0,
+ shellModuleSchema, 0, 0);
+ sqlite3_create_function(GLOBAL_DB, "shell_putsnl", 1,SQLITE_UTF8,psx,
+ shellPutsFunc, 0, 0);
+ sqlite3_create_function(GLOBAL_DB, "usleep", 1,SQLITE_UTF8,0,
+ shellUSleepFunc, 0, 0);
+#ifndef SQLITE_NOHAVE_SYSTEM
+ sqlite3_create_function(GLOBAL_DB, "edit", 1, SQLITE_UTF8, 0,
+ editFunc, 0, 0);
+ sqlite3_create_function(GLOBAL_DB, "edit", 2, SQLITE_UTF8, 0,
+ editFunc, 0, 0);
+#endif
+ if( psi->openMode==SHELL_OPEN_ZIPFILE ){
+ char *zSql = smprintf("CREATE VIRTUAL TABLE zip USING zipfile(%Q);",
+ zDbFilename);
+ shell_check_ooms(zSql);
+ sstr_holder(zSql);
+ s3_exec_noom(DBX(psx), zSql, 0, 0, 0);
+ release_holder();
+ }
+#ifndef SQLITE_OMIT_DESERIALIZE
+ else
+ if( psi->openMode==SHELL_OPEN_DESERIALIZE
+ || psi->openMode==SHELL_OPEN_HEXDB ){
+ int rc;
+ int nData = 0;
+ unsigned char *aData;
+ if( psi->openMode==SHELL_OPEN_DESERIALIZE ){
+ aData = (unsigned char*)readFile(zDbFilename, &nData);
+ }else{
+ aData = readHexDb(psi, &nData);
+ if( aData==0 ){
+ return;
+ }
+ }
+ rc = sqlite3_deserialize(DBX(psx), "main", aData, nData, nData,
+ SQLITE_DESERIALIZE_RESIZEABLE |
+ SQLITE_DESERIALIZE_FREEONCLOSE);
+ if( rc ){
+ utf8_printf(STD_ERR, "Error: sqlite3_deserialize() returns %d\n", rc);
+ }
+ if( psi->szMax>0 ){
+ sqlite3_file_control(DBX(psx), "main", SQLITE_FCNTL_SIZE_LIMIT,
+ &psi->szMax);
+ }
+ }
+#endif
+ if( DBX(psx)!=0 ){
+ if( psi->bSafeModeFuture ){
+ sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
+ }
+ sqlite3_db_config(
+ DBX(psx), SQLITE_DBCONFIG_STMT_SCANSTATUS, psi->scanstatsOn,(int*)0);
+ }
+#if SHELL_DYNAMIC_EXTENSION
+ notify_subscribers(psi, NK_DbUserAppeared, DBX(psx));
+#endif
+ }
+}
+
+#if HAVE_READLINE || HAVE_EDITLINE
+/*
+** Readline completion callbacks
+*/
+static char *readline_completion_generator(const char *text, int state){
+ static sqlite3_stmt *pStmt = 0;
+ char *zRet;
+ if( state==0 ){
+ char *zSql;
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
+ " FROM completion(%Q) ORDER BY 1", text);
+ s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt);
+ }
+ if( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *z = (const char*)sqlite3_column_text(pStmt,0);
+ if( z!=0 ){
+ zRet = strdup(z);
+ shell_check_oomm(zRet);
+ }
+ }else{
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ zRet = 0;
+ }
+ return zRet;
+}
+static char **readline_completion(const char *zText, int iStart, int iEnd){
+ (void)iStart;
+ (void)iEnd;
+ rl_attempted_completion_over = 1;
+ return rl_completion_matches(zText, readline_completion_generator);
+}
+
+#elif HAVE_LINENOISE
+/*
+** Linenoise completion callback
+*/
+static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
+ i64 nLine = strlen(zLine);
+ i64 i, iStart;
+ sqlite3_stmt *pStmt = 0;
+ char *zSql;
+ char zBuf[1000];
+
+ if( nLine>(i64)sizeof(zBuf)-30 ) return;
+ if( zLine[0]=='.' || zLine[0]=='#') return;
+ for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
+ if( i==nLine-1 ) return;
+ iStart = i+1;
+ memcpy(zBuf, zLine, iStart);
+ zSql = smprintf("SELECT DISTINCT candidate COLLATE nocase"
+ " FROM completion(%Q,%Q) ORDER BY 1",
+ &zLine[iStart], zLine);
+ s3_prep_noom_free(GLOBAL_DB, &zSql, &pStmt);
+ sqlite3_exec(GLOBAL_DB, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
+ int nCompletion = sqlite3_column_bytes(pStmt, 0);
+ if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
+ memcpy(zBuf+iStart, zCompletion, nCompletion+1);
+ linenoiseAddCompletion(lc, zBuf);
+ }
+ }
+ sqlite3_finalize(pStmt);
+}
+#endif
+
+/*
+** Do C-language style escape sequence translation.
+**
+** \a -> alarm
+** \b -> backspace
+** \t -> tab
+** \n -> newline
+** \v -> vertical tab
+** \f -> form feed
+** \r -> carriage return
+** \s -> space
+** \" -> "
+** \' -> '
+** \\ -> backslash
+** \NNN -> ascii character NNN in octal
+** \xHH -> ascii character HH in hexadecimal
+*/
+static void resolve_backslashes(char *z){
+ int i, j;
+ char c;
+ while( *z && *z!='\\' ) z++;
+ for(i=j=0; (c = z[i])!=0; i++, j++){
+ if( c=='\\' && z[i+1]!=0 ){
+ c = z[++i];
+ if( c=='a' ){
+ c = '\a';
+ }else if( c=='b' ){
+ c = '\b';
+ }else if( c=='t' ){
+ c = '\t';
+ }else if( c=='n' ){
+ c = '\n';
+ }else if( c=='v' ){
+ c = '\v';
+ }else if( c=='f' ){
+ c = '\f';
+ }else if( c=='r' ){
+ c = '\r';
+ }else if( c=='"' ){
+ c = '"';
+ }else if( c=='\'' ){
+ c = '\'';
+ }else if( c=='\\' ){
+ c = '\\';
+ }else if( c=='x' ){
+ int nhd = 0, hdv;
+ u8 hv = 0;
+ while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
+ hv = (u8)((hv<<4)|hdv);
+ ++nhd;
+ }
+ i += nhd;
+ c = hv;
+ }else if( c>='0' && c<='7' ){
+ c -= '0';
+ if( z[i+1]>='0' && z[i+1]<='7' ){
+ i++;
+ c = (c<<3) + z[i] - '0';
+ if( z[i+1]>='0' && z[i+1]<='7' ){
+ i++;
+ c = (c<<3) + z[i] - '0';
+ }
+ }
+ }
+ }
+ z[j] = c;
+ }
+ if( j<i ) z[j] = 0;
+}
+
+/*
+** Interpret zArg as either an integer or a boolean value. Return 1 or 0
+** for TRUE and FALSE. Return the integer value if appropriate.
+*/
+static int booleanValue(const char *zArg){
+ static const char *zBoolNames[] = {
+ "no","yes", "off","on",
+#ifdef BOOLNAMES_ARE_BOOLEAN
+ "false","true",
+#endif
+ 0
+ };
+ int i;
+ if( zArg[0]=='0' && zArg[1]=='x' ){
+ for(i=2; hexDigitValue(zArg[i])>=0; i++){}
+ }else{
+ for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
+ }
+ if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
+ for( i=0; zBoolNames[i]!=0; ++i ){
+ if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
+ }
+ utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
+ zArg);
+ return 0;
+}
+
+/*
+** Set or clear a shell flag according to a boolean value.
+*/
+static void setOrClearFlag(ShellExState *psx, unsigned mFlag, const char *zArg){
+ if( booleanValue(zArg) ){
+ ShellSetFlag(psx, mFlag);
+ }else{
+ ShellClearFlag(psx, mFlag);
+ }
+}
+
+/*
+** Close an output file, provided it is not stderr or stdout
+*/
+static void output_file_close(FILE *f){
+ if( f && f!=STD_OUT && f!=STD_ERR ) fclose(f);
+}
+
+/*
+** Try to open an output file. The names "stdout" and "stderr" are
+** recognized and do the right thing. NULL is returned if the output
+** filename is "off".
+*/
+static FILE *output_file_open(const char *zFile, int bTextMode){
+ FILE *f;
+ if( cli_strcmp(zFile,"stdout")==0 ){
+ f = STD_OUT;
+ }else if( cli_strcmp(zFile, "stderr")==0 ){
+ f = STD_ERR;
+ }else if( cli_strcmp(zFile, "off")==0 ){
+ f = 0;
+ }else{
+ f = fopen(zFile, bTextMode ? "w" : "wb");
+ if( f==0 ){
+ utf8_printf(STD_ERR, "Error: cannot open \"%s\"\n", zFile);
+ }
+ }
+ return f;
+}
+
+#ifndef SQLITE_OMIT_TRACE
+/*
+** A routine for handling output from sqlite3_trace().
+*/
+static int sql_trace_callback(
+ unsigned mType, /* The trace type */
+ void *pArg, /* The shell state pointer */
+ void *pP, /* Usually a pointer to sqlite_stmt */
+ void *pX /* Auxiliary output */
+){
+ ShellInState *psi = (ShellInState*)pArg;
+ sqlite3_stmt *pStmt;
+ const char *zSql;
+ i64 nSql;
+ if( psi->traceOut==0 ) return 0;
+ if( mType==SQLITE_TRACE_CLOSE ){
+ utf8_printf(psi->traceOut, "-- closing database connection\n");
+ return 0;
+ }
+ if( mType!=SQLITE_TRACE_ROW && pX!=0 && ((const char*)pX)[0]=='-' ){
+ zSql = (const char*)pX;
+ }else{
+ pStmt = (sqlite3_stmt*)pP;
+ switch( psi->eTraceType ){
+ case SHELL_TRACE_EXPANDED: {
+ zSql = sqlite3_expanded_sql(pStmt);
+ break;
+ }
#ifdef SQLITE_ENABLE_NORMALIZE
- " --normalized Normal the SQL statements",
+ case SHELL_TRACE_NORMALIZED: {
+ zSql = sqlite3_normalized_sql(pStmt);
+ break;
+ }
#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",
+ default: {
+ zSql = sqlite3_sql(pStmt);
+ break;
+ }
+ }
+ }
+ if( zSql==0 ) return 0;
+ nSql = strlen(zSql);
+ if( nSql>1000000000 ) nSql = 1000000000; /* clamp to 1 billion */
+ while( nSql>0 && zSql[nSql-1]==';' ){ nSql--; }
+ switch( mType ){
+ case SQLITE_TRACE_ROW:
+ case SQLITE_TRACE_STMT: {
+ utf8_printf(psi->traceOut, "%.*s;\n", (int)nSql, zSql);
+ break;
+ }
+ case SQLITE_TRACE_PROFILE: {
+ sqlite3_int64 nNanosec = pX ? *(sqlite3_int64*)pX : 0;
+ utf8_printf(psi->traceOut,"%.*s; -- %lld ns\n", (int)nSql,zSql,nNanosec);
+ break;
+ }
+ }
+ return 0;
+}
#endif
- ".version Show source, library and compiler versions",
- ".vfsinfo ?AUX? Information about the top-level VFS",
- ".vfslist List all available VFSes",
- ".vfsname ?AUX? Print the name of the VFS stack",
- ".width NUM1 NUM2 ... Set minimum column widths for columnar output",
- " Negative values right-justify",
+
+/*
+** A no-op routine that runs with the ".breakpoint" dot-command.
+** This is a useful spot to set a debugger breakpoint.
+**
+** This routine does not do anything practical. The code are there simply
+** to prevent the compiler from optimizing this routine out.
+*/
+static void test_breakpoint(void){
+ static int nCall = 0;
+ if( (nCall++)==0xffffffff ) printf("Many .breakpoints have run\n");
+}
+
+/*
+** An object used to read a CSV and other files for import.
+*/
+typedef struct ImportCtx ImportCtx;
+struct ImportCtx {
+ const char *zFile; /* Name of the input file */
+ FILE *in; /* Read the CSV text from this input stream */
+ int (SQLITE_CDECL *xCloser)(FILE*); /* Func to close in */
+ char *z; /* Accumulated text for a field */
+ int n; /* Number of bytes in z */
+ int nAlloc; /* Space allocated for z[] */
+ int nLine; /* Current line number */
+ int nRow; /* Number of rows imported */
+ int nErr; /* Number of errors encountered */
+ int bNotFirst; /* True if one or more bytes already read */
+ int cTerm; /* Character that terminated the most recent field */
+ int cColSep; /* The column separator character. (Usually ",") */
+ int cRowSep; /* The row separator character. (Usually "\n") */
};
+/* Clean up resourced used by an ImportCtx */
+static void import_cleanup(ImportCtx *p){
+ if( p->in!=0 && p->xCloser!=0 ){
+ p->xCloser(p->in);
+ p->in = 0;
+ }
+ sqlite3_free(p->z);
+ p->z = 0;
+}
+
+/* Append a single byte to z[] */
+static void import_append_char(ImportCtx *p, int c){
+ if( p->n+1>=p->nAlloc ){
+ p->nAlloc += p->nAlloc + 100;
+ p->z = sqlite3_realloc64(p->z, p->nAlloc);
+ shell_check_ooms(p->z);
+ }
+ p->z[p->n++] = (char)c;
+}
+
+/* Read a single field of CSV text. Compatible with rfc4180 and extended
+** with the option of having a separator other than ",".
+**
+** + Input comes from p->in.
+** + Store results in p->z of length p->n. Space to hold p->z comes
+** from sqlite3_malloc64().
+** + Use p->cSep as the column separator. The default is ",".
+** + Use p->rSep as the row separator. The default is "\n".
+** + Keep track of the line number in p->nLine.
+** + Store the character that terminates the field in p->cTerm. Store
+** EOF on end-of-file.
+** + Report syntax errors on stderr
+*/
+static char *SQLITE_CDECL csv_read_one_field(ImportCtx *p){
+ int c;
+ int cSep = (u8)p->cColSep;
+ int rSep = (u8)p->cRowSep;
+ p->n = 0;
+ c = fgetc(p->in);
+ if( c==EOF || seenInterrupt ){
+ p->cTerm = EOF;
+ return 0;
+ }
+ if( c=='"' ){
+ int pc, ppc;
+ int startLine = p->nLine;
+ int cQuote = c;
+ pc = ppc = 0;
+ while( 1 ){
+ c = fgetc(p->in);
+ if( c==rSep ) p->nLine++;
+ if( c==cQuote ){
+ if( pc==cQuote ){
+ pc = 0;
+ continue;
+ }
+ }
+ if( (c==cSep && pc==cQuote)
+ || (c==rSep && pc==cQuote)
+ || (c==rSep && pc=='\r' && ppc==cQuote)
+ || (c==EOF && pc==cQuote)
+ ){
+ do{ p->n--; }while( p->z[p->n]!=cQuote );
+ p->cTerm = c;
+ break;
+ }
+ if( pc==cQuote && c!='\r' ){
+ utf8_printf(STD_ERR, "%s:%d: unescaped %c character\n",
+ p->zFile, p->nLine, cQuote);
+ }
+ if( c==EOF ){
+ utf8_printf(STD_ERR, "%s:%d: unterminated %c-quoted field\n",
+ p->zFile, startLine, cQuote);
+ p->cTerm = c;
+ break;
+ }
+ import_append_char(p, c);
+ ppc = pc;
+ pc = c;
+ }
+ }else{
+ /* If this is the first field being parsed and it begins with the
+ ** UTF-8 BOM (0xEF BB BF) then skip the BOM */
+ if( (c&0xff)==0xef && p->bNotFirst==0 ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
+ if( (c&0xff)==0xbb ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
+ if( (c&0xff)==0xbf ){
+ p->bNotFirst = 1;
+ p->n = 0;
+ return csv_read_one_field(p);
+ }
+ }
+ }
+ while( c!=EOF && c!=cSep && c!=rSep ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
+ }
+ if( c==rSep ){
+ p->nLine++;
+ if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
+ }
+ p->cTerm = c;
+ }
+ if( p->z ) p->z[p->n] = 0;
+ p->bNotFirst = 1;
+ return p->z;
+}
+
+/* Read a single field of ASCII delimited text.
+**
+** + Input comes from p->in.
+** + Store results in p->z of length p->n. Space to hold p->z comes
+** from sqlite3_malloc64().
+** + Use p->cSep as the column separator. The default is "\x1F".
+** + Use p->rSep as the row separator. The default is "\x1E".
+** + Keep track of the row number in p->nLine.
+** + Store the character that terminates the field in p->cTerm. Store
+** EOF on end-of-file.
+** + Report syntax errors on stderr
+*/
+static char *SQLITE_CDECL ascii_read_one_field(ImportCtx *p){
+ int c;
+ int cSep = (u8)p->cColSep;
+ int rSep = (u8)p->cRowSep;
+ p->n = 0;
+ c = fgetc(p->in);
+ if( c==EOF || seenInterrupt ){
+ p->cTerm = EOF;
+ return 0;
+ }
+ while( c!=EOF && c!=cSep && c!=rSep ){
+ import_append_char(p, c);
+ c = fgetc(p->in);
+ }
+ if( c==rSep ){
+ p->nLine++;
+ }
+ p->cTerm = c;
+ if( p->z ) p->z[p->n] = 0;
+ return p->z;
+}
+
+/*
+** Try to transfer data for table zTable. If an error is seen while
+** moving forward, try to go backwards. The backwards movement won't
+** work for WITHOUT ROWID tables.
+*/
+static void tryToCloneData(
+ ShellExState *psx,
+ sqlite3 *newDb,
+ const char *zTable
+){
+ sqlite3_stmt *pQuery = 0;
+ sqlite3_stmt *pInsert = 0;
+ char *zQuery = 0;
+ char *zInsert = 0;
+ int rc;
+ int i, j, n;
+ int nTable = strlen30(zTable);
+ int k = 0;
+ int cnt = 0;
+ const int spinRate = 10000;
+
+ shell_check_ooms(zQuery = smprintf("SELECT * FROM \"%w\"", zTable));
+ rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
+ if( rc ){
+ utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
+ sqlite3_extended_errcode(DBX(psx)), sqlite3_errmsg(DBX(psx)),
+ zQuery);
+ goto end_data_xfer;
+ }
+ n = sqlite3_column_count(pQuery);
+ zInsert = sqlite3_malloc64(200 + nTable + n*3);
+ shell_check_ooms(zInsert);
+ sqlite3_snprintf(200+nTable,zInsert,
+ "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable);
+ i = strlen30(zInsert);
+ for(j=1; j<n; j++){
+ memcpy(zInsert+i, ",?", 2);
+ i += 2;
+ }
+ memcpy(zInsert+i, ");", 3);
+ rc = s3_prepare_v2_noom(newDb, zInsert, -1, &pInsert, 0);
+ if( rc ){
+ utf8_printf(STD_ERR, "Error %d: %s on [%s]\n",
+ sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb),
+ zInsert);
+ goto end_data_xfer;
+ }
+ for(k=0; k<2; k++){
+ while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
+ for(i=0; i<n; i++){
+ switch( sqlite3_column_type(pQuery, i) ){
+ case SQLITE_NULL: {
+ sqlite3_bind_null(pInsert, i+1);
+ break;
+ }
+ case SQLITE_INTEGER: {
+ sqlite3_bind_int64(pInsert, i+1, sqlite3_column_int64(pQuery,i));
+ break;
+ }
+ case SQLITE_FLOAT: {
+ sqlite3_bind_double(pInsert, i+1, sqlite3_column_double(pQuery,i));
+ break;
+ }
+ case SQLITE_TEXT: {
+ sqlite3_bind_text(pInsert, i+1,
+ (const char*)sqlite3_column_text(pQuery,i),
+ -1, SQLITE_STATIC);
+ break;
+ }
+ case SQLITE_BLOB: {
+ sqlite3_bind_blob(pInsert, i+1, sqlite3_column_blob(pQuery,i),
+ sqlite3_column_bytes(pQuery,i),
+ SQLITE_STATIC);
+ break;
+ }
+ }
+ } /* End for */
+ rc = sqlite3_step(pInsert);
+ if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
+ utf8_printf(STD_ERR, "Error %d: %s\n", sqlite3_extended_errcode(newDb),
+ sqlite3_errmsg(newDb));
+ }
+ sqlite3_reset(pInsert);
+ cnt++;
+ if( (cnt%spinRate)==0 ){
+ fprintf(STD_OUT, "%c\b", "|/-\\"[(cnt/spinRate)%4]);
+ fflush(STD_OUT);
+ }
+ } /* End while */
+ if( rc==SQLITE_DONE ) break;
+ sqlite3_finalize(pQuery);
+ sqlite3_free(zQuery);
+ zQuery = smprintf("SELECT * FROM \"%w\" ORDER BY rowid DESC;", zTable);
+ rc = s3_prep_noom_free(DBX(psx), &zQuery, &pQuery);
+ if( rc ){
+ utf8_printf(STD_ERR, "Warning: cannot step \"%s\" backwards", zTable);
+ break;
+ }
+ } /* End for(k=0...) */
+
+end_data_xfer:
+ sqlite3_finalize(pQuery);
+ sqlite3_finalize(pInsert);
+ sqlite3_free(zQuery);
+ sqlite3_free(zInsert);
+}
+
+
/*
-** 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.
-**
-** Return the number of matches.
+** Try to transfer all rows of the schema that match zWhere. For
+** each row, invoke xForEach() on the object defined by that row.
+** If an error is encountered while moving forward through the
+** sqlite_schema table, try again moving backwards.
*/
-static int showHelp(FILE *out, const char *zPattern){
- int i = 0;
- int j = 0;
- int n = 0;
- char *zPat;
- if( zPattern==0
- || zPattern[0]=='0'
- || cli_strcmp(zPattern,"-a")==0
- || cli_strcmp(zPattern,"-all")==0
- || cli_strcmp(zPattern,"--all")==0
- ){
- enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 };
- enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 };
- /* Show all or most commands
- ** *zPattern==0 => summary of documented commands only
- ** *zPattern=='0' => whole help for undocumented commands
- ** Otherwise => whole help for documented commands
- */
- enum HelpWanted hw = HW_SummaryOnly;
- enum HelpHave hh = HH_More;
- if( zPattern!=0 ){
- hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull;
- }
- for(i=0; i<ArraySize(azHelp); i++){
- switch( azHelp[i][0] ){
- case ',':
- hh = HH_Summary|HH_Undoc;
- break;
- case '.':
- hh = HH_Summary;
- break;
- default:
- hh &= ~HH_Summary;
- break;
- }
- if( ((hw^hh)&HH_Undoc)==0 ){
- if( (hh&HH_Summary)!=0 ){
- utf8_printf(out, ".%s\n", azHelp[i]+1);
- ++n;
- }else if( (hw&HW_SummaryOnly)==0 ){
- utf8_printf(out, "%s\n", azHelp[i]);
- }
+static void tryToCloneSchema(
+ ShellExState *psx,
+ sqlite3 *newDb,
+ const char *zWhere,
+ void (*xForEach)(ShellExState*,sqlite3*,const char*)
+){
+ sqlite3_stmt *pQuery = 0;
+ char *zQuery = 0;
+ int rc;
+ const unsigned char *zName;
+ const unsigned char *zSql;
+ char *zErrMsg = 0;
+ RESOURCE_MARK(mark);
+
+ zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
+ " WHERE %s ORDER BY rowid ASC", zWhere);
+ shell_check_ooms(zQuery);
+ sstr_ptr_holder(&zQuery);
+ rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
+ if( rc ){
+ utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
+ sqlite3_extended_errcode(DBX(psx)),
+ sqlite3_errmsg(DBX(psx)), zQuery);
+ goto end_schema_xfer;
+ }
+ stmt_ptr_holder(&pQuery);
+ while( (rc = s3_step_noom(pQuery))==SQLITE_ROW ){
+ zName = sqlite3_column_text(pQuery, 0);
+ zSql = sqlite3_column_text(pQuery, 1);
+ if( zName==0 || zSql==0 ) continue;
+ if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){
+ /* Consider directing this output to current output. */
+ fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
+ s3_exec_noom(newDb, (const char*)zSql, 0, 0, &zErrMsg);
+ if( zErrMsg ){
+ utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
+ sqlite3_free(zErrMsg);
+ zErrMsg = 0;
}
}
- }else{
- /* Seek documented commands for which zPattern is an exact prefix */
- zPat = sqlite3_mprintf(".%s*", zPattern);
- shell_check_oom(zPat);
- for(i=0; i<ArraySize(azHelp); i++){
- if( sqlite3_strglob(zPat, azHelp[i])==0 ){
- utf8_printf(out, "%s\n", azHelp[i]);
- j = i+1;
- n++;
- }
+ if( xForEach ){
+ xForEach(psx, newDb, (const char*)zName);
}
- sqlite3_free(zPat);
- if( n ){
- if( n==1 ){
- /* when zPattern is a prefix of exactly one command, then include
- ** the details of that command, which should begin at offset j */
- while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
- utf8_printf(out, "%s\n", azHelp[j]);
- j++;
- }
- }
- return n;
+ /* Consider directing this output to current output. */
+ fprintf(STD_OUT, "done\n");
+ }
+ if( rc!=SQLITE_DONE ){
+ sqlite3_finalize(pQuery);
+ pQuery = 0;
+ sqlite3_free(zQuery);
+ zQuery = smprintf("SELECT name, sql FROM sqlite_schema"
+ " WHERE %s ORDER BY rowid DESC", zWhere);
+ shell_check_ooms(zQuery);
+ rc = s3_prepare_v2_noom(DBX(psx), zQuery, -1, &pQuery, 0);
+ if( rc ){
+ utf8_printf(STD_ERR, "Error: (%d) %s on [%s]\n",
+ sqlite3_extended_errcode(DBX(psx)),
+ sqlite3_errmsg(DBX(psx)), zQuery);
+ goto end_schema_xfer;
}
- /* Look for documented commands that contain zPattern anywhere.
- ** Show complete text of all documented commands that match. */
- zPat = sqlite3_mprintf("%%%s%%", zPattern);
- shell_check_oom(zPat);
- for(i=0; i<ArraySize(azHelp); i++){
- if( azHelp[i][0]==',' ){
- while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
- continue;
+ while( (rc = s3_step_noom(pQuery))==SQLITE_ROW ){
+ zName = sqlite3_column_text(pQuery, 0);
+ zSql = sqlite3_column_text(pQuery, 1);
+ if( zName==0 || zSql==0 ) continue;
+ if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue;
+ /* Consider directing ... */
+ fprintf(STD_OUT, "%s... ", zName); fflush(STD_OUT);
+ s3_exec_noom(newDb, (const char*)zSql, 0, 0, &zErrMsg);
+ if( zErrMsg ){
+ utf8_printf(STD_ERR, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
+ sqlite3_free(zErrMsg);
+ zErrMsg = 0;
}
- if( azHelp[i][0]=='.' ) j = i;
- if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
- utf8_printf(out, "%s\n", azHelp[j]);
- while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
- j++;
- utf8_printf(out, "%s\n", azHelp[j]);
- }
- i = j;
- n++;
+ if( xForEach ){
+ xForEach(psx, newDb, (const char*)zName);
}
+ /* Consider directing ... */
+ fprintf(STD_OUT, "done\n");
}
- sqlite3_free(zPat);
}
- return n;
+end_schema_xfer:
+ RESOURCE_FREE(mark);
}
-/* Forward reference */
-static int process_input(ShellState *p);
+/*
+** Open a new database file named "zNewDb". Try to recover as much information
+** as possible out of the main database (which might be corrupt) and write it
+** into zNewDb.
+*/
+static void tryToClone(ShellExState *psx, const char *zNewDb){
+ int rc;
+ sqlite3 *newDb = 0;
+ if( access(zNewDb,0)==0 ){
+ utf8_printf(STD_ERR, "File \"%s\" already exists.\n", zNewDb);
+ return;
+ }
+ rc = sqlite3_open(zNewDb, &newDb);
+ if( rc ){
+ utf8_printf(STD_ERR, "Cannot create output database: %s\n",
+ sqlite3_errmsg(newDb));
+ }else{
+ // sqlite3_exec(DBX(psx), "PRAGMA writable_schema=ON;", 0, 0, 0);
+ sqlite3_db_config(newDb, SQLITE_DBCONFIG_WRITABLE_SCHEMA, 1, 0);
+ sqlite3_db_config(newDb, SQLITE_DBCONFIG_DEFENSIVE, 0, 0);
+ sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
+ tryToCloneSchema(psx, newDb, "type='table'", tryToCloneData);
+ tryToCloneSchema(psx, newDb, "type!='table'", 0);
+ sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);
+ // sqlite3_exec(DBX(psx), "PRAGMA writable_schema=OFF;", 0, 0, 0);
+ }
+ close_db(newDb);
+}
/*
-** 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.
+** Change the output file back to stdout.
**
-** NULL is returned if any error is encountered. The final value of *pnByte
-** is undefined in this case.
+** If the psi->doXdgOpen flag is set, that means the output was being
+** redirected to a temporary file named by psi->zTempFile. In that case,
+** launch start/open/xdg-open on that temporary file.
*/
-static char *readFile(const char *zName, int *pnByte){
- FILE *in = fopen(zName, "rb");
- long nIn;
- size_t nRead;
- char *pBuf;
- int rc;
- if( in==0 ) return 0;
- rc = fseek(in, 0, SEEK_END);
- if( rc!=0 ){
- raw_printf(stderr, "Error: '%s' not seekable\n", zName);
- fclose(in);
- return 0;
+static void output_reset(ShellInState *psi){
+ if( psi->outfile[0]=='|' ){
+#ifndef SQLITE_OMIT_POPEN
+ pclose(psi->out);
+#endif
+ }else{
+ output_file_close(psi->out);
+#ifndef SQLITE_NOHAVE_SYSTEM
+ if( psi->doXdgOpen ){
+ const char *zXdgOpenCmd =
+#if defined(_WIN32)
+ "start";
+#elif defined(__APPLE__)
+ "open";
+#else
+ "xdg-open";
+#endif
+ char *zCmd;
+ zCmd = smprintf("%s %s", zXdgOpenCmd, psi->zTempFile);
+ if( system(zCmd) ){
+ utf8_printf(STD_ERR, "Failed: [%s]\n", zCmd);
+ }else{
+ /* Give the start/open/xdg-open command some time to get
+ ** going before we continue, and potential delete the
+ ** psi->zTempFile data file out from under it */
+ sqlite3_sleep(2000);
+ }
+ sqlite3_free(zCmd);
+ outputModePop(psi);
+ psi->doXdgOpen = 0;
+ }
+#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
}
- nIn = ftell(in);
- rewind(in);
- pBuf = sqlite3_malloc64( nIn+1 );
- if( pBuf==0 ){
- raw_printf(stderr, "Error: out of memory\n");
- fclose(in);
- return 0;
+ psi->outfile[0] = 0;
+ psi->out = STD_OUT;
+}
+
+/*
+** Run an SQL command and return the single integer result.
+** No parameter binding is done.
+*/
+static int db_int(sqlite3 *db, const char *zSql){
+ sqlite3_stmt *pStmt;
+ int res = 0;
+ s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0);
+ stmt_holder(pStmt);
+ if( pStmt && s3_step_noom(pStmt)==SQLITE_ROW ){
+ res = sqlite3_column_int(pStmt,0);
}
- nRead = fread(pBuf, nIn, 1, in);
- fclose(in);
- if( nRead!=1 ){
- sqlite3_free(pBuf);
- raw_printf(stderr, "Error: cannot read '%s'\n", zName);
- return 0;
+ release_holder();
+ return res;
+}
+
+/*
+** 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;
+ if( s3_prepare_v2_noom(db, zSql, -1, &pStmt, 0)==SQLITE_OK && pStmt!=0 ){
+ stmt_holder(pStmt);
- if( bBind ) bind_prepared_stmt(db, pStmt);
++ if( bBind ) bind_prepared_stmt(db, pStmt, 0);
+ if( s3_step_noom(pStmt)==SQLITE_ROW ){
+ shell_check_ooms(zRes = smprintf("%s", sqlite3_column_text(pStmt,0)));
+ }
+ release_holder();
+ }
+ return zRes;
+}
+
+#if SQLITE_SHELL_HAVE_RECOVER
+/*
+** Convert a 2-byte or 4-byte big-endian integer into a native integer
+*/
+static unsigned int get2byteInt(unsigned char *a){
+ return (a[0]<<8) + a[1];
+}
+static unsigned int get4byteInt(unsigned char *a){
+ return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
+}
+
+/*
+** Implementation of the ".dbinfo" command.
+**
+** Return 1 on error, 2 to exit, and 0 otherwise.
+*/
+static int shell_dbinfo_command(ShellExState *psx, int nArg, char **azArg){
+ static const struct { const char *zName; int ofst; } aField[] = {
+ { "file change counter:", 24 },
+ { "database page count:", 28 },
+ { "freelist page count:", 36 },
+ { "schema cookie:", 40 },
+ { "schema format:", 44 },
+ { "default cache size:", 48 },
+ { "autovacuum top root:", 52 },
+ { "incremental vacuum:", 64 },
+ { "text encoding:", 56 },
+ { "user version:", 60 },
+ { "application id:", 68 },
+ { "software version:", 96 },
+ };
+ static const struct { const char *zName; const char *zSql; } aQuery[] = {
+ { "number of tables:",
+ "SELECT count(*) FROM %s WHERE type='table'" },
+ { "number of indexes:",
+ "SELECT count(*) FROM %s WHERE type='index'" },
+ { "number of triggers:",
+ "SELECT count(*) FROM %s WHERE type='trigger'" },
+ { "number of views:",
+ "SELECT count(*) FROM %s WHERE type='view'" },
+ { "schema size:",
+ "SELECT total(length(sql)) FROM %s" },
+ };
+ int i, rc;
+ unsigned iDataVersion;
+ char *zSchemaTab;
+ char *zDb = nArg>=2 ? azArg[1] : "main";
+ sqlite3_stmt *pStmt = 0;
+ unsigned char aHdr[100];
+ FILE *out = ISS(psx)->out;
+
+ open_db(psx, 0);
+ if( DBX(psx)==0 ) return 1;
+ rc = s3_prepare_v2_noom(DBX(psx),
+ "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
+ -1, &pStmt, 0);
+ if( rc ){
+ utf8_printf(STD_ERR, "error: %s\n", sqlite3_errmsg(DBX(psx)));
+ sqlite3_finalize(pStmt);
+ return 1;
+ }
+ sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC);
+ if( sqlite3_step(pStmt)==SQLITE_ROW
+ && sqlite3_column_bytes(pStmt,0)>100
+ ){
+ const u8 *pb = sqlite3_column_blob(pStmt,0);
+ shell_check_ooms(pb);
+ memcpy(aHdr, pb, 100);
+ sqlite3_finalize(pStmt);
+ }else{
+ raw_printf(STD_ERR, "unable to read database header\n");
+ sqlite3_finalize(pStmt);
+ return 1;
}
- 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]);
+ i = get2byteInt(aHdr+16);
+ if( i==1 ) i = 65536;
+ utf8_printf(out, "%-20s %d\n", "database page size:", i);
+ utf8_printf(out, "%-20s %d\n", "write format:", aHdr[18]);
+ utf8_printf(out, "%-20s %d\n", "read format:", aHdr[19]);
+ utf8_printf(out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
+ for(i=0; i<ArraySize(aField); i++){
+ int ofst = aField[i].ofst;
+ unsigned int val = get4byteInt(aHdr + ofst);
+ utf8_printf(out, "%-20s %u", aField[i].zName, val);
+ switch( ofst ){
+ case 56: {
+ if( val==1 ) raw_printf(out, " (utf8)");
+ if( val==2 ) raw_printf(out, " (utf16le)");
+ if( val==3 ) raw_printf(out, " (utf16be)");
+ }
+ }
+ raw_printf(out, "\n");
}
- 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]);
+ if( zDb==0 ){
+ zSchemaTab = smprintf("main.sqlite_schema");
+ }else if( cli_strcmp(zDb,"temp")==0 ){
+ zSchemaTab = smprintf("%s", "sqlite_temp_schema");
+ }else{
+ zSchemaTab = smprintf("\"%w\".sqlite_schema", zDb);
}
- pAuxDb->nSession = 0;
+ for(i=0; i<ArraySize(aQuery); i++){
+ char *zSql = smprintf(aQuery[i].zSql, zSchemaTab);
+ int val = db_int(DBX(psx), zSql);
+ sqlite3_free(zSql);
+ utf8_printf(out, "%-20s %d\n", aQuery[i].zName, val);
+ }
+ sqlite3_free(zSchemaTab);
+ sqlite3_file_control(DBX(psx), zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
+ utf8_printf(out, "%-20s %u\n", "data version", iDataVersion);
+ return 0;
}
-#else
-# define session_close_all(X,Y)
-#endif
+#endif /* SQLITE_SHELL_HAVE_RECOVER */
/*
-** Implementation of the xFilter function for an open session. Omit
-** any tables named by ".session filter" but let all other table through.
+** Print the current sqlite3_errmsg() value to stderr and return 1.
*/
-#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;
- }
+static int shellDatabaseError(sqlite3 *db){
+ const char *zErr = sqlite3_errmsg(db);
+ utf8_printf(STD_ERR, "Error: %s\n", zErr);
return 1;
}
-#endif
/*
-** Try to deduce the type of file for zName based on its content. Return
-** one of the SHELL_OPEN_* constants.
+** Compare the pattern in zGlob[] against the text in z[]. Return TRUE
+** if they match and FALSE (0) if they do not match.
**
-** 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.
+** Globbing rules:
+**
+** '*' Matches any sequence of zero or more characters.
+**
+** '?' Matches exactly one character.
+**
+** [...] Matches one character from the enclosed list of
+** characters.
+**
+** [^...] Matches one character not in the enclosed list.
+**
+** '#' Matches any sequence of one or more digits with an
+** optional + or - sign in front
+**
+** ' ' Any span of whitespace matches any other span of
+** whitespace.
+**
+** Extra whitespace at the end of z[] is ignored.
*/
-int deduceDatabaseType(const char *zName, int dfltZip){
- FILE *f = fopen(zName, "rb");
- size_t n;
- int rc = SHELL_OPEN_UNSPEC;
- char zBuf[100];
- if( f==0 ){
- if( dfltZip && sqlite3_strlike("%.zip",zName,0)==0 ){
- return SHELL_OPEN_ZIPFILE;
+static int testcase_glob(const char *zGlob, const char *z){
+ int c, c2;
+ int invert;
+ int seen;
+
+ while( (c = (*(zGlob++)))!=0 ){
+ if( IsSpace(c) ){
+ if( !IsSpace(*z) ) return 0;
+ zGlob = skipWhite(zGlob);
+ z = skipWhite(z);
+ }else if( c=='*' ){
+ while( (c=(*(zGlob++))) == '*' || c=='?' ){
+ if( c=='?' && (*(z++))==0 ) return 0;
+ }
+ if( c==0 ){
+ return 1;
+ }else if( c=='[' ){
+ while( *z && testcase_glob(zGlob-1,z)==0 ){
+ z++;
+ }
+ return (*z)!=0;
+ }
+ while( (c2 = (*(z++)))!=0 ){
+ while( c2!=c ){
+ c2 = *(z++);
+ if( c2==0 ) return 0;
+ }
+ if( testcase_glob(zGlob,z) ) return 1;
+ }
+ return 0;
+ }else if( c=='?' ){
+ if( (*(z++))==0 ) return 0;
+ }else if( c=='[' ){
+ int prior_c = 0;
+ seen = 0;
+ invert = 0;
+ c = *(z++);
+ if( c==0 ) return 0;
+ c2 = *(zGlob++);
+ if( c2=='^' ){
+ invert = 1;
+ c2 = *(zGlob++);
+ }
+ if( c2==']' ){
+ if( c==']' ) seen = 1;
+ c2 = *(zGlob++);
+ }
+ while( c2 && c2!=']' ){
+ if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
+ c2 = *(zGlob++);
+ if( c>=prior_c && c<=c2 ) seen = 1;
+ prior_c = 0;
+ }else{
+ if( c==c2 ){
+ seen = 1;
+ }
+ prior_c = c2;
+ }
+ c2 = *(zGlob++);
+ }
+ if( c2==0 || (seen ^ invert)==0 ) return 0;
+ }else if( c=='#' ){
+ if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++;
+ if( !IsDigit(z[0]) ) return 0;
+ z++;
+ while( IsDigit(z[0]) ){ z++; }
}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;
+ if( c!=(*(z++)) ) return 0;
}
}
- fclose(f);
- return rc;
+ return *skipWhite(z)==0;
}
-#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 standard input.
+** Compare the string as a command-line option with either one or two
+** initial "-" characters.
*/
-static unsigned char *readHexDb(ShellState *p, int *pnData){
- unsigned char *a = 0;
- int nLine;
- int n = 0;
- int pgsz = 0;
- int iOffset = 0;
- int j, k;
- int rc;
- FILE *in;
- const char *zDbFilename = p->pAuxDb->zDbFilename;
- unsigned int x[16];
- char zLine[1000];
- if( zDbFilename ){
- in = fopen(zDbFilename, "r");
- if( in==0 ){
- utf8_printf(stderr, "cannot open \"%s\" for reading\n", zDbFilename);
- return 0;
- }
- nLine = 0;
- }else{
- in = p->in;
- nLine = p->lineno;
- if( in==0 ) in = stdin;
- }
- *pnData = 0;
- nLine++;
- if( fgets(zLine, sizeof(zLine), in)==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;
- if( pgsz<512 || pgsz>65536 || (pgsz&(pgsz-1))!=0 ) goto readHexDb_error;
- n = (n+pgsz-1)&~(pgsz-1); /* Round n up to the next multiple of pgsz */
- a = sqlite3_malloc( n ? n : 1 );
- shell_check_oom(a);
- memset(a, 0, n);
- if( pgsz<512 || pgsz>65536 || (pgsz & (pgsz-1))!=0 ){
- utf8_printf(stderr, "invalid pagesize\n");
- goto readHexDb_error;
- }
- for(nLine++; fgets(zLine, sizeof(zLine), in)!=0; nLine++){
- rc = sscanf(zLine, "| page %d offset %d", &j, &k);
- if( rc==2 ){
- iOffset = k;
- continue;
- }
- if( cli_strncmp(zLine, "| end ", 6)==0 ){
- break;
- }
- rc = sscanf(zLine,"| %d: %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x",
- &j, &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7],
- &x[8], &x[9], &x[10], &x[11], &x[12], &x[13], &x[14], &x[15]);
- if( rc==17 ){
- k = iOffset+j;
- if( k+16<=n && k>=0 ){
- int ii;
- for(ii=0; ii<16; ii++) a[k+ii] = x[ii]&0xff;
- }
- }
- }
- *pnData = n;
- if( in!=p->in ){
- fclose(in);
- }else{
- p->lineno = nLine;
- }
- return a;
+static int optionMatch(const char *zStr, const char *zOpt){
+ if( zStr[0]!='-' ) return 0;
+ zStr++;
+ if( zStr[0]=='-' ) zStr++;
+ return cli_strcmp(zStr, zOpt)==0;
+}
-readHexDb_error:
- if( in!=p->in ){
- fclose(in);
- }else{
- while( fgets(zLine, sizeof(zLine), p->in)!=0 ){
- nLine++;
- if(cli_strncmp(zLine, "| end ", 6)==0 ) break;
- }
- p->lineno = nLine;
- }
- sqlite3_free(a);
- utf8_printf(stderr,"Error on line %d of --hexdb input\n", nLine);
- return 0;
+/*
+** Delete a file.
+*/
+int shellDeleteFile(const char *zFilename){
+ int rc;
+#ifdef _WIN32
+ wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename);
+ rc = _wunlink(z);
+ sqlite3_free(z);
+#else
+ rc = unlink(zFilename);
+#endif
+ return rc;
}
-#endif /* SQLITE_OMIT_DESERIALIZE */
/*
-** Scalar function "usleep(X)" invokes sqlite3_sleep(X) and returns X.
+** Try to delete the temporary file (if there is one) and free the
+** memory used to hold the name of the temp file.
*/
-static void shellUSleepFunc(
- sqlite3_context *context,
- int argcUnused,
- sqlite3_value **argv
-){
- int sleep = sqlite3_value_int(argv[0]);
- (void)argcUnused;
- sqlite3_sleep(sleep/1000);
- sqlite3_result_int(context, sleep);
+static void clearTempFile(ShellInState *psi){
+ if( psi->zTempFile==0 ) return;
+ if( psi->doXdgOpen ) return;
+ if( shellDeleteFile(psi->zTempFile) ) return;
+ sqlite3_free(psi->zTempFile);
+ psi->zTempFile = 0;
}
-/* Flags for open_db().
-**
-** The default behavior of open_db() is to exit(1) if the database fails to
-** open. The OPEN_DB_KEEPALIVE flag changes that so that it prints an error
-** but still returns without calling exit.
-**
-** The OPEN_DB_ZIPFILE flag causes open_db() to prefer to open files as a
-** ZIP archive if the file does not exist or is empty and its name matches
-** the *.zip pattern.
+/*
+** Create a new temp file name with the given suffix.
*/
-#define OPEN_DB_KEEPALIVE 0x001 /* Return after error if true */
-#define OPEN_DB_ZIPFILE 0x002 /* Open as ZIP if name matches *.zip */
+static void newTempFile(ShellInState *psi, const char *zSuffix){
+ clearTempFile(psi);
+ sqlite3_free(psi->zTempFile);
+ psi->zTempFile = 0;
+ if( DBI(psi) ){
+ sqlite3_file_control(DBI(psi), 0, SQLITE_FCNTL_TEMPFILENAME,
+ &psi->zTempFile);
+ }
+ if( psi->zTempFile==0 ){
+ /* If DB is an in-memory database then the TEMPFILENAME file-control
+ ** will not work and we will need to fallback to guessing */
+ char *zTemp;
+ sqlite3_uint64 r;
+ sqlite3_randomness(sizeof(r), &r);
+ zTemp = getenv("TEMP");
+ if( zTemp==0 ) zTemp = getenv("TMP");
+ if( zTemp==0 ){
+#ifdef _WIN32
+ zTemp = "\\tmp";
+#else
+ zTemp = "/tmp";
+#endif
+ }
+ psi->zTempFile = smprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
+ }else{
+ psi->zTempFile = smprintf("%z.%s", psi->zTempFile, zSuffix);
+ }
+ shell_check_ooms(psi->zTempFile);
+}
/*
-** Make sure the database is open. If it is not, then open it. If
-** the database fails to open, print an error message and exit.
+** The implementation of SQL scalar function fkey_collate_clause(), used
+** by the ".lint fkey-indexes" command. This scalar function is always
+** called with four arguments - the parent table name, the parent column name,
+** the child table name and the child column name.
+**
+** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col')
+**
+** If either of the named tables or columns do not exist, this function
+** returns an empty string. An empty string is also returned if both tables
+** and columns exist but have the same default collation sequence. Or,
+** if both exist but the default collation sequences are different, this
+** function returns the string " COLLATE <parent-collation>", where
+** <parent-collation> is the default collation sequence of the parent column.
*/
-static void open_db(ShellState *p, int openFlags){
- if( p->db==0 ){
- const char *zDbFilename = p->pAuxDb->zDbFilename;
- if( p->openMode==SHELL_OPEN_UNSPEC ){
- if( zDbFilename==0 || zDbFilename[0]==0 ){
- p->openMode = SHELL_OPEN_NORMAL;
- }else{
- p->openMode = (u8)deduceDatabaseType(zDbFilename,
- (openFlags & OPEN_DB_ZIPFILE)!=0);
- }
- }
- switch( p->openMode ){
- case SHELL_OPEN_APPENDVFS: {
- sqlite3_open_v2(zDbFilename, &p->db,
- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, "apndvfs");
- break;
- }
- case SHELL_OPEN_HEXDB:
- case SHELL_OPEN_DESERIALIZE: {
- sqlite3_open(0, &p->db);
- break;
- }
- case SHELL_OPEN_ZIPFILE: {
- sqlite3_open(":memory:", &p->db);
- break;
- }
- case SHELL_OPEN_READONLY: {
- sqlite3_open_v2(zDbFilename, &p->db,
- SQLITE_OPEN_READONLY|p->openFlags, 0);
- break;
- }
- case SHELL_OPEN_UNSPEC:
- case SHELL_OPEN_NORMAL: {
- sqlite3_open_v2(zDbFilename, &p->db,
- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|p->openFlags, 0);
- break;
- }
- }
- globalDb = p->db;
- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
- utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n",
- zDbFilename, sqlite3_errmsg(p->db));
- if( (openFlags & OPEN_DB_KEEPALIVE)==0 ){
- exit(1);
- }
- sqlite3_close(p->db);
- sqlite3_open(":memory:", &p->db);
- if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
- utf8_printf(stderr,
- "Also: unable to open substitute in-memory database.\n"
- );
- exit(1);
- }else{
- utf8_printf(stderr,
- "Notice: using substitute in-memory database instead of \"%s\"\n",
- zDbFilename);
- }
- }
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, (int)0, (int*)0);
+static void shellFkeyCollateClause(
+ sqlite3_context *pCtx,
+ int nVal,
+ sqlite3_value **apVal
+){
+ sqlite3 *db = sqlite3_context_db_handle(pCtx);
+ const char *zParent;
+ const char *zParentCol;
+ const char *zParentSeq;
+ const char *zChild;
+ const char *zChildCol;
+ const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */
+ int rc;
- /* Reflect the use or absence of --unsafe-testing invocation. */
- {
- int testmode_on = ShellHasFlag(p,SHFLG_TestingMode);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_TRUSTED_SCHEMA, testmode_on,0);
- sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, !testmode_on,0);
- }
+ assert( nVal==4 );
+ zParent = (const char*)sqlite3_value_text(apVal[0]);
+ zParentCol = (const char*)sqlite3_value_text(apVal[1]);
+ zChild = (const char*)sqlite3_value_text(apVal[2]);
+ zChildCol = (const char*)sqlite3_value_text(apVal[3]);
-#ifndef SQLITE_OMIT_LOAD_EXTENSION
- sqlite3_enable_load_extension(p->db, 1);
-#endif
- sqlite3_shathree_init(p->db, 0, 0);
- sqlite3_uint_init(p->db, 0, 0);
- sqlite3_decimal_init(p->db, 0, 0);
- sqlite3_base64_init(p->db, 0, 0);
- sqlite3_base85_init(p->db, 0, 0);
- sqlite3_regexp_init(p->db, 0, 0);
- sqlite3_ieee_init(p->db, 0, 0);
- sqlite3_series_init(p->db, 0, 0);
-#ifndef SQLITE_SHELL_FIDDLE
- sqlite3_fileio_init(p->db, 0, 0);
- sqlite3_completion_init(p->db, 0, 0);
-#endif
-#ifdef SQLITE_HAVE_ZLIB
- if( !p->bSafeModePersist ){
- sqlite3_zipfile_init(p->db, 0, 0);
- sqlite3_sqlar_init(p->db, 0, 0);
- }
-#endif
-#ifdef SQLITE_SHELL_EXTFUNCS
- /* Create a preprocessing mechanism for extensions to make
- * their own provisions for being built into the shell.
- * This is a short-span macro. See further below for usage.
- */
-#define SHELL_SUB_MACRO(base, variant) base ## _ ## variant
-#define SHELL_SUBMACRO(base, variant) SHELL_SUB_MACRO(base, variant)
- /* Let custom-included extensions get their ..._init() called.
- * The WHATEVER_INIT( db, pzErrorMsg, pApi ) macro should cause
- * the extension's sqlite3_*_init( db, pzErrorMsg, pApi )
- * initialization routine to be called.
- */
- {
- int irc = SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, INIT)(p->db);
- /* Let custom-included extensions expose their functionality.
- * The WHATEVER_EXPOSE( db, pzErrorMsg ) macro should cause
- * the SQL functions, virtual tables, collating sequences or
- * VFS's implemented by the extension to be registered.
- */
- if( irc==SQLITE_OK
- || irc==SQLITE_OK_LOAD_PERMANENTLY ){
- SHELL_SUBMACRO(SQLITE_SHELL_EXTFUNCS, EXPOSE)(p->db, 0);
- }
-#undef SHELL_SUB_MACRO
-#undef SHELL_SUBMACRO
- }
-#endif
+ sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC);
+ rc = sqlite3_table_column_metadata(
+ db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0
+ );
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_table_column_metadata(
+ db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0
+ );
+ }
- sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
- shellAddSchemaName, 0, 0);
- sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
- shellModuleSchema, 0, 0);
- sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
- shellPutsFunc, 0, 0);
- sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
- shellUSleepFunc, 0, 0);
-#ifndef SQLITE_NOHAVE_SYSTEM
- sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
- editFunc, 0, 0);
- sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
- editFunc, 0, 0);
-#endif
+ if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){
+ char *z = smprintf(" COLLATE %s", zParentSeq);
+ sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
+ sqlite3_free(z);
+ }
+}
- if( p->openMode==SHELL_OPEN_ZIPFILE ){
- char *zSql = sqlite3_mprintf(
- "CREATE VIRTUAL TABLE zip USING zipfile(%Q);", zDbFilename);
- shell_check_oom(zSql);
- sqlite3_exec(p->db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
+#if !defined SQLITE_OMIT_VIRTUALTABLE
+static void shellPrepare(
+ sqlite3 *db,
+ int *pRc,
+ const char *zSql,
+ sqlite3_stmt **ppStmt
+){
+ *ppStmt = 0;
+ if( *pRc==SQLITE_OK ){
+ int rc = s3_prepare_v2_noom(db, zSql, -1, ppStmt, 0);
+ if( rc!=SQLITE_OK ){
+ raw_printf(STD_ERR, "sql error: %s (%d)\n",
+ sqlite3_errmsg(db), sqlite3_errcode(db)
+ );
+ *pRc = rc;
}
-#ifndef SQLITE_OMIT_DESERIALIZE
- else
- if( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){
- int rc;
- int nData = 0;
- unsigned char *aData;
- if( p->openMode==SHELL_OPEN_DESERIALIZE ){
- aData = (unsigned char*)readFile(zDbFilename, &nData);
- }else{
- aData = readHexDb(p, &nData);
- }
- if( aData==0 ){
- return;
- }
- rc = sqlite3_deserialize(p->db, "main", aData, nData, nData,
- SQLITE_DESERIALIZE_RESIZEABLE |
- SQLITE_DESERIALIZE_FREEONCLOSE);
- if( rc ){
- utf8_printf(stderr, "Error: sqlite3_deserialize() returns %d\n", rc);
- }
- if( p->szMax>0 ){
- sqlite3_file_control(p->db, "main", SQLITE_FCNTL_SIZE_LIMIT, &p->szMax);
+ }
+}
+
+/*
+** Create a prepared statement using printf-style arguments for the SQL.
+**
+** This routine is could be marked "static". But it is not always used,
+** depending on compile-time options. By omitting the "static", we avoid
+** nuisance compiler warnings about "defined but not used".
+*/
+void shellPreparePrintf(
+ sqlite3 *db,
+ int *pRc,
+ sqlite3_stmt **ppStmt,
+ const char *zFmt,
+ ...
+){
+ *ppStmt = 0;
+ if( *pRc==SQLITE_OK ){
+ va_list ap;
+ char *z;
+ va_start(ap, zFmt);
+ z = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+ if( z==0 ){
+ *pRc = SQLITE_NOMEM;
+ }else{
+ shellPrepare(db, pRc, z, ppStmt);
+ sqlite3_free(z);
+ }
+ }
+}
+
+/* Finalize the prepared statement created using shellPreparePrintf().
+**
+** This routine is could be marked "static". But it is not always used,
+** depending on compile-time options. By omitting the "static", we avoid
+** nuisance compiler warnings about "defined but not used".
+*/
+void shellFinalize(
+ int *pRc,
+ sqlite3_stmt *pStmt
+){
+ if( pStmt ){
+ sqlite3 *db = sqlite3_db_handle(pStmt);
+ int rc = sqlite3_finalize(pStmt);
+ if( *pRc==SQLITE_OK ){
+ if( rc!=SQLITE_OK ){
+ raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
}
+ *pRc = rc;
}
-#endif
}
- if( p->db!=0 ){
- if( p->bSafeModePersist ){
- sqlite3_set_authorizer(p->db, safeModeAuth, p);
+}
+
+/* Reset the prepared statement created using shellPreparePrintf().
+**
+** This routine is could be marked "static". But it is not always used,
+** depending on compile-time options. By omitting the "static", we avoid
+** nuisance compiler warnings about "defined but not used".
+*/
+void shellReset(
+ int *pRc,
+ sqlite3_stmt *pStmt
+){
+ int rc = sqlite3_reset(pStmt);
+ if( *pRc==SQLITE_OK ){
+ if( rc!=SQLITE_OK ){
+ sqlite3 *db = sqlite3_db_handle(pStmt);
+ raw_printf(STD_ERR, "SQL error: %s\n", sqlite3_errmsg(db));
}
- sqlite3_db_config(
- p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
- );
+ *pRc = rc;
}
}
+#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
+
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+# define ARCHIVE_ENABLE 1
+#else
+# define ARCHIVE_ENABLE 0
+#endif
+#if ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE)
+/******************************************************************************
+** The ".archive" or ".ar" command.
+*/
/*
-** Attempt to close the database connection. Report errors.
+** Structure representing a single ".ar" command.
*/
-void close_db(sqlite3 *db){
- int rc = sqlite3_close(db);
- if( rc ){
- utf8_printf(stderr, "Error: sqlite3_close() returns %d: %s\n",
- rc, sqlite3_errmsg(db));
- }
+typedef struct ArCommand ArCommand;
+struct ArCommand {
+ u8 eCmd; /* An AR_CMD_* value */
+ u8 bVerbose; /* True if --verbose */
+ u8 bZip; /* True if the archive is a ZIP */
+ u8 bDryRun; /* True if --dry-run */
+ u8 bAppend; /* True if --append */
+ u8 bGlob; /* True if --glob */
+ u8 fromCmdLine; /* Run from -A instead of .archive */
+ int nArg; /* Number of command arguments */
+ char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */
+ const char *zFile; /* --file argument, or NULL */
+ const char *zDir; /* --directory argument, or NULL */
+ char **azArg; /* Array of command arguments */
+ ShellExState *p; /* Shell state */
+ FILE *out; /* Where to put normal messages or info */
+ sqlite3 *db; /* Database containing the archive */
+};
+
+/*
+** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
+*/
+static int arUsage(FILE *f, ArCommand *pAr){
+ showHelp(f,"archive", pAr->p);
+ return SQLITE_ERROR;
}
-#if HAVE_READLINE || HAVE_EDITLINE
/*
-** Readline completion callbacks
+** Print an error message for the .ar command to stderr and return
+** SQLITE_ERROR.
*/
-static char *readline_completion_generator(const char *text, int state){
- static sqlite3_stmt *pStmt = 0;
- char *zRet;
- if( state==0 ){
- char *zSql;
- sqlite3_finalize(pStmt);
- zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
- " FROM completion(%Q) ORDER BY 1", text);
- shell_check_oom(zSql);
- sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- }
- if( sqlite3_step(pStmt)==SQLITE_ROW ){
- const char *z = (const char*)sqlite3_column_text(pStmt,0);
- zRet = z ? strdup(z) : 0;
+static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
+ va_list ap;
+ char *z;
+ va_start(ap, zFmt);
+ z = sqlite3_vmprintf(zFmt, ap);
+ va_end(ap);
+ utf8_printf(STD_ERR, "Error: %s\n", z);
+ if( pAr->fromCmdLine ){
+ utf8_printf(STD_ERR, "Use \"-A\" for more help\n");
}else{
- sqlite3_finalize(pStmt);
- pStmt = 0;
- zRet = 0;
+ utf8_printf(STD_ERR, "Use \".archive --help\" for more help\n");
}
- return zRet;
-}
-static char **readline_completion(const char *zText, int iStart, int iEnd){
- (void)iStart;
- (void)iEnd;
- rl_attempted_completion_over = 1;
- return rl_completion_matches(zText, readline_completion_generator);
+ sqlite3_free(z);
+ return SQLITE_ERROR;
}
-#elif HAVE_LINENOISE
/*
-** Linenoise completion callback
+** Values for ArCommand.eCmd.
*/
-static void linenoise_completion(const char *zLine, linenoiseCompletions *lc){
- i64 nLine = strlen(zLine);
- i64 i, iStart;
- sqlite3_stmt *pStmt = 0;
- char *zSql;
- char zBuf[1000];
+#define AR_CMD_CREATE 1
+#define AR_CMD_UPDATE 2
+#define AR_CMD_INSERT 3
+#define AR_CMD_EXTRACT 4
+#define AR_CMD_LIST 5
+#define AR_CMD_HELP 6
+#define AR_CMD_REMOVE 7
- if( nLine>(i64)sizeof(zBuf)-30 ) return;
- if( zLine[0]=='.' || zLine[0]=='#') return;
- for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
- if( i==nLine-1 ) return;
- iStart = i+1;
- memcpy(zBuf, zLine, iStart);
- zSql = sqlite3_mprintf("SELECT DISTINCT candidate COLLATE nocase"
- " FROM completion(%Q,%Q) ORDER BY 1",
- &zLine[iStart], zLine);
- shell_check_oom(zSql);
- sqlite3_prepare_v2(globalDb, zSql, -1, &pStmt, 0);
- sqlite3_free(zSql);
- sqlite3_exec(globalDb, "PRAGMA page_count", 0, 0, 0); /* Load the schema */
- while( sqlite3_step(pStmt)==SQLITE_ROW ){
- const char *zCompletion = (const char*)sqlite3_column_text(pStmt, 0);
- int nCompletion = sqlite3_column_bytes(pStmt, 0);
- if( iStart+nCompletion < (i64)sizeof(zBuf)-1 && zCompletion ){
- memcpy(zBuf+iStart, zCompletion, nCompletion+1);
- linenoiseAddCompletion(lc, zBuf);
- }
+/*
+** Other (non-command) switches.
+*/
+#define AR_SWITCH_VERBOSE 8
+#define AR_SWITCH_FILE 9
+#define AR_SWITCH_DIRECTORY 10
+#define AR_SWITCH_APPEND 11
+#define AR_SWITCH_DRYRUN 12
+#define AR_SWITCH_GLOB 13
+
+static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
+ switch( eSwitch ){
+ case AR_CMD_CREATE:
+ case AR_CMD_EXTRACT:
+ case AR_CMD_LIST:
+ case AR_CMD_REMOVE:
+ case AR_CMD_UPDATE:
+ case AR_CMD_INSERT:
+ case AR_CMD_HELP:
+ if( pAr->eCmd ){
+ return arErrorMsg(pAr, "multiple command options");
+ }
+ pAr->eCmd = eSwitch;
+ break;
+
+ case AR_SWITCH_DRYRUN:
+ pAr->bDryRun = 1;
+ break;
+ case AR_SWITCH_GLOB:
+ pAr->bGlob = 1;
+ break;
+ case AR_SWITCH_VERBOSE:
+ pAr->bVerbose = 1;
+ break;
+ case AR_SWITCH_APPEND:
+ pAr->bAppend = 1;
+ deliberate_fall_through;
+ case AR_SWITCH_FILE:
+ pAr->zFile = zArg;
+ break;
+ case AR_SWITCH_DIRECTORY:
+ pAr->zDir = zArg;
+ break;
}
- sqlite3_finalize(pStmt);
+
+ return SQLITE_OK;
}
-#endif
/*
-** Do C-language style dequoting.
-**
-** \a -> alarm
-** \b -> backspace
-** \t -> tab
-** \n -> newline
-** \v -> vertical tab
-** \f -> form feed
-** \r -> carriage return
-** \s -> space
-** \" -> "
-** \' -> '
-** \\ -> backslash
-** \NNN -> ascii character NNN in octal
-** \xHH -> ascii character HH in hexadecimal
+** Parse the command line for an ".ar" command. The results are written into
+** structure (*pAr). DCR_Ok is returned if the command line is parsed
+** successfully, otherwise an error message is written to stderr and an
+** appropriate DCR_* argument error is returned.
*/
-static void resolve_backslashes(char *z){
- int i, j;
- char c;
- while( *z && *z!='\\' ) z++;
- for(i=j=0; (c = z[i])!=0; i++, j++){
- if( c=='\\' && z[i+1]!=0 ){
- c = z[++i];
- if( c=='a' ){
- c = '\a';
- }else if( c=='b' ){
- c = '\b';
- }else if( c=='t' ){
- c = '\t';
- }else if( c=='n' ){
- c = '\n';
- }else if( c=='v' ){
- c = '\v';
- }else if( c=='f' ){
- c = '\f';
- }else if( c=='r' ){
- c = '\r';
- }else if( c=='"' ){
- c = '"';
- }else if( c=='\'' ){
- c = '\'';
- }else if( c=='\\' ){
- c = '\\';
- }else if( c=='x' ){
- int nhd = 0, hdv;
- u8 hv = 0;
- while( nhd<2 && (c=z[i+1+nhd])!=0 && (hdv=hexDigitValue(c))>=0 ){
- hv = (u8)((hv<<4)|hdv);
- ++nhd;
+static DotCmdRC arParseCommand(
+ char **azArg, /* Array of arguments passed to dot command */
+ int nArg, /* Number of entries in azArg[] */
+ ArCommand *pAr /* Populate this object */
+){
+ struct ArSwitch {
+ const char *zLong;
+ char cShort;
+ u8 eSwitch;
+ u8 bArg;
+ } aSwitch[] = {
+ { "create", 'c', AR_CMD_CREATE, 0 },
+ { "extract", 'x', AR_CMD_EXTRACT, 0 },
+ { "insert", 'i', AR_CMD_INSERT, 0 },
+ { "list", 't', AR_CMD_LIST, 0 },
+ { "remove", 'r', AR_CMD_REMOVE, 0 },
+ { "update", 'u', AR_CMD_UPDATE, 0 },
+ { "help", 'h', AR_CMD_HELP, 0 },
+ { "verbose", 'v', AR_SWITCH_VERBOSE, 0 },
+ { "file", 'f', AR_SWITCH_FILE, 1 },
+ { "append", 'a', AR_SWITCH_APPEND, 1 },
+ { "directory", 'C', AR_SWITCH_DIRECTORY, 1 },
+ { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 },
+ { "glob", 'g', AR_SWITCH_GLOB, 0 },
+ };
+ int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
+ struct ArSwitch *pEnd = &aSwitch[nSwitch];
+ DotCmdRC rv = DCR_Ok;
+
+ if( nArg<=1 ){
+ utf8_printf(STD_ERR, "Error: Wrong number of arguments to \"%s\".\n",
+ azArg[0]);
+ if( stdin_is_interactive ){
+ utf8_printf(STD_ERR, "Usage:\n");
+ arUsage(STD_ERR, pAr);
+ return DCR_CmdErred;
+ }
+ }else{
+ char *z = azArg[1];
+ if( z[0]!='-' ){
+ /* Traditional style [tar] invocation */
+ int i;
+ int iArg = 2;
+ for(i=0; z[i]; i++){
+ const char *zArg = 0;
+ struct ArSwitch *pOpt;
+ for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
+ if( z[i]==pOpt->cShort ) break;
+ }
+ if( pOpt==pEnd ){
+ arErrorMsg(pAr, "unrecognized option: %c", z[i]);
+ return DCR_Unknown|i;
+ }
+ if( pOpt->bArg ){
+ if( iArg>=nArg ){
+ arErrorMsg(pAr, "option requires an argument: %c",z[i]);
+ return DCR_Unpaired|i;
+ }
+ zArg = azArg[iArg++];
+ }
+ rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
+ if( rv!=DCR_Ok ) return rv;
+ }
+ pAr->nArg = nArg-iArg;
+ if( pAr->nArg>0 ){
+ pAr->azArg = &azArg[iArg];
+ }
+ }else{
+ /* Non-traditional invocation */
+ int iArg;
+ for(iArg=1; iArg<nArg; iArg++){
+ int n;
+ z = azArg[iArg];
+ if( z[0]!='-' ){
+ /* All remaining command line words are command arguments. */
+ pAr->azArg = &azArg[iArg];
+ pAr->nArg = nArg-iArg;
+ break;
}
- i += nhd;
- c = (u8)hv;
- }else if( c>='0' && c<='7' ){
- c -= '0';
- if( z[i+1]>='0' && z[i+1]<='7' ){
- i++;
- c = (c<<3) + z[i] - '0';
- if( z[i+1]>='0' && z[i+1]<='7' ){
- i++;
- c = (c<<3) + z[i] - '0';
+ n = strlen30(z);
+
+ if( z[1]!='-' ){
+ int i;
+ /* One or more short options */
+ for(i=1; i<n; i++){
+ const char *zArg = 0;
+ struct ArSwitch *pOpt;
+ for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
+ if( z[i]==pOpt->cShort ) break;
+ }
+ if( pOpt==pEnd ){
+ arErrorMsg(pAr, "unrecognized option: %c", z[i]);
+ return DCR_Unknown|iArg;
+ }
+ if( pOpt->bArg ){
+ if( i<(n-1) ){
+ zArg = &z[i+1];
+ i = n;
+ }else{
+ if( iArg>=(nArg-1) ){
+ arErrorMsg(pAr, "option requires an argument: %c", z[i]);
+ return DCR_Unpaired|iArg;
+ }
+ zArg = azArg[++iArg];
+ }
+ }
+ rv = arProcessSwitch(pAr, pOpt->eSwitch, zArg);
+ if( rv!=DCR_Ok ) return rv;
+ }
+ }else if( z[2]=='\0' ){
+ /* A -- option, indicating that all remaining command line words
+ ** are command arguments. */
+ pAr->azArg = &azArg[iArg+1];
+ pAr->nArg = nArg-iArg-1;
+ break;
+ }else{
+ /* A long option */
+ const char *zArg = 0; /* Argument for option, if any */
+ struct ArSwitch *pMatch = 0; /* Matching option */
+ struct ArSwitch *pOpt; /* Iterator */
+ for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
+ const char *zLong = pOpt->zLong;
+ if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
+ if( pMatch ){
+ arErrorMsg(pAr, "ambiguous option: %s",z);
+ return DCR_Ambiguous|iArg;
+ }else{
+ pMatch = pOpt;
+ }
+ }
+ }
+
+ if( pMatch==0 ){
+ arErrorMsg(pAr, "unrecognized option: %s", z);
+ return DCR_Unknown|iArg;
+ }
+ if( pMatch->bArg ){
+ if( iArg>=(nArg-1) ){
+ arErrorMsg(pAr, "option requires an argument: %s", z);
+ return DCR_Unpaired|iArg;
+ }
+ zArg = azArg[++iArg];
}
+ if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
}
}
}
}
/*
-** Try to transfer data for table zTable. If an error is seen while
-** moving forward, try to go backwards. The backwards movement won't
-** work for WITHOUT ROWID tables.
+** Implementation of ".ar" dot command.
*/
-static void tryToCloneData(
- ShellState *p,
- sqlite3 *newDb,
- const char *zTable
+static DotCmdRC arDotCommand(
+ ShellExState *pState, /* Current shell tool state */
+ int fromCmdLine, /* True if -A command-line option, not .ar cmd */
+ char **azArg, /* Array of arguments passed to dot command */
+ int nArg /* Number of entries in azArg[] */
){
- sqlite3_stmt *pQuery = 0;
- sqlite3_stmt *pInsert = 0;
- char *zQuery = 0;
- char *zInsert = 0;
+ ArCommand cmd;
+ DotCmdRC rv;
int rc;
- int i, j, n;
- int nTable = strlen30(zTable);
- int k = 0;
- int cnt = 0;
- const int spinRate = 10000;
-
- zQuery = sqlite3_mprintf("SELECT * FROM \"%w\"", zTable);
- shell_check_oom(zQuery);
- rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
- if( rc ){
- utf8_printf(stderr, "Error %d: %s on [%s]\n",
- sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
- zQuery);
- goto end_data_xfer;
- }
- n = sqlite3_column_count(pQuery);
- zInsert = sqlite3_malloc64(200 + nTable + n*3);
- shell_check_oom(zInsert);
- sqlite3_snprintf(200+nTable,zInsert,
- "INSERT OR IGNORE INTO \"%s\" VALUES(?", zTable);
- i = strlen30(zInsert);
- for(j=1; j<n; j++){
- memcpy(zInsert+i, ",?", 2);
- i += 2;
- }
- memcpy(zInsert+i, ");", 3);
- rc = sqlite3_prepare_v2(newDb, zInsert, -1, &pInsert, 0);
- if( rc ){
- utf8_printf(stderr, "Error %d: %s on [%s]\n",
- sqlite3_extended_errcode(newDb), sqlite3_errmsg(newDb),
- zInsert);
- goto end_data_xfer;
- }
- for(k=0; k<2; k++){
- while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
- for(i=0; i<n; i++){
- switch( sqlite3_column_type(pQuery, i) ){
- case SQLITE_NULL: {
- sqlite3_bind_null(pInsert, i+1);
- break;
- }
- case SQLITE_INTEGER: {
- sqlite3_bind_int64(pInsert, i+1, sqlite3_column_int64(pQuery,i));
- break;
- }
- case SQLITE_FLOAT: {
- sqlite3_bind_double(pInsert, i+1, sqlite3_column_double(pQuery,i));
- break;
- }
- case SQLITE_TEXT: {
- sqlite3_bind_text(pInsert, i+1,
- (const char*)sqlite3_column_text(pQuery,i),
- -1, SQLITE_STATIC);
- break;
- }
- case SQLITE_BLOB: {
- sqlite3_bind_blob(pInsert, i+1, sqlite3_column_blob(pQuery,i),
- sqlite3_column_bytes(pQuery,i),
- SQLITE_STATIC);
- break;
- }
+ memset(&cmd, 0, sizeof(cmd));
+ cmd.fromCmdLine = fromCmdLine;
+ rv = arParseCommand(azArg, nArg, &cmd);
+ cmd.out = ISS(pState)->out;
+ if( rv==DCR_Ok ){
+ int eDbType = SHELL_OPEN_UNSPEC;
+ cmd.p = pState;
+ cmd.db = DBX(pState);
+ if( cmd.zFile ){
+ eDbType = deduceDatabaseType(cmd.zFile, 1);
+ }else{
+ eDbType = ISS(pState)->openMode;
+ }
+ if( eDbType==SHELL_OPEN_ZIPFILE ){
+ if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
+ if( cmd.zFile==0 ){
+ cmd.zSrcTable = smprintf("zip");
+ }else{
+ cmd.zSrcTable = smprintf("zipfile(%Q)", cmd.zFile);
}
- } /* End for */
- rc = sqlite3_step(pInsert);
- if( rc!=SQLITE_OK && rc!=SQLITE_ROW && rc!=SQLITE_DONE ){
- utf8_printf(stderr, "Error %d: %s\n", sqlite3_extended_errcode(newDb),
- sqlite3_errmsg(newDb));
}
- sqlite3_reset(pInsert);
- cnt++;
- if( (cnt%spinRate)==0 ){
- printf("%c\b", "|/-\\"[(cnt/spinRate)%4]);
- fflush(stdout);
+ cmd.bZip = 1;
+ }else if( cmd.zFile ){
+ int flags;
+ if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
+ if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
+ || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
+ flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
+ }else{
+ flags = SQLITE_OPEN_READONLY;
}
- } /* End while */
- if( rc==SQLITE_DONE ) break;
- sqlite3_finalize(pQuery);
- sqlite3_free(zQuery);
- zQuery = sqlite3_mprintf("SELECT * FROM \"%w\" ORDER BY rowid DESC;",
- zTable);
- shell_check_oom(zQuery);
- rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
- if( rc ){
- utf8_printf(stderr, "Warning: cannot step \"%s\" backwards", zTable);
- break;
+ cmd.db = 0;
+ if( cmd.bDryRun ){
+ utf8_printf(cmd.out, "-- open database '%s'%s\n", cmd.zFile,
+ eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
+ }
+ 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",
+ cmd.zFile, sqlite3_errmsg(cmd.db)
+ );
+ goto end_ar_command;
+ }
+ sqlite3_fileio_init(cmd.db, 0, 0);
+ sqlite3_sqlar_init(cmd.db, 0, 0);
+ sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
+ shellPutsFunc, 0, 0);
+ }
+ if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
+ if( cmd.eCmd!=AR_CMD_CREATE
+ && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
+ ){
+ utf8_printf(STD_ERR, "database does not contain an 'sqlar' table\n");
+ rc = SQLITE_ERROR;
+ goto end_ar_command;
+ }
+ cmd.zSrcTable = smprintf("sqlar");
}
- } /* End for(k=0...) */
-end_data_xfer:
- sqlite3_finalize(pQuery);
- sqlite3_finalize(pInsert);
- sqlite3_free(zQuery);
- sqlite3_free(zInsert);
-}
+ switch( cmd.eCmd ){
+ case AR_CMD_CREATE:
+ rc = arCreateOrUpdateCommand(&cmd, 0, 0);
+ break;
+
+ case AR_CMD_EXTRACT:
+ rc = arExtractCommand(&cmd);
+ break;
+
+ case AR_CMD_LIST:
+ rc = arListCommand(&cmd);
+ break;
+
+ case AR_CMD_HELP:
+ arUsage(cmd.out, &cmd);
+ break;
+
+ case AR_CMD_INSERT:
+ rc = arCreateOrUpdateCommand(&cmd, 1, 0);
+ break;
+
+ case AR_CMD_REMOVE:
+ rc = arRemoveCommand(&cmd);
+ break;
+
+ default:
+ assert( cmd.eCmd==AR_CMD_UPDATE );
+ rc = arCreateOrUpdateCommand(&cmd, 1, 1);
+ break;
+ }
+ }
+end_ar_command:
+ if( cmd.db!=DBX(pState) ){
+ close_db(cmd.db);
+ }
+ sqlite3_free(cmd.zSrcTable);
+ return (rv!=DCR_Ok)? rv : (DCR_Ok|(rc!=0));
+}
+/* End of the ".archive" or ".ar" command logic
+*******************************************************************************/
+#endif /* ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE) */
+#if SQLITE_SHELL_HAVE_RECOVER
/*
-** Try to transfer all rows of the schema that match zWhere. For
-** each row, invoke xForEach() on the object defined by that row.
-** If an error is encountered while moving forward through the
-** sqlite_schema table, try again moving backwards.
+** This function is used as a callback by the recover extension. Simply
+** print the supplied SQL statement to stdout.
*/
-static void tryToCloneSchema(
- ShellState *p,
- sqlite3 *newDb,
- const char *zWhere,
- void (*xForEach)(ShellState*,sqlite3*,const char*)
-){
- sqlite3_stmt *pQuery = 0;
- char *zQuery = 0;
- int rc;
- const unsigned char *zName;
- const unsigned char *zSql;
- char *zErrMsg = 0;
+static int recoverSqlCb(void *pCtx, const char *zSql){
+ ShellInState *pState = (ShellInState*)pCtx;
+ utf8_printf(pState->out, "%s;\n", zSql); /* ToDo: get right member here. */
+ return SQLITE_OK;
+}
- zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema"
- " WHERE %s ORDER BY rowid ASC", zWhere);
- shell_check_oom(zQuery);
- rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
- if( rc ){
- utf8_printf(stderr, "Error: (%d) %s on [%s]\n",
- sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
- zQuery);
- goto end_schema_xfer;
- }
- while( (rc = sqlite3_step(pQuery))==SQLITE_ROW ){
- zName = sqlite3_column_text(pQuery, 0);
- zSql = sqlite3_column_text(pQuery, 1);
- if( zName==0 || zSql==0 ) continue;
- if( sqlite3_stricmp((char*)zName, "sqlite_sequence")!=0 ){
- printf("%s... ", zName); fflush(stdout);
- sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
- if( zErrMsg ){
- utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
- sqlite3_free(zErrMsg);
- zErrMsg = 0;
+#endif /* SQLITE_SHELL_HAVE_RECOVER */
+
+#ifndef SQLITE_SHELL_FIDDLE
+static DotCmdRC
+writeDb( char *azArg[], int nArg, ShellExState *psx, 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( ISS(psx)->bSafeMode ) return DCR_AbortError;
+ for(j=1; j<nArg; j++){
+ const char *z = azArg[j];
+ if( z[0]=='-' ){
+ if( z[1]=='-' ) z++;
+ if( cli_strcmp(z, "-append")==0 ){
+ zVfs = "apndvfs";
+ }else if( cli_strcmp(z, "-async")==0 ){
+ bAsync = 1;
+ }else{
+ return DCR_Unknown|j;
}
+ }else if( zDestFile==0 ){
+ zDestFile = azArg[j];
+ }else if( zDb==0 ){
+ zDb = zDestFile;
+ zDestFile = azArg[j];
+ }else{
+ return DCR_TooMany|j;
}
- if( xForEach ){
- xForEach(p, newDb, (const char*)zName);
- }
- printf("done\n");
}
- if( rc!=SQLITE_DONE ){
- sqlite3_finalize(pQuery);
- sqlite3_free(zQuery);
- zQuery = sqlite3_mprintf("SELECT name, sql FROM sqlite_schema"
- " WHERE %s ORDER BY rowid DESC", zWhere);
- shell_check_oom(zQuery);
- rc = sqlite3_prepare_v2(p->db, zQuery, -1, &pQuery, 0);
- if( rc ){
- utf8_printf(stderr, "Error: (%d) %s on [%s]\n",
- sqlite3_extended_errcode(p->db), sqlite3_errmsg(p->db),
- zQuery);
- goto end_schema_xfer;
- }
- while( sqlite3_step(pQuery)==SQLITE_ROW ){
- zName = sqlite3_column_text(pQuery, 0);
- zSql = sqlite3_column_text(pQuery, 1);
- if( zName==0 || zSql==0 ) continue;
- if( sqlite3_stricmp((char*)zName, "sqlite_sequence")==0 ) continue;
- printf("%s... ", zName); fflush(stdout);
- sqlite3_exec(newDb, (const char*)zSql, 0, 0, &zErrMsg);
- if( zErrMsg ){
- utf8_printf(stderr, "Error: %s\nSQL: [%s]\n", zErrMsg, zSql);
- sqlite3_free(zErrMsg);
- zErrMsg = 0;
- }
- if( xForEach ){
- xForEach(p, newDb, (const char*)zName);
- }
- printf("done\n");
- }
+ if( zDestFile==0 ){
+ return DCR_Missing;
}
-end_schema_xfer:
- sqlite3_finalize(pQuery);
- sqlite3_free(zQuery);
-}
-
-/*
-** Open a new database file named "zNewDb". Try to recover as much information
-** as possible out of the main database (which might be corrupt) and write it
-** into zNewDb.
-*/
-static void tryToClone(ShellState *p, const char *zNewDb){
- int rc;
- sqlite3 *newDb = 0;
- if( access(zNewDb,0)==0 ){
- utf8_printf(stderr, "File \"%s\" already exists.\n", zNewDb);
- return;
+ if( zDb==0 ) zDb = "main";
+ rc = sqlite3_open_v2(zDestFile, &pDest,
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
+ if( rc!=SQLITE_OK ){
+ *pzErr = smprintf("cannot open \"%s\"\n", zDestFile);
+ close_db(pDest);
+ return DCR_Error;
}
- rc = sqlite3_open(zNewDb, &newDb);
- if( rc ){
- utf8_printf(stderr, "Cannot create output database: %s\n",
- sqlite3_errmsg(newDb));
+ if( bAsync ){
+ sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
+ 0, 0, 0);
+ }
+ open_db(psx, 0);
+ pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
+ if( pBackup==0 ){
+ *pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest));
+ close_db(pDest);
+ return DCR_Error;
+ }
+ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
+ sqlite3_backup_finish(pBackup);
+ if( rc==SQLITE_DONE ){
+ rc = 0;
}else{
- sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0);
- sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
- tryToCloneSchema(p, newDb, "type='table'", tryToCloneData);
- tryToCloneSchema(p, newDb, "type!='table'", 0);
- sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);
- sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(pDest));
+ rc = 1;
}
- close_db(newDb);
+ close_db(pDest);
+ return DCR_Ok|rc;
}
+#endif /* !defined(SQLITE_SHELL_FIDDLE) */
/*
-** Change the output file back to stdout.
-**
-** If the p->doXdgOpen flag is set, that means the output was being
-** redirected to a temporary file named by p->zTempFile. In that case,
-** launch start/open/xdg-open on that temporary file.
-*/
-static void output_reset(ShellState *p){
- if( p->outfile[0]=='|' ){
-#ifndef SQLITE_OMIT_POPEN
- pclose(p->out);
+ * 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_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
- }else{
- output_file_close(p->out);
-#ifndef SQLITE_NOHAVE_SYSTEM
- if( p->doXdgOpen ){
- const char *zXdgOpenCmd =
-#if defined(_WIN32)
- "start";
-#elif defined(__APPLE__)
- "open";
+
+/* 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
- "xdg-open";
+# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
#endif
- char *zCmd;
- zCmd = sqlite3_mprintf("%s %s", zXdgOpenCmd, p->zTempFile);
- if( system(zCmd) ){
- utf8_printf(stderr, "Failed: [%s]\n", zCmd);
- }else{
- /* Give the start/open/xdg-open command some time to get
- ** going before we continue, and potential delete the
- ** p->zTempFile data file out from under it */
- sqlite3_sleep(2000);
+
+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 \
+";
+#else
+ /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
+ static const char * const zColDigits = "\
+SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
+ WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
+ ELSE 5 FROM (SELECT count(*) AS nc 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
+ s3_exec_noom(*pDb, zTabMake, 0, 0, 0);
+ }
+ assert(*pDb!=0);
+ s3_prepare_v2_noom(*pDb, zTabFill, -1, &pStmt, 0);
+ stmt_holder(pStmt);
+ shell_check_nomem(sqlite3_bind_text(pStmt, 1, zColNew, -1, 0));
+ s3_step_noom(pStmt);
+ release_holder();
+ return 0;
+ }else if( *pDb==0 ){
+ return 0;
+ }else{
+ /* Formulate the columns spec, close the DB, zero *pDb. */
+ char *zColsSpec = 0;
+ int hasDupes = db_int(*pDb, zHasDupes);
+ int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
+ if( hasDupes ){
+#ifdef SHELL_COLUMN_RENAME_CLEAN
+ s3_exec_noom(*pDb, zDedoctor, 0, 0, 0);
+#endif
+ s3_exec_noom(*pDb, zSetReps, 0, 0, 0);
+ s3_prepare_v2_noom(*pDb, zRenameRank, -1, &pStmt, 0);
+ stmt_holder(pStmt);
+ sqlite3_bind_int(pStmt, 1, nDigits);
+ rc = s3_step_noom(pStmt);
+ release_holder();
+ if( rc!=SQLITE_DONE ) shell_check_nomem(SQLITE_NOMEM);
+ }
+ /* This assert is maybe overly cautious for above de-dup DML, but that can
+ * be replaced via #define's. So this check is made for debug builds. */
+ assert(db_int(*pDb, zHasDupes)==0);
+ rc = s3_prepare_v2_noom(*pDb, zCollectVar, -1, &pStmt, 0);
+ rc = s3_step_noom(pStmt);
+ if( rc==SQLITE_ROW ){
+ zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0));
+ }else{
+ zColsSpec = 0;
+ }
+ if( pzRenamed!=0 ){
+ if( !hasDupes ) *pzRenamed = 0;
+ else{
+ sqlite3_finalize(pStmt);
+ if( SQLITE_OK==s3_prepare_v2_noom(*pDb, zRenamesDone, -1, &pStmt, 0)
+ && SQLITE_ROW==sqlite3_step(pStmt) ){
+ *pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0));
+ }else
+ *pzRenamed = 0;
}
- sqlite3_free(zCmd);
- outputModePop(p);
- p->doXdgOpen = 0;
}
-#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
+ sqlite3_finalize(pStmt);
+ sqlite3_close(*pDb);
+ *pDb = 0;
+ return zColsSpec;
}
- p->outfile[0] = 0;
- p->out = stdout;
}
+#if SHELL_DATAIO_EXT
+
/*
-** Run an SQL command and return the single integer result.
+** Standard ExportHandlers
+** These implement the built-in renderers of query results.
- ** Two are provided, one for freeform results, the other for columnar results.
++** Two are provided, one for free-form results, the other for columnar results.
*/
-static int db_int(sqlite3 *db, const char *zSql){
- sqlite3_stmt *pStmt;
- int res = 0;
- sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
- if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
- res = sqlite3_column_int(pStmt,0);
+
+static void EH_FF_destruct(ExportHandler *pMe);
+static void EH_CM_destruct(ExportHandler *pMe);
+static const char *EH_FF_name(ExportHandler *pMe);
+static const char *EH_CM_name(ExportHandler *pMe);
+static const char *EH_help(ExportHandler *pMe, const char *zWhat);
+static int EH_FF_config(ExportHandler *pMe, int io, char **pzTell);
+static int EH_CM_config(ExportHandler *pMe, int io, char **pzTell);
+static int EH_openResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ int numArgs, char *azArgs[],
+ const char * zName);
+static int EH_FF_prependResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_CM_prependResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_FF_rowResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_CM_rowResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_FF_appendResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static int EH_CM_appendResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt);
+static void EH_closeResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES,
+ char **pzErr);
+
+static VTABLE_NAME(ExportHandler) exporter_Vtab_FF = {
+ EH_FF_destruct,
+ EH_FF_name,
+ EH_help,
+ EH_FF_config,
+ EH_openResultsOutStream,
+ EH_FF_prependResultsOut,
+ EH_FF_rowResultsOut,
+ EH_FF_appendResultsOut,
+ EH_closeResultsOutStream
+};
+
+static VTABLE_NAME(ExportHandler) exporter_Vtab_CM = {
+ EH_CM_destruct,
+ EH_CM_name,
+ EH_help,
+ EH_CM_config,
+ EH_openResultsOutStream,
+ EH_CM_prependResultsOut,
+ EH_CM_rowResultsOut,
+ EH_CM_appendResultsOut,
+ EH_closeResultsOutStream
+};
+
+typedef struct {
+ char **azCols; /* Names of result columns */
+ char **azVals; /* Results */
+ int *aiTypes; /* Result types */
+} ColumnsInfo;
+
+typedef struct {
+ VTABLE_NAME(ExportHandler) *pMethods;
+ ShellInState *psi;
+ int nCol;
+ sqlite3_uint64 nRow;
+ void *pData;
+ void *pRowInfo;
+ ColumnsInfo colInfo;
+} BuiltInFFExporter;
+#define BI_FF_EXPORTER_INIT(psi) { & exporter_Vtab_FF, psi, 0, 0, 0, 0 }
+
+typedef struct {
+ VTABLE_NAME(ExportHandler) *pMethods;
+ ShellInState *psi;
+ int nCol;
+ sqlite3_uint64 nRow;
+ void *pData;
+ void *pRowInfo;
+ const char *colSep;
+ const char *rowSep;
+} BuiltInCMExporter;
+#define BI_CM_EXPORTER_INIT(psi) { & exporter_Vtab_CM, psi, 0, 0, 0, 0 }
+
+static void EH_FF_destruct(ExportHandler *pMe){
+ /* This serves two purposes: idempotent reinitialize, and final takedown */
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ if( pbie->nCol!=0 ){
+ sqlite3_free(pbie->pData);
+ pbie->pData = 0;
+ }
+ pbie->nRow = 0;
+ pbie->nCol = 0;
+}
+
+static const char *zEmpty = "";
+
+static void EH_CM_destruct(ExportHandler *pMe){
+ /* This serves two purposes: idempotent reinitialize, and final takedown */
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ if( pbie->nCol!=0 ){
+ sqlite3_uint64 nData = pbie->nCol * (pbie->nRow + 1), i;
+ const char *zNull = pbie->psi->nullValue;
+ char **azData = (char**)pbie->pData;
+ for(i=0; i<nData; i++){
+ char *z = azData[i];
+ if( z!=zEmpty && z!=zNull ) sqlite3_free(z);
+ }
+ sqlite3_free(pbie->pData);
+ sqlite3_free(pbie->pRowInfo);
+ pbie->pData = 0;
+ pbie->pRowInfo = 0;
+ }
+ pbie->nCol = 0;
+ pbie->nRow = 0;
+ pbie->colSep = 0;
+ pbie->rowSep = 0;
+}
+
+static int EH_FF_config(ExportHandler *pMe, int io, char **pzTell){
+ BuiltInCMExporter *pThis = (BuiltInCMExporter*)pMe;
+ UNUSED_PARAMETER(io);
+ UNUSED_PARAMETER(pzTell);
+ return SQLITE_OK;
+}
+static int EH_CM_config(ExportHandler *pMe, int io, char **pzTell){
+ BuiltInFFExporter *pThis = (BuiltInFFExporter*)pMe;
+ ShellInState *psi = pThis->psi;
+ if( io==0 && pzTell!=0 ){
+ *pzTell = smprintf("--wrap %d --wordwrap %s --%squote", psi->cmOpts.iWrap,
+ psi->cmOpts.bWordWrap ? "on" : "off",
+ psi->cmOpts.bQuote ? "" : "no");
}
- sqlite3_finalize(pStmt);
- return res;
+ return SQLITE_OK;
}
-#if SQLITE_SHELL_HAVE_RECOVER
-/*
-** Convert a 2-byte or 4-byte big-endian integer into a native integer
-*/
-static unsigned int get2byteInt(unsigned char *a){
- return (a[0]<<8) + a[1];
+static const char *zModeName(ShellInState *psi){
+ int mi = psi->mode;
+ return (mi>=0 && mi<MODE_COUNT_OF)? modeDescr[mi].zModeName : 0;
}
-static unsigned int get4byteInt(unsigned char *a){
- return (a[0]<<24) + (a[1]<<16) + (a[2]<<8) + a[3];
+static const char *EH_FF_name(ExportHandler *pMe){
+ return zModeName(((BuiltInFFExporter*)pMe)->psi);
+}
+static const char *EH_CM_name(ExportHandler *pMe){
+ return zModeName(((BuiltInCMExporter*)pMe)->psi);
}
-/*
-** Implementation of the ".dbinfo" command.
-**
-** Return 1 on error, 2 to exit, and 0 otherwise.
-*/
-static int shell_dbinfo_command(ShellState *p, int nArg, char **azArg){
- static const struct { const char *zName; int ofst; } aField[] = {
- { "file change counter:", 24 },
- { "database page count:", 28 },
- { "freelist page count:", 36 },
- { "schema cookie:", 40 },
- { "schema format:", 44 },
- { "default cache size:", 48 },
- { "autovacuum top root:", 52 },
- { "incremental vacuum:", 64 },
- { "text encoding:", 56 },
- { "user version:", 60 },
- { "application id:", 68 },
- { "software version:", 96 },
- };
- static const struct { const char *zName; const char *zSql; } aQuery[] = {
- { "number of tables:",
- "SELECT count(*) FROM %s WHERE type='table'" },
- { "number of indexes:",
- "SELECT count(*) FROM %s WHERE type='index'" },
- { "number of triggers:",
- "SELECT count(*) FROM %s WHERE type='trigger'" },
- { "number of views:",
- "SELECT count(*) FROM %s WHERE type='view'" },
- { "schema size:",
- "SELECT total(length(sql)) FROM %s" },
- };
- int i, rc;
- unsigned iDataVersion;
- char *zSchemaTab;
- char *zDb = nArg>=2 ? azArg[1] : "main";
- sqlite3_stmt *pStmt = 0;
- unsigned char aHdr[100];
- open_db(p, 0);
- if( p->db==0 ) return 1;
- rc = sqlite3_prepare_v2(p->db,
- "SELECT data FROM sqlite_dbpage(?1) WHERE pgno=1",
- -1, &pStmt, 0);
- if( rc ){
- utf8_printf(stderr, "error: %s\n", sqlite3_errmsg(p->db));
- sqlite3_finalize(pStmt);
- return 1;
+static const char *EH_help(ExportHandler *pMe, const char *zWhat){
+ (void)(pMe);
+ (void)(zWhat);
+ return 0;
+}
+
+static int EH_openResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ int numArgs, char *azArgs[],
+ const char * zName){
+ /* The built-in exporters have a predetermined destination, and their
+ * action is set by the shell state .mode member, so this method has
+ * nothing to do. For similar reasons, the shell never calls it. That
+ * could change if .mode command functionality is moved to here.
+ */
+ (void)(pMe);
+ (void)(pSES);
+ (void)(pzErr);
+ (void)(numArgs);
+ (void)(azArgs);
+ (void)(zName);
+ return 0;
+}
+
+static int EH_CM_prependResultsOut(ExportHandler *pMe,
+ ShellExState *psx, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ ShellInState *psi = ISS(psx);
+ sqlite3_int64 nRow = 0;
+ char **azData = 0;
+ sqlite3_int64 nAlloc = 0;
+ char *abRowDiv = 0;
+ const unsigned char *uz;
+ const char *z;
+ char **azQuoted = 0;
+ int rc;
+ sqlite3_int64 i, nData;
+ int j, w, n;
+ const unsigned char **azNextLine = 0;
+ int bNextLine = 0;
+ int bMultiLineRowExists = 0;
+ int bw = psi->cmOpts.bWordWrap;
+ int nColumn = sqlite3_column_count(pStmt);
+
+ if( nColumn==0 ){
+ rc = sqlite3_step(pStmt);
+ assert(rc!=SQLITE_ROW);
+ return rc;
}
- sqlite3_bind_text(pStmt, 1, zDb, -1, SQLITE_STATIC);
- if( sqlite3_step(pStmt)==SQLITE_ROW
- && sqlite3_column_bytes(pStmt,0)>100
- ){
- const u8 *pb = sqlite3_column_blob(pStmt,0);
- shell_check_oom(pb);
- memcpy(aHdr, pb, 100);
- sqlite3_finalize(pStmt);
- }else{
- raw_printf(stderr, "unable to read database header\n");
- sqlite3_finalize(pStmt);
- return 1;
+ EH_CM_destruct(pMe);
+
+ nAlloc = nColumn*4;
+ if( nAlloc<=0 ) nAlloc = 1;
+ azData = sqlite3_malloc64( nAlloc*sizeof(char*) );
+ shell_check_ooms(azData);
+ azNextLine = sqlite3_malloc64( nColumn*sizeof(char*) );
+ shell_check_ooms((void*)azNextLine);
+ memset((void*)azNextLine, 0, nColumn*sizeof(char*) );
+ if( psi->cmOpts.bQuote ){
+ azQuoted = sqlite3_malloc64( nColumn*sizeof(char*) );
+ shell_check_ooms(azQuoted);
+ memset(azQuoted, 0, nColumn*sizeof(char*) );
}
- i = get2byteInt(aHdr+16);
- if( i==1 ) i = 65536;
- utf8_printf(p->out, "%-20s %d\n", "database page size:", i);
- utf8_printf(p->out, "%-20s %d\n", "write format:", aHdr[18]);
- utf8_printf(p->out, "%-20s %d\n", "read format:", aHdr[19]);
- utf8_printf(p->out, "%-20s %d\n", "reserved bytes:", aHdr[20]);
- for(i=0; i<ArraySize(aField); i++){
- int ofst = aField[i].ofst;
- unsigned int val = get4byteInt(aHdr + ofst);
- utf8_printf(p->out, "%-20s %u", aField[i].zName, val);
- switch( ofst ){
- case 56: {
- if( val==1 ) raw_printf(p->out, " (utf8)");
- if( val==2 ) raw_printf(p->out, " (utf16le)");
- if( val==3 ) raw_printf(p->out, " (utf16be)");
+ abRowDiv = sqlite3_malloc64( nAlloc/nColumn );
+ shell_check_ooms(abRowDiv);
+ if( nColumn>psx->numWidths ){
+ psx->pSpecWidths = realloc(psx->pSpecWidths, (nColumn+1)*2*sizeof(int));
+ shell_check_oomm(psx->pSpecWidths);
+ for(i=psx->numWidths; i<nColumn; i++) psx->pSpecWidths[i] = 0;
+ psx->numWidths = nColumn;
+ psx->pHaveWidths = &psx->pSpecWidths[nColumn];
+ }
+ memset(psx->pHaveWidths, 0, nColumn*sizeof(int));
+ for(i=0; i<nColumn; i++){
+ w = psx->pSpecWidths[i];
+ if( w<0 ) w = -w;
+ psx->pHaveWidths[i] = w;
+ }
+ for(i=0; i<nColumn; i++){
+ const unsigned char *zNotUsed;
+ int wx = psx->pSpecWidths[i];
+ if( wx==0 ){
+ wx = psi->cmOpts.iWrap;
+ }
+ if( wx<0 ) wx = -wx;
+ uz = (const unsigned char*)sqlite3_column_name(pStmt,i);
+ if( uz==0 ) uz = (u8*)"";
+ azData[i] = translateForDisplayAndDup(uz, &zNotUsed, wx, bw);
+ }
+ while( bNextLine || SQLITE_ROW==sqlite3_step(pStmt) ){
+ int useNextLine = bNextLine;
+ bNextLine = 0;
+ if( (nRow+2)*nColumn >= nAlloc ){
+ nAlloc *= 2;
+ azData = sqlite3_realloc64(azData, nAlloc*sizeof(char*));
+ shell_check_ooms(azData);
+ abRowDiv = sqlite3_realloc64(abRowDiv, nAlloc/nColumn);
+ shell_check_ooms(abRowDiv);
+ }
+ abRowDiv[nRow] = 1;
+ nRow++;
+ for(i=0; i<nColumn; i++){
+ int wx = psx->pSpecWidths[i];
+ if( wx==0 ){
+ wx = psi->cmOpts.iWrap;
+ }
+ if( wx<0 ) wx = -wx;
+ if( useNextLine ){
+ uz = azNextLine[i];
+ if( uz==0 ) uz = (u8*)zEmpty;
+ }else if( psi->cmOpts.bQuote ){
+ sqlite3_free(azQuoted[i]);
+ azQuoted[i] = quoted_column(pStmt,i);
+ uz = (const unsigned char*)azQuoted[i];
+ }else{
+ uz = (const unsigned char*)sqlite3_column_text(pStmt,i);
+ if( uz==0 ) uz = (u8*)psi->nullValue;
+ }
+ azData[nRow*nColumn + i]
+ = translateForDisplayAndDup(uz, &azNextLine[i], wx, bw);
+ if( azNextLine[i] ){
+ bNextLine = 1;
+ abRowDiv[nRow-1] = 0;
+ bMultiLineRowExists = 1;
}
}
- raw_printf(p->out, "\n");
}
- if( zDb==0 ){
- zSchemaTab = sqlite3_mprintf("main.sqlite_schema");
- }else if( cli_strcmp(zDb,"temp")==0 ){
- zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema");
- }else{
- zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb);
+ sqlite3_free((void*)azNextLine);
+ if( azQuoted ){
+ for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
+ sqlite3_free(azQuoted);
}
- for(i=0; i<ArraySize(aQuery); i++){
- char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
- int val = db_int(p->db, zSql);
- sqlite3_free(zSql);
- utf8_printf(p->out, "%-20s %d\n", aQuery[i].zName, val);
+ if( nRow==0 ){
+ EH_CM_destruct(pMe);
+ return SQLITE_DONE;
}
- sqlite3_free(zSchemaTab);
- sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
- utf8_printf(p->out, "%-20s %u\n", "data version", iDataVersion);
- return 0;
-}
-#endif /* SQLITE_SHELL_HAVE_RECOVER */
-
-/*
-** Print the current sqlite3_errmsg() value to stderr and return 1.
-*/
-static int shellDatabaseError(sqlite3 *db){
- const char *zErr = sqlite3_errmsg(db);
- utf8_printf(stderr, "Error: %s\n", zErr);
- return 1;
-}
-/*
-** Compare the pattern in zGlob[] against the text in z[]. Return TRUE
-** if they match and FALSE (0) if they do not match.
-**
-** Globbing rules:
-**
-** '*' Matches any sequence of zero or more characters.
-**
-** '?' Matches exactly one character.
-**
-** [...] Matches one character from the enclosed list of
-** characters.
-**
-** [^...] Matches one character not in the enclosed list.
-**
-** '#' Matches any sequence of one or more digits with an
-** optional + or - sign in front
-**
-** ' ' Any span of whitespace matches any other span of
-** whitespace.
-**
-** Extra whitespace at the end of z[] is ignored.
-*/
-static int testcase_glob(const char *zGlob, const char *z){
- int c, c2;
- int invert;
- int seen;
+ nData = nColumn*(nRow+1);
- while( (c = (*(zGlob++)))!=0 ){
- if( IsSpace(c) ){
- if( !IsSpace(*z) ) return 0;
- while( IsSpace(*zGlob) ) zGlob++;
- while( IsSpace(*z) ) z++;
- }else if( c=='*' ){
- while( (c=(*(zGlob++))) == '*' || c=='?' ){
- if( c=='?' && (*(z++))==0 ) return 0;
- }
- if( c==0 ){
- return 1;
- }else if( c=='[' ){
- while( *z && testcase_glob(zGlob-1,z)==0 ){
- z++;
- }
- return (*z)!=0;
- }
- while( (c2 = (*(z++)))!=0 ){
- while( c2!=c ){
- c2 = *(z++);
- if( c2==0 ) return 0;
+ for(i=0; i<nData; i++){
+ z = azData[i];
+ if( z==0 ) z = (char*)zEmpty;
+ n = strlenChar(z);
+ j = i%nColumn;
+ if( n>psx->pHaveWidths[j] ) psx->pHaveWidths[j] = n;
+ }
+ if( seenInterrupt ) goto done;
+ switch( psi->cMode ){
+ case MODE_Column: {
+ pbie->colSep = " ";
+ pbie->rowSep = "\n";
+ if( psi->showHeader ){
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ if( psx->pSpecWidths[i]<0 ) w = -w;
+ utf8_width_print(psi->out, w, azData[i]);
+ fputs(i==nColumn-1?"\n":" ", psi->out);
+ }
+ for(i=0; i<nColumn; i++){
+ print_dashes(psi->out, psx->pHaveWidths[i]);
+ fputs(i==nColumn-1?"\n":" ", psi->out);
}
- if( testcase_glob(zGlob,z) ) return 1;
}
- return 0;
- }else if( c=='?' ){
- if( (*(z++))==0 ) return 0;
- }else if( c=='[' ){
- int prior_c = 0;
- seen = 0;
- invert = 0;
- c = *(z++);
- if( c==0 ) return 0;
- c2 = *(zGlob++);
- if( c2=='^' ){
- invert = 1;
- c2 = *(zGlob++);
+ break;
+ }
+ case MODE_Table: {
+ pbie->colSep = " | ";
+ pbie->rowSep = " |\n";
+ print_row_separator(psx, nColumn, "+");
+ fputs("| ", psi->out);
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ n = strlenChar(azData[i]);
+ utf8_printf(psi->out, "%*s%s%*s",
+ (w-n)/2, "", azData[i], (w-n+1)/2, "");
+ fputs(i==nColumn-1?" |\n":" | ", psi->out);
}
- if( c2==']' ){
- if( c==']' ) seen = 1;
- c2 = *(zGlob++);
+ print_row_separator(psx, nColumn, "+");
+ break;
+ }
+ case MODE_Markdown: {
+ pbie->colSep = " | ";
+ pbie->rowSep = " |\n";
+ fputs("| ", psi->out);
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ n = strlenChar(azData[i]);
+ utf8_printf(psi->out, "%*s%s%*s",
+ (w-n)/2, "", azData[i], (w-n+1)/2, "");
+ fputs(i==nColumn-1?" |\n":" | ", psi->out);
}
- while( c2 && c2!=']' ){
- if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
- c2 = *(zGlob++);
- if( c>=prior_c && c<=c2 ) seen = 1;
- prior_c = 0;
- }else{
- if( c==c2 ){
- seen = 1;
- }
- prior_c = c2;
- }
- c2 = *(zGlob++);
+ print_row_separator(psx, nColumn, "|");
+ break;
+ }
+ case MODE_Box: {
+ pbie->colSep = " " BOX_13 " ";
+ pbie->rowSep = " " BOX_13 "\n";
+ print_box_row_separator(psx, nColumn, BOX_23, BOX_234, BOX_34);
+ utf8_printf(psi->out, BOX_13 " ");
+ for(i=0; i<nColumn; i++){
+ w = psx->pHaveWidths[i];
+ n = strlenChar(azData[i]);
+ utf8_printf(psi->out, "%*s%s%*s%s",
+ (w-n)/2, "", azData[i], (w-n+1)/2, "",
+ i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
}
- if( c2==0 || (seen ^ invert)==0 ) return 0;
- }else if( c=='#' ){
- if( (z[0]=='-' || z[0]=='+') && IsDigit(z[1]) ) z++;
- if( !IsDigit(z[0]) ) return 0;
- z++;
- while( IsDigit(z[0]) ){ z++; }
- }else{
- if( c!=(*(z++)) ) return 0;
+ print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
+ break;
}
}
- while( IsSpace(*z) ){ z++; }
- return *z==0;
-}
-
-
-/*
-** Compare the string as a command-line option with either one or two
-** initial "-" characters.
-*/
-static int optionMatch(const char *zStr, const char *zOpt){
- if( zStr[0]!='-' ) return 0;
- zStr++;
- if( zStr[0]=='-' ) zStr++;
- return cli_strcmp(zStr, zOpt)==0;
-}
-
-/*
-** Delete a file.
-*/
-int shellDeleteFile(const char *zFilename){
- int rc;
-#ifdef _WIN32
- wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename);
- rc = _wunlink(z);
- sqlite3_free(z);
-#else
- rc = unlink(zFilename);
-#endif
- return rc;
+ done:
+ pbie->nCol = nColumn;
+ pbie->pData = azData;
+ pbie->nRow = nRow;
+ if( bMultiLineRowExists ){
+ pbie->pRowInfo = abRowDiv;
+ }else{
+ pbie->pRowInfo = 0;
+ sqlite3_free(abRowDiv);
+ }
+ if( seenInterrupt ){
+ EH_CM_destruct(pMe);
+ return SQLITE_INTERRUPT;
+ }
+ return SQLITE_OK;
}
-/*
-** Try to delete the temporary file (if there is one) and free the
-** memory used to hold the name of the temp file.
-*/
-static void clearTempFile(ShellState *p){
- if( p->zTempFile==0 ) return;
- if( p->doXdgOpen ) return;
- if( shellDeleteFile(p->zTempFile) ) return;
- sqlite3_free(p->zTempFile);
- p->zTempFile = 0;
-}
+static int EH_CM_rowResultsOut(ExportHandler *pMe,
+ ShellExState *psx, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ ShellInState *psi = pbie->psi;
+ sqlite3_int64 nRow = pbie->nRow;
+ int nColumn = pbie->nCol, j, w;
+ char **azData = (char**)(pbie->pData);
+ sqlite3_int64 nData = (nRow+1)*nColumn, i;
+ char *abRowDiv = pbie->pRowInfo;
+ const char *z;
-/*
-** Create a new temp file name with the given suffix.
-*/
-static void newTempFile(ShellState *p, const char *zSuffix){
- clearTempFile(p);
- sqlite3_free(p->zTempFile);
- p->zTempFile = 0;
- if( p->db ){
- sqlite3_file_control(p->db, 0, SQLITE_FCNTL_TEMPFILENAME, &p->zTempFile);
- }
- if( p->zTempFile==0 ){
- /* If p->db is an in-memory database then the TEMPFILENAME file-control
- ** will not work and we will need to fallback to guessing */
- char *zTemp;
- sqlite3_uint64 r;
- sqlite3_randomness(sizeof(r), &r);
- zTemp = getenv("TEMP");
- if( zTemp==0 ) zTemp = getenv("TMP");
- if( zTemp==0 ){
-#ifdef _WIN32
- zTemp = "\\tmp";
-#else
- zTemp = "/tmp";
-#endif
+ (void)(pzErr);
+ (void)(pStmt);
+ if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
+ for(i=nColumn, j=0; i<nData; i++, j++){
+ if( j==0 && psi->cMode!=MODE_Column ){
+ utf8_printf(psi->out, "%s", psi->cMode==MODE_Box?BOX_13" ":"| ");
+ }
+ z = azData[i];
+ if( z==0 ) z = zEmpty;
+ w = psx->pHaveWidths[j];
+ if( psx->pSpecWidths[j]<0 ) w = -w;
+ utf8_width_print(psi->out, w, z);
+ if( j==nColumn-1 ){
+ utf8_printf(psi->out, "%s", pbie->rowSep);
+ if( abRowDiv!=0 && abRowDiv[i/nColumn-1] && i+1<nData ){
+ if( psi->cMode==MODE_Table ){
+ print_row_separator(psx, nColumn, "+");
+ }else if( psi->cMode==MODE_Box ){
+ print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
+ }else if( psi->cMode==MODE_Column ){
+ raw_printf(psi->out, "\n");
+ }
+ }
+ j = -1;
+ if( seenInterrupt ){
+ EH_CM_destruct(pMe);
+ return SQLITE_INTERRUPT;
+ }
+ }else{
+ utf8_printf(psi->out, "%s", pbie->colSep);
}
- p->zTempFile = sqlite3_mprintf("%s/temp%llx.%s", zTemp, r, zSuffix);
- }else{
- p->zTempFile = sqlite3_mprintf("%z.%s", p->zTempFile, zSuffix);
}
- shell_check_oom(p->zTempFile);
+ return SQLITE_DONE;
}
+static int EH_CM_appendResultsOut(ExportHandler *pMe,
+ ShellExState *psx, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInCMExporter *pbie = (BuiltInCMExporter*)pMe;
+ ShellInState *psi = ISS(psx);
+ sqlite3_int64 nRow = pbie->nRow;
+ int nColumn = pbie->nCol;
+ char **azData = (char**)(pbie->pData);
+ sqlite3_int64 nData = (nRow+1)*nColumn;
-/*
-** The implementation of SQL scalar function fkey_collate_clause(), used
-** by the ".lint fkey-indexes" command. This scalar function is always
-** called with four arguments - the parent table name, the parent column name,
-** the child table name and the child column name.
-**
-** fkey_collate_clause('parent-tab', 'parent-col', 'child-tab', 'child-col')
-**
-** If either of the named tables or columns do not exist, this function
-** returns an empty string. An empty string is also returned if both tables
-** and columns exist but have the same default collation sequence. Or,
-** if both exist but the default collation sequences are different, this
-** function returns the string " COLLATE <parent-collation>", where
-** <parent-collation> is the default collation sequence of the parent column.
-*/
-static void shellFkeyCollateClause(
- sqlite3_context *pCtx,
- int nVal,
- sqlite3_value **apVal
-){
- sqlite3 *db = sqlite3_context_db_handle(pCtx);
- const char *zParent;
- const char *zParentCol;
- const char *zParentSeq;
- const char *zChild;
- const char *zChildCol;
- const char *zChildSeq = 0; /* Initialize to avoid false-positive warning */
- int rc;
-
- assert( nVal==4 );
- zParent = (const char*)sqlite3_value_text(apVal[0]);
- zParentCol = (const char*)sqlite3_value_text(apVal[1]);
- zChild = (const char*)sqlite3_value_text(apVal[2]);
- zChildCol = (const char*)sqlite3_value_text(apVal[3]);
-
- sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC);
- rc = sqlite3_table_column_metadata(
- db, "main", zParent, zParentCol, 0, &zParentSeq, 0, 0, 0
- );
- if( rc==SQLITE_OK ){
- rc = sqlite3_table_column_metadata(
- db, "main", zChild, zChildCol, 0, &zChildSeq, 0, 0, 0
- );
- }
+ if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
- if( rc==SQLITE_OK && sqlite3_stricmp(zParentSeq, zChildSeq) ){
- char *z = sqlite3_mprintf(" COLLATE %s", zParentSeq);
- sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
- sqlite3_free(z);
+ if( psi->cMode==MODE_Table ){
+ print_row_separator(psx, nColumn, "+");
+ }else if( psi->cMode==MODE_Box ){
+ print_box_row_separator(psx, nColumn, BOX_12, BOX_124, BOX_14);
}
+ EH_CM_destruct(pMe);
+ return SQLITE_OK;
}
+static int EH_FF_prependResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ int nc = sqlite3_column_count(pStmt);
+ int rc;
-/*
-** The implementation of dot-command ".lint fkey-indexes".
-*/
-static int lintFkeyIndexes(
- ShellState *pState, /* Current shell tool state */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
-){
- sqlite3 *db = pState->db; /* Database handle to query "main" db of */
- FILE *out = pState->out; /* Stream to write non-error output to */
- int bVerbose = 0; /* If -verbose is present */
- int bGroupByParent = 0; /* If -groupbyparent is present */
- int i; /* To iterate through azArg[] */
- const char *zIndent = ""; /* How much to indent CREATE INDEX by */
- int rc; /* Return code */
- sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */
-
- /*
- ** This SELECT statement returns one row for each foreign key constraint
- ** in the schema of the main database. The column values are:
- **
- ** 0. The text of an SQL statement similar to:
- **
- ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
- **
- ** This SELECT is similar to the one that the foreign keys implementation
- ** needs to run internally on child tables. If there is an index that can
- ** be used to optimize this query, then it can also be used by the FK
- ** implementation to optimize DELETE or UPDATE statements on the parent
- ** table.
- **
- ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
- ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema
- ** contains an index that can be used to optimize the query.
- **
- ** 2. Human readable text that describes the child table and columns. e.g.
- **
- ** "child_table(child_key1, child_key2)"
- **
- ** 3. Human readable text that describes the parent table and columns. e.g.
- **
- ** "parent_table(parent_key1, parent_key2)"
- **
- ** 4. A full CREATE INDEX statement for an index that could be used to
- ** optimize DELETE or UPDATE statements on the parent table. e.g.
- **
- ** "CREATE INDEX child_table_child_key ON child_table(child_key)"
- **
- ** 5. The name of the parent table.
- **
- ** These six values are used by the C logic below to generate the report.
- */
- const char *zSql =
- "SELECT "
- " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
- " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
- " || fkey_collate_clause("
- " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
- ", "
- " 'SEARCH ' || s.name || ' USING COVERING INDEX*('"
- " || group_concat('*=?', ' AND ') || ')'"
- ", "
- " s.name || '(' || group_concat(f.[from], ', ') || ')'"
- ", "
- " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
- ", "
- " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
- " || ' ON ' || quote(s.name) || '('"
- " || group_concat(quote(f.[from]) ||"
- " fkey_collate_clause("
- " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
- " || ');'"
- ", "
- " f.[table] "
- "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
- "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
- "GROUP BY s.name, f.id "
- "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
- ;
- const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
+ pbie->pMethods->destruct(pMe);
+ if( nc>0 ){
+ /* allocate space for col name ptr, value ptr, and type */
+ pbie->pData = sqlite3_malloc64(3*nc*sizeof(const char*) + 1);
+ if( !pbie->pData ){
+ shell_out_of_memory();
+ }else{
+ ColumnsInfo ci
+ = { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] };
+ int i;
+ assert(sizeof(int) <= sizeof(char *));
+ pbie->nCol = nc;
+ pbie->colInfo = ci;
+ /* save off ptrs to column names */
+ for(i=0; i<nc; i++){
+ pbie->colInfo.azCols[i] = (char *)sqlite3_column_name(pStmt, i);
+ }
+ }
+ return SQLITE_OK;
+ }
+ rc = sqlite3_step(pStmt);
+ assert(rc!=SQLITE_ROW);
+ return rc;
+}
- for(i=2; i<nArg; i++){
- int n = strlen30(azArg[i]);
- if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
- bVerbose = 1;
+static int EH_FF_rowResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ ShellInState *psi = ISS(pSES);
+ int rc = sqlite3_step(pStmt);
+ int i, x, nc = pbie->nCol;
+ if( rc==SQLITE_ROW ){
+ ColumnsInfo *pc = &pbie->colInfo;
+ sqlite3_uint64 nr = ++(pbie->nRow);
+ for( i=0; i<nc; ++i ){
+ pc->aiTypes[i] = x = sqlite3_column_type(pStmt, i);
+ if( x==SQLITE_BLOB
+ && (psi->cMode==MODE_Insert || psi->cMode==MODE_Quote) ){
+ pc->azVals[i] = "";
+ }else{
+ pc->azVals[i] = (char*)sqlite3_column_text(pStmt, i);
+ }
+ if( !pc->azVals[i] && (x!=SQLITE_NULL) ){
+ rc = SQLITE_NOMEM;
+ break; /* from for */
+ }
}
- else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
- bGroupByParent = 1;
- zIndent = " ";
+ /* if data and types extracted successfully... */
+ if( SQLITE_ROW==rc ){
+ /* call the supplied callback with the result row data */
+ if( shell_callback(pSES, nc, pc->azVals, pc->azCols, pc->aiTypes) ){
+ rc = SQLITE_ABORT;
+ }
}
- else{
- raw_printf(stderr, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
- azArg[0], azArg[1]
- );
- return SQLITE_ERROR;
+ }
+ return rc;
+}
+
+static int EH_FF_appendResultsOut(ExportHandler *pMe,
+ ShellExState *pSES, char **pzErr,
+ sqlite3_stmt *pStmt){
+ BuiltInFFExporter *pbie = (BuiltInFFExporter*)pMe;
+ ShellInState *psi = ISS(pSES);
+ if( psi->cMode==MODE_Json ){
+ fputs("]\n", psi->out);
+ }else if( psi->cMode==MODE_Count ){
+ utf8_printf(psi->out, "%llu row%s\n", pbie->nRow, pbie->nRow!=1 ? "s" : "");
+ }
+ EH_FF_destruct(pMe);
+ return SQLITE_OK;
+}
+
+static void EH_closeResultsOutStream(ExportHandler *pMe,
+ ShellExState *pSES,
+ char **pzErr){
+ /* The built-in exporters have a predetermined destination which is
+ * never "closed", so this method has nothing to do. For similar
+ * reasons, it is not called by the shell.
+ */
+ (void)(pMe);
+ (void)(pSES);
+ (void)(pzErr);
+}
+#endif /* SHELL_DATAIO_EXT */
+
+#if SHELL_DYNAMIC_EXTENSION
+
+/* Ensure there is room in loaded extension info list for one being loaded.
+ * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
+ */
+static ShExtInfo *pending_ext_info(ShellInState *psi){
+ int ixpe = psi->ixExtPending;
+ assert(ixpe!=0);
+ if( ixpe >= psi->numExtLoaded ){
+ psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
+ (ixpe+1)*sizeof(ShExtInfo));
+ shell_check_ooms(psi->pShxLoaded);
+ ++psi->numExtLoaded;
+ memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
+ }
+ return &psi->pShxLoaded[ixpe];
+}
+
+/* Register a dot-command, to be called during extension load/init. */
+static int register_dot_command(ShellExState *p,
+ ExtensionId eid, DotCommand *pMC){
+ ShellInState *psi = ISS(p);
+ ShExtInfo *psei = pending_ext_info(psi);
+ const char *zSql
+ = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, ?, ?)";
+ int ie = psi->ixExtPending;
+ assert(psi->pShxLoaded!=0 && p->dbShell!=0);
+ if( pMC==0 ) return SQLITE_ERROR;
+ else{
+ const char *zName = pMC->pMethods->name(pMC);
+ sqlite3_stmt *pStmt;
+ int nc = psei->numDotCommands;
+ int rc;
+ if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
+ psei->extId = eid;
+ rc = s3_prepare_v2_noom(p->dbShell, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+ psei->ppDotCommands
+ = sqlite3_realloc(psei->ppDotCommands, (nc+1)*sizeof(DotCommand *));
+ shell_check_ooms(psei->ppDotCommands);
+ sqlite3_bind_text(pStmt, 1, zName, -1, 0);
+ sqlite3_bind_int(pStmt, 2, ie);
+ sqlite3_bind_int(pStmt, 3, nc);
+ rc = sqlite3_step(pStmt);
+ sqlite3_finalize(pStmt);
+ if( rc==SQLITE_DONE ){
+ psei->ppDotCommands[nc++] = pMC;
+ psei->numDotCommands = nc;
+ notify_subscribers(psi, NK_NewDotCommand, pMC);
+ if( cli_strcmp("unknown", zName)==0 ){
+ psi->pUnknown = pMC;
+ psei->pUnknown = pMC;
+ }
+ return SQLITE_OK;
+ }else{
+ psei->ppDotCommands[nc] = 0;
}
}
+ return SQLITE_ERROR;
+}
- /* Register the fkey_collate_clause() SQL function */
- rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
- 0, shellFkeyCollateClause, 0, 0
- );
+/* Register an output data display (or other disposition) mode */
+static int register_exporter(ShellExState *p,
+ ExtensionId eid, ExportHandler *pEH){
+ return SQLITE_ERROR;
+}
+/* Register an import variation from (various sources) for .import */
+static int register_importer(ShellExState *p,
+ ExtensionId eid, ImportHandler *pIH){
+ return SQLITE_ERROR;
+}
- if( rc==SQLITE_OK ){
- rc = sqlite3_prepare_v2(db, zSql, -1, &pSql, 0);
+/* See registerScripting API in shext_linkage.h */
+static int register_scripting(ShellExState *p, ExtensionId eid,
+ ScriptSupport *pSS){
+ ShellInState *psi = ISS(p);
+ if( psi->scriptXid!=0 || psi->script!=0 ){
+ /* Scripting support already provided. Only one provider is allowed. */
+ return SQLITE_BUSY;
}
- if( rc==SQLITE_OK ){
- sqlite3_bind_int(pSql, 1, bGroupByParent);
+ if( eid==0 || pSS==0 || psi->ixExtPending==0 ){
+ /* Scripting addition allowed only when sqlite3_*_init() runs. */
+ return SQLITE_MISUSE;
}
+ psi->script = pSS;
+ psi->scriptXid = eid;
+ return SQLITE_OK;
+}
- if( rc==SQLITE_OK ){
- int rc2;
- char *zPrev = 0;
- while( SQLITE_ROW==sqlite3_step(pSql) ){
- int res = -1;
- sqlite3_stmt *pExplain = 0;
- const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
- const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
- const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
- const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
- const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
- const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
+/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
- * Depending on zHelp==0, either register or unregister ad-hoc treatment
- * of zName for this extension (identified by eid.)
++ * Depending on zHelp==0, either register or unregister treatment of
++ * of zName for this extension (identified by eId.)
+ */
- static int register_adhoc_command(ShellExState *p, ExtensionId eid,
++static int register_adhoc_command(ShellExState *p, ExtensionId eId,
+ const char *zName, const char *zHelp){
+ ShellInState *psi = ISS(p);
+ u8 bRegNotRemove = zHelp!=0;
+ const char *zSql = bRegNotRemove
+ ? "INSERT OR REPLACE INTO "SHELL_AHELP_TAB
+ "(name, extIx, helpText) VALUES(?, ?, ?||?||?)"
+ : "DELETE FROM "SHELL_AHELP_TAB" WHERE name=? AND extIx=?";
+ sqlite3_stmt *pStmt;
+ int rc, ie;
- if( zEQP==0 ) continue;
- if( zGlob==0 ) continue;
- rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
- if( rc!=SQLITE_OK ) break;
- if( SQLITE_ROW==sqlite3_step(pExplain) ){
- const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
- res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan)
- || 0==sqlite3_strglob(zGlobIPK, zPlan));
- }
- rc = sqlite3_finalize(pExplain);
- if( rc!=SQLITE_OK ) break;
+ assert(psi->pShxLoaded!=0 && p->dbShell!=0);
+ for( ie=psi->numExtLoaded-1; ie>0; --ie ){
- if( psi->pShxLoaded[ie].extId==eid ) break;
++ if( psi->pShxLoaded[ie].extId==eId ) break;
+ }
+ if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE;
+ rc = s3_prepare_v2_noom(p->dbShell, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+ sqlite3_bind_text(pStmt, 1, zName, -1, 0);
+ sqlite3_bind_int(pStmt, 2, ie);
+ if( bRegNotRemove ){
+ int nc = strlen30(zHelp);
+ char cLead = *zHelp;
+ /* Add leading '.' if no help classifier present. */
+ const char *zCL = (cLead!='.' && cLead!=',')? "." : "";
+ /* Add trailing newline if not already there. */
+ const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : "";
+ sqlite3_bind_text(pStmt, 3, zCL, -1, 0);
+ sqlite3_bind_text(pStmt, 4, zHelp, -1, 0);
+ sqlite3_bind_text(pStmt, 5, zLE, -1, 0);
+ }
+ rc = sqlite3_step(pStmt);
+ sqlite3_finalize(pStmt);
+ return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR;
+}
- if( res<0 ){
- raw_printf(stderr, "Error: internal error");
- break;
+/*
+ * Subscribe to (or unsubscribe from) messages about various changes.
+ * Unsubscribe, effected when nkMin==NK_Unsubscribe, always succeeds.
+ * Return SQLITE_OK on success, or one of these error codes:
+ * SQLITE_ERROR when the nkMin value is unsupported by this host;
+ * SQLITE_NOMEM when a required allocation failed; or
- * SQLITE_MISUSE when the provided eid or eventHandler is invalid.
++ * SQLITE_MISUSE when the provided eId or eventHandler is invalid.
+ */
- static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
++static int subscribe_events(ShellExState *p, ExtensionId eId, void *pvUserData,
+ NoticeKind nkMin, ShellEventNotify eventHandler){
+ ShellInState *psi = ISS(p);
+ struct EventSubscription *pes = psi->pSubscriptions;
+ struct EventSubscription *pesLim = pes + psi->numSubscriptions;
+ if( nkMin==NK_Unsubscribe ){
+ /* unsubscribe (if now subscribed) */
+ while( pes < pesLim ){
+ if( (eventHandler==0 || eventHandler==pes->eventHandler)
- && (pes->eid==0 || pes->eid==eid)
- && (eid!=0 || eventHandler!=0 ||/*for shell use*/ pvUserData==p ) ){
++ && (pes->eid==0 || pes->eid==eId)
++ && (eId!=0 || eventHandler!=0 ||/*for shell use*/ pvUserData==p ) ){
+ int nLeft = pesLim - pes;
+ assert(pes->eventHandler!=0);
+ pes->eventHandler(pes->pvUserData, NK_Unsubscribe, pes->eid, p);
+ if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes));
+ --pesLim;
+ --psi->numSubscriptions;
}else{
- if( bGroupByParent
- && (bVerbose || res==0)
- && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
- ){
- raw_printf(out, "-- Parent table %s\n", zParent);
- sqlite3_free(zPrev);
- zPrev = sqlite3_mprintf("%s", zParent);
- }
-
- if( res==0 ){
- raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
- }else if( bVerbose ){
- raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
- zIndent, zFrom, zTarget
- );
- }
+ ++pes;
}
}
- sqlite3_free(zPrev);
-
- if( rc!=SQLITE_OK ){
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
- }
-
- rc2 = sqlite3_finalize(pSql);
- if( rc==SQLITE_OK && rc2!=SQLITE_OK ){
- rc = rc2;
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
+ if( psi->numSubscriptions==0 ){
+ sqlite3_free(psi->pSubscriptions);
+ psi->pSubscriptions = 0;
}
+ return SQLITE_OK;
}else{
- raw_printf(stderr, "%s\n", sqlite3_errmsg(db));
+ /* subscribe only if minimum NoticeKind supported by this host */
+ if( nkMin > NK_CountOf ) return SQLITE_ERROR;
- if( eventHandler==0 || eid==0 ) return SQLITE_MISUSE;
++ if( eventHandler==0 || eId==0 ) return SQLITE_MISUSE;
+ while( pes < pesLim ){
+ /* Never add duplicate handlers, but may renew their user data. */
- if( pes->eid==eid && pes->eventHandler==eventHandler ){
++ if( pes->eid==eId && pes->eventHandler==eventHandler ){
+ pes->pvUserData = pvUserData;
+ return SQLITE_OK;
+ }
+ ++pes;
+ }
+ assert(pes==pesLim);
+ pes = sqlite3_realloc(psi->pSubscriptions,
+ (psi->numSubscriptions+1)*sizeof(*pes));
+ if( pes==0 ) return SQLITE_NOMEM;
+ psi->pSubscriptions = pes;
+ pes += (psi->numSubscriptions++);
- pes->eid = eid;
++ pes->eid = eId;
+ pes->pvUserData = pvUserData;
+ pes->eventHandler = eventHandler;
+ return SQLITE_OK;
}
-
- return rc;
}
/*
-** Implementation of ".lint" dot command.
-*/
-static int lintDotCommand(
- ShellState *pState, /* Current shell tool state */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
-){
- int n;
- n = (nArg>=2 ? strlen30(azArg[1]) : 0);
- if( n<1 || sqlite3_strnicmp(azArg[1], "fkey-indexes", n) ) goto usage;
- return lintFkeyIndexes(pState, azArg, nArg);
-
- usage:
- raw_printf(stderr, "Usage %s sub-command ?switches...?\n", azArg[0]);
- raw_printf(stderr, "Where sub-commands are:\n");
- raw_printf(stderr, " fkey-indexes\n");
- return SQLITE_ERROR;
+ * Unsubscribe all event listeners having an ExtensionId > 0. This is
+ * done just prior closing the shell DB (when dynamic extensions will
+ * be unloaded and accessing them in any way is good for a crash.)
+ */
+static void unsubscribe_extensions(ShellInState *psi){
+ ShellExState *psx = XSS(psi);
+ int esix = 0;
+
+ if( psi->numExtLoaded<=1 ) return; /* Ignore shell pseudo-extension. */
+ while( esix<psi->numSubscriptions ){
+ struct EventSubscription *pes = psi->pSubscriptions+esix;
+ if( pes->eid > 0 ){
+ int nsin = psi->numSubscriptions;
+ subscribe_events(psx, pes->eid, psx, NK_Unsubscribe, 0);
+ esix = esix + 1 + (psi->numSubscriptions - nsin);
+ }else ++esix;
+ }
}
-#if !defined SQLITE_OMIT_VIRTUALTABLE
-static void shellPrepare(
- sqlite3 *db,
- int *pRc,
- const char *zSql,
- sqlite3_stmt **ppStmt
+static struct InSource *currentInputSource(ShellExState *p){
+ return ISS(p)->pInSource;
+}
+
+static int nowInteractive(ShellExState *p){
+ return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
+}
+
+static const char *shellInvokedAs(void){
+ return Argv0;
+}
+
+static const char *shellStartupDir(void){
+ return startupDir;
+}
+
+static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
+static DotCommand * findDotCommand(const char *, ShellExState *, int *);
+static DotCmdRC runDotCommand(DotCommand*, char *[], int na, ShellExState*);
+
+static ExtensionHelpers extHelpers = {
+ 13,
+ {
+ failIfSafeMode,
+ utf8_out_printf,
+ currentInputSource,
+ strLineGet,
+ findDotCommand,
+ runDotCommand,
+ setColumnWidths,
+ nowInteractive,
+ shellInvokedAs,
+ shellStartupDir,
+ one_input_line,
+ free_input_line,
+ sqlite3_enable_load_extension,
+ 0
+ }
+};
+
+static ShellExtensionAPI shellExtAPI = {
+ &extHelpers, 6, {
+ register_dot_command,
+ register_exporter,
+ register_importer,
+ register_scripting,
+ subscribe_events,
+ register_adhoc_command,
+ 0
+ }
+};
+
+/* This SQL function provides a way for a just-loaded shell extension to
+ * obtain a ShellExtensionLink pointer from the shell core while using
+ * the same sqlite3_load_extension API used for SQLite extensions.
+ *
+ * (It is also useful for debugging a shell extension, as a breakpoint
+ * on it will be hit soon after loading and before real work is done.)
+ */
+static void shell_linkage(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
){
- *ppStmt = 0;
- if( *pRc==SQLITE_OK ){
- int rc = sqlite3_prepare_v2(db, zSql, -1, ppStmt, 0);
- if( rc!=SQLITE_OK ){
- raw_printf(stderr, "sql error: %s (%d)\n",
- sqlite3_errmsg(db), sqlite3_errcode(db)
- );
- *pRc = rc;
- }
+ int linkKind = 0;
+ void *pv;
+ if( argc>0 ){
+ linkKind = sqlite3_value_int(argv[0]);
+ }
+ switch (linkKind){
+ case 0:
+ pv = sqlite3_user_data(context);
+ break;
+ case 1:
+ pv = &extHelpers;
+ break;
+ case 2:
+ pv = &shellExtAPI;
+ break;
+ default:
+ pv = 0;
}
+ if( pv==0 ) sqlite3_result_null(context);
+ else sqlite3_result_pointer(context, pv, SHELLEXT_API_POINTERS, 0);
}
-/*
-** Create a prepared statement using printf-style arguments for the SQL.
-**
-** This routine is could be marked "static". But it is not always used,
-** depending on compile-time options. By omitting the "static", we avoid
-** nuisance compiler warnings about "defined but not used".
-*/
-void shellPreparePrintf(
- sqlite3 *db,
- int *pRc,
- sqlite3_stmt **ppStmt,
- const char *zFmt,
- ...
-){
- *ppStmt = 0;
- if( *pRc==SQLITE_OK ){
- va_list ap;
- char *z;
- va_start(ap, zFmt);
- z = sqlite3_vmprintf(zFmt, ap);
- va_end(ap);
- if( z==0 ){
- *pRc = SQLITE_NOMEM;
+/* Free the memory held by a ShExtInfo object but not the object itself.
+ * No notifications associated with takedown and termination are done. */
+static void free_ShExtInfo( ShExtInfo *psei ){
+ if( psei ){
+ if( psei->ppDotCommands ) sqlite3_free(psei->ppDotCommands);
+ if( psei->ppExportHandlers ) sqlite3_free(psei->ppExportHandlers);
+ if( psei->ppImportHandlers ) sqlite3_free(psei->ppImportHandlers);
+ memset(psei, 0, sizeof(ShExtInfo));
+ }
+}
+
+/* Do the initialization needed for use of dbShell for command lookup
+ * and dispatch and for I/O handler lookup and dispatch.
+ */
+static int begin_db_dispatch(ShellExState *psx){
+ ShellInState *psi = ISS(psx);
+ sqlite3_stmt *pStmt = 0;
+ int ic, rc1, rc2;
+ int rc = 0;
+ char *zErr = 0;
+ const char *zSql;
+ ShExtInfo sei = SHEXT_INFO_INIT;
+ AnyResourceHolder arh_sei = {&sei, (GenericFreer)free_ShExtInfo};
+ ResourceMark mark = holder_mark();
+
+ sstr_ptr_holder(&zErr);
+ /* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */
+ assert(psx->dbShell==0 || (psi->numExtLoaded==0 && psi->pShxLoaded==0));
+ rc = ensure_shell_db(psx);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: Shell DB uncreatable. Best to .quit soon.\n");
+ return SQLITE_ERROR;
+ }
+ if( ensure_dispatch_table(psx)!=SQLITE_OK ) return 1;
+
+ psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo));
+ shell_check_ooms(psi->pShxLoaded);
+ /* The ShellInState object now owns above allocation, so initialize it. */
+ memset(psi->pShxLoaded, 0, 2*sizeof(ShExtInfo));
+ any_ref_holder(&arh_sei); /* protect against early aborts */
+ sei.ppDotCommands
+ = (DotCommand **)sqlite3_malloc((numCommands+2)*sizeof(DotCommand *));
+ sei.ppExportHandlers
+ = (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *));
+ sei.ppImportHandlers
+ = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *));
+ if( sei.ppDotCommands==0||sei.ppExportHandlers==0||sei.ppImportHandlers==0
+ || psi->pShxLoaded==0 ){
+ shell_out_of_memory();
+ }
+ sei.numExportHandlers = 0;
+ sei.numImportHandlers = 0;
+ for( ic=0; ic<(int)numCommands; ++ic ){
+ sei.ppDotCommands[ic] = builtInCommand(ic);
+ }
+ sei.numDotCommands = ic;
+ zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)";
+ rc1 = s3_prepare_v2_noom(psx->dbShell, zSql, -1, &pStmt, 0);
+ stmt_holder(pStmt);
+ rc2 = s3_exec_noom(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
+ if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ){
+ rc = SQLITE_ERROR;
+ }else{
+ assert(sei.numDotCommands>0);
+ for( ic=0; ic<sei.numDotCommands; ++ic ){
+ DotCommand *pmc = sei.ppDotCommands[ic];
+ const char *zName = pmc->pMethods->name(pmc);
+ sqlite3_reset(pStmt);
+ shell_check_nomem(sqlite3_bind_text(pStmt, 1, zName, -1, 0));
+ sqlite3_bind_int(pStmt, 2, ic);
+ rc = s3_step_noom(pStmt);
+ if( rc!=SQLITE_DONE ){
+ sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
+ break;
+ }
+ }
+ if( rc!=SQLITE_DONE ){
+ rc = SQLITE_ERROR;
+ zSql = "ABORT";
}else{
- shellPrepare(db, pRc, z, ppStmt);
- sqlite3_free(z);
+ rc = SQLITE_OK;
+ zSql = "COMMIT";
+ }
+ rc2 = s3_exec_noom(psx->dbShell, zSql, 0, 0, &zErr);
+ if( SQLITE_OK==rc ){
+ /* Transfer just-built ShExtInfo to ShellInState use and ownership. */
+ psi->pShxLoaded[psi->numExtLoaded++] = sei;
+ arh_sei.pAny = 0;
+ sqlite3_enable_load_extension(psx->dbShell, 1);
+ psi->bDbDispatch = 1;
}
}
+ RESOURCE_FREE(mark);
+
+ return rc;
}
-/* Finalize the prepared statement created using shellPreparePrintf().
-**
-** This routine is could be marked "static". But it is not always used,
-** depending on compile-time options. By omitting the "static", we avoid
-** nuisance compiler warnings about "defined but not used".
-*/
-void shellFinalize(
- int *pRc,
- sqlite3_stmt *pStmt
-){
- if( pStmt ){
- sqlite3 *db = sqlite3_db_handle(pStmt);
- int rc = sqlite3_finalize(pStmt);
- if( *pRc==SQLITE_OK ){
- if( rc!=SQLITE_OK ){
- raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
+/* Call one loaded extension's destructors, in reverse order of their
+ * objects' creation.
+ */
+static void run_one_shext_dtors(ShExtInfo *psei){
+ int j;
+ if( psei->ppDotCommands!=0 ){
+ for( j=psei->numDotCommands; j>0; --j ){
+ DotCommand *pmc = psei->ppDotCommands[j-1];
+ if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
+ }
+ }
+ if( psei->ppExportHandlers!=0 ){
+ for( j=psei->numExportHandlers; j>0; --j ){
+ ExportHandler *peh = psei->ppExportHandlers[j-1];
+ if( peh->pMethods->destruct!=0 ) peh->pMethods->destruct(peh);
+ }
+ }
+ if( psei->ppImportHandlers!=0 ){
+ for( j=psei->numImportHandlers; j>0; --j ){
+ ImportHandler *pih = psei->ppImportHandlers[j-1];
+ if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih);
+ }
+ }
+ if( psei->extDtor!=0 ){
+ psei->extDtor(psei->pvExtObj);
+ }
+}
+
+/* Call all existent loaded extension destructors, in reverse order of their
+ * objects' creation, except for scripting support which is done last,
- * then free the tracking dyna-arrays.
++ * then free the tracking dynamic arrays.
+ */
+static void free_all_shext_tracking(ShellInState *psi){
+ if( psi->pShxLoaded!=0 ){
+ int i = psi->numExtLoaded;
+ while( i>1 ){
+ ShExtInfo *psei = &psi->pShxLoaded[--i];
+ run_one_shext_dtors(psei);
+ free_ShExtInfo(psei);
+ if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){
+ assert(psi->script!=0);
+ if (psi->script->pMethods->destruct){
+ psi->script->pMethods->destruct(psi->script);
+ }
+ psi->script = 0;
+ psi->scriptXid = 0;
}
- *pRc = rc;
}
+ free_ShExtInfo(psi->pShxLoaded);
+ sqlite3_free(psi->pShxLoaded);
+ psi->pShxLoaded = 0;
+ psi->numExtLoaded = 0;
}
}
-/* Reset the prepared statement created using shellPreparePrintf().
-**
-** This routine is could be marked "static". But it is not always used,
-** depending on compile-time options. By omitting the "static", we avoid
-** nuisance compiler warnings about "defined but not used".
-*/
-void shellReset(
- int *pRc,
- sqlite3_stmt *pStmt
-){
- int rc = sqlite3_reset(pStmt);
- if( *pRc==SQLITE_OK ){
- if( rc!=SQLITE_OK ){
- sqlite3 *db = sqlite3_db_handle(pStmt);
- raw_printf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
+static DotCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){
+ assert(extIx>=0);
+ if( extIx>=0 && extIx<psi->numExtLoaded ){
+ ShExtInfo *psei = & psi->pShxLoaded[extIx];
+ if( cmdIx>=0 && cmdIx<psei->numDotCommands ){
+ return psei->ppDotCommands[cmdIx];
}
- *pRc = rc;
}
+ return 0;
}
-#endif /* !defined SQLITE_OMIT_VIRTUALTABLE */
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
-/******************************************************************************
-** The ".archive" or ".ar" command.
-*/
-/*
-** Structure representing a single ".ar" command.
-*/
-typedef struct ArCommand ArCommand;
-struct ArCommand {
- u8 eCmd; /* An AR_CMD_* value */
- u8 bVerbose; /* True if --verbose */
- u8 bZip; /* True if the archive is a ZIP */
- u8 bDryRun; /* True if --dry-run */
- u8 bAppend; /* True if --append */
- u8 bGlob; /* True if --glob */
- u8 fromCmdLine; /* Run from -A instead of .archive */
- int nArg; /* Number of command arguments */
- char *zSrcTable; /* "sqlar", "zipfile($file)" or "zip" */
- const char *zFile; /* --file argument, or NULL */
- const char *zDir; /* --directory argument, or NULL */
- char **azArg; /* Array of command arguments */
- ShellState *p; /* Shell state */
- sqlite3 *db; /* Database containing the archive */
-};
+static int load_shell_extension(ShellExState *psx, const char *zFile,
+ const char *zProc, char **pzErr,
+ int nLoadArgs, char **azLoadArgs){
+ ShellExtensionLink shxLink = {
+ sizeof(ShellExtensionLink),
+ &shellExtAPI,
+ psx, /* pSXS */
+ 0, /* zErrMsg */
+ 0, /* ExtensionId */
+ 0, /* Extension destructor */
+ 0, /* Extension data ref */
+ nLoadArgs, azLoadArgs /* like-named members */
+ }; //extDtor(pvExtObj)
+ ShellInState *psi = ISS(psx);
+ /* save script support state for possible fallback if load fails */
+ ScriptSupport *pssSave = psi->script;
+ ExtensionId ssiSave = psi->scriptXid;
+ int rc;
+
+ if( pzErr ) *pzErr = 0;
+ if( psx->dbShell==0 || ISS(psx)->numExtLoaded==0 ){
+ rc = begin_db_dispatch(psx);
+ if( rc!=SQLITE_OK ) return rc;
+ assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0);
+ }
+ psi->ixExtPending = psi->numExtLoaded;
+ sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
+ SQLITE_DIRECTONLY|SQLITE_UTF8,
+ &shxLink, shell_linkage, 0, 0);
+ rc = sqlite3_load_extension(psx->dbShell, zFile, zProc, &shxLink.zErrMsg);
+ sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
+ SQLITE_DIRECTONLY|SQLITE_UTF8,
- 0, 0, 0, 0); /* deregister */
++ 0, 0, 0, 0); /* unregister */
+ if( pzErr!=0 ) *pzErr = shxLink.zErrMsg;
+ if( rc==SQLITE_OK ){
+ /* Keep extension's id and destructor for later disposal. */
+ ShExtInfo *psei = pending_ext_info(psi);
+ if( psei->extId!=0 && psei->extId!=shxLink.eid ) rc = SQLITE_MISUSE;
+ psei->extId = shxLink.eid;
+ psei->extDtor = shxLink.extensionDestruct;
+ psei->pvExtObj = shxLink.pvExtensionObject;
+ }else{
+ /* Release all resources extension might have registered before failing. */
+ if( psi->ixExtPending < psi->numExtLoaded ){
+ run_one_shext_dtors(psi->pShxLoaded+psi->ixExtPending);
+ free_ShExtInfo(psi->pShxLoaded+psi->ixExtPending);
+ --psi->numExtLoaded;
+ }
+ /* And make it unwind any scripting linkage it might have setup. */
+ if( psi->script!=0 ) psi->script->pMethods->destruct(psi->script);
+ psi->script = pssSave;
+ psi->scriptXid = ssiSave;
+ }
+ psi->ixExtPending = 0;
+ if( rc!=SQLITE_OK ){
+ if( rc==SQLITE_MISUSE && pzErr!=0 ){
+ *pzErr = smprintf("extension id mismatch %z\n", *pzErr);
+ }
+ rc = SQLITE_ERROR;
+ }
+ return rc;
+}
+#endif
-/*
-** Print a usage message for the .ar command to stderr and return SQLITE_ERROR.
+/* Dot-command implementation functions are defined in this section.
+COMMENT Define dot-commands and provide for their dispatch and .help text.
+COMMENT These should be kept in command name order for coding convenience
+COMMENT except where dot-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 command_table entry initializers, to be later
- COMMENT emitted by a mkshellc macro. (See EMIT_DOTCMD_INIT further on.)
++COMMENT emitted by a macro invocation. (See EMIT_DOTCMD_INIT further on.)
+** All dispatchable dot-command execute functions have this signature:
+static int someCommand(char *azArg[], int nArg, ShellExState *p, char **pzErr);
*/
-static int arUsage(FILE *f){
- showHelp(f,"archive");
- return SQLITE_ERROR;
+DISPATCH_CONFIG[
+ RETURN_TYPE=DotCmdRC
+ STORAGE_CLASS=static
+ ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellExState *$arg6, char **$arg7
+ DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 },
+ DOTCMD_INIT={ DOT_CMD_INFO(${cmd}, $arg1,$arg2,$arg3), {<HT0>, <HT1>}, 0 },
+ 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(SHELL_OMIT_SEEARGS));
+/*****************
+ * The .seeargs command
+ */
+COLLECT_HELP_TEXT[
+ ",seeargs Echo arguments suffixed with |",
+];
+DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){
+ int ia = 0;
+ for (ia=1; ia<nArg; ++ia)
+ raw_printf(ISS(p)->out, "%s%s", azArg[ia], (ia==nArg-1)? "|\n" : "|");
+ return DCR_Ok;
}
-/*
-** Print an error message for the .ar command to stderr and return
-** SQLITE_ERROR.
-*/
-static int arErrorMsg(ArCommand *pAr, const char *zFmt, ...){
- va_list ap;
- char *z;
- va_start(ap, zFmt);
- z = sqlite3_vmprintf(zFmt, ap);
- va_end(ap);
- utf8_printf(stderr, "Error: %s\n", z);
- if( pAr->fromCmdLine ){
- utf8_printf(stderr, "Use \"-A\" for more help\n");
+CONDITION_COMMAND(archive ARCHIVE_ENABLE && !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * 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 ? 2 0 azArg nArg p ){
+ open_db(p, 0);
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ return arDotCommand(p, 0, azArg, nArg);
+}
+
+/*****************
+ * The .auth command
+ */
+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 ){
+ open_db(p, 0);
+ if( booleanValue(azArg[1]) ){
+ sqlite3_set_authorizer(DBX(p), shellAuth, p);
+ }else if( ISS(p)->bSafeModeFuture ){
+ sqlite3_set_authorizer(DBX(p), safeModeAuth, p);
}else{
- utf8_printf(stderr, "Use \".archive --help\" for more help\n");
+ sqlite3_set_authorizer(DBX(p), 0, 0);
}
- sqlite3_free(z);
- return SQLITE_ERROR;
+ return DCR_Ok;
}
-/*
-** Values for ArCommand.eCmd.
-*/
-#define AR_CMD_CREATE 1
-#define AR_CMD_UPDATE 2
-#define AR_CMD_INSERT 3
-#define AR_CMD_EXTRACT 4
-#define AR_CMD_LIST 5
-#define AR_CMD_HELP 6
-#define AR_CMD_REMOVE 7
-
-/*
-** Other (non-command) switches.
-*/
-#define AR_SWITCH_VERBOSE 8
-#define AR_SWITCH_FILE 9
-#define AR_SWITCH_DIRECTORY 10
-#define AR_SWITCH_APPEND 11
-#define AR_SWITCH_DRYRUN 12
-#define AR_SWITCH_GLOB 13
+/*****************
+ * The .backup and .save commands (aliases for each other)
+ * These defer to writeDb in the dispatch table, so are not here.
+ */
+CONDITION_COMMAND(backup !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(save !defined(SQLITE_SHELL_FIDDLE) );
+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()",
+];
+DISPATCHABLE_COMMAND( backup 4 2 5 ){
+ return writeDb( azArg, nArg, p, pzErr);
+}
+DISPATCHABLE_COMMAND( save 3 2 5 ){
+ return writeDb( azArg, nArg, p, pzErr);
+}
-static int arProcessSwitch(ArCommand *pAr, int eSwitch, const char *zArg){
- switch( eSwitch ){
- case AR_CMD_CREATE:
- case AR_CMD_EXTRACT:
- case AR_CMD_LIST:
- case AR_CMD_REMOVE:
- case AR_CMD_UPDATE:
- case AR_CMD_INSERT:
- case AR_CMD_HELP:
- if( pAr->eCmd ){
- return arErrorMsg(pAr, "multiple command options");
- }
- pAr->eCmd = eSwitch;
- break;
+/*****************
+ * 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 DCR_Ok;
+}
- case AR_SWITCH_DRYRUN:
- pAr->bDryRun = 1;
- break;
- case AR_SWITCH_GLOB:
- pAr->bGlob = 1;
- break;
- case AR_SWITCH_VERBOSE:
- pAr->bVerbose = 1;
- break;
- case AR_SWITCH_APPEND:
- pAr->bAppend = 1;
- deliberate_fall_through;
- case AR_SWITCH_FILE:
- pAr->zFile = zArg;
- break;
- case AR_SWITCH_DIRECTORY:
- pAr->zDir = zArg;
- break;
+CONDITION_COMMAND(cd !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * 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(ISS(p)->out, 1);
+ }else{
+ setTextMode(ISS(p)->out, 1);
}
+ return DCR_Ok;
+}
- return SQLITE_OK;
+DISPATCHABLE_COMMAND( cd ? 2 2 ){
+ int rc=0;
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ else{
+#if defined(_WIN32) || defined(WIN32)
+ wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]);
+ shell_check_ooms(z);
+ rc = (z)? !SetCurrentDirectoryW(z) : 1;
+ sqlite3_free(z);
+#else
+ rc = chdir(azArg[1]);
+#endif
+ }
+ if( rc ){
+ utf8_printf(STD_ERR, "Cannot change to directory \"%s\"\n", azArg[1]);
+ rc = 1;
+ }
+ return DCR_Ok|rc;
}
-/*
-** Parse the command line for an ".ar" command. The results are written into
-** structure (*pAr). SQLITE_OK is returned if the command line is parsed
-** successfully, otherwise an error message is written to stderr and
-** SQLITE_ERROR returned.
+/* The ".breakpoint" command causes a call to the no-op routine named
+ * test_breakpoint(). It is undocumented.
*/
-static int arParseCommand(
- char **azArg, /* Array of arguments passed to dot command */
- int nArg, /* Number of entries in azArg[] */
- ArCommand *pAr /* Populate this object */
-){
- struct ArSwitch {
- const char *zLong;
- char cShort;
- u8 eSwitch;
- u8 bArg;
- } aSwitch[] = {
- { "create", 'c', AR_CMD_CREATE, 0 },
- { "extract", 'x', AR_CMD_EXTRACT, 0 },
- { "insert", 'i', AR_CMD_INSERT, 0 },
- { "list", 't', AR_CMD_LIST, 0 },
- { "remove", 'r', AR_CMD_REMOVE, 0 },
- { "update", 'u', AR_CMD_UPDATE, 0 },
- { "help", 'h', AR_CMD_HELP, 0 },
- { "verbose", 'v', AR_SWITCH_VERBOSE, 0 },
- { "file", 'f', AR_SWITCH_FILE, 1 },
- { "append", 'a', AR_SWITCH_APPEND, 1 },
- { "directory", 'C', AR_SWITCH_DIRECTORY, 1 },
- { "dryrun", 'n', AR_SWITCH_DRYRUN, 0 },
- { "glob", 'g', AR_SWITCH_GLOB, 0 },
- };
- int nSwitch = sizeof(aSwitch) / sizeof(struct ArSwitch);
- struct ArSwitch *pEnd = &aSwitch[nSwitch];
+COLLECT_HELP_TEXT[
+ ",breakpoint calls test_breakpoint(). (a debugging aid)",
+];
+DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
+ test_breakpoint();
+ return DCR_Ok;
+}
- if( nArg<=1 ){
- utf8_printf(stderr, "Wrong number of arguments. Usage:\n");
- return arUsage(stderr);
+CONDITION_COMMAND(check !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(clone !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * The .changes, .check, .clone and .connection commands
+ */
+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 DCR_Ok;
+}
+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;
+ DotCmdRC rv = DCR_Ok;
+ output_reset(ISS(p));
+ if( nArg!=2 ){
+ return DCR_ArgWrong;
+ }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){
+ *pzErr = smprintf("Error: cannot read 'testcase-out.txt'\n");
+ rv = DCR_Return;
+ }else if( testcase_glob(azArg[1],zRes)==0 ){
+ *pzErr =
+ smprintf("testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n",
+ ISS(p)->zTestcase, azArg[1], zRes);
+ rv = DCR_Error;
}else{
- char *z = azArg[1];
- if( z[0]!='-' ){
- /* Traditional style [tar] invocation */
- int i;
- int iArg = 2;
- for(i=0; z[i]; i++){
- const char *zArg = 0;
- struct ArSwitch *pOpt;
- for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
- if( z[i]==pOpt->cShort ) break;
- }
- if( pOpt==pEnd ){
- return arErrorMsg(pAr, "unrecognized option: %c", z[i]);
- }
- if( pOpt->bArg ){
- if( iArg>=nArg ){
- return arErrorMsg(pAr, "option requires an argument: %c",z[i]);
- }
- zArg = azArg[iArg++];
- }
- if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
- }
- pAr->nArg = nArg-iArg;
- if( pAr->nArg>0 ){
- pAr->azArg = &azArg[iArg];
- }
- }else{
- /* Non-traditional invocation */
- int iArg;
- for(iArg=1; iArg<nArg; iArg++){
- int n;
- z = azArg[iArg];
- if( z[0]!='-' ){
- /* All remaining command line words are command arguments. */
- pAr->azArg = &azArg[iArg];
- pAr->nArg = nArg-iArg;
- break;
- }
- n = strlen30(z);
-
- if( z[1]!='-' ){
- int i;
- /* One or more short options */
- for(i=1; i<n; i++){
- const char *zArg = 0;
- struct ArSwitch *pOpt;
- for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
- if( z[i]==pOpt->cShort ) break;
- }
- if( pOpt==pEnd ){
- return arErrorMsg(pAr, "unrecognized option: %c", z[i]);
- }
- if( pOpt->bArg ){
- if( i<(n-1) ){
- zArg = &z[i+1];
- i = n;
- }else{
- if( iArg>=(nArg-1) ){
- return arErrorMsg(pAr, "option requires an argument: %c",
- z[i]);
- }
- zArg = azArg[++iArg];
- }
- }
- if( arProcessSwitch(pAr, pOpt->eSwitch, zArg) ) return SQLITE_ERROR;
- }
- }else if( z[2]=='\0' ){
- /* A -- option, indicating that all remaining command line words
- ** are command arguments. */
- pAr->azArg = &azArg[iArg+1];
- pAr->nArg = nArg-iArg-1;
- break;
- }else{
- /* A long option */
- const char *zArg = 0; /* Argument for option, if any */
- struct ArSwitch *pMatch = 0; /* Matching option */
- struct ArSwitch *pOpt; /* Iterator */
- for(pOpt=&aSwitch[0]; pOpt<pEnd; pOpt++){
- const char *zLong = pOpt->zLong;
- if( (n-2)<=strlen30(zLong) && 0==memcmp(&z[2], zLong, n-2) ){
- if( pMatch ){
- return arErrorMsg(pAr, "ambiguous option: %s",z);
- }else{
- pMatch = pOpt;
- }
- }
- }
-
- if( pMatch==0 ){
- return arErrorMsg(pAr, "unrecognized option: %s", z);
- }
- if( pMatch->bArg ){
- if( iArg>=(nArg-1) ){
- return arErrorMsg(pAr, "option requires an argument: %s", z);
- }
- zArg = azArg[++iArg];
- }
- if( arProcessSwitch(pAr, pMatch->eSwitch, zArg) ) return SQLITE_ERROR;
- }
- }
+ utf8_printf(STD_OUT, "testcase-%s ok\n", ISS(p)->zTestcase);
+ ISS(p)->nCheck++;
+ }
+ sqlite3_free(zRes);
+ return (zRes==0)? DCR_Abort : rv;
+}
+DISPATCHABLE_COMMAND( clone ? 2 2 ){
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ tryToClone(p, azArg[1]);
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( connection ? 1 4 ){
+ ShellInState *psi = ISS(p);
+ if( nArg==1 ){
+ /* List available connections */
+ int i;
+ for(i=0; i<ArraySize(psi->aAuxDb); i++){
+ const char *zFile = psi->aAuxDb[i].zDbFilename;
+ if( psi->aAuxDb[i].db==0 && psi->pAuxDb!=&psi->aAuxDb[i] ){
+ zFile = "(not open)";
+ }else if( zFile==0 ){
+ zFile = "(memory)";
+ }else if( zFile[0]==0 ){
+ zFile = "(temporary-file)";
+ }
+ if( psi->pAuxDb == &psi->aAuxDb[i] ){
+ utf8_printf(STD_OUT, "ACTIVE %d: %s\n", i, zFile);
+ }else if( psi->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( psi->pAuxDb != &psi->aAuxDb[i] && i>=0 && i<ArraySize(psi->aAuxDb) ){
+ psi->pAuxDb->db = DBX(p);
+ psi->pAuxDb = &psi->aAuxDb[i];
+#if SHELL_DYNAMIC_EXTENSION
+ if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBX(p));
+#endif
+ globalDb = DBX(p) = psi->pAuxDb->db;
+#if SHELL_DYNAMIC_EXTENSION
+ if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBX(p));
+#endif
+ psi->pAuxDb->db = 0;
+ }
+ }else if( nArg==3 && cli_strcmp(azArg[1], "close")==0
+ && IsDigit(azArg[2][0]) && azArg[2][1]==0 ){
+ int i = azArg[2][0] - '0';
+ if( i<0 || i>=ArraySize(psi->aAuxDb) ){
+ /* No-op */
+ }else if( psi->pAuxDb == &psi->aAuxDb[i] ){
+ raw_printf(STD_ERR, "cannot close the active database connection\n");
+ return DCR_Error;
+ }else if( psi->aAuxDb[i].db ){
+ session_close_all(psi, i);
+ close_db(psi->aAuxDb[i].db);
+ psi->aAuxDb[i].db = 0;
}
+ }else{
+ return DCR_ArgWrong;
}
- if( pAr->eCmd==0 ){
- utf8_printf(stderr, "Required argument missing. Usage:\n");
- return arUsage(stderr);
- }
- return SQLITE_OK;
+ return DCR_Ok;
}
-/*
-** This function assumes that all arguments within the ArCommand.azArg[]
-** array refer to archive members, as for the --extract, --list or --remove
-** commands. It checks that each of them are "present". If any specified
-** file is not present in the archive, an error is printed to stderr and an
-** error code returned. Otherwise, if all specified arguments are present
-** in the archive, SQLITE_OK is returned. Here, "present" means either an
-** exact equality when pAr->bGlob is false or a "name GLOB pattern" match
-** when pAr->bGlob is true.
-**
-** This function strips any trailing '/' characters from each argument.
-** This is consistent with the way the [tar] command seems to work on
-** Linux.
-*/
-static int arCheckEntries(ArCommand *pAr){
- int rc = SQLITE_OK;
- if( pAr->nArg ){
- int i, j;
- sqlite3_stmt *pTest = 0;
- const char *zSel = (pAr->bGlob)
- ? "SELECT name FROM %s WHERE glob($name,name)"
- : "SELECT name FROM %s WHERE name=$name";
+CONDITION_COMMAND(dbinfo SQLITE_SHELL_HAVE_RECOVER);
+/*****************
+ * 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 = 0;
+ sqlite3 *db;
+ int i;
+ open_db(p, 0);
+ db = DBX(p);
+ rc = s3_prepare_v2_noom(db, "PRAGMA database_list", -1, &pStmt, 0);
+ stmt_holder(pStmt);
+ if( rc || pStmt==0 ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
+ rc = 1;
+ }else{
+ while( s3_step_noom(pStmt)==SQLITE_ROW ){
+ int eTxn, bRdonly;
+ 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;
+ eTxn = sqlite3_txn_state(db, zSchema);
+ bRdonly = sqlite3_db_readonly(db, zSchema);
+ utf8_printf(ISS(p)->out, "%s: %s %s%s\n",
+ zSchema,
+ zFile[0] ? zFile : "\"\"",
+ bRdonly ? "r/o" : "r/w",
+ eTxn==SQLITE_TXN_NONE ? "" :
+ eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn");
+ }
+ }
+ release_holder();
+ return DCR_Ok|(rc!=0);
+}
+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 },
+ { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER },
+ { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS },
+ { "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 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
+ if( nArg>=3 ){
+ sqlite3_db_config(DBX(p), aDbConfig[ii].op, booleanValue(azArg[2]), 0);
+ }
+ sqlite3_db_config(DBX(p), aDbConfig[ii].op, -1, &v);
+ utf8_printf(ISS(p)->out, "%19s %s\n",
+ aDbConfig[ii].zName, v ? "on" : "off");
+ if( nArg>1 ) break;
+ }
+ if( nArg>1 && ii==ArraySize(aDbConfig) ){
+ *pzErr = smprintf("Error: unknown dbconfig \"%s\"\n"
+ "Enter \".dbconfig\" with no arguments for a list\n",
+ azArg[1]);
+ return DCR_ArgWrong;
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){
+ return shell_dbinfo_command(p, nArg, azArg);
+}
- shellPreparePrintf(pAr->db, &rc, &pTest, zSel, pAr->zSrcTable);
- j = sqlite3_bind_parameter_index(pTest, "$name");
- for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
- char *z = pAr->azArg[i];
- int n = strlen30(z);
- int bOk = 0;
- while( n>0 && z[n-1]=='/' ) n--;
- z[n] = '\0';
- sqlite3_bind_text(pTest, j, z, -1, SQLITE_STATIC);
- if( SQLITE_ROW==sqlite3_step(pTest) ){
- bOk = 1;
+/*****************
+ * 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 ){
+ ShellInState *psi = ISS(p);
+ char *zLike = 0;
+ char *zSchema = "main";
+ char *zSql;
+ int i;
+ int savedShowHeader = psi->showHeader;
+ int savedShellFlags = psi->shellFlgs;
+ sstr_ptr_holder(&zLike);
+ 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( cli_strcmp(z,"preserve-rowids")==0 ){
+#ifdef SQLITE_OMIT_VIRTUALTABLE
+ *pzErr = smprintf("The --preserve-rowids option is not compatible"
+ " with SQLITE_OMIT_VIRTUALTABLE\n");
+ release_holder();
+ return DCR_ArgWrong;
+#else
+ ShellSetFlag(p, SHFLG_PreserveRowid);
+#endif
+ }else{
+ if( cli_strcmp(z,"newlines")==0 ){
+ ShellSetFlag(p, SHFLG_Newlines);
+ }else if( cli_strcmp(z,"data-only")==0 ){
+ ShellSetFlag(p, SHFLG_DumpDataOnly);
+ }else if( cli_strcmp(z,"nosys")==0 ){
+ ShellSetFlag(p, SHFLG_DumpNoSys);
+ }else if( cli_strcmp(z,"schema")==0 && ++i<nArg ){
+ zSchema = azArg[i];
+ }else{
+ *pzErr = smprintf("Unknown option \"%s\" on \".dump\"\n", azArg[i]);
+ release_holder();
+ return DCR_ArgWrong;
+ }
}
- shellReset(&rc, pTest);
- if( rc==SQLITE_OK && bOk==0 ){
- utf8_printf(stderr, "not found in archive: %s\n", z);
- rc = SQLITE_ERROR;
+ }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 = smprintf(
+ "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]
+ );
+
+ shell_check_ooms(zExpr);
+ if( zLike ){
+ zLike = smprintf("%z OR %z", zLike, zExpr);
+ }else{
+ zLike = zExpr;
}
}
- shellFinalize(&rc, pTest);
}
- return rc;
-}
-/*
-** Format a WHERE clause that can be used against the "sqlar" table to
-** identify all archive members that match the command arguments held
-** in (*pAr). Leave this WHERE clause in (*pzWhere) before returning.
-** 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,
- ArCommand *pAr,
- char **pzWhere /* OUT: New WHERE clause */
-){
- char *zWhere = 0;
- const char *zSameOp = (pAr->bGlob)? "GLOB" : "=";
- if( *pRc==SQLITE_OK ){
- if( pAr->nArg==0 ){
- zWhere = sqlite3_mprintf("1");
+ open_db(p, 0);
+
+ if( (psi->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(psi->out, "PRAGMA foreign_keys=OFF;\n");
+ raw_printf(psi->out, "BEGIN TRANSACTION;\n");
+ }
+ psi->writableSchema = 0;
+ psi->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(DBX(p), "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
+ psi->nErr = 0;
+ if( zLike==0 ) zLike = smprintf("true");
+ zSql = smprintf("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);
+ shell_check_ooms(zSql);
+ sstr_ptr_holder(&zSql);
+ run_schema_dump_query(psi,zSql);
+ if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ sqlite3_free(zSql);
+ zSql = smprintf(
+ "SELECT sql FROM sqlite_schema AS o "
+ "WHERE (%s) AND sql NOT NULL"
+ " AND type IN ('index','trigger','view')",
+ zLike
+ );
+ run_table_dump_query(psi, zSql);
+ }
+ release_holder(); /* zSql */
+ if( psi->writableSchema ){
+ raw_printf(psi->out, "PRAGMA writable_schema=OFF;\n");
+ psi->writableSchema = 0;
+ }
+ sqlite3_exec(DBX(p), "PRAGMA writable_schema=OFF;", 0, 0, 0);
+ sqlite3_exec(DBX(p), "RELEASE dump;", 0, 0, 0);
+ if( (psi->shellFlgs & SHFLG_DumpDataOnly)==0 ){
+ raw_printf(psi->out, psi->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n");
+ }
+ psi->showHeader = savedShowHeader;
+ psi->shellFlgs = savedShellFlags;
+ release_holder(); /* zLike */
+
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( echo ? 2 2 ){
+ setOrClearFlag(p, SHFLG_Echo, azArg[1]);
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( eqp ? 0 0 ){
+ ShellInState *psi = ISS(p);
+ if( nArg==2 ){
+ psi->autoEQPtest = 0;
+ if( psi->autoEQPtrace ){
+ if( DBX(p) ) sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
+ psi->autoEQPtrace = 0;
+ }
+ if( cli_strcmp(azArg[1],"full")==0 ){
+ psi->autoEQP = AUTOEQP_full;
+ }else if( cli_strcmp(azArg[1],"trigger")==0 ){
+ psi->autoEQP = AUTOEQP_trigger;
+#ifdef SQLITE_DEBUG
+ }else if( cli_strcmp(azArg[1],"test")==0 ){
+ psi->autoEQP = AUTOEQP_on;
+ psi->autoEQPtest = 1;
+ }else if( cli_strcmp(azArg[1],"trace")==0 ){
+ psi->autoEQP = AUTOEQP_full;
+ psi->autoEQPtrace = 1;
+ open_db(p, 0);
+ sqlite3_exec(DBX(p), "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0);
+ sqlite3_exec(DBX(p), "PRAGMA vdbe_trace=ON;", 0, 0, 0);
+#endif
}else{
- int i;
- const char *zSep = "";
- for(i=0; i<pAr->nArg; i++){
- const char *z = pAr->azArg[i];
- zWhere = sqlite3_mprintf(
- "%z%s name %s '%q' OR substr(name,1,%d) %s '%q/'",
- zWhere, zSep, zSameOp, z, strlen30(z)+1, zSameOp, z
- );
- if( zWhere==0 ){
- *pRc = SQLITE_NOMEM;
- break;
- }
- zSep = " OR ";
- }
+ psi->autoEQP = (u8)booleanValue(azArg[1]);
}
+ }else{
+ return DCR_ArgWrong;
}
- *pzWhere = zWhere;
+ return DCR_Ok;
}
-/*
-** Implementation of .ar "lisT" command.
-*/
-static int arListCommand(ArCommand *pAr){
- const char *zSql = "SELECT %s FROM %s WHERE %s";
- const char *azCols[] = {
- "name",
- "lsmode(mode), sz, datetime(mtime, 'unixepoch'), name"
- };
-
- char *zWhere = 0;
- sqlite3_stmt *pSql = 0;
+CONDITION_COMMAND(cease !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(exit !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(quit !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * The .cease, .exit and .quit commands
+ * These are together so that their differing effects are apparent.
+ */
+CONDITION_COMMAND(cease defined(SHELL_CEASE));
+COLLECT_HELP_TEXT[
+ ".cease ?CODE? Cease shell operation, with optional return code",
+ " Return code defaults to 0, otherwise is limited to non-signal values",
+ ".exit ?CODE? Exit shell program, maybe with return-code CODE",
+ " Exit immediately if CODE != 0, else functions as \"quit this input\"",
+ ".quit Stop interpreting input stream, done if primary.",
+];
+DISPATCHABLE_COMMAND( cease 4 1 2 ){
+ /* .cease effects an exit, always. Only the exit code is variable. */
+ int rc = 0;
+ if( nArg>1 ){
+ rc = (int)integerValue(azArg[1]);
+ if( rc>0x7f ) rc = 0x7f;
+ }
+ p->shellAbruptExit = 0x100|rc;
+ return DCR_Exit;
+}
+DISPATCHABLE_COMMAND( exit 3 1 0 ){
+ /* .exit acts like .quit with no argument or a zero argument,
+ * only returning. With a non-zero argument, it effects an exit. */
int rc;
+ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ){
+ rc &= 0xff; /* Mimic effect of legacy call to exit(). */
+#ifdef SHELL_EXIT_EXITS_PROCESS
+ terminate_actions();
+ exit(rc);
+#else
+ p->shellAbruptExit = 0x100|rc;
+#endif
+ }
+ return DCR_Return;
+}
+DISPATCHABLE_COMMAND( quit 1 1 0 ){
+ /* .quit would be more aptly named .return, as it does nothing more. */
+ return DCR_Return;
+}
- rc = arCheckEntries(pAr);
- arWhereClause(&rc, pAr, &zWhere);
+/*****************
+ * 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 ){
+ ShellInState *psi = ISS(p);
+ int rv = DCR_Ok;
+ char *zErr = 0;
+ int i;
+ int iSample = 0;
- shellPreparePrintf(pAr->db, &rc, &pSql, zSql, azCols[pAr->bVerbose],
- pAr->zSrcTable, zWhere);
- if( pAr->bDryRun ){
- utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
- }else{
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
- if( pAr->bVerbose ){
- utf8_printf(pAr->p->out, "%s % 10d %s %s\n",
- sqlite3_column_text(pSql, 0),
- sqlite3_column_int(pSql, 1),
- sqlite3_column_text(pSql, 2),
- sqlite3_column_text(pSql, 3)
- );
+ if( psi->bSafeMode ) return DCR_AbortError;
+ assert( psi->expert.pExpert==0 );
+ memset(&psi->expert, 0, sizeof(ExpertInfo));
+
+ open_db(p, 0);
+
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ int n;
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ n = strlen30(z);
+ if( n>=2 && 0==cli_strncmp(z, "-verbose", n) ){
+ psi->expert.bVerbose = 1;
+ }
+ else if( n>=2 && 0==cli_strncmp(z, "-sample", n) ){
+ if( i==(nArg-1) ){
+ return DCR_Unpaired|i;
}else{
- utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
+ iSample = (int)integerValue(azArg[++i]);
+ if( iSample<0 || iSample>100 ){
+ *pzErr = smprintf("value out of range: %s\n", azArg[i]);
+ return DCR_ArgWrong|i;
+ }
}
}
+ else{
+ return DCR_Unknown|i;
+ }
}
- shellFinalize(&rc, pSql);
- sqlite3_free(zWhere);
- return rc;
+
+ psi->expert.pExpert = sqlite3_expert_new(DBI(psi), &zErr);
+ if( psi->expert.pExpert==0 ){
+ *pzErr = smprintf("sqlite3_expert_new: %s\n",
+ zErr ? zErr : "out of memory");
+ return DCR_Error;
+ }else{
+ sqlite3_expert_config(psi->expert.pExpert, EXPERT_CONFIG_SAMPLE, iSample);
+ }
+
+ return DCR_Ok;
}
+DISPATCHABLE_COMMAND( explain ? 1 2 ){
+ /* The ".explain" command is automatic now. It is largely
+ ** pointless, retained purely for backwards compatibility */
+ ShellInState *psi = ISS(p);
+ int val = 1;
+ if( nArg>1 ){
+ if( cli_strcmp(azArg[1],"auto")==0 ){
+ val = 99;
+ }else{
+ val = booleanValue(azArg[1]);
+ }
+ }
+ if( val==1 && psi->mode!=MODE_Explain ){
+ psi->normalMode = psi->mode;
+ psi->mode = MODE_Explain;
+ psi->autoExplain = 0;
+ }else if( val==0 ){
+ if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
+ psi->autoExplain = 0;
+ }else if( val==99 ){
+ if( psi->mode==MODE_Explain ) psi->mode = psi->normalMode;
+ psi->autoExplain = 1;
+ }
+ return DCR_Ok;
+}
-/*
-** Implementation of .ar "Remove" command.
-*/
-static int arRemoveCommand(ArCommand *pAr){
+/*****************
+ * The .excel, .once and .output commands
+ * These share much implementation, so they stick together.
+ */
+CONDITION_COMMAND(excel !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(once !defined(SQLITE_SHELL_FIDDLE));
+CONDITION_COMMAND(output !defined(SQLITE_SHELL_FIDDLE));
+
+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\")",
+];
+#ifndef SQLITE_SHELL_FIDDLE
+/* Shared implementation of .excel, .once and .output */
+static DotCmdRC outputRedirs(char *azArg[], int nArg,
+ ShellInState *psi, char **pzErr,
+ int bOnce, int eMode){
+ /* bOnce => 0: .output, 1: .once, 2: .excel */
+ /* eMode => 'x' for excel, else 0 */
int rc = 0;
- char *zSql = 0;
- char *zWhere = 0;
+ char *zFile = 0;
+ u8 bTxtMode = 0;
+ u8 bPutBOM = 0;
+ int i;
+ static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,0};
- if( pAr->nArg ){
- /* Verify that args actually exist within the archive before proceeding.
- ** And formulate a WHERE clause to match them. */
- rc = arCheckEntries(pAr);
- arWhereClause(&rc, pAr, &zWhere);
- }
- if( rc==SQLITE_OK ){
- zSql = sqlite3_mprintf("DELETE FROM %s WHERE %s;",
- pAr->zSrcTable, zWhere);
- if( pAr->bDryRun ){
- utf8_printf(pAr->p->out, "%s\n", zSql);
- }else{
- char *zErr = 0;
- rc = sqlite3_exec(pAr->db, "SAVEPOINT ar;", 0, 0, 0);
- if( rc==SQLITE_OK ){
- rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
- if( rc!=SQLITE_OK ){
- sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
- }else{
- rc = sqlite3_exec(pAr->db, "RELEASE ar;", 0, 0, 0);
+ sstr_ptr_holder(&zFile);
+ if( psi->bSafeMode ) return DCR_AbortError;
+ for(i=1; i<nArg; i++){
+ char *z = azArg[i];
+ if( z[0]=='-' ){
+ if( z[1]=='-' ) z++;
+ if( cli_strcmp(z,"-bom")==0 ){
+ bPutBOM = 1;
+ }else if( bOnce!=2 && cli_strcmp(z,"-x")==0 ){
+ eMode = 'x'; /* spreadsheet */
+ }else if( bOnce!=2 && cli_strcmp(z,"-e")==0 ){
+ eMode = 'e'; /* text editor */
+ }else{
+ return DCR_Unknown|i;
+ }
+ }else if( zFile==0 && eMode!='e' && eMode!='x' ){
+ zFile = smprintf("%s", z);
+ shell_check_ooms(zFile);
+ if( zFile[0]=='|' ){
+ while( i+1<nArg ){
+ zFile = smprintf("%z %s", zFile, azArg[++i]);
+ shell_check_ooms(zFile);
}
+ break;
}
- if( zErr ){
- utf8_printf(stdout, "ERROR: %s\n", zErr);
- sqlite3_free(zErr);
+ }else{
+ release_holder();
+ return DCR_TooMany|i;
+ }
+ }
+ if( zFile==0 ){
+ zFile = smprintf("stdout");
+ shell_check_ooms(zFile);
+ }
+ if( bOnce ){
+ psi->outCount = 2;
+ }else{
+ psi->outCount = 0;
+ }
+ output_reset(psi);
+#ifndef SQLITE_NOHAVE_SYSTEM
+ if( eMode=='e' || eMode=='x' ){
+ psi->doXdgOpen = 1;
+ outputModePush(psi);
+ if( eMode=='x' ){
+ /* spreadsheet mode. Output as CSV. */
+ newTempFile(psi, "csv");
+ psi->shellFlgs &= ~SHFLG_Echo;
+ psi->mode = MODE_Csv;
+ sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, SEP_Comma);
+ sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, SEP_CrLf);
+ }else{
+ /* text editor mode */
+ newTempFile(psi, "txt");
+ bTxtMode = 1;
+ }
+ sqlite3_free(zFile);
+ zFile = smprintf("%s", psi->zTempFile);
+ }
+#endif /* SQLITE_NOHAVE_SYSTEM */
+ shell_check_ooms(zFile);
+ if( zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = smprintf("pipes are not supported in this OS\n");
+ rc = 1;
+ psi->out = STD_OUT;
+#else
+ psi->out = popen(zFile + 1, "w");
+ if( psi->out==0 ){
+ *pzErr = smprintf("cannot open pipe \"%s\"\n", zFile + 1);
+ psi->out = STD_OUT;
+ rc = 1;
+ }else{
+ if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
+ sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
+ }
+#endif
+ }else{
+ psi->out = output_file_open(zFile, bTxtMode);
+ if( psi->out==0 ){
+ if( cli_strcmp(zFile,"off")!=0 ){
+ *pzErr = smprintf("cannot write to \"%s\"\n", zFile);
}
+ psi->out = STD_OUT;
+ rc = 1;
+ } else {
+ if( bPutBOM ) fwrite(zBOM, 1, 3, psi->out);
+ sqlite3_snprintf(sizeof(psi->outfile), psi->outfile, "%s", zFile);
}
}
- sqlite3_free(zWhere);
- sqlite3_free(zSql);
- return rc;
+ release_holder();
+ return DCR_Ok|rc;
}
+#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
-/*
-** Implementation of .ar "eXtract" command.
-*/
-static int arExtractCommand(ArCommand *pAr){
- const char *zSql1 =
- "SELECT "
- " ($dir || name),"
- " writefile(($dir || name), %s, mode, mtime) "
- "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"
- " AND name NOT GLOB '*..[/\\]*'";
+DISPATCHABLE_COMMAND( excel ? 1 2 ){
+ return outputRedirs(azArg, nArg, ISS(p), pzErr, 2, 'x');
+}
+DISPATCHABLE_COMMAND( once ? 1 6 ){
+ return outputRedirs(azArg, nArg, ISS(p), pzErr, 1, 0);
+}
+DISPATCHABLE_COMMAND( output ? 1 6 ){
+ return outputRedirs(azArg, nArg, ISS(p), pzErr, 0, 0);
+}
- const char *azExtraArg[] = {
- "sqlar_uncompress(data, sz)",
- "data"
- };
- sqlite3_stmt *pSql = 0;
- int rc = SQLITE_OK;
- char *zDir = 0;
- char *zWhere = 0;
- int i, j;
+/*****************
+ * 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, "" },
+ { "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" },*/
+ };
+ ShellInState *psi = ISS(p);
+ 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;
- /* If arguments are specified, check that they actually exist within
- ** the archive before proceeding. And formulate a WHERE clause to
- ** match them. */
- rc = arCheckEntries(pAr);
- arWhereClause(&rc, pAr, &zWhere);
+ open_db(p, 0);
+ zCmd = nArg>=2 ? azArg[1] : "help";
- if( rc==SQLITE_OK ){
- if( pAr->zDir ){
- zDir = sqlite3_mprintf("%s/", pAr->zDir);
- }else{
- zDir = sqlite3_mprintf("");
- }
- if( zDir==0 ) rc = SQLITE_NOMEM;
+ if( zCmd[0]=='-'
+ && (cli_strcmp(zCmd,"--schema")==0 || cli_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];
}
- shellPreparePrintf(pAr->db, &rc, &pSql, zSql1,
- azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
- );
+ /* The argument can optionally begin with "-" or "--" */
+ if( zCmd[0]=='-' && zCmd[1] ){
+ zCmd++;
+ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+ }
- if( rc==SQLITE_OK ){
- j = sqlite3_bind_parameter_index(pSql, "$dir");
- sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC);
+ /* --help lists all file-controls */
+ if( cli_strcmp(zCmd,"help")==0 ){
+ utf8_printf(psi->out, "Available file-controls:\n");
+ for(i=0; i<ArraySize(aCtrl); i++){
+ utf8_printf(psi->out, " .filectrl %s %s\n",
+ aCtrl[i].zCtrlName, aCtrl[i].zUsage);
+ }
+ return DCR_Error;
+ }
- /* Run the SELECT statement twice. The first time, writefile() is called
- ** for all archive members that should be extracted. The second time,
- ** only for the directories. This is because the timestamps for
- ** extracted directories must be reset after they are populated (as
- ** populating them changes the timestamp). */
- for(i=0; i<2; i++){
- j = sqlite3_bind_parameter_index(pSql, "$dirOnly");
- sqlite3_bind_int(pSql, j, i);
- if( pAr->bDryRun ){
- utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
+ /* 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( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
+ if( filectrl<0 ){
+ filectrl = aCtrl[i].ctrlCode;
+ iCtrl = i;
}else{
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
- if( i==0 && pAr->bVerbose ){
- utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
- }
- }
+ *pzErr = smprintf("ambiguous file-control: \"%s\"\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ return DCR_ArgWrong;
}
- shellReset(&rc, pSql);
}
- shellFinalize(&rc, pSql);
}
+ if( filectrl<0 ){
+ *pzErr = smprintf("unknown file-control: %s\n"
+ "Use \".filectrl --help\" for help\n", zCmd);
+ return DCR_ArgWrong;
+ }else{
+ switch(filectrl){
+ case SQLITE_FCNTL_SIZE_LIMIT: {
+ if( nArg!=2 && nArg!=3 ) break;
+ iRes = nArg==3 ? integerValue(azArg[2]) : -1;
+ sqlite3_file_control(DBX(p), 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(DBX(p), 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(DBX(p), 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(DBX(p), zSchema, filectrl, &x);
+ iRes = x;
+ isOk = 1;
+ break;
+ }
+ case SQLITE_FCNTL_TEMPFILENAME: {
+ char *z = 0;
+ if( nArg!=2 ) break;
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &z);
+ if( z ){
+ utf8_printf(psi->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(DBX(p), zSchema, filectrl, &x);
+ }
+ x = -1;
+ sqlite3_file_control(DBX(p), zSchema, filectrl, &x);
+ utf8_printf(psi->out,"%d\n", x);
+ isOk = 2;
+ break;
+ }
+ }
+ }
+ if( isOk==0 && iCtrl>=0 ){
+ *pzErr = smprintf("Usage: .filectrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+ return DCR_CmdErred;
+ }else if( isOk==1 ){
+ char zBuf[21];
+ sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", iRes);
+ raw_printf(psi->out, "%s\n", zBuf);
+ }
+ return DCR_Ok;
+}
- sqlite3_free(zDir);
- sqlite3_free(zWhere);
- return rc;
+static void modePopper(ShellInState *psi){
+ outputModePop(psi);
}
-/*
-** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out.
-*/
-static int arExecSql(ArCommand *pAr, const char *zSql){
+DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
int rc;
- if( pAr->bDryRun ){
- utf8_printf(pAr->p->out, "%s\n", zSql);
- rc = SQLITE_OK;
+ int doStats = 0;
+ ShellInState *psi = ISS(p);
+ u8 useMode = MODE_Semi;
+ AnyResourceHolder arh = {psi, (GenericFreer)modePopper};
+
+ if( nArg==2 && optionMatch(azArg[1], "indent") ){
+ useMode = MODE_Pretty;
+ nArg = 1;
+ }
+ if( nArg!=1 ){
+ return DCR_TooMany|1;
+ }
+ outputModePush(psi); /* Can fail to return due to OOM. */
+ any_ref_holder(&arh);
+ psi->showHeader = 0;
+ psi->cMode = psi->mode = useMode;
+ open_db(p, 0);
+ rc = s3_exec_noom(DBX(p),
+ "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, p, 0
+ );
+ if( rc==SQLITE_OK ){
+ sqlite3_stmt *pStmt;
+ rc = s3_prepare_v2_noom(p->dbUser,
+ "SELECT rowid FROM sqlite_schema"
+ " WHERE name GLOB 'sqlite_stat[134]'",
+ -1, &pStmt, 0);
+ stmt_holder(pStmt);
+ doStats = s3_step_noom(pStmt)==SQLITE_ROW;
+ release_holder();
+ }
+ if( doStats==0 ){
+ raw_printf(psi->out, "/* No STAT tables available */\n");
}else{
- char *zErr = 0;
- rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
- if( zErr ){
- utf8_printf(stdout, "ERROR: %s\n", zErr);
- sqlite3_free(zErr);
+ const char *zOldDestTable = p->zDestTable;
+ raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
+ psi->cMode = psi->mode = MODE_Insert;
+ p->zDestTable = "sqlite_stat1";
+ shell_exec(p, "SELECT * FROM sqlite_stat1", 0);
+ p->zDestTable = "sqlite_stat4";
+ shell_exec(p, "SELECT * FROM sqlite_stat4", 0);
+ raw_printf(psi->out, "ANALYZE sqlite_schema;\n");
+ p->zDestTable = zOldDestTable;
+ }
+ release_holder(); /* Restore shell state */
+ return rc > 0;
+}
+
+/*****************
+ * The .headers command
+ */
+COLLECT_HELP_TEXT[
+ ".headers on|off Turn display of headers on or off",
+];
+DISPATCHABLE_COMMAND( headers 6 2 2 ){
+ ISS(p)->showHeader = booleanValue(azArg[1]);
+ ISS(p)->shellFlgs |= SHFLG_HeaderSet;
+ return DCR_Ok;
+}
+
+/*****************
+ * The .help command
+ */
+
+/* This literal's value AND address are used for help's workings. */
+static const char *zHelpAll = "-all";
+
+COLLECT_HELP_TEXT[
+ ".help ?PATTERN?|?-all? Show help for PATTERN or everything, or summarize",
+ " Repeat -all to see undocumented commands",
+];
+DISPATCHABLE_COMMAND( help 3 1 3 ){
+ const char *zPat = 0;
+ FILE *out = ISS(p)->out;
+ if( nArg>1 ){
+ char *z = azArg[1];
+ if( (nArg==2 && azArg[1][0]=='0' && azArg[1][1]==0)
+ || (nArg==3 && cli_strcmp(z, zHelpAll)==0
+ && cli_strcmp(azArg[2], zHelpAll)==0) ){
+ /* Show the undocumented command help */
+ zPat = zHelpAll;
+ }else if( cli_strcmp(z,"-a")==0 || optionMatch(z, "all") ){
+ zPat = "";
+ }else{
+ zPat = z;
}
}
- return rc;
+ if( showHelp(out, zPat, p)==0 && nArg>1 ){
+ utf8_printf(out, "Nothing matches '%s'\n", azArg[1]);
+ }
+ /* Help pleas never fail! */
+ return DCR_Ok;
}
+CONDITION_COMMAND(import !defined(SQLITE_SHELL_FIDDLE));
+/*****************
+ * 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 = 0; /* within this schema (may default to "main") */
+ 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 psi->colSeparator[] */
+ char *zSql = 0; /* An SQL statement */
+ char *zFullTabName = 0; /* Table name with schema if applicable */
+ ImportCtx sCtx = {0}; /* Reader context */
+ AnyResourceHolder arh = { &sCtx, (GenericFreer)import_cleanup };
+ 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 */
+ FILE *out = ISS(p)->out; /* output stream */
+ char *zCreate = 0; /* CREATE TABLE statement text */
+ ShellInState *psi = ISS(p);
+ ResourceMark mark = holder_mark();
+ int rc = 0;
-/*
-** Implementation of .ar "create", "insert", and "update" commands.
-**
-** create -> Create a new SQL archive
-** insert -> Insert or reinsert all files listed
-** update -> Insert files that have changed or that were not
-** previously in the archive
-**
-** Create the "sqlar" table in the database if it does not already exist.
-** Then add each file in the azFile[] array to the archive. Directories
-** are added recursively. If argument bVerbose is non-zero, a message is
-** printed on stdout for each file archived.
-**
-** The create command is the same as update, except that it drops
-** any existing "sqlar" table before beginning. The "insert" command
-** always overwrites every file named on the command-line, where as
-** "update" only overwrites if the size or mtime or mode has changed.
-*/
-static int arCreateOrUpdateCommand(
- ArCommand *pAr, /* Command arguments and options */
- int bUpdate, /* true for a --create. */
- int bOnlyIfChanged /* Only update if file has changed */
-){
- const char *zCreate =
- "CREATE TABLE IF NOT EXISTS sqlar(\n"
- " name TEXT PRIMARY KEY, -- name of the file\n"
- " mode INT, -- access permissions\n"
- " mtime INT, -- last modification time\n"
- " sz INT, -- original file size\n"
- " data BLOB -- compressed content\n"
- ")";
- const char *zDrop = "DROP TABLE IF EXISTS sqlar";
- const char *zInsertFmt[2] = {
- "REPLACE INTO %s(name,mode,mtime,sz,data)\n"
- " SELECT\n"
- " %s,\n"
- " mode,\n"
- " mtime,\n"
- " CASE substr(lsmode(mode),1,1)\n"
- " WHEN '-' THEN length(data)\n"
- " WHEN 'd' THEN 0\n"
- " ELSE -1 END,\n"
- " sqlar_compress(data)\n"
- " FROM fsdir(%Q,%Q) AS disk\n"
- " WHERE lsmode(mode) NOT LIKE '?%%'%s;"
- ,
- "REPLACE INTO %s(name,mode,mtime,data)\n"
- " SELECT\n"
- " %s,\n"
- " mode,\n"
- " mtime,\n"
- " data\n"
- " FROM fsdir(%Q,%Q) AS disk\n"
- " WHERE lsmode(mode) NOT LIKE '?%%'%s;"
- };
- int i; /* For iterating through azFile[] */
- int rc; /* Return code */
- const char *zTab = 0; /* SQL table into which to insert */
- char *zSql;
- char zTemp[50];
- char *zExists = 0;
-
- arExecSql(pAr, "PRAGMA page_size=512");
- rc = arExecSql(pAr, "SAVEPOINT ar;");
- if( rc!=SQLITE_OK ) return rc;
- zTemp[0] = 0;
- if( pAr->bZip ){
- /* Initialize the zipfile virtual table, if necessary */
- if( pAr->zFile ){
- sqlite3_uint64 r;
- sqlite3_randomness(sizeof(r),&r);
- sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r);
- zTab = zTemp;
- zSql = sqlite3_mprintf(
- "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)",
- zTab, pAr->zFile
- );
- rc = arExecSql(pAr, zSql);
- sqlite3_free(zSql);
+ if(psi->bSafeMode) return DCR_AbortError;
+ memset(&sCtx, 0, sizeof(sCtx));
+ if( psi->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{
+ return DCR_TooMany|i;
+ }
+ }else if( cli_strcmp(z,"-v")==0 ){
+ eVerbose++;
+ }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
+ zSchema = azArg[++i];
+ }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
+ nSkip = integerValue(azArg[++i]);
+ }else if( cli_strcmp(z,"-ascii")==0 ){
+ sCtx.cColSep = SEP_Unit[0];
+ sCtx.cRowSep = SEP_Record[0];
+ xRead = ascii_read_one_field;
+ useOutputMode = 0;
+ }else if( cli_strcmp(z,"-csv")==0 ){
+ sCtx.cColSep = ',';
+ sCtx.cRowSep = '\n';
+ xRead = csv_read_one_field;
+ useOutputMode = 0;
}else{
- zTab = "zip";
+ return DCR_Unknown|i;
}
+ }
+ if( zTable==0 ){
+ *pzErr = smprintf("missing %s argument.\n", zFile==0 ? "FILE" : "TABLE");
+ return DCR_Missing;
+ }
+ 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(psi->colSeparator);
+ if( nSep==0 ){
+ zYap = "non-null column separator required for import";
+ }
+ if( nSep>1 ){
+ zYap = "multi-character or multi-byte column separators"
+ " not allowed for import";
+ }
+ nSep = strlen30(psi->rowSeparator);
+ if( nSep==0 ){
+ zYap = "non-null row separator required for import";
+ }
+ if( zYap!=0 ){
+ *pzErr = smprintf("%s\n", zYap);
+ return DCR_Error;
+ }
+ if( nSep==2 && psi->mode==MODE_Csv
+ && cli_strcmp(psi->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(psi->rowSeparator), psi->rowSeparator, SEP_Row);
+ nSep = strlen30(psi->rowSeparator);
+ }
+ if( nSep>1 ){
+ *pzErr
+ = smprintf("multi-character row separators not allowed for import\n");
+ return DCR_Error;
+ }
+ sCtx.cColSep = (u8)psi->colSeparator[0];
+ sCtx.cRowSep = (u8)psi->rowSeparator[0];
+ }
+ sCtx.zFile = zFile;
+ sCtx.nLine = 1;
+ if( sCtx.zFile[0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = smprintf("pipes are not supported in this OS\n");
+ return DCR_Error;
+#else
+ sCtx.in = popen(sCtx.zFile+1, "r");
+ sCtx.zFile = "<pipe>";
+ sCtx.xCloser = pclose;
+#endif
}else{
- /* Initialize the table for an SQLAR */
- zTab = "sqlar";
- if( bUpdate==0 ){
- rc = arExecSql(pAr, zDrop);
- if( rc!=SQLITE_OK ) goto end_ar_transaction;
+ sCtx.in = fopen(sCtx.zFile, "rb");
+ sCtx.xCloser = fclose;
+ }
+ if( sCtx.in==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", zFile);
+ return DCR_Error;
+ }
+ /* Here and below, resources must be freed before exit. */
+ any_ref_holder(&arh);
+ sCtx.z = sqlite3_malloc64(120);
+ shell_check_ooms(sCtx.z);
+ if( eVerbose>=2 || (eVerbose>=1 && useOutputMode) ){
+ char zSep[2];
+ zSep[1] = 0;
+ zSep[0] = sCtx.cColSep;
+ utf8_printf(out, "Column separator ");
+ output_c_string(out, zSep);
+ utf8_printf(out, ", row separator ");
+ zSep[0] = sCtx.cRowSep;
+ output_c_string(out, zSep);
+ utf8_printf(out, "\n");
+ }
+ while( (nSkip--)>0 ){
+ while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
+ }
+ if( zSchema!=0 ){
+ zFullTabName = smprintf("\"%w\".\"%w\"", zSchema, zTable);
+ }else{
+ zFullTabName = smprintf("\"%w\"", zTable);
+ }
+ shell_check_ooms(zFullTabName);
+ sstr_ptr_holder(&zFullTabName);
+ zSql = smprintf("SELECT * FROM %s", zFullTabName);
+ shell_check_ooms(zSql);
+ sstr_ptr_holder(&zSql);
+ nByte = strlen30(zSql);
+ rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
+ stmt_ptr_holder(&pStmt);
+ import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
+ if( rc && sqlite3_strglob("no such table: *", sqlite3_errmsg(DBX(p)))==0 ){
+ zCreate = smprintf("CREATE TABLE %s", zFullTabName);
+ sqlite3 *dbCols = 0;
+ char *zRenames = 0;
+ char *zColDefs;
+ shell_check_ooms(zCreate);
+ sstr_ptr_holder(&zCreate); /* +1 */
+ sstr_ptr_holder(&zRenames); /* +2 */
+ sstr_ptr_holder(&zColDefs); /* +3 */
+ conn_ptr_holder(&dbCols);
+ while( xRead(&sCtx) ){
+ zAutoColumn(sCtx.z, &dbCols, 0);
+ if( sCtx.cTerm!=sCtx.cColSep ) break;
+ }
+ zColDefs = zAutoColumn(0, &dbCols, &zRenames);
+ if( zRenames!=0 ){
+ FILE *fh = INSOURCE_IS_INTERACTIVE(psi->pInSource)? out : STD_ERR;
+ utf8_printf(fh, "Columns renamed during .import %s due to duplicates:\n"
+ "%s\n", sCtx.zFile, zRenames);
+ }
+ assert(dbCols==0);
+ drop_holder(); /* dbCols */
+ if( zColDefs==0 ){
+ *pzErr = smprintf("%s: empty file\n", sCtx.zFile);
+ import_fail: /* entry from outer blocks */
+ RESOURCE_FREE(mark);
+ return DCR_Error;
+ }
+ zCreate = smprintf("%z%z\n", zCreate, zColDefs);
+ zColDefs = 0;
+ shell_check_ooms(zCreate);
+ if( eVerbose>=1 ){
+ utf8_printf(out, "%s\n", zCreate);
+ }
+ rc = s3_exec_noom(DBX(p), zCreate, 0, 0, 0);
+ if( rc ){
+ *pzErr = smprintf("%s failed:\n%s\n", zCreate, sqlite3_errmsg(DBX(p)));
+ goto import_fail;
+ }
+ rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
+ }
+ if( rc ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ goto import_fail;
+ }
+ nCol = sqlite3_column_count(pStmt);
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ if( nCol==0 ) return DCR_Ok; /* no columns, no error */
+ sqlite3_free(zSql);
+ zSql = sqlite3_malloc64( nByte*2 + 20 + nCol*2 );
+ shell_check_ooms(zSql);
+ sqlite3_snprintf(nByte+20, zSql, "INSERT INTO %s VALUES(?", zFullTabName);
+ j = strlen30(zSql);
+ for(i=1; i<nCol; i++){
+ zSql[j++] = ',';
+ zSql[j++] = '?';
+ }
+ zSql[j++] = ')';
+ zSql[j] = 0;
+ if( eVerbose>=2 ){
+ utf8_printf(psi->out, "Insert using: %s\n", zSql);
+ }
+ rc = s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
+ if( rc ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ goto import_fail;
+ }
+ needCommit = sqlite3_get_autocommit(DBX(p));
+ if( needCommit ) sqlite3_exec(DBX(p), "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( psi->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++; }
+ }
}
- rc = arExecSql(pAr, zCreate);
+ 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(DBX(p)));
+ sCtx.nErr++;
+ }else{
+ sCtx.nRow++;
+ }
+ }
+ }while( sCtx.cTerm!=EOF );
+
+ if( needCommit ) sqlite3_exec(DBX(p), "COMMIT", 0, 0, 0);
+ if( eVerbose>0 ){
+ utf8_printf(out,
+ "Added %d rows with %d errors using %d lines of input\n",
+ sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
}
- if( bOnlyIfChanged ){
- zExists = sqlite3_mprintf(
- " AND NOT EXISTS("
- "SELECT 1 FROM %s AS mem"
- " WHERE mem.name=disk.name"
- " AND mem.mtime=disk.mtime"
- " AND mem.mode=disk.mode)", zTab);
+ RESOURCE_FREE(mark);
+ return DCR_Ok|(sCtx.nErr>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 ){
+ FILE *out = ISS(p)->out;
+ 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;
+ }
+ memcpy(kwBuf, zKW, szKW);
+ kwBuf[szKW] = 0;
+ utf8_printf(out, "%s%s", kwBuf, zSep);
+ }
+ }
+ }
+ if( nCol>0 ) utf8_printf(out, "\n");
}else{
- zExists = sqlite3_mprintf("");
+ int szKW = strlen30(azArg[1]);
+ int isKeyword = sqlite3_keyword_check(azArg[1], szKW);
+ utf8_printf(out, "%s is%s a keyword\n",
+ azArg[1], (isKeyword)? "" : " not");
}
- if( zExists==0 ) rc = SQLITE_NOMEM;
- for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
- char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab,
- pAr->bVerbose ? "shell_putsnl(name)" : "name",
- pAr->azArg[i], pAr->zDir, zExists);
- rc = arExecSql(pAr, zSql2);
- sqlite3_free(zSql2);
+ return DCR_Ok;
+}
+
+/*****************
+ * The .imposter, .iotrace, .limit, .lint and .log commands
+ */
+#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
+# define LOAD_ENABLE 1
+#else
+# define LOAD_ENABLE 0
+#endif
+CONDITION_COMMAND( imposter !defined(SQLITE_OMIT_TEST_CONTROL) );
+CONDITION_COMMAND( iotrace defined(SQLITE_ENABLE_IOTRACE) );
+CONDITION_COMMAND( load LOAD_ENABLE );
+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",
+];
+COLLECT_HELP_TEXT[
+#if !defined(SQLITE_SHELL_FIDDLE)
+ ".log FILE|on|off Turn logging on or off. FILE can be stderr/stdout",
+#else
+ ".log on|off Turn logging on or off.",
+#endif
+];
+DISPATCHABLE_COMMAND( imposter ? 3 3 ){
+ int rc = 0;
+ char *zSql = 0;
+ char *zCollist = 0;
+ sqlite3_stmt *pStmt = 0;
+ sqlite3 *db;
+ 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;
+ ResourceMark mark = holder_mark();
+
+ if( !ShellHasFlag(p,SHFLG_TestingMode) ){
+ utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
+ "imposter");
+ return DCR_Error;
+ }
+ if( !(nArg==3 || (nArg==2 && sqlite3_stricmp(azArg[1],"off")==0)) ){
+ *pzErr = smprintf("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 DCR_SayUsage;
}
-end_ar_transaction:
+ open_db(p, 0);
+ db = DBX(p);
+ if( nArg==2 ){
+ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 1);
+ return DCR_Ok;
+ }
+ sstr_ptr_holder(&zSql);
+ zSql = smprintf("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]);
+ rc = s3_prep_noom_free(db, &zSql, &pStmt);
if( rc!=SQLITE_OK ){
- sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
+ release_holder();
+ return DCR_Error;
+ }
+ stmt_ptr_holder(&pStmt);
+ if( s3_step_noom(pStmt)==SQLITE_ROW ){
+ tnum = sqlite3_column_int(pStmt, 0);
+ isWO = sqlite3_column_int(pStmt, 1);
+ }
+ zSql = smprintf("PRAGMA index_xinfo='%q'", azArg[1]);
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ rc = s3_prep_noom_free(db, &zSql, &pStmt);
+ i = 0;
+ sstr_ptr_holder(&zCollist);
+ 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 = smprintf("\"%w\"", zCol);
+ }else{
+ zCollist = smprintf("%z,\"%w\"", zCollist, zCol);
+ }
+ }
+ if( i==0 || tnum==0 ){
+ *pzErr = smprintf("no such index: \"%s\"\n", azArg[1]);
+ RESOURCE_FREE(mark);
+ return DCR_Error;
+ }
+ if( lenPK==0 ) lenPK = 100000;
+ zSql = smprintf("CREATE TABLE \"%w\"(%s,PRIMARY KEY(%.*s))"
+ "WITHOUT ROWID", azArg[2], zCollist, lenPK, zCollist);
+ shell_check_ooms(zSql);
+ rc = sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 1, tnum);
+ if( rc==SQLITE_OK ){
+ rc = s3_exec_noom(db, zSql, 0, 0, 0);
+ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, "main", 0, 0);
+ if( rc ){
+ *pzErr = smprintf("Error in [%s]: %s\n", zSql, sqlite3_errmsg(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{
- rc = arExecSql(pAr, "RELEASE ar;");
- if( pAr->bZip && pAr->zFile ){
- zSql = sqlite3_mprintf("DROP TABLE %s", zTemp);
- arExecSql(pAr, zSql);
- sqlite3_free(zSql);
+ *pzErr = smprintf("SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
+ }
+ RESOURCE_FREE(mark);
+ return DCR_Ok|(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( cli_strcmp(azArg[1], "-")==0 ){
+ sqlite3IoTrace = iotracePrintf;
+ iotrace = STD_OUT;
+ }else{
+ iotrace = fopen(azArg[1], "w");
+ if( iotrace==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
+ sqlite3IoTrace = 0;
+ return DCR_Error;
+ }else{
+ sqlite3IoTrace = iotracePrintf;
}
}
- sqlite3_free(zExists);
- return rc;
+ return DCR_Ok;
}
-/*
-** Implementation of ".ar" dot command.
-*/
-static int arDotCommand(
- ShellState *pState, /* Current shell tool state */
- int fromCmdLine, /* True if -A command-line option, not .ar cmd */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
-){
- ArCommand cmd;
- int rc;
- memset(&cmd, 0, sizeof(cmd));
- cmd.fromCmdLine = fromCmdLine;
- rc = arParseCommand(azArg, nArg, &cmd);
- if( rc==SQLITE_OK ){
- int eDbType = SHELL_OPEN_UNSPEC;
- cmd.p = pState;
- cmd.db = pState->db;
- if( cmd.zFile ){
- eDbType = deduceDatabaseType(cmd.zFile, 1);
- }else{
- eDbType = pState->openMode;
+/*****************
+ * The .limits and .load commands
+ */
+COLLECT_HELP_TEXT[
+ ",limits ?LIMIT_NAME? Display limit selected by its name, or all limits",
+ ".load FILE ?ENTRY? Load a SQLite extension library",
+ " If ENTRY is provided, the entry point \"sqlite_ENTRY_init\" is called.",
+ " Otherwise, the entry point name is derived from the FILE's name.",
+];
+
+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(DBX(p), aLimit[i].limitCode, -1));
}
- if( eDbType==SHELL_OPEN_ZIPFILE ){
- if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
- if( cmd.zFile==0 ){
- cmd.zSrcTable = sqlite3_mprintf("zip");
+ }else if( nArg>3 ){
+ return DCR_TooMany;
+ }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{
- cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
+ *pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
+ return DCR_Error;
}
}
- cmd.bZip = 1;
- }else if( cmd.zFile ){
- int flags;
- if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
- if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
- || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
- flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
- }else{
- flags = SQLITE_OPEN_READONLY;
- }
- cmd.db = 0;
- if( cmd.bDryRun ){
- 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,
- eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "cannot open file: %s (%s)\n",
- cmd.zFile, sqlite3_errmsg(cmd.db)
- );
- goto end_ar_command;
- }
- sqlite3_fileio_init(cmd.db, 0, 0);
- sqlite3_sqlar_init(cmd.db, 0, 0);
- sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
- shellPutsFunc, 0, 0);
-
}
- if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
- if( cmd.eCmd!=AR_CMD_CREATE
- && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
- ){
- utf8_printf(stderr, "database does not contain an 'sqlar' table\n");
- rc = SQLITE_ERROR;
- goto end_ar_command;
- }
- cmd.zSrcTable = sqlite3_mprintf("sqlar");
+ if( iLimit<0 ){
+ *pzErr = smprintf("unknown limit: \"%s\"\n"
+ "enter \".limits\" with no arguments for a list.\n",
+ azArg[1]);
+ return DCR_ArgWrong;
}
-
- switch( cmd.eCmd ){
- case AR_CMD_CREATE:
- rc = arCreateOrUpdateCommand(&cmd, 0, 0);
- break;
-
- case AR_CMD_EXTRACT:
- rc = arExtractCommand(&cmd);
- break;
-
- case AR_CMD_LIST:
- rc = arListCommand(&cmd);
- break;
-
- case AR_CMD_HELP:
- arUsage(pState->out);
- break;
-
- case AR_CMD_INSERT:
- rc = arCreateOrUpdateCommand(&cmd, 1, 0);
- break;
-
- case AR_CMD_REMOVE:
- rc = arRemoveCommand(&cmd);
- break;
-
- default:
- assert( cmd.eCmd==AR_CMD_UPDATE );
- rc = arCreateOrUpdateCommand(&cmd, 1, 1);
- break;
+ if( nArg==3 ){
+ sqlite3_limit(DBX(p), aLimit[iLimit].limitCode,
+ (int)integerValue(azArg[2]));
}
+ fprintf(STD_OUT, "%20s %d\n", aLimit[iLimit].zLimitName,
+ sqlite3_limit(DBX(p), aLimit[iLimit].limitCode, -1));
}
-end_ar_command:
- if( cmd.db!=pState->db ){
- close_db(cmd.db);
- }
- sqlite3_free(cmd.zSrcTable);
-
- return rc;
+ return DCR_Ok;
}
-/* End of the ".archive" or ".ar" command logic
-*******************************************************************************/
-#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
-#if SQLITE_SHELL_HAVE_RECOVER
+DISPATCHABLE_COMMAND( lint 3 1 0 ){
+ sqlite3 *db; /* Database handle to query "main" db of */
+ FILE *out = ISS(p)->out; /* Stream to write non-error output to */
+ int bVerbose = 0; /* If -verbose is present */
+ int bGroupByParent = 0; /* If -groupbyparent is present */
+ int i; /* To iterate through azArg[] */
+ const char *zIndent = ""; /* How much to indent CREATE INDEX by */
+ int rc; /* Return code */
+ sqlite3_stmt *pSql = 0; /* Compiled version of SQL statement below */
+ ResourceMark mark = holder_mark();
-/*
-** This function is used as a callback by the recover extension. Simply
-** print the supplied SQL statement to stdout.
-*/
-static int recoverSqlCb(void *pCtx, const char *zSql){
- ShellState *pState = (ShellState*)pCtx;
- utf8_printf(pState->out, "%s;\n", zSql);
- return SQLITE_OK;
-}
+ i = (nArg>=2 ? strlen30(azArg[1]) : 0);
+ if( i==0 || 0!=sqlite3_strnicmp(azArg[1], "fkey-indexes", i) ){
+ *pzErr = smprintf
+ ("Usage %s sub-command ?switches...?\n"
+ "Where sub-commands are:\n"
+ " fkey-indexes\n", azArg[0]);
+ return DCR_SayUsage;
+ }
+ open_db(p, 0);
+ db = DBX(p);
-/*
-** 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;
- const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */
- const char *zLAF = "lost_and_found";
- int bFreelist = 1; /* 0 if --ignore-freelist is specified */
- int bRowids = 1; /* 0 if --no-rowids */
- sqlite3_recover *p = 0;
- int i = 0;
+ /*
+ ** This SELECT statement returns one row for each foreign key constraint
+ ** in the schema of the main database. The column values are:
+ **
+ ** 0. The text of an SQL statement similar to:
+ **
+ ** "EXPLAIN QUERY PLAN SELECT 1 FROM child_table WHERE child_key=?"
+ **
+ ** This SELECT is similar to the one that the foreign keys implementation
+ ** needs to run internally on child tables. If there is an index that can
+ ** be used to optimize this query, then it can also be used by the FK
+ ** implementation to optimize DELETE or UPDATE statements on the parent
+ ** table.
+ **
+ ** 1. A GLOB pattern suitable for sqlite3_strglob(). If the plan output by
+ ** the EXPLAIN QUERY PLAN command matches this pattern, then the schema
+ ** contains an index that can be used to optimize the query.
+ **
+ ** 2. Human readable text that describes the child table and columns. e.g.
+ **
+ ** "child_table(child_key1, child_key2)"
+ **
+ ** 3. Human readable text that describes the parent table and columns. e.g.
+ **
+ ** "parent_table(parent_key1, parent_key2)"
+ **
+ ** 4. A full CREATE INDEX statement for an index that could be used to
+ ** optimize DELETE or UPDATE statements on the parent table. e.g.
+ **
+ ** "CREATE INDEX child_table_child_key ON child_table(child_key)"
+ **
+ ** 5. The name of the parent table.
+ **
+ ** These six values are used by the C logic below to generate the report.
+ */
+ const char *zSql =
+ "SELECT "
+ " 'EXPLAIN QUERY PLAN SELECT 1 FROM ' || quote(s.name) || ' WHERE '"
+ " || group_concat(quote(s.name) || '.' || quote(f.[from]) || '=?' "
+ " || fkey_collate_clause("
+ " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]),' AND ')"
+ ", "
+ " 'SEARCH ' || s.name || ' USING COVERING INDEX*('"
+ " || group_concat('*=?', ' AND ') || ')'"
+ ", "
+ " s.name || '(' || group_concat(f.[from], ', ') || ')'"
+ ", "
+ " f.[table] || '(' || group_concat(COALESCE(f.[to], p.[name])) || ')'"
+ ", "
+ " 'CREATE INDEX ' || quote(s.name ||'_'|| group_concat(f.[from], '_'))"
+ " || ' ON ' || quote(s.name) || '('"
+ " || group_concat(quote(f.[from]) ||"
+ " fkey_collate_clause("
+ " f.[table], COALESCE(f.[to], p.[name]), s.name, f.[from]), ', ')"
+ " || ');'"
+ ", "
+ " f.[table] "
+ "FROM sqlite_schema AS s, pragma_foreign_key_list(s.name) AS f "
+ "LEFT JOIN pragma_table_info AS p ON (pk-1=seq AND p.arg=f.[table]) "
+ "GROUP BY s.name, f.id "
+ "ORDER BY (CASE WHEN ? THEN f.[table] ELSE s.name END)"
+ ;
+ const char *zGlobIPK = "SEARCH * USING INTEGER PRIMARY KEY (rowid=?)";
- 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("-ignore-freelist", z, n)==0 ){
- bFreelist = 0;
- }else
- if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
- /* This option determines the name of the ATTACH-ed database used
- ** internally by the recovery extension. The default is "" which
- ** means to use a temporary database that is automatically deleted
- ** when closed. This option is undocumented and might disappear at
- ** any moment. */
- i++;
- zRecoveryDb = azArg[i];
- }else
- if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
- i++;
- zLAF = azArg[i];
- }else
- if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
- bRowids = 0;
+ for(i=2; i<nArg; i++){
+ int n = strlen30(azArg[i]);
+ if( n>1 && sqlite3_strnicmp("-verbose", azArg[i], n)==0 ){
+ bVerbose = 1;
+ }
+ else if( n>1 && sqlite3_strnicmp("-groupbyparent", azArg[i], n)==0 ){
+ bGroupByParent = 1;
+ zIndent = " ";
}
else{
- utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
- showHelp(pState->out, azArg[0]);
- return 1;
+ raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
+ azArg[0], azArg[1]
+ );
+ return DCR_Unknown|i;
}
}
- p = sqlite3_recover_init_sql(
- pState->db, "main", recoverSqlCb, (void*)pState
+ /* Register the fkey_collate_clause() SQL function */
+ rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
+ 0, shellFkeyCollateClause, 0, 0
);
- sqlite3_recover_config(p, 789, (void*)zRecoveryDb); /* Debug use only */
- sqlite3_recover_config(p, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
- sqlite3_recover_config(p, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
- sqlite3_recover_config(p, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
-
- sqlite3_recover_run(p);
- if( sqlite3_recover_errcode(p)!=SQLITE_OK ){
- const char *zErr = sqlite3_recover_errmsg(p);
- int errCode = sqlite3_recover_errcode(p);
- raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
+ if( rc==SQLITE_OK ){
+ rc = s3_prepare_v2_noom(db, zSql, -1, &pSql, 0);
+ }
+ /* Track resources after here. */
+ stmt_ptr_holder(&pSql);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int(pSql, 1, bGroupByParent);
}
- rc = sqlite3_recover_finish(p);
- return rc;
-}
-#endif /* SQLITE_SHELL_HAVE_RECOVER */
-
-/*
- * 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);
-}
-#endif
+ if( rc==SQLITE_OK ){
+ char *zPrev = 0;
+ sqlite3_stmt *pExplain = 0;
+ sstr_ptr_holder(&zPrev);
+ stmt_ptr_holder(&pExplain);
+ while( SQLITE_ROW==s3_step_noom(pSql) ){
+ int res = -1;
+ const char *zEQP = (const char*)sqlite3_column_text(pSql, 0);
+ const char *zGlob = (const char*)sqlite3_column_text(pSql, 1);
+ const char *zFrom = (const char*)sqlite3_column_text(pSql, 2);
+ const char *zTarget = (const char*)sqlite3_column_text(pSql, 3);
+ const char *zCI = (const char*)sqlite3_column_text(pSql, 4);
+ const char *zParent = (const char*)sqlite3_column_text(pSql, 5);
-#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
+ if( zEQP==0 || zGlob==0 ) continue;
+ rc = s3_prepare_v2_noom(db, zEQP, -1, &pExplain, 0);
+ if( rc!=SQLITE_OK ) break;
+ if( SQLITE_ROW==s3_step_noom(pExplain) ){
+ const char *zPlan = (const char*)sqlite3_column_text(pExplain, 3);
+ res = zPlan!=0 && ( 0==sqlite3_strglob(zGlob, zPlan)
+ || 0==sqlite3_strglob(zGlobIPK, zPlan));
+ }
+ rc = sqlite3_finalize(pExplain);
+ pExplain = 0;
+ if( rc!=SQLITE_OK ) break;
-/* 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
+ if( res<0 ){
+ raw_printf(STD_ERR, "Error: internal error");
+ break;
+ }else{
+ if( bGroupByParent
+ && (bVerbose || res==0)
+ && (zPrev==0 || sqlite3_stricmp(zParent, zPrev))
+ ){
+ raw_printf(out, "-- Parent table %s\n", zParent);
+ sqlite3_free(zPrev);
+ zPrev = smprintf("%s", zParent);
+ shell_check_ooms(zPrev);
+ }
-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 \
-";
-#else
- /* Counting on SQLITE_MAX_COLUMN < 100,000 here. (32767 is the hard limit.) */
- static const char * const zColDigits = "\
-SELECT CASE WHEN (nc < 10) THEN 1 WHEN (nc < 100) THEN 2 \
- WHEN (nc < 1000) THEN 3 WHEN (nc < 10000) THEN 4 \
- ELSE 5 FROM (SELECT count(*) AS nc 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);
- }
- 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{
- /* Formulate the columns spec, close the DB, zero *pDb. */
- char *zColsSpec = 0;
- int hasDupes = db_int(*pDb, zHasDupes);
- int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 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);
- sqlite3_finalize(pStmt);
- if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
- }
- 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;
+ if( res==0 ){
+ raw_printf(out, "%s%s --> %s\n", zIndent, zCI, zTarget);
+ }else if( bVerbose ){
+ raw_printf(out, "%s/* no extra indexes required for %s -> %s */\n",
+ zIndent, zFrom, zTarget);
+ }
}
}
- sqlite3_finalize(pStmt);
- sqlite3_close(*pDb);
- *pDb = 0;
- return zColsSpec;
+
+ if( rc!=SQLITE_OK ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
+ }else{
+ rc = sqlite3_finalize(pSql);
+ pSql = 0;
+ if( rc!=SQLITE_OK ) *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
+ }
+ }else{
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(db));
}
-}
+ RESOURCE_FREE(mark);
-/*
-** 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];
+ return DCR_Ok|(rc!=0);
+}
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( p->expert.pExpert ){
- expertFinish(p, 1, 0);
+DISPATCHABLE_COMMAND( load ? 2 3 ){
+ const char *zFile = 0, *zProc = 0;
+ int ai = 1, rc;
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ if( nArg<2 || azArg[1][0]==0 ){
+ /* Must have a non-empty FILE. (Will not load self.) */
+ return DCR_SayUsage;
+ }
+ while( ai<nArg ){
+ const char *zA = azArg[ai++];
+ if( zFile==0 ) zFile = zA;
+ else if( zProc==0 ) zProc = zA;
+ else return DCR_TooMany|ai;
}
+ open_db(p, 0);
+ rc = sqlite3_load_extension(DBX(p), zFile, zProc, pzErr);
+ return DCR_Ok|(rc!=SQLITE_OK);
+}
+
+DISPATCHABLE_COMMAND( log ? 2 2 ){
+ const char *zFile = azArg[1];
+ int bOn = cli_strcmp(zFile,"on")==0;
+ int bOff = cli_strcmp(zFile,"off")==0;
+#if defined(SQLITE_SHELL_FIDDLE)
+ if( !bOn && !bOff ) return DCR_SayUsage;
+#else
+ if( ISS(p)->bSafeMode && !bOn && !bOff ) return DCR_AbortError;
#endif
+ output_file_close(ISS(p)->pLog);
+ if( bOff ){
+ ISS(p)->pLog = 0;
+ return DCR_Ok;
+ }
+ if( bOn ) zFile = "stdout";
+ ISS(p)->pLog = output_file_open(zFile, 0);
+ return DCR_Ok|(ISS(p)->pLog==0);
+}
- /* 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;
- }
- 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]);
+static void effectMode(ShellInState *psi, 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( (psi->shellFlgs & SHFLG_HeaderSet)==0 ){
+ psi->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;
}
- azArg[nArg] = 0;
-
- /* Process the input line.
- */
- if( nArg==0 ) return 0; /* no tokens, no error */
- n = strlen30(azArg[0]);
- c = azArg[0][0];
- clearTempFile(p);
+ if( zRowSep!=0 ){
+ sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator, zRowSep);
+ }
+ if( zColSep!=0 ){
+ sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator, zColSep);
+ }
+ psi->mode = modeNominal;
+}
-#ifndef SQLITE_OMIT_AUTHORIZATION
- if( c=='a' && cli_strncmp(azArg[0], "auth", n)==0 ){
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .auth ON|OFF\n");
- rc = 1;
- goto meta_command_exit;
+/*****************
+ * The .mode command
+ */
+COLLECT_HELP_TEXT[
+ ".mode MODE ?OPTIONS? Set output mode",
+ " MODE is one of:",
+ " ascii Columns/rows delimited by 0x1F and 0x1E",
+ " box Tables using unicode box-drawing characters",
+ " csv Comma-separated values",
+ " column Output in columns. (See .width)",
+ " html HTML <table> code",
+ " insert SQL insert statements for TABLE",
+ " json Results in a JSON array",
+ " line One value per line",
+ " list Values delimited by \"|\"",
+ " markdown Markdown table format",
+ " qbox Shorthand for \"box --wrap 60 --quote\"",
+ " quote Escape answers as for SQL",
+ " table ASCII-art table",
+ " tabs Tab-separated values",
+ " tcl TCL list elements",
+ " OPTIONS: (for columnar modes or insert mode):",
+ " --wrap N Wrap output lines to no longer than N characters",
+ " --wordwrap B Wrap or not at word boundaries per B (on/off)",
+ " --ww Shorthand for \"--wordwrap 1\"",
+ " --quote Quote output text as SQL literals",
+ " --noquote Do not quote output text",
+ " TABLE The name of SQL table used for \"insert\" mode",
+];
+DISPATCHABLE_COMMAND( mode ? 1 0 ){
+ ShellInState *psi = ISS(p);
+ const char *zTabname = 0;
+ const char *zArg;
+ int i, aix;
+ u8 foundMode = MODE_COUNT_OF, setMode = MODE_COUNT_OF;
+ ColModeOpts cmOpts = ColModeOpts_default;
+ for(aix=1; aix<nArg; aix++){
+ zArg = azArg[aix];
+ if( optionMatch(zArg,"wrap") && aix+1<nArg ){
+ cmOpts.iWrap = integerValue(azArg[++aix]);
+ }else if( optionMatch(zArg,"ww") ){
+ cmOpts.bWordWrap = 1;
+ }else if( optionMatch(zArg,"wordwrap") && aix+1<nArg ){
+ cmOpts.bWordWrap = (u8)booleanValue(azArg[++aix]);
+ }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);
+ int isPlural = (nza>0 && zArg[nza-1]=='s');
+ if( foundMode!=MODE_COUNT_OF ) goto mode_badarg;
+ for( im=0; im<MODE_COUNT_OF; ++im ){
+ int nz = nza - (modeDescr[im].bMayPluralize && isPlural);
+ if( modeDescr[im].bUserBlocked ) continue;
+ if( cli_strncmp(zArg,modeDescr[im].zModeName,nz)==0 ){
+ if( nz<nza && nz!=strlen30(modeDescr[im].zModeName) ) continue;
+ foundMode = (u8)im;
+ setMode = modeDescr[im].iAliasFor;
+ break;
+ }
+ }
+ if( cli_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 ){
+ FILE *out = psi->out;
+ const char *zMode;
+#if SHELL_DATAIO_EXT
+ char *zTell = 0;
+ int mrc;
+ zMode = psi->pActiveExporter->pMethods->name(psi->pActiveExporter);
+ mrc = psi->pActiveExporter->pMethods->config(psi->pActiveExporter,0,&zTell);
+ if( zTell!=0 ){
+ raw_printf(out, "current output mode: %s %s\n", zMode, zTell);
+ sqlite3_free(zTell);
+ }else{
+ raw_printf(out, "current output mode: %s\n", zMode);
}
- 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
+ i = psi->mode;
+ assert(i>=0 && i<MODE_COUNT_OF);
+ zMode = modeDescr[i].zModeName;
+ /* Mode not specified. Show present mode (and toss any options set.) */
+ if( MODE_IS_COLUMNAR(psi->mode) ){
+ raw_printf
+ (out, "current output mode: %s --wrap %d --wordwrap %s --%squote\n",
+ zMode, psi->cmOpts.iWrap,
+ psi->cmOpts.bWordWrap ? "on" : "off",
+ psi->cmOpts.bQuote ? "" : "no");
}else{
- sqlite3_set_authorizer(p->db, 0, 0);
+ raw_printf(out, "current output mode: %.*s\n", nms, zMode);
}
- }else
#endif
-
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \
- && !defined(SQLITE_SHELL_FIDDLE)
- if( c=='a' && cli_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
+ }else{
+ effectMode(psi, foundMode, setMode);
+ if( MODE_IS_COLUMNAR(setMode) ){
+ psi->cmOpts = cmOpts;
+#if SHELL_DATAIO_EXT
+ psi->pActiveExporter = psi->pColumnarExporter;
+#endif
+ }else{
+#if SHELL_DATAIO_EXT
+ psi->pActiveExporter = psi->pFreeformExporter;
#endif
+ if( setMode==MODE_Insert ){
+ set_table_name(p, zTabname ? zTabname : "table");
+ }
+ }
+ }
+ psi->cMode = psi->mode;
+ return DCR_Ok;
+ flag_unknown:
+ *pzErr = smprintf("Unknown .mode option: %s\nValid options:\n%s",
+ zArg,
+ " --noquote\n"
+ " --quote\n"
+ " --wordwrap on/off\n"
+ " --wrap N\n"
+ " --ww\n");
+ return DCR_Unknown|aix;
+ mode_unknown:
+ *pzErr = smprintf("Mode should be one of:\n"
+ " ascii box column csv html insert json line\n"
+ " list markdown qbox quote table tabs tcl\n");
+ return DCR_Unknown|aix;
+ mode_badarg:
+ *pzErr = smprintf("Invalid .mode argument: %s\n", zArg);
+ return DCR_ArgWrong|aix;
+}
+
+/*****************
+ * The .oomfake command
+ */
+CONDITION_COMMAND(oomfake defined(SQLITE_DEBUG));
+COLLECT_HELP_TEXT[
+ ",oomfake [how_soon] Set how soon or whether to simulate OOM condition",
+];
+DISPATCHABLE_COMMAND( oomfake ? 1 2 azArg nArg p ){
+ if( nArg>1 ){
+ int oomf = (int)integerValue(azArg[1]);
+ fake_oom_countdown = oomf;
+ }
+ else raw_printf(ISS(p)->out, "OOM sim in %d allocations\n",
+ fake_oom_countdown);
+ return DCR_Ok;
+}
+/* Note that .open is (partially) available in WASM builds but is
+** currently only intended to be used by the fiddle tool, not
+** end users, so is "undocumented." */
+#ifdef SQLITE_SHELL_FIDDLE
+# define HOPEN ",open"
+#else
+# define HOPEN ".open"
+#endif
+/*****************
+ * The .nonce, .nullvalue and .open commands
+ */
+CONDITION_COMMAND(nonce !defined(SQLITE_SHELL_FIDDLE));
+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
+ " --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 ){
+ ShellInState *psi = ISS(p);
+ 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;
+ int openFlags = 0;
+ sqlite3_int64 szMax = 0;
+ int rc = 0;
+ /* Check for command-line arguments */
+ for(iName=1; iName<nArg; iName++){
+ const char *z = azArg[iName];
#ifndef SQLITE_SHELL_FIDDLE
- if( (c=='b' && n>=3 && cli_strncmp(azArg[0], "backup", n)==0)
- || (c=='s' && n>=3 && cli_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( cli_strcmp(z, "-append")==0 ){
- zVfs = "apndvfs";
- }else
- if( cli_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);
+ 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") ){
+ 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 ){
+ szMax = integerValue(azArg[++iName]);
+# endif /* SQLITE_OMIT_DESERIALIZE */
+ }else
+#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ if( z[0]=='-' ){
+ return DCR_Unknown|iName;
+ }else if( zFN ){
+ *pzErr = smprintf("extra argument: \"%s\"\n", z);
+ return DCR_TooMany;
+ }else{
+ zFN = z;
}
- 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;
+ }
+
+ /* Close the existing database */
+ session_close_all(psi, -1);
+#if SHELL_DYNAMIC_EXTENSION
+ if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbAboutToClose, DBX(p));
+#endif
+ close_db(DBX(p));
+ DBX(p) = 0;
+ psi->pAuxDb->zDbFilename = 0;
+ sqlite3_free(psi->pAuxDb->zFreeOnClose);
+ psi->pAuxDb->zFreeOnClose = 0;
+ psi->openMode = openMode;
+ psi->openFlags = 0;
+ psi->szMax = 0;
+
+ /* If a filename is specified, try to open it first */
+ if( zFN || psi->openMode==SHELL_OPEN_HEXDB ){
+ if( newFlag && zFN && !psi->bSafeMode ) shellDeleteFile(zFN);
+#ifndef SQLITE_SHELL_FIDDLE
+ if( psi->bSafeMode
+ && psi->openMode!=SHELL_OPEN_HEXDB
+ && zFN
+ && cli_strcmp(zFN,":memory:")!=0
+ ){
+ *pzErr = smprintf("cannot open database files in safe mode");
+ return DCR_AbortError;
}
- while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
- sqlite3_backup_finish(pBackup);
- if( rc==SQLITE_DONE ){
- rc = 0;
+#else
+ /* WASM mode has its own sandboxed pseudo-filesystem. */
+#endif
+ if( zFN ){
+ zNewFilename = smprintf("%s", zFN);
+ shell_check_ooms(zNewFilename);
}else{
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
+ zNewFilename = 0;
+ }
+ psi->pAuxDb->zDbFilename = zNewFilename;
+ psi->openFlags = openFlags;
+ psi->szMax = szMax;
+ open_db(p, OPEN_DB_KEEPALIVE);
+ if( DBX(p)==0 ){
+ *pzErr = smprintf("cannot open '%z'\n", zNewFilename);
rc = 1;
+ }else{
+ psi->pAuxDb->zFreeOnClose = zNewFilename;
}
- close_db(pDest);
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ }
+ if( DBX(p)==0 ){
+ /* As a fall-back open a TEMP database */
+ psi->pAuxDb->zDbFilename = 0;
+ open_db(p, 0);
+ }
+ return DCR_Ok|(rc!=0);
+}
- if( c=='b' && n>=3 && cli_strncmp(azArg[0], "bail", n)==0 ){
- if( nArg==2 ){
- bail_on_error = booleanValue(azArg[1]);
- }else{
- raw_printf(stderr, "Usage: .bail on|off\n");
+DISPATCHABLE_COMMAND( nonce ? 2 2 ){
+ ShellInState *psi = ISS(p);
+ if( psi->zNonce==0 || cli_strcmp(azArg[1],psi->zNonce)!=0 ){
+ raw_printf(STD_ERR, "line %d: incorrect nonce: \"%s\"\n",
+ psi->pInSource->lineno, azArg[1]);
+ p->shellAbruptExit = 0x102;
+ return DCR_Abort;
+ }
+ /* Suspend safe mode for 1 dot-command after this. */
+ psi->bSafeModeFuture = 2;
+ return DCR_Ok;
+}
+
+DISPATCHABLE_COMMAND( nullvalue ? 2 2 ){
+ sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s",
+ (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]);
+ return DCR_Ok;
+}
+
+/* Helper functions for .parameter and .vars commands
+ */
+
+struct keyval_row { char * value; int uses; int hits; };
+
+static int kv_find_callback(void *pData, int nc, char **pV, char **pC){
+ assert(nc>=1);
+ assert(cli_strcmp(pC[0],"value")==0);
+ struct keyval_row *pParam = (struct keyval_row *)pData;
+ assert(pParam->value==0); /* key values are supposedly unique. */
+ if( pParam->value!=0 ) sqlite3_free( pParam->value );
+ pParam->value = smprintf("%s", pV[0]); /* source owned by statement */
+ if( nc>1 ) pParam->uses = (int)integerValue(pV[1]);
+ ++pParam->hits;
+ 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);
+
+/* 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 smprintf("%s%s", zHome, zPath+1);
+ }
+ utf8_printf(STD_ERR, "Error: %s\n", zErr);
+ 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.
+ */
+static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName,
+ int bSaveNotLoad, ParamTableUse ptu,
+ const char *azNames[], int nNames){
+ char *zSql = 0; /* to be sqlite3_free()'ed */
+ sqlite3_str *sbCopy = 0;
+ sqlite3 *dbStore = 0;
+ const char *zHere = 0;
+ const char *zThere = SH_KV_STORE_SNAME;
+ const char *zTo;
+ const char *zFrom;
+ int rc = 0;
+ int openFlags = (bSaveNotLoad)
+ ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE
+ : SQLITE_OPEN_READONLY;
+
+ switch( ptu ){
+ case PTU_Binding: zHere = PARAM_TABLE_SNAME; break;
+ case PTU_Script: zHere = SHVAR_TABLE_SNAME; break;
+ default: assert(0); return 1;
+ }
+ zTo = (bSaveNotLoad)? zThere : zHere;
+ zFrom = (bSaveNotLoad)? zHere : zThere;
+ /* Ensure store DB can be opened and/or created appropriately. */
+ rc = shell_check_nomem(sqlite3_open_v2(zStoreDbName,&dbStore,openFlags,0));
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n",
+ bSaveNotLoad? "open/create" : "read", zStoreDbName);
+ return rc;
+ }
- /* Ensure it has the kv store table, or handle its absence. */
++ /* Ensure it has the key/value store table, or handle its absence. */
+ assert(dbStore!=0);
+ conn_ptr_holder(&dbStore);
+ if( sqlite3_table_column_metadata
+ (dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){
+ if( !bSaveNotLoad ){
+ utf8_printf(STD_ERR, "Error: No key/value pairs 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 "SH_KV_STORE_NAME"(\n"
+ " key TEXT PRIMARY KEY,\n"
+ " value,\n"
+ " uses INT\n"
+ ") WITHOUT ROWID;";
+ rc = s3_exec_noom(dbStore, zCT, 0, 0, 0);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Cannot create table %s. Nothing saved.", zThere);
+ }
}
- }else
+ }
+ release_holder();
+ assert(dbStore==0);
+ if( rc!=0 ) return rc;
- if( c=='b' && n>=3 && cli_strncmp(azArg[0], "binary", n)==0 ){
- if( nArg==2 ){
- if( booleanValue(azArg[1]) ){
- setBinaryMode(p->out, 1);
- }else{
- setTextMode(p->out, 1);
- }
+ zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA);
+ shell_check_ooms(zSql);
+ sstr_ptr_holder(&zSql);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ release_holder();
+ if( rc!=SQLITE_OK ) return rc;
+
+ sbCopy = sqlite3_str_new(db);
+ sqst_ptr_holder(&sbCopy);
+ 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);
+ drop_holder();
+ shell_check_ooms(zSql);
+ sstr_ptr_holder(&zSql);
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
+ release_holder();
+
+ s3_exec_noom(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0);
+ return rc;
+}
+
- /* Default locations of kv store DBs for .parameters and .vars save/load. */
++/* Default locations of key/value DBs for .parameters and .vars save/load. */
+static const char *zDefaultParamStore = "~/sqlite_params.sdb";
+static const char *zDefaultVarStore = "~/sqlite_vars.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.
++ * When return is 0, there is an error; what needs doing cannot be done.
+ * The return must eventually be sqlite3_free()'ed.
+ */
+static char *kv_store_path(const char *zSpec, ParamTableUse ptu){
+ if( zSpec==0 || zSpec[0]==0 || cli_strcmp(zSpec,"~")==0 ){
+ const char *zDef;
+ switch( ptu ){
+ case PTU_Binding: zDef = zDefaultParamStore; break;
+ case PTU_Script: zDef = zDefaultVarStore; break;
+ default: return 0;
+ }
+ return home_based_path(zDef);
+ }else if ( zSpec[0]=='~' ){
+ return home_based_path(zSpec);
+ }
+ return smprintf("%s", zSpec);
+}
+
- /* Load some or all kv pairs. Arguments are "load FILE ?NAMES?". */
++/* Load some or all key/value pairs. Arguments are "load FILE ?NAMES?". */
+static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu,
+ const char *azArg[], int nArg){
+ char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
+ if( zStore==0 ){
+ utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n");
+ return DCR_Error;
+ }else{
+ const char **pzFirst = (nArg>2)? azArg+2 : 0;
+ int nNames = (nArg>2)? nArg-2 : 0;
+ int rc;
+ sstr_holder(zStore);
+ rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames);
+ release_holder();
+ return rc;
+ }
+}
+
+/* Save some or all parameters. Arguments are "save FILE ?NAMES?". */
+static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu,
+ const char *azArg[], int nArg){
+ char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu);
+ if( zStore==0 ){
+ utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n");
+ return DCR_Error;
+ }else{
+ const char **pzFirst = (nArg>2)? azArg+2 : 0;
+ int nNames = (nArg>2)? nArg-2 : 0;
+ int rc;
+ sstr_holder(zStore);
+ rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames);
+ release_holder();
+ return rc;
+ }
+}
+
+#ifndef SQLITE_NOHAVE_SYSTEM
++/* Remove leading comment line of form "-- tag\n" from a by-ref string.
++ * The *pz_nt argument is dynamically allocated (ala sqlite3_malloc() and is
++ * replaced if the removal is done. This is a helper for 2nd next function.
++ */
++static void remove_name_tag(char **pz_nt, char *tag){
++ int tagLen;
++ char *zUntag;
++ assert(pz_nt!=0 && tag!=0);
++ if( *pz_nt==0 || strstr(*pz_nt, "-- ")!=*pz_nt ) return;
++ if( strstr((*pz_nt)+3, tag)!=(*pz_nt)+3 ) return;
++ tagLen = strlen30(tag)+3;
++ if( (*pz_nt)[tagLen] != '\n' ) return;
++ zUntag = smprintf("%s", *pz_nt+tagLen+1);
++ shell_check_ooms(zUntag);
++ sqlite3_free(*pz_nt);
++ *pz_nt = zUntag;
++}
++
+/*
+ * Edit one named value in the parameters or shell variables 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 selected table,
+ * and serves to select which of the two tables is modified.
++ *
++ * During editing, the 1st line of the text shows the variable name.
++ * If that is left as-is, it is removed after editing is done.
+ */
+static int edit_one_kvalue(sqlite3 *db, char *name, int eval,
- ParamTableUse uses, const char * zEditor){
++ ParamTableUse uses, const char * zEd, char **pzErr){
+ struct keyval_row kvRow = {0,0,0};
+ int rc;
+ char * zVal = 0;
+ const char *zTab = 0;
+ char * zSql = 0;
++ const char *zFmt;
+
++ sstr_ptr_holder(&zVal); /* +1 */
+ switch( uses ){
+ case PTU_Script: zTab = SHVAR_TABLE_SNAME; break;
+ case PTU_Binding: zTab = PARAM_TABLE_SNAME; break;
+ default: assert(0);
+ }
+ zSql = smprintf("SELECT value, uses FROM %s "
+ "WHERE key=%Q AND uses=%d", zTab, name, uses);
+ shell_check_ooms(zSql);
- sstr_ptr_holder(&kvRow.value);
- sstr_holder(zSql);
++ sstr_ptr_holder(&kvRow.value); /* +2 */
++ sstr_holder(zSql); /* +3 */
+ s3_exec_noom(db, zSql, kv_find_callback, &kvRow, 0);
- release_holder();
++ release_holder(); /* zSql =2 */
+ assert(kvRow.hits<2);
+ if( kvRow.hits==1 && kvRow.uses==uses){
+ /* Editing an existing value of same kind. */
- release_holder(); /* kvRow.value */
- if( eval!=0 ){
- zSql = smprintf("SELECT edit(value, %Q) FROM %s "
- "WHERE key=%Q AND uses=%d", zEditor, zTab, name, uses);
- shell_check_ooms(zSql);
- sstr_holder(zSql);
- zVal = db_text(db, zSql, 1);
- release_holder();
- sqlite3_free(zSql);
- zSql = smprintf("UPDATE %s SET value=(SELECT %s) "
- "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses);
- }else{
- zSql = smprintf("UPDATE %s SET value=edit(value, %Q) WHERE"
- " key=%Q AND uses=%d", zTab, zEditor, name, uses);
- }
++ release_holder(); /* kvRow.value =1 */
++ zSql = smprintf("SELECT edit('-- %s\n'||value, %Q) FROM %s "
++ "WHERE key=%Q AND uses=%d", name, zEd, zTab, name, uses);
++ shell_check_ooms(zSql);
++ sstr_holder(zSql);
++ zVal = db_text(db, zSql, 0);
++ release_holder(); /* zSql =1 */
++ remove_name_tag(&zVal, name);
++ zFmt = (!eval)? "UPDATE %s SET value=%Q) WHERE key=%Q AND uses=%d"
++ : "UPDATE %s SET value=(SELECT %s) WHERE key=%Q AND uses=%d";
++ zSql = smprintf(zFmt, zTab, zVal, name, uses);
+ }else{
+ /* Editing a new value of same kind. */
+ assert(kvRow.value==0 || kvRow.uses!=uses);
- drop_holder(); /* kvRow.value */
- if( eval!=0 ){
- zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor);
- sstr_holder(zSql);
- zVal = db_text(db, zSql, 1);
- release_holder();
- zSql = smprintf("INSERT INTO %s(key,value,uses)"
- " VALUES (%Q,(SELECT %s LIMIT 1),%d)",
- zTab, name, zVal, uses);
- }else{
- zSql = smprintf("INSERT INTO %s(key,value,uses)"
- " VALUES (%Q,edit('-- %q;%s', %Q),%d)",
- zTab, name, name, "\n", zEditor, uses);
- }
++ drop_holder(); /* kvRow.value =1 */
++ zSql = smprintf("SELECT edit('-- %s\n', %Q)", name, zEd);
++ shell_check_ooms(zSql);
++ sstr_holder(zSql);
++ zVal = db_text(db, zSql, 1);
++ release_holder(); /* zSql =1 */
++ remove_name_tag(&zVal, name);
++ zFmt = (!eval)? "INSERT INTO %s(key,value,uses) VALUES (%Q,%Q,%d)"
++ : "INSERT INTO %s(key,value,uses) VALUES(%Q,(SELECT %s LIMIT 1),%d)";
++ zSql = smprintf(zFmt, zTab, name, zVal, uses);
+ }
+ shell_check_ooms(zSql);
- sstr_holder(zSql);
- sstr_holder(zVal);
++ sstr_holder(zSql); /* +2 */
+ rc = sqlite3_exec(db, zSql, 0, 0, 0);
- release_holders(2);
++ if( rc!=SQLITE_OK && pzErr!=0 ){
++ if( eval ){
++ *pzErr = smprintf("Cannot evaluate SELECT %s\n(Fails with %s)\n",
++ zVal, sqlite3_errmsg(db));
+ }else{
- raw_printf(stderr, "Usage: .binary on|off\n");
- rc = 1;
++ *pzErr = smprintf("Cannot execute %s\n(Fails with %s)\n",
++ zSql, sqlite3_errmsg(db));
+ }
- }else
++ }
++ release_holders(2); /* =0 */
+ return rc!=SQLITE_OK;
+}
+#endif
- /* The undocumented ".breakpoint" command causes a call to the no-op
- ** routine named test_breakpoint().
- */
- if( c=='b' && n>=3 && cli_strncmp(azArg[0], "breakpoint", n)==0 ){
- test_breakpoint();
- }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 = smprintf("%z%s%s", z, zSep, *valBeg);
+ zSep = " ";
+ ++valBeg;
+ }
+ return z;
+}
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='c' && cli_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;
+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 }
+};
+
+/* 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;
+}
+
+/* Most of .vars set
+ * Return SQLITE_OK on success, else SQLITE_ERROR.
+ */
+static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){
+ int rc = SQLITE_OK;
+ char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
+ sqlite3_stmt *pStmtSet = 0;
+ char *zValue = (zValGlom==0)? *valBeg : zValGlom;
+ char *zSql
+ = smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)"
+ "VALUES(%Q,%Q,"SPTU_Script");", name, zValue);
+ sstr_holder(zValGlom);
+ rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
+ assert(rc==SQLITE_OK);
+ stmt_holder(pStmtSet);
+ rc = s3_step_noom(pStmtSet);
+ rc = (SQLITE_DONE==rc)? SQLITE_OK : SQLITE_ERROR;
+ release_holders(2);
+ return rc;
+}
+
+
+/* Most of the .parameter set subcommand (per help text)
+ * Return SQLITE_OK on success, else SQLITE_ERROR.
+ */
+static int param_set(sqlite3 *db, char cCast,
+ char *name, char **valBeg, char **valLim){
+ char *zSql = 0;
+ char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
+ sqlite3_stmt *pStmtSet = 0;
+ /* Above objects are managed. */
+ const char *zCastTo = 0;
+ int rc = SQLITE_OK, retries = 0, needsEval = 1;
+ char *zValue = (zValGlom==0)? *valBeg : zValGlom;
+
+ sstr_holder(zValGlom); /* +1 */
+ 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: .cd DIRECTORY\n");
- rc = 1;
}
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
-
- if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
- if( nArg==2 ){
- setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
+ }
+ stmt_ptr_holder(&pStmtSet); /* +2 */
+ if( needsEval ){
+ if( zCastTo!=0 ){
+ zSql = smprintf
+ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+ " VALUES(%Q,CAST((%s) AS %s),"SPTU_Binding");",
+ name, zValue, zCastTo );
}else{
- raw_printf(stderr, "Usage: .changes on|off\n");
- rc = 1;
- }
- }else
+ zSql = smprintf
+ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+ "VALUES(%Q,(%s),"SPTU_Binding");", name, zValue );
+ }
+ rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
+ }
+ 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 = smprintf
+ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
+ "VALUES(%Q,%Q,"SPTU_Binding");", name, zValue );
+ rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
+ assert(rc==SQLITE_OK);
+ }
+ sqlite3_step(pStmtSet);
+ release_holders(2);
+ return rc;
+}
-#ifndef SQLITE_SHELL_FIDDLE
- /* 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 && cli_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 ){
- 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
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+/* list or ls subcommand for .parameter and .vars dot-commands */
+static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort,
+ char **pzArgs, int nArg){
+ sqlite3_stmt *pStmt = 0;
+ sqlite3_str *sbList = 0;
+ char *zFromWhere = 0;
+ char *zSql = 0;
+ /* Above objects are managed. */
+ RESOURCE_MARK(mark);
+ sqlite3 *db;
+ int len = 0, rc;
+ const char *zTab;
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='c' && cli_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;
+ switch( ptu ){
+ case PTU_Binding:
+ db = DBX(psx);
+ zTab = PARAM_TABLE_SNAME;
+ break;
+ case PTU_Script:
+ db = psx->dbShell;
+ zTab = SHVAR_TABLE_NAME;
+ break;
+ default: assert(0); return;
+ }
+ sbList = sqlite3_str_new(db);
+ sqst_holder(sbList);
+ sqlite3_str_appendf(sbList, "FROM ");
+ sqlite3_str_appendf(sbList, zTab);
+ sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND ");
+ append_glob_terms(sbList, "key",
+ (const char **)pzArgs, (const char **)pzArgs+nArg);
+ shell_check_nomem(sqlite3_str_errcode(sbList));
+ zFromWhere = sqlite3_str_finish(sbList);
+ drop_holder();
+ shell_check_ooms(zFromWhere);
+ sstr_holder(zFromWhere); /* +1 */
+ zSql = smprintf("SELECT max(length(key)) %s", zFromWhere);
+ sstr_ptr_holder(&zSql);
+ rc = s3_prep_noom_free(db, &zSql, &pStmt);
+ stmt_ptr_holder(&pStmt);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int(pStmt, 1, ptu);
+ if( s3_step_noom(pStmt)==SQLITE_ROW ){
+ len = sqlite3_column_int(pStmt, 0);
+ if( len>40 ) len = 40;
+ if( len<4 ) len = 4;
}
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
-
- if( c=='c' && cli_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);
+ sqlite3_finalize(pStmt);
+ pStmt = 0;
+ }
+ if( len ){
+ FILE *out = ISS(psx)->out;
+ sqlite3_free(zSql);
+ zSql = 0;
+ if( !bShort ){
+ int nBindings = 0, nScripts = 0;
+ zSql = smprintf("SELECT key, uses,"
+ " iif(typeof(value)='text', quote(value), value) as v"
+ " %s ORDER BY uses, key", zFromWhere);
+ rc = s3_prep_noom_free(db, &zSql, &pStmt);
+ sqlite3_bind_int(pStmt, 1, ptu);
+ while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){
+ ParamTableUse ptux = sqlite3_column_int(pStmt,1);
+ const char *zName = sqlite3_column_text(pStmt,0);
+ const char *zValue = sqlite3_column_text(pStmt,2);
+ if( !zName ) zName = "?";
+ if( !zValue ) zValue = "?";
+ switch( ptux ){
+ case PTU_Binding:
+ if( nBindings++ == 0 ){
+ utf8_printf(out, "Bindings:\n%-*s %s\n", len, "name", "value");
+ }
+ utf8_printf(out, "%-*s %s\n", len, zName, zValue);
+ break;
+ case PTU_Script:
+ if( nScripts++ == 0 ){
+ utf8_printf(out, "Scripts:\n%-*s %s\n", len, "name", "value");
+ }
+ utf8_printf(out, "%-*s %s\n", len, zName, zValue);
+ break;
+ default: break; /* Ignore */
}
}
- }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 && cli_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;
- }
- }else{
- raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
- rc = 1;
- }
- }else
-
- if( c=='d' && n>1 && cli_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;
}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++;
- }
- }
- 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
-
- if( c=='d' && n>=3 && cli_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 },
- { "reverse_scanorder", SQLITE_DBCONFIG_REVERSE_SCANORDER },
- { "stmt_scanstatus", SQLITE_DBCONFIG_STMT_SCANSTATUS },
- { "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 && cli_strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue;
- if( nArg>=3 ){
- sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0);
+ int nc = 0, ncw = 78/(len+2);
+ zSql = smprintf("SELECT key %s ORDER BY key", zFromWhere);
+ rc = s3_prep_noom_free(db, &zSql, &pStmt);
+ sqlite3_bind_int(pStmt, 1, ptu);
+ while( rc==SQLITE_OK && s3_step_noom(pStmt)==SQLITE_ROW ){
+ utf8_printf(out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""),
+ len, sqlite3_column_text(pStmt,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");
+ if( nc>0 ) utf8_printf(out, "\n");
}
- }else
+ }
+ RESOURCE_FREE(mark);
+}
-#if SQLITE_SHELL_HAVE_RECOVER
- if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbinfo", n)==0 ){
- rc = shell_dbinfo_command(p, nArg, azArg);
- }else
+/* 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;
+ }
+ sqlite3_str_appendf(pStr, ")");
+ }
+}
- if( c=='r' && cli_strncmp(azArg[0], "recover", n)==0 ){
- open_db(p, 0);
- rc = recoverDatabaseCmd(p, nArg, azArg);
- }else
-#endif /* SQLITE_SHELL_HAVE_RECOVER */
+/* 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,
++ * This is used for ".parameter mumble ?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;
+ }
+ sqlite3_str_appendf(pStr, ")");
+ }
+}
- if( c=='d' && cli_strncmp(azArg[0], "dump", n)==0 ){
- char *zLike = 0;
- 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( cli_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);
+/*****************
+ * The .parameter command
+ */
+COLLECT_HELP_TEXT[
+ ".parameter CMD ... Manage per-DB SQL parameter bindings 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 to use edited value as text or -e to evaluate it.",
++ " edit ?OPT? ?NAME? ... Use edit() to create or alter parameter(s)",
++ " OPT may be -t to use edited value(s) as text or -e to evaluate it,",
++ " or -r or -m to edit referenced or missing parameters from last query.",
#endif
- }else
- if( cli_strcmp(z,"newlines")==0 ){
- ShellSetFlag(p, SHFLG_Newlines);
- }else
- if( cli_strcmp(z,"data-only")==0 ){
- ShellSetFlag(p, SHFLG_DumpDataOnly);
- }else
- if( cli_strcmp(z,"nosys")==0 ){
- ShellSetFlag(p, SHFLG_DumpNoSys);
- }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 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]
- );
+ " init Initialize TEMP table for bindings and scripts",
+ " list ?PATTERNS? List current DB parameters table binding 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, VALUE is the space-",
+ " joined argument list. TOPT may be one of {-b -i -n -r -t} to cast the",
+ " 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);
+ sqlite3 *db = DBX(p);
- if( zLike ){
- zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
- }else{
- zLike = zExpr;
+ /* .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( cli_strcmp(azArg[1],"clear")==0 || cli_strcmp(azArg[1],"unset")==0 ){
+ if( param_table_exists(db) && (nArg>2 || azArg[1][0]=='c') ){
+ sqlite3_str *sbZap = sqlite3_str_new(db);
+ char *zSql = 0;
+ 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_ooms(zSql);
+ sstr_holder(zSql);
+ sqlite3_exec(db, zSql, 0, 0, 0);
+ release_holder();
+ }
+ }else
+#ifndef SQLITE_NOHAVE_SYSTEM
+ /* .parameter edit ?NAMES?
+ ** Edit the named parameters. Any that do not exist are created.
+ */
+ if( cli_strcmp(azArg[1],"edit")==0 ){
+ ShellInState *psi = ISS(p);
++ char *zErr = 0;
+ int ia = 2;
+ int eval = 0;
++ int reffed = 0;
++ int missing = 0;
+ int edSet;
+
+ if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
+ utf8_printf(STD_ERR, "Error: "
+ ".parameter edit can only be used interactively.\n");
+ return DCR_Error;
+ }
+ param_table_init(db);
+ edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
+ if( edSet < 0 ) return DCR_Error;
+ else ia += edSet;
+ /* Future: Allow an option whereby new value can be evaluated
+ * the way that .parameter set ... does.
+ */
+ while( ia < nArg ){
+ ParamTableUse ptu;
+ char *zA = azArg[ia];
+ char cf = (zA[0]=='-')? zA[1] : 0;
+ if( cf!=0 && zA[2]==0 ){
+ ++ia;
+ switch( cf ){
+ case 'e': eval = 1; continue;
+ case 't': eval = 0; continue;
++ case 'm': missing = 1; reffed = 0; continue;
++ case 'r': reffed = 1; missing = 0; continue;
+ default:
+ utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA);
+ return DCR_Error;
}
}
- rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor);
+ ptu = classify_param_name(zA);
+ if( ptu!=PTU_Binding ){
+ utf8_printf(STD_ERR, "Error: "
+ "%s cannot be a binding parameter name.\n", zA);
+ return DCR_Error;
+ }
-
- 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 sqlite_schema AS o "
- "WHERE (%s) AND type=='table'"
- " AND sql NOT NULL"
- " ORDER BY tbl_name='sqlite_sequence', rowid",
- 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;
++ rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor, &zErr);
+ ++ia;
++ if( zErr!=0 ){
++ utf8_printf(STD_ERR, "%s", zErr);
++ sqlite3_free(zErr);
++ zErr = 0;
++ }
+ if( rc!=0 ) return DCR_Error;
+ }
++ if( reffed|missing ){
++ char acSqlCount[] = "SELECT count(*) FROM "PARAM_TABLE_SNAME
++ " WHERE uses="SPTU_Ref" AND value<=0";
++ const char *zSqlName = "SELECT key FROM "PARAM_TABLE_SNAME
++ " WHERE uses="SPTU_Ref" AND value<=%d ORDER BY rowid"
++ " LIMIT 1 OFFSET %d";
++ int nve, vix;
++ acSqlCount[sizeof(acSqlCount)-2] = (reffed)? '1' : '0';
++ nve = db_int(db, acSqlCount);
++ for( vix=0; vix<nve; ++vix ){
++ char *zQV = smprintf(zSqlName, (int)reffed, vix);
++ char *zVar = 0;
++ sstr_holder(zQV);
++ sstr_ptr_holder(&zVar);
++ shell_check_ooms(zQV);
++ zVar = db_text(db, zQV, 0);
++ rc = edit_one_kvalue(db, zVar, eval, PTU_Binding, psi->zEditor, &zErr);
++ release_holders(2);
++ if( zErr!=0 ){
++ utf8_printf(STD_ERR, "%s", zErr);
++ sqlite3_free(zErr);
++ zErr = 0;
++ }
++ if( rc!=0 ) return DCR_Error;
++ }
++ /* Edit */
+ }
}else
+#endif
- if( c=='e' && cli_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;
- }
+ /* .parameter init
+ ** Make sure the TEMP table used to hold bind parameters exists.
+ ** Create it if necessary.
+ */
+ if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
+ param_table_init(db);
}else
- if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){
- if( nArg==2 ){
- p->autoEQPtest = 0;
- if( p->autoEQPtrace ){
- if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0);
- p->autoEQPtrace = 0;
- }
- if( cli_strcmp(azArg[1],"full")==0 ){
- p->autoEQP = AUTOEQP_full;
- }else if( cli_strcmp(azArg[1],"trigger")==0 ){
- p->autoEQP = AUTOEQP_trigger;
-#ifdef SQLITE_DEBUG
- }else if( cli_strcmp(azArg[1],"test")==0 ){
- p->autoEQP = AUTOEQP_on;
- p->autoEQPtest = 1;
- }else if( cli_strcmp(azArg[1],"trace")==0 ){
- p->autoEQP = AUTOEQP_full;
- p->autoEQPtrace = 1;
- 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{
- raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\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 && ((cli_strcmp(azArg[1],"list")==0)
+ || (cli_strcmp(azArg[1],"ls")==0)) ){
+ list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2);
}else
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='e' && cli_strncmp(azArg[0], "exit", n)==0 ){
- if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
- rc = 2;
+ /* .parameter load
+ ** Load all or named parameters from specified or default (DB) file.
+ */
+ if( cli_strcmp(azArg[1],"load")==0 ){
+ param_table_init(db);
+ rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1);
}else
-#endif
- /* The ".explain" command is automatic now. It is largely pointless. It
- ** retained purely for backwards compatibility */
- if( c=='e' && cli_strncmp(azArg[0], "explain", n)==0 ){
- int val = 1;
- if( nArg>=2 ){
- if( cli_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;
- }
+ /* .parameter save
+ ** Save all or named parameters into specified or default (DB) file.
+ */
+ if( cli_strcmp(azArg[1],"save")==0 ){
+ rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1);
}else
-#ifndef SQLITE_OMIT_VIRTUALTABLE
- if( c=='e' && cli_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]);
+ /* .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 && cli_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_Binding ){
+ utf8_printf(STD_ERR,
+ "Error: %s is not a usable parameter name.\n", azArg[inv]);
rc = 1;
}else{
- open_db(p, 0);
- expertDotCommand(p, azArg, nArg);
+ param_table_init(db);
+ rc = param_set(db, cCast, azArg[inv], &azArg[inv+1], &azArg[nArg]);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db));
+ rc = 1;
+ }
}
}else
-#endif
-
- if( c=='f' && cli_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]=='-'
- && (cli_strcmp(zCmd,"--schema")==0 || cli_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];
- }
+ { /* If no command name and arg count matches, show a syntax error */
+ showHelp(ISS(p)->out, "parameter", p);
+ return DCR_CmdErred;
+ }
- /* The argument can optionally begin with "-" or "--" */
- if( zCmd[0]=='-' && zCmd[1] ){
- zCmd++;
- if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
- }
+ return DCR_Ok | (rc!=0);
+}
- /* --help lists all file-controls */
- if( cli_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);
+/*****************
+ * 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++){
+ utf8_printf(ISS(p)->out, "%s%s", azArg[i], (i==nArg-1)? "\n" : " ");
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( progress 3 2 0 ){
+ ShellInState *psi = ISS(p);
+ int i;
+ int nn = 0;
+ psi->flgProgress = 0;
+ psi->mxProgress = 0;
+ psi->nProgress = 0;
+ for(i=1; i<nArg; i++){
+ const char *z = azArg[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){
+ psi->flgProgress |= SHELL_PROGRESS_QUIET;
+ continue;
}
- 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( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
- if( filectrl<0 ){
- filectrl = aCtrl[i].ctrlCode;
- iCtrl = i;
+ if( cli_strcmp(z,"reset")==0 ){
+ psi->flgProgress |= SHELL_PROGRESS_RESET;
+ continue;
+ }
+ if( cli_strcmp(z,"once")==0 ){
+ psi->flgProgress |= SHELL_PROGRESS_ONCE;
+ continue;
+ }
+ if( cli_strcmp(z,"limit")==0 ){
+ if( i+1>=nArg ){
+ *pzErr = smprintf("missing argument on --limit\n");
+ return DCR_Unpaired|i;
}else{
- utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
- "Use \".filectrl --help\" for help\n", zCmd);
- rc = 1;
- goto meta_command_exit;
+ psi->mxProgress = (int)integerValue(azArg[++i]);
}
+ continue;
}
- }
- if( filectrl<0 ){
- utf8_printf(stderr,"Error: unknown file-control: %s\n"
- "Use \".filectrl --help\" for help\n", zCmd);
+ return DCR_Unknown|i;
}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;
- }
- }
+ nn = (int)integerValue(z);
+ }
+ }
+ open_db(p, 0);
+ sqlite3_progress_handler(DBX(p), nn, progress_handler, psi);
+ return DCR_Ok;
+}
+
+/* Allow too few arguments by tradition, (a form of no-op.) */
+DISPATCHABLE_COMMAND( prompt ? 1 3 ){
+ if( nArg >= 2) {
+ SET_MAIN_PROMPT(azArg[1]);
+ }
+ if( nArg >= 3) {
+ SET_MORE_PROMPT(azArg[2]);
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * The .recover and .restore commands
+ */
+CONDITION_COMMAND( recover SQLITE_SHELL_HAVE_RECOVER );
+CONDITION_COMMAND( restore !defined(SQLITE_SHELL_FIDDLE) );
+COLLECT_HELP_TEXT[
+ ".recover Recover as much data as possible from corrupt db.",
+ " --ignore-freelist Ignore pages that appear to be on db freelist",
+ " --lost-and-found TABLE Alternative name for the lost-and-found table",
+ " --no-rowids Do not attempt to recover rowid values",
+ " that are not also INTEGER PRIMARY KEYs",
+ ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
+];
+
+/*
+** 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 ){
+ int rc = SQLITE_OK;
+ const char *zRecoveryDb = ""; /* Name of "recovery" database. Debug only */
+ const char *zLAF = "lost_and_found";
+ int bFreelist = 1; /* 0 if --ignore-freelist is specified */
+ int bRowids = 1; /* 0 if --no-rowids */
+ sqlite3_recover *pr = 0;
+ int i = 0;
+ FILE *out = ISS(p)->out;
+ sqlite3 *db;
+
+ open_db(p, 0);
+ db = DBX(p);
+
+ 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("-ignore-freelist", z, n)==0 ){
+ bFreelist = 0;
+ }else
+ if( n<=12 && memcmp("-recovery-db", z, n)==0 && i<(nArg-1) ){
+ /* This option determines the name of the ATTACH-ed database used
+ ** internally by the recovery extension. The default is "" which
+ ** means to use a temporary database that is automatically deleted
+ ** when closed. This option is undocumented and might disappear at
+ ** any moment. */
+ i++;
+ zRecoveryDb = azArg[i];
+ }else
+ if( n<=15 && memcmp("-lost-and-found", z, n)==0 && i<(nArg-1) ){
+ i++;
+ zLAF = azArg[i];
+ }else
+ if( n<=10 && memcmp("-no-rowids", z, n)==0 ){
+ bRowids = 0;
}
- 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);
+ else{
+ utf8_printf(stderr, "unexpected option: %s\n", azArg[i]);
+ showHelp(stderr, azArg[0], p);
+ return DCR_Error;
}
- }else
+ }
- if( c=='f' && cli_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");
- }
- }else
+ pr = sqlite3_recover_init_sql(db, "main", recoverSqlCb, (void*)p);
- if( c=='h' && cli_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;
- }
- }else
+ sqlite3_recover_config(pr, 789, (void*)zRecoveryDb); /* Debug use only */
+ sqlite3_recover_config(pr, SQLITE_RECOVER_LOST_AND_FOUND, (void*)zLAF);
+ sqlite3_recover_config(pr, SQLITE_RECOVER_ROWIDS, (void*)&bRowids);
+ sqlite3_recover_config(pr, SQLITE_RECOVER_FREELIST_CORRUPT,(void*)&bFreelist);
- if( c=='h' && cli_strncmp(azArg[0], "help", n)==0 ){
- if( nArg>=2 ){
- n = showHelp(p->out, azArg[1]);
- if( n==0 ){
- utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]);
- }
- }else{
- showHelp(p->out, 0);
- }
- }else
+ sqlite3_recover_run(pr);
+ if( sqlite3_recover_errcode(pr)!=SQLITE_OK ){
+ const char *zErr = sqlite3_recover_errmsg(pr);
+ int errCode = sqlite3_recover_errcode(pr);
+ raw_printf(stderr, "sql error: %s (%d)\n", zErr, errCode);
+ }
+ rc = sqlite3_recover_finish(pr);
+ return (rc!=SQLITE_OK)? DCR_Error : DCR_Ok;
+}
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='i' && cli_strncmp(azArg[0], "import", n)==0 ){
- char *zTable = 0; /* Insert data into this table */
- char *zSchema = 0; /* within this schema (may default to "main") */
- 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 */
- char *zFullTabName; /* Table name with schema if applicable */
- 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 */
- char *zCreate = 0; /* CREATE TABLE statement text */
-
- failIfSafeMode(p, "cannot run .import in safe mode");
- memset(&sCtx, 0, sizeof(sCtx));
- if( p->mode==MODE_Ascii ){
- xRead = ascii_read_one_field;
- }else{
- xRead = csv_read_one_field;
- }
+DISPATCHABLE_COMMAND( restore ? 2 3 ){
+ int rc;
+ const char *zSrcFile;
+ const char *zDb;
+ sqlite3 *pSrc = 0;
+ sqlite3_backup *pBackup;
+ AnyResourceHolder arh = { 0, (GenericFreer)sqlite3_backup_finish };
+ int nTimeout = 0;
+
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ if( nArg==2 ){
+ zSrcFile = azArg[1];
+ zDb = "main";
+ }else if( nArg==3 ){
+ zSrcFile = azArg[2];
+ zDb = azArg[1];
+ }else{
+ return DCR_TooMany;
+ }
+ rc = shell_check_nomem(sqlite3_open(zSrcFile, &pSrc));
+ conn_holder(pSrc);
+ if( rc!=SQLITE_OK ){
+ *pzErr = smprintf("cannot open \"%s\"\n", zSrcFile);
+ release_holder();
+ return DCR_Error;
+ }
+ open_db(p, 0);
+ pBackup = sqlite3_backup_init(DBX(p), zDb, pSrc, "main");
+ if( pBackup==0 ){
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ release_holder();
+ return DCR_Error;
+ }
+ arh.pAny = pBackup;
+ any_ref_holder(&arh);
+ while( (rc = shell_check_nomem(sqlite3_backup_step(pBackup,100)))==SQLITE_OK
+ || rc==SQLITE_BUSY ){
+ if( rc==SQLITE_BUSY ){
+ if( nTimeout++ >= 3 ) break;
+ sqlite3_sleep(100);
+ }
+ }
+ if( rc==SQLITE_DONE ){
+ rc = 0;
+ }else if( rc==SQLITE_BUSY || rc==SQLITE_LOCKED ){
+ *pzErr = smprintf("source database is busy\n");
rc = 1;
- 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");
- goto meta_command_exit;
- }
- }else if( cli_strcmp(z,"-v")==0 ){
- eVerbose++;
- }else if( cli_strcmp(z,"-schema")==0 && i<nArg-1 ){
- zSchema = azArg[++i];
- }else if( cli_strcmp(z,"-skip")==0 && i<nArg-1 ){
- nSkip = integerValue(azArg[++i]);
- }else if( cli_strcmp(z,"-ascii")==0 ){
- sCtx.cColSep = SEP_Unit[0];
- sCtx.cRowSep = SEP_Record[0];
- xRead = ascii_read_one_field;
- useOutputMode = 0;
- }else if( cli_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");
- 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");
- 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");
- goto meta_command_exit;
- }
- if( nSep>1 ){
- raw_printf(stderr,
- "Error: multi-character column separators not allowed"
- " for import\n");
- goto meta_command_exit;
- }
- nSep = strlen30(p->rowSeparator);
- if( nSep==0 ){
- raw_printf(stderr,
- "Error: non-null row separator required for import\n");
- goto meta_command_exit;
- }
- if( nSep==2 && p->mode==MODE_Csv
- && cli_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");
- goto meta_command_exit;
- }
- sCtx.cColSep = (u8)p->colSeparator[0];
- sCtx.cRowSep = (u8)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");
- goto meta_command_exit;
-#else
- sCtx.in = popen(sCtx.zFile+1, "r");
- sCtx.zFile = "<pipe>";
- sCtx.xCloser = pclose;
+ }else{
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(DBX(p)));
+ rc = 1;
+ }
+ release_holders(2);
+ return DCR_Ok|rc;
+}
+
+/*****************
+ * The .scanstats and .schema commands
+ */
+COLLECT_HELP_TEXT[
+ ".scanstats on|off|est Turn sqlite3_stmt_scanstatus() metrics on or off",
+ ".schema ?PATTERN? Show the CREATE statements matching PATTERN",
+ " Options:",
+ " --indent Try to pretty-print the schema",
+ " --nosys Omit objects whose names start with \"sqlite_\"",
+];
+DISPATCHABLE_COMMAND( scanstats ? 2 2 ){
+ if( cli_strcmp(azArg[1], "est")==0 ){
+ ISS(p)->scanstatsOn = 2;
+ }else{
+ ISS(p)->scanstatsOn = (u8)booleanValue(azArg[1]);
+ }
+ open_db(p, 0);
+ sqlite3_db_config(DBX(p), SQLITE_DBCONFIG_STMT_SCANSTATUS,
+ ISS(p)->scanstatsOn, (int*)0);
+#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
+ raw_printf(STD_ERR, "Warning: .scanstats not available in this build.\n");
#endif
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( schema ? 1 2 ){
+ int rc = 0;
+ ShellText sSelect = {0};
+ ShellInState *psi = ISS(p);
+ u8 useMode = MODE_Semi;
+ AnyResourceHolder arh = {psi, (GenericFreer)modePopper};
+ RESOURCE_MARK(mark);
+ 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);
+
+ initText(&sSelect);
+ for(ii=1; ii<nArg; ii++){
+ if( optionMatch(azArg[ii],"indent") ){
+ useMode = MODE_Pretty;
+ }else if( optionMatch(azArg[ii],"debug") ){
+ bDebug = 1;
+ }else if( optionMatch(azArg[ii],"nosys") ){
+ bNoSystemTabs = 1;
+ }else if( azArg[ii][0]=='-' ){
+ return DCR_Unknown|ii;
+ }else if( zName==0 ){
+ zName = azArg[ii];
}else{
- sCtx.in = fopen(sCtx.zFile, "rb");
- sCtx.xCloser = fclose;
- }
- if( sCtx.in==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", zFile);
- goto meta_command_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");
- }
- sCtx.z = sqlite3_malloc64(120);
- if( sCtx.z==0 ){
- import_cleanup(&sCtx);
- shell_out_of_memory();
- }
- /* Below, resources must be freed before exit. */
- while( (nSkip--)>0 ){
- while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
- }
- if( zSchema!=0 ){
- zFullTabName = sqlite3_mprintf("\"%w\".\"%w\"", zSchema, zTable);
- }else{
- zFullTabName = sqlite3_mprintf("\"%w\"", zTable);
- }
- zSql = sqlite3_mprintf("SELECT * FROM %s", zFullTabName);
- if( zSql==0 || zFullTabName==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 ){
- sqlite3 *dbCols = 0;
- char *zRenames = 0;
- char *zColDefs;
- zCreate = sqlite3_mprintf("CREATE TABLE %s", zFullTabName);
- 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"
- "%s\n", sCtx.zFile, zRenames);
- sqlite3_free(zRenames);
- }
- assert(dbCols==0);
- if( zColDefs==0 ){
- utf8_printf(stderr,"%s: empty file\n", sCtx.zFile);
- import_fail:
- sqlite3_free(zCreate);
- sqlite3_free(zSql);
- sqlite3_free(zFullTabName);
- import_cleanup(&sCtx);
- 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));
- goto import_fail;
- }
- sqlite3_free(zCreate);
- zCreate = 0;
- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
- }
+ return DCR_TooMany;
+ }
+ }
+ outputModePush(psi); /* May OOM fail. */
+ any_ref_holder(&arh);
+ psi->showHeader = 0;
+ psi->cMode = psi->mode = useMode;
+ 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] = smprintf(
+ "CREATE TABLE %s (\n"
+ " type text,\n"
+ " name text,\n"
+ " tbl_name text,\n"
+ " rootpage integer,\n"
+ " sql text\n"
+ ")", zName);
+ shell_check_ooms(new_argv[0]);
+ sstr_holder(new_argv[0]);
+ new_argv[1] = 0;
+ new_colv[0] = "sql";
+ new_colv[1] = 0;
+ callback(p, 1, new_argv, new_colv);
+ release_holder();
+ }
+ }
+ if( zDiv ){
+ sqlite3_stmt *pStmt = 0;
+ rc = s3_prepare_v2_noom(p->dbUser,
+ "SELECT name FROM pragma_database_list",
+ -1, &pStmt, 0);
+ stmt_ptr_holder(&pStmt);
if( rc ){
- if (pStmt) sqlite3_finalize(pStmt);
- utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
- goto import_fail;
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(p->dbUser));
+ RESOURCE_FREE(mark);
+ return DCR_Error;
+ }
+ text_ref_holder(&sSelect);
+ appendText(&sSelect, "SELECT sql FROM", 0);
+ iSchema = 0;
+ while( s3_step_noom(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, zDb, '\'');
+ appendText(&sSelect, " AS sname FROM ", 0);
+ appendText(&sSelect, zDb, quoteChar(zDb));
+ appendText(&sSelect, ".sqlite_schema", 0);
}
- sqlite3_free(zSql);
- 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 %s VALUES(?", zFullTabName);
- 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);
- if( rc ){
- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
- if (pStmt) sqlite3_finalize(pStmt);
- goto import_fail;
+#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);
}
- sqlite3_free(zSql);
- sqlite3_free(zFullTabName);
- 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++; }
- }
+#endif
+ appendText(&sSelect, ") WHERE ", 0);
+ if( zName ){
+ char *zQarg = smprintf("%Q", zName);
+ int bGlob;
+ shell_check_ooms(zQarg);
+ sstr_holder(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);
}
- 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++;
- }
+ appendText(&sSelect, bGlob ? " GLOB " : " LIKE ", 0);
+ appendText(&sSelect, zQarg, 0);
+ if( !bGlob ){
+ appendText(&sSelect, " ESCAPE '\\' ", 0);
}
- }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);
- }
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
-
-#ifndef SQLITE_UNTESTABLE
- if( c=='i' && cli_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( !ShellHasFlag(p,SHFLG_TestingMode) ){
- utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
- "imposter");
- rc = 1;
- goto meta_command_exit;
- }
- 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;
+ appendText(&sSelect, " AND ", 0);
+ release_holder();
}
- 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);
+ if( bNoSystemTabs ){
+ appendText(&sSelect, "name NOT LIKE 'sqlite_%%' AND ", 0);
}
- 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);
- }
+ appendText(&sSelect, "sql IS NOT NULL"
+ " ORDER BY snum, rowid", 0);
+ if( bDebug ){
+ utf8_printf(psi->out, "SQL: %s;\n", sSelect.z);
+ }else{
+ rc = sqlite3_exec(p->dbUser, sSelect.z, callback, p, &zErrMsg);
}
- 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"
- );
- }
+ }
+ RESOURCE_FREE(mark);
+ if( zErrMsg ){
+ *pzErr = zErrMsg;
+ return DCR_Error;
+ }else if( rc != SQLITE_OK ){
+ *pzErr = smprintf("Error: querying schema information\n");
+ return DCR_Error;
+ }else{
+ return DCR_Ok;
+ }
+}
+
+/*****************
+ * 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( separator ? 2 3 ){
+ if( nArg>=2 ){
+ sqlite3_snprintf(sizeof(ISS(p)->colSeparator), ISS(p)->colSeparator,
+ "%.*s", (int)ArraySize(ISS(p)->colSeparator)-1, azArg[1]);
+ }
+ if( nArg>=3 ){
+ sqlite3_snprintf(sizeof(ISS(p)->rowSeparator), ISS(p)->rowSeparator,
+ "%.*s", (int)ArraySize(ISS(p)->rowSeparator)-1, azArg[2]);
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( session 3 2 0 ){
+ int rc = 0;
+ struct AuxDb *pAuxDb = ISS(p)->pAuxDb;
+ OpenSession *pSession = &pAuxDb->aSession[0];
+ FILE *out = ISS(p)->out;
+ 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( cli_strcmp(pAuxDb->aSession[iSes].zName, azArg[1])==0 ) break;
+ }
+ if( iSes<pAuxDb->nSession ){
+ pSession = &pAuxDb->aSession[iSes];
+ azCmd++;
+ nCmd--;
}else{
- raw_printf(stderr, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
- rc = 1;
+ pSession = &pAuxDb->aSession[0];
+ iSes = 0;
}
- sqlite3_free(zSql);
- }else
-#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
+ }
-#ifdef SQLITE_ENABLE_IOTRACE
- if( c=='i' && cli_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( cli_strcmp(azArg[1], "-")==0 ){
- sqlite3IoTrace = iotracePrintf;
- iotrace = stdout;
+ /* .session attach TABLE
+ ** Invoke the sqlite3session_attach() interface to attach a particular
+ ** table so that it is never filtered.
+ */
+ if( cli_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{
- iotrace = fopen(azArg[1], "w");
- if( iotrace==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
- sqlite3IoTrace = 0;
- rc = 1;
- }else{
- sqlite3IoTrace = iotracePrintf;
+ rc = sqlite3session_attach(pSession->p, azCmd[1]);
+ if( rc ){
+ raw_printf(STD_ERR, "ERROR: sqlite3session_attach() returns %d\n", rc);
+ rc = 0;
}
}
}else
}
}else
- if( c=='o' && cli_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 */
- int openMode = SHELL_OPEN_UNSPEC;
-
- /* Check for command-line arguments */
- for(iName=1; iName<nArg; iName++){
- const char *z = azArg[iName];
-#ifndef SQLITE_SHELL_FIDDLE
- 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
-#endif /* !SQLITE_SHELL_FIDDLE */
- 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;
- }
- }
+ /* .session open DB NAME
+ ** Open a new session called NAME on the attached database DB.
+ ** DB is normally "main".
+ */
+ if( cli_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( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
+ utf8_printf(STD_ERR, "Session \"%s\" already exists\n", zName);
+ return DCR_Error;
+ }
+ }
+ if( pAuxDb->nSession>=ArraySize(pAuxDb->aSession) ){
+ raw_printf
+ (STD_ERR, "Maximum of %d sessions\n", ArraySize(pAuxDb->aSession));
+ return DCR_Error;
+ }
+ pSession = &pAuxDb->aSession[pAuxDb->nSession];
+ rc = sqlite3session_create(DBX(p), azCmd[1], &pSession->p);
+ if( rc ){
+ *pzErr = smprintf("Cannot open session: error code=%d\n", rc);
+ return DCR_Error;
+ }
+ pSession->nFilter = 0;
+ sqlite3session_table_filter(pSession->p, session_filter, pSession);
+ pAuxDb->nSession++;
+ zName = smprintf("%s", zName);
+ shell_check_ooms(zName);
+ pSession->zName = zName;
+ }else{
- /* 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 no command name matches, show a syntax error */
+ session_syntax_error:
+ showHelp(out, "session", p);
+ return DCR_CmdErred;
+ }
+ return DCR_Ok|(rc!=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);
-#ifndef SQLITE_SHELL_FIDDLE
- if( p->bSafeMode
- && p->openMode!=SHELL_OPEN_HEXDB
- && zFN
- && cli_strcmp(zFN,":memory:")!=0
- ){
- failIfSafeMode(p, "cannot open disk-based database files in safe mode");
- }
-#else
- /* WASM mode has its own sandboxed pseudo-filesystem. */
-#endif
- 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
+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 */
+ RESOURCE_MARK(mark);
-#ifndef SQLITE_SHELL_FIDDLE
- if( (c=='o'
- && (cli_strncmp(azArg[0], "output", n)==0
- || cli_strncmp(azArg[0], "once", n)==0))
- || (c=='e' && n==5 && cli_strcmp(azArg[0],"excel")==0)
- ){
- char *zFile = 0;
- int bTxtMode = 0;
- int i;
- int eMode = 0;
- int bOnce = 0; /* 0: .output, 1: .once, 2: .excel */
- unsigned char zBOM[4]; /* Byte-order mark to using if --bom is present */
-
- zBOM[0] = 0;
- failIfSafeMode(p, "cannot run .%s in safe mode", azArg[0]);
- if( c=='e' ){
- eMode = 'x';
- bOnce = 2;
- }else if( cli_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( cli_strcmp(z,"-bom")==0 ){
- zBOM[0] = 0xef;
- zBOM[1] = 0xbb;
- zBOM[2] = 0xbf;
- zBOM[3] = 0;
- }else if( c!='e' && cli_strcmp(z,"-x")==0 ){
- eMode = 'x'; /* spreadsheet */
- }else if( c!='e' && cli_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;
- }
- }
- if( zFile==0 ){
- zFile = sqlite3_mprintf("stdout");
- }
- if( bOnce ){
- p->outCount = 2;
+ open_db(p, 0);
+ for(i=1; i<nArg; i++){
+ const char *z = azArg[i];
+ if( z[0]=='-' ){
+ z++;
+ if( z[0]=='-' ) z++;
+ if( cli_strcmp(z,"schema")==0 ){
+ bSchema = 1;
+ }else
+ if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0
+ || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0
+ ){
+ iSize = atoi(&z[5]);
+ }else
+ if( cli_strcmp(z,"debug")==0 ){
+ bDebug = 1;
+ }else
+ {
+ *pzErr = smprintf("Unknown option \"%s\" on \"%s\"\n",
+ azArg[i], azArg[0]);
+ return DCR_Unknown|i;
+ }
+ }else if( zLike ){
+ return DCR_TooMany;
}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);
+ zLike = z;
+ bSeparate = 1;
+ if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 1;
}
-#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;
+ }
+ if( bSchema ){
+ zSql = "SELECT lower(name) AS tname 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) AS tname FROM sqlite_schema"
+ " WHERE type='table' AND coalesce(rootpage,0)>1"
+ " AND name NOT LIKE 'sqlite_%'"
+ " ORDER BY 1 collate nocase";
+ }
+ s3_prepare_v2_noom(DBX(p), zSql, -1, &pStmt, 0);
+ stmt_ptr_holder(&pStmt); /* +1 */
+ initText(&sQuery);
+ text_ref_holder(&sQuery); /* +2 */
+ initText(&sSql);
+ text_ref_holder(&sSql); /* +3 */
+ appendText(&sSql, "WITH [sha3sum$query](a,b) AS(",0);
+ zSep = "VALUES(";
+ while( SQLITE_ROW==s3_step_noom(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( cli_strncmp(zTab, "sqlite_",7)!=0 ){
+ appendText(&sQuery,"SELECT * FROM ", 0);
+ appendText(&sQuery,zTab,'"');
+ appendText(&sQuery," NOT INDEXED;", 0);
+ }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){
+ appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
+ " ORDER BY name;", 0);
+ }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){
+ appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
+ " ORDER BY name;", 0);
+ }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){
+ appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
+ " ORDER BY tbl,idx;", 0);
+ }else if( cli_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 = "),(";
+ }
+ if( bSeparate ){
+ zSql = smprintf(
+ "%s))"
+ " SELECT lower(hex(sha3_query(a,%d))) AS hash, b AS label"
+ " FROM [sha3sum$query]",
+ sSql.z, iSize);
+ }else{
+ zSql = smprintf(
+ "%s))"
+ " SELECT lower(hex(sha3_query(group_concat(a,''),%d))) AS hash"
+ " FROM [sha3sum$query]",
+ sSql.z, iSize);
+ }
+ release_holders(mark);
+ shell_check_ooms(zSql);
+ sstr_holder(zSql); /* +1 */
+ if( bDebug ){
+ utf8_printf(ISS(p)->out, "%s\n", zSql);
+ }else{
+ shell_exec(p, zSql, 0);
+ }
+ release_holder(); /* 0 */
+#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE)
+ {
+ int lrc;
+ char *zRevText = /* Query for reversible to-blob-to-text check */
+ "SELECT lower(name) as tname FROM sqlite_schema\n"
+ "WHERE type='table' AND coalesce(rootpage,0)>1\n"
+ "AND name NOT LIKE 'sqlite_%%'%s\n"
+ "ORDER BY 1 collate nocase";
+ zRevText = smprintf(zRevText, zLike? " AND name LIKE $tspec" : "");
+ zRevText = smprintf(
+ /* lower-case query is first run, producing upper-case query. */
+ "with tabcols as materialized(\n"
+ "select tname, cname\n"
+ "from ("
+ " select ss.tname as tname, ti.name as cname\n"
+ " from (%z) ss\n inner join pragma_table_info(tname) ti))\n"
+ "select 'SELECT total(bad_text_count) AS bad_text_count\n"
+ "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n"
+ " from (select 'SELECT COUNT(*) AS bad_text_count\n"
+ "FROM '||tname||' WHERE '\n"
+ "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n"
+ "|| ' AND typeof('||cname||')=''text'' ',\n"
+ "' OR ') as query, tname from tabcols group by tname)"
+ , zRevText);
+ if( bDebug ) utf8_printf(ISS(p)->out, "%s\n", zRevText);
+ lrc = s3_prep_noom_free(DBX(p), &zRevText, &pStmt);
+ if( lrc!=SQLITE_OK ){
+ RESOURCE_FREE(mark);
+ return DCR_Error;
+ }
+ stmt_holder(pStmt);
+ if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC);
+ lrc = SQLITE_ROW==s3_step_noom(pStmt);
+ if( lrc ){
+ const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
+ sqlite3_stmt *pCheckStmt;
+ lrc = s3_prepare_v2_noom(DBX(p), zGenQuery, -1, &pCheckStmt, 0);
+ if( bDebug ) utf8_printf(ISS(p)->out, "%s\n", zGenQuery);
+ stmt_holder(pCheckStmt);
+ if( SQLITE_OK!=lrc ){
+ RESOURCE_FREE(mark);
+ return DCR_Error;
}else{
- if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out);
- sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
- }
-#endif
- }else{
- p->out = output_file_open(zFile, bTxtMode);
- if( p->out==0 ){
- if( cli_strcmp(zFile,"off")!=0 ){
- utf8_printf(stderr,"Error: cannot write to \"%s\"\n", zFile);
+ if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){
+ double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
+ if( countIrreversible>0 ){
+ int sz = (int)(countIrreversible + 0.5);
+ utf8_printf(stderr,
+ "Digest includes %d invalidly encoded text field%s.\n",
+ sz, (sz>1)? "s": "");
+ }
}
- p->out = stdout;
- rc = 1;
- } else {
- if( zBOM[0] ) fwrite(zBOM, 1, 3, p->out);
- sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", zFile);
}
}
- sqlite3_free(zFile);
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ }
+#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */
+ RESOURCE_FREE(mark);
+ return DCR_Ok;
+}
- if( c=='p' && n>=3 && cli_strncmp(azArg[0], "parameter", n)==0 ){
- open_db(p,0);
- if( nArg<=1 ) goto parameter_syntax_error;
+/*****************
+ * The .selftest* and .show commands
+ */
+CONDITION_COMMAND( selftest_bool defined(SQLITE_DEBUG) );
+CONDITION_COMMAND( selftest_int defined(SQLITE_DEBUG) );
+COLLECT_HELP_TEXT[
+ ",selftest ?OPTIONS? Run tests defined in the SELFTEST table",
+ " Options:",
+ " --init Create a new SELFTEST table",
+ " -v Verbose output",
+ ",selftest_bool ?ARGS? Show boolean values of ARGS as flag tokens",
+ ",selftest_int ?ARGS? Show integer values of ARGS as integer tokens",
+ ".show Show the current values for various settings",
+];
- /* .parameter clear
- ** Clear all bind parameters by dropping the TEMP table that holds them.
- */
- if( nArg==2 && cli_strcmp(azArg[1],"clear")==0 ){
- sqlite3_exec(p->db, "DROP TABLE IF EXISTS temp.sqlite_parameters;",
- 0, 0, 0);
- }else
+DISPATCHABLE_COMMAND( selftest_bool 10 0 0 ){
+ int i, v;
+ for(i=1; i<nArg; i++){
+ v = booleanValue(azArg[i]);
+ utf8_printf(ISS(p)->out, "%s: %d 0x%x\n", azArg[i], v, v);
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( selftest_int 10 0 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(ISS(p)->out, "%s", zBuf);
+ }
+ return DCR_Ok;
+}
- /* .parameter list
- ** List all bind parameters.
- */
- if( nArg==2 && cli_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
+DISPATCHABLE_COMMAND( selftest 4 0 0 ){
+ int rc = 0;
+ ShellInState *psi = ISS(p);
+ 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 */
+ RESOURCE_MARK(mark);
+ ShellText str = {0}; /* Answer for a query */
+ sqlite3_stmt *pStmt = 0; /* Query against the SELFTEST table */
- /* .parameter init
- ** Make sure the TEMP table used to hold bind parameters exists.
- ** Create it if necessary.
- */
- if( nArg==2 && cli_strcmp(azArg[1],"init")==0 ){
- bind_table_init(p);
+ for(i=1; i<nArg; i++){
+ const char *z = azArg[i];
+ if( z[0]=='-' && z[1]=='-' ) z++;
+ if( cli_strcmp(z,"-init")==0 ){
+ bIsInit = 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 && cli_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;
+ if( cli_strcmp(z,"-v")==0 ){
+ bVerbose++;
+ }else
+ {
+ *pzErr = smprintf
+ ("Unknown option \"%s\" on \"%s\"\n"
+ "Should be one of: --init -v\n", azArg[i], azArg[0]);
+ return DCR_ArgWrong;
+ }
+ }
+ open_db(p,0);
+ if( sqlite3_table_column_metadata(DBX(p),"main","selftest",0,0,0,0,0,0)
+ != SQLITE_OK ){
+ bSelftestExists = 0;
+ }else{
+ bSelftestExists = 1;
+ }
+ if( bIsInit ){
+ createSelftestTable(ISS(p));
+ bSelftestExists = 1;
+ }
+ initText(&str);
+ text_ref_holder(&str);
+ appendText(&str, "x", 0);
+ stmt_ptr_holder(&pStmt);
+ for(k=bSelftestExists; k>=0; k--){
+ if( k==1 ){
+ rc = s3_prepare_v2_noom(DBX(p),
+ "SELECT tno,op,cmd,ans FROM selftest ORDER BY tno",
+ -1, &pStmt, 0);
+ }else{
+ rc = s3_prepare_v2_noom(DBX(p),
+ "VALUES(0,'memo','Missing SELFTEST table - default checks only',''),"
+ " (1,'run','PRAGMA integrity_check','ok')",
+ -1, &pStmt, 0);
+ }
+ if( rc ){
+ *pzErr = smprintf("Error querying the selftest table\n");
+ RESOURCE_FREE(mark);
+ return DCR_Error;
+ }
+ for(i=1; s3_step_noom(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 ){
+ /* This unusually directed output is for test purposes. */
+ fprintf(STD_OUT, "%d: %s %s\n", tno, zOp, zSql);
+ }
+ if( cli_strcmp(zOp,"memo")==0 ){
+ utf8_printf(psi->out, "%s\n", zSql);
+ }else if( cli_strcmp(zOp,"run")==0 ){
+ char *zErrMsg = 0;
+ sstr_ptr_holder(&zErrMsg);
+ str.n = 0;
+ str.z[0] = 0;
+ rc = sqlite3_exec(DBX(p), zSql, captureOutputCallback, &str, &zErrMsg);
+ nTest++;
+ if( bVerbose ){
+ utf8_printf(psi->out, "Result: %s\n", str.z);
+ }
+ if( rc || zErrMsg ){
+ nErr++;
+ rc = 1;
+ utf8_printf(psi->out, "%d: error-code-%d: %s\n", tno, rc,
+ (zErrMsg)? zErrMsg : "");
+ }else if( cli_strcmp(zAns,str.z)!=0 ){
+ nErr++;
rc = 1;
+ utf8_printf(psi->out, "%d: Expected: [%s]\n", tno, zAns);
+ utf8_printf(psi->out, "%d: Got: [%s]\n", tno, str.z);
}
+ release_holder();
+ }else{
+ *pzErr = smprintf
+ ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
+ rc = 1;
+ break;
}
- sqlite3_step(pStmt);
- sqlite3_finalize(pStmt);
- }else
+ } /* End loop over rows of content from SELFTEST */
+ } /* End loop over k */
+ RESOURCE_FREE(mark);
+ utf8_printf(psi->out, "%d errors out of %d tests\n", nErr, nTest);
+ return rc > 0;
+}
- /* .parameter unset NAME
- ** Remove the NAME binding from the parameter binding table, if it
- ** exists.
- */
- if( nArg==3 && cli_strcmp(azArg[1],"unset")==0 ){
- char *zSql = sqlite3_mprintf(
- "DELETE FROM temp.sqlite_parameters WHERE key=%Q", azArg[2]);
- shell_check_oom(zSql);
- sqlite3_exec(p->db, zSql, 0, 0, 0);
- sqlite3_free(zSql);
- }else
- /* If no command name matches, show a syntax error */
- parameter_syntax_error:
- showHelp(p->out, "parameter");
- }else
+/*****************
+ * The .shell and .system commands
+ */
+#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
+# define SHELLOUT_ENABLE 1
+#else
+# define SHELLOUT_ENABLE 0
+#endif
+CONDITION_COMMAND( shell SHELLOUT_ENABLE );
+CONDITION_COMMAND( system SHELLOUT_ENABLE );
+COLLECT_HELP_TEXT[
+ ".shell CMD ARGS... Run CMD ARGS... in a system shell",
+ ".system CMD ARGS... Run CMD ARGS... in a system shell",
+];
- if( c=='p' && n>=3 && cli_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]);
+#if SHELLOUT_ENABLE
+static DotCmdRC shellOut(char *azArg[], int nArg,
+ ShellExState *psx, char **pzErr){
+ char *zCmd = 0;
+ int i, x;
+ if( ISS(psx)->bSafeMode ) return DCR_AbortError;
+ zCmd = smprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
+ for(i=2; i<nArg; i++){
+ zCmd = smprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
+ zCmd, azArg[i]);
+ }
+ shell_check_ooms(zCmd);
+ sstr_holder(zCmd);
+ x = system(zCmd);
+ release_holder();
+ if( x ) raw_printf(STD_ERR, "%s command returns %d\n", azArg[0], x);
+ return DCR_Ok;
+}
+#endif
+DISPATCHABLE_COMMAND( shell ? 2 0 ){
+ return shellOut(azArg, nArg, p, pzErr);
+}
+
+DISPATCHABLE_COMMAND( system ? 2 0 ){
+ return shellOut(azArg, nArg, p, pzErr);
+}
+
+/*****************
+ * The .shxload and .shxopts commands
+ */
+CONDITION_COMMAND( shxload (SHELL_DYNAMIC_EXTENSION)!=0 );
+CONDITION_COMMAND( shxopts (SHELL_EXTENSIONS)!=0 );
+COLLECT_HELP_TEXT[
+ ".shxload FILE ?OPTIONS? Load a CLI shell extension library",
+ " The first option may name the init function to be called upon load.",
+ " Otherwise, its name is derived from FILE. Either way, the entry point"
+ " \"sqlite_NAME_init\" is called. All options after \"--\" are passed to",
+ " the extension's init function in the ShellExtensionLink struct.",
+ ".shxopts ?SIGNED_OPTS? Show or alter shell extension options",
+ " Run without arguments to see their self-descriptive names",
+];
+
+DISPATCHABLE_COMMAND( shxload 4 2 0 ){
+ const char *zFile = 0, *zProc = 0;
+ int ai = 1, rc;
+ char **pzExtArgs = 0;
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ while( ai<nArg ){
+ const char *zA = azArg[ai++];
+ if( cli_strcmp(zA, "--")==0 ){
+ pzExtArgs = azArg + ai;
+ break;
}
- raw_printf(p->out, "\n");
- }else
+ if( zFile==0 ) zFile = zA;
+ else if( zProc==0 ) zProc = zA;
+ }
+ if( zFile==0 ) return DCR_Missing;
+ if( pzExtArgs==0 ) pzExtArgs = azArg + ai;
+ rc = load_shell_extension(p, zFile, zProc, pzErr, nArg-ai, pzExtArgs);
+ return DCR_Ok|(rc!=SQLITE_OK);
+}
-#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
- if( c=='p' && n>=3 && cli_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( cli_strcmp(z,"quiet")==0 || cli_strcmp(z,"q")==0 ){
- p->flgProgress |= SHELL_PROGRESS_QUIET;
- continue;
- }
- if( cli_strcmp(z,"reset")==0 ){
- p->flgProgress |= SHELL_PROGRESS_RESET;
- continue;
- }
- if( cli_strcmp(z,"once")==0 ){
- p->flgProgress |= SHELL_PROGRESS_ONCE;
- continue;
- }
- if( cli_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;
+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;
+ ShellInState *psi = ISS(p);
+ 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( cli_strcmp(azArg[ia]+1, shopts[io].name)==0 ){
+ if( cs=='+' ) psi->bExtendedDotCmds |= shopts[io].mask;
+ else psi->bExtendedDotCmds &= ~shopts[io].mask;
+ break;
}
- utf8_printf(stderr, "Error: unknown option: \"%s\"\n", azArg[i]);
- rc = 1;
- goto meta_command_exit;
- }else{
- nn = (int)integerValue(z);
+ }
+ if( io==ArraySize(shopts) ){
+ zAbout = azArg[ia];
+ zMoan = "is not a recognized option name";
+ goto moan_error;
}
}
- open_db(p, 0);
- sqlite3_progress_handler(p->db, nn, progress_handler, p);
- }else
-#endif /* SQLITE_OMIT_PROGRESS_CALLBACK */
-
- if( c=='p' && cli_strncmp(azArg[0], "prompt", n)==0 ){
- if( nArg >= 2) {
- shell_strncpy(mainPrompt,azArg[1],(int)ArraySize(mainPrompt)-1);
- }
- if( nArg >= 3) {
- shell_strncpy(continuePrompt,azArg[2],(int)ArraySize(continuePrompt)-1);
- }
- }else
+ }else{
+ raw_printf(psi->out,
+ " name value \"-shxopts set\"\n"
+ " -------- ----- ---------------\n");
+ for( io=0; io<ArraySize(shopts); ++io ){
+ unsigned m = shopts[io].mask;
+ unsigned v = ((psi->bExtendedDotCmds & m) == m)? 1 : 0;
+ raw_printf(psi->out,
+ " %9s %2d \"-shxopts 0x%02X\"\n",
+ shopts[io].name, v, m);
+ }
+ }
+ return DCR_Ok;
+ moan_error:
+ *pzErr = smprintf("Error: %s %s\n", zAbout, zMoan);
+ return DCR_CmdErred;
+}
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='q' && cli_strncmp(azArg[0], "quit", n)==0 ){
- rc = 2;
- }else
-#endif
+DISPATCHABLE_COMMAND( show ? 1 1 ){
+ static const char *azBool[] = { "off", "on", "trigger", "full"};
+ const char *zOut;
+ ShellInState *psi = ISS(p);
+ FILE *out = psi->out;
+ int i;
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='r' && n>=3 && cli_strncmp(azArg[0], "read", n)==0 ){
- FILE *inSaved = p->in;
- int savedLineno = p->lineno;
- failIfSafeMode(p, "cannot run .read in safe mode");
- if( nArg!=2 ){
- raw_printf(stderr, "Usage: .read FILE\n");
- rc = 1;
- goto meta_command_exit;
+ utf8_printf(out, "%12.12s: %s\n","echo",
+ azBool[ShellHasFlag(p, SHFLG_Echo)]);
+ utf8_printf(out, "%12.12s: %s\n","eqp", azBool[psi->autoEQP&3]);
+ utf8_printf(out, "%12.12s: %s\n","explain",
+ psi->mode==MODE_Explain
+ ? "on" : psi->autoExplain ? "auto" : "off");
+ utf8_printf(out,"%12.12s: %s\n","headers", azBool[psi->showHeader!=0]);
+#if SHELL_DATAIO_EXT
+ {
+ char *zTell = 0;
+ int mrc;
+ zOut = psi->pActiveExporter->pMethods->name(psi->pActiveExporter);
+ mrc = psi->pActiveExporter->pMethods->config(psi->pActiveExporter,0,&zTell);
+ if( zTell!=0 ){
+ utf8_printf(out, "%12.12s: %s %s\n", "mode", zOut, zTell);
+ }else{
+ utf8_printf(out, "%12.12s: %s\n", "mode", zOut);
}
- 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;
+ sqlite3_free(zTell);
+ }
#else
- p->in = popen(azArg[1]+1, "r");
- if( p->in==0 ){
- utf8_printf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
- rc = 1;
- }else{
- rc = process_input(p);
- pclose(p->in);
- }
+ zOut = modeDescr[psi->mode].zModeName;
+ if( MODE_IS_COLUMNAR(psi->mode) ){
+ utf8_printf(out, "%12.12s: %s --wrap %d --wordwrap %s --%squote\n", "mode",
+ zOut, psi->cmOpts.iWrap,
+ psi->cmOpts.bWordWrap ? "on" : "off",
+ psi->cmOpts.bQuote ? "" : "no");
+ }else{
+ utf8_printf(out, "%12.12s: %s\n","mode", zOut);
+ }
#endif
- }else if( (p->in = openChrSource(azArg[1]))==0 ){
- utf8_printf(stderr,"Error: cannot open \"%s\"\n", azArg[1]);
- rc = 1;
+ utf8_printf(out, "%12.12s: ", "nullvalue");
+ output_c_string(out, psi->nullValue);
+ raw_printf(out, "\n");
+ utf8_printf(out,"%12.12s: %s\n","output",
+ strlen30(psi->outfile) ? psi->outfile : "stdout");
+ utf8_printf(out,"%12.12s: ", "colseparator");
+ output_c_string(out, psi->colSeparator);
+ raw_printf(out, "\n");
+ utf8_printf(out,"%12.12s: ", "rowseparator");
+ output_c_string(out, psi->rowSeparator);
+ raw_printf(out, "\n");
+ switch( psi->statsOn ){
+ case 0: zOut = "off"; break;
+ default: zOut = "on"; break;
+ case 2: zOut = "stmt"; break;
+ case 3: zOut = "vmstep"; break;
+ }
+ utf8_printf(out, "%12.12s: %s\n","stats", zOut);
+ utf8_printf(out, "%12.12s: ", "width");
+ for (i=0;i<p->numWidths;i++) {
+ raw_printf(out, "%d ", p->pSpecWidths[i]);
+ }
+ raw_printf(out, "\n");
+ utf8_printf(out, "%12.12s: %s\n", "filename",
+ psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : "");
+ return DCR_Ok;
+}
+
+/*****************
+ * The .stats command
+ */
+COLLECT_HELP_TEXT[
+ ".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",
+];
+DISPATCHABLE_COMMAND( stats ? 0 0 ){
+ ShellInState *psi = ISS(p);
+ if( nArg==2 ){
+ if( cli_strcmp(azArg[1],"stmt")==0 ){
+ psi->statsOn = 2;
+ }else if( cli_strcmp(azArg[1],"vmstep")==0 ){
+ psi->statsOn = 3;
}else{
- rc = process_input(p);
- fclose(p->in);
+ psi->statsOn = (u8)booleanValue(azArg[1]);
}
- p->in = inSaved;
- p->lineno = savedLineno;
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ }else if( nArg==1 ){
+ display_stats(DBX(p), psi, 0);
+ }else{
+ *pzErr = smprintf("Usage: .stats ?on|off|stmt|vmstep?\n");
+ return DCR_SayUsage;
+ }
+ return DCR_Ok;
+}
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='r' && n>=3 && cli_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];
+/*****************
+ * The .tables, .views, .indices and .indexes commands
+ * 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, ShellExState *p,
+ char **pzErr, char ot){
+ int rc;
+ sqlite3_stmt *pStmt = 0;
+ ShellText s = {0};
+ char **azResult = 0;
+ AnyResourceHolder arh = { 0, (GenericFreer)freeNameList };
+ int nRow, nAlloc;
+ int ii;
+ RESOURCE_MARK(mark);
+
+ initText(&s);
+ text_ref_holder(&s);
+ open_db(p, 0);
+ rc = sqlite3_prepare_v2(DBX(p), "PRAGMA database_list", -1, &pStmt, 0);
+ stmt_ptr_holder(&pStmt);
+ if( shell_check_nomem(rc) ){
+ db_err_bail:
+ RESOURCE_FREE(mark);
+ return shellDatabaseError(DBX(p));
+ }
+
+ 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 = smprintf("Usage: .indexes ?LIKE-PATTERN?\n");
+ RESOURCE_FREE(mark);
+ return DCR_SayUsage;
+ }
+ 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{
- 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;
+ 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_%'";
+ deliberate_fall_through;
+ case 'T':
+ zFilter = "'table','view'";
+ break;
+ default:
+ assert(0);
}
- 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;
+ 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);
+ pStmt = 0;
+ if( rc==SQLITE_OK ){
+ appendText(&s, " ORDER BY 1", 0);
+ rc = s3_prepare_v2_noom(DBX(p), s.z, -1, &pStmt, 0);
+ }
+ if( rc ) goto db_err_bail;
+
+ /* Run the SQL statement prepared by the above block. Store the results
+ ** as an array of nul-terminated strings in azResult[]. The 0th element
+ ** of azResult[] is not used so that freeNameList() can free it. */
+ 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);
+ }
+ any_ref_holder(&arh);
+ while( s3_step_noom(pStmt)==SQLITE_ROW ){
+ if( nRow+2 > nAlloc ){
+ char **azNew;
+ int n2 = nAlloc*2 + 10;
+ azNew = sqlite3_realloc64(azResult, sizeof(azResult[0])*n2);
+ shell_check_ooms(azNew);
+ /* Keep the object usable by freeNameList at all times. */
+ memset(azNew+nAlloc, 0, (n2-nAlloc)*sizeof(azResult[0]));
+ nAlloc = n2;
+ arh.pAny = azNew;
+ azResult = azNew;
+ }
+ ++nRow;
+ azResult[nRow] = smprintf("%s", sqlite3_column_text(pStmt, 0));
+ shell_check_ooms(azResult[nRow]);
+ }
+ if( sqlite3_finalize(pStmt)!=SQLITE_OK ){
+ pStmt = 0;
+ goto db_err_bail;
+ }
+ pStmt = 0;
+ /* 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=1; i<=nRow; i++){
+ len = strlen30(azResult[i]);
+ if( len>maxlen ) maxlen = len;
}
- while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK
- || rc==SQLITE_BUSY ){
- if( rc==SQLITE_BUSY ){
- if( nTimeout++ >= 3 ) break;
- sqlite3_sleep(100);
+ nPrintCol = 80/(maxlen+2);
+ if( nPrintCol<1 ) nPrintCol = 1;
+ nPrintRow = (nRow + nPrintCol - 1)/nPrintCol;
+ for(i=1; i<=nPrintRow; i++){
+ for(j=i; j<=nRow; j+=nPrintRow){
+ char *zSp = j<=nPrintRow ? "" : " ";
+ utf8_printf(ISS(p)->out, "%s%-*s", zSp, maxlen,
+ azResult[j] ? azResult[j]:"");
}
+ raw_printf(ISS(p)->out, "\n");
}
- 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;
- }
- close_db(pSrc);
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ }
+ RESOURCE_FREE(mark);
+ return DCR_Ok;
+}
- if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){
- if( nArg==2 ){
- if( cli_strcmp(azArg[1], "est")==0 ){
- p->scanstatsOn = 2;
- }else{
- p->scanstatsOn = (u8)booleanValue(azArg[1]);
- }
- open_db(p, 0);
- sqlite3_db_config(
- p->db, SQLITE_DBCONFIG_STMT_SCANSTATUS, p->scanstatsOn, (int*)0
- );
-#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
- raw_printf(stderr, "Warning: .scanstats not available in this build.\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
- }else{
- raw_printf(stderr, "Usage: .scanstats on|off|est\n");
- rc = 1;
+];
+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 DCR_Unknown|1;
}
- }else
+ }
+#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');
+}
- if( c=='s' && cli_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;
+/*****************
+ * The .selecttrace, .treetrace and .wheretrace commands (undocumented)
+ */
+static DotCmdRC setTrace( char *azArg[], int nArg, ShellExState *psx, int ts ){
+ unsigned int x = nArg>1 ? (unsigned int)integerValue(azArg[1]) : ~0;
+ sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, ts, &x);
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( selecttrace 0 1 2 ){
+ return setTrace(azArg, nArg, p, 1);
+}
+DISPATCHABLE_COMMAND( treetrace 0 1 2 ){
+ return setTrace(azArg, nArg, p, 1);
+}
+DISPATCHABLE_COMMAND( wheretrace 0 1 2 ){
+ return setTrace(azArg, nArg, p, 3);
+}
- 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, 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);
- }else{
- rc = sqlite3_exec(p->db, sSelect.z, callback, &data, &zErrMsg);
- }
- freeText(&sSelect);
- }
- 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;
- }else{
- rc = 0;
- }
- }else
+/*****************
+ * The .testcase, .testctrl, .timeout, .timer and .trace commands
+ */
+CONDITION_COMMAND( testcase !defined(SQLITE_SHELL_FIDDLE) );
+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)",
+];
+
+/* Begin redirecting output to the file "testcase-out.txt" */
+DISPATCHABLE_COMMAND( testcase ? 0 0 ){
+ ShellInState *psi = ISS(p);
+ output_reset(psi);
+ psi->out = output_file_open("testcase-out.txt", 0);
+ if( psi->out==0 ){
+ raw_printf(STD_ERR, "Error: cannot open 'testcase-out.txt'\n");
+ }
+ if( nArg>=2 ){
+ sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "%s", azArg[1]);
+ }else{
+ sqlite3_snprintf(sizeof(psi->zTestcase), psi->zTestcase, "?");
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( testctrl ? 0 0 ){
+ FILE *out = ISS(p)->out;
+ 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;
+
+ if( !ShellHasFlag(p,SHFLG_TestingMode) ){
+ utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
+ "testctrl");
+ return DCR_Error;
+ }
+ open_db(p, 0);
+ zCmd = nArg>=2 ? azArg[1] : "help";
- if( (c=='s' && n==11 && cli_strncmp(azArg[0], "selecttrace", n)==0)
- || (c=='t' && n==9 && cli_strncmp(azArg[0], "treetrace", n)==0)
- ){
- unsigned int x = nArg>=2? (unsigned int)integerValue(azArg[1]) : 0xffffffff;
- sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 1, &x);
- }else
+ /* The argument can optionally begin with "-" or "--" */
+ if( zCmd[0]=='-' && zCmd[1] ){
+ zCmd++;
+ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+ }
-#if defined(SQLITE_ENABLE_SESSION)
- if( c=='s' && cli_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( cli_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;
- }
+ /* --help lists all test-controls */
+ if( cli_strcmp(zCmd,"help")==0 ){
+ utf8_printf(out, "Available test-controls:\n");
+ for(i=0; i<ArraySize(aCtrl); i++){
+ utf8_printf(out, " .testctrl %s %s\n",
+ aCtrl[i].zCtrlName, aCtrl[i].zUsage);
}
+ return DCR_CmdErred;
+ }
- /* .session attach TABLE
- ** Invoke the sqlite3session_attach() interface to attach a particular
- ** table so that it is never filtered.
- */
- if( cli_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");
+ /* 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( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
+ if( testctrl<0 ){
+ testctrl = aCtrl[i].ctrlCode;
+ iCtrl = i;
}else{
- rc = sqlite3session_attach(pSession->p, azCmd[1]);
- if( rc ){
- raw_printf(stderr, "ERROR: sqlite3session_attach() returns %d\n", rc);
- rc = 0;
- }
- }
- }else
+ *pzErr = smprintf
+ ("Error: ambiguous test-control: \"%s\"\n"
+ "Use \".testctrl --help\" for help\n", zCmd);
+ return DCR_ArgWrong;
+ }
+ }
+ }
+ 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
+ && failIfSafeMode(p,"line %d: \".testctrl %s\" "
+ "may not be used in safe mode\n",
+ ISS(p)->pInSource->lineno,
+ aCtrl[iCtrl].zCtrlName) ){
+ return DCR_Abort;
+ }else{
+ switch(testctrl){
- /* .session changeset FILE
- ** .session patchset FILE
- ** Write a changeset or patchset into a file. The file is overwritten.
- */
- if( cli_strcmp(azCmd[0],"changeset")==0
- || cli_strcmp(azCmd[0],"patchset")==0
- ){
- FILE *out = 0;
- failIfSafeMode(p, "cannot run \".session %s\" in safe mode", azCmd[0]);
- 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]);
- }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 ){
- printf("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);
- }
- sqlite3_free(pChng);
- fclose(out);
+ /* 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, DBX(p), opt);
+ isOk = 3;
}
- }else
+ break;
- /* .session close
- ** Close the identified session
- */
- if( cli_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];
+ /* 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;
}
- }else
+ break;
- /* .session enable ?BOOLEAN?
- ** Query or set the enable flag
- */
- if( cli_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);
+ /* 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;
}
- }else
+ break;
- /* .session filter GLOB ....
- ** Set a list of GLOB patterns of table names to be excluded.
- */
- if( cli_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_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 && cli_strcmp(azArg[2],"random")==0 ){
+ sqlite3_randomness(sizeof(ii),&ii);
+ fprintf(STD_OUT, "-- random seed: %d\n", 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);
+ if( nArg==3 ){
+ db = 0;
+ }else{
+ db = DBX(p);
+ /* Make sure the schema has been loaded */
+ sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
}
- pSession->nFilter = ii-1;
+ rc2 = sqlite3_test_control(testctrl, ii, db);
+ isOk = 3;
}
- }else
+ break;
- /* .session indirect ?BOOLEAN?
- ** Query or set the indirect flag
- */
- if( cli_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);
+ /* 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;
}
- }else
+ break;
- /* .session isempty
- ** Determine if the session is empty
- */
- if( cli_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_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;
}
- }else
+ break;
- /* .session list
- ** List all currently open sessions
- */
- if( cli_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
+ /* sqlite3_test_control(sqlite3*) */
+ case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
+ rc2 = sqlite3_test_control(testctrl, DBX(p));
+ isOk = 3;
+ break;
- /* .session open DB NAME
- ** Open a new session called NAME on the attached database DB.
- ** DB is normally "main".
- */
- if( cli_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( cli_strcmp(pAuxDb->aSession[i].zName,zName)==0 ){
- utf8_printf(stderr, "Session \"%s\" already exists\n", zName);
- goto meta_command_exit;
- }
+ case SQLITE_TESTCTRL_IMPOSTER:
+ if( nArg==5 ){
+ rc2 = sqlite3_test_control(testctrl, DBX(p),
+ azArg[2],
+ integerValue(azArg[3]),
+ integerValue(azArg[4]));
+ isOk = 3;
}
- 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;
+ break;
+
+ case SQLITE_TESTCTRL_SEEK_COUNT: {
+ u64 x = 0;
+ rc2 = sqlite3_test_control(testctrl, DBX(p), &x);
+ utf8_printf(out, "%llu\n", x);
+ isOk = 3;
+ break;
+ }
+
+#ifdef YYCOVERAGE
+ case SQLITE_TESTCTRL_PARSER_COVERAGE: {
+ if( nArg==2 ){
+ sqlite3_test_control(testctrl, out);
+ isOk = 3;
}
- 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");
- }else
+ break;
+ }
#endif
-
#ifdef SQLITE_DEBUG
- /* Undocumented commands for internal testing. Subject to change
- ** without notice. */
- if( c=='s' && n>=10 && cli_strncmp(azArg[0], "selftest-", 9)==0 ){
- if( cli_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);
+ 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(out, " ");
+ utf8_printf(out, "%d: %d", id, val);
+ id++;
+ }
+ if( id>1 ) utf8_printf(out, "\n");
+ isOk = 3;
}
+ break;
}
- if( cli_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);
- }
+#endif
}
- }else
+ }
+ if( isOk==0 && iCtrl>=0 ){
+ utf8_printf(out, "Usage: .testctrl %s %s\n", zCmd,aCtrl[iCtrl].zUsage);
+ return DCR_CmdErred;
+ }else if( isOk==1 ){
+ raw_printf(out, "%d\n", rc2);
+ }else if( isOk==2 ){
+ raw_printf(out, "0x%08x\n", rc2);
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( timeout 4 1 2 ){
+ open_db(p, 0);
+ sqlite3_busy_timeout(DBX(p), nArg>=2 ? (int)integerValue(azArg[1]) : 0);
+ return DCR_Ok;
+}
+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 DCR_Ok;
+}
+DISPATCHABLE_COMMAND( trace ? 0 0 ){
+ ShellInState *psi = ISS(p);
+ 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") ){
+ psi->eTraceType = SHELL_TRACE_EXPANDED;
+ }
+#ifdef SQLITE_ENABLE_NORMALIZE
+ else if( optionMatch(z, "normalized") ){
+ psi->eTraceType = SHELL_TRACE_NORMALIZED;
+ }
#endif
-
- if( c=='s' && n>=4 && cli_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 */
-
- open_db(p,0);
- for(i=1; i<nArg; i++){
- const char *z = azArg[i];
- if( z[0]=='-' && z[1]=='-' ) z++;
- if( cli_strcmp(z,"-init")==0 ){
- bIsInit = 1;
- }else
- if( cli_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;
+ else if( optionMatch(z, "plain") ){
+ psi->eTraceType = SHELL_TRACE_PLAIN;
}
- }
- 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);
+ else if( optionMatch(z, "profile") ){
+ mType |= SQLITE_TRACE_PROFILE;
}
- 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( cli_strcmp(zOp,"memo")==0 ){
- utf8_printf(p->out, "%s\n", zSql);
- }else
- if( cli_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( cli_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;
- }
- } /* 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
+ 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 {
+ return DCR_Unknown|jj;
+ }
+ }else{
+ output_file_close(psi->traceOut);
+ psi->traceOut = output_file_open(z, 0);
+ }
+ }
+ if( psi->traceOut==0 ){
+ sqlite3_trace_v2(DBX(p), 0, 0, 0);
+ }else{
+ if( mType==0 ) mType = SQLITE_TRACE_STMT;
+ sqlite3_trace_v2(DBX(p), mType, sql_trace_callback, psi);
+ }
+ return DCR_Ok;
+}
- if( c=='s' && cli_strncmp(azArg[0], "separator", n)==0 ){
- if( nArg<2 || nArg>3 ){
- raw_printf(stderr, "Usage: .separator COL ?ROW?\n");
- rc = 1;
+/*****************
+ * The .unknown command (undocumented)
+ */
+COLLECT_HELP_TEXT[
+ ",unknown ?ARGS? Handle attempt to use an unknown dot command",
+ " The invocation dispatcher calls this after replacing azArg[0] with the",
+ " mystery command name, leaving remaining arguments as originally passed.",
+ " An extension may override this to provide new dot commands dynamically.",
+ " This name and operation were inspired by a similar feature of TCL.",
+];
+DISPATCHABLE_COMMAND( unknown ? 1 0 ){
+ /* Dispatcher will call this for dot commands it cannot find. */
+ return DCR_Unknown|0;
+}
+
+/*****************
+ * The .unmodule command
+ */
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
+# define UNMODULE_ENABLE 1
+#else
+# define UNMODULE_ENABLE 0
+#endif
+CONDITION_COMMAND( unmodule UNMODULE_ENABLE );
+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 && cli_strncmp(zOpt, "-allexcept",lenOpt)==0 ){
+ assert( azArg[nArg]==0 );
+ sqlite3_drop_modules(DBX(p), nArg>2 ? (const char**)(azArg+2) : 0);
+ }else{
+ for(ii=1; ii<nArg; ii++){
+ sqlite3_create_module(DBX(p), azArg[ii], 0, 0);
}
- if( nArg>=2 ){
- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator,
- "%.*s", (int)ArraySize(p->colSeparator)-1, azArg[1]);
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * 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 = smprintf(usage);
+ return DCR_SayUsage;
+ }
+ open_db(p, 0);
+ if( cli_strcmp(azArg[1],"login")==0 ){
+ if( nArg!=4 ){
+ goto teach_fail;
}
- if( nArg>=3 ){
- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator,
- "%.*s", (int)ArraySize(p->rowSeparator)-1, azArg[2]);
+ rc = sqlite3_user_authenticate(DBX(p), azArg[2], azArg[3],
+ strlen30(azArg[3]));
+ if( rc ){
+ *pzErr = smprintf(0,"Authentication failed for user %s\n", azArg[2]);
+ return DCR_Error;
}
- }else
-
- if( c=='s' && n>=4 && cli_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( cli_strcmp(z,"schema")==0 ){
- bSchema = 1;
- }else
- if( cli_strcmp(z,"sha3-224")==0 || cli_strcmp(z,"sha3-256")==0
- || cli_strcmp(z,"sha3-384")==0 || cli_strcmp(z,"sha3-512")==0
- ){
- iSize = atoi(&z[5]);
- }else
- if( cli_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]);
- rc = 1;
- goto meta_command_exit;
- }
- }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;
- }
+ }else if( cli_strcmp(azArg[1],"add")==0 ){
+ if( nArg!=5 ){
+ goto teach_fail;
}
- if( bSchema ){
- zSql = "SELECT lower(name) as tname 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) as tname 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( cli_strncmp(zTab, "sqlite_",7)!=0 ){
- appendText(&sQuery,"SELECT * FROM ", 0);
- appendText(&sQuery,zTab,'"');
- appendText(&sQuery," NOT INDEXED;", 0);
- }else if( cli_strcmp(zTab, "sqlite_schema")==0 ){
- appendText(&sQuery,"SELECT type,name,tbl_name,sql FROM sqlite_schema"
- " ORDER BY name;", 0);
- }else if( cli_strcmp(zTab, "sqlite_sequence")==0 ){
- appendText(&sQuery,"SELECT name,seq FROM sqlite_sequence"
- " ORDER BY name;", 0);
- }else if( cli_strcmp(zTab, "sqlite_stat1")==0 ){
- appendText(&sQuery,"SELECT tbl,idx,stat FROM sqlite_stat1"
- " ORDER BY tbl,idx;", 0);
- }else if( cli_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 = "),(";
+ rc = sqlite3_user_add(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
+ booleanValue(azArg[4]));
+ if( rc ){
+ *pzErr = smprintf(0,"User-Add failed: %d\n", rc);
+ return DCR_Error;
}
- 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);
+ }else if( cli_strcmp(azArg[1],"edit")==0 ){
+ if( nArg!=5 ){
+ goto teach_fail;
}
-#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && !defined(SQLITE_OMIT_VIRTUALTABLE)
- {
- int lrc;
- char *zRevText = /* Query for reversible to-blob-to-text check */
- "SELECT lower(name) as tname FROM sqlite_schema\n"
- "WHERE type='table' AND coalesce(rootpage,0)>1\n"
- "AND name NOT LIKE 'sqlite_%%'%s\n"
- "ORDER BY 1 collate nocase";
- zRevText = sqlite3_mprintf(zRevText, zLike? " AND name LIKE $tspec" : "");
- zRevText = sqlite3_mprintf(
- /* lower-case query is first run, producing upper-case query. */
- "with tabcols as materialized(\n"
- "select tname, cname\n"
- "from ("
- " select ss.tname as tname, ti.name as cname\n"
- " from (%z) ss\n inner join pragma_table_info(tname) ti))\n"
- "select 'SELECT total(bad_text_count) AS bad_text_count\n"
- "FROM ('||group_concat(query, ' UNION ALL ')||')' as btc_query\n"
- " from (select 'SELECT COUNT(*) AS bad_text_count\n"
- "FROM '||tname||' WHERE '\n"
- "||group_concat('CAST(CAST('||cname||' AS BLOB) AS TEXT)<>'||cname\n"
- "|| ' AND typeof('||cname||')=''text'' ',\n"
- "' OR ') as query, tname from tabcols group by tname)"
- , zRevText);
- shell_check_oom(zRevText);
- if( bDebug ) utf8_printf(p->out, "%s\n", zRevText);
- lrc = sqlite3_prepare_v2(p->db, zRevText, -1, &pStmt, 0);
- if( lrc!=SQLITE_OK ){
- /* assert(lrc==SQLITE_NOMEM); // might also be SQLITE_ERROR if the
- ** user does cruel and unnatural things like ".limit expr_depth 0". */
- rc = 1;
- }else{
- if( zLike ) sqlite3_bind_text(pStmt,1,zLike,-1,SQLITE_STATIC);
- lrc = SQLITE_ROW==sqlite3_step(pStmt);
- if( lrc ){
- const char *zGenQuery = (char*)sqlite3_column_text(pStmt,0);
- sqlite3_stmt *pCheckStmt;
- lrc = sqlite3_prepare_v2(p->db, zGenQuery, -1, &pCheckStmt, 0);
- if( bDebug ) utf8_printf(p->out, "%s\n", zGenQuery);
- if( lrc!=SQLITE_OK ){
- rc = 1;
- }else{
- if( SQLITE_ROW==sqlite3_step(pCheckStmt) ){
- double countIrreversible = sqlite3_column_double(pCheckStmt, 0);
- if( countIrreversible>0 ){
- int sz = (int)(countIrreversible + 0.5);
- utf8_printf(stderr,
- "Digest includes %d invalidly encoded text field%s.\n",
- sz, (sz>1)? "s": "");
- }
- }
- sqlite3_finalize(pCheckStmt);
- }
- sqlite3_finalize(pStmt);
- }
- }
- if( rc ) utf8_printf(stderr, ".sha3sum failed.\n");
- sqlite3_free(zRevText);
+ rc = sqlite3_user_change(DBX(p), azArg[2], azArg[3], strlen30(azArg[3]),
+ booleanValue(azArg[4]));
+ if( rc ){
+ *pzErr = smprintf(0,"User-Edit failed: %d\n", rc);
+ return DCR_Error;
}
-#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */
- sqlite3_free(zSql);
- }else
-
-#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
- if( c=='s'
- && (cli_strncmp(azArg[0], "shell", n)==0
- || cli_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;
+ }else if( cli_strcmp(azArg[1],"delete")==0 ){
+ if( nArg!=3 ){
+ goto teach_fail;
}
- 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]);
+ rc = sqlite3_user_delete(DBX(p), azArg[2]);
+ if( rc ){
+ *pzErr = smprintf(0,"User-Delete failed: %d\n", rc);
+ return DCR_Error;
}
- 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) && !defined(SQLITE_SHELL_FIDDLE) */
+ }else{
+ goto teach_fail;
+ }
+ return DCR_Ok;
+}
- if( c=='s' && cli_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
+/*****************
+ * The .vars command
+ */
+COLLECT_HELP_TEXT[
+ ".vars ?OPTIONS? ... Manipulate and display shell variables",
+ " clear ?NAMES? Erase all or only given named variables",
+#ifndef SQLITE_NOHAVE_SYSTEM
+ " edit ?-e? NAME Use edit() to create or alter variable NAME",
+ " With a -e option, the edited value is evaluated as a SQL expression.",
+#endif
+ " list ?PATTERNS? List shell variables table values",
+ " Alternatively, to list just some or all names: ls ?PATTERNS?",
+ " load ?FILE? ?NAMES? Load some or all named variables from FILE",
+ " If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
+ " save ?FILE? ?NAMES? Save some or all named variables into FILE",
+ " If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb",
+ " set NAME VALUE Give shell variable NAME a value of VALUE",
+ " NAME must begin with a letter to be executable by .x, Other leading",
+ " characters have special uses. VALUE is the space-joined arguments.",
+ " unset ?NAMES? Remove named variables(s) from variables table",
+];
+DISPATCHABLE_COMMAND( vars 2 1 0 ){
+ DotCmdRC rv = DCR_Ok;
++ char *zErr = 0;
+ sqlite3 *dbs = p->dbShell;
+ const char *zCmd = (nArg>1)? azArg[1] : "ls";
+ int rc = 0;
+ int ncCmd = strlen30(zCmd);
- if( c=='s' && cli_strncmp(azArg[0], "stats", n)==0 ){
- if( nArg==2 ){
- if( cli_strcmp(azArg[1],"stmt")==0 ){
- p->statsOn = 2;
- }else if( cli_strcmp(azArg[1],"vmstep")==0 ){
- p->statsOn = 3;
- }else{
- p->statsOn = (u8)booleanValue(azArg[1]);
- }
- }else if( nArg==1 ){
- display_stats(p->db, p, 0);
- }else{
- raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
- rc = 1;
- }
- }else
+ if( *zCmd>'e' && ncCmd<2 ) return DCR_Ambiguous|1;
+#define SUBCMD(scn) (cli_strncmp(zCmd, scn, ncCmd)==0)
- if( (c=='t' && n>1 && cli_strncmp(azArg[0], "tables", n)==0)
- || (c=='i' && (cli_strncmp(azArg[0], "indices", n)==0
- || cli_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);
- }
+ /* This could be done lazily, but with more code. */
+ if( (dbs && ensure_shvars_table(dbs)!=SQLITE_OK) ){
+ return DCR_Error;
+ }else{
+ if( ensure_shell_db(p)!=SQLITE_OK ) return DCR_Error;
+ dbs = p->dbShell;
+ assert(dbs!=0);
+ if( ensure_shvars_table(dbs)!=SQLITE_OK ) return DCR_Error;
+ }
- 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);
- }
+ /* .vars clear and .vars unset ?NAMES?
+ ** Delete some or all key/value pairs from the shell variables table.
+ ** Without any arguments, clear deletes them all and unset does nothing.
+ */
+ if( SUBCMD("clear") || SUBCMD("unset") ){
+ if( (nArg>2 || zCmd[0]=='c') ){
+ sqlite3_str *sbZap = sqlite3_str_new(dbs);
+ char *zSql;
+ sqst_ptr_holder(&sbZap);
+ sqlite3_str_appendf
+ (sbZap, "DELETE FROM "SHVAR_TABLE_SNAME" WHERE key ");
+ append_in_clause(sbZap,
+ (const char **)&azArg[2], (const char **)&azArg[nArg]);
+ zSql = sqlite3_str_finish(sbZap);
+ drop_holder();
+ shell_check_ooms(zSql);
+ sstr_holder(zSql);
+ rc = sqlite3_exec(dbs, zSql, 0, 0, 0);
+ release_holder();
}
- 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]:"");
+#ifndef SQLITE_NOHAVE_SYSTEM
+ }else if( SUBCMD("edit") ){
+ ShellInState *psi = ISS(p);
+ int ia = 2;
+ int eval = 0;
+ int edSet;
+
+ if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){
+ utf8_printf(STD_ERR, "Error: "
+ ".vars edit can only be used interactively.\n");
+ return DCR_Error;
+ }
+ edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 );
+ if( edSet < 0 ) return DCR_Error;
+ else ia += edSet;
+ while( ia < nArg ){
+ ParamTableUse ptu;
+ char *zA = azArg[ia];
+ char cf = (zA[0]=='-')? zA[1] : 0;
+ if( cf!=0 && zA[2]==0 ){
+ ++ia;
+ switch( cf ){
+ case 'e': eval = 1; continue;
+ case 't': eval = 0; continue;
+ default:
+ utf8_printf(STD_ERR, "Error: bad .vars edit option: %s\n", zA);
+ return DCR_Error;
}
- raw_printf(p->out, "\n");
}
- rc = edit_one_kvalue(dbs, zA, eval, ptu, psi->zEditor);
+ ptu = classify_param_name(zA);
+ if( ptu!=PTU_Script ){
+ utf8_printf(STD_ERR,
+ "Error: %s cannot be a shell variable name.\n", zA);
+ return DCR_Error;
+ }
++ rc = edit_one_kvalue(dbs, zA, eval, ptu, psi->zEditor, &zErr);
+ ++ia;
++ if( zErr!=0 ){
++ utf8_printf(STD_ERR, "%s", zErr);
++ sqlite3_free(zErr);
++ zErr = 0;
++ }
+ if( rc!=0 ) return DCR_Error;
}
-
- for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
- sqlite3_free(azResult);
- }else
-
-#ifndef SQLITE_SHELL_FIDDLE
- /* Begin redirecting output to the file "testcase-out.txt" */
- if( c=='t' && cli_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");
- }
- if( nArg>=2 ){
- sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "%s", azArg[1]);
+#endif
+ }else if( SUBCMD("list") || SUBCMD("ls") ){
+ int nTailArgs = nArg - 1 - (nArg>1);
+ char **pzTailArgs = azArg + 1 + (nArg>1);
+ list_pov_entries(p, PTU_Script, zCmd[1]=='s', pzTailArgs, nTailArgs);
+ }else if( SUBCMD("load") ){
+ rc = kv_pairs_load(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
+ }else if( SUBCMD("save") ){
+ rc = kv_pairs_save(dbs, PTU_Script, (const char **)azArg+1, nArg-1);
+ }else if( SUBCMD("set") ){
+ ParamTableUse ptu;
+ if( nArg<4 ) return DCR_Missing;
+ ptu = classify_param_name(azArg[2]);
+ if( ptu!=PTU_Script ){
+ utf8_printf(STD_ERR,
+ "Error: %s is not a valid shell variable name.\n", azArg[2]);
+ rc = 1;
}else{
- sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
+ rc = shvar_set(dbs, azArg[2], &azArg[3], &azArg[nArg]);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(dbs));
+ rc = 1;
+ }
}
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ }else{
+ showHelp(ISS(p)->out, "vars", p);
+ return DCR_CmdErred;
+ }
+ return DCR_Ok | (rv!=0) | (rc!=0);
+#undef SUBCMD
+}
-#ifndef SQLITE_UNTESTABLE
- if( c=='t' && n>=8 && cli_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" },
-#ifdef YYCOVERAGE
- {"parser_coverage", SQLITE_TESTCTRL_PARSER_COVERAGE,0,"" },
+/*****************
+ * The .vfsinfo, .vfslist, .vfsname and .version commands
+ */
+COLLECT_HELP_TEXT[
+ ".version Show source, library and compiler versions",
+ ".vfsinfo ?AUX? Information about the top-level VFS",
+ ".vfslist List all available VFSes",
+ ".vfsname ?AUX? Print the name of the VFS stack",
+];
+DISPATCHABLE_COMMAND( version ? 1 1 ){
+ FILE *out = ISS(p)->out;
+ utf8_printf(out, "SQLite %s %s\n" /*extra-version-info*/,
+ sqlite3_libversion(), sqlite3_sourceid());
+#if SQLITE_HAVE_ZLIB
+ utf8_printf(out, "zlib version %s\n", zlibVersion());
#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;
-
- if( !ShellHasFlag(p,SHFLG_TestingMode) ){
- utf8_printf(stderr, ".%s unavailable without --unsafe-testing\n",
- "testctrl");
- 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(out, "clang-" CTIMEOPT_VAL(__clang_major__) "."
+ CTIMEOPT_VAL(__clang_minor__) "."
+ CTIMEOPT_VAL(__clang_patchlevel__) "\n");
+#elif defined(_MSC_VER)
+ utf8_printf(out, "msvc-" CTIMEOPT_VAL(_MSC_VER) "\n");
+#elif defined(__GNUC__) && defined(__VERSION__)
+ utf8_printf(out, "gcc-" __VERSION__ "\n");
+#endif
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( vfsinfo ? 1 2 ){
+ const char *zDbName = nArg==2 ? azArg[1] : "main";
+ sqlite3_vfs *pVfs = 0;
+ if( DBX(p) ){
+ sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFS_POINTER, &pVfs);
+ if( pVfs ){
+ FILE *out = ISS(p)->out;
+ utf8_printf(out, "vfs.zName = \"%s\"\n", pVfs->zName);
+ raw_printf(out, "vfs.iVersion = %d\n", pVfs->iVersion);
+ raw_printf(out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
+ raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
}
- open_db(p, 0);
- zCmd = nArg>=2 ? azArg[1] : "help";
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( vfslist ? 1 1 ){
+ sqlite3_vfs *pVfs;
+ sqlite3_vfs *pCurrent = 0;
+ FILE *out = ISS(p)->out;
+ if( DBX(p) ){
+ sqlite3_file_control(DBX(p), "main", SQLITE_FCNTL_VFS_POINTER, &pCurrent);
+ }
+ for(pVfs=sqlite3_vfs_find(0); pVfs; pVfs=pVfs->pNext){
+ utf8_printf(out, "vfs.zName = \"%s\"%s\n", pVfs->zName,
+ pVfs==pCurrent ? " <--- CURRENT" : "");
+ raw_printf(out, "vfs.iVersion = %d\n", pVfs->iVersion);
+ raw_printf(out, "vfs.szOsFile = %d\n", pVfs->szOsFile);
+ raw_printf(out, "vfs.mxPathname = %d\n", pVfs->mxPathname);
+ if( pVfs->pNext ){
+ raw_printf(out, "-----------------------------------\n");
+ }
+ }
+ return DCR_Ok;
+}
+DISPATCHABLE_COMMAND( vfsname ? 0 0 ){
+ const char *zDbName = nArg==2 ? azArg[1] : "main";
+ char *zVfsName = 0;
+ if( DBX(p) ){
+ sqlite3_file_control(DBX(p), zDbName, SQLITE_FCNTL_VFSNAME, &zVfsName);
+ if( zVfsName ){
+ utf8_printf(ISS(p)->out, "%s\n", zVfsName);
+ sqlite3_free(zVfsName);
+ }
+ }
+ return DCR_Ok;
+}
+
+/*****************
+ * The .width command
+ */
+static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths){
+ int j;
+ int *pSW = p->pSpecWidths;
+ p->numWidths = nWidths;
+ pSW = realloc(pSW, (nWidths+1)*sizeof(int)*2);;
+ shell_check_oomm(pSW);
+ p->pSpecWidths = pSW;
+ if( nWidths>0 ){
+ p->pHaveWidths = &p->pSpecWidths[nWidths];
+ for(j=0; j<nWidths; j++){
+ p->pSpecWidths[j] = (int)integerValue(azWidths[j]);
+ p->pHaveWidths[j] = 0;
+ }
+ }else p->pHaveWidths = p->pSpecWidths;
+}
+COLLECT_HELP_TEXT[
+ ".width NUM1 NUM2 ... Set minimum column widths for columnar output",
+ " Negative values right-justify",
+];
+DISPATCHABLE_COMMAND( width ? 1 0 ){
+ setColumnWidths(p, azArg+1, nArg-1);
+ return DCR_Ok;
+}
+
+/*****************
+ * The .x, .read and .eval commands
+ * These are together because they share some function and implementation.
+ */
+CONDITION_COMMAND(read !defined(SQLITE_SHELL_FIDDLE));
+COLLECT_HELP_TEXT[
+ ".eval ?ARGS? Process each ARG's content as shell input.",
+ ".read FILE Read input from FILE",
+ " If FILE begins with \"|\", it is a command that generates the input.",
+ ".x ?OBJS or FLAGS? ... Excecute content of objects as shell input",
+ " FLAGS can be any of {-k -s -f} specifying what subsequent arguments are.",
+ " Arguments after -k are keys in a key/value table kept by the shell DB,",
+ " for which the object content to be executed is the corresponding value.",
+ " Arguments after -f name either files (or pipes), which are to be read",
+ " and the content executed. Input pipe names begin with '|'; the rest is",
+ " an OS-shell command which can be run by the OS shell to produce output.",
+ " Arguments after -s are strings, content of which is to be executed.",
+ " The default in effect for arguments prior to any FLAG is -k .",
+ " Arguments are executed in order until one fails.",
+];
+
+/* Return an allocated string with trailing whitespace trimmed except
+ * for a trailing newline. If empty (or OOM), return 0. Otherwise, the
+ * caller must eventually pass the return to sqlite3_free().
+ */
+static char *zPrepForEval(const char *zVal, int ntc){
+ int ixNewline = 0;
+ char c;
+ while( ntc>0 && IsSpace(c = zVal[ntc-1]) ){
+ if( c=='\n' ) ixNewline = ntc-1;
+ --ntc;
+ }
+ if( ntc>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 = ixNewline>0;
+ return smprintf( "%.*s%s", ntc, zVal, &"\n"[nle] );
+ }else{
+ return 0;
+ }
+}
+
+/* Evaluate a string as input to the CLI.
+ * zName is the name to be given to the source for error reporting.
+ * Return usual dot command return codes as filtered by process_input().
+ * No provision is made for error emission because, presumably, that
+ * has been done by whatever dot commands or SQL execution is invoked.
+ */
+static DotCmdRC shellEvalText(char *zIn, const char *zName, ShellExState *psx){
+ DotCmdRC rv;
+ ShellInState *psi = ISS(psx);
+ InSource inRedir
+ = INSOURCE_STR_REDIR(zIn, zName, psi->pInSource);
+ AnyResourceHolder arh = { &psi->pInSource, (GenericFreer)finish_InSource };
+ psi->pInSource = &inRedir;
+ any_ref_holder(&arh);
+ rv = process_input(psi);
+ release_holder();
+ return rv;
+}
+
+DISPATCHABLE_COMMAND( eval 3 1 0 ){
+ DotCmdRC rv = DCR_Ok;
+ int ia = 1;
+ int nErrors = 0;
+ while( ia < nArg ){
+ char *zA = azArg[ia++];
+ int nc = strlen30(zA);
+ char *zSubmit = zPrepForEval(zA, nc);
+ if( zSubmit ){
+ char zName[] = "eval arg[999]";
+ sqlite3_snprintf(sizeof(zName), zName,"eval arg[%d]", ia-1);
+ rv = shellEvalText(zSubmit, zName, p);
+ sqlite3_free(zSubmit);
+ if( rv<DCR_ArgIxMask ){
+ nErrors += (rv & DCR_Error);
+ /* Handle DCR_Return, DCR_Exit or DCR_Abort */
+ if( rv>DCR_Error ) break;
+ }
+ }
+ }
+ rv |= (nErrors>0);
+ /* If error to be returned, indicate that complaining about it is done. */
+ return (rv==DCR_Error)? DCR_CmdErred : rv;
+}
- /* The argument can optionally begin with "-" or "--" */
- if( zCmd[0]=='-' && zCmd[1] ){
- zCmd++;
- if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
+DISPATCHABLE_COMMAND( read 3 2 2 ){
+ DotCmdRC rc = DCR_Ok;
+ ShellInState *psi = ISS(p);
+ InSource inSourceRedir
+ = INSOURCE_FILE_REDIR(0, azArg[1], psi->pInSource);
+
+ if( psi->bSafeMode ) return DCR_AbortError;
+ if( azArg[1][0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = smprintf("pipes are not supported in this OS\n");
+ rc = DCR_Error;
+#else
+ if( (inSourceRedir.inFile = popen(azArg[1]+1, "r"))==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
+ rc = DCR_Error;
+ }else{
+ inSourceRedir.closer.stream = pclose;
}
- /* If error(s) occured during process, leave complaining to them. */
+#endif
+ }else if( (inSourceRedir.inFile = openChrSource(azArg[1]))==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
+ rc = DCR_Error;
+ }else{
+ inSourceRedir.closer.stream = fclose;
+ }
+ if( inSourceRedir.inFile!=0 ){
+ AnyResourceHolder arh = { &(psi->pInSource),(GenericFreer)finish_InSource };
+ psi->pInSource = &inSourceRedir;
+ any_ref_holder(&arh);
+ rc = process_input(psi);
++ /* If error(s) occurred during process, leave complaining to them. */
+ if( rc==DCR_Error ) rc = DCR_CmdErred;
+ release_holder();
+ }
+ return rc;
+}
- /* --help lists all test-controls */
- if( cli_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);
+DISPATCHABLE_COMMAND( x ? 1 0 ){
+ int ia, nErrors = 0;
+ sqlite3_stmt *pStmt = 0;
+ sqlite3 *dbs = p->dbShell;
+ DotCmdRC rv = DCR_Ok;
+ enum { AsVar, AsString, AsFile } evalAs = AsVar;
+
+ for( ia=1; ia<nArg && nErrors==0; ++ia ){
+ char *zSubmit = 0;
+ const char *zOpt = azArg[ia];
+ if ( *zOpt == '-' ){
+ static const char *azOpts[] = { "k", "s", "f" };
+ int io = ArraySize(azOpts);
+ while( io > 0 ){
+ if( optionMatch(zOpt, azOpts[--io]) ){
+ evalAs = io;
+ zOpt = 0;
+ break;
+ }
}
- rc = 1;
- goto meta_command_exit;
+ if( zOpt==0 ) continue;
}
-
- /* 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( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
- if( testctrl<0 ){
- testctrl = aCtrl[i].ctrlCode;
- iCtrl = i;
+ switch( evalAs ){
+ case AsVar:
+ if( pStmt==0 ){
+ int rc;
+ if( dbs==0 || !shvars_table_exists(dbs) ){
+ utf8_printf(STD_ERR,
+ "\".x vname\" can only be done after .var set ... .\n");
+ return DCR_Error;
+ }
+ rc = s3_prepare_v2_noom(dbs, "SELECT value FROM "SHVAR_TABLE_SNAME
+ " WHERE key=$1 AND uses="SPTU_Script,
+ -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, SHVAR_TABLE_SNAME" is wrongly created.\n");
+ return DCR_Error;
+ }
+ }
+ if( isalpha(azArg[ia][0]) ){
+ int rc = sqlite3_reset(pStmt);
+ rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_ROW ){
+ ShellInState *psi = ISS(p);
+ const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
+ int nb = sqlite3_column_bytes(pStmt, 0);
+ zSubmit = zPrepForEval((const char *)zValue, nb);
+ sqlite3_reset(pStmt); /* End the script read to unlock DB. */
+ if( zSubmit ){
+ rv = shellEvalText(zSubmit, azArg[ia], p);
+ sqlite3_free(zSubmit);
+ }else{
+ continue; /* All white (or OOM), ignore. */
+ }
}else{
- utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n"
- "Use \".testctrl --help\" for help\n", zCmd);
- rc = 1;
- goto meta_command_exit;
+ utf8_printf(STD_ERR,
+ "Skipping var '%s' (not set and executable.)\n",
+ azArg[ia]);
+ ++nErrors;
+ }
+ }else{
+ utf8_printf(STD_ERR,
+ "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
+ ++nErrors;
+ }
+ break;
+ case AsString:
+ {
+ zSubmit = zPrepForEval(zOpt, strlen30(zOpt));
+ if( zSubmit ){
+ char zName[] = "x arg[999]";
+ sqlite3_snprintf(sizeof(zName), zName,"x arg[%d]", ia);
+ rv = shellEvalText(zSubmit, zName, p);
+ sqlite3_free(zSubmit);
}
}
+ break;
+ case AsFile:
+ {
+ char *av[] = {"read", (char*)zOpt};
+ rv = readCommand(av, ArraySize(av), p, pzErr);
+ }
+ break;
}
- 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->lineno, aCtrl[iCtrl].zCtrlName);
- exit(1);
+ if( rv<DCR_ArgIxMask ){
+ nErrors += (rv & DCR_Error);
+ /* Handle DCR_Return, DCR_Exit or DCR_Abort */
+ if( rv>DCR_Error ) break;
}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;
-
- /* 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;
+ ++nErrors;
+ rv = DCR_Error;
+ }
+ }
+ sqlite3_finalize(pStmt);
+ rv |= (nErrors>0);
+ /* If error to be returned, indicate that complaining about it is done. */
+ return (rv==DCR_Error)? DCR_CmdErred : rv;
+}
- /* 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;
+/* End of published, standard dot-command implementation functions
+COMMENT Build-time overrides of above dot-commands or new dot-commands may be
+COMMENT incorporated into shell.c via: -it COMMAND_CUSTOMIZE=<customize source>
+COMMENT where <customize source> names a file using the above methodology to
+COMMENT define new or altered dot-commands and their help text.
+*/
+INCLUDE( COMMAND_CUSTOMIZE );
+
+static void DotCommand_destruct(DotCommand *);
+static const char * DotCommand_name(DotCommand *);
+static const char * DotCommand_help(DotCommand *, const char *);
+static DotCmdRC
+ DotCommand_argsCheck(DotCommand *, char **, int nArgs, char *azArgs[]);
+static DotCmdRC
+ DotCommand_execute(DotCommand *, ShellExState *, char **, int, char *[]);
+
+static VTABLE_NAME(DotCommand) dot_cmd_VtabBuiltIn = {
+ DotCommand_destruct,
+ DotCommand_name,
+ DotCommand_help,
+ DotCommand_argsCheck,
+ DotCommand_execute
+};
- /* 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 && cli_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;
+/* Define and populate command dispatch table. */
+static struct CommandInfo {
+ VTABLE_NAME(DotCommand) *mcVtabBuiltIn;
+ const char * cmdName;
+ DotCmdRC (*cmdDoer)(char *azArg[], int nArg,
+ ShellExState *, char **pzErr);
+ unsigned char minLen, minArgs, maxArgs;
+ const char *azHelp[2]; /* primary and secondary help text */
+ void * pCmdData;
+ } command_table[] = {
+ COMMENT Emit the dispatch table entries generated and collected above.
+#define DOT_CMD_INFO(cmd, nlenMin, minArgs, maxArgs) \
+ &dot_cmd_VtabBuiltIn, #cmd, cmd ## Command, nlenMin, minArgs, maxArgs
+ EMIT_DOTCMD_INIT(2);
+#undef DOT_CMD_INFO
+ { 0, 0, 0, 0, (u8)~0, (u8)~0, {0,0}, 0 }
+};
+static unsigned numCommands
+ = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
- /* 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;
+static DotCommand *builtInCommand(int ix){
+ if( ix<0 || (unsigned)ix>=numCommands ) return 0;
+ return (DotCommand *)&command_table[ix];
+}
- /* 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;
+static void DotCommand_destruct(DotCommand *pMe){
+ UNUSED_PARAMETER(pMe);
+}
- /* sqlite3_test_control(sqlite3*) */
- case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
- rc2 = sqlite3_test_control(testctrl, p->db);
- isOk = 3;
- break;
+static const char * DotCommand_name(DotCommand *pMe){
+ return ((struct CommandInfo *)pMe)->cmdName;
+}
- 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;
+static const char * DotCommand_help(DotCommand *pMe, const char * zWhat){
+ struct CommandInfo *pci = (struct CommandInfo *)pMe;
+ if( zWhat==0 ) return pci->azHelp[0];
+ if( *zWhat==0 ) return pci->azHelp[1];
+ else return 0;
+}
- 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;
- }
+static DotCmdRC
+ DotCommand_argsCheck(DotCommand *pMe,
+ char **pzErrMsg, int nArgs, char *azArgs[]){
+ struct CommandInfo *pci = (struct CommandInfo *)pMe;
+ UNUSED_PARAMETER(azArgs);
+ if( pci->minArgs > nArgs ){
+ if( pzErrMsg ){
+ *pzErrMsg = smprintf("Too few arguments for \".%s\", need %d\n",
+ azArgs[0], pci->minArgs-1);
+ }
+ return DCR_TooFew;
+ }else if( pci->maxArgs > 0 && pci->maxArgs < nArgs ){
+ if( pzErrMsg ){
+ *pzErrMsg = smprintf("Too many arguments for \".%s\", over %d\n",
+ azArgs[0], pci->maxArgs-1);
+ }
+ return DCR_TooMany;
+ }else return DCR_Ok;
+}
-#ifdef YYCOVERAGE
- 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;
- }
- }
- 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);
- }
- }else
-#endif /* !defined(SQLITE_UNTESTABLE) */
+static DotCmdRC
+ DotCommand_execute(DotCommand *pMe, ShellExState *pssx,
+ char **pzErrMsg, int nArgs, char *azArgs[]){
+ return (((struct CommandInfo *)pMe)->cmdDoer)(azArgs, nArgs, pssx, pzErrMsg);
+}
- if( c=='t' && n>4 && cli_strncmp(azArg[0], "timeout", n)==0 ){
- open_db(p, 0);
- sqlite3_busy_timeout(p->db, nArg>=2 ? (int)integerValue(azArg[1]) : 0);
+/*****************
+** DotCommand iteration by name match, used by the .help dot-command.
- ** DotCommands, or their ad-hoc stand-ins, having matching names are produced
++** DotCommands, or improvised stand-ins, having matching names are produced
+** in lexical order, with the iterator indicating which has been produced.
+** If .zAdhocHelpName == 0, it is a regular DotCommand. Otherwise, the
+** ".unknown" DotCommand is returned, whose help() method is to be used.
+** Any returned CmdMatchIter must eventually be passed to freeCmdMatchIter().
+*/
+typedef struct CmdMatchIter {
+ ShellExState *psx;
+ /* 0 indicates prepared statement; non-0 is the glob pattern. */
+ const char *zPattern;
+ union {
+ DotCommand *pDotCmd;
+#if SHELL_DYNAMIC_EXTENSION
+ sqlite3_stmt *stmt;
+#endif
+ } cursor;
+#if SHELL_DYNAMIC_EXTENSION
- char *zAdhocHelpText; /* registered extension ad-hoc help */
++ char *zAdhocHelpText; /* registered extension improvised help */
+#endif
+} CmdMatchIter;
+
+/* Release resources held by the iterator and clear it. */
+static void freeCmdMatchIter(CmdMatchIter *pMMI){
+ if( pMMI->zPattern!=0 ){
+ sqlite3_free((void *)pMMI->zPattern);
+ pMMI->zPattern = 0;
+ pMMI->cursor.pDotCmd = 0;
+ }
+#if SHELL_DYNAMIC_EXTENSION
+ else{
+ sqlite3_finalize(pMMI->cursor.stmt);
+ pMMI->cursor.stmt = 0;
+ }
+ sqlite3_free(pMMI->zAdhocHelpText);
+ pMMI->zAdhocHelpText = 0;
+#endif
+}
+
+/* Prepare an iterator that will produce a sequence of DotCommand
+ * pointers whose referents' names match the given cmdFragment.
+ * Return how many will match (if iterated upon return.) */
+static int findMatchingDotCmds(const char *cmdFragment,
+ CmdMatchIter *pMMI,
+ ShellExState *psx){
+ CmdMatchIter mmi = { psx, 0, 0 };
+ int rv = 0;
+#if SHELL_DYNAMIC_EXTENSION
+ if( ISS(psx)->bDbDispatch ){
+ sqlite3_stmt *stmtCount = 0;
+ /* Prepare rv.stmt to yield results glob-matching cmdFragment. */
+ static const char * const zSqlIter =
+ "SELECT name, extIx, cmdIx, help FROM "SHELL_HELP_VIEW" "
+ "WHERE name glob (?||'*') ORDER BY name";
+ static const char * const zSqlCount =
+ "SELECT count(*) FROM "SHELL_HELP_VIEW" "
+ "WHERE name glob (?||'*')";
+ if( pMMI ){
+ s3_prepare_v2_noom(psx->dbShell, zSqlIter, -1, &mmi.cursor.stmt, 0);
+ sqlite3_bind_text(mmi.cursor.stmt, 1, cmdFragment? cmdFragment:"", -1, 0);
+ }
+ s3_prepare_v2_noom(psx->dbShell, zSqlCount, -1, &stmtCount, 0);
+ sqlite3_bind_text(stmtCount, 1, cmdFragment? cmdFragment : "", -1, 0);
+ if( SQLITE_ROW==sqlite3_step(stmtCount) ){
+ rv = sqlite3_column_int(stmtCount, 0);
+ }else assert(0);
+ sqlite3_finalize(stmtCount);
}else
+#endif
+ {
+ int i = 0;
+ mmi.zPattern = smprintf("%s*", cmdFragment? cmdFragment : "");
+ shell_check_ooms((void *)mmi.zPattern);
+
+ struct CommandInfo *pCI = command_table;
+ mmi.cursor.pDotCmd = (DotCommand *)command_table;
+ while( pCI<command_table+numCommands ){
+ if( sqlite3_strglob(mmi.zPattern, pCI->cmdName)==0 ) ++rv;
+ ++pCI;
+ }
+ }
+ if( pMMI ) *pMMI = mmi;
+ else freeCmdMatchIter(&mmi);
+ return rv;
+}
- if( c=='t' && n>=5 && cli_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;
+/* Produce the next DotCommand pointer from the iterator, or 0 if no next. */
+static DotCommand * nextMatchingDotCmd(CmdMatchIter *pMMI){
+ DotCommand *rv = 0;
+#if SHELL_DYNAMIC_EXTENSION
+ if( pMMI->zPattern==0 ){
+ int rc = sqlite3_step(pMMI->cursor.stmt);
+ if( rc==SQLITE_ROW ){
+ /* name, extIx, cmdIx, help */
+ int extIx = sqlite3_column_int(pMMI->cursor.stmt, 1);
+ int cmdIx = sqlite3_column_int(pMMI->cursor.stmt, 2);
+ ShellInState *psi = ISS(pMMI->psx);
+ sqlite3_free(pMMI->zAdhocHelpText);
+ if( cmdIx>=0 ){
+ pMMI->zAdhocHelpText = 0;
+ return command_by_index(psi, extIx, cmdIx);
+ }else{
+ const unsigned char *zHT = sqlite3_column_text(pMMI->cursor.stmt, 3);
+ assert(psi->pUnknown!=0);
+ assert(extIx<psi->numExtLoaded && extIx>0);
+ if( zHT==0 ) zHT = sqlite3_column_text(pMMI->cursor.stmt, 0);
+ pMMI->zAdhocHelpText = smprintf("%s", zHT);
+ return psi->pShxLoaded[extIx].pUnknown;
}
}else{
- raw_printf(stderr, "Usage: .timer on|off\n");
- rc = 1;
+ sqlite3_finalize(pMMI->cursor.stmt);
+ pMMI->cursor.stmt = 0;
}
}else
+#endif
+ {
+ struct CommandInfo *pCI = (struct CommandInfo *)(pMMI->cursor.pDotCmd);
+ assert(pCI>=command_table && pCI<=command_table+numCommands);
+ while( pCI<command_table+numCommands ){
+ if( sqlite3_strglob(pMMI->zPattern, pCI->cmdName)==0 ){
+ rv = pMMI->cursor.pDotCmd;
+ }
+ pMMI->cursor.pDotCmd = (DotCommand *)(++pCI);
+ if( rv!=0 ) break;
+ }
+ }
+ return rv;
+}
-#ifndef SQLITE_OMIT_TRACE
- if( c=='t' && cli_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;
- }
+/*****************
+** DotCommand lookup
+**
+** For the non-extended or non-extensible shell, this function does
+** a binary search of the fixed list of dot-command info structs.
+** For an extended shell, it queries the shell's DB. Either way,
+** this function returns a DotCommand pointer if one can be found
+** with an adequate match for the given name. Here, "adequate" may
+** vary according to whether shell extensions have been loaded. If
+** not, the match must be for as many characters as set within the
+** above CommandInfo array (set via DISPATCHABLE_COMMAND macro call.)
+** If shell extensions are loaded, the match must be long enough to
+** result in a unique lookup.
+*/
+DotCommand *findDotCommand(const char *cmdName, ShellExState *psx,
+ /* out */ int *pnFound){
+ if( pnFound ) *pnFound = 0;
+#if SHELL_DYNAMIC_EXTENSION
+ if( ISS(psx)->bDbDispatch ){
+ int rc;
+ int extIx = -1, cmdIx = -1, nf = 0;
+ sqlite3_stmt *pStmt = 0;
+ const char *zSql = "SELECT COUNT(*), extIx, cmdIx"
+ " FROM "SHELL_DISP_VIEW" WHERE name glob (?||'*')";
+ rc = s3_prepare_v2_noom(psx->dbShell, zSql, -1, &pStmt, 0);
+ sqlite3_bind_text(pStmt, 1, cmdName, -1, 0);
+ rc = sqlite3_step(pStmt);
+ nf = sqlite3_column_int(pStmt, 0);
+ extIx = sqlite3_column_int(pStmt, 1);
+ cmdIx = sqlite3_column_int(pStmt, 2);
+ sqlite3_finalize(pStmt);
+ if( rc!= SQLITE_ROW ) return 0;
+ if( pnFound ) *pnFound = nf;
+ if( nf!=1 ) return 0; /* Future: indicate collisions if > 1 */
+ return (cmdIx<0)? 0 : command_by_index(ISS(psx), extIx, cmdIx);
+ }else
#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;
- }
+ {
+ int cmdLen = strlen30(cmdName);
+ struct CommandInfo *pci = 0;
+ int ixb = 0, ixe = numCommands-1;
+ while( ixb <= ixe ){
+ int ixm = (ixb+ixe)/2;
+ int md = cli_strncmp(cmdName, command_table[ixm].cmdName, cmdLen);
+ if( md>0 ){
+ ixb = ixm+1;
+ }else if( md<0 ){
+ ixe = ixm-1;
}else{
- output_file_close(p->traceOut);
- p->traceOut = output_file_open(z, 0);
+ /* Have a match, see whether it's ambiguous. */
+ if( command_table[ixm].minLen > cmdLen ){
+ if( (ixm>0
+ && !cli_strncmp(cmdName, command_table[ixm-1].cmdName, cmdLen))
+ ||
+ (ixm<ixe
+ && !cli_strncmp(cmdName, command_table[ixm+1].cmdName, cmdLen))){
+ /* Yes, a neighbor matches too. */
+ if( pnFound ) *pnFound = 2; /* could be more, but >1 suffices */
+ return 0;
+ }
+ }
+ pci = &command_table[ixm];
+ if( pnFound ) *pnFound = 1;
+ break;
}
}
- 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);
- }
- }else
-#endif /* !defined(SQLITE_OMIT_TRACE) */
+ if( pnFound && pci ) *pnFound = 1;
+ return (DotCommand *)pci;
+ }
+}
-#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_VIRTUALTABLE)
- if( c=='u' && cli_strncmp(azArg[0], "unmodule", n)==0 ){
- int ii;
- int lenOpt;
- char *zOpt;
- if( nArg<2 ){
- raw_printf(stderr, "Usage: .unmodule [--allexcept] NAME ...\n");
+/*
+** Given a DotCommand, desired help level,
- ** ( possibly retreived ad-hoc help text for extensible shell, )
++** ( possibly retrieved improvised help text for extensible shell, )
+** and an optional all-text search pattern, then
+** when level==0 and primary help available, output it
+** when level==1 and primary or secondary help available, output it
+** when level==2 and any help text matches pattern, output it
+** when level>2 or no pattern: output all help text
+** If cLead==0, anything meeting above criteria is output. Otherwise, output
+** is restricted to those commands whose primary help begins with cLead.
+** Return 1 if anything output, else 0.
+*/
+static int putSelectedCmdHelp(DotCommand *pmc, int iLevel, char cLead,
+#if SHELL_DYNAMIC_EXTENSION
+ const char *zHelpText,
+#endif
+ FILE *out, const char *zSearch){
+ int rc = 0;
+ assert(pmc!=0);
+#if SHELL_DYNAMIC_EXTENSION
+ if( zHelpText ){
+ const char *zHT = zHelpText+1; /* skip over classifier */
+ if( cLead && *zHelpText!= cLead ) return 0;
+ if( *zHelpText==0 ) return 0;
+ const char *zLE = zHT;
+ switch( iLevel ){
+ case 0:
+ while( *zLE && *zLE++!='\n' ) {}
+ utf8_printf(out,".%.*s", (int)(zLE-zHT), zHT);
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 && cli_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);
+ break;
+ case 2:
+ if( zSearch ){
+ if( !sqlite3_strlike(zSearch, zHT, 0) ) break;
}
+ deliberate_fall_through;
+ case 1:
+ default:
+ utf8_printf(out,".%s", zHT);
+ rc = 1;
}
}else
#endif
-
-#if SQLITE_USER_AUTHENTICATION
- if( c=='u' && cli_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( cli_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( cli_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( cli_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);
+ {
+ const char *zHTp = pmc->pMethods->help(pmc, 0);
+ const char *zHTs = pmc->pMethods->help(pmc, "");
+ if( !zHTp && !zHTs ) return 0;
+ if( cLead && zHTp && *zHTp!= cLead ) return 0;
+ switch( iLevel ){
+ case 0:
+ case 1:
+ if( zHTp ){
+ utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
rc = 1;
}
- }else if( cli_strcmp(azArg[1],"delete")==0 ){
- if( nArg!=3 ){
- raw_printf(stderr, "Usage: .user delete USER\n");
+ if( iLevel>0 && zHTs ){
+ utf8_printf(out, HELP_TEXT_FMTS, zHTs);
rc = 1;
- goto meta_command_exit;
}
- rc = sqlite3_user_delete(p->db, azArg[2]);
- if( rc ){
- raw_printf(stderr, "User-Delete failed: %d\n", rc);
- rc = 1;
+ break;
+ case 2:
+ if( zSearch ){
+ int m = 0;
+ if( zHTp && !sqlite3_strlike(zSearch, zHTp, 0) ) ++m;
+ if( zHTs && !sqlite3_strlike(zSearch, zHTs, 0) ) ++m;
+ if( m==0 ) break;
}
- }else{
- raw_printf(stderr, "Usage: .user login|add|edit|delete ...\n");
+ deliberate_fall_through;
+ default:
+ if( zHTp ) utf8_printf(out, HELP_TEXT_FMTP, zHTp+1);
+ if( zHTs ) utf8_printf(out, HELP_TEXT_FMTS, zHTs);
rc = 1;
- goto meta_command_exit;
}
- }else
-#endif /* SQLITE_USER_AUTHENTICATION */
+ }
+ return rc;
+}
- if( c=='v' && cli_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());
+/*
+** Output primary (single-line) help for a known command.
+*/
+static void showPrimaryHelp(FILE *out, const char *zCmd, ShellExState *psx){
+ CmdMatchIter cmi = {0};
+ int nm = findMatchingDotCmds(zCmd, &cmi, psx);
+ DotCommand *pdc = nextMatchingDotCmd(&cmi);
+ if( pdc!=0 ){
+ putSelectedCmdHelp(pdc, 0, 0,
+#if SHELL_DYNAMIC_EXTENSION
+ cmi.zAdhocHelpText,
#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");
+ out, 0);
+ }
+ freeCmdMatchIter(&cmi);
+}
+
+/*
+** Output various subsets of help text. These 6 are defined:
+** 1. HO_AllP For all commands, primary help text only.
+** 2. HO_AllX For all commands, complete help text.
+** 3. HO_LikeP For multiple commands matching pattern, primary help text only.
+** 4. HO_OneX For a single matched command, complete help text.
+** 5. HO_LikeT For commands whose help contains a pattern, complete help text.
+** 6. HO_Undoc For all internal "undocumented" (without normal help) commands.
- ** These variations are indicated thusly:
++** These variations are indicated thus:
+** 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
+** 6. zPattern is exactly the pointer known locally as zHelpAll.
+**
+** Return the number of matches.
+*/
+static int showHelp(FILE *out, const char *zPattern, ShellExState *psx){
+ u8 bNullPattern = zPattern==0;
+ u8 bShowUndoc = zPattern==zHelpAll;
+ u8 bEmptyPattern = !bNullPattern && (*zPattern==0 || bShowUndoc);
+ int npm = 0; /* track how many matches found */
+ CmdMatchIter cmi = {0};
+ DotCommand *pdc;
+ char *zPat = 0;
+ char cLead = (bShowUndoc)? ',' : '.';
+ int iLevel = 0;
+ enum {
+ HO_Tbd, HO_AllP, HO_AllX, HO_LikeP, HO_OneX, HO_LikeT, HO_Undoc
+ } hoKind = bShowUndoc? HO_Undoc : HO_Tbd;
+
+ if( hoKind==HO_Undoc ){
+ unsigned ixct = 0;
+ utf8_printf(out, "%s\n%s\n",
+ "The following commands are for SQLite diagnosis and internal testing.",
+ "They are undocumented and subject to change without notice.");
+ /* Bypass command lookup/resolution. This is just for internal commands. */
+ while( ixct<numCommands ){
+ struct CommandInfo *pci = &command_table[ixct];
+ const char *zH = pci->azHelp[0];
+ if( zH && *zH==cLead ){
+ utf8_printf(out, HELP_TEXT_FMTP, zH+1);
+ zH = pci->azHelp[1];
+ if( zH ) utf8_printf(out, HELP_TEXT_FMTS, zH);
+ ++npm;
+ }
+ ++ixct;
+ }
+ return npm;
+ }
+ npm = findMatchingDotCmds(zPattern, &cmi, psx);
+ if( bNullPattern ) hoKind = HO_AllP;
+ else if( bEmptyPattern ) hoKind = HO_AllX;
+ else if( npm>1 ) hoKind = HO_LikeP;
+ else if( npm==1 ) hoKind = HO_OneX;
+ else{
+ hoKind = HO_LikeT;
+ zPat = smprintf("%%%s%%", zPattern);
+ shell_check_ooms(zPat);
+ }
+ zPattern = 0;
+ iLevel = 1;
+ switch( hoKind ){
+ case HO_AllP: case HO_LikeP:
+ iLevel = 0;
+ break;
+ case HO_AllX:
+ break;
+ case HO_OneX:
+ cLead = 0;
+ break;
+ case HO_LikeT:
+ zPattern = zPat;
+ break;
+ default: return 0;
+ }
+ npm = 0;
+ while( 0 != (pdc = nextMatchingDotCmd(&cmi)) ){
+ npm += putSelectedCmdHelp(pdc, iLevel, cLead,
+#if SHELL_DYNAMIC_EXTENSION
+ cmi.zAdhocHelpText,
#endif
- }else
+ out, zPattern);
+ }
+ freeCmdMatchIter(&cmi);
+ sqlite3_free(zPat);
+ return npm;
+}
- if( c=='v' && cli_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);
+/* Perform preparation needed prior to actually running any dot command. */
+static void command_prep(ShellInState *psi){
+ clearTempFile(psi);
+#ifndef SQLITE_OMIT_VIRTUALTABLE
+ if( psi->expert.pExpert ){
+ expertFinish(psi, 1, 0);
+ }
+#endif
+}
+
+/* Perform post-execution actions needed after running any dot command. */
+static void command_post(ShellInState *psi){
+ if( psi->outCount ){
+ psi->outCount--;
+ if( psi->outCount==0 ) output_reset(psi);
+ }
+ updateSafeMode(psi);
+}
+
+/* Issue errors per returned DotCmdRC and error message, handle certain
+ * exceptional returns, and translate return to the sanitized first 8.
+ */
+static DotCmdRC dot_command_errors(char *zErr, char *azArg[], int nArg,
+ DotCmdRC dcr, ShellExState *psx){
+ if( psx->shellAbruptExit!=0 ){
+ if( psx->shellAbruptExit>0x1ff ) dcr = DCR_AbortError;
+ else dcr = DCR_Exit | (dcr & DCR_Error);
+ }
+ if( dcr==DCR_CmdErred ){
+ /* Error message(s) already emitted. Just translate to execute error. */
+ dcr = DCR_Error;
+ }else if( dcr==DCR_SayUsage ){
+ if( zErr ){
+ utf8_printf(STD_ERR, "%s", zErr);
+ }else{
+ utf8_printf(STD_ERR, "Usage:\n");
+ showPrimaryHelp(STD_ERR, azArg[0], psx);
+ }
+ dcr = DCR_Error;
+ }else if( dcr > DCR_AbortError ){
+ /* Handle invocation errors. */
+ int ia = dcr & DCR_ArgIxMask;
+ const char *pArg1st = (ia>=nArg)? "" : azArg[ia];
+ const char *pArg2nd = 0;
+ int ec = dcr & ~DCR_ArgIxMask;
+ int unknownCmd = 0;
+ const char *z = 0;
+ switch( ec ){
+ case DCR_Unknown:
+ unknownCmd = ia==0;
+ if( unknownCmd ) z = "unknown dot command: \".%s\"";
+ else{
+ z = "On .%s, unknown option or subcommand: \"%s\"";
+ pArg2nd = pArg1st;
+ pArg1st = azArg[0];
}
+ break;
+ case DCR_Ambiguous:
+ if( ia>0 ){
+ z = "For .%s, \"%s\" is ambiguous; it matches more than one subcommand";
+ pArg2nd = pArg1st;
+ pArg1st = azArg[0];
+ }else z = "\"%s\" is ambiguous; it matches multiple dot commands";
+ break;
+ case DCR_Unpaired:
+ z = "With .%s, option %s must be paired with a value argument";
+ pArg2nd = pArg1st;
+ pArg1st = azArg[0];
+ break;
+ case DCR_TooMany:
+ z = "Excess arguments provided; try .help %s";
+ pArg1st = azArg[0];
+ break;
+ case DCR_TooFew:
+ z = "Insufficient arguments; try .help %s";
+ pArg1st = azArg[0];
+ break;
+ case DCR_Missing:
+ z = "Required arguments missing; try .help %s";
+ pArg1st = azArg[0];
+ break;
+ case DCR_ArgWrong:
+ z = "Argument(s) incorrect; try .help %s";
+ pArg1st = azArg[0];
+ break;
+ default:
+ z = "? Bizarre return from %s";
+ break;
}
- }else
-
- if( c=='v' && cli_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);
+ if( !zErr ){
+ if( z!=0 ){
+ char *ze = smprintf("Error: %z\n", smprintf(z, pArg1st, pArg2nd));
+ utf8_printf(STD_ERR, "%s", ze);
+ sqlite3_free(ze);
+ }
+ }else{
+ const char *fmt
+ = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
+ utf8_printf(STD_ERR, fmt, zErr);
}
- 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( INSOURCE_IS_INTERACTIVE(ISS(psx)->pInSource) ){
+ if( unknownCmd ){
+ utf8_printf(STD_ERR, "Enter \".help\" for a list of commands.\n");
+ }else{
+ utf8_printf(STD_ERR, "Usage:\n");
+ showPrimaryHelp(STD_ERR, azArg[0], psx);
}
}
- }else
-
- if( c=='v' && cli_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);
+ /* If the shell DB becomes messed up, at least .quit will be doable. */
+ if( unknownCmd && psx->dbShell!=0
+ && sqlite3_strnicmp(azArg[0],"quit",strlen30(azArg[0]))==0 ){
+ dcr = DCR_Return;
+ }else{
+ dcr = DCR_Error;
+ }
+ }else{
+ /* Handle execution errors. */
+ if( dcr==DCR_AbortError ){
+ if( zErr ){
+ utf8_printf(STD_ERR, "Error: \".%s\" may not %s in -safe mode\n",
+ azArg[0], zErr);
+ }else {
+ utf8_printf(STD_ERR, "Error: \".%s\" forbidden in -safe mode\n",
+ azArg[0]);
+ }
+ psx->shellAbruptExit = 0x203;
+ }else{
+ int error = dcr & DCR_Error;
+ int action = dcr & ~DCR_Error;
+ if( error ){
+ if( zErr ){
+ const char *fmt
+ = (sqlite3_strnicmp(zErr, "Error", 5)==0)? "%s" : "Error: %s";
+ utf8_printf(STD_ERR, fmt, zErr);
+ }
+ else utf8_printf(STD_ERR, "Error: .%s failed\n", azArg[0]);
}
}
- }else
+ }
+ return dcr;
+}
- if( c=='w' && cli_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
+/* Argument-check and execute a found DotCommand, wrapping execution
+ * with command_{prep,post}(...), and issue errors as made evident.
+ * Return one of the "Post-execute action and success/error status"
+ * codes from the DotCmdRC enum.
+ *
+ * Note that this function is exposed for shell extensions to use.
+ *
+ * This should be called for "top-level" dot command execution only.
+ * Should an extension wrap or use a DotCommand object to effect its
+ * own functionality, that object's execute() method should be called
+ * directly, without going through this function.
+ */
+static DotCmdRC runDotCommand(DotCommand *pmc, char *azArg[], int nArg,
+ ShellExState *psx){
+ char *zErr = 0;
+ DotCmdRC dcr = pmc->pMethods->argsCheck(pmc, &zErr, nArg, azArg);
+ ResourceMark mark = holder_mark();
- if( c=='w' && cli_strncmp(azArg[0], "width", n)==0 ){
- int j;
- assert( nArg<=ArraySize(azArg) );
- p->nWidth = nArg-1;
- p->colWidth = realloc(p->colWidth, (p->nWidth+1)*sizeof(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]);
- }
- }else
+ command_prep(ISS(psx));
+ sstr_ptr_holder(&zErr);
+ if( dcr==DCR_Ok ){
+ dcr = pmc->pMethods->execute(pmc, psx, &zErr, nArg, azArg);
+ }
+ if( dcr!=DCR_Ok ){
+ dcr = dot_command_errors(zErr, azArg, nArg, dcr, psx);
+ }
+ RESOURCE_FREE(mark);
+ command_post(ISS(psx));
+ return dcr;
+}
- {
- utf8_printf(stderr, "Error: unknown command or invalid arguments: "
- " \"%s\". Enter \".help\" for help\n", azArg[0]);
- rc = 1;
+/*
+** If an input line or line group begins with "." then invoke this routine
+** to process that line.
+**
+** Returns sanitized DotCmdRC values, with invocation error codes
+** translated to DCR_Error, so that only these 8 returns are possible:
+** DCR_Ok, DCR_Return, DCR_Exit or DCR_Abort possibly or'ed with DCR_Error.
+**
+** Any applicable error messages are issued along with output messages.
+*/
+static DotCmdRC do_dot_command(char *zLine, ShellExState *psx){
+ int h = 1; /* Passing over leading '.' */
+ int nArg = 0;
+ char *azArg[52];
+ DotCmdRC dcr = DCR_Ok;
+#if SHELL_VARIABLE_EXPANSION
+ int ncLineIn = strlen30(zLine);
+ u8 bExpVars = SHEXT_VAREXP(ISS(psx));
+#endif
+ RipStackDest dotRipDest = RIP_STACK_DEST_INIT;
+
+ /* Parse the input line into tokens which are 0-terminated and left in-place.
+ */
+ while( zLine[h] && nArg<ArraySize(azArg)-1 ){
+ /* Future: Complain if this fixed argument count limit is hit. */
+ 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;
+ }
+ 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; /* No code here relies on this, but some extension might. */
-meta_command_exit:
- if( p->outCount ){
- p->outCount--;
- if( p->outCount==0 ) output_reset(p);
+ /* Process the input line. If it was empty, do nothing and declare success.
+ * Note that "empty" includes a leading '.' followed by nothing else.
+ */
+ register_exit_ripper(&dotRipDest);
+ if( 0==RIP_TO_HERE(dotRipDest) ){
+ if( nArg>0 ){
+ int nFound;
+ DotCommand *pmc = findDotCommand(azArg[0], psx, &nFound);
+ if( pmc==0 || nFound>1 ){
+ if( nFound==0 ){
+ dcr = DCR_Unknown;
+#if SHELL_DYNAMIC_EXTENSION
+ pmc = ISS(psx)->pUnknown;
+ if( pmc ) dcr = runDotCommand(pmc, azArg, nArg, psx);
+#endif
+ }else{
+ dcr = DCR_Ambiguous;
+ }
+ if( dcr > DCR_ArgIxMask ){
+ /* Issue error for unknown or inadequately specified dot command. */
+ dcr = dot_command_errors(0, azArg, nArg, dcr, psx);
+ }
+ }
+ else{
+ char *arg0 = azArg[0];
+ azArg[0] = (char *)(pmc->pMethods->name(pmc));
+ /* Run found command and issue or handle any errors it may report. */
+ dcr = runDotCommand(pmc, azArg, nArg, psx);
+ azArg[0] = arg0;
+ }
+ }
+ forget_exit_ripper(&dotRipDest);
+ }else{
+ dcr = DCR_Abort|DCR_Error;
}
- p->bSafeMode = p->bSafeModePersist;
- return rc;
+#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 dcr;
}
/* Line scan result and intermediate states (supporting scan resumption)
}
/*
-** Run a single line of SQL. Return the number of errors.
+** Run a single line of SQL. Return the number of errors.
+** bAltIn indicates that input has been redirected in some way.
+*/
+static int runOneSqlLine(ShellExState *psx, char *zSql,
+ int bAltIn, int startline){
+ int rc;
+ char *zErrMsg = 0;
+ RipStackDest sqlRipDest = RIP_STACK_DEST_INIT;
+
+ open_db(psx, 0);
+ sstr_ptr_holder(&zErrMsg);
+ if( ShellHasFlag(psx,SHFLG_Backslash) ) resolve_backslashes(zSql);
+ if( ISS(psx)->flgProgress & SHELL_PROGRESS_RESET ) ISS(psx)->nProgress = 0;
+ BEGIN_TIMER;
+ register_exit_ripper(&sqlRipDest);
+ if( 0==RIP_TO_HERE(sqlRipDest) ){
+ rc = shell_exec(psx, zSql, &zErrMsg);
+ forget_exit_ripper(&sqlRipDest);
+ }else{
+ psx->shellAbruptExit = 0x102;
+ rc = 1;
+ }
+ END_TIMER;
+ if( rc || zErrMsg ){
+ char zPrefix[100];
+ const char *zErrorTail;
+ const char *zErrorType;
+ if( psx->shellAbruptExit==0 ){
+ if( zErrMsg==0 ){
+ zErrorType = "Error";
+ zErrorTail = sqlite3_errmsg(DBX(psx));
+ }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){
+ zErrorType = "Parse error";
+ zErrorTail = &zErrMsg[12];
+ }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){
+ zErrorType = "Runtime error";
+ zErrorTail = &zErrMsg[10];
+ }else{
+ zErrorType = "Error";
+ zErrorTail = zErrMsg;
+ }
+ if( bAltIn || !stdin_is_interactive ){
+ sqlite3_snprintf(sizeof(zPrefix), zPrefix,
+ "%s near line %d:", zErrorType, startline);
+ }else{
+ sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
+ }
+ utf8_printf(STD_ERR, "%s %s\n", zPrefix, zErrorTail);
+ }
+ rc = 1;
+ }else if( ShellHasFlag(psx, SHFLG_CountChanges) ){
+ char zLineBuf[36+2*20];
+ sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
+ "changes: %lld total_changes: %lld",
+ sqlite3_changes64(DBX(psx)), sqlite3_total_changes64(DBX(psx)));
+ raw_printf(ISS(psx)->out, "%s\n", zLineBuf);
+ }
+ release_holder();
+ return rc;
+}
+
+#if SHELL_EXTENDED_PARSING
- /* Resumable line classsifier for dot-commands
++/* Resumable line classifier for dot-commands
+**
+** Determines if a dot-command is open, having either an unclosed
+** quoted argument or an escape sequence opener ('\') at its end.
+**
+** The FSM design/behavior assumes/requires that a terminating '\'
+** is not part of the character sequence being classified -- that
+** it represents an escaped newline which is removed as physical
+** lines are spliced to accumulate logical lines.
+**
+** The line or added line-portion is passed as zCmd.
+** The pScanState pointer must reference an (opaque) DCmd_ScanState,
+** which must be set to DCSS_Start to initialize the scanner state.
+** Resumed scanning should always be done with zCmd logically just
+** past the last non-0 char of the text previously passed in, with
+** any previously scanned, trailing newline escape first trimmed.
+** Returns are: 0 => not open (aka complete), 1 => is open (incomplete)
+** The following macros may be applied to the scan state:
+*/
+#define DCSS_InDarkArg(dcss) (((dcss)&argPosMask)==inDqArg)
+#define DCSS_EndEscaped(dcss) (((dcss)&endEscaped)!=0)
+#define DCSS_IsOpen(dcss) (((dcss)&isOpenMask)!=0)
+typedef enum {
+ DCSS_Start = 0,
+ twixtArgs = 0, inSqArg = 1, inDarkArg = 2, inDqArg = 3, /* ordered */
+ endEscaped = 4, /* bit used */
+ argPosMask = 3, /* bits used */
+ isOpenMask = 1|4 /* bit test */
+} DCmd_ScanState;
+
+static void dot_command_scan(char *zCmd, DCmd_ScanState *pScanState,
+ SCAN_TRACKER_REFTYPE pst){
+ DCmd_ScanState ss = *pScanState & ~endEscaped;
+ char c = (ss&isOpenMask)? 1 : *zCmd++;
+ while( c!=0 ){
+ switch( ss ){
+ case twixtArgs:
+ CONTINUE_PROMPT_AWAITC(pst, 0);
+ while( IsSpace(c) ){
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ switch( c ){
+ case '\\':
+ if( *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }else goto inDark;
+ case '\'': ss = inSqArg; goto inSq;
+ case '"': ss = inDqArg; goto inDq;
+ default: ss = inDarkArg; goto inDark;
+ }
+ inSq:
+ case inSqArg:
+ CONTINUE_PROMPT_AWAITC(pst, '\'');
+ while( (c=*zCmd++)!='\'' ){
+ if( c==0 ) goto atEnd;
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ }
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ inDq:
+ case inDqArg:
+ CONTINUE_PROMPT_AWAITC(pst, '"');
+ do {
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ if( c=='\\' ){
+ if( (c=*zCmd++)==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ } while( c!='"' );
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ inDark:
+ case inDarkArg:
+ CONTINUE_PROMPT_AWAITC(pst, 0);
+ while( !IsSpace(c) ){
+ if( c=='\\' && *zCmd==0 ){
+ ss |= endEscaped;
+ goto atEnd;
+ }
+ if( (c=*zCmd++)==0 ) goto atEnd;
+ }
+ ss = twixtArgs;
+ c = *zCmd++;
+ continue;
+ case endEscaped: case isOpenMask: default:
+ ; /* Not reachable, but quiet compilers unable to see this. */
+ }
+ }
+ atEnd:
+ *pScanState = ss;
+}
+#else
+# define dot_command_scan(x,y,z)
+#endif
+
+/* 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 runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){
- int rc;
- char *zErrMsg = 0;
-
- open_db(p, 0);
- if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql);
- if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0;
- BEGIN_TIMER;
- rc = shell_exec(p, zSql, &zErrMsg);
- END_TIMER;
- if( rc || zErrMsg ){
- char zPrefix[100];
- const char *zErrorTail;
- const char *zErrorType;
- if( zErrMsg==0 ){
- zErrorType = "Error";
- zErrorTail = sqlite3_errmsg(p->db);
- }else if( cli_strncmp(zErrMsg, "in prepare, ",12)==0 ){
- zErrorType = "Parse error";
- zErrorTail = &zErrMsg[12];
- }else if( cli_strncmp(zErrMsg, "stepping, ", 10)==0 ){
- zErrorType = "Runtime error";
- zErrorTail = &zErrMsg[10];
- }else{
- zErrorType = "Error";
- zErrorTail = zErrMsg;
- }
- if( in!=0 || !stdin_is_interactive ){
- sqlite3_snprintf(sizeof(zPrefix), zPrefix,
- "%s near line %d:", zErrorType, startline);
- }else{
- sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%s:", zErrorType);
- }
- utf8_printf(stderr, "%s %s\n", zPrefix, zErrorTail);
- sqlite3_free(zErrMsg);
- zErrMsg = 0;
- return 1;
- }else if( ShellHasFlag(p, SHFLG_CountChanges) ){
- char zLineBuf[2000];
- sqlite3_snprintf(sizeof(zLineBuf), zLineBuf,
- "changes: %lld total_changes: %lld",
- sqlite3_changes64(p->db), sqlite3_total_changes64(p->db));
- raw_printf(p->out, "%s\n", zLineBuf);
+static int line_join_done(DCmd_ScanState dcss, char *zLine,
+ i64 *pnLength, char *pcLE){
+ /* It is ready only if has no open argument or escaped newline. */
+ int bOpen = DCSS_IsOpen(dcss);
+ if( !DCSS_EndEscaped(dcss) ){
+ *pcLE = '\n';
+ return !bOpen;
+ }else{
+ *pcLE = (bOpen || DCSS_InDarkArg(dcss))? 0 : ' ';
+ /* Swallow the trailing escape character. */
+ zLine[--*pnLength] = 0;
+ return 0;
}
- return 0;
-}
-
-static void echo_group_input(ShellState *p, const char *zDo){
- if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo);
}
+#endif
-#ifdef SQLITE_SHELL_FIDDLE
/*
-** Alternate one_input_line() impl for wasm mode. This is not in the primary
-** impl because we need the global shellState and cannot access it from that
-** function without moving lots of code around (creating a larger/messier diff).
+** Grow the accumulation line buffer to accommodate ncNeed chars.
- ** In/out parameters pz and pna reference the buffer and its size.
++** In/out parameters pzBuf and pnAccum reference the buffer and its size.
+** The buffer must eventually be sqlite3_free()'ed by the caller.
*/
- static void grow_line_buffer(char **pz, i64 *pna, int ncNeed){
-static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
- /* Parse the next line from shellState.wasm.zInput. */
- const char *zBegin = shellState.wasm.zPos;
- const char *z = zBegin;
- char *zLine = 0;
- i64 nZ = 0;
++static void grow_line_buffer(char **pzBuf, i64 *pnAccum, int ncNeed){
- if( ncNeed > *pna ){
- *pna += *pna + (*pna>>1) + 100;
- *pz = sqlite3_realloc(*pz, *pna);
- shell_check_ooms(*pz);
- UNUSED_PARAMETER(in);
- UNUSED_PARAMETER(isContinuation);
- if(!z || !*z){
- return 0;
++ if( ncNeed > *pnAccum ){
++ *pnAccum += *pnAccum + (*pnAccum>>1) + 100;
++ *pzBuf = sqlite3_realloc(*pzBuf, *pnAccum);
++ shell_check_ooms(*pzBuf);
}
- while(*z && isspace(*z)) ++z;
- zBegin = z;
- for(; *z && '\n'!=*z; ++nZ, ++z){}
- if(nZ>0 && '\r'==zBegin[nZ-1]){
- --nZ;
- }
- shellState.wasm.zPos = z;
- zLine = realloc(zPrior, nZ+1);
- shell_check_oom(zLine);
- memcpy(zLine, zBegin, nZ);
- zLine[nZ] = 0;
- return zLine;
}
-#endif /* SQLITE_SHELL_FIDDLE */
/*
-** Read input from *in and process it. If *in==0 then input
-** is interactive - the user is typing it it. Otherwise, input
-** is coming from a file or device. A prompt is issued and history
-** is saved only if input is interactive. An interrupt signal will
-** cause this routine to exit immediately, unless input is interactive.
+** Read input from designated source (p->pInSource) and process it.
+** If pInSource==0 then input is interactive - the user is typing it.
+** Otherwise, input is coming from a file, stream device or string.
+** Prompts issue and history is saved only for interactive input.
+** An interrupt signal will cause this routine to exit immediately,
+** with "exit demanded" code returned, unless input is interactive.
**
-** Return the number of errors.
-*/
-static int process_input(ShellState *p){
- char *zLine = 0; /* A single input line */
- char *zSql = 0; /* Accumulated SQL text */
- i64 nLine; /* Length of current line */
- i64 nSql = 0; /* Bytes of zSql[] used */
- i64 nAlloc = 0; /* Allocated zSql[] space */
- int rc; /* Error code */
- int errCnt = 0; /* Number of errors seen */
- i64 startline = 0; /* Line number for start of current input */
- QuickScanState qss = QSS_Start; /* Accumulated line status (so far) */
-
- if( p->inputNesting==MAX_INPUT_NESTING ){
- /* This will be more informative in a later version. */
- utf8_printf(stderr,"Input nesting limit (%d) reached at line %d."
- " Check recursion.\n", MAX_INPUT_NESTING, p->lineno);
- return 1;
- }
- ++p->inputNesting;
- p->lineno = 0;
- CONTINUE_PROMPT_RESET;
- while( errCnt==0 || !bail_on_error || (p->in==0 && stdin_is_interactive) ){
- fflush(p->out);
- zLine = one_input_line(p->in, zLine, nSql>0);
- if( zLine==0 ){
- /* End of input */
- if( p->in==0 && stdin_is_interactive ) printf("\n");
- break;
- }
- if( seenInterrupt ){
- if( p->in!=0 ) break;
- seenInterrupt = 0;
- }
- p->lineno++;
- if( QSS_INPLAIN(qss)
- && line_is_command_terminator(zLine)
- && line_is_complete(zSql, nSql) ){
- memcpy(zLine,";",2);
- }
- qss = quickscan(zLine, qss, CONTINUE_PROMPT_PSTATE);
- if( QSS_PLAINWHITE(qss) && nSql==0 ){
- /* Just swallow single-line whitespace */
- echo_group_input(p, zLine);
- qss = QSS_Start;
- continue;
- }
- if( zLine && (zLine[0]=='.' || zLine[0]=='#') && nSql==0 ){
- CONTINUE_PROMPT_RESET;
- echo_group_input(p, zLine);
- if( zLine[0]=='.' ){
- rc = do_meta_command(zLine, p);
- if( rc==2 ){ /* exit requested */
+** Returns are the post-execute values of enum DotCmdRC:
+** DCR_Ok, DCR_Return, DCR_Exit, DCR_Abort
+** each of which may be bit-wise or'ed with DCR_Error.
+*/
+static DotCmdRC process_input(ShellInState *psi){
+ char *zLineInput = 0; /* a line-at-a-time input buffer or usable result */
+ char *zLineAccum = 0; /* accumulation buffer, used for multi-line input */
+ /* Above two pointers could be local to the group handling loop, but are
+ * not so that the number of memory allocations can be reduced. They are
+ * reused from one incoming group to another, realloc()'ed as needed. */
+ i64 naAccum = 0; /* tracking how big zLineAccum buffer has become */
+ /* Flag for ending the overall group processing loop, always 1 or 0 */
+ u8 bInputEnd = 0;
+ /* Termination kind: DCR_Ok, DCR_Error, DCR_Return, DCR_Exit, DCR_Abort,
+ * the greatest of whichever is applicable */
+ u8 termKind = (XSS(psi)->shellAbruptExit==0)? DCR_Ok : DCR_Exit;
+ /* Flag to indicate input from shell invocation argument. */
+ u8 bInvokeArg = INSOURCE_IS_INVOKEARG(psi->pInSource);
+ /* Flag to affect prompting and interrupt action */
+ u8 bInteractive = INSOURCE_IS_INTERACTIVE(psi->pInSource);
+ int nErrors = 0; /* count of errors during execution or its prep */
+
+ /* Block overly-recursive or absurdly nested input redirects. */
+ if( psi->inputNesting>=MAX_INPUT_NESTING ){
+ InSource *pInSrc = psi->pInSource->pFrom;
+ const char *zLead = "Input nesting limit ("
+ SHELL_STRINGIFY(MAX_INPUT_NESTING)") reached,";
+ int i = 3;
+ assert(pInSrc!=0 && MAX_INPUT_NESTING>0);
+ while( i-->0 && pInSrc!=0 ){
+ utf8_printf(STD_ERR,
+ "%s from line %d of \"%s\"",
+ zLead, pInSrc->lineno, pInSrc->zSourceSay);
+ zLead = (i%2==0)? "\n" : "";
+ pInSrc=pInSrc->pFrom;
+ }
+ utf8_printf(STD_ERR, " ...\nError: Check recursion.\n");
+ return DCR_Error;
+ }
+ ++psi->inputNesting;
+
+ /* line-group processing loop (per SQL block, dot-command or comment) */
+ while( !bInputEnd && termKind==DCR_Ok ){
+#if SHELL_DYNAMIC_EXTENSION
+ ScriptSupport *pSS = psi->script;
+#endif
+ int nGroupLines = 0; /* count of lines belonging to this group */
+ i64 ncLineIn = 0; /* how many (non-zero) chars are in zLineInput */
+ i64 ncLineAcc = 0; /* how many (non-zero) chars are in zLineAccum */
+ i64 iLastLine = 0; /* index of last accumulated line start */
+ /* Initialize resumable scanner(s). */
+ SqlScanState sqScanState = SSS_Start; /* for SQL scan */
+#if SHELL_EXTENDED_PARSING
+ DCmd_ScanState dcScanState = DCSS_Start; /* for dot-command scan */
+ int nLeadWhite = 0; /* skips over initial whitespace to . or # */
+ char cLineEnd = '\n'; /* May be swallowed or replaced with space. */
+#else
+# define nLeadWhite 0 /* For legacy parsing, no white before . or # . */
+# define cLineEnd '\n' /* For legacy parsing, this always joins lines. */
+#endif
+ /* An ordered enum to record kind of incoming line group. Its ordering
+ * means than a value greater than Comment implies something runnable.
+ */
+ enum { Tbd = 0, Eof, Comment, Sql, Cmd
+#if SHELL_DYNAMIC_EXTENSION
+ , Script
+#endif
+ } inKind = Tbd;
+ /* An enum signifying the group disposition state */
+ enum {
+ Incoming, Runnable, Dumpable, Erroneous, Ignore
+ } disposition = Incoming;
+ char **pzLineUse = &zLineInput; /* ref line to be processed */
+ i64 *pncLineUse = &ncLineIn; /* ref that line's char count */
+ int iStartline = 0; /* starting line number of group */
+
+ seenInterrupt = 0;
+ fflush(psi->out);
+ CONTINUE_PROMPT_RESET;
+ zLineInput = one_input_line(psi->pInSource, zLineInput,
+ nGroupLines>0, &shellPrompts);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
+ inKind = Eof;
+ disposition = Ignore;
+ if( bInteractive ) printf("\n");
+ }else{
+ ++nGroupLines;
+ iStartline = psi->pInSource->lineno;
+ ncLineIn = strlen30(zLineInput);
+ if( seenInterrupt ){
+ seenInterrupt = 0;
+ disposition = Dumpable;
+ }else{
+ /* Classify and check for single-line dispositions, prep for more. */
+#if SHELL_EXTENDED_PARSING
+ nLeadWhite = (SHEXT_PARSING(psi))
+ ? skipWhite(zLineInput)-zLineInput
+ : 0; /* Disallow leading whitespace for . or # in legacy mode. */
+#endif
+#if SHELL_DYNAMIC_EXTENSION
+ if( pSS && pSS->pMethods->isScriptLeader(pSS, zLineInput+nLeadWhite) ){
+ inKind = Script;
+ }else
+#endif
+ {
+ switch( zLineInput[nLeadWhite] ){
+ case '.':
+ inKind = Cmd;
+ dot_command_scan(zLineInput+nLeadWhite, &dcScanState,
+ CONTINUE_PROMPT_PSTATE);
+ break;
+ case '#':
+ inKind = Comment;
+ break;
+ default:
+ /* Might be SQL, or a swallowable whole SQL comment. */
+ sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
+ if( SSS_PLAINWHITE(sqScanState) ){
+ /* It's either all blank or a whole SQL comment. Swallowable. */
+ inKind = Comment;
+ }else{
+ /* Something dark, not a # comment or dot-command. Must be SQL. */
+ inKind = Sql;
+ }
+ break;
+ }
+ }
+ }
+ } /* end read/classify initial group input line */
+
+ /* Here, if not at end of input, the initial line of group is in, and
+ * it has been scanned and classified. Next, do the processing needed
+ * to recognize whether the initial line or accumulated group so far
+ * is complete such that it may be run, and perform joining of more
+ * lines into the group while it is not so complete. This loop ends
+ * with the input group line(s) ready to be run, or if the input ends
+ * before it is ready, with the group marked as erroneous.
+ */
+ while( disposition==Incoming ){
+ PROMPTS_UPDATE(inKind == Sql || inKind == Cmd);
+ /* Check whether more to accumulate, or ready for final disposition. */
+ switch( inKind ){
+ case Comment:
+ disposition = Dumpable;
+ case Cmd:
+#if SHELL_EXTENDED_PARSING
+ if( SHEXT_PARSING(psi) ){
+ if( line_join_done(dcScanState, *pzLineUse, pncLineUse, &cLineEnd) ){
+ disposition = Runnable;
+ }
+ }else
+#endif
+ disposition = Runnable; /* Legacy, any dot-command line is ready. */
+ break;
+#if SHELL_DYNAMIC_EXTENSION
+ case Script:
+ if( pSS==0
+ || pSS->pMethods->scriptIsComplete(pSS, *pzLineUse+nLeadWhite, 0) ){
+ disposition = Runnable;
+ }
+ break;
+#endif
+ case Sql:
+ /* Check to see if it is complete and ready to run. */
+ if( SSS_SEMITERM(sqScanState) && 1==sqlite3_complete(*pzLineUse)){
+ disposition = Runnable;
+ }else if( SSS_PLAINWHITE(sqScanState) ){
+ /* It is a leading single-line or multi-line comment. */
+ disposition = Runnable;
+ inKind = Comment;
+ }else{
+ char *zT = line_is_command_terminator(zLineInput);
+ if( zT!=0 ){
+ /* Last line is a lone go or / -- prep for running it. */
+ if( nGroupLines>1 ){
+ disposition = Runnable;
+ memcpy(*pzLineUse+iLastLine,";\n",3);
+ *pncLineUse = iLastLine + 2;
+ }else{
+ /* Unless nothing preceded it, then dump it. */
+ disposition = Dumpable;
+ }
+ }
+ }
+ break;
+ case Tbd: case Eof: default: assert(0); /* Not reachable */
+ } /* end switch on inKind */
+ /* Collect and accumulate more input if group not yet complete. */
+ if( disposition==Incoming ){
+ if( nGroupLines==1 ){
+ grow_line_buffer(&zLineAccum, &naAccum, ncLineIn+2);
+ /* Copy line just input */
+ memcpy(zLineAccum, zLineInput, ncLineIn);
+ zLineAccum[ncLineIn] = 0;
+ ncLineAcc = ncLineIn;
+ pzLineUse = &zLineAccum;
+ pncLineUse = &ncLineAcc;
+ }
+ /* Read in next line of group, (if available.) */
+ zLineInput = one_input_line(psi->pInSource, zLineInput,
+ nGroupLines>0, &shellPrompts);
+ if( zLineInput==0 ){
+ bInputEnd = 1;
+ if( inKind==Sql && bInvokeArg ){
+ /* As a special dispensation, SQL arguments on the command line
+ ** do not need to end with ';' (or a lone go.) */
+ if( nGroupLines>1 ){
+ grow_line_buffer(&zLineAccum, &naAccum, ncLineAcc+2);
+ }
+ strcpy( zLineAccum+ncLineAcc, ";" );
+ if( 1==sqlite3_complete(*pzLineUse) ){
+ zLineAccum[ncLineAcc] = 0;
+ disposition = Runnable;
+ continue;
+ }
+ }
+ disposition = Erroneous;
+ inKind = Eof;
+ if( bInteractive ) printf("\n");
+ continue;
+ }
+ ++nGroupLines;
+ ncLineIn = strlen30(zLineInput);
+ /* Scan line just input (if needed) and append to accumulation. */
+ switch( inKind ){
+ case Cmd:
+ dot_command_scan(zLineInput, &dcScanState, CONTINUE_PROMPT_PSTATE);
+ break;
+ case Sql:
+ sql_prescan(zLineInput, &sqScanState, CONTINUE_PROMPT_PSTATE);
+ break;
+ default:
break;
- }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;
+#if SHELL_EXTENDED_PARSING
+ cLineEnd = '\n'; /* reset to default after use */
+#endif
+ memcpy(zLineAccum+ncLineAcc, zLineInput, ncLineIn);
+ iLastLine = ncLineAcc;
+ ncLineAcc += ncLineIn;
+ zLineAccum[ncLineAcc] = 0;
+ } /* end glom another line */
+ } /* end group collection loop */
+ /* Here, the group is fully collected or known to be incomplete forever. */
+ CONTINUE_PROMPT_RESET;
+ switch( disposition ){
+ case Dumpable:
+ echo_group_input(psi, *pzLineUse);
+#if SHELL_DYNAMIC_EXTENSION
+ if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
+#endif
+ break;
+ case Runnable:
+ switch( inKind ){
+ case Sql:
+ echo_group_input(psi, *pzLineUse);
+ nErrors += runOneSqlLine(XSS(psi), *pzLineUse,
+ INSOURCE_IS_INTERACTIVE(psi->pInSource),
+ iStartline);
+ break;
+ case Cmd: {
+ DotCmdRC dcr;
+ echo_group_input(psi, *pzLineUse);
+ dcr = do_dot_command(*pzLineUse+nLeadWhite, XSS(psi));
+ nErrors += (dcr & DCR_Error);
+ dcr &= ~DCR_Error;
+ if( dcr > termKind ) termKind = dcr;
+ break;
}
- qss = QSS_Start;
- continue;
- }
- /* No single-line dispositions remain; accumulate line(s). */
- nLine = strlen(zLine);
- if( nSql+nLine+2>=nAlloc ){
- /* Grow buffer by half-again increments when big. */
- nAlloc = nSql+(nSql>>1)+nLine+100;
- zSql = realloc(zSql, nAlloc);
- shell_check_oom(zSql);
- }
- if( nSql==0 ){
- i64 i;
- for(i=0; zLine[i] && IsSpace(zLine[i]); i++){}
- assert( nAlloc>0 && zSql!=0 );
- memcpy(zSql, zLine+i, nLine+1-i);
- startline = p->lineno;
- nSql = nLine-i;
- }else{
- zSql[nSql++] = '\n';
- memcpy(zSql+nSql, zLine, nLine+1);
- nSql += nLine;
- }
- if( nSql && QSS_SEMITERM(qss) && sqlite3_complete(zSql) ){
- echo_group_input(p, zSql);
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- CONTINUE_PROMPT_RESET;
- nSql = 0;
- if( p->outCount ){
- output_reset(p);
- p->outCount = 0;
- }else{
- clearTempFile(p);
+#if SHELL_DYNAMIC_EXTENSION
+ case Script: {
+ char *zErr = 0;
+ DotCmdRC dcr;
+ assert(pSS!=0);
+ /* Consider: Should echo flag be honored here? */
+ pSS->pMethods->resetCompletionScan(pSS);
+ dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite,
+ XSS(psi), &zErr);
+ if( dcr!=DCR_Ok || zErr!=0 ){
+ /* Future: Handle errors more informatively and like dot commands. */
+ nErrors += (dcr!=DCR_Ok);
+ if( zErr!=0 ){
+ utf8_printf(STD_ERR, "Error: %s\n", zErr);
+ sqlite3_free(zErr);
+ }
+ }
+ break;
}
- p->bSafeMode = p->bSafeModePersist;
- qss = QSS_Start;
- }else if( nSql && QSS_PLAINWHITE(qss) ){
- echo_group_input(p, zSql);
- nSql = 0;
- qss = QSS_Start;
+#endif
+ default:
+ assert(inKind!=Tbd);
+ break;
+ }
+ if( XSS(psi)->shellAbruptExit!=0 ){
+ termKind = DCR_Exit;
+ }
+ break;
+ case Erroneous:
+ utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n",
+ psi->pInSource->lineno, psi->pInSource->zSourceSay);
+#if SHELL_DYNAMIC_EXTENSION
+ if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS);
+#endif
+ ++nErrors;
+ break;
+ case Ignore:
+ break;
+ default: assert(0);
}
+ if( bail_on_error && nErrors>0 && termKind==DCR_Ok ) termKind = DCR_Error;
+ } /* end group consume/prep/(run, dump or complain) loop */
+
+ /* Cleanup and determine return value based on flags and error count. */
+ free(zLineInput); /* Allocated via malloc() by readline or equivalents. */
+ sqlite3_free(zLineAccum);
+
+ /* Translate DCR_Return because it has been done here, not to propagate
+ * unless input is from shell invocation argument. */
+ if( termKind==DCR_Return && psi->pInSource!=&cmdInSource ){
+ termKind = DCR_Ok;
}
- if( nSql ){
- /* This may be incomplete. Let the SQL parser deal with that. */
- echo_group_input(p, zSql);
- errCnt += runOneSqlLine(p, zSql, p->in, startline);
- CONTINUE_PROMPT_RESET;
- }
- free(zSql);
- free(zLine);
- --p->inputNesting;
- return errCnt>0;
+ return termKind|(nErrors>0);
}
/*
}
}
+static void sayInterrupted(void){
+ if( seenInterrupt ){
+ fprintf(stderr, "SQLite CLI interrupted.\n");
+ }
+}
+
/*
- ** Initialize the state information in datai and datax.
-** Initialize the state information in data
++** Initialize the internal and external shell state information.
+** Does no heap allocation, so will do no OOM abort.
*/
-static void main_init(ShellState *data) {
- memset(data, 0, sizeof(*data));
- data->normalMode = data->cMode = data->mode = MODE_List;
- data->autoExplain = 1;
- data->pAuxDb = &data->aAuxDb[0];
- memcpy(data->colSeparator,SEP_Column, 2);
- memcpy(data->rowSeparator,SEP_Row, 2);
- data->showHeader = 0;
- data->shellFlgs = SHFLG_Lookaside;
- sqlite3_config(SQLITE_CONFIG_LOG, shellLog, data);
-#if !defined(SQLITE_SHELL_FIDDLE)
+static void main_init(ShellInState *pDatai, ShellExState *pDatax) {
+ memset(pDatai, 0, sizeof(*pDatai));
+ memset(pDatax, 0, sizeof(*pDatax));
+ pDatax->sizeofThis = sizeof(*pDatax);
+ pDatax->pSIS = pDatai;
+ pDatai->pSXS = pDatax;
+ pDatax->pShowHeader = &pDatai->showHeader;
+ pDatax->zFieldSeparator = &pDatai->colSeparator[0];
+ pDatax->zRecordSeparator = &pDatai->rowSeparator[0];
+ pDatax->zNullValue = &pDatai->nullValue[0];
+ pDatai->out = STD_OUT;
+ pDatai->normalMode = pDatai->cMode = pDatai->mode = MODE_List;
+ pDatai->autoExplain = 1;
+ pDatai->pAuxDb = &pDatai->aAuxDb[0];
+ memcpy(pDatai->colSeparator,SEP_Column, 2);
+ memcpy(pDatai->rowSeparator,SEP_Row, 2);
+ pDatai->showHeader = 0;
+ pDatai->shellFlgs = SHFLG_Lookaside;
+ sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
+#ifndef SQLITE_SHELL_FIDDLE
verify_uninitialized();
+#else
+ datai.zDefaultDbName = "/fiddle.sqlite3";
#endif
sqlite3_config(SQLITE_CONFIG_URI, 1);
+ sqlite3_config(SQLITE_CONFIG_LOG, shellLog, pDatai);
sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
sqlite3_snprintf(sizeof(mainPrompt), mainPrompt,"sqlite> ");
sqlite3_snprintf(sizeof(continuePrompt), continuePrompt," ...> ");
- ** Takedown and deallocate data structures held in datai and datax.
+ /* Source at EOF (for now), saying it is command line. */
+ pDatai->pInSource = &cmdInSource;
+}
+
+/*
- /* Forcefull unsubscribe static extension event listeners. */
++** Takedown and deallocate data structures held in shell state structs.
+** Does no heap allocation, so will do no OOM abort. It leaves those
+** structs zero-initialized to aid valgrind memory leak reporting.
+*/
+static void main_cleanup(ShellInState *psi, ShellExState *psx) {
+ int i;
+ set_table_name(psx, 0);
+ if( psx->dbUser ){
+ session_close_all(psi, -1);
+#if SHELL_DYNAMIC_EXTENSION
+ notify_subscribers(psi, NK_DbAboutToClose, psx->dbUser);
+#endif
+ close_db(psx->dbUser);
+ }
+ for(i=0; i<ArraySize(psi->aAuxDb); i++){
+ sqlite3_free(psi->aAuxDb[i].zFreeOnClose);
+ if( psi->aAuxDb[i].db ){
+ session_close_all(psi, i);
+ close_db(psi->aAuxDb[i].db);
+ }
+ }
+ find_home_dir(1);
+ output_reset(psi);
+ psi->doXdgOpen = 0;
+ clearTempFile(psi);
+ sqlite3_free(psi->zEditor);
+ explain_data_delete(psi);
+#if SHELL_DYNAMIC_EXTENSION
+ notify_subscribers(psi, NK_DbAboutToClose, psx->dbShell);
+ /* It is necessary that the shell DB be closed after the user DBs.
+ * This is because loaded extensions are held by the shell DB and
+ * are therefor (automatically) unloaded when it is closed. */
+ notify_subscribers(psi, NK_ExtensionUnload, psx->dbShell);
+ /* Forcefully unsubscribe any extension which ignored above or did
+ * not unsubscribe upon getting above event. */
+ unsubscribe_extensions(psi);
+ /* This must be done before any extensions unload. */
+ free_all_shext_tracking(psi);
+ /* Unload extensions and free the DB used for dealing with them. */
+ sqlite3_close(psx->dbShell);
+ /* This notification can only reach statically linked extensions. */
+ notify_subscribers(psi, NK_ShutdownImminent, 0);
++ /* Forcefully unsubscribe static extension event listeners. */
+ subscribe_events(psx, 0, psx, NK_Unsubscribe, 0);
+#endif /* SHELL_DYNAMIC_EXTENSION */
+ free(psx->pSpecWidths);
+ free(psi->zNonce);
+ for(i=0; i<psi->nSavedModes; ++i) sqlite3_free(psi->pModeStack[i]);
+ /* Clear shell state objects so that valgrind detects real memory leaks. */
+ memset(psi, 0, sizeof(*psi));
+ memset(psx, 0, sizeof(*psx));
+}
+
+/* Setup or restore ^C interrupt handling. */
+static void interruption_alter(int setup_nrestore){
+#if defined(SIGINT)
+# if !defined(SHELL_LEGACY_SIGINT)
+ static struct sigaction cli_sigaction = { 0 };
+ static struct sigaction cli_sigrestore = { 0 };
+ if( setup_nrestore ){
+ /* Leave .sa_mask and .sa_flags zero-initialized. */
+ cli_sigaction.sa_handler = interrupt_handler;
+ sigaction(SIGINT,&cli_sigaction,&cli_sigrestore);
+ }else{
+ sigaction(SIGINT,&cli_sigrestore,&cli_sigaction);
+ }
+# else
+ static __sighandler_t cli_sigrestore = SIG_DFL;
+ if( setup_nrestore ){
+ cli_sigrestore = signal(SIGINT, interrupt_handler);
+ }else{
+ signal(SIGINT, cli_sigrestore);
+ }
+# endif
+#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
+ BOOL add = setup_nrestore!=0;
+ BOOL success = SetConsoleCtrlHandler(ConsoleCtrlHandler, add);
+ if( add && !success ) fprintf(stderr, "No ^C handler.\n");
+#else
+ (void)setup_nrestore;
+#endif
+}
+
+/*
+** Prepare execution environment for running shell.
+** Return is SQLITE_OK on success, else an error code.
+*/
+static int execution_prepare(ShellInState *psi, ShellExState *psx){
+ /* Register the control-C (SIGINT) handler.
+ ** Make sure we have a valid signal handler early, before anything
+ ** is done that might take long. */
+ interruption_alter(1);
+ /* Ensure stderr is unbuffered. */
+ setvbuf(STD_ERR, 0, _IONBF, 0);
+#ifdef SQLITE_SHELL_FIDDLE
+ stdin_is_interactive = 0;
+ stdout_is_console = 1;
+#else
+ stdin_is_interactive = isatty(0);
+ stdout_is_console = isatty(1);
+#endif
+#if SHELL_WIN_UTF8_OPT
+ upon_terminate(console_restore);
+#endif
+ upon_terminate(sayInterrupted);
+
+#if !defined(_WIN32_WCE)
+ if( getenv("SQLITE_DEBUG_BREAK") ){
+ if( isatty(0) && isatty(2) ){
+ fprintf(STD_ERR,
+ "attach debugger to process %d and press any key to continue.\n",
+ GETPID());
+ fgetc(STD_IN);
+ }else{
+#if defined(_WIN32) || defined(WIN32)
+#if SQLITE_OS_WINRT
+ __debugbreak();
+#else
+ DebugBreak();
+#endif
+#elif defined(SIGTRAP)
+ raise(SIGTRAP);
+#endif
+ }
+ }
+#endif
+
+ /**** Data initialization. ****/
+ main_init(psi,psx);
+ init_std_inputs(stdin);
+#if SHELL_DATAIO_EXT
+ {
+ BuiltInFFExporter *pFFX
+ = (BuiltInFFExporter *)sqlite3_malloc(sizeof(BuiltInFFExporter));
+ BuiltInCMExporter *pCMX
+ = (BuiltInCMExporter *)sqlite3_malloc(sizeof(BuiltInCMExporter));
+ if( pFFX!=0 && pCMX!=0 ){
+ BuiltInFFExporter ffExporter = BI_FF_EXPORTER_INIT( psi );
+ BuiltInCMExporter cmExporter = BI_CM_EXPORTER_INIT( psi );
+ memcpy(pFFX, &ffExporter, sizeof(BuiltInFFExporter));
+ memcpy(pCMX, &cmExporter, sizeof(BuiltInCMExporter));
+ psi->pFreeformExporter = (ExportHandler *)pFFX;
+ psi->pColumnarExporter = (ExportHandler *)pCMX;
+ psi->pActiveExporter = psi->pFreeformExporter;
+ }else{
+ sqlite3_free(pFFX);
+ sqlite3_free(pCMX);
+ return SQLITE_NOMEM;
+ }
+ }
+#endif
+ return SQLITE_OK;
+}
+
+#ifndef SQLITE_SHELL_FIDDLE
+/*
+** Undo execution environment preparation for shell.
+*/
+static void execution_restore(ShellInState *psi, ShellExState *psx){
+#if SHELL_DATAIO_EXT
+ ExportHandler *pEHCM = psi->pColumnarExporter;
+ ExportHandler *pEHFF = psi->pFreeformExporter;
+ pEHCM->pMethods->destruct(pEHCM);
+ pEHFF->pMethods->destruct(pEHFF);
+ sqlite3_free(pEHCM);
+ sqlite3_free(pEHFF);
+#endif
+ main_cleanup(psi, psx);
+ interruption_alter(0);
}
+#endif
/*
** Output text to the console in a font that attracts extra attention.
FOREGROUND_RED|FOREGROUND_INTENSITY
);
#endif
- printf("%s", zText);
-#if !SQLITE_OS_WINRT
- SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes);
+ fprintf(STD_OUT, "%s", zText);
+#if !SQLITE_OS_WINRT
+ SetConsoleTextAttribute(out, defaultScreenInfo.wAttributes);
+#endif
+}
+#else
+static void printBold(const char *zText){
+ fprintf(STD_OUT, "\033[1m%s\033[0m", zText);
+}
+#endif
+
+/*
+** Get the argument to an --option. Throw an error and die if no argument
+** is available.
+*/
+static char *cmdline_option_value(int argc, char **argv, int i){
+ if( i==argc ){
+ utf8_printf(STD_ERR, "%s: Error: missing argument to %s\n",
+ argv[0], argv[argc-1]);
+ quit_moan("invocation error", 1);
+ }
+ return argv[i];
+}
+
+static void zapGlobalDbLock(void){
+ if( pGlobalDbLock ){
+ sqlite3_mutex_free(pGlobalDbLock);
+ pGlobalDbLock = 0;
+ }
+}
+
+/* A vector of command strings collected from the command line. */
+typedef struct CmdArgs {
+ char **azCmd; /* the strings */
+ int nCmd; /* how many collected */
+ u8 bArgsHeld; /* whether "the strings" are owned by this object */
+} CmdArgs;
+/* Freer for above. */
+static void freeCmdArgs(CmdArgs *pca){
+ int i;
+ if( !pca ) return;
+ if( pca->bArgsHeld ){
+ for( i=0; i<pca->nCmd; ++i ){
+ free(pca->azCmd[i]);
+ }
+ }
+ free(pca->azCmd);
+ pca->azCmd = 0;
+ pca->nCmd = 0;
+}
+/* Capacity grower for above. May terminate for OOM. */
+static void growCmdArgs(CmdArgs *pca, int nRoom){
+ void *vaz = realloc(pca->azCmd, sizeof(pca->azCmd[0])*nRoom);
+ shell_check_oomm(vaz);
+ pca->azCmd = (char**)vaz;
+}
+
+/* Data collected during args scanning. */
+typedef struct ArgsData {
+ int readStdin; /* whether stdin will be read */
+ int nOptsEnd; /* where -- seen, else argc */
+ const char *zInitFile; /* specified init file */
+ const char *zVfs; /* -vfs command-line option */
+ short bQuiet; /* -quiet option */
+#if ARCHIVE_ENABLE
+ short bArCmd; /* -A option given */
+#endif /* ARCHIVE_ENABLE */
+} ArgsData;
+
+/*
+** Perform CLI invocation argument processing.
+** This code is collected here for convenience, to declutter main()
+** and to make this processing a little simpler to understand.
+** Parameters are:
+** argc, argv : command-line arguments
+** pass : the pass number, 1 or 2
+** *pcaCmd (out) : arguments preceded by -cmd (which are run first)
+** *pcaBare (out) : non-option or -A arguments (never the DB name)
+** *pad (out, in/out) : option data not held in Shell??State
+**
+** The 1st pass must be done with SQLite uninitialized.
+** The 2nd pass is indifferent to SQLite initialized or not.
+**
+** Returns are: 0 => normal, 1 => error, 2 => quit
+**
- ** This function may terminate abrubtly under OOM conditions.
++** This function may terminate abruptly under OOM conditions.
+*/
+static int scanInvokeArgs(int argc, char **argv, int pass, ShellInState *psi,
+ CmdArgs *pcaCmd, CmdArgs *pcaBare, ArgsData *pad){
+ int rc = 0;
+ DotCmdRC drc;
+ int i;
+ if( pass==1 ){
+ for(i=1; i<argc && rc<2; i++){
+ char *z = argv[i];
+ if( z[0]!='-' || i>pad->nOptsEnd ){
+ if( psi->aAuxDb->zDbFilename==0 ){
+ psi->aAuxDb->zDbFilename = z;
+ }else{
+ growCmdArgs(pcaBare, pcaBare->nCmd+1);
+ pcaBare->azCmd[pcaBare->nCmd++] = z;
- /* Excesss, non-option-like arguments are interpreted as SQL (or
++ /* Excess, non-option-like arguments are interpreted as SQL (or
+ ** dot-commands) and mean that nothing is to be read from stdin. */
+ pad->readStdin = 0;
+ }
+ continue;
+ }
+ if( z[1]=='-' ) z++;
+ if( cli_strcmp(z, "-")==0 ){
+ pad->nOptsEnd = i;
+ continue;
+ }else if( cli_strcmp(z,"-separator")==0
+ || cli_strcmp(z,"-nullvalue")==0
+ || cli_strcmp(z,"-newline")==0
+ || cli_strcmp(z,"-cmd")==0
+ ){
+ (void)cmdline_option_value(argc, argv, ++i);
+ /* Will pickup value on next pass. */
+ }else if( cli_strcmp(z,"-init")==0 ){
+ pad->zInitFile = cmdline_option_value(argc, argv, ++i);
+ }else if( cli_strcmp(z,"-batch")==0 ){
+ /* Need to check for batch mode here to so we can avoid printing
+ ** informational messages (like from process_sqliterc) before
+ ** we do the actual processing of arguments later in a second pass.
+ */
+ stdin_is_interactive = 0;
+ }else if( cli_strcmp(z,"-heap")==0 ){
+#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
+ const char *zSize;
+ sqlite3_int64 szHeap;
+
+ zSize = cmdline_option_value(argc, argv, ++i);
+ szHeap = integerValue(zSize);
+ if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_HEAP,malloc((int)szHeap),(int)szHeap, 64);
+#else
+ (void)cmdline_option_value(argc, argv, ++i);
+#endif
+ }else if( cli_strcmp(z,"-pagecache")==0 ){
+ sqlite3_int64 n, sz;
+ void *pvCache = 0;
+ sz = integerValue(cmdline_option_value(argc,argv,++i));
+ if( sz>70000 ) sz = 70000;
+ if( sz<0 ) sz = 0;
+ n = integerValue(cmdline_option_value(argc,argv,++i));
+ if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
+ n = 0xffffffffffffLL/sz;
+ }
+ verify_uninitialized();
+ if( n>0 && sz>0 ) pvCache = malloc(n*sz);
+ shell_check_oomm(pvCache);
+ sqlite3_config(SQLITE_CONFIG_PAGECACHE, pvCache, sz, n);
+ psi->shellFlgs |= SHFLG_Pagecache;
+ }else if( cli_strcmp(z,"-lookaside")==0 ){
+ int n, sz;
+ sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ if( sz<0 ) sz = 0;
+ n = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ if( n<0 ) n = 0;
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
+ if( sz*n==0 ) psi->shellFlgs &= ~SHFLG_Lookaside;
+ }else if( cli_strcmp(z,"-threadsafe")==0 ){
+ int n;
+ n = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ verify_uninitialized();
+ switch( n ){
+ case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
+ case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
+ default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
+ }
+#ifdef SQLITE_ENABLE_VFSTRACE
+ }else if( cli_strcmp(z,"-vfstrace")==0 ){
+ extern int vfstrace_register(
+ const char *zTraceName,
+ const char *zOldVfsName,
+ int (*xOut)(const char*,void*),
+ void *pOutArg,
+ int makeDefault
+ );
+ vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,STD_ERR,1);
+#endif
+#ifdef SQLITE_ENABLE_MULTIPLEX
+ }else if( cli_strcmp(z,"-multiplex")==0 ){
+ extern int sqlite3_multiplex_initialize(const char*,int);
+ sqlite3_multiplex_initialize(0, 1);
+#endif
+ }else if( cli_strcmp(z,"-mmap")==0 ){
+ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ }else if( cli_strcmp(z,"-sorterref")==0 ){
+ sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
+ verify_uninitialized();
+ sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
+#endif
+ }else if( cli_strcmp(z,"-vfs")==0 ){
+ pad->zVfs = cmdline_option_value(argc, argv, ++i);
+#ifdef SQLITE_HAVE_ZLIB
+ }else if( cli_strcmp(z,"-zip")==0 ){
+ psi->openMode = SHELL_OPEN_ZIPFILE;
+#endif
+ }else if( cli_strcmp(z,"-append")==0 ){
+ psi->openMode = SHELL_OPEN_APPENDVFS;
+#ifndef SQLITE_OMIT_DESERIALIZE
+ }else if( cli_strcmp(z,"-deserialize")==0 ){
+ psi->openMode = SHELL_OPEN_DESERIALIZE;
+ }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
+ psi->szMax = integerValue(argv[++i]);
+#endif
+ }else if( cli_strcmp(z,"-readonly")==0 ){
+ psi->openMode = SHELL_OPEN_READONLY;
+ }else if( cli_strcmp(z,"-nofollow")==0 ){
+ psi->openFlags = SQLITE_OPEN_NOFOLLOW;
+#if ARCHIVE_ENABLE
+ }else if( cli_strncmp(z, "-A",2)==0 ){
+ /* All remaining command-line arguments are passed to the ".archive"
+ ** command, so ignore them */
+ break;
#endif
-}
-#else
-static void printBold(const char *zText){
- printf("\033[1m%s\033[0m", zText);
-}
+ }else if( cli_strcmp(z, "-memtrace")==0 ){
+ sqlite3MemTraceActivate(STD_ERR);
+ }else if( cli_strcmp(z,"-bail")==0 ){
+ bail_on_error = 1;
+#if SHELL_EXTENSIONS
+ }else if( cli_strcmp(z,"-shxopts")==0 ){
+ psi->bExtendedDotCmds = (u8)integerValue(argv[++i]);
#endif
-
-/*
-** Get the argument to an --option. Throw an error and die if no argument
-** is available.
-*/
-static char *cmdline_option_value(int argc, char **argv, int i){
- if( i==argc ){
- utf8_printf(stderr, "%s: Error: missing argument to %s\n",
- argv[0], argv[argc-1]);
- exit(1);
+ }else if( cli_strcmp(z,"-nonce")==0 ){
+ free(psi->zNonce);
+ z = cmdline_option_value(argc,argv,++i);
+ shell_check_oomm(psi->zNonce = strdup(z));
+ }else if( cli_strcmp(z,"-quiet")==0 ){
+ pad->bQuiet = (int)integerValue(cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
+ psi->shellFlgs |= SHFLG_TestingMode;
+ }else if( cli_strcmp(z,"-safe")==0 ){
+ /* catch this on the second pass (Unsafe is fine on invocation.) */
+ }
+ }
+ }else if( pass==2 ){
+ for(i=1; i<argc && rc<2; i++){
+ char *z = argv[i];
+ char *zModeSet = 0;
+ if( z[0]!='-' || i>=pad->nOptsEnd ) continue;
+ if( z[1]=='-' ){ z++; }
+ if( cli_strcmp(z,"-init")==0 ){
+ i++;
+ }else if( cli_strcmp(z,"-html")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-list")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-quote")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-line")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-column")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-json")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-markdown")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-table")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-box")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-csv")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-ascii")==0 ){
+ zModeSet = z;
+ }else if( cli_strcmp(z,"-tabs")==0 ){
+ zModeSet = z;
+#ifdef SQLITE_HAVE_ZLIB
+ }else if( cli_strcmp(z,"-zip")==0 ){
+ psi->openMode = SHELL_OPEN_ZIPFILE;
+#endif
+ }else if( cli_strcmp(z,"-append")==0 ){
+ psi->openMode = SHELL_OPEN_APPENDVFS;
+#ifndef SQLITE_OMIT_DESERIALIZE
+ }else if( cli_strcmp(z,"-deserialize")==0 ){
+ psi->openMode = SHELL_OPEN_DESERIALIZE;
+ }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
+ psi->szMax = integerValue(argv[++i]);
+#endif
+ }else if( cli_strcmp(z,"-readonly")==0 ){
+ psi->openMode = SHELL_OPEN_READONLY;
+ }else if( cli_strcmp(z,"-nofollow")==0 ){
+ psi->openFlags |= SQLITE_OPEN_NOFOLLOW;
+ }else if( cli_strcmp(z,"-separator")==0 ){
+ sqlite3_snprintf(sizeof(psi->colSeparator), psi->colSeparator,
+ "%s",cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-newline")==0 ){
+ sqlite3_snprintf(sizeof(psi->rowSeparator), psi->rowSeparator,
+ "%s",cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-nullvalue")==0 ){
+ sqlite3_snprintf(sizeof(psi->nullValue), psi->nullValue,
+ "%s",cmdline_option_value(argc,argv,++i));
+ }else if( cli_strcmp(z,"-header")==0 ){
+ psi->showHeader = 1;
+ ShellSetFlagI(psi, SHFLG_HeaderSet);
+ }else if( cli_strcmp(z,"-noheader")==0 ){
+ psi->showHeader = 0;
+ ShellSetFlagI(psi, SHFLG_HeaderSet);
+ }else if( cli_strcmp(z,"-echo")==0 ){
+ ShellSetFlagI(psi, SHFLG_Echo);
+ }else if( cli_strcmp(z,"-eqp")==0 ){
+ psi->autoEQP = AUTOEQP_on;
+ }else if( cli_strcmp(z,"-eqpfull")==0 ){
+ psi->autoEQP = AUTOEQP_full;
+ }else if( cli_strcmp(z,"-stats")==0 ){
+ psi->statsOn = 1;
+ }else if( cli_strcmp(z,"-scanstats")==0 ){
+ psi->scanstatsOn = 1;
+ }else if( cli_strcmp(z,"-backslash")==0 ){
+ /* Undocumented command-line option: -backslash
+ ** Causes C-style backslash escapes to be evaluated in SQL statements
+ ** prior to sending the SQL into SQLite. Useful for injecting crazy
+ ** bytes in the middle of SQL statements for testing and debugging.
+ */
+ ShellSetFlagI(psi, SHFLG_Backslash);
+ }else if( cli_strcmp(z,"-bail")==0 ){
+ /* No-op. The bail_on_error flag should already be set. */
+#if SHELL_EXTENSIONS
+ }else if( cli_strcmp(z,"-shxopts")==0 ){
+ i++; /* Handled on first pass. */
+#endif
+ }else if( cli_strcmp(z,"-version")==0 ){
+ fprintf(STD_OUT, "%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
+ rc = 2;
+ }else if( cli_strcmp(z,"-interactive")==0 ){
+ stdin_is_interactive = 1;
+ }else if( cli_strcmp(z,"-batch")==0 ){
+ stdin_is_interactive = 0;
+ }else if( cli_strcmp(z,"-utf8")==0 ){
+#if SHELL_WIN_UTF8_OPT
+ console_utf8 = 1;
+#endif /* SHELL_WIN_UTF8_OPT */
+ }else if( cli_strcmp(z,"-heap")==0 ){
+ i++;
+ }else if( cli_strcmp(z,"-pagecache")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-lookaside")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-threadsafe")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-nonce")==0 ){
+ i+=2;
+ }else if( cli_strcmp(z,"-mmap")==0 ){
+ i++;
+ }else if( cli_strcmp(z,"-memtrace")==0 ){
+ i++;
+#ifdef SQLITE_ENABLE_SORTER_REFERENCES
+ }else if( cli_strcmp(z,"-sorterref")==0 ){
+ i++;
+#endif
+ }else if( cli_strcmp(z,"-vfs")==0 ){
+ i++;
+#ifdef SQLITE_ENABLE_VFSTRACE
+ }else if( cli_strcmp(z,"-vfstrace")==0 ){
+ i++;
+#endif
+#ifdef SQLITE_ENABLE_MULTIPLEX
+ }else if( cli_strcmp(z,"-multiplex")==0 ){
+ i++;
+#endif
+ }else if( cli_strcmp(z,"-help")==0 ){
+ usage(1);
+ rc = 2;
+ break;
+ }else if( cli_strcmp(z,"-cmd")==0 ){
+ /* Run commands that follow -cmd first and separately from commands
+ ** that simply appear on the command-line. This is likely surprising.
+ ** Better would be to run all commands in the order that they appear.
+ ** But we retain this goofy behavior for historical compatibility. */
+ if( i==argc-1 ) break; /* Pretend (un)specified command is empty. */
+ growCmdArgs(pcaCmd, pcaCmd->nCmd+1);
+ pcaCmd->azCmd[pcaCmd->nCmd++] = cmdline_option_value(argc,argv,++i);
+#if ARCHIVE_ENABLE
+ }else if( cli_strncmp(z, "-A", 2)==0 ){
+ if( pcaBare->nCmd>0 ){
+ utf8_printf(STD_ERR, "Error: cannot mix regular SQL or dot-commands"
+ " with \"%s\"\n", z);
+ rc = 1;
+ break;
+ }
+ growCmdArgs(pcaBare, argc-i+1);
+ if( z[2] ) pcaBare->azCmd[pcaBare->nCmd++] = &z[2];
+ while( i<argc ){
+ pcaBare->azCmd[pcaBare->nCmd++] = argv[i++];
+ }
+ pad->readStdin = 0;
+ pad->bArCmd = 1;
+ break;
+#endif /* ARCHIVE_ENABLE */
+ }else if( cli_strcmp(z,"-safe")==0 ){
+ psi->bSafeMode = psi->bSafeModeFuture = 1;
+ }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
+ /* Acted upon in first pass. */
+ }else if( cli_strcmp(z,"-quiet")==0 ){
+ ++i;
+ }else{
+ utf8_printf(STD_ERR,"%s: Error: unknown option: %s\n", Argv0, z);
+ raw_printf(STD_ERR,"Use -help for a list of options.\n");
+ rc = 2;
+ }
+ if( zModeSet!=0 ){
+ char *azModeCmd[] = { ".mode", zModeSet+1 };
+ modeCommand(azModeCmd, 2, XSS(psi), 0);
+ psi->cMode = psi->mode;
+ }
+ }
}
- return argv[i];
-}
-
-static void sayAbnormalExit(void){
- if( seenInterrupt ) fprintf(stderr, "Program interrupted.\n");
+ return rc;
}
#ifndef SQLITE_SHELL_IS_UTF8
#ifdef SQLITE_DEBUG
sqlite3_int64 mem_main_enter = 0;
#endif
- char *zErrMsg = 0;
#ifdef SQLITE_SHELL_FIDDLE
-# define data shellState
+# define datai shellStateI
+# define datax shellStateX
#else
- ShellState data;
+ ShellInState datai;
+ ShellExState datax;
#endif
- const char *zInitFile = 0;
- int i;
- int rc = 0;
+ RipStackDest mainRipDest = RIP_STACK_DEST_INIT;
+ int i; /* General purpose */
+ int iAbruptExitCode;
+ int rc = 0; /* main() exit code */
+ DotCmdRC drc = DCR_Ok;
int warnInmemoryDb = 0;
- int readStdin = 1;
- int nCmd = 0;
- int nOptsEnd = argc;
- char **azCmd = 0;
- const char *zVfs = 0; /* Value of -vfs command-line option */
+ /* azCmd, nCmd, bArgsHeld */
+ CmdArgs cmdArgsCmd = {0,0,0}; /* for -cmd <do_x> invocation arguments */
+ AnyResourceHolder cacRH = {&cmdArgsCmd, (GenericFreer)freeCmdArgs};
+ CmdArgs cmdArgsBare = {0,0,0}; /* for bare or -A invocation arguments */
+ AnyResourceHolder cabRH = {&cmdArgsBare, (GenericFreer)freeCmdArgs};
+ /* readStdin, nOptsEnd, zInitFile, zVfs, bQuiet */
+ ArgsData argsData = { 1, argc, 0,0,0 };
#if !SQLITE_SHELL_IS_UTF8
- char **argvToFree = 0;
- int argcToFree = 0;
-#endif
- setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
-
-#ifdef SQLITE_SHELL_FIDDLE
- stdin_is_interactive = 0;
- stdout_is_console = 1;
- data.wasm.zDefaultDbName = "/fiddle.sqlite3";
-#else
- stdin_is_interactive = isatty(0);
- stdout_is_console = isatty(1);
-#endif
-#if SHELL_WIN_UTF8_OPT
- atexit(console_restore); /* Needs revision for CLI as library call */
-#endif
- atexit(sayAbnormalExit);
-#ifdef SQLITE_DEBUG
- mem_main_enter = sqlite3_memory_used();
-#endif
-#if !defined(_WIN32_WCE)
- if( getenv("SQLITE_DEBUG_BREAK") ){
- if( isatty(0) && isatty(2) ){
- fprintf(stderr,
- "attach debugger to process %d and press any key to continue.\n",
- GETPID());
- fgetc(stdin);
- }else{
-#if defined(_WIN32) || defined(WIN32)
-#if SQLITE_OS_WINRT
- __debugbreak();
-#else
- DebugBreak();
-#endif
-#elif defined(SIGTRAP)
- raise(SIGTRAP);
-#endif
- }
- }
-#endif
- /* Register a valid signal handler early, before much else is done. */
-#ifdef SIGINT
- signal(SIGINT, interrupt_handler);
-#elif (defined(_WIN32) || defined(WIN32)) && !defined(_WIN32_WCE)
- if( !SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE) ){
- fprintf(stderr, "No ^C handler.\n");
- }
+ CmdArgs argsUtf8 = {0,0,1};
+ AnyResourceHolder caRH = {&argsUtf8, (GenericFreer)freeCmdArgs};
#endif
+ RESOURCE_MARK(mark);
+ /* Just return error if using mismatched dynamic SQLite library version. */
#if USE_SYSTEM_SQLITE+0!=1
if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){
- utf8_printf(stderr, "SQLite header and source version mismatch\n%s\n%s\n",
+ utf8_printf(STD_ERR, "SQLite header and source version mismatch\n%s\n%s\n",
sqlite3_sourceid(), SQLITE_SOURCE_ID);
- exit(1);
+ return 1;
}
#endif
- main_init(&data);
- /* On Windows, we must translate command-line arguments into UTF-8.
- ** The SQLite memory allocator subsystem has to be enabled in order to
- ** do this. But we want to run an sqlite3_shutdown() afterwards so that
- ** subsequent sqlite3_config() calls will work. So copy all results into
- ** memory that does not come from the SQLite memory allocator.
- */
-#if !SQLITE_SHELL_IS_UTF8
sqlite3_initialize();
- argvToFree = malloc(sizeof(argv[0])*argc*2);
- shell_check_oom(argvToFree);
- argcToFree = argc;
- argv = argvToFree + argc;
- for(i=0; i<argc; i++){
- char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
- i64 n;
- shell_check_oom(z);
- n = strlen(z);
- argv[i] = malloc( n+1 );
- shell_check_oom(argv[i]);
- memcpy(argv[i], z, n+1);
- argvToFree[i] = argv[i];
- sqlite3_free(z);
+
+ /* Create a mutex for thread-safe query execution interruption. */
+ if( pGlobalDbLock==0 ){
+ pGlobalDbLock = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST);
}
- sqlite3_shutdown();
-#endif
+ upon_terminate(zapGlobalDbLock);
- assert( argc>=1 && argv && argv[0] );
- Argv0 = argv[0];
+#ifdef SQLITE_DEBUG
+ mem_main_enter = sqlite3_memory_used();
+#endif
-#ifdef SQLITE_SHELL_DBNAME_PROC
- {
- /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
- ** of a C-function that will provide the name of the database file. Use
- ** this compile-time option to embed this shell program in larger
- ** applications. */
- extern void SQLITE_SHELL_DBNAME_PROC(const char**);
- SQLITE_SHELL_DBNAME_PROC(&data.pAuxDb->zDbFilename);
- warnInmemoryDb = 0;
- }
+ /* On Windows, we must translate command-line arguments into UTF-8.
+ ** The SQLite memory allocator subsystem has to be enabled in order to
+ ** do this. But we want to run an sqlite3_shutdown() afterwards so that
+ ** subsequent sqlite3_config() calls will work. So copy all results into
+ ** memory that does not come from the SQLite memory allocator.
+ */
+#if !SQLITE_SHELL_IS_UTF8
+ growCmdArgs(&argsUtf8, argc);
+ argsUtf8.nCmd = 0;
+ any_ref_holder(&caRH); /* This will normally activate as shell exits. */
+ for(i=0; i<argc; i++){
+ char *z = sqlite3_win32_unicode_to_utf8(wargv[i]);
+ shell_check_ooms(z);
+ sstr_holder(z);
+ argsUtf8.azCmd[i] = strdup(z);
+ shell_check_oomm(argsUtf8.azCmd[i]);
+ release_holder();
+ ++argsUtf8.nCmd;
+ }
+ argv = argsUtf8.azCmd;
#endif
- /* Do an initial pass through the command-line argument to locate
- ** the name of the database file, the name of the initialization file,
- ** the size of the alternative malloc heap,
- ** and the first command to execute.
+ /**** Execution environment setup. ****/
+ sqlite3_shutdown();
+ if( (rc = execution_prepare(&datai,&datax))!=0 ){
+ utf8_printf(STD_ERR, "Cannot initialize. Quitting.\n");
+ return 1;
+ }
+ sqlite3_initialize();
+ /* From here on, within the true clause of this next test, various
+ ** heap allocations are made which may fail, resulting in an abrupt
+ ** shell exit. Such an exit happens in 1 of 2 ways: A held resource
+ ** stack and the call stack are ripped back to this point; or just
+ ** the held resource stack is ripped back and a process exit occurs.
+ ** This kind of exit is considered unrecoverable, so it is taken for
+ ** all processing, whether of invocation arguments, ~/.sqliterc, or
+ ** input from stdin (and input redirects instigated there.)
*/
-#ifndef SQLITE_SHELL_FIDDLE
- verify_uninitialized();
-#endif
- for(i=1; i<argc; i++){
- char *z;
- z = argv[i];
- if( z[0]!='-' || i>nOptsEnd ){
- if( data.aAuxDb->zDbFilename==0 ){
- data.aAuxDb->zDbFilename = z;
- }else{
- /* Excess arguments are interpreted as SQL (or dot-commands) and
- ** mean that nothing is read from stdin */
- readStdin = 0;
- nCmd++;
- azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd);
- shell_check_oom(azCmd);
- azCmd[nCmd-1] = z;
- }
- continue;
+ register_exit_ripper(&mainRipDest);
+ if( 0==RIP_TO_HERE(mainRipDest) ){
+ /**** Input processing. ****/
+
+ assert( argc>=1 && argv && argv[0] );
+ Argv0 = argv[0];
+#if SHELL_DYNAMIC_EXTENSION
+ initStartupDir();
+ if( isExtendedBasename(Argv0) ){
+ datai.bExtendedDotCmds = SHELL_ALL_EXTENSIONS;
}
- if( z[1]=='-' ) z++;
- if( cli_strcmp(z, "-")==0 ){
- nOptsEnd = i;
- continue;
- }else if( cli_strcmp(z,"-separator")==0
- || cli_strcmp(z,"-nullvalue")==0
- || cli_strcmp(z,"-newline")==0
- || cli_strcmp(z,"-cmd")==0
- ){
- (void)cmdline_option_value(argc, argv, ++i);
- }else if( cli_strcmp(z,"-init")==0 ){
- zInitFile = cmdline_option_value(argc, argv, ++i);
- }else if( cli_strcmp(z,"-batch")==0 ){
- /* Need to check for batch mode here to so we can avoid printing
- ** informational messages (like from process_sqliterc) before
- ** we do the actual processing of arguments later in a second pass.
- */
- stdin_is_interactive = 0;
- }else if( cli_strcmp(z,"-heap")==0 ){
-#if defined(SQLITE_ENABLE_MEMSYS3) || defined(SQLITE_ENABLE_MEMSYS5)
- const char *zSize;
- sqlite3_int64 szHeap;
-
- zSize = cmdline_option_value(argc, argv, ++i);
- szHeap = integerValue(zSize);
- if( szHeap>0x7fff0000 ) szHeap = 0x7fff0000;
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_HEAP, malloc((int)szHeap), (int)szHeap, 64);
-#else
- (void)cmdline_option_value(argc, argv, ++i);
-#endif
- }else if( cli_strcmp(z,"-pagecache")==0 ){
- sqlite3_int64 n, sz;
- sz = integerValue(cmdline_option_value(argc,argv,++i));
- if( sz>70000 ) sz = 70000;
- if( sz<0 ) sz = 0;
- n = integerValue(cmdline_option_value(argc,argv,++i));
- if( sz>0 && n>0 && 0xffffffffffffLL/sz<n ){
- n = 0xffffffffffffLL/sz;
- }
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_PAGECACHE,
- (n>0 && sz>0) ? malloc(n*sz) : 0, sz, n);
- data.shellFlgs |= SHFLG_Pagecache;
- }else if( cli_strcmp(z,"-lookaside")==0 ){
- int n, sz;
- sz = (int)integerValue(cmdline_option_value(argc,argv,++i));
- if( sz<0 ) sz = 0;
- n = (int)integerValue(cmdline_option_value(argc,argv,++i));
- if( n<0 ) n = 0;
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, n);
- if( sz*n==0 ) data.shellFlgs &= ~SHFLG_Lookaside;
- }else if( cli_strcmp(z,"-threadsafe")==0 ){
- int n;
- n = (int)integerValue(cmdline_option_value(argc,argv,++i));
- verify_uninitialized();
- switch( n ){
- case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
- case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
- default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
- }
-#ifdef SQLITE_ENABLE_VFSTRACE
- }else if( cli_strcmp(z,"-vfstrace")==0 ){
- extern int vfstrace_register(
- const char *zTraceName,
- const char *zOldVfsName,
- int (*xOut)(const char*,void*),
- void *pOutArg,
- int makeDefault
- );
- vfstrace_register("trace",0,(int(*)(const char*,void*))fputs,stderr,1);
#endif
-#ifdef SQLITE_ENABLE_MULTIPLEX
- }else if( cli_strcmp(z,"-multiplex")==0 ){
- extern int sqlite3_multiplex_initialize(const char*,int);
- sqlite3_multiplex_initialize(0, 1);
-#endif
- }else if( cli_strcmp(z,"-mmap")==0 ){
- sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_MMAP_SIZE, sz, sz);
-#if defined(SQLITE_ENABLE_SORTER_REFERENCES)
- }else if( cli_strcmp(z,"-sorterref")==0 ){
- sqlite3_int64 sz = integerValue(cmdline_option_value(argc,argv,++i));
- verify_uninitialized();
- sqlite3_config(SQLITE_CONFIG_SORTERREF_SIZE, (int)sz);
-#endif
- }else if( cli_strcmp(z,"-vfs")==0 ){
- zVfs = cmdline_option_value(argc, argv, ++i);
-#ifdef SQLITE_HAVE_ZLIB
- }else if( cli_strcmp(z,"-zip")==0 ){
- data.openMode = SHELL_OPEN_ZIPFILE;
+
+ any_ref_holder(&cacRH);
+ any_ref_holder(&cabRH);
+
+#ifdef SQLITE_SHELL_DBNAME_PROC
+ {
+ /* If the SQLITE_SHELL_DBNAME_PROC macro is defined, then it is the name
+ ** of a C-function that will provide the name of the database file. Use
+ ** this compile-time option to embed this shell program in larger
+ ** applications. */
+ extern void SQLITE_SHELL_DBNAME_PROC(const char**);
+ SQLITE_SHELL_DBNAME_PROC(&datai.pAuxDb->zDbFilename);
+ warnInmemoryDb = 0;
+ }
#endif
- }else if( cli_strcmp(z,"-append")==0 ){
- data.openMode = SHELL_OPEN_APPENDVFS;
-#ifndef SQLITE_OMIT_DESERIALIZE
- }else if( cli_strcmp(z,"-deserialize")==0 ){
- data.openMode = SHELL_OPEN_DESERIALIZE;
- }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
- data.szMax = integerValue(argv[++i]);
-#endif
- }else if( cli_strcmp(z,"-readonly")==0 ){
- data.openMode = SHELL_OPEN_READONLY;
- }else if( cli_strcmp(z,"-nofollow")==0 ){
- data.openFlags = SQLITE_OPEN_NOFOLLOW;
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
- }else if( cli_strncmp(z, "-A",2)==0 ){
- /* All remaining command-line arguments are passed to the ".archive"
- ** command, so ignore them */
- break;
+
+ /* Do an initial pass through the command-line argument to locate
+ ** the name of the database file, the name of the initialization file,
+ ** the size of the alternative malloc heap,
+ ** and the first command to execute.
+ */
+ sqlite3_shutdown();
+#ifndef SQLITE_SHELL_FIDDLE
+ verify_uninitialized();
#endif
- }else if( cli_strcmp(z, "-memtrace")==0 ){
- sqlite3MemTraceActivate(stderr);
- }else if( cli_strcmp(z,"-bail")==0 ){
- bail_on_error = 1;
- }else if( cli_strcmp(z,"-nonce")==0 ){
- free(data.zNonce);
- data.zNonce = strdup(cmdline_option_value(argc, argv, ++i));
- }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
- ShellSetFlag(&data,SHFLG_TestingMode);
- }else if( cli_strcmp(z,"-safe")==0 ){
- /* no-op - catch this on the second pass */
- }
- }
+ i = scanInvokeArgs(argc, argv, /*pass*/ 1, &datai,
+ &cmdArgsCmd, &cmdArgsBare, &argsData);
#ifndef SQLITE_SHELL_FIDDLE
- verify_uninitialized();
+ verify_uninitialized();
#endif
-
#ifdef SQLITE_SHELL_INIT_PROC
- {
- /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
- ** of a C-function that will perform initialization actions on SQLite that
- ** occur just before or after sqlite3_initialize(). Use this compile-time
- ** option to embed this shell program in larger applications. */
- extern void SQLITE_SHELL_INIT_PROC(void);
- SQLITE_SHELL_INIT_PROC();
- }
+ {
+ /* If the SQLITE_SHELL_INIT_PROC macro is defined, then it is the name
+ ** of a C-function that will perform initialization actions on SQLite that
+ ** occur just before or after sqlite3_initialize(). Use this compile-time
+ ** option to embed this shell program in larger applications.
+ **
+ ** The provided C-function must call sqlite3_initialize() sometime
+ ** before returning (leaving SQLite in the initialized state.)
+ */
+ extern void SQLITE_SHELL_INIT_PROC(void);
+ SQLITE_SHELL_INIT_PROC();
+ }
#else
- /* All the sqlite3_config() calls have now been made. So it is safe
- ** to call sqlite3_initialize() and process any command line -vfs option. */
- sqlite3_initialize();
+ /* All the sqlite3_config() calls have now been made. So it is safe
+ ** to call sqlite3_initialize() and process any command line -vfs option. */
+ sqlite3_initialize();
#endif
- if( zVfs ){
- sqlite3_vfs *pVfs = sqlite3_vfs_find(zVfs);
- if( pVfs ){
- sqlite3_vfs_register(pVfs, 1);
- }else{
- utf8_printf(stderr, "no such VFS: \"%s\"\n", zVfs);
- exit(1);
+ if( argsData.zVfs ){
+ sqlite3_vfs *pVfs = sqlite3_vfs_find(argsData.zVfs);
+ if( pVfs ){
+ sqlite3_vfs_register(pVfs, 1);
+ }else{
+ utf8_printf(STD_ERR, "no such VFS: \"%s\"\n", argsData.zVfs);
+ rc = 1;
+ goto shell_bail;
+ }
}
- }
- if( data.pAuxDb->zDbFilename==0 ){
+ if( datai.pAuxDb->zDbFilename==0 ){
#ifndef SQLITE_OMIT_MEMORYDB
- data.pAuxDb->zDbFilename = ":memory:";
- warnInmemoryDb = argc==1;
+ datai.pAuxDb->zDbFilename = ":memory:";
+ warnInmemoryDb = argc==1;
#else
- utf8_printf(stderr,"%s: Error: no database filename specified\n", Argv0);
- return 1;
+ utf8_printf(STD_ERR,"%s: Error: no database filename specified\n", Argv0);
+ rc = 1;
+ goto shell_bail;
#endif
- }
- data.out = stdout;
+ }
#ifndef SQLITE_SHELL_FIDDLE
- sqlite3_appendvfs_init(0,0,0);
+ sqlite3_appendvfs_init(0,0,0);
#endif
- /* Go ahead and open the database file if it already exists. If the
- ** file does not exist, delay opening it. This prevents empty database
- ** files from being created if a user mistypes the database name argument
- ** to the sqlite command-line tool.
- */
- if( access(data.pAuxDb->zDbFilename, 0)==0 ){
- open_db(&data, 0);
- }
+ /* Go ahead and open the database file if it already exists. If the
+ ** file does not exist, delay opening it. This prevents empty database
+ ** files from being created if a user mistypes the database name argument
+ ** to the sqlite command-line tool.
+ */
+ if( access(datai.pAuxDb->zDbFilename, 0)==0 ){
+ open_db(&datax, 0);
+ }
- /* Process the initialization file if there is one. If no -init option
- ** is given on the command line, look for a file named ~/.sqliterc and
- ** try to process it.
- */
- process_sqliterc(&data,zInitFile);
+ /* Process the initialization file if there is one. If no -init option
+ ** is given on the command line, look for a file named ~/.sqliterc and
+ ** try to process it, without any quitting or bail-on-error.
+ */
+ process_sqliterc(&datai,argsData.zInitFile);
+
+ /* Make a second pass through the command-line argument and set
+ ** options. This second pass is delayed until after the initialization
+ ** file is processed so that the command-line arguments will override
+ ** settings in the initialization file.
+ */
+ rc = scanInvokeArgs(argc, argv, /*pass*/ 2, &datai,
+ &cmdArgsCmd, &cmdArgsBare, &argsData);
+ if( rc>0 ){
+ goto shell_bail;
+ }
- /* Make a second pass through the command-line argument and set
- ** options. This second pass is delayed until after the initialization
- ** file is processed so that the command-line arguments will override
- ** settings in the initialization file.
- */
- for(i=1; i<argc; i++){
- char *z = argv[i];
- if( z[0]!='-' || i>=nOptsEnd ) continue;
- if( z[1]=='-' ){ z++; }
- if( cli_strcmp(z,"-init")==0 ){
- i++;
- }else if( cli_strcmp(z,"-html")==0 ){
- data.mode = MODE_Html;
- }else if( cli_strcmp(z,"-list")==0 ){
- data.mode = MODE_List;
- }else if( cli_strcmp(z,"-quote")==0 ){
- data.mode = MODE_Quote;
- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator, SEP_Comma);
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator, SEP_Row);
- }else if( cli_strcmp(z,"-line")==0 ){
- data.mode = MODE_Line;
- }else if( cli_strcmp(z,"-column")==0 ){
- data.mode = MODE_Column;
- }else if( cli_strcmp(z,"-json")==0 ){
- data.mode = MODE_Json;
- }else if( cli_strcmp(z,"-markdown")==0 ){
- data.mode = MODE_Markdown;
- }else if( cli_strcmp(z,"-table")==0 ){
- data.mode = MODE_Table;
- }else if( cli_strcmp(z,"-box")==0 ){
- data.mode = MODE_Box;
- }else if( cli_strcmp(z,"-csv")==0 ){
- data.mode = MODE_Csv;
- memcpy(data.colSeparator,",",2);
-#ifdef SQLITE_HAVE_ZLIB
- }else if( cli_strcmp(z,"-zip")==0 ){
- data.openMode = SHELL_OPEN_ZIPFILE;
-#endif
- }else if( cli_strcmp(z,"-append")==0 ){
- data.openMode = SHELL_OPEN_APPENDVFS;
-#ifndef SQLITE_OMIT_DESERIALIZE
- }else if( cli_strcmp(z,"-deserialize")==0 ){
- data.openMode = SHELL_OPEN_DESERIALIZE;
- }else if( cli_strcmp(z,"-maxsize")==0 && i+1<argc ){
- data.szMax = integerValue(argv[++i]);
-#endif
- }else if( cli_strcmp(z,"-readonly")==0 ){
- data.openMode = SHELL_OPEN_READONLY;
- }else if( cli_strcmp(z,"-nofollow")==0 ){
- data.openFlags |= SQLITE_OPEN_NOFOLLOW;
- }else if( cli_strcmp(z,"-ascii")==0 ){
- data.mode = MODE_Ascii;
- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Unit);
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Record);
- }else if( cli_strcmp(z,"-tabs")==0 ){
- data.mode = MODE_List;
- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,SEP_Tab);
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,SEP_Row);
- }else if( cli_strcmp(z,"-separator")==0 ){
- sqlite3_snprintf(sizeof(data.colSeparator), data.colSeparator,
- "%s",cmdline_option_value(argc,argv,++i));
- }else if( cli_strcmp(z,"-newline")==0 ){
- sqlite3_snprintf(sizeof(data.rowSeparator), data.rowSeparator,
- "%s",cmdline_option_value(argc,argv,++i));
- }else if( cli_strcmp(z,"-nullvalue")==0 ){
- sqlite3_snprintf(sizeof(data.nullValue), data.nullValue,
- "%s",cmdline_option_value(argc,argv,++i));
- }else if( cli_strcmp(z,"-header")==0 ){
- data.showHeader = 1;
- ShellSetFlag(&data, SHFLG_HeaderSet);
- }else if( cli_strcmp(z,"-noheader")==0 ){
- data.showHeader = 0;
- ShellSetFlag(&data, SHFLG_HeaderSet);
- }else if( cli_strcmp(z,"-echo")==0 ){
- ShellSetFlag(&data, SHFLG_Echo);
- }else if( cli_strcmp(z,"-eqp")==0 ){
- data.autoEQP = AUTOEQP_on;
- }else if( cli_strcmp(z,"-eqpfull")==0 ){
- data.autoEQP = AUTOEQP_full;
- }else if( cli_strcmp(z,"-stats")==0 ){
- data.statsOn = 1;
- }else if( cli_strcmp(z,"-scanstats")==0 ){
- data.scanstatsOn = 1;
- }else if( cli_strcmp(z,"-backslash")==0 ){
- /* Undocumented command-line option: -backslash
- ** Causes C-style backslash escapes to be evaluated in SQL statements
- ** prior to sending the SQL into SQLite. Useful for injecting
- ** crazy bytes in the middle of SQL statements for testing and debugging.
- */
- ShellSetFlag(&data, SHFLG_Backslash);
- }else if( cli_strcmp(z,"-bail")==0 ){
- /* No-op. The bail_on_error flag should already be set. */
- }else if( cli_strcmp(z,"-version")==0 ){
- printf("%s %s\n", sqlite3_libversion(), sqlite3_sourceid());
- return 0;
- }else if( cli_strcmp(z,"-interactive")==0 ){
- stdin_is_interactive = 1;
- }else if( cli_strcmp(z,"-batch")==0 ){
- stdin_is_interactive = 0;
- }else if( cli_strcmp(z,"-utf8")==0 ){
#if SHELL_WIN_UTF8_OPT
- console_utf8 = 1;
-#endif /* SHELL_WIN_UTF8_OPT */
- }else if( cli_strcmp(z,"-heap")==0 ){
- i++;
- }else if( cli_strcmp(z,"-pagecache")==0 ){
- i+=2;
- }else if( cli_strcmp(z,"-lookaside")==0 ){
- i+=2;
- }else if( cli_strcmp(z,"-threadsafe")==0 ){
- i+=2;
- }else if( cli_strcmp(z,"-nonce")==0 ){
- i += 2;
- }else if( cli_strcmp(z,"-mmap")==0 ){
- i++;
- }else if( cli_strcmp(z,"-memtrace")==0 ){
- i++;
-#ifdef SQLITE_ENABLE_SORTER_REFERENCES
- }else if( cli_strcmp(z,"-sorterref")==0 ){
- i++;
-#endif
- }else if( cli_strcmp(z,"-vfs")==0 ){
- i++;
-#ifdef SQLITE_ENABLE_VFSTRACE
- }else if( cli_strcmp(z,"-vfstrace")==0 ){
- i++;
-#endif
-#ifdef SQLITE_ENABLE_MULTIPLEX
- }else if( cli_strcmp(z,"-multiplex")==0 ){
- i++;
+ if( console_utf8 && stdin_is_interactive ){
+ console_prepare();
+ }else{
+ setBinaryMode(stdin, 0);
+ console_utf8 = 0;
+ }
#endif
- }else if( cli_strcmp(z,"-help")==0 ){
- usage(1);
- }else if( cli_strcmp(z,"-cmd")==0 ){
- /* Run commands that follow -cmd first and separately from commands
- ** that simply appear on the command-line. This seems goofy. It would
- ** be better if all commands ran in the order that they appear. But
- ** we retain the goofy behavior for historical compatibility. */
- if( i==argc-1 ) break;
- z = cmdline_option_value(argc,argv,++i);
- if( z[0]=='.' ){
- rc = do_meta_command(z, &data);
- if( rc && bail_on_error ) return rc==2 ? 0 : rc;
- }else{
- open_db(&data, 0);
- rc = shell_exec(&data, z, &zErrMsg);
- if( zErrMsg!=0 ){
- utf8_printf(stderr,"Error: %s\n", zErrMsg);
- if( bail_on_error ) return rc!=0 ? rc : 1;
- }else if( rc!=0 ){
- utf8_printf(stderr,"Error: unable to process SQL \"%s\"\n", z);
- if( bail_on_error ) return rc;
+ if( cmdArgsCmd.nCmd > 0 ){
+ /* cmdArgsCmd not empty; some -cmd commands are to be run first. */
+ for( i=0; i<cmdArgsCmd.nCmd; ++i ){
+ set_invocation_cmd(cmdArgsCmd.azCmd[i]);
+ drc = process_input(&datai);
+ rc = (drc>2)? 2 : drc;
+ if( rc>0 ) goto shell_bail;
+ }
+ }
+
+ if( !argsData.readStdin && cmdArgsBare.nCmd>0 ){
+ /* cmdArgsBare holds either "bare command" arguments or -A arguments.
+ ** (Former are arguments not naming the DB or beginning with '-'.)
+ ** Run whichever kind there are. */
+#if ARCHIVE_ENABLE
+ if( argsData.bArCmd ){
+ open_db(&datax, OPEN_DB_ZIPFILE);
+ drc = arDotCommand(&datax, 1, cmdArgsBare.azCmd, cmdArgsBare.nCmd);
+ }else
+#endif /* ARCHIVE_ENABLE */
+ {
+ /* Run bare command arguments like separate command-line inputs. */
+ for(i=0; i<cmdArgsBare.nCmd && rc<2; i++){
+ set_invocation_cmd(cmdArgsBare.azCmd[i]);
+ drc = process_input(&datai);
+ rc = (drc>2)? 2 : drc;
+ if( drc > DCR_Ok ) break;
}
}
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
- }else if( cli_strncmp(z, "-A", 2)==0 ){
- if( nCmd>0 ){
- utf8_printf(stderr, "Error: cannot mix regular SQL or dot-commands"
- " with \"%s\"\n", z);
- return 1;
- }
- open_db(&data, OPEN_DB_ZIPFILE);
- if( z[2] ){
- argv[i] = &z[2];
- arDotCommand(&data, 1, argv+(i-1), argc-(i-1));
- }else{
- arDotCommand(&data, 1, argv+i, argc-i);
- }
- readStdin = 0;
- break;
-#endif
- }else if( cli_strcmp(z,"-safe")==0 ){
- data.bSafeMode = data.bSafeModePersist = 1;
- }else if( cli_strcmp(z,"-unsafe-testing")==0 ){
- /* Acted upon in first pass. */
+ rc = (drc>2)? 2 : drc;
+ if( rc>0 ) goto shell_bail;
}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;
- }
- data.cMode = data.mode;
- }
-#if SHELL_WIN_UTF8_OPT
- if( console_utf8 && stdin_is_interactive ){
- console_prepare();
- }else{
- setBinaryMode(stdin, 0);
- console_utf8 = 0;
- }
-#endif
-
- if( !readStdin ){
- /* Run all arguments that do not begin with '-' as if they were separate
- ** command-line inputs, except for the argToSkip argument which contains
- ** the database filename.
- */
- for(i=0; i<nCmd; i++){
- if( azCmd[i][0]=='.' ){
- rc = do_meta_command(azCmd[i], &data);
- if( rc ){
- free(azCmd);
- return rc==2 ? 0 : rc;
+ /* Run commands received from standard input (however defined.)
+ */
+#ifndef SQLITE_SHELL_FIDDLE
+ if( stdin_is_interactive ){
+ char *zHistory = 0;
+ if( argsData.bQuiet>1 ){
+ /* bQuiet>1 is almost like normal interactive, but quieter, without
+ ** prompts and avoids history keeping and line editor completions.
+ ** It exists for testing or bug repro purposes. */
+ mainPrompt[0] = 0;
+ continuePrompt[0] = 0;
+ }else{
+ char *zHome;
+ if( argsData.bQuiet<1 ){
+ 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 ){
+ static const char zBasename[] = ".sqlite_history";
+ int nHistory = strlen30(zHome) + 2 + sizeof(zBasename);
+ if( (zHistory = malloc(nHistory))!=0 ){
+ sqlite3_snprintf(nHistory, zHistory,"%s/%s", zHome, zBasename);
+ }
+ }
+ if( zHistory ){
+ mstr_holder(zHistory);
+ shell_read_history(zHistory);
+ }
+# if HAVE_READLINE || HAVE_EDITLINE
+ rl_attempted_completion_function = readline_completion;
+# elif HAVE_LINENOISE
+ linenoiseSetCompletionCallback(linenoise_completion);
+# endif
}
- }else{
- open_db(&data, 0);
- echo_group_input(&data, azCmd[i]);
- rc = shell_exec(&data, azCmd[i], &zErrMsg);
- if( zErrMsg || rc ){
- if( zErrMsg!=0 ){
- utf8_printf(stderr,"Error: %s\n", zErrMsg);
- }else{
- utf8_printf(stderr,"Error: unable to process SQL: %s\n", azCmd[i]);
+ datai.pInSource = &termInSource; /* read from stdin interactively */
+ drc = process_input(&datai);
+ if( !argsData.bQuiet ){
+ if( zHistory ){
+ shell_stifle_history(2000);
+ shell_write_history(zHistory);
+ release_holder();
}
- sqlite3_free(zErrMsg);
- free(azCmd);
- return rc!=0 ? rc : 1;
}
- }
- }
+ }else{
+ datai.pInSource = &stdInSource; /* read from stdin without prompts */
+ drc = process_input(&datai);
+ }
+#else /* !defined(SQLITE_SHELL_FIDDLE) */
+ datai.pInSource = &fiddleInSource; /* read per set_fiddle_input_text() */
+ drc = process_input(&datai);
+#endif /* defined(SQLITE_SHELL_FIDDLE) */
+ rc = (drc>2)? 2 : drc;
+ }
+ shell_bail:
+ forget_exit_ripper(&mainRipDest);
+ /* Next is a no-op except for "goto shell_bail" early exits. */
+ RESOURCE_FREE(mainRipDest.resDest);
}else{
- /* Run commands received from standard input
- */
- if( stdin_is_interactive ){
- char *zHome;
- char *zHistory;
- int nHistory;
- printf(
- "SQLite version %s %.19s\n" /*extra-version-info*/
- "Enter \".help\" for usage hints.\n",
- sqlite3_libversion(), sqlite3_sourceid()
- );
- if( warnInmemoryDb ){
- printf("Connected to a ");
- printBold("transient in-memory database");
- printf(".\nUse \".open FILENAME\" to reopen on a "
- "persistent database.\n");
- }
- zHistory = getenv("SQLITE_HISTORY");
- if( zHistory ){
- zHistory = strdup(zHistory);
- }else if( (zHome = find_home_dir(0))!=0 ){
- nHistory = strlen30(zHome) + 20;
- if( (zHistory = malloc(nHistory))!=0 ){
- sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
- }
- }
- if( zHistory ){ shell_read_history(zHistory); }
-#if HAVE_READLINE || HAVE_EDITLINE
- rl_attempted_completion_function = readline_completion;
-#elif HAVE_LINENOISE
- linenoiseSetCompletionCallback(linenoise_completion);
-#endif
- data.in = 0;
- rc = process_input(&data);
- if( zHistory ){
- shell_stifle_history(2000);
- shell_write_history(zHistory);
- free(zHistory);
- }
- }else{
- data.in = stdin;
- rc = process_input(&data);
- }
+ /* Abrupt, stack-ripping exit arrives here (with mainRipDest forgotten.) */
+ utf8_printf(STD_ERR,"Terminating with error (2) after orderly cleanup.\n");
+ datax.shellAbruptExit = 0x102;
}
- /* Extract this code to preserve its value before datax disposal. */
+
++ /* Extract this code to preserve its value before disposal. */
+ iAbruptExitCode = datax.shellAbruptExit;
+
+ /**** Termination cleanup. ****/
#ifndef SQLITE_SHELL_FIDDLE
/* In WASM mode we have to leave the db state in place so that
- ** client code can "push" SQL into it after this call returns. */
- free(azCmd);
- set_table_name(&data, 0);
- if( data.db ){
- session_close_all(&data, -1);
- close_db(data.db);
- }
- for(i=0; i<ArraySize(data.aAuxDb); i++){
- sqlite3_free(data.aAuxDb[i].zFreeOnClose);
- if( data.aAuxDb[i].db ){
- session_close_all(&data, i);
- close_db(data.aAuxDb[i].db);
- }
- }
- find_home_dir(1);
- output_reset(&data);
- data.doXdgOpen = 0;
- clearTempFile(&data);
-#if !SQLITE_SHELL_IS_UTF8
- for(i=0; i<argcToFree; i++) free(argvToFree[i]);
- free(argvToFree);
-#endif
- free(data.colWidth);
- free(data.zNonce);
- /* Clear the global data structure so that valgrind will detect memory
- ** leaks */
- memset(&data, 0, sizeof(data));
-#ifdef SQLITE_DEBUG
+ ** client code can "push" SQL into it after this call returns.
+ ** For that build, just bypass freeing all acquired resources.
+ */
+ execution_restore(&datai, &datax);
+ RESOURCE_FREE(mark);
+# ifdef SQLITE_DEBUG
if( sqlite3_memory_used()>mem_main_enter ){
utf8_printf(stderr, "Memory leaked: %u bytes\n",
(unsigned int)(sqlite3_memory_used()-mem_main_enter));
}
-#endif
-#endif /* !SQLITE_SHELL_FIDDLE */
+# endif
+#endif /* !defined(SQLITE_SHELL_FIDDLE) */
+ /* Process exit codes to yield single shell exit code.
+ * rc == 2 is a quit signal, resulting in no error by itself.
+ * datax.shellAbruptExit conveyed either a 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( iAbruptExitCode!=0 ){
+ rc = iAbruptExitCode & 0xff;
+ if( iAbruptExitCode>0x1ff ) raw_printf(STD_ERR,"Abnormal exit (%d)\n",rc);
+ }else{
- /* rc is one of 0,1,2, mapping to 0,1,0 shellexit codes. */
++ /* rc is one of 0,1,2, mapping to 0,1,0 shell exit codes. */
+ rc &= ~2;
+ }
+ terminate_actions();
return rc;
}