cQuote = quoteChar(zCol);
appendText(&s, zCol, cQuote);
}
++ if( rc==SQLITE_NOMEM ) shell_out_of_memory();
appendText(&s, ")", 0);
-- sqlite3_finalize(pStmt);
-- if( nRow==0 ){
-- freeText(&s);
-- s.z = 0;
-- }
-- return s.z;
++ if( nRow!=0 ) rv = takeText(&s);
++ RESOURCE_FREE(rm_mark);
++ return rv;
}
--
+ /*
+ ** SQL function: strtod(X)
+ **
+ ** Use the C-library strtod() function to convert string X into a double.
+ ** Used for comparing the accuracy of SQLite's internal text-to-float conversion
+ ** routines against the C-library.
+ */
+ static void shellStrtod(
+ sqlite3_context *pCtx,
+ int nVal,
+ sqlite3_value **apVal
+ ){
+ char *z = (char*)sqlite3_value_text(apVal[0]);
+ + UNUSED_PARAMETER(nVal);
+ if( z==0 ) return;
+ sqlite3_result_double(pCtx, strtod(z,0));
+ }
+
+ /*
+ ** SQL function: dtostr(X)
+ **
+ ** Use the C-library printf() function to convert real value X into a string.
+ ** Used for comparing the accuracy of SQLite's internal float-to-text conversion
+ ** routines against the C-library.
+ */
+ static void shellDtostr(
+ sqlite3_context *pCtx,
+ int nVal,
+ sqlite3_value **apVal
+ ){
+ double r = sqlite3_value_double(apVal[0]);
+ int n = nVal>=2 ? sqlite3_value_int(apVal[1]) : 26;
+ char z[400];
+ if( n<1 ) n = 1;
+ if( n>350 ) n = 350;
+ sprintf(z, "%#+.*e", n, r);
+ sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT);
+ }
+
/*
** SQL function: shell_module_schema(X)
**
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 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 */
}
/*
--** SQL function: edit(VALUE)
- ** edit(VALUE,EDITOR)
++** Emit formatted output to shell's current output, possibly translated
++** for the legacy console on the Windows platform. This is exposed as
++** a helper for extensions so that they may share a common buffering
++** for FILE* output or share output capture when/if that is implemented.
++*/
++static void utf8_out_printf(ShellExState *p, const char *zFormat, ...){
++ va_list ap;
++ va_start(ap, zFormat);
++#if defined(_WIN32) || defined(WIN32)
++ vf_utf8_printf(ISS(p)->out, zFormat, ap);
++#else
++ vfprintf(ISS(p)->out, zFormat, ap);
++#endif
++ va_end(ap);
++}
++
++/*
++** SQL function: edit(VALUE)
+ ** edit(VALUE,EDITOR)
**
** These steps:
**
#endif /* SQLITE_NOHAVE_SYSTEM */
/*
--** Save or restore the current output mode
++** Save or restore the current output mode, partially per spec. (OM_STATE)
++*/
++typedef enum {
++ SWM_showHeader = 1, SWM_shellFlags = 2, SWM_mode = 4, SWM_cmOpts = 8,
++ SWM_colSeparator = 0x10, SWM_rowSeparator = 0x20, SWM_everything = 0x3F,
++ SWM_CountOf = 6
++} SaveWhatMode;
++
++/* This is available in most C89+ C compilers as offsetof(...), but since we
++ * cater to the most arcane C89-like compilers around, define it for sure:
++ */
++#define MEMBER_OFFSET(stype, member) ((size_t)&(((stype*)0)->member))
++#define MEMBER_SIZEOF(stype, member) (sizeof(((stype*)0)->member))
++static struct {
++ size_t offset;
++ size_t size;
++} outputModeCopy[] = {
++#define SS_MEMBER_COPY(mn) \
++ { MEMBER_OFFSET(ShellInState,mn), MEMBER_SIZEOF(ShellInState,mn) }
++ SS_MEMBER_COPY(showHeader), SS_MEMBER_COPY(shellFlgs),
++ SS_MEMBER_COPY(mode), SS_MEMBER_COPY(cmOpts),
++ SS_MEMBER_COPY(colSeparator), SS_MEMBER_COPY(rowSeparator)
++#undef SS_MEMBER_COPY
++};
++
++/* Allocate a buffer, copy requested output mode data to it, and return it.
++ * This never fails under OOM conditions. Instead, it returns 0.
++ */
++static OutputModeSave *outputModeSave(ShellInState *psi, SaveWhatMode w){
++ u16 what = (u16)w;
++ int i, nAlloc = sizeof(what)+1, mask = 1;
++ char *pSaved = 0, *pFill;
++ for( i=0; i<SWM_CountOf; mask<<=1, ++i ){
++ if( (what & mask)!=0 ) nAlloc += (int)outputModeCopy[i].size;
++ }
++ assert(i==ArraySize(outputModeCopy));
++ pSaved = sqlite3_malloc(nAlloc);
++ if( pSaved==0 ) return 0;
++ *(u16 *)pSaved = what;
++ pFill = pSaved + sizeof(what);
++ for( mask=1, i=0; i<SWM_CountOf; mask<<=1, ++i ){
++ if( (what & mask)!=0 ){
++ size_t nb = outputModeCopy[i].size;
++ memcpy(pFill, ((char*)psi)+outputModeCopy[i].offset, nb);
++ pFill += nb;
++ }
++ }
++ *pFill = 0xA5;
++ return (OutputModeSave *)pSaved;
++}
++
++/* From a buffer returned by outputModeSave, restore output mode data.
++ * The buffer is freed and its pointer is invalidated.
++ * If called with some other buffer, results are undefined, likely bad.
++ */
++static void outputModeRestore(ShellInState *psi, OutputModeSave *pSaved){
++ sqlite3_uint64 nA = sqlite3_msize(pSaved);
++ u16 what = (nA>sizeof(what))? *((u16 *)pSaved) : 0;
++ int i, nAlloc = sizeof(what)+1, mask = 1;
++ char *pTake = (char *)pSaved + sizeof(what);
++ for( i=0; i<SWM_CountOf && nAlloc<nA; mask<<=1, ++i ){
++ if( (what & mask)!=0 ){
++ size_t nb = outputModeCopy[i].size;
++ memcpy(((char*)psi)+outputModeCopy[i].offset, pTake, nb);
++ pTake += nb;
++ }
++ }
++ assert(*pTake==(char)0xA5);
++ sqlite3_free(pSaved);
++}
++
++/*
++** Save or restore the current output mode, in whole or in part.
++** A save may abort on OOM.
*/
--static void outputModePush(ShellState *p){
-- p->modePrior = p->mode;
-- p->priorShFlgs = p->shellFlgs;
-- memcpy(p->colSepPrior, p->colSeparator, sizeof(p->colSeparator));
-- memcpy(p->rowSepPrior, p->rowSeparator, sizeof(p->rowSeparator));
++static void outputModePushSome(ShellInState *psi, SaveWhatMode w){
++ OutputModeSave *pOMS;
++ assert(psi->nSavedModes<MODE_STACK_MAX); /* Fail hard for this logic error. */
++ if( psi->nSavedModes>=MODE_STACK_MAX ) return;
++ pOMS = outputModeSave(psi, w);
++ shell_check_ooms(pOMS);
++ psi->pModeStack[psi->nSavedModes++] = pOMS;
+}
- static void outputModePop(ShellState *p){
- p->mode = p->modePrior;
- p->shellFlgs = p->priorShFlgs;
- memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
- memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
++static void outputModePush(ShellInState *psi){
++ outputModePushSome(psi, SWM_everything);
+ }
-static void outputModePop(ShellState *p){
- p->mode = p->modePrior;
- p->shellFlgs = p->priorShFlgs;
- memcpy(p->colSeparator, p->colSepPrior, sizeof(p->colSeparator));
- memcpy(p->rowSeparator, p->rowSepPrior, sizeof(p->rowSeparator));
++static void outputModePop(ShellInState *p){
++ OutputModeSave *pOMS;
++ assert(p->nSavedModes>0); /* Should not be here unless something pushed. */
++ if( p->nSavedModes==0 ) return;
++ pOMS = p->pModeStack[--p->nSavedModes];
++ assert(pOMS!=0);
++ p->pModeStack[p->nSavedModes] = 0;
++ outputModeRestore(p, pOMS);
}
/*
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.
+ **
-** The help text for each individual command begins with a line that starts
-** with ".". Subsequent lines are supplemental information.
++** 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.
**
--** There must be two or more spaces between the end of the command and the
--** start of the description of what that command does.
--*/
--static const char *(azHelp[]) = {
--#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",
--#endif
--#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
-- ".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 ...)",
--#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",
--#if defined(SQLITE_ENABLE_SESSION)
-- ".session ?NAME? CMD ... Create or control sessions",
-- " Subcommands:",
-- " attach TABLE Attach TABLE",
-- " changeset FILE Write a changeset into FILE",
-- " close Close one session",
-- " enable ?BOOLEAN? Set or query the enable bit",
-- " filter GLOB... Reject tables matching GLOBs",
-- " indirect ?BOOLEAN? Mark or query the indirect status",
-- " isempty Query whether the session is empty",
-- " list List currently open session names",
-- " open DB NAME Open a new session on DB",
-- " patchset FILE Write a patchset into FILE",
-- " If ?NAME? is omitted, the first defined session is used.",
--#endif
-- ".sha3sum ... Compute a SHA3 hash of database content",
-- " Options:",
-- " --schema Also hash the sqlite_schema table",
-- " --sha3-224 Use the sha3-224 algorithm",
-- " --sha3-256 Use the sha3-256 algorithm (default)",
-- " --sha3-384 Use the sha3-384 algorithm",
-- " --sha3-512 Use the sha3-512 algorithm",
-- " Any other argument is a LIKE pattern for tables to hash",
--#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
-- ".shell CMD ARGS... Run CMD ARGS... in a system shell",
--#endif
-- ".show Show the current values for various settings",
-- ".stats ?ARG? Show stats or turn stats on or off",
-- " off Turn off automatic stat display",
-- " on Turn on automatic stat display",
-- " stmt Show statement stats",
-- " vmstep Show the virtual machine step count only",
--#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE)
-- ".system CMD ARGS... Run CMD ARGS... in a system shell",
--#endif
-- ".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
--#ifndef SQLITE_SHELL_FIDDLE
-- ",testcase NAME Begin redirecting output to 'testcase-out.txt'",
--#endif
-- ",testctrl CMD ... Run various sqlite3_test_control() operations",
-- " Run \".testctrl\" with no arguments for details",
-- ".timeout MS Try opening locked tables for MS milliseconds",
-- ".timer on|off Turn SQL timer on or off",
--#ifndef SQLITE_OMIT_TRACE
-- ".trace ?OPTIONS? Output each SQL statement as it is run",
-- " FILE Send output to FILE",
-- " stdout Send output to stdout",
-- " stderr Send output to stderr",
-- " off Disable tracing",
-- " --expanded Expand query parameters",
--#ifdef SQLITE_ENABLE_NORMALIZE
-- " --normalized Normal the SQL statements",
--#endif
-- " --plain Show SQL as it is input",
-- " --stmt Trace statement execution (SQLITE_TRACE_STMT)",
-- " --profile Profile statements (SQLITE_TRACE_PROFILE)",
-- " --row Trace each row (SQLITE_TRACE_ROW)",
-- " --close Trace connection close (SQLITE_TRACE_CLOSE)",
--#endif /* SQLITE_OMIT_TRACE */
--#ifdef SQLITE_DEBUG
-- ".unmodule NAME ... Unregister virtual table modules",
-- " --allexcept Unregister everything except those named",
--#endif
-- ".version Show source, library and compiler versions",
-- ".vfsinfo ?AUX? Information about the top-level VFS",
-- ".vfslist List all available VFSes",
-- ".vfsname ?AUX? Print the name of the VFS stack",
-- ".width NUM1 NUM2 ... Set minimum column widths for columnar output",
-- " Negative values right-justify",
--};
--
--/*
--** Output help text.
--**
--** zPattern describes the set of commands for which help text is provided.
--** If zPattern is NULL, then show all commands, but only give a one-line
--** description of each.
--**
--** Return the number of matches.
--*/
--static int showHelp(FILE *out, const char *zPattern){
-- int i = 0;
-- int j = 0;
-- int n = 0;
-- char *zPat;
-- if( zPattern==0
-- || zPattern[0]=='0'
-- || cli_strcmp(zPattern,"-a")==0
-- || cli_strcmp(zPattern,"-all")==0
-- || cli_strcmp(zPattern,"--all")==0
-- ){
-- enum HelpWanted { HW_NoCull = 0, HW_SummaryOnly = 1, HW_Undoc = 2 };
-- enum HelpHave { HH_Undoc = 2, HH_Summary = 1, HH_More = 0 };
-- /* Show all or most commands
-- ** *zPattern==0 => summary of documented commands only
-- ** *zPattern=='0' => whole help for undocumented commands
-- ** Otherwise => whole help for documented commands
-- */
-- enum HelpWanted hw = HW_SummaryOnly;
-- enum HelpHave hh = HH_More;
-- if( zPattern!=0 ){
-- hw = (*zPattern=='0')? HW_NoCull|HW_Undoc : HW_NoCull;
-- }
-- for(i=0; i<ArraySize(azHelp); i++){
-- switch( azHelp[i][0] ){
-- case ',':
-- hh = HH_Summary|HH_Undoc;
-- break;
-- case '.':
-- hh = HH_Summary;
-- break;
-- default:
-- hh &= ~HH_Summary;
-- break;
-- }
-- if( ((hw^hh)&HH_Undoc)==0 ){
-- if( (hh&HH_Summary)!=0 ){
-- utf8_printf(out, ".%s\n", azHelp[i]+1);
-- ++n;
-- }else if( (hw&HW_SummaryOnly)==0 ){
-- utf8_printf(out, "%s\n", azHelp[i]);
-- }
-- }
-- }
-- }else{
-- /* Seek documented commands for which zPattern is an exact prefix */
-- zPat = sqlite3_mprintf(".%s*", zPattern);
-- shell_check_oom(zPat);
-- for(i=0; i<ArraySize(azHelp); i++){
-- if( sqlite3_strglob(zPat, azHelp[i])==0 ){
-- utf8_printf(out, "%s\n", azHelp[i]);
-- j = i+1;
-- n++;
-- }
-- }
-- sqlite3_free(zPat);
-- if( n ){
-- if( n==1 ){
-- /* when zPattern is a prefix of exactly one command, then include
-- ** the details of that command, which should begin at offset j */
-- while( j<ArraySize(azHelp)-1 && azHelp[j][0]==' ' ){
-- utf8_printf(out, "%s\n", azHelp[j]);
-- j++;
-- }
-- }
-- return n;
-- }
-- /* Look for documented commands that contain zPattern anywhere.
-- ** Show complete text of all documented commands that match. */
-- zPat = sqlite3_mprintf("%%%s%%", zPattern);
-- shell_check_oom(zPat);
-- for(i=0; i<ArraySize(azHelp); i++){
-- if( azHelp[i][0]==',' ){
-- while( i<ArraySize(azHelp)-1 && azHelp[i+1][0]==' ' ) ++i;
-- continue;
-- }
-- if( azHelp[i][0]=='.' ) j = i;
-- if( sqlite3_strlike(zPat, azHelp[i], 0)==0 ){
-- utf8_printf(out, "%s\n", azHelp[j]);
-- while( j<ArraySize(azHelp)-1 && azHelp[j+1][0]==' ' ){
-- j++;
-- utf8_printf(out, "%s\n", azHelp[j]);
-- }
-- i = j;
-- n++;
-- }
-- }
-- sqlite3_free(zPat);
-- }
-- return n;
--}
--
--/* Forward reference */
--static int process_input(ShellState *p);
--
--/*
--** Read the content of file zName into memory obtained from sqlite3_malloc64()
--** and return a pointer to the buffer. The caller is responsible for freeing
--** the memory.
--**
--** If parameter pnByte is not NULL, (*pnByte) is set to the number of bytes
--** read.
--**
--** For convenience, a nul-terminator byte is always appended to the data read
--** from the file before the buffer is returned. This byte is not included in
--** the final value of (*pnByte), if applicable.
--**
--** NULL is returned if any error is encountered. The final value of *pnByte
--** is undefined in this case.
++** This function always returns; no abrupt OOM exits are taken.
*/
static char *readFile(const char *zName, int *pnByte){
FILE *in = fopen(zName, "rb");
}
#endif
-/*
++#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
++
+/*
** Try to deduce the type of file for zName based on its content. Return
** one of the SHELL_OPEN_* constants.
**
}
#endif
-- sqlite3_create_function(p->db, "strtod", 1, SQLITE_UTF8, 0,
+++ sqlite3_create_function(GLOBAL_DB, "strtod", 1, SQLITE_UTF8, 0,
+ shellStrtod, 0, 0);
-- sqlite3_create_function(p->db, "dtostr", 1, SQLITE_UTF8, 0,
+++ sqlite3_create_function(GLOBAL_DB, "dtostr", 1, SQLITE_UTF8, 0,
+ shellDtostr, 0, 0);
-- sqlite3_create_function(p->db, "dtostr", 2, SQLITE_UTF8, 0,
+++ sqlite3_create_function(GLOBAL_DB, "dtostr", 2, SQLITE_UTF8, 0,
+ shellDtostr, 0, 0);
-- sqlite3_create_function(p->db, "shell_add_schema", 3, SQLITE_UTF8, 0,
++ sqlite3_create_function(GLOBAL_DB, "shell_add_schema", 3,SQLITE_UTF8,0,
shellAddSchemaName, 0, 0);
-- sqlite3_create_function(p->db, "shell_module_schema", 1, SQLITE_UTF8, 0,
++ sqlite3_create_function(GLOBAL_DB, "shell_module_schema", 1,SQLITE_UTF8,0,
shellModuleSchema, 0, 0);
-- sqlite3_create_function(p->db, "shell_putsnl", 1, SQLITE_UTF8, p,
++ sqlite3_create_function(GLOBAL_DB, "shell_putsnl", 1,SQLITE_UTF8,psx,
shellPutsFunc, 0, 0);
-- sqlite3_create_function(p->db, "usleep",1,SQLITE_UTF8,0,
++ sqlite3_create_function(GLOBAL_DB, "usleep", 1,SQLITE_UTF8,0,
shellUSleepFunc, 0, 0);
#ifndef SQLITE_NOHAVE_SYSTEM
-- sqlite3_create_function(p->db, "edit", 1, SQLITE_UTF8, 0,
++ sqlite3_create_function(GLOBAL_DB, "edit", 1, SQLITE_UTF8, 0,
editFunc, 0, 0);
-- sqlite3_create_function(p->db, "edit", 2, SQLITE_UTF8, 0,
++ sqlite3_create_function(GLOBAL_DB, "edit", 2, SQLITE_UTF8, 0,
editFunc, 0, 0);
#endif
--
-- 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( 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( p->openMode==SHELL_OPEN_DESERIALIZE || p->openMode==SHELL_OPEN_HEXDB ){
++ else if( psi->openMode==SHELL_OPEN_DESERIALIZE
++ || psi->openMode==SHELL_OPEN_HEXDB ){
int rc;
int nData = 0;
unsigned char *aData;
for(i=0; zArg[i]>='0' && zArg[i]<='9'; i++){}
}
if( i>0 && zArg[i]==0 ) return (int)(integerValue(zArg) & 0xffffffff);
-- if( sqlite3_stricmp(zArg, "on")==0 || sqlite3_stricmp(zArg,"yes")==0 ){
-- return 1;
- }
- if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
- return 0;
++ for( i=0; zBoolNames[i]!=0; ++i ){
++ if( sqlite3_stricmp(zArg, zBoolNames[i])==0 ) return i&1;
}
- if( sqlite3_stricmp(zArg, "off")==0 || sqlite3_stricmp(zArg,"no")==0 ){
- return 0;
- }
-- utf8_printf(stderr, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
-- zArg);
++ utf8_printf(STD_ERR, "ERROR: Not a boolean value: \"%s\". Assuming \"no\".\n",
++ zArg);
return 0;
}
}
}
++#if SHELL_DATAIO_EXT
++
/*
--** If an input line begins with "." then invoke this routine to
--** process that line.
--**
--** Return 1 on error, 2 to exit, and 0 otherwise.
++** Standard ExportHandlers
++** These implement the built-in renderers of query results.
++** Two are provided, one for free-form results, the other for columnar results.
*/
--static int do_meta_command(char *zLine, ShellState *p){
-- int h = 1;
-- int nArg = 0;
-- int n, c;
-- int rc = 0;
-- char *azArg[52];
--#ifndef SQLITE_OMIT_VIRTUALTABLE
-- if( p->expert.pExpert ){
-- expertFinish(p, 1, 0);
-- }
--#endif
++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
++};
-- /* 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 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");
}
-- azArg[nArg] = 0;
++ return SQLITE_OK;
++}
-- /* Process the input line.
-- */
-- if( nArg==0 ) return 0; /* no tokens, no error */
-- n = strlen30(azArg[0]);
-- c = azArg[0][0];
-- clearTempFile(p);
++static const char *zModeName(ShellInState *psi){
++ int mi = psi->mode;
++ return (mi>=0 && mi<MODE_COUNT_OF)? modeDescr[mi].zModeName : 0;
++}
++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);
++}
--#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;
++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;
++ }
++ 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*) );
++ }
++ 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;
}
-- open_db(p, 0);
-- if( booleanValue(azArg[1]) ){
-- sqlite3_set_authorizer(p->db, shellAuth, p);
-- }else if( p->bSafeModePersist ){
-- sqlite3_set_authorizer(p->db, safeModeAuth, p);
-- }else{
-- sqlite3_set_authorizer(p->db, 0, 0);
++ 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);
}
-- }else
--#endif
++ 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;
++ }
++ }
++ }
++ sqlite3_free((void*)azNextLine);
++ if( azQuoted ){
++ for(i=0; i<nColumn; i++) sqlite3_free(azQuoted[i]);
++ sqlite3_free(azQuoted);
++ }
++ if( nRow==0 ){
++ EH_CM_destruct(pMe);
++ return SQLITE_DONE;
++ }
--#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
--#endif
++ nData = nColumn*(nRow+1);
--#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;
++ 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);
}
-- }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;
}
++ break;
}
-- 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);
++ 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);
++ }
++ print_row_separator(psx, nColumn, "+");
++ break;
}
-- 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;
++ 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);
++ }
++ print_row_separator(psx, nColumn, "|");
++ break;
}
-- while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
-- sqlite3_backup_finish(pBackup);
-- if( rc==SQLITE_DONE ){
-- rc = 0;
-- }else{
-- utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
-- rc = 1;
++ 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" ");
++ }
++ print_box_row_separator(psx, nColumn, BOX_123, BOX_1234, BOX_134);
++ break;
}
-- close_db(pDest);
-- }else
--#endif /* !defined(SQLITE_SHELL_FIDDLE) */
++ }
++ 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;
++}
-- 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");
-- rc = 1;
-- }
-- }else
++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;
-- 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);
++ (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{
-- raw_printf(stderr, "Usage: .binary on|off\n");
-- rc = 1;
++ utf8_printf(psi->out, "%s", pbie->colSep);
}
- }else
-
- /* 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
++ }
++ return SQLITE_DONE;
++}
- #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;
- }
- }else{
- raw_printf(stderr, "Usage: .cd DIRECTORY\n");
- rc = 1;
- }
- /* 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
- #endif /* !defined(SQLITE_SHELL_FIDDLE) */
++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;
- if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
-#ifndef SQLITE_SHELL_FIDDLE
- if( c=='c' && cli_strcmp(azArg[0],"cd")==0 ){
- failIfSafeMode(p, "cannot run .cd in safe mode");
-- if( nArg==2 ){
- setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
-#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;
- }
-- }else{
- raw_printf(stderr, "Usage: .changes on|off\n");
- raw_printf(stderr, "Usage: .cd DIRECTORY\n");
-- rc = 1;
-- }
-- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
++ if( nRow==0 || nColumn==0 ) return SQLITE_INTERRUPT;
- #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;
- if( c=='c' && n>=3 && cli_strncmp(azArg[0], "changes", n)==0 ){
- if( nArg==2 ){
- setOrClearFlag(p, SHFLG_CountChanges, azArg[1]);
-- }else{
- utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase);
- p->nCheck++;
- raw_printf(stderr, "Usage: .changes on|off\n");
- rc = 1;
-- }
- sqlite3_free(zRes);
-- }else
- #endif /* !defined(SQLITE_SHELL_FIDDLE) */
++ 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;
++}
--#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");
- /* 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) */
++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;
- if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){
- if( nArg==1 ){
- /* List available connections */
-#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]);
++ 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{
- raw_printf(stderr, "Usage: .clone FILENAME\n");
- rc = 1;
- }
- }else
-#endif /* !defined(SQLITE_SHELL_FIDDLE) */
-
- if( c=='c' && cli_strncmp(azArg[0], "connection", n)==0 ){
- if( nArg==1 ){
- /* List available connections */
++ ColumnsInfo ci
++ = { (char **)pbie->pData, &ci.azCols[nc], (int *)&ci.azVals[nc] };
int i;
-- for(i=0; i<ArraySize(p->aAuxDb); i++){
-- const char *zFile = p->aAuxDb[i].zDbFilename;
-- if( p->aAuxDb[i].db==0 && p->pAuxDb!=&p->aAuxDb[i] ){
-- zFile = "(not open)";
-- }else if( zFile==0 ){
-- zFile = "(memory)";
-- }else if( zFile[0]==0 ){
-- zFile = "(temporary-file)";
-- }
-- if( p->pAuxDb == &p->aAuxDb[i] ){
-- utf8_printf(stdout, "ACTIVE %d: %s\n", i, zFile);
-- }else if( p->aAuxDb[i].db!=0 ){
-- utf8_printf(stdout, " %d: %s\n", i, zFile);
-- }
-- }
-- }else if( nArg==2 && IsDigit(azArg[1][0]) && azArg[1][1]==0 ){
-- int i = azArg[1][0] - '0';
-- if( p->pAuxDb != &p->aAuxDb[i] && i>=0 && i<ArraySize(p->aAuxDb) ){
-- p->pAuxDb->db = p->db;
-- p->pAuxDb = &p->aAuxDb[i];
-- globalDb = p->db = p->pAuxDb->db;
-- p->pAuxDb->db = 0;
-- }
-- }else if( nArg==3 && 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;
++ 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);
}
-- }else{
-- raw_printf(stderr, "Usage: .connection [close] [CONNECTION-NUMBER]\n");
-- rc = 1;
}
-- }else
++ return SQLITE_OK;
++ }
++ rc = sqlite3_step(pStmt);
++ assert(rc!=SQLITE_ROW);
++ return rc;
++}
-- 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++;
++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);
}
-- }
-- 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);
++ if( !pc->azVals[i] && (x!=SQLITE_NULL) ){
++ rc = SQLITE_NOMEM;
++ break; /* from for */
}
-- 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 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
++ }
++ return rc;
++}
--#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
++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;
++}
-- 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 */
++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( 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);
--#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]
-- );
++#if SHELL_DYNAMIC_EXTENSION
-- if( zLike ){
-- zLike = sqlite3_mprintf("%z OR %z", zLike, zExpr);
-- }else{
-- zLike = zExpr;
-- }
-- }
++/* 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;
++}
-- open_db(p, 0);
++/* Register an output data display (or other disposition) mode */
++static int register_exporter(ShellExState *p,
++ ExtensionId eid, ExportHandler *pEH){
++ return SQLITE_ERROR;
++}
-- 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;
-- }else
++/* Register an import variation from (various sources) for .import */
++static int register_importer(ShellExState *p,
++ ExtensionId eid, ImportHandler *pIH){
++ return SQLITE_ERROR;
++}
-- 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;
-- }
-- }else
++/* 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( 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( 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;
-- }
-- }else
++/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
++ * 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,
++ 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;
--#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;
-- }else
--#endif
++ assert(psi->pShxLoaded!=0 && p->dbShell!=0);
++ for( ie=psi->numExtLoaded-1; ie>0; --ie ){
++ 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;
++}
-- /* 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;
++/*
++ * 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.
++ */
++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 ) ){
++ 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{
-- val = booleanValue(azArg[1]);
++ ++pes;
}
}
-- 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;
++ if( psi->numSubscriptions==0 ){
++ sqlite3_free(psi->pSubscriptions);
++ psi->pSubscriptions = 0;
}
-- }else
++ return SQLITE_OK;
++ }else{
++ /* subscribe only if minimum NoticeKind supported by this host */
++ if( nkMin > NK_CountOf ) return SQLITE_ERROR;
++ 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 ){
++ 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->pvUserData = pvUserData;
++ pes->eventHandler = eventHandler;
++ return SQLITE_OK;
++ }
++}
--#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]);
-- rc = 1;
-- }else{
-- open_db(p, 0);
-- expertDotCommand(p, azArg, nArg);
-- }
-- }else
--#endif
++/*
++ * 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( 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;
++static struct InSource *currentInputSource(ShellExState *p){
++ return ISS(p)->pInSource;
++}
-- open_db(p, 0);
-- zCmd = nArg>=2 ? azArg[1] : "help";
++static int nowInteractive(ShellExState *p){
++ return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
++}
-- 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];
-- }
++static const char *shellInvokedAs(void){
++ return Argv0;
++}
-- /* The argument can optionally begin with "-" or "--" */
-- if( zCmd[0]=='-' && zCmd[1] ){
-- zCmd++;
-- if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
-- }
++static const char *shellStartupDir(void){
++ return startupDir;
++}
-- /* --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);
-- }
-- rc = 1;
-- goto meta_command_exit;
-- }
++static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
++static DotCommand * findDotCommand(const char *, ShellExState *, int *);
++static DotCmdRC runDotCommand(DotCommand*, char *[], int na, ShellExState*);
-- /* 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{
-- utf8_printf(stderr, "Error: ambiguous file-control: \"%s\"\n"
-- "Use \".filectrl --help\" for help\n", zCmd);
-- rc = 1;
-- goto meta_command_exit;
-- }
++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
++){
++ 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);
++}
++
++/* 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( filectrl<0 ){
-- utf8_printf(stderr,"Error: unknown file-control: %s\n"
-- "Use \".filectrl --help\" for help\n", zCmd);
++ if( rc!=SQLITE_DONE ){
++ rc = SQLITE_ERROR;
++ zSql = "ABORT";
}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;
-- }
-- }
++ rc = SQLITE_OK;
++ zSql = "COMMIT";
}
-- 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);
++ 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;
}
-- }else
++ }
++ RESOURCE_FREE(mark);
-- 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
++ return rc;
++}
+
- 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;
++/* 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);
}
- 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
++ }
++ 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);
+ }
- }else
-
- if( c=='h' && cli_strncmp(azArg[0], "headers", n)==0 ){
- if( nArg==2 ){
- p->showHeader = booleanValue(azArg[1]);
- p->shellFlgs |= SHFLG_HeaderSet;
++ }
++ 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 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;
++ }
++ }
++ free_ShExtInfo(psi->pShxLoaded);
++ sqlite3_free(psi->pShxLoaded);
++ psi->pShxLoaded = 0;
++ psi->numExtLoaded = 0;
++ }
++}
++
++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];
++ }
++ }
++ return 0;
++}
++
++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); /* 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
++
++/* 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 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);
++*/
++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;
++}
++
++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{
++ sqlite3_set_authorizer(DBX(p), 0, 0);
++ }
++ return DCR_Ok;
++}
++
++/*****************
++ * 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);
++}
++
++/*****************
++ * 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;
++}
++
++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;
++}
++
++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;
++}
++
++/* The ".breakpoint" command causes a call to the no-op routine named
++ * test_breakpoint(). It is undocumented.
++*/
++COLLECT_HELP_TEXT[
++ ",breakpoint calls test_breakpoint(). (a debugging aid)",
++];
++DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
++ test_breakpoint();
++ return DCR_Ok;
++}
++
++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{
++ 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;
++ }
++ return DCR_Ok;
++}
++
++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 = open_db(p, 0);
++
++ 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);
++}
++
++/*****************
++ * 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;
++ }
++ }
++ }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;
++ }
++ }
++ }
++
++ 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{
++ psi->autoEQP = (u8)booleanValue(azArg[1]);
++ }
++ }else{
++ return DCR_ArgWrong;
++ }
++ return DCR_Ok;
++}
++
++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;
++}
++
++/*****************
++ * 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;
++
++ 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{
++ 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;
++ }
++ }
++
++ 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;
++}
++
++/*****************
++ * 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 *zFile = 0;
++ u8 bTxtMode = 0;
++ u8 bPutBOM = 0;
++ int i;
++ static unsigned const char zBOM[4] = {0xef,0xbb,0xbf,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;
++ }
++ }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);
++ }
++ }
++ release_holder();
++ return DCR_Ok|rc;
++}
++#endif /* !defined(SQLITE_SHELL_FIDDLE)*/
++
++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);
++}
++
++
++/*****************
++ * 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;
++
++ 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];
++ }
++
++ /* The argument can optionally begin with "-" or "--" */
++ if( zCmd[0]=='-' && zCmd[1] ){
++ zCmd++;
++ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
++ }
++
++ /* --help lists all file-controls */
++ if( 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;
++ }
++
++ /* 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{
++ *pzErr = smprintf("ambiguous file-control: \"%s\"\n"
++ "Use \".filectrl --help\" for help\n", zCmd);
++ return DCR_ArgWrong;
++ }
++ }
++ }
++ 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;
++}
++
++static void modePopper(ShellInState *psi){
++ outputModePop(psi);
++}
++
++DISPATCHABLE_COMMAND( fullschema ? 1 2 ){
++ int rc;
++ 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{
++ 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;
++ }
++ }
++ 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;
++
++ 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{
++ 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{
++ 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++; }
++ }
++ }
++ 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( 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]);
++ 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);
++ }
++ 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{
++ 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");
++ }
++ 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;
++ }
++ db = open_db(p, 0);
++ 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 ){
++ 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{
++ *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;
++ }
++ }
++ return DCR_Ok;
++}
++
++/*****************
++ * 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));
++ }
++ }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{
++ *pzErr = smprintf("ambiguous limit: \"%s\"\n", azArg[1]);
++ return DCR_Error;
++ }
++ }
++ }
++ if( iLimit<0 ){
++ *pzErr = smprintf("unknown limit: \"%s\"\n"
++ "enter \".limits\" with no arguments for a list.\n",
++ azArg[1]);
++ return DCR_ArgWrong;
++ }
++ 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));
++ }
++ return DCR_Ok;
++}
++
++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();
++
++ 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;
++ }
++ db = open_db(p, 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=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{
++ raw_printf(STD_ERR, "Usage: %s %s ?-verbose? ?-groupbyparent?\n",
++ azArg[0], azArg[1]
++ );
++ return DCR_Unknown|i;
++ }
++ }
++
++ /* Register the fkey_collate_clause() SQL function */
++ rc = sqlite3_create_function(db, "fkey_collate_clause", 4, SQLITE_UTF8,
++ 0, shellFkeyCollateClause, 0, 0
++ );
++
++ 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);
++ }
++
++ 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);
++
++ 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;
++
++ 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);
++ }
++
++ 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);
++ }
+ }
++ }
++
++ 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);
++
++ return DCR_Ok|(rc!=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;
++ }
++ rc = sqlite3_load_extension(open_db(p, 0), 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);
++}
++
++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;
++ }
++ 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;
++}
++
++/*****************
++ * 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 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{
- raw_printf(stderr, "Usage: .headers on|off\n");
- rc = 1;
++ /* 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{
- showHelp(p->out, 0);
++ raw_printf(out, "current output mode: %s\n", zMode);
}
-- }else
-
- 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
++ int 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{
- showHelp(p->out, 0);
++ raw_printf(out, "current output mode: %.*s\n", nms, zMode);
+ }
- }else
++#endif
++ }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=='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;
++ 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{
-- xRead = csv_read_one_field;
-- }
-- 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;
-- }
++ zFN = z;
}
-- 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;
++ }
++
++ /* 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;
}
-- 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;
++ /* WASM mode has its own sandboxed pseudo-filesystem. */
#endif
++ if( zFN ){
++ zNewFilename = smprintf("%s", zFN);
++ shell_check_ooms(zNewFilename);
}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);
++ 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{
-- 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);
-- }
-- if( rc ){
-- if (pStmt) sqlite3_finalize(pStmt);
-- utf8_printf(stderr,"Error: %s\n", sqlite3_errmsg(p->db));
-- goto import_fail;
++ psi->pAuxDb->zFreeOnClose = zNewFilename;
}
-- 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;
-- }
-- 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++; }
-- }
-- }
-- if( sCtx.cTerm==sCtx.cColSep ){
-- do{
-- xRead(&sCtx);
-- i++;
-- }while( sCtx.cTerm==sCtx.cColSep );
-- utf8_printf(stderr, "%s:%d: expected %d columns but found %d - "
-- "extras ignored\n",
-- sCtx.zFile, startLine, nCol, i);
-- }
-- if( i>=nCol ){
-- sqlite3_step(pStmt);
-- rc = sqlite3_reset(pStmt);
-- if( rc!=SQLITE_OK ){
-- utf8_printf(stderr, "%s:%d: INSERT failed: %s\n", sCtx.zFile,
-- startLine, sqlite3_errmsg(p->db));
-- sCtx.nErr++;
-- }else{
-- sCtx.nRow++;
-- }
-- }
-- }while( sCtx.cTerm!=EOF );
++ }
++ 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);
++}
-- 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) */
++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;
++}
--#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. */
++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;
++ 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 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;
-- goto meta_command_exit;
-- }
-- open_db(p, 0);
-- if( nArg==2 ){
-- sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->db, "main", 0, 1);
-- goto meta_command_exit;
-- }
-- zSql = sqlite3_mprintf(
-- "SELECT rootpage, 0 FROM sqlite_schema"
-- " WHERE name='%q' AND type='index'"
-- "UNION ALL "
-- "SELECT rootpage, 1 FROM sqlite_schema"
-- " WHERE name='%q' AND type='table'"
-- " AND sql LIKE '%%without%%rowid%%'",
-- azArg[1], azArg[1]
-- );
-- sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
-- sqlite3_free(zSql);
-- if( sqlite3_step(pStmt)==SQLITE_ROW ){
-- tnum = sqlite3_column_int(pStmt, 0);
-- isWO = sqlite3_column_int(pStmt, 1);
-- }
-- sqlite3_finalize(pStmt);
-- zSql = sqlite3_mprintf("PRAGMA index_xinfo='%q'", azArg[1]);
-- rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
-- sqlite3_free(zSql);
-- i = 0;
-- while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
-- char zLabel[20];
-- const char *zCol = (const char*)sqlite3_column_text(pStmt,2);
-- i++;
-- if( zCol==0 ){
-- if( sqlite3_column_int(pStmt,1)==-1 ){
-- zCol = "_ROWID_";
-- }else{
-- sqlite3_snprintf(sizeof(zLabel),zLabel,"expr%d",i);
-- zCol = zLabel;
-- }
-- }
-- if( isWO && lenPK==0 && sqlite3_column_int(pStmt,5)==0 && zCollist ){
-- lenPK = (int)strlen(zCollist);
-- }
-- if( zCollist==0 ){
-- zCollist = sqlite3_mprintf("\"%w\"", zCol);
-- }else{
-- zCollist = sqlite3_mprintf("%z,\"%w\"", zCollist, zCol);
++ }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);
}
}
-- 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"
-- );
-- }
++ }
++ release_holder();
++ assert(dbStore==0);
++ if( rc!=0 ) return rc;
++
++ 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 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 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 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 * 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;
++ char * zEntrySql = 0;
++
++ 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);
++ }
++ if( eval ){
++ zSql = smprintf("SELECT value, uses FROM %s WHERE key=%Q"
++ " AND uses IN (%d, "SPTU_Entry") ORDER BY uses",
++ zTab, name, uses);
++ }else{
++ 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); /* +2 */
++ sstr_holder(zSql); /* +3 */
++ s3_exec_noom(db, zSql, kv_find_callback, &kvRow, 0);
++ release_holder(); /* zSql =2 */
++ assert(kvRow.hits<3);
++ if( kvRow.hits>=1 ){
++ /* Editing an existing value of same kind. */
++ zVal = kvRow.value;
++ drop_holder(); /* kvRow.value =1 */
++ zSql = smprintf("SELECT edit('-- %s\n'||%Q, %Q)", name, zVal, zEd);
++ 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 =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); /* +2 */
++ if( eval ){
++ zEntrySql = smprintf("INSERT OR REPLACE INTO %s(key,value,uses)"
++ " VALUES(%Q,%Q,"SPTU_Entry")", zTab, name,zVal);
++ }else{
++ zEntrySql = smprintf("DELETE FROM %s WHERE key=%Q AND uses="SPTU_Entry,
++ zTab, name);
++ }
++ if( zEntrySql ){
++ sqlite3_exec(db, zEntrySql, 0, 0, 0);
++ sqlite3_free(zEntrySql);
++ }
++ rc = sqlite3_exec(db, zSql, 0, 0, 0);
++ 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, "SQLITE_TESTCTRL_IMPOSTER returns %d\n", rc);
-- rc = 1;
++ *pzErr = smprintf("Cannot execute %s\n(Fails with %s)\n",
++ zSql, sqlite3_errmsg(db));
}
++ }
++ release_holders(2); /* =0 */
++ return rc!=SQLITE_OK;
++}
++#endif
++
++/* Space-join values in an argument list. *valLim is not included. */
++char *values_join( char **valBeg, char **valLim ){
++ char *z = 0;
++ const char *zSep = 0;
++ while( valBeg < valLim ){
++ z = smprintf("%z%s%s", z, zSep, *valBeg);
++ zSep = " ";
++ ++valBeg;
++ }
++ return z;
++}
++
++#define INT_RE "((\\d+)|(0[xX][0-9a-fA-F]+))"
++#define REAL_RE "((\\d+(\\.\\d+)?([eE][-+]?\\d{1,4})?)|(\\.\\d+))"
++#define HEXP_RE "([0-9a-fA-F]{2,2})"
++const char *param_set_literals[] = {
++ /* int */ "^[-+]?" INT_RE "$",
++ /* real */ "^[-+]?" REAL_RE "$",
++ /* blob */ "^[xX]'"HEXP_RE"*'$",
++ /* text */ "^'([^']|'')*'$"
++};
++#undef INT_RE
++#undef REAL_RE
++#undef HEXP_RE
++
++/* 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;
++}
++
++
++/* Effect most of the .parameter set subcommand (per help text.)
++ * Return SQLITE_OK on success, else SQLITE_ERROR. Error
++ * explanation is placed in *pzErr, to be later sqlite3_free()'ed.
++ */
++static int param_set(sqlite3 *db, u8 bEval, char *name,
++ char **valBeg, char **valLim, char **pzErr){
++ char *zSql = 0;
++ char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0;
++ sqlite3_stmt *pStmtSet = 0;
++ /* Above objects are managed. */
++ const char **pSL;
++ u8 bLitMatch = 0;
++ int rc = SQLITE_OK, retries = 0;
++ char *zValue = (zValGlom==0)? *valBeg : zValGlom;
++
++ sstr_holder(zValGlom); /* +1 */
++ if( !bEval ){
++ /* No eval specified; see if the value matches a common literal form. */
++ zSql = smprintf("SELECT regexp($re,%Q)", zValue);
++ rc = s3_prep_noom_free(db, &zSql, &pStmtSet);
++ stmt_ptr_holder(&pStmtSet); /* +2 */
++ for( pSL=param_set_literals; pSL<PastArray(param_set_literals); ++pSL ){
++ sqlite3_reset(pStmtSet);
++ rc = sqlite3_bind_text(pStmtSet,1,*pSL,-1,SQLITE_STATIC);
++ shell_check_nomem(rc);
++ rc = shell_check_nomem(sqlite3_step(pStmtSet));
++ assert(rc==SQLITE_ROW);
++ if( 0!=(bLitMatch = sqlite3_column_int(pStmtSet, 0)) ) break;
++ }
++ release_holder(); /* =1 */
++ }
++ /* These possible conditions may exist, to be handled thus:
++ * 1. No evaluation was specified.
++ * Value must be a recognizable literal or will be treated as text.
++ * Success is assured (absent OOM.)
++ * 2. Evaluation was specified.
++ * Just do it and take whatever type results.
++ * Report failure if that occurs.
++ * In both cases, a PTU_Entry row is set to the input value.
++ */
++ stmt_ptr_holder(&pStmtSet); /* +2 */
++ if( bEval || bLitMatch ){
++ zSql = smprintf
++ ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ " VALUES(%Q,(%s),"SPTU_Binding")", name, zValue );
++ }else{
++ 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);
++ if( rc!=SQLITE_OK ){
++ /* Reach here when value matches no literal and fails evaluation. */
++ sqlite3_finalize(pStmtSet);
++ pStmtSet = 0;
++ if( pzErr ){
++ *pzErr = smprintf("Parameter %s set as text.\nCompiling \"%s\" fails.\n"
++ "(%s)", name, zValue, sqlite3_errmsg(db));
++ }
++ 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);
++ }
++ zSql = smprintf("INSERT OR REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)"
++ " VALUES(%Q,%Q,"SPTU_Entry")", name, zValue);
++ if( zSql ){
++ sqlite3_exec(db, zSql, 0, 0, 0);
sqlite3_free(zSql);
-- }else
--#endif /* !defined(SQLITE_OMIT_TEST_CONTROL) */
++ }
++ sqlite3_step(pStmtSet);
++ release_holders(2);
++ return rc;
++}
--#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;
++/* 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;
++
++ 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;
++ }
++ 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,"
++ "CASE typeof(value)"
++ " WHEN 'text' THEN quote(value)"
++ " WHEN 'blob' THEN 'x'''||hex(value)||''''"
++ " ELSE value END"
++ " %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 = (const char*)sqlite3_column_text(pStmt,0);
++ const char *zValue = (const char*)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{
-- 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;
++ 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));
}
++ if( nc>0 ) utf8_printf(out, "\n");
}
-- }else
++ }
++ RESOURCE_FREE(mark);
++}
++
++/* 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, ")");
++ }
++}
++
++/* 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 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, ")");
++ }
++}
++
++/*****************
++ * 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(s)",
++ " OPT may be -t to use edited value(s) as text or -e to evaluate it,",
++ " or -m or -r to edit missing or referenced parameters from last query.",
#endif
++ " 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 ?OPT? NAME VALUE Give SQL parameter NAME a value of VALUE",
++ " NAME must begin with one of $,:,@,? for bindings, VALUE is the space-",
++ " joined arguments. OPT may -e to evaluate VALUE as a SQL expression.",
++ " unset ?NAMES? Remove named parameter(s) from parameters table",
++];
++DISPATCHABLE_COMMAND( parameter 2 2 0 ){
++ int rc = 0;
++ char *zErr = 0;
++ sqlite3 *db = open_db(p,0);
-- if( c=='l' && n>=5 && cli_strncmp(azArg[0], "limits", n)==0 ){
-- static const struct {
-- const char *zLimitName; /* Name of a limit */
-- int limitCode; /* Integer code for that limit */
-- } aLimit[] = {
-- { "length", SQLITE_LIMIT_LENGTH },
-- { "sql_length", SQLITE_LIMIT_SQL_LENGTH },
-- { "column", SQLITE_LIMIT_COLUMN },
-- { "expr_depth", SQLITE_LIMIT_EXPR_DEPTH },
-- { "compound_select", SQLITE_LIMIT_COMPOUND_SELECT },
-- { "vdbe_op", SQLITE_LIMIT_VDBE_OP },
-- { "function_arg", SQLITE_LIMIT_FUNCTION_ARG },
-- { "attached", SQLITE_LIMIT_ATTACHED },
-- { "like_pattern_length", SQLITE_LIMIT_LIKE_PATTERN_LENGTH },
-- { "variable_number", SQLITE_LIMIT_VARIABLE_NUMBER },
-- { "trigger_depth", SQLITE_LIMIT_TRIGGER_DEPTH },
-- { "worker_threads", SQLITE_LIMIT_WORKER_THREADS },
-- };
-- int i, n2;
-- open_db(p, 0);
-- if( nArg==1 ){
-- for(i=0; i<ArraySize(aLimit); i++){
-- printf("%20s %d\n", aLimit[i].zLimitName,
-- sqlite3_limit(p->db, aLimit[i].limitCode, -1));
-- }
-- }else if( nArg>3 ){
-- raw_printf(stderr, "Usage: .limit NAME ?NEW-VALUE?\n");
-- rc = 1;
-- goto meta_command_exit;
-- }else{
-- int iLimit = -1;
-- n2 = strlen30(azArg[1]);
-- for(i=0; i<ArraySize(aLimit); i++){
-- if( sqlite3_strnicmp(aLimit[i].zLimitName, azArg[1], n2)==0 ){
-- if( iLimit<0 ){
-- iLimit = i;
-- }else{
-- utf8_printf(stderr, "ambiguous limit: \"%s\"\n", azArg[1]);
-- rc = 1;
-- goto meta_command_exit;
-- }
++ /* .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);
++ 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;
}
}
-- if( iLimit<0 ){
-- utf8_printf(stderr, "unknown limit: \"%s\"\n"
-- "enter \".limits\" with no arguments for a list.\n",
-- azArg[1]);
-- rc = 1;
-- goto meta_command_exit;
++ 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;
}
-- if( nArg==3 ){
-- sqlite3_limit(p->db, aLimit[iLimit].limitCode,
-- (int)integerValue(azArg[2]));
++ 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;
}
-- printf("%20s %d\n", aLimit[iLimit].zLimitName,
-- sqlite3_limit(p->db, aLimit[iLimit].limitCode, -1));
++ /* Edit */
}
}else
++#endif
-- if( c=='l' && n>2 && cli_strncmp(azArg[0], "lint", n)==0 ){
-- open_db(p, 0);
-- lintDotCommand(p, azArg, nArg);
++ /* .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 !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_FIDDLE)
-- if( c=='l' && cli_strncmp(azArg[0], "load", n)==0 ){
-- const char *zFile, *zProc;
-- char *zErrMsg = 0;
-- failIfSafeMode(p, "cannot run .load in safe mode");
-- if( nArg<2 || azArg[1][0]==0 ){
-- /* Must have a non-empty FILE. (Will not load self.) */
-- raw_printf(stderr, "Usage: .load FILE ?ENTRYPOINT?\n");
-- rc = 1;
-- goto meta_command_exit;
-- }
-- zFile = azArg[1];
-- zProc = nArg>=3 ? azArg[2] : 0;
-- open_db(p, 0);
-- rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
-- if( rc!=SQLITE_OK ){
-- utf8_printf(stderr, "Error: %s\n", zErrMsg);
-- sqlite3_free(zErrMsg);
-- 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
--#endif
-- if( c=='l' && cli_strncmp(azArg[0], "log", n)==0 ){
-- if( nArg!=2 ){
-- raw_printf(stderr, "Usage: .log FILENAME\n");
-- rc = 1;
-- }else{
-- const char *zFile = azArg[1];
-- if( p->bSafeMode
-- && cli_strcmp(zFile,"on")!=0
-- && cli_strcmp(zFile,"off")!=0
-- ){
-- raw_printf(stdout, "cannot set .log to anything other "
-- "than \"on\" or \"off\"\n");
-- zFile = "off";
-- }
-- output_file_close(p->pLog);
-- if( cli_strcmp(zFile,"on")==0 ) zFile = "stdout";
-- p->pLog = output_file_open(zFile, 0);
-- }
++ /* .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
-- if( c=='m' && cli_strncmp(azArg[0], "mode", n)==0 ){
-- const char *zMode = 0;
-- const char *zTabname = 0;
-- int i, n2;
-- ColModeOpts cmOpts = ColModeOpts_default;
-- for(i=1; i<nArg; i++){
-- const char *z = azArg[i];
-- if( optionMatch(z,"wrap") && i+1<nArg ){
-- cmOpts.iWrap = integerValue(azArg[++i]);
-- }else if( optionMatch(z,"ww") ){
-- cmOpts.bWordWrap = 1;
-- }else if( optionMatch(z,"wordwrap") && i+1<nArg ){
-- cmOpts.bWordWrap = (u8)booleanValue(azArg[++i]);
-- }else if( optionMatch(z,"quote") ){
-- cmOpts.bQuote = 1;
-- }else if( optionMatch(z,"noquote") ){
-- cmOpts.bQuote = 0;
-- }else if( zMode==0 ){
-- zMode = z;
-- /* Apply defaults for qbox pseudo-mode. If that
-- * overwrites already-set values, user was informed of this.
-- */
-- if( cli_strcmp(z, "qbox")==0 ){
-- ColModeOpts cmo = ColModeOpts_default_qbox;
-- zMode = "box";
-- cmOpts = cmo;
-- }
-- }else if( zTabname==0 ){
-- zTabname = z;
-- }else if( z[0]=='-' ){
-- utf8_printf(stderr, "unknown option: %s\n", z);
-- utf8_printf(stderr, "options:\n"
-- " --noquote\n"
-- " --quote\n"
-- " --wordwrap on/off\n"
-- " --wrap N\n"
-- " --ww\n");
-- rc = 1;
-- goto meta_command_exit;
-- }else{
-- utf8_printf(stderr, "extra argument: \"%s\"\n", z);
-- rc = 1;
-- goto meta_command_exit;
-- }
-- }
-- if( zMode==0 ){
-- if( p->mode==MODE_Column
-- || (p->mode>=MODE_Markdown && p->mode<=MODE_Box)
-- ){
-- raw_printf
-- (p->out,
-- "current output mode: %s --wrap %d --wordwrap %s --%squote\n",
-- modeDescr[p->mode], p->cmOpts.iWrap,
-- p->cmOpts.bWordWrap ? "on" : "off",
-- p->cmOpts.bQuote ? "" : "no");
-- }else{
-- raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
-- }
-- zMode = modeDescr[p->mode];
-- }
-- n2 = strlen30(zMode);
-- if( cli_strncmp(zMode,"lines",n2)==0 ){
-- p->mode = MODE_Line;
-- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
-- }else if( cli_strncmp(zMode,"columns",n2)==0 ){
-- p->mode = MODE_Column;
-- if( (p->shellFlgs & SHFLG_HeaderSet)==0 ){
-- p->showHeader = 1;
-- }
-- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
-- p->cmOpts = cmOpts;
-- }else if( cli_strncmp(zMode,"list",n2)==0 ){
-- p->mode = MODE_List;
-- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Column);
-- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
-- }else if( cli_strncmp(zMode,"html",n2)==0 ){
-- p->mode = MODE_Html;
-- }else if( cli_strncmp(zMode,"tcl",n2)==0 ){
-- p->mode = MODE_Tcl;
-- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Space);
-- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
-- }else if( cli_strncmp(zMode,"csv",n2)==0 ){
-- p->mode = MODE_Csv;
-- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
-- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_CrLf);
-- }else if( cli_strncmp(zMode,"tabs",n2)==0 ){
-- p->mode = MODE_List;
-- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Tab);
-- }else if( cli_strncmp(zMode,"insert",n2)==0 ){
-- p->mode = MODE_Insert;
-- set_table_name(p, zTabname ? zTabname : "table");
-- }else if( cli_strncmp(zMode,"quote",n2)==0 ){
-- p->mode = MODE_Quote;
-- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Comma);
-- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Row);
-- }else if( cli_strncmp(zMode,"ascii",n2)==0 ){
-- p->mode = MODE_Ascii;
-- sqlite3_snprintf(sizeof(p->colSeparator), p->colSeparator, SEP_Unit);
-- sqlite3_snprintf(sizeof(p->rowSeparator), p->rowSeparator, SEP_Record);
-- }else if( cli_strncmp(zMode,"markdown",n2)==0 ){
-- p->mode = MODE_Markdown;
-- p->cmOpts = cmOpts;
-- }else if( cli_strncmp(zMode,"table",n2)==0 ){
-- p->mode = MODE_Table;
-- p->cmOpts = cmOpts;
-- }else if( cli_strncmp(zMode,"box",n2)==0 ){
-- p->mode = MODE_Box;
-- p->cmOpts = cmOpts;
-- }else if( cli_strncmp(zMode,"count",n2)==0 ){
-- p->mode = MODE_Count;
-- }else if( cli_strncmp(zMode,"off",n2)==0 ){
-- p->mode = MODE_Off;
-- }else if( cli_strncmp(zMode,"json",n2)==0 ){
-- p->mode = MODE_Json;
-- }else{
-- raw_printf(stderr, "Error: mode should be one of: "
-- "ascii box column csv html insert json line list markdown "
-- "qbox quote table tabs tcl\n");
-- rc = 1;
-- }
-- p->cMode = p->mode;
++ /* .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_SHELL_FIDDLE
-- if( c=='n' && cli_strcmp(azArg[0], "nonce")==0 ){
-- if( nArg!=2 ){
-- raw_printf(stderr, "Usage: .nonce NONCE\n");
++ /* .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 inv = 2;
++ u8 bEval = 0;
++ char cOpt;
++ ParamTableUse ptu;
++
++ while( inv<nArg && (cOpt = option_char(azArg[inv]))!=0 ){
++ if( cOpt!='e' ) goto param_fail;
++ bEval = 1;
++ ++inv;
++ }
++ if( nArg-inv < 2 ) goto param_fail;
++ 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 if( p->zNonce==0 || cli_strcmp(azArg[1],p->zNonce)!=0 ){
-- raw_printf(stderr, "line %d: incorrect nonce: \"%s\"\n",
-- p->lineno, azArg[1]);
-- exit(1);
}else{
-- p->bSafeMode = 0;
-- return 0; /* Return immediately to bypass the safe mode reset
-- ** at the end of this procedure */
-- }
-- }else
--#endif /* !defined(SQLITE_SHELL_FIDDLE) */
--
-- if( c=='n' && cli_strncmp(azArg[0], "nullvalue", n)==0 ){
-- if( nArg==2 ){
-- sqlite3_snprintf(sizeof(p->nullValue), p->nullValue,
-- "%.*s", (int)ArraySize(p->nullValue)-1, azArg[1]);
-- }else{
-- raw_printf(stderr, "Usage: .nullvalue STRING\n");
-- rc = 1;
++ param_table_init(db);
++ rc = param_set(db, bEval, azArg[inv], &azArg[inv+1], &azArg[nArg], &zErr);
++ if( rc!=SQLITE_OK || zErr ){
++ utf8_printf(STD_ERR, "%s: %s\n", (rc)? "Error" : "Warning",
++ (zErr)? zErr : sqlite3_errmsg(db));
++ sqlite3_free(zErr);
++ }
}
}else
}
}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
++ /* .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(out, "session %s indirect flag = %d\n",
++ pSession->zName, ii);
++ }
++ }else
++
++ /* .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(out, "session %s isempty flag = %d\n",
++ pSession->zName, ii);
++ }
++ }else
++
++ /* .session list
++ ** List all currently open sessions
++ */
++ if( cli_strcmp(azCmd[0],"list")==0 ){
++ for(i=0; i<pAuxDb->nSession; i++){
++ utf8_printf(out, "%d %s\n", i, pAuxDb->aSession[i].zName);
++ }
++ }else
++
++ /* .session open DB NAME
++ ** Open a new session called NAME on the attached database DB.
++ ** DB is normally "main".
++ */
++ if( 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{
++
++ /* If no command name matches, show a syntax error */
++ session_syntax_error:
++ showHelp(out, "session", p);
++ return DCR_CmdErred;
++ }
++ return DCR_Ok|(rc!=0);
++}
++
++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);
++
++ 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
-- ){
++ || 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;
-- }
-- }
-- 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 = "),(";
-- }
-- 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);
++ 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{
-- shell_exec(p, zSql, 0);
++ zLike = z;
++ bSeparate = 1;
++ if( sqlite3_strlike("sqlite\\_%", zLike, '\\')==0 ) bSchema = 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 = 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 printf('\"%%w\"',ss.tname) as tname,"
-- " printf('\"%%w\"',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;
++ {
++ 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"
+++ " select printf('\"%%w\"',ss.tname) as tname,"
+++ " printf('\"%%w\"',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( 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);
++ 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(pStmt);
}
}
-- if( rc ) utf8_printf(stderr, ".sha3sum failed.\n");
-- sqlite3_free(zRevText);
}
++ }
#endif /* !defined(*_OMIT_SCHEMA_PRAGMAS) && !defined(*_OMIT_VIRTUALTABLE) */
-- sqlite3_free(zSql);
-- }else
++ RESOURCE_FREE(mark);
++ return DCR_Ok;
++}
--#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;
-- }
-- zCmd = sqlite3_mprintf(strchr(azArg[1],' ')==0?"%s":"\"%s\"", azArg[1]);
-- for(i=2; i<nArg && zCmd!=0; i++){
-- zCmd = sqlite3_mprintf(strchr(azArg[i],' ')==0?"%z %s":"%z \"%s\"",
-- zCmd, azArg[i]);
-- }
-- x = zCmd!=0 ? system(zCmd) : 1;
-- sqlite3_free(zCmd);
-- if( x ) raw_printf(stderr, "System command returns %d\n", x);
-- }else
--#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_FIDDLE) */
++/*****************
++ * 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",
++];
-- 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
++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;
++}
-- 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);
++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 */
++
++ 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
++ {
++ *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{
-- raw_printf(stderr, "Usage: .stats ?on|off|stmt|vmstep?\n");
-- rc = 1;
++ 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);
}
-- }else
--
-- 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);
-- }
--
-- 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);
++ *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{
-- appendText(&s," WHERE type='index'"
-- " AND tbl_name LIKE ?1", 0);
++ *pzErr = smprintf
++ ("Unknown operation \"%s\" on selftest line %d\n", zOp, tno);
++ rc = 1;
++ break;
}
++ } /* 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;
++}
++
++/*****************
++ * 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 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;
}
-- 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]:"");
++ if( zFile==0 ) zFile = zA;
++ else if( zProc==0 ) zProc = zA;
++ }
++ if( zFile==0 ) return DCR_Missing;
++ if( pzExtArgs==0 ) pzExtArgs = azArg + ai;
++ open_db(p, 0);
++ rc = load_shell_extension(p, zFile, zProc, pzErr, nArg-ai, pzExtArgs);
++ return DCR_Ok|(rc!=SQLITE_OK);
++}
++
++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;
}
-- raw_printf(p->out, "\n");
++ }
++ if( io==ArraySize(shopts) ){
++ zAbout = azArg[ia];
++ zMoan = "is not a recognized option name";
++ goto moan_error;
}
}
++ }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;
++}
-- for(ii=0; ii<nRow; ii++) sqlite3_free(azResult[ii]);
-- sqlite3_free(azResult);
-- }else
++static void showWidths(ShellExState *p){
++ int i = 0;
++ while( i < p->numWidths ){
++ raw_printf(ISS(p)->out," %d",p->pSpecWidths[i++]);
++ }
++ raw_printf(ISS(p)->out,"\n");
++}
--#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]);
++DISPATCHABLE_COMMAND( show ? 1 1 ){
++ static const char *azBool[] = { "off", "on", "trigger", "full"};
++ const char *zOut;
++ ShellInState *psi = ISS(p);
++ FILE *out = psi->out;
++
++ 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{
-- sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
++ utf8_printf(out, "%12.12s: %s\n", "mode", zOut);
}
-- }else
--#endif /* !defined(SQLITE_SHELL_FIDDLE) */
++ sqlite3_free(zTell);
++ }
++#else
++ 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
++ 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");
++ showWidths(p);
++ utf8_printf(out, "%12.12s: %s\n", "filename",
++ psi->pAuxDb->zDbFilename ? psi->pAuxDb->zDbFilename : "");
++ return DCR_Ok;
++}
--#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 unless --unsafe-testing */
-- const char *zUsage; /* Usage notes */
-- } aCtrl[] = {
++/*****************
++ * 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{
++ psi->statsOn = (u8)booleanValue(azArg[1]);
++ }
++ }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;
++}
++
++/*****************
++ * 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{
++ 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);
++ }
++ 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;
++ }
++ 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");
++ }
++ }
++ RESOURCE_FREE(mark);
++ return DCR_Ok;
++}
++
++COLLECT_HELP_TEXT[
++#ifndef LEGACY_TABLES_LISTING
++ ".tables ?FLAG? ?TVLIKE? List names of tables and/or views",
++ " FLAG may be -t, -v or -s to list only tables, views or system tables",
++ " TVLIKE may restrict the listing to names matching given LIKE pattern",
++#else
++ ".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
++#endif
++];
++DISPATCHABLE_COMMAND( tables 2 1 3 ){
++ char objType = 'T';
++#ifndef LEGACY_TABLES_LISTING
++ if( nArg>1 && azArg[1][0]=='-' && azArg[1][1]!=0 && azArg[1][2]==0 ){
++ char c = azArg[1][1];
++ switch (c){
++ case 's':
++ case 't':
++ case 'v':
++ objType = c;
++ ++azArg;
++ --nArg;
++ break;
++ default:
++ return DCR_Unknown|1;
++ }
++ }
++#endif
++ return showTableLike(azArg, nArg, p, pzErr, objType);
++}
++DISPATCHABLE_COMMAND( indexes 3 1 2 ){
++ return showTableLike(azArg, nArg, p, pzErr, 'i');
++}
++DISPATCHABLE_COMMAND( indices 3 1 2 ){
++ return showTableLike(azArg, nArg, p, pzErr, 'i');
++}
++
++/*****************
++ * The .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);
++}
++
++/*****************
++ * 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 */
+++ int unSafe; /* Not valid unless --unsafe-testing */
++ 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, "" },*/
{"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;
++ };
++ 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";
++ open_db(p, 0);
++ zCmd = nArg>=2 ? azArg[1] : "help";
++
++ /* The argument can optionally begin with "-" or "--" */
++ if( zCmd[0]=='-' && zCmd[1] ){
++ zCmd++;
++ if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
++ }
-- /* The argument can optionally begin with "-" or "--" */
-- if( zCmd[0]=='-' && zCmd[1] ){
-- zCmd++;
-- if( zCmd[0]=='-' && zCmd[1] ) zCmd++;
++ /* --help lists all test-controls */
++ if( cli_strcmp(zCmd,"help")==0 ){
++ utf8_printf(out, "Available test-controls:\n");
++ for(i=0; i<ArraySize(aCtrl); i++){
+++ if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
++ utf8_printf(out, " .testctrl %s %s\n",
++ aCtrl[i].zCtrlName, aCtrl[i].zUsage);
}
++ return DCR_CmdErred;
++ }
-- /* --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++){
-- if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
-- utf8_printf(p->out, " .testctrl %s %s\n",
-- aCtrl[i].zCtrlName, aCtrl[i].zUsage);
++ /* convert testctrl text option to value. allow any unique prefix
++ ** of the option name, or a numerical value. */
++ n2 = strlen30(zCmd);
++ for(i=0; i<ArraySize(aCtrl); i++){
+++ if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
++ if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
++ if( testctrl<0 ){
++ testctrl = aCtrl[i].ctrlCode;
++ iCtrl = i;
++ }else{
++ *pzErr = smprintf
++ ("Error: ambiguous test-control: \"%s\"\n"
++ "Use \".testctrl --help\" for help\n", zCmd);
++ return DCR_ArgWrong;
}
-- rc = 1;
-- goto meta_command_exit;
}
- }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;
++ }
++ if( testctrl<0 ){
++ utf8_printf(STD_ERR,"Error: unknown test-control: %s\n"
++ "Use \".testctrl --help\" for help\n", zCmd);
++ }else{
++ switch(testctrl){
-- /* convert testctrl text option to value. allow any unique prefix
-- ** of the option name, or a numerical value. */
-- n2 = strlen30(zCmd);
-- for(i=0; i<ArraySize(aCtrl); i++){
-- if( aCtrl[i].unSafe && !ShellHasFlag(p,SHFLG_TestingMode) ) continue;
-- if( cli_strncmp(zCmd, aCtrl[i].zCtrlName, n2)==0 ){
-- if( testctrl<0 ){
-- testctrl = aCtrl[i].ctrlCode;
-- iCtrl = i;
++ /* 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;
++ }
++ break;
++
++ /* sqlite3_test_control(int) */
++ case SQLITE_TESTCTRL_PRNG_SAVE:
++ case SQLITE_TESTCTRL_PRNG_RESTORE:
++ case SQLITE_TESTCTRL_BYTEORDER:
++ if( nArg==2 ){
++ rc2 = sqlite3_test_control(testctrl);
++ isOk = testctrl==SQLITE_TESTCTRL_BYTEORDER ? 1 : 3;
++ }
++ break;
++
++ /* sqlite3_test_control(int, uint) */
++ case SQLITE_TESTCTRL_PENDING_BYTE:
++ if( nArg==3 ){
++ unsigned int opt = (unsigned int)integerValue(azArg[2]);
++ rc2 = sqlite3_test_control(testctrl, opt);
++ isOk = 3;
++ }
++ break;
++
++ /* sqlite3_test_control(int, 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);
++ }
++ if( nArg==3 ){
++ db = 0;
}else{
-- utf8_printf(stderr, "Error: ambiguous test-control: \"%s\"\n"
-- "Use \".testctrl --help\" for help\n", zCmd);
-- rc = 1;
-- goto meta_command_exit;
++ db = DBX(p);
++ /* Make sure the schema has been loaded */
++ sqlite3_table_column_metadata(db, 0, "x", 0, 0, 0, 0, 0, 0);
++ }
++ rc2 = sqlite3_test_control(testctrl, ii, db);
++ isOk = 3;
++ }
++ break;
++
++ /* sqlite3_test_control(int, int) */
++ case SQLITE_TESTCTRL_ASSERT:
++ case SQLITE_TESTCTRL_ALWAYS:
++ if( nArg==3 ){
++ int opt = booleanValue(azArg[2]);
++ rc2 = sqlite3_test_control(testctrl, opt);
++ isOk = 1;
++ }
++ break;
++
++ /* sqlite3_test_control(int, int) */
++ case SQLITE_TESTCTRL_LOCALTIME_FAULT:
++ case SQLITE_TESTCTRL_NEVER_CORRUPT:
++ if( nArg==3 ){
++ int opt = booleanValue(azArg[2]);
++ rc2 = sqlite3_test_control(testctrl, opt);
++ isOk = 3;
++ }
++ break;
++
++ /* sqlite3_test_control(sqlite3*) */
++ case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS:
++ rc2 = sqlite3_test_control(testctrl, DBX(p));
++ isOk = 3;
++ break;
++
++ case SQLITE_TESTCTRL_IMPOSTER:
++ if( nArg==5 ){
++ rc2 = sqlite3_test_control(testctrl, DBX(p),
++ azArg[2],
++ integerValue(azArg[3]),
++ integerValue(azArg[4]));
++ isOk = 3;
++ }
++ 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;
++ }
++ 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(out, " ");
++ utf8_printf(out, "%d: %d", id, val);
++ id++;
}
++ if( id>1 ) utf8_printf(out, "\n");
++ isOk = 3;
}
++ break;
++ }
++#endif
}
-- if( testctrl<0 ){
-- utf8_printf(stderr,"Error: unknown test-control: %s\n"
-- "Use \".testctrl --help\" for help\n", zCmd);
++ }
++ 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
++ else if( optionMatch(z, "plain") ){
++ psi->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 {
++ return DCR_Unknown|jj;
++ }
}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;
++ 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;
++}
-- /* 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;
++/*****************
++ * 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;
++}
-- /* 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;
++/*****************
++ * 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;
-- /* 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;
++ 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);
++ }
++ }
++ return DCR_Ok;
++}
-- /* 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;
-- }
++/*****************
++ * 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;
++ }
++ 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( cli_strcmp(azArg[1],"add")==0 ){
++ if( nArg!=5 ){
++ goto teach_fail;
++ }
++ 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;
++ }
++ }else if( cli_strcmp(azArg[1],"edit")==0 ){
++ if( nArg!=5 ){
++ goto teach_fail;
++ }
++ 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;
++ }
++ }else if( cli_strcmp(azArg[1],"delete")==0 ){
++ if( nArg!=3 ){
++ goto teach_fail;
++ }
++ rc = sqlite3_user_delete(DBX(p), azArg[2]);
++ if( rc ){
++ *pzErr = smprintf(0,"User-Delete failed: %d\n", rc);
++ return DCR_Error;
++ }
++ }else{
++ goto teach_fail;
++ }
++ return DCR_Ok;
++}
++
++/*****************
++ * 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( *zCmd>'e' && ncCmd<2 ) return DCR_Ambiguous|1;
++#define SUBCMD(scn) (cli_strncmp(zCmd, scn, ncCmd)==0)
++
++ /* 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;
++ }
++
++ /* .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();
++ }
++#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;
++ }
++ }
++ 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;
++ }
++#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{
++ 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{
++ showHelp(ISS(p)->out, "vars", p);
++ return DCR_CmdErred;
++ }
++ return DCR_Ok | (rv!=0) | (rc!=0);
++#undef SUBCMD
++}
++
++/*****************
++ * 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
++#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);
++ }
++ }
++ 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. A lone ? shows the current values.",
++];
++DISPATCHABLE_COMMAND( width ? 1 0 ){
++ if( nArg==2 && cli_strcmp(azArg[1],"?")==0 ) showWidths(p);
++ else 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;
++}
++
++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;
++ }
++#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;
++}
++
++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;
++ }
++ }
++ if( zOpt==0 ) continue;
++ }
++ 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(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( rv<DCR_ArgIxMask ){
++ nErrors += (rv & DCR_Error);
++ /* Handle DCR_Return, DCR_Exit or DCR_Abort */
++ if( rv>DCR_Error ) break;
++ }else{
++ ++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;
++}
++
++/* 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
++};
++
++/* 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;
++
++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;
-- }
++static DotCmdRC
++ DotCommand_execute(DotCommand *pMe, ShellExState *pssx,
++ char **pzErrMsg, int nArgs, char *azArgs[]){
++ return (((struct CommandInfo *)pMe)->cmdDoer)(azArgs, nArgs, pssx, pzErrMsg);
++}
++
++/*****************
++** DotCommand iteration by name match, used by the .help dot-command.
++** 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
--#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;
-- }
++ } cursor;
++#if SHELL_DYNAMIC_EXTENSION
++ char *zAdhocHelpText; /* registered extension improvised help */
#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) */
++} 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
++}
-- 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);
++/* 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 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