From: larrybr Date: Thu, 29 Jun 2023 17:58:51 +0000 (+0000) Subject: Sync w/trunk X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f5b71c20a9a5d6e1e8b9aa7eab4e84d54c80bc63;p=thirdparty%2Fsqlite.git Sync w/trunk FossilOrigin-Name: fe9aa2e9c17f33a8f6045e9cf755f8251d261c34f89a59d658b72f7c99a6a12c --- f5b71c20a9a5d6e1e8b9aa7eab4e84d54c80bc63 diff --cc README.md index 0e60376bab,10262653e7,10262653e7..10262653e7 mode 100755,100644,100644..100755 --- a/README.md +++ b/README.md diff --cc ext/misc/tclshext.c.in index 38ae492817,0000000000,0000000000..f641251612 mode 100755,000000,000000..100755 --- a/ext/misc/tclshext.c.in +++ b/ext/misc/tclshext.c.in @@@@ -1,1308 -1,0 -1,0 +1,1316 @@@@ ++/* ++** 2022 March 20 ++** ++** The author disclaims copyright to this source code. In place of ++** a legal notice, here is a blessing: ++** ++** May you do good and not evil. ++** May you find forgiveness for yourself and forgive others. ++** May you share freely, never taking more than you give. ++** ++************************************************************************* ++** This file contains code to implement the "tclshext" shell extension ++** for use with the extensible "sqlite3" CLI shell. On *Nix, build thusly: ++ tool/mkshellc.tcl ext/misc/tclshext.c.in > tclshext.c ++ gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 \ ++ tclshext.c -o tclshext.so -ltcl8.6 ++** Or, (after ./configure ...), use the provided Makefile thusly: ++ make tcl_shell_extension ++** If the Tk library is available, it can be linked and used thusly: ++ gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 \ ++ -DSHELL_ENABLE_TK tclshext.c -o tclshext.so -ltcl8.6 -ltk8.6 ++** Or, make the same target with Tk thusly: ++ make tcl_shell_extension WITH_TK=1 ++** Later TCL versions can be used if desired (and installed.) ++** ++** TCL scripting support is added with a registerScripting() call (in the ++** ShellExtensionAPI), meeting ScriptingSupport interface requirements. ++*/ ++static const char * const zTclHelp = ++ "This extension adds these features to the host shell:\n" ++ " 1. TCL scripting support is added.\n" ++ " 2. TCL commands are added: udb shdb now_interactive get_tcl_group ..\n" ++ " 3. The .tcl and .unknown dot commands are added.\n" ++ " 4. If built with Tk capability, the gui TCL command will be added if this\n" ++ " extension was loaded using shell command: .load tclshext -shext -tk .\n" ++ " Any other arguments beyond -shext are copied into TCL's argv variable.\n" ++ "Operation:\n" ++ " Shell input groups beginning with \"..\" are treated as TCL input, in\n" ++ " these ways: (1) When a bare \"..\" is entered, a TCL REPL loop is run\n" ++ " until the end of input is seen; (2) When \"..D ...\" is entered, (where\n" ++ " \"D\" is a dot-command name), the D dot command will be run in its normal\n" ++ " fashion, but its arguments will be collected according to TCL parsing\n" ++ " rules then expanded as usual for TCL commands; and (3) when \".. T ...\"\n" ++ " is entered, (where \"T\" is a TCL command name), that TCL command and its\n" ++ " arguments will be collected and expanded according to TCL parsing rules,\n" ++ " then run in the TCL execution environment (in its global namespace), but\n" ++ " the shell REPL and execution environment remains in effect afterward.\n" ++ "\n" ++ " Note that cases 2 and 3 differ in having space after the leading \"..\".\n" ++ "\n" ++ " The phrase \"end of input\" means either: end-of-file is seen on a file,\n" ++ " pipe or string stream input, or a lone \".\" on the first and only line\n" ++ " of an input line group is seen. This convention is useful in scripting\n" ++ " when it is expedient to switch execution environments from within the\n" ++ " same input stream. This could be input piped in from another process.\n" ++ "\n" ++ ; ++/* ++** For example: ++ # From shell, enter TCL REPL: ++ .. ++ # Initialize some variables and insert into the DB: ++ set var1 [compute_however ...] ++ set var2 [derive_somehow ...] ++ udb eval { INSERT INTO SomeTable(col1, col2) VALUES($var1, var2) } ++ # Leave REPL ++ . ++ # Generate and keep pretty output: ++ .mode box -ww ++ .header on ++ .once prettified.txt ++ SELECT * FROM SomeTable; ++ # Alternatively, the query can be run from the TCL environment: ++ .. ++ set tstamp [clock format [clock seconds] -format %Y-%m-%d] ++ .once "backup of prettified.txt made $tstamp" ++ .eval {SELECT col1, col2 FROM SomeTable} ++ # Return to shell environment: ++ . ++** ++** For any of these ways of providing TCL input, the same TCL interpreter ++** is used, with its state maintained from one input to the next. In this ++** way, .sqliterc or other preparatory shell scripts (or typing) can be ++** made to provide useful, user-defined shell enhancements or specialized ++** procedures (aka "TCL commands") for oft-repeated tasks. ++** ++** The added TCL commands are: ++** udb shdb ; # exposes the user DB and shell DB for access via TCL ++** now_interactive ; # indicates whether current input is interactive ++** get_tcl_group ; # gets one TCL input line group from current input ++** register_adhoc_command ; # aids creation of dot commands with help ++** .. ; # does nothing, silently and without error ++** ++** The .. command exists so that a lone ".." on an input line suffices ++** to ensure the TCL REPL is running. This is symmetric with a lone "." ++** input to the TCL REPL because it either terminates the loop or, if ++** entered in the shell environment, quietly does nothing without error. ++** ++** The added .tcl dot command may be used to enter a TCL REPL, or with ++** arguments, it will read files as TCL. (This is somewhat extraneous, ++** as the same can be done with TCL commands, but it is more easily done ++** from the shell invocation, and the .tcl command's integration into ++** the .help facility provides a way for users to get help for "..".) ++** ++** The added .unknown dot command overrides the shell's .unknown so ++** that new dot commands can be implemented in TCL and then be run ++** from the shell in the dot command execution context. ++*/ ++ ++#include "shx_link.h" ++ ++/* Extension boiler-plate to dynamically link into host's SQLite library */ ++SQLITE_EXTENSION_INIT1; ++ ++/* Extension boiler-plate for a function to get ShellExtensionLink pointer ++ * from db passed to extension init() and define a pair of static API refs. ++ */ ++SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher); ++#define SHX_API(entry) pShExtApi->entry ++#define SHX_HELPER(entry) pExtHelpers->entry ++#define oprintf pExtHelpers->utf8CurrentOutPrintf ++ ++/* This is not found in the API pointer table published for extensions: */ ++#define sqlite3_enable_load_extension SHX_HELPER(enable_load_extension) ++ ++/* Forward reference for use as ExtensionId */ ++#ifdef _WIN32 ++__declspec(dllexport) ++#endif ++int sqlite3_tclshext_init(sqlite3*, char**, const sqlite3_api_routines*); ++ ++/* Control how tclsqlite.c compiles. (REPL is effected here, not there.) */ ++#define STATIC_BUILD /* Not publishing TCL API */ ++#undef SQLITE_AMALGAMATION ++#undef TCLSH ++#include "tclOO.h" ++#ifdef SHELL_ENABLE_TK ++#include "tk.h" /* Only used if option -tk passed during load. */ ++#endif ++INCLUDE ../../src/tclsqlite.c ++ ++#if defined(_WIN32) || defined(WIN32) ++# include ++# define getDir(cArray) _getcwd(cArray, sizeof(cArray)) ++# define chdir(s) _chdir(s) ++#else ++# define getDir(cArray) getcwd(cArray, sizeof(cArray)) ++#endif ++ ++typedef struct TclCmd TclCmd; ++typedef struct UnkCmd UnkCmd; ++ ++static struct InterpManage { ++ Tcl_Interp *pInterp; ++ int nRefs; ++} interpKeep = { 0, 0 }; ++ ++static Tcl_Interp *getInterp(){ ++ assert(interpKeep.nRefs>0 && interpKeep.pInterp!=0); ++ return interpKeep.pInterp; ++} ++ ++static void Tcl_TakeDown(void *pv){ ++ assert(pv==&interpKeep); ++ if( --interpKeep.nRefs==0 ){ ++ if( interpKeep.pInterp ){ ++ Tcl_DeleteInterp(interpKeep.pInterp); ++ Tcl_Release(interpKeep.pInterp); ++ interpKeep.pInterp = 0; ++ Tcl_Finalize(); ++ } ++ } ++} ++ ++static int Tcl_BringUp( ++#ifdef SHELL_ENABLE_TK ++ int *pWithTk, ++#endif ++ char **pzErrMsg){ ++ if( ++interpKeep.nRefs==1 ){ ++ const char *zShellName = SHX_HELPER(shellInvokedAs)(); ++ const char *zShellDir = SHX_HELPER(shellStartupDir)(); ++ if( zShellDir!=0 ){ ++ char cwd[FILENAME_MAX+1]; ++ if( getDir(cwd) && 0==chdir(zShellDir) ){ ++ int rc; ++ Tcl_FindExecutable(zShellName); ++ rc = chdir(cwd); /* result ignored, kept only to silence gcc */ ++ } ++ } ++ interpKeep.pInterp = Tcl_CreateInterp(); ++ Tcl_SetSystemEncoding(interpKeep.pInterp, "utf-8"); ++ Sqlite3_Init(interpKeep.pInterp); ++ Tcl_Preserve(interpKeep.pInterp); ++ if( 0==Tcl_OOInitStubs(interpKeep.pInterp) ){ ++ *pzErrMsg = sqlite3_mprintf("Tcl v8.6 or higher required.\n"); ++ Tcl_TakeDown(&interpKeep); ++ return SQLITE_ERROR; ++ } ++ if( Tcl_Init(interpKeep.pInterp)!=TCL_OK ){ ++ *pzErrMsg = sqlite3_mprintf("Tcl interpreter startup failed.\n"); ++ Tcl_TakeDown(&interpKeep); ++ return SQLITE_ERROR; ++ } ++#ifdef SHELL_ENABLE_TK ++ else if( *pWithTk ){ ++ if( TCL_OK!=Tk_Init(interpKeep.pInterp) ){ ++ fprintf(stderr, "Could not load/initialize Tk." ++ " (Non-fatal, extension is loaded.)\n"); ++ *pWithTk = 0; ++ } ++ } ++#endif ++ } ++ return (interpKeep.pInterp!=0)? SQLITE_OK : SQLITE_ERROR; ++} ++ ++static void copy_complaint(char **pzErr, Tcl_Interp *pi); ++static DotCmdRC runTclREPL(Tcl_Interp *interp, char **pzErrMsg); ++ ++/* Following DERIVED_METHOD(...) macro calls' arguments were copied and ++ * pasted from the respective interface declarations in shext_linkage.h ++ */ ++ ++/* This is in the interface for anouncing what was just provided. */ ++DERIVED_METHOD(const char *, name, ScriptSupport,TclSS, 0,()){ ++ (void)(pThis); ++ return "TclTk"; ++} ++ ++/* Provide help for users of this scripting implementation. */ ++DERIVED_METHOD(const char *, help, ScriptSupport,TclSS, 1,(const char *zHK)){ ++ (void)(pThis); ++ if( zHK==0 ){ ++ return "Provides TCL scripting support for SQLite extensible shell.\n"; ++ }else if( *zHK==0 ) return zTclHelp; ++ return 0; ++} ++ ++/* Not doing this yet. */ ++DERIVED_METHOD(int, configure, ScriptSupport,TclSS, ++ 4,( ShellExState *pSES, char **pzErr, ++ int numArgs, char *azArgs[] )){ ++ (void)(pThis); ++ return 0; ++} ++ ++/* Say line is script lead-in iff its first dark is "..". ++ * In combination with dot commands also being TCL commands and the ++ * special handling in the next three functions, this effects what is ++ * promised in this file's header text and by .tcl's help text. ++ */ ++DERIVED_METHOD(int, isScriptLeader, ScriptSupport,TclSS, ++ 1,( const char *zScriptLeadingLine )){ ++ char c; ++ (void)(pThis); ++ while( (c=*zScriptLeadingLine++) && (c==' '||c=='\t') ) {} ++ return (c=='.' && *zScriptLeadingLine=='.'); ++} ++ ++/* Say line group is complete if it passes muster as ready-to-go TCL. */ ++DERIVED_METHOD(int, scriptIsComplete, ScriptSupport,TclSS, ++ 2,( const char *zScript, char **pzWhyNot )){ ++ (void)(pThis); ++ (void)(pzWhyNot); ++ return Tcl_CommandComplete(zScript); ++} ++ ++/* As we rely on Tcl_CommandComplete(), no resumable scanning is done. */ ++DERIVED_METHOD(void, resetCompletionScan, ScriptSupport,TclSS, 0,()){ ++ (void)(pThis); ++} ++ ++/* Run as TCL after some jiggering with the leading dots. */ ++DERIVED_METHOD(DotCmdRC, runScript, ScriptSupport,TclSS, ++ 3,( const char *zScript, ShellExState *psx, char **pzErrMsg )){ ++ char c; ++ Tcl_Interp *interp = getInterp(); ++ (void)(pThis); ++ (void)(psx); ++ ++ if( interp==0 ) return DCR_Error; ++ while( (c=*zScript++) && (c==' '||c=='\t') ) {} ++ if( c=='.' && *zScript++=='.' ){ ++ int rc = 0; ++ int nc = strlen30(zScript); ++ /* At this point, *zScript should fall into one of these cases: */ ++ switch( *zScript ){ ++ case '.': ++ /* Three dots, assume user meant to run a dot command. */ ++ one_shot_tcl: ++ rc = Tcl_EvalEx(interp, zScript, /* needs no adjustment */ ++ nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL); ++ if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp()); ++ break; ++ case ' ': case '\t': ++ /* Two dots then whitespace, it's a TCL one-shot command. */ ++ while( (c = *zScript)!=0 && c==' ' || c=='\t' ) ++zScript, --nc; ++ if ( c!=0 ) goto one_shot_tcl; ++ /* It looks like "..", so run it that way via fall-thru. */ ++ case 0: ++ /* Two lone dots, user wants to run TCL REPL. */ ++ return runTclREPL(interp, pzErrMsg); ++ default: ++ /* Two dots then dark not dot, may be a dot command. */ ++ if( *zScript>='a' && *zScript<='z' ){ ++ --zScript, ++nc; ++ goto one_shot_tcl; ++ } ++ /* It cannot be a dot command; a user tip is apparently needed. */ ++ if( pzErrMsg ){ ++ *pzErrMsg = sqlite3_mprintf("Nothing valid begins with ..%c\n" ++ "Run .help tcl to see what is valid.\n", ++ *zScript); ++ return DCR_SayUsage; ++ } ++ } ++ return DCR_Ok|(rc!=TCL_OK); ++ } ++ return DCR_Error; /* Silent error because it should not happen. */ ++} ++ ++DERIVED_METHOD(void, destruct, DotCommand,TclCmd, 0, ()){ ++ /* Nothing to do, instance data is static. */ ++ (void)(pThis); ++} ++static DERIVED_METHOD(void, destruct, DotCommand,UnkCmd, 0, ()); ++ ++DERIVED_METHOD(void, destruct, ScriptSupport,TclSS, 0, ()){ ++ /* Nothing to do, instance data is static. */ ++ (void)(pThis); ++} ++ ++DERIVED_METHOD(const char *, name, DotCommand,TclCmd, 0,()){ ++ return "tcl"; ++} ++DERIVED_METHOD(const char *, name, DotCommand,UnkCmd, 0,()){ ++ return "unknown"; ++} ++ ++DERIVED_METHOD(const char *, help, DotCommand,TclCmd, 1,(const char *zHK)){ ++ (void)(pThis); ++ if( zHK==0 ) ++ return ++ ".tcl ?FILES? Run a TCL REPL or interpret files as TCL.\n"; ++ if( *zHK==0 ) ++ return ++ " If FILES are provided, they name files to be read in as TCL.\n" ++ " Otherwise, a read/evaluate/print loop is run until a lone \".\" is\n" ++ " entered as complete TCL input or input end-of-stream is encountered.\n" ++ "\n" ++ " The same REPL can be run with a lone \"..\". Or the \"..\" prefix\n" ++ " may be used thusly, \"..dotcmd ...\" or \".. tclcmd ...\", to run a\n" ++ " single dot command or TCL command, respectively, whereupon it will\n" ++ " be run in its respective execution environment after its arguments\n" ++ " are collected using TCL parsing rules and expanded as for TCL in\n" ++ " the TCL base namespace. In this way, arguments may be \"computed\".\n" ++ ; ++ return 0; ++} ++ ++DERIVED_METHOD(const char *, help, DotCommand,UnkCmd, 1,(const char *zHK)); ++ ++DERIVED_METHOD(DotCmdRC, argsCheck, DotCommand,TclCmd, 3, ++ (char **pzErrMsg, int nArgs, char *azArgs[])){ ++ return DCR_Ok; ++} ++DERIVED_METHOD(DotCmdRC, argsCheck, DotCommand,UnkCmd, 3, ++ (char **pzErrMsg, int nArgs, char *azArgs[])){ ++ return DCR_Ok; ++} ++ ++static void copy_complaint(char **pzErr, Tcl_Interp *pi){ ++ if( pzErr ){ ++ Tcl_Obj *po = Tcl_GetObjResult(pi); ++ *pzErr = sqlite3_mprintf("%s\n", Tcl_GetStringFromObj(po,0)); ++ } ++} ++ ++/* The .tcl/.. REPL script is one of the 3 following string literals, ++ * selected at build time for these different purposes: ++ * 1st: a simple input collection, reading only stdin, which may ++ * be (handily) used as a fallback for debugging purposes. ++ * 2nd: input collection which honors the shell's input switching ++ * and otherwise has low dependency upon shell features, which ++ * means that it has no input line editing or history recall. ++ * 3rd: an input collection which fully leverages the shell's ++ * input collection. It has higher shell dependency, and for ++ * that it gains the shell's line editing and history recall, ++ * in addition to working with the shell's input switching. ++ * It also supports recursive REPLs when return is caught. ++ */ ++#ifdef TCL_REPL_STDIN_ONLY ++# define TCL_REPL 1 ++#elif defined(TCL_REPL_LOW_DEPENDENCY) ++# define TCL_REPL 2 ++#else ++# define TCL_REPL 3 ++#endif ++ ++ ++#if TCL_REPL==1 /* a fallback for debug */ ++TCL_CSTR_LITERAL(static const char * const zREPL = ){ ++ set line {} ++ while {![eof stdin]} { ++ if {$line!=""} { ++ puts -nonewline "> " ++ } else { ++ puts -nonewline "% " ++ } ++ flush stdout ++ append line [gets stdin] ++ if {$line eq "."} break ++ if {[info complete $line]} { ++ if {[catch {uplevel #0 $line} result]} { ++ puts stderr "Error: $result" ++ } elseif {$result!=""} { ++ puts $result ++ } ++ set line {} ++ } else { ++ append line \n ++ } ++ } ++ if {$line ne "."} {puts {}} ++ read stdin 0 ++}; ++#elif TCL_REPL==2 /* minimal use of shell's read */ ++TCL_CSTR_LITERAL(static const char * const zREPL = ){ ++ namespace eval ::REPL { ++ variable line {} ++ variable at_end 0 ++ variable prompting [now_interactive] ++ } ++ while {!$::REPL::at_end} { ++ if {$::REPL::prompting} { ++ if {$::REPL::line!=""} { ++ puts -nonewline "...> " ++ } else { ++ puts -nonewline "tcl% " ++ } ++ } ++ flush stdout ++ set ::REPL::li [get_input_line] ++ if {$::REPL::li eq ""} { ++ set ::REPL::at_end 1 ++ } elseif {[string trimright $::REPL::li] eq "."} { ++ if {$::REPL::line ne ""} { ++ throw {NONE} {incomplete input at EOF} ++ } ++ set ::REPL::at_end 1 ++ } else { ++ append ::REPL::line $::REPL::li ++ if {[string trim $::REPL::line] eq ""} { ++ set ::REPL::line "" ++ continue ++ } ++ if {[info complete $::REPL::line]} { ++ set ::REPL::rc [catch {uplevel #0 $::REPL::line} ::REPL::result] ++ if {$::REPL::rc == 0} { ++ if {$::REPL::result!="" && $::REPL::prompting} { ++ puts $::REPL::result ++ } ++ } elseif {$::REPL::rc == 1} { ++ puts stderr "Error: $::REPL::result" ++ } elseif {$::REPL::rc == 2} { ++ set ::REPL::at_end 1 ++ } ++ set ::REPL::line {} ++ } ++ } ++ } ++ if {$::REPL::prompting && $::REPL::li ne ".\n"} {puts {}} ++ namespace delete ::REPL ++ read stdin 0 ++}; ++#elif TCL_REPL==3 ++/* using shell's input collection with line editing (if configured) */ ++static const char * const zREPL = "uplevel #0 sqlite_shell_REPL"; ++ ++TCL_CSTR_LITERAL(static const char * const zDefineREPL = ){ ++ proc sqlite_shell_REPL {} { ++ if {[info exists ::tcl_interactive]} { ++ set save_interactive $::tcl_interactive ++ } ++ set ::tcl_interactive [now_interactive] ++ while {1} { ++ foreach {group ready} [get_tcl_group] {} ++ set trimmed [string trim $group] ++ if {$group eq "" && !$ready} break ++ if {$trimmed eq ""} continue ++ if {!$ready && $trimmed ne ""} { ++ throw {NONE} {incomplete input at EOF} ++ } ++ if {$trimmed eq "."} break ++ set rc [catch {uplevel #0 $group} result] ++ if {$rc == 0} { ++ if {$result != "" && $::tcl_interactive} { ++ puts $result ++ } ++ } elseif {$rc == 1} { ++ puts stderr "Error: $result" ++ } elseif {$rc == 2} { ++ return -code 2 ++ } ++ } ++ if {$::tcl_interactive && $trimmed ne "."} {puts {}} ++ read stdin 0 ++ if {[info exists save_interactive]} { ++ set ::tcl_interactive $save_interactive ++ } else { unset ::tcl_interactive } ++ } ++}; ++#else ++ "throw {NONE} {not built for REPL}\n" ++#endif ++ ++/* Enter the preferred REPL */ ++static DotCmdRC runTclREPL(Tcl_Interp *interp, char **pzErrMsg){ ++ int rc = Tcl_Eval(interp, zREPL); ++ clearerr(stdin); /* Cure issue where stdin gets stuck after keyboard EOF. */ ++ if( rc!=TCL_OK ){ ++ copy_complaint(pzErrMsg, interp); ++ return DCR_Error; ++ } ++ return DCR_Ok; ++} ++ ++DERIVED_METHOD(DotCmdRC, execute, DotCommand,TclCmd, 4, ++ (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){ ++ TclCmd *ptc = (TclCmd *)pThis; ++ if( nArgs>1 ){ ++ /* Read named files into the interpreter. */ ++ int rc = TCL_OK; ++ int aix; ++ for( aix=0; aix<(nArgs-1) && rc==TCL_OK; ++aix ){ ++ rc = Tcl_EvalFile(getInterp(), azArgs[aix+1]); ++ } ++ if( rc!=TCL_OK ){ ++ copy_complaint(pzErrMsg, getInterp()); ++ return DCR_Error; ++ } ++ return DCR_Ok; ++ }else{ ++ /* Enter a REPL */ ++ return runTclREPL(getInterp(), pzErrMsg); ++ } ++} ++ ++DERIVED_METHOD(DotCmdRC, execute, DotCommand,UnkCmd, 4, ++ (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){ ++ Tcl_Interp *interp = getInterp(); ++ Tcl_Obj **ppo; ++ char zName[50]; ++ int ia, rc; ++ ++ if( interp==0 || nArgs==0 ) return DCR_Unknown; ++ ++ sqlite3_snprintf(sizeof(zName), zName, ".%s", azArgs[0]); ++ if( !Tcl_FindCommand(interp, zName, 0, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) ){ ++ if( !SHX_HELPER(nowInteractive)(psx) ){ ++ *pzErrMsg = sqlite3_mprintf("Command %s not found.\n", zName); ++ return DCR_Unknown; ++ }else{ ++ fprintf(stderr, "The %s command does not yet exist.\n", zName); ++ oprintf(psx, "Run .help to see existent dot commands," ++ " or create %s as a TCL proc.\n", zName); ++ return DCR_CmdErred; ++ } ++ } ++ ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*)); ++ if( ppo==0 ) return TCL_ERROR; ++ for( ia=0; ia ". ++ * This routine returns a 2 element list consisting of: ++ * the collected input lines, joined with "\n", as a string ++ * and ++ * the line group status, as an integer. ++ * The status is either 0, meaning input EOF was encountered, ++ * or 1, meaning the input is a complete TCL line group. ++ * There are only these return combinations: ++ * { Empty 0 } => no input obtained and no more to be had ++ * { Other 0 } => input collected, but is invalid TCL ++ * { Other 1 } => input collected, may be valid TCL ++ * By design, this combination is never returned: ++ * { Empty 1 } => no input collected but valid TCL ++ */ ++static int getTclGroup(void *pvSS, Tcl_Interp *interp, ++ int objc, Tcl_Obj *const objv[]){ - if( objc==1 ){ - static Prompts cueTcl = { "tcl% ", " > " }; +++ if( objc>=1 && objc<=3 ){ +++ Prompts cueTcl = { "tcl% ", " > " }; ++ ShellExState *psx = (ShellExState *)pvSS; ++ struct InSource *pis = SHX_HELPER(currentInputSource)(psx); ++ int isComplete = 0; ++ char *zIn = 0; ++ int isContinuation = 0; +++ +++ switch( objc ){ +++ case 3: cueTcl.zContinue = (const char*)Tcl_GetStringFromObj(objv[2],0); +++ case 2: cueTcl.zMain = (const char*)Tcl_GetStringFromObj(objv[1],0); +++ default: break; +++ } ++ do { ++ zIn = SHX_HELPER(oneInputLine)(pis, zIn, isContinuation, &cueTcl); ++ if( isContinuation ){ ++ if( zIn ){ ++ Tcl_AppendResult(interp, "\n", zIn, (char*)0); ++ isComplete = Tcl_CommandComplete(Tcl_GetStringResult(interp)); ++ } ++ }else if( zIn ){ ++ isComplete = Tcl_CommandComplete(zIn); ++ Tcl_SetResult(interp, zIn, TCL_VOLATILE); ++ } ++ isContinuation = 1; ++ } while( zIn && !isComplete ); ++ if( zIn ) SHX_HELPER(freeInputLine)(zIn); ++ { ++ Tcl_Obj *const objvv[] = { ++ Tcl_NewStringObj(Tcl_GetStringResult(interp) , -1), ++ Tcl_NewIntObj(isComplete) ++ }; /* These unowned objects go directly into result, becoming owned. */ ++ Tcl_ResetResult(interp); ++ Tcl_SetObjResult(interp, Tcl_NewListObj(2, objvv)); ++ } ++ return TCL_OK; ++ }else{ ++ Tcl_SetResult(interp, "too many arguments", TCL_STATIC); ++ return TCL_ERROR; ++ } ++} ++#endif ++ ++/* C implementation of TCL proc, now_interactive */ ++static int nowInteractive(void *pvSS, Tcl_Interp *interp, ++ int nArgs, const char *azArgs[]){ ++ if( nArgs==1 ){ ++ ShellExState *psx = (ShellExState *)pvSS; ++ struct InSource *pis = SHX_HELPER(currentInputSource)(psx); ++ static const char * zAns[2] = { "0","1" }; ++ int iiix = (SHX_HELPER(nowInteractive)(psx) != 0); ++ Tcl_SetResult(interp, (char *)zAns[iiix], TCL_STATIC); ++ return TCL_OK; ++ }else{ ++ Tcl_SetResult(interp, "too many arguments", TCL_STATIC); ++ return TCL_ERROR; ++ } ++} ++ ++#ifdef SHELL_ENABLE_TK ++static int numEventLoops = 0; ++static int inOuterLoop = 0; ++ ++static int exitThisTkGUI(void *pvSS, Tcl_Interp *interp, ++ int nArgs, const char *azArgs[]){ ++ if( numEventLoops==0 && !inOuterLoop ){ ++ int ec = 0; ++ if( nArgs>=2 ){ ++ if( azArgs[1] && sscanf(azArgs[1], "%d", &ec)!=1 ){ ++ ec = 1; ++ fprintf(stderr, "Exit: %d\n", ec); ++ }else{ ++ const char *zA = (azArgs[1])? azArgs[1] : "null"; ++ fprintf(stderr, "Exit: \"%s\"\n", azArgs[1]); ++ } ++ }else{ ++ fprintf(stderr, "Exit without argument\n"); ++ } ++ fprintf(stderr, "Exit: \"%s\"\n", azArgs[1]); ++ // exit(ec); ++ } ++ --numEventLoops; ++ return TCL_BREAK; ++} ++ ++static void runTclEventLoop(void){ ++ int inOuter = inOuterLoop; ++ int nmw = Tk_GetNumMainWindows(); ++ /* This runs without looking at stdin. So it cannot be a REPL, yet. ++ * Unless user has created something for it to do, it does nothing. */ ++ /* Tk_MapWindow(Tk_MainWindow(interpKeep.pInterp)); */ ++ ++numEventLoops; ++ inOuterLoop = 1; ++ while( nmw > 0 ) { ++ Tcl_DoOneEvent(0); ++ nmw = Tk_GetNumMainWindows(); ++ /* if( nmw==1 ){ */ ++ /* Tk_UnmapWindow(Tk_MainWindow(interpKeep.pInterp)); */ ++ /* nmw = Tk_GetNumMainWindows(); */ ++ /* break; */ ++ /* } */ ++ } ++ if( nmw==0 ){ ++ fprintf(stderr, ++ "Tk application and its root window destroyed. Restarting Tk.\n"); ++ Tk_Init(interpKeep.pInterp); ++ } ++ --numEventLoops; ++ inOuterLoop = inOuter; ++} ++ ++static int runTkGUI(void *pvSS, Tcl_Interp *interp, ++ int nArgs, const char *azArgs[]){ ++ (void)(pvSS); /* ShellExState *psx = (ShellExState *)pvSS; */ ++ Tcl_SetMainLoop(runTclEventLoop); ++ runTclEventLoop(); ++ return TCL_OK; ++} ++#endif /* defined(SHELL_ENABLE_TK) */ ++ ++#define UNKNOWN_RENAME "_original_unknown" ++ ++/* C implementation of TCL ::register_adhoc_command name ?help? */ ++static int registerAdHocCommand(/* ShellExState */ void *pv, ++ Tcl_Interp *interp, ++ int nArgs, const char *azArgs[]){ ++ ShellExState *psx = (ShellExState*)pv; ++ if( nArgs>3 ){ ++ Tcl_SetResult(interp, "too many arguments", TCL_STATIC); ++ }else if( nArgs<2 ){ ++ Tcl_SetResult(interp, "too few arguments", TCL_STATIC); ++ }else{ ++ const char *zHT = (nArgs==3)? azArgs[2] : 0; ++ Tcl_ResetResult(interp); ++ SHX_API(registerAdHocCommand)(psx, sqlite3_tclshext_init, azArgs[1], zHT); ++ return TCL_OK; ++ } ++ return TCL_ERROR; ++} ++ ++/* C implementation of TCL unknown to (maybe) delegate to dot commands */ ++static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp, ++ int nArgs, const char *azArgs[]){ ++ const char *name = (nArgs>1 && *azArgs[1]=='.')? azArgs[1]+1 : 0; ++ ShellExState *psx = (ShellExState *)pvSS; ++ DotCommand *pdc = 0; ++ int nFound = 0; ++ int ia, rc; ++ ++ if( name ) pdc = SHX_HELPER(findDotCommand)(name, psx, &nFound); ++ if( pdc==(DotCommand*)&tclcmd && nArgs==2 ){ ++ /* Will not do a nested REPL, just silently semi-fake it. */ ++ return TCL_OK; ++ } ++ if( pdc && nFound==1 ){ ++ /* Run the dot command and interpret its returns. */ ++ DotCmdRC drc = SHX_HELPER(runDotCommand)(pdc, (char **)azArgs+1, ++ nArgs-1, psx); ++ if( drc==DCR_Ok ) return TCL_OK; ++ else if( drc==DCR_Return ){ ++ return TCL_RETURN; ++ }else{ ++ Tcl_AppendResult(interp, "Execution of .", name, " failed.", 0); ++ return TCL_ERROR; ++ } ++ }else{ ++ /* Defer to the TCL-default unknown command, or fail here. */ ++ if( 0!=Tcl_FindCommand(interp, UNKNOWN_RENAME, 0, TCL_GLOBAL_ONLY) ){ ++ Tcl_Obj **ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*)); ++ if( ppo==0 ) return TCL_ERROR; ++ ppo[0] = Tcl_NewStringObj(UNKNOWN_RENAME, -1); ++ Tcl_IncrRefCount(ppo[0]); ++ for( ia=1; iappSdb ++ = (SqliteDb**)Tcl_Realloc((char*)pudb->ppSdb, (pudb->numSdb+1)*sizeof(p)); ++ memset(pudb->ppSdb + pudb->numSdb, 0, sizeof(SqliteDb*)); ++ p = (SqliteDb*)Tcl_Alloc(sizeof(SqliteDb)); ++ memset(p, 0, sizeof(SqliteDb)); ++ pudb->ppSdb[pudb->numSdb] = p; ++ p->db = udb; ++ p->interp = pudb->interp; ++ p->maxStmt = NUM_PREPARED_STMTS; ++ p->openFlags = SQLITE_OPEN_URI; ++ p->nRef = 1; ++ return pudb->numSdb++; ++} ++ ++/* Remove a DB from the list */ ++static void udbRemove(UserDb *pudb, int ix){ ++ SqliteDb *pdb; ++ assert(ix>=0 && ixnumSdb); ++ /* The code below is highly dependent upon implementation details of ++ * tclsqlite.c , and may become incorrect if that code changes. This ++ * is an accepted downside of reusing vast portions of that code. ++ * The minutiae in these comments is to explain the dependencies so ++ * that adjustments might be easily made when proven necessary. */ ++ pdb = pudb->ppSdb[ix]; ++#ifndef SQLITE_OMIT_INCRBLOB ++ /* This is a preemptive action, which is normally done by the ++ * delDatabaseRef() routine, which needs a non-zero db pointer ++ * to reach Tcl_UnregisterChannel()'s implementation. We do it ++ * now because, to avoid closing that db, that pointer will be ++ * set to 0 when delDatabaseRef() runs. */ ++ closeIncrblobChannels(pdb); ++ /* Prevent closeIncrblobChannels() from trying to free anything. */ ++ pdb->pIncrblob = 0; ++#endif ++ /* This appears to not be necessary; it is defensive in case the ++ * flushStmtCache() or dbFreeStmt() code begins to use pdb->db . ++ * We rely on its behavior whereby, once flushed, the cache is ++ * made to appear empty in the SqliteDb struct. */ ++ flushStmtCache(pdb); ++ /* This next intervention prevents delDatabaseRef() from closing ++ * the .db ; this relies on sqlite3_close(0) being a NOP. If the ++ * SqliteDb takedown code changes, this may lead to an address ++ * fault. For that reason, the *.in which produces this source ++ * should be tested by excercising the TCL udb command well. */ ++ pdb->db = 0; ++ assert(pdb->nRef==1); ++ /* Use the "stock" delete for sqlite3-generated objects. */ ++ delDatabaseRef(pdb); ++ /* At this point, pdb has been Tcl_Free()'ed. Forget it. */ ++ --pudb->numSdb; ++ { ++ int nshift = pudb->numSdb-ix; ++ if( nshift>0 ){ ++ memmove(pudb->ppSdb+ix, pudb->ppSdb+ix+1, nshift*sizeof(pdb)); ++ } ++ } ++ /* Adjust index to currently visible DB. */ ++ if( ix==pudb->ixuSdb ) pudb->ixuSdb = -1; ++ else if( ixixuSdb ) --pudb->ixuSdb; ++} ++ ++static struct UserDb *udbCreate(Tcl_Interp *interp, ShellExState *psx); ++ ++/* Cleanup the UserDb singleton. Should only be done at shutdown. ++ * This routine is idempotent, and may be called redundantly. ++ */ ++static void udbCleanup(UserDb *pudb){ ++ /* If this is called too early, when *pudb is still associated with ++ * active (not yet closed) SqliteDb objects, those will simply be ++ * orphaned and leaked. But this assert may make the error evident. */ ++ if( pudb==0 ) pudb = udbCreate(0, 0); ++ assert(pudb->numSdb==0); ++ if( pudb->ppSdb ) Tcl_Free((char*)pudb->ppSdb); ++ memset(pudb, 0, sizeof(UserDb)); ++ pudb->ixuSdb = -1; ++} ++ ++/* Hunt for given db in UserDb's list. Return its index if found, else -1. */ ++static int udbIndexOfDb(UserDb *pudb, sqlite3 *psdb){ ++ int ix = 0; ++ while( ix < pudb->numSdb ){ ++ if( psdb==pudb->ppSdb[ix]->db ) return ix; ++ else ++ix; ++ } ++ return -1; ++} ++ ++/* The event handler used to keep udb command's wrapped DB in sync with ++ * changes to the ShellExState .dbUser member. This task is complicated ++ * by effects of these dot commands: .open ; .connection ; and .quit, ++ * .exit or various other shell exit causes. The intention is to always ++ * have an orderly and leak-free shutdown (excepting kill/OOM aborts.) ++ */ ++static int udbEventHandle(void *pv, NoticeKind nk, void *pvSubject, ++ ShellExState *psx){ ++ UserDb *pudb = (UserDb*)pv; ++ if( nk==NK_ShutdownImminent ){ ++ udbCleanup(pudb); ++ }else if( nk==NK_Unsubscribe ){ ++ assert(pudb==0 || pudb->numSdb==0); ++ }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing ++ || nk==NK_DbAboutToClose || nk==NK_ExtensionUnload ){ ++ sqlite3 *dbSubject = (sqlite3*)pvSubject; ++ int ix = udbIndexOfDb(pudb, dbSubject); ++ switch( nk ){ ++ case NK_DbUserAppeared: ++ if( ix>=0 ) pudb->ixuSdb = ix; ++ else pudb->ixuSdb = udbAdd(pudb, dbSubject); ++ break; ++ case NK_DbUserVanishing: ++ if( ix>=0 ) pudb->ixuSdb = -1; ++ break; ++ case NK_ExtensionUnload: ++ SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init, 0, ++ NK_Unsubscribe, udbEventHandle); ++ /* fall thru */ ++ case NK_DbAboutToClose: ++ if( ix>=0 ) udbRemove(pudb, ix); ++ break; ++ ++ /* Only above need handling. (So, clever tools, be quiet!) */ ++ default: break; ++ } ++ } ++ return 0; ++} ++ ++/* Create the UserDb object supporting TCL "udb" command operations. ++ * It's not wholly created because it is a singleton. Any subsequent ++ * creation is ignored; instead, the singleton is returned. This ++ * object is made to release resources only upon shutdown. If a TCL ++ * user removes the udb command, this avoids problems arising from ++ * this object holding references to databases that may still be in ++ * use, either as the active .dbUser or as a blob streaming store. */ ++static struct UserDb *udbCreate(Tcl_Interp *interp, ShellExState *psx){ ++ static UserDb *rv = 0; ++ static UserDb udb = { 0 }; ++ if( interp==0 || psx==0 ) return &udb; ++ if( rv==0 ){ ++ sqlite3 *sdbS = psx->dbShell; ++ sqlite3 *sdbU = psx->dbUser; ++ rv = &udb; ++ rv->interp = interp; ++ rv->psx = psx; ++ rv->ppSdb = (SqliteDb**)Tcl_Alloc(6*sizeof(SqliteDb*)); ++ memset(rv->ppSdb, 0, 6*sizeof(SqliteDb*)); ++ assert(sdbS!=0); ++ udbAdd(rv, sdbS); ++ if( sdbU!=0 ){ ++ rv->ixuSdb = udbAdd(rv, sdbU); ++ } else rv->ixuSdb = -1; ++ rv->nRef = 1; ++ /* Arrange that this object tracks lifetimes and visibility of the ++ * ShellExState .dbUser member values which udb purports to wrap, ++ * and that shdb ceases wrapping the .dbShell member at shutdown. ++ * This subscription eventually leads to a udbCleanup() call. */ ++ SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init, ++ rv, NK_CountOf, udbEventHandle); ++ } ++ return rv; ++} ++ ++static const char *azDbNames[] = { "shdb", "udb", 0 }; ++static const int numDbNames = 2; ++ ++/* C implementation behind added TCL udb command */ ++static int UserDbObjCmd(void *cd, Tcl_Interp *interp, ++ int objc, Tcl_Obj * const * objv){ ++ UserDb *pudb = (UserDb*)cd; ++ static const char *azDoHere[] = { "close", 0 }; ++ enum DbDoWhat { DDW_Close }; ++ int doWhat = -1; ++ int whichDb = -1; ++ const char *zMoan; ++ ++ if( Tcl_GetIndexFromObj(interp, objv[0], azDbNames, ++ "shell DB command", 0, &whichDb)){ ++ zMoan = " is not a wrapped DB.\n"; ++ goto complain_fail; ++ } ++ if( whichDb>0 ) whichDb = pudb->ixuSdb; ++ /* Delegate all subcommands except above to the now-visible SqliteDb. */ ++ if( objc>=2 ++ && TCL_OK==Tcl_GetIndexFromObj(0, objv[1], azDoHere, "", 0, &doWhat)){ ++ switch( doWhat ){ ++ case DDW_Close: ++ zMoan = " close disallowed. It is a wrapped DB belonging to the shell."; ++ goto complain_fail; ++ default: ; /* Fine */ ++ } ++ } ++ if( pudb->numSdb==0 || whichDb<0 ){ ++ zMoan = " references no DB yet.\n"; ++ goto complain_fail; ++ } ++ return DbObjCmd(pudb->ppSdb[whichDb], interp, objc, objv); ++ ++ complain_fail: ++ Tcl_AppendResult(interp, ++ Tcl_GetStringFromObj(objv[0], (int*)0), zMoan, (char*)0); ++ return TCL_ERROR; ++} ++ ++/* Get the udb command subsystem initialized and create "udb" TCL command. */ ++static int userDbInit(Tcl_Interp *interp, ShellExState *psx){ ++ UserDb *pudb = udbCreate(interp, psx); ++ int nCreate = 0; ++ int ic; ++ for( ic=0; icnRef; ++ return TCL_OK; ++ } ++ return TCL_ERROR; ++} ++ ++/* ++** Extension load function. ++*/ ++#ifdef _WIN32 ++__declspec(dllexport) ++#endif ++int sqlite3_tclshext_init( ++ sqlite3 *db, ++ char **pzErrMsg, ++ const sqlite3_api_routines *pApi ++){ ++ static const char * const azLoadFailures[] = { ++ "Extension load failed unexpectedly.", ++ "No ShellExtensionLink.\n Use '.load tclshext -shext' to load.", ++ "Outdated shell host extension API.\n Update the shell.", ++ "Outdated shell host helper API.\n Use a newer shell.", ++ }; ++ int iLoadStatus; ++ SQLITE_EXTENSION_INIT2(pApi); ++ SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db); ++ ++ SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink); ++ iLoadStatus = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 6, 10); ++ if( iLoadStatus!=EXLD_Ok ){ ++ if( iLoadStatus>=sizeof(azLoadFailures)/sizeof(azLoadFailures[0]) ){ ++ iLoadStatus = 0; ++ } ++ *pzErrMsg = sqlite3_mprintf("%s\n", azLoadFailures[iLoadStatus]); ++ return SQLITE_ERROR; ++ }else{ ++ ShellExState *psx = pShExtLink->pSXS; ++ Tcl_Obj *targv = Tcl_NewListObj(0, NULL); ++ const char *zAppName = "tclshext"; ++ int tnarg = 0; ++#ifdef SHELL_ENABLE_TK ++ int ldTk = 0; ++#endif ++ int rc = 0; ++ ++ if( pShExtLink->nLoadArgs>0 ){ ++ int ila; ++ for( ila=0; ilanLoadArgs; ++ila ){ ++ const char *zA = pShExtLink->azLoadArgs[ila]; ++ if( strcmp(zA,"-tk")==0 ){ ++#ifdef SHELL_ENABLE_TK ++ ldTk = 1; ++#else ++ *pzErrMsg = sqlite3_mprintf("Option -tk not supported by this " ++ "tclshext extension as built.\n"); ++ return SQLITE_ERROR; ++#endif ++ }else{ ++ /* Collect args not affecting init into the argv list. */ ++ Tcl_ListObjAppendElement(NULL, targv, Tcl_NewStringObj(zA, -1)); ++ ++tnarg; ++ } ++ } ++ } ++ rc = SHX_API(registerDotCommand)(psx, sqlite3_tclshext_init, ++ (DotCommand *)&unkcmd); ++ rc = SHX_API(registerDotCommand)(psx, sqlite3_tclshext_init, ++ (DotCommand *)&tclcmd); ++ if( rc==SQLITE_OK && ++ (rc = Tcl_BringUp( ++#ifdef SHELL_ENABLE_TK ++ &ldTk, ++#endif ++ pzErrMsg))==SQLITE_OK ++ ){ ++ Tcl_Interp *interp = getInterp(); ++ if( TCL_OK==userDbInit(interp, psx) ){ ++ UserDb *pudb = udbCreate(interp, psx); ++ pShExtLink->extensionDestruct = (void (*)(void*))udbCleanup; ++ pShExtLink->pvExtensionObject = pudb; ++ } ++ SHX_API(registerScripting)(psx, sqlite3_tclshext_init, ++ (ScriptSupport *)&tclss); ++#if TCL_REPL==1 || TCL_REPL==2 ++ Tcl_CreateCommand(interp, "get_input_line", getInputLine, psx, 0); ++#endif ++#if TCL_REPL==3 ++ Tcl_CreateObjCommand(interp, "get_tcl_group", getTclGroup, psx, 0); ++ Tcl_Eval(interp, zDefineREPL); ++#endif ++ Tcl_CreateCommand(interp, "now_interactive", nowInteractive, psx, 0); ++ /* Rename unknown so that calls to it can be intercepted. */ ++ Tcl_Eval(interp, "rename unknown "UNKNOWN_RENAME); ++ Tcl_CreateCommand(interp, "unknown", unknownDotDelegate, psx, 0); ++ /* Add a command to facilitate ad-hoc TCL dot commands showing up in ++ * the .help output with help text as specified by calling this: */ ++ Tcl_CreateCommand(interp, "register_adhoc_command", ++ registerAdHocCommand, (void*)psx, 0); ++ ++ /* Define this proc so that ".." either gets to the TCL REPL loop ++ * or does nothing (if already in it), as a user convenience. */ ++ Tcl_Eval(interp, "proc .. {} {}"); ++#ifdef SHELL_ENABLE_TK ++ if( ldTk ){ ++ /* Create a proc to launch GUI programs, in faint mimicry of wish. ++ * ++ * Its first argument, pgmName is the name to be given to the GUI ++ * program that may be launched, used for error reporting and to ++ * become the value of the ::argv0 that it sees. ++ * ++ * Its second argument, pgmSetup, will be executed as a list (of ++ * a command and its arguments) to setup the GUI program. It may ++ * do anything necessary to prepare for the GUI program to be ++ * run by running a Tk event loop. It may be an empty list, in ++ * which case pgmName must name a list serving the same purpose. ++ * ++ * Subsequent arguments to this proc will be passed to the GUI ++ * program in the ::argv/::argc variable pair it sees. ++ * ++ * If only two empty arguments are provided to this proc, (whether ++ * as defaulted or explictly passed), the GUI event loop will be ++ * run with whatever conditions have been setup prior to the call. ++ * (This is perfectly legitimate; this "gui" proc provides a way ++ * to package GUI preparation and separate it from GUI run.) ++ * ++ * It is the responsibility of whatever setup code is run, if any, ++ * to leave Tk objects and variables set so that when a GUI event ++ * loop is run, some useful GUI program runs and can terminate. ++ * ++ * Before running the setup code, a variable, ::isHost, is set ++ * true to possibly inform the setup code that it should avoid ++ * exit and exec calls. Setup code which is designed for either ++ * hosted or standalone use, when run with $::isHost!=0, may opt ++ * to leave variables ::exitCode and ::resultValue set which are ++ * taken to indicate pseudo-exit status and a string result to ++ * be used for error reporting or possibly other purposes. ++ * ++ * If the above responsibilities cannot be met, setup code should ++ * fail in some way so that its execution produces a TCL error or ++ * follows the ::exitCode and ::resultValue convention. Otherwise, ++ * annoying sqlite3 shell hangs or abrupt exits may result. ++ */ ++ TCL_CSTR_LITERAL(const char * const zGui =){ ++ proc gui {{pgmName ""} {pgmSetup {}} args} { ++ unset -nocomplain ::exitCode ++ set ::tcl_interactive [now_interactive] ++ set saveArgs [list $::argv0 $::argc $::argv] ++ if {"$pgmName" ne ""} { ++ set ::argv0 $pgmName ++ } else {set ::argv0 "?"} ++ set ::argv $args ++ set ::argc [llength $args] ++ if {[llength $pgmSetup] == 0 && $pgmName ne ""} { ++ if { [catch {set ::programSetup [subst "\$$pgmName"]}] } { ++ foreach {::argv0 ::argc ::argv} $saveArgs {} ++ return -code 1 "Error: pgmSetup empty, and pgmName does not\ ++ name a list that might be\n executed in\ ++ its place. Consult tclshext doc on using the gui command." ++ } ++ } elseif {[llength $pgmSetup] == 0 && $pgmName eq ""} { ++ unset -nocomplain ::programSetup ++ } else { ++ set ::programSetup $pgmSetup ++ } ++ if {[info exists ::programSetup] && [llength $::programSetup] > 0} { ++ set rc [catch {uplevel #0 { ++ {*}$::programSetup ++ }} result options] ++ if {$rc==1} { ++ puts stderr "gui setup failed: $result" ++ puts stderr [dict get $options -errorinfo] ++ } elseif {[info exists ::exitCode] && $::exitCode!=0} { ++ puts stderr "gui setup failed: $::resultValue" ++ } else { run_gui_event_loop } ++ } else { ++ run_gui_event_loop ++ } ++ foreach {::argv0 ::argc ::argv} $saveArgs {} ++ } ++ }; ++ /* Create a command which nearly emuluates Tk_MainLoop(). It runs a ++ * GUI event loop, so does not return until either: all Tk top level ++ * windows are destroyed, which causes and error return, or the Tk ++ * app has called the replacement exit routine described next. */ ++ Tcl_CreateCommand(interp, "run_gui_event_loop", runTkGUI, psx, 0); ++ Tcl_Eval(interp, "rename exit process_exit"); ++ Tcl_CreateCommand(interp, "exit", exitThisTkGUI, psx, 0); ++ Tcl_Eval(interp, zGui); ++ Tcl_SetMainLoop(runTclEventLoop); ++ zAppName = "tclshext_tk"; ++ } ++#endif /* ..TK */ ++ Tcl_SetVar2Ex(interp, "::argv0", NULL, ++ Tcl_NewStringObj(zAppName,-1), TCL_GLOBAL_ONLY); ++ Tcl_SetVar2Ex(interp, "::argc", NULL, ++ Tcl_NewIntObj(tnarg), TCL_GLOBAL_ONLY); ++ Tcl_SetVar2Ex(interp, "::argv", NULL, targv, TCL_GLOBAL_ONLY); ++ Tcl_SetVar2Ex(interp, "::tcl_interactive", NULL, ++ Tcl_NewIntObj(SHX_HELPER(nowInteractive)(psx)), ++ TCL_GLOBAL_ONLY); ++ Tcl_SetVar2Ex(interp, "::isHosted", NULL, ++ Tcl_NewIntObj(1), TCL_GLOBAL_ONLY); ++ pShExtLink->eid = sqlite3_tclshext_init; ++ } ++ if( rc==SQLITE_OK ){ ++ pShExtLink->extensionDestruct = Tcl_TakeDown; ++ pShExtLink->pvExtensionObject = &interpKeep; ++ }else{ ++ Tcl_TakeDown(&interpKeep); ++ } ++ return rc; ++ } ++} diff --cc manifest index 89552890ee,3d0377b14d,897ab5ae64..69a2ecc342 --- a/manifest +++ b/manifest @@@@ -1,13 -1,13 -1,13 +1,13 @@@@ - C Sync\sw/trunk\s(for\sCLI\s-cachetrace) - D 2023-06-21T14:48:41.994 - C Fix\sharmless\scompiler\swarnings\sabout\sunused\sfunction\sarguments. - D 2023-06-29T17:48:32.337 -C Further\srefine\sthe\sdtostr()\stesting\sfunction\sin\sthe\sCLI\sso\sthat\sit\stakes\san\noptional\ssecond\sparameter\swhich\sis\sthe\snumber\sof\ssignificant\sdigits\sto\sdisplay. -D 2023-06-29T17:26:21.732 +++C Sync\sw/trunk +++D 2023-06-29T17:58:51.696 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 --F Makefile.in 0f4cb3955aaff8a40ec3857ba1784bd98b69802e51eff979f874b65713b627b2 ++F Makefile.in dcc5b1491fff94b9a8f16f00714ed6a6c886d853970d0cd4a4066296063e96fd x F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241 --F Makefile.msc 7248d860f71ab164b4cec3c415e6cc1bd9fee860c370d65bd8bb49e9572521e2 --F README.md c1c4218efcc4071a6e26db2b517fdbc1035696a29b370edd655faddbef02b224 --F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa ++F Makefile.msc 7b31410d0f91251d4268b1abcab7b1a26b52d37193dd7ead87b2acb302ecf6b6 x - F README.md 8ff80689b9cb9f6e9b842edf31a3358ff53bc538c351799e03dd3e5455e637e5 x +++F README.md c1c4218efcc4071a6e26db2b517fdbc1035696a29b370edd655faddbef02b224 x ++F VERSION c6366dc72582d3144ce87b013cc35fe48d62f6d07d5be0c9716ea33c862144aa x F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F art/sqlite370.eps aa97a671332b432a54e1d74ff5e8775be34200c2 F art/sqlite370.ico af56c1d00fee7cd4753e8631ed60703ed0fc6e90 @@@@ -290,10 -287,10 -287,10 +290,10 @@@@ F ext/misc/completion.c 6dafd7f4348eecc F ext/misc/compress.c 3354c77a7c8e86e07d849916000cdac451ed96500bfb5bd83b20eb61eee012c9 F ext/misc/csv.c ca8d6dafc5469639de81937cb66ae2e6b358542aba94c4f791910d355a8e7f73 F ext/misc/dbdump.c b8592f6f2da292c62991a13864a60d6c573c47a9cc58362131b9e6a64f823e01 - F ext/misc/decimal.c 57d85fa20a5a74d3b0dfc78ab7934ae6c9f5aa8eed915faa2b5246bec87ddc6d -F ext/misc/decimal.c 17303ac06f50d750fdb635c8be1f8eb820fa95896fd08c08af332b62fe8bcfce + +F ext/misc/decimal.c 24ccb63e9af6ed7de2e8e3b300061ad91169a44cc0c35dab92b7d2e1e7574f28 F ext/misc/eval.c 04bc9aada78c888394204b4ed996ab834b99726fb59603b0ee3ed6e049755dc1 F ext/misc/explain.c 0086fab288d4352ea638cf40ac382aad3b0dc5e845a1ea829a694c015fd970fe --F ext/misc/fileio.c 4e7f7cd30de8df4820c552f14af3c9ca451c5ffe1f2e7bef34d598a12ebfb720 ++F ext/misc/fileio.c 37f19acaf22562bae05f530c81c7b24b2c5c091503b115b54ec127958fb5c8bb F ext/misc/fossildelta.c 1240b2d3e52eab1d50c160c7fe1902a9bd210e052dc209200a750bbf885402d5 F ext/misc/fuzzer.c eae560134f66333e9e1ca4c8ffea75df42056e2ce8456734565dbe1c2a92bf3d F ext/misc/ieee754.c 984d51fe23e956484ec1049df6f5257002e3ab338cabceb39761c2e80ad10bf4 @@@@ -320,7 -317,6 -317,6 +320,7 @@@@ F ext/misc/showauth.c 732578f0fe4ce42d5 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f F ext/misc/sqlar.c 53e7d48f68d699a24f1a92e68e71eca8b3a9ff991fe9588c2a05bde103c6e7b7 F ext/misc/stmt.c bc30d60d55e70d0133f10ac6103fe9336543f673740b73946f98758a2bb16dd7 - F ext/misc/tclshext.c.in 732f33460956d1416f5ed7a991cfd367d3767f35ad20cb68343a54e50b32ba74 x +++F ext/misc/tclshext.c.in eaf544f41cedc1a824e369ab782e3bf3e36f227c9da10d8bdf7187937f80e10a x F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b @@@@ -514,10 -510,10 -510,10 +514,10 @@@@ F ext/wasm/api/sqlite3-worker1.c-pp.js F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.js 0dad6a02ad796f1003d3b7048947d275c4d6277f63767b8e685c27df8fdac93e F ext/wasm/c-pp.c 6d80d8569d85713effe8b0818a3cf51dc779e3f0bf8dc88771b8998552ee25b4 --F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 ++F ext/wasm/common/SqliteTestUtil.js 7adaeffef757d8708418dc9190f72df22367b531831775804b31598b44f6aa51 x F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15 F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f - F ext/wasm/common/whwasmutil.js 749a1f81f85835e9a384e9706f2a955a7158f2b8cc9da33f41105cac7775a305 + F ext/wasm/common/whwasmutil.js 03407d7b61b817fd135c82401987e56688a45ee4d6b9c0eced160c0000d6e4c2 F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 @@@@ -573,16 -569,16 -569,16 +573,16 @@@@ F sqlite.pc.in 42b7bf0d02e08b9e77734a47 F sqlite3.1 fc7ad8990fc8409983309bb80de8c811a7506786 F sqlite3.pc.in 48fed132e7cb71ab676105d2a4dc77127d8c1f3a F sqlite_cfg.h.in baf2e409c63d4e7a765e17769b6ff17c5a82bbd9cbf1e284fd2e4cefaff3fcf2 --F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5 --F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f ++F src/alter.c 3ff8c2fca0c0636d43459154bb40d79c882df1b34df77f89c4ec47ab2e2389f5 x ++F src/analyze.c d4cc28738c29e009640ec20ebb6936ba6fcefff0d11aa93398d9bb9a5ead6c1f x F src/attach.c cc9d00d30da916ff656038211410ccf04ed784b7564639b9b61d1839ed69fd39 --F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 ++F src/auth.c 19b7ccacae3dfba23fc6f1d0af68134fa216e9040e53b0681b4715445ea030b4 x F src/backup.c 5c97e8023aab1ce14a42387eb3ae00ba5a0644569e3476f38661fa6f824c3523 --F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 --F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 --F src/btree.c c0c93b6cb4dc133b528c1290bb4ad0f2414452f9a5758ff2b106af718874f39e ++F src/bitvec.c 9eac5f42c11914d5ef00a75605bb205e934f435c579687f985f1f8b0995c8645 x ++F src/btmutex.c 79a43670447eacc651519a429f6ece9fd638563cf95b469d6891185ddae2b522 x - F src/btree.c 481666a3dd26b1cb16a9e9baaa3f6b17cab52a1d7a5836e5dcf5b45b85d4a51d x +++F src/btree.c c0c93b6cb4dc133b528c1290bb4ad0f2414452f9a5758ff2b106af718874f39e x F src/btree.h aa354b9bad4120af71e214666b35132712b8f2ec11869cb2315c52c81fad45cc --F src/btreeInt.h 3b4eff7155c0cea6971dc51f62e3529934a15a6640ec607dd42a767e379cb3a9 ++F src/btreeInt.h 3b4eff7155c0cea6971dc51f62e3529934a15a6640ec607dd42a767e379cb3a9 x F src/build.c a8ae3b32d9aa9bbd2c0e97d7c0dd80def9fbca408425de1608f57ee6f47f45f4 F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e @@@@ -591,23 -587,23 -587,23 +591,23 @@@@ F src/date.c f73f203b3877cef866c60ab402 F src/dbpage.c f3eea5f7ec47e09ee7da40f42b25092ecbe961fc59566b8e5f705f34335b2387 F src/dbstat.c ec92074baa61d883de58c945162d9e666c13cd7cf3a23bc38b4d1c4d0b2c2bef F src/delete.c cd5f5cd06ed0b6a882ec1a8c2a0d73b3cecb28479ad19e9931c4706c5e2182be - F src/expr.c 36f6a47c8a2c20ec3c267a60fc598857876edd60af0cb40caf7b69b651fd73bf x --F src/expr.c 8d1656b65e26af3e34f78e947ac423f0d20c214ed25a67486e433bf16ca6b543 +++F src/expr.c 8d1656b65e26af3e34f78e947ac423f0d20c214ed25a67486e433bf16ca6b543 x F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007 --F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 --F src/func.c 8ada46d362a153b8dfb5385c3aaa9a7d75ebf306b33d2e663aa03920126a1bc3 ++F src/fkey.c a7fcbf7e66d14dbb73cf49f31489ebf66d0e6006c62b95246924a3bae9f37b36 x - F src/func.c 6303e1ccb80dbd0d9b52f902a01d3b105981486fdfd66f9e1ddfd74aaf3032fc x +++F src/func.c 8ada46d362a153b8dfb5385c3aaa9a7d75ebf306b33d2e663aa03920126a1bc3 x F src/global.c bd0892ade7289f6e20bff44c07d06371f2ff9b53cea359e7854b9b72f65adc30 --F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 ++F src/hash.c 9ee4269fb1d6632a6fecfb9479c93a1f29271bddbbaf215dd60420bcb80c7220 x F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 --F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 ++F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 x F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 --F src/json.c 14c474fb1249a46eb44e878e2361f36abfe686b134039b0d1883d93d61505b4a ++F src/json.c 14c474fb1249a46eb44e878e2361f36abfe686b134039b0d1883d93d61505b4a x F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa --F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465 --F src/main.c 5fd4b65d61ae6155f36756ed508a39b38b49355b031188961e8d923f43f4bc49 ++F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465 x ++F src/main.c 5fd4b65d61ae6155f36756ed508a39b38b49355b031188961e8d923f43f4bc49 x F src/malloc.c 47b82c5daad557d9b963e3873e99c22570fb470719082c6658bf64e3012f7d23 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 --F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 ++F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 x F src/mem2.c c8bfc9446fd0798bddd495eb5d9dbafa7d4b7287d8c22d50a83ac9daa26d8a75 F src/mem3.c 30301196cace2a085cbedee1326a49f4b26deff0af68774ca82c1f7c06fda4f6 F src/mem5.c b7da5c10a726aacacc9ad7cdcb0667deec643e117591cc69cf9b4b9e7f3e96ff @@@@ -626,42 -621,39 -621,39 +626,42 @@@@ F src/os.h 1ff5ae51d339d0e30d8a9d814f4b F src/os_common.h 6c0eb8dd40ef3e12fe585a13e709710267a258e2c8dd1c40b1948a1d14582e06 F src/os_kv.c 4d39e1f1c180b11162c6dc4aa8ad34053873a639bac6baae23272fc03349986a F src/os_setup.h 6011ad7af5db4e05155f385eb3a9b4470688de6f65d6166b8956e58a3d872107 --F src/os_unix.c 95b407307deb902a3bd9a5d5666c7838709cccb337baeee6ef0a53f512d3673e --F src/os_win.c 7038223a1cda0a47e2ab4db47f63bf1833fe53ba0542f0f283a062ea13894103 ++F src/os_unix.c 95b407307deb902a3bd9a5d5666c7838709cccb337baeee6ef0a53f512d3673e x ++F src/os_win.c 7038223a1cda0a47e2ab4db47f63bf1833fe53ba0542f0f283a062ea13894103 x F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a --F src/pager.c 5ddf3a74c633a008ea6b2f5b3186167e88e2c8ca8a252ecab06ab3f1eb48e60f ++F src/pager.c 5ddf3a74c633a008ea6b2f5b3186167e88e2c8ca8a252ecab06ab3f1eb48e60f x F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3 --F src/parse.y 8828f9e15f04d469eab9c0f2aed504e534b1c97c68836bed6f07afab29c2ac0b --F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b ++F src/parse.y 8828f9e15f04d469eab9c0f2aed504e534b1c97c68836bed6f07afab29c2ac0b x ++F src/pcache.c 4cd4a0043167da9ba7e19b4d179a0e6354e7fe32c16f781ecf9bf0a5ff63b40b x F src/pcache.h 1497ce1b823cf00094bb0cf3bac37b345937e6f910890c626b16512316d3abf5 --F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 --F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a ++F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00 x ++F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a x F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7 --F src/prepare.c d6c4354f8ea0dc06962fbabc4b68c4471a45276a2918c929be00f9f537f69eb1 -F src/printf.c cb8357e7810848a2148acacfa8897c84905cadc3b040c2e7e0d2b000a3bcef82 ++F src/prepare.c d6c4354f8ea0dc06962fbabc4b68c4471a45276a2918c929be00f9f537f69eb1 x - F src/printf.c b9320cdbeca0b336c3f139fd36dd121e4167dd62b35fbe9ccaa9bab44c0af38d + +F src/printf.c a87473be34fa2acafa27692b8ae078275c7e23360956c93c07ff22f5d609cbd7 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c ++F src/resmanage.c b85f77821ea0280c73a6afc1dc3b6884ba3a402515c6bf27ba1567408e4f121d x ++F src/resmanage.h eb63130e49d7b696a33c6d39d1c437a050bbef42b4f6f4405a15fc563c7dd61e x F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9 --F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 --F src/select.c 383b9dba12493c365ee2036bcadd73013b7c0f7d2afcda0c378317c335d60ac2 - F src/shell.c.in 2c02c819349de410d63fcc0217763dfe5a42dbe58f2d68046d4ea8a376d12c26 -F src/shell.c.in 63e3b76912c92ece8165577df0f7981808d52c5fd169fa5ab92d2e668f7d8bdd --F src/sqlite.h.in 3076d78836b6dac53b3ab0875fc8fd15bca8077aad4d33c85336e05af6aef8c7 ++F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 x ++F src/select.c 383b9dba12493c365ee2036bcadd73013b7c0f7d2afcda0c378317c335d60ac2 x - F src/shell.c.in bfaedead984f71ca65601b7c2692516c00b144fcbb8634851233b45c95838a91 x +++F src/shell.c.in 3d9053cee4ccb6744e4b18f1f980d037bfebc8a01c162fbc6f35e194858a4ab1 ++F src/shext_linkage.h 4a686427844d5d2b71f2095cb032280fb262490795f0710487ebbedb3732f1cb x ++F src/sqlite.h.in 3076d78836b6dac53b3ab0875fc8fd15bca8077aad4d33c85336e05af6aef8c7 x F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4 - F src/sqliteInt.h 8974b60740b108269f51e833e85191be6bf9f06f317ee34a53b7ec215762cf8c + F src/sqliteInt.h f7e904f7fdeccfd5606ca4457122e647dcbaf307ed8615ac4865f8b5f536f77b --F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 ++F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6 x F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 --F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd --F src/test1.c e6ab4a00671f052366a01bcb7fdf2e2f6bb4aa884cd01e738c5590dcf47a99ca ++F src/tclsqlite.c ecbc3c99c0d0c3ed122a913f143026c26d38d57f33e06bb71185dd5c1efe37cd x ++F src/test1.c e6ab4a00671f052366a01bcb7fdf2e2f6bb4aa884cd01e738c5590dcf47a99ca x F src/test2.c 827446e259a3b7ab949da1542953edda7b5117982576d3e6f1c24a0dd20a5cef --F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e ++F src/test3.c e5178558c41ff53236ae0271e9acb3d6885a94981d2eb939536ee6474598840e x F src/test4.c 4533b76419e7feb41b40582554663ed3cd77aaa54e135cf76b3205098cd6e664 F src/test5.c 328aae2c010c57a9829d255dc099d6899311672d --F src/test6.c e53bc69dc3cb3815fb74df74f38159ec05ba6dd5273216062e26bc797f925530 --F src/test8.c ccc5d3e2a2bf7248f7da185e2afc4c08b4c6840447f5eb4dd106db165fddbdbc ++F src/test6.c e53bc69dc3cb3815fb74df74f38159ec05ba6dd5273216062e26bc797f925530 x ++F src/test8.c ccc5d3e2a2bf7248f7da185e2afc4c08b4c6840447f5eb4dd106db165fddbdbc x F src/test9.c 12e5ba554d2d1cbe0158f6ab3f7ffcd7a86ee4e5 F src/test_async.c 195ab49da082053fdb0f949c114b806a49ca770a F src/test_autoext.c 915d245e736652a219a907909bb6710f0d587871 @@@@ -715,28 -705,28 -705,28 +715,28 @@@@ F src/trigger.c ad6ab9452715fa9a8075442 F src/update.c 0aa36561167a7c40d01163238c297297962f31a15a8d742216b3c37cdf25f731 F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 - F src/util.c 6f9d2f278dcc8d41c618980cd3cfe88e1bafc0626209b917c6773d8202d29ef6 x --F src/util.c a6b41c67ff2a5379b46b77e587b2e0adb2d2ddcc5669691674ca4d28e2755ae4 --F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 --F src/vdbe.c c993304c609326cf625b4ad30cbb0e15a3f64c941cf2c9713d0c360b4abbaa98 --F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 --F src/vdbeInt.h 7bd49eef8f89c1a271fbf12d80a206bf56c876814c5fc6bee340f4e1907095ae --F src/vdbeapi.c de9703f8705afc393cc2864669ce28cf9516983c8331d59aa2b978de01634365 +++F src/util.c a6b41c67ff2a5379b46b77e587b2e0adb2d2ddcc5669691674ca4d28e2755ae4 x ++F src/vacuum.c 604fcdaebe76f3497c855afcbf91b8fa5046b32de3045bab89cc008d68e40104 x - F src/vdbe.c 6c0de640ef3be08cf2992d588a7501aee0f1003027bc952a6916a35f6e33b4cf x +++F src/vdbe.c c993304c609326cf625b4ad30cbb0e15a3f64c941cf2c9713d0c360b4abbaa98 x ++F src/vdbe.h 41485521f68e9437fdb7ec4a90f9d86ab294e9bb8281e33b235915e29122cfc0 x ++F src/vdbeInt.h 7bd49eef8f89c1a271fbf12d80a206bf56c876814c5fc6bee340f4e1907095ae x ++F src/vdbeapi.c de9703f8705afc393cc2864669ce28cf9516983c8331d59aa2b978de01634365 x F src/vdbeaux.c 4d5e68a3850d0b193a692eca6442d7afe35252aaf29728a67adcb542ecabd9ce F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce --F src/vdbemem.c 710119a8e35e47813681c48703d65a80ba22792192de90bc51dc0d6366f2a79e --F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 ++F src/vdbemem.c 710119a8e35e47813681c48703d65a80ba22792192de90bc51dc0d6366f2a79e x ++F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015 x F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823 F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac --F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 ++F src/vtab.c 1ecf8c3745d29275688d583e12822fa984d421e0286b5ef50c137bc3bf6d7a64 x F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9 --F src/wal.c cbfeeb7415baa545efa244dd34bb5af4ae953a206fed720c6fa7f1ef763ec122 ++F src/wal.c cbfeeb7415baa545efa244dd34bb5af4ae953a206fed720c6fa7f1ef763ec122 x F src/wal.h c3aa7825bfa2fe0d85bef2db94655f99870a285778baa36307c0a16da32b226a F src/walker.c 7c7ea0115345851c3da4e04e2e239a29983b61fb5b038b94eede6aba462640e2 --F src/where.c 2dc708cf8b6a691fb79f16bbc46567497ee6f991043318d421e294b2da114d93 --F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a --F src/wherecode.c bff0bc56cb1a382de266c2db3a691135c18a4360b6ad5e069e5c415d57eb0c38 --F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 --F src/window.c b7ad9cff3ce8ae6f8cc25e18e1a258426cb6bd2999aace6f5248d781b2a74098 ++F src/where.c 94c90f838057e8f1ac6ab6e4caf64bbf487f4d0d33e64643731ff99e80a50b54 x ++F src/whereInt.h c7d19902863beadec1d04e66aca39c0bcd60b74f05f0eaa7422c7005dfc5d51a x ++F src/wherecode.c bff0bc56cb1a382de266c2db3a691135c18a4360b6ad5e069e5c415d57eb0c38 x ++F src/whereexpr.c dc5096eca5ed503999be3bdee8a90c51361289a678d396a220912e9cb73b3c00 x ++F src/window.c b7ad9cff3ce8ae6f8cc25e18e1a258426cb6bd2999aace6f5248d781b2a74098 x F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2 F test/affinity2.test ce1aafc86e110685b324e9a763eab4f2a73f737842ec3b687bd965867de90627 F test/affinity3.test f094773025eddf31135c7ad4cde722b7696f8eb07b97511f98585addf2a510a9 @@@@ -1478,11 -1468,11 -1468,11 +1478,11 @@@@ F test/sharedA.test 64bdd21216dda2c6a3b F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 --F test/shell1.test 300b77328aaafb9f3e7a53a26e4162fbf92181d92251d259ff105a2275ff998d --F test/shell2.test 35226c070a8c7f64fd016dfac2a0db2a40f709b3131f61daacd9dad61536c9cb --F test/shell3.test 91febeac0412812bf6370abb8ed72700e32bf8f9878849414518f662dfd55e8a --F test/shell4.test 9abd0c12a7e20a4c49e84d5be208d2124fa6c09e728f56f1f4bee0f02853935f --F test/shell5.test c8b6c54f26ec537f8558273d7ed293ca3725ef42e6b12b8f151718628bd1473b ++F test/shell1.test cac1fd2d681e0b79de9488069e4b2287da30788ce96a34788cbffbfe57e7b0a4 - F test/shell2.test 71f2f0dbc9fc042f78bc4403b8d4cf0d5ff2ae5eb14285fdec6c1abc945bc87c +++F test/shell2.test c4471c97c0da342c9b02160abd5263de118b4bbb411b3318ee2a179d7bf18a08 ++F test/shell3.test 5dc710deede6e811e7af2bd91867d4128f023edc5a6cff6b8ad89e32af54c033 ++F test/shell4.test 8116d7b9dbefe6e2237908afbd6738637e0426486373231d3ad8984471d4e04c ++F test/shell5.test ec82248dda87329f7dfced8637515b68db4ee75d94c8129dc017376fb61a8247 F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 3fd093d481aaa94dc77fb73f1044c1f19c7efe3477a395cc4f7450133bc54915 @@@@ -2053,8 -2041,8 -2041,8 +2053,8 @@@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 - P e9f2119106f687ecf9bc9c5f78c043ce7dd91874d3fb516d7621dbd806d8a174 61dfa92b44ad38a7aac76a09e167819ce5d0acace3e06ba9ed17b3264cc043c1 - R 9a3f8a0af0f755f036bc314cbeda13d9 - P 41580ba452fdbc3f73da60d8030289d38614c4cab8d24140d7cc44a54b2da8d2 - R 479219669b5b9c2257f0511e6dc18154 -P d758859f6ab94ddb9b3ee6f6f5f24b16e2b7a7712761080cfc6540d68b5a0c97 -R a4c45eeed81feef61bc930db9802b9d0 --U drh - Z e7211a376de07c93dd5c0d56ad175411 -Z 04c984163e7286ef19d8dbc870dfbf03 +++P a61f9377014ee582ec469f1066196e07b745295724c6d3ff4baffcaed22ae5a1 24927c1377314a10177da4a57191593440aa97fd0c5949fdf25a22df1d947600 2f9d4444aa503102a00d6e6769dadc57d4b26a2c07f145f23f2f28e0c161246d +++R bd3140ab1e0bb2a93e6d3c105e3f3689 ++U larrybr - Z b13b22bfc690f89590baa97daf429a4b +++Z 497f78a875664dfe4baa50f499afe15b # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index 8dc805f768,5f3f75a4be,ceb4c37b4b..4b532a727a --- a/manifest.uuid +++ b/manifest.uuid @@@@ -1,1 -1,1 -1,1 +1,1 @@@@ - a61f9377014ee582ec469f1066196e07b745295724c6d3ff4baffcaed22ae5a1 - 24927c1377314a10177da4a57191593440aa97fd0c5949fdf25a22df1d947600 -2f9d4444aa503102a00d6e6769dadc57d4b26a2c07f145f23f2f28e0c161246d +++fe9aa2e9c17f33a8f6045e9cf755f8251d261c34f89a59d658b72f7c99a6a12c diff --cc src/btree.c index 7f79327324,aa4e286013,aa4e286013..aa4e286013 mode 100755,100644,100644..100755 --- a/src/btree.c +++ b/src/btree.c diff --cc src/expr.c index b0fe32a7eb,a8e552f2c7,a8e552f2c7..a8e552f2c7 mode 100755,100644,100644..100755 --- a/src/expr.c +++ b/src/expr.c diff --cc src/func.c index 14f485d4de,c505c37d69,c505c37d69..c505c37d69 mode 100755,100644,100644..100755 --- a/src/func.c +++ b/src/func.c diff --cc src/shell.c.in index 519dc68e3e,2b0884e1c0,fef9d45c32..802a2cb57b mode 100755,100644,100644..100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@@@ -1566,13 -1198,55 -1198,54 +1566,52 @@@@ static char *shellFakeSchema 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) ** @@@@ -1995,78 -1516,24 -1515,24 +2034,78 @@@@ typedef struct ShellInState 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 */ @@@@ -2250,25 -1698,8 -1697,8 +2289,25 @@@@ static int failIfSafeMode } /* --** 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: ** @@@@ -2413,100 -1842,19 -1841,19 +2452,100 @@@@ edit_func_end #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; isizeof(what))? *((u16 *)pSaved) : 0; ++ int i, nAlloc = sizeof(what)+1, mask = 1; ++ char *pTake = (char *)pSaved + sizeof(what); ++ for( i=0; imodePrior = 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->nSavedModesnSavedModes>=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); } /* @@@@ -5691,40 -4725,422 -4724,422 +5730,40 @@@@ static int run_schema_dump_query 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 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; inumSubscriptions ){ ++ 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. ** @@@@ -6130,31 -5503,37 -5502,37 +6169,37 @@@@ static sqlite3 * open_db(ShellExState * } #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; @@@@ -6350,11 -5729,14 -5728,14 +6395,11 @@@@ static int booleanValue(const char *zAr 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; } @@@@ -8427,4087 -7976,1481 -7975,1481 +8472,4087 @@@@ FROM ( } } ++#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] && nArgnCol!=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; ipData); ++ 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 && mipsi); ++} ++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; ipSpecWidths[i] = 0; ++ psx->numWidths = nColumn; ++ psx->pHaveWidths = &psx->pSpecWidths[nColumn]; ++ } ++ memset(psx->pHaveWidths, 0, nColumn*sizeof(int)); ++ for(i=0; ipSpecWidths[i]; ++ if( w<0 ) w = -w; ++ psx->pHaveWidths[i] = w; ++ } ++ for(i=0; ipSpecWidths[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; ipSpecWidths[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=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; jpsx->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; ipHaveWidths[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; iout, 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; ipHaveWidths[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; ipHaveWidths[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; ipHaveWidths[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; icMode!=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+1cMode==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; iaAuxDb); 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 && iaAuxDb) ){ -- 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; icolInfo.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; iaiTypes[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; idb, 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; ii1 && 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; iixExtPending 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( esixnumSubscriptions ){ ++ 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; iout, "Available file-controls:\n"); -- for(i=0; iout, " .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; i0 ){ ++ 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; icpMethods->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 && extIxnumExtLoaded ){ ++ ShExtInfo *psei = & psi->pShxLoaded[extIx]; ++ if( cmdIx>=0 && cmdIxnumDotCommands ){ ++ 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), {, }, 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; iaout, "%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; iaAuxDb); 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 && iaAuxDb) ){ ++ 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; ii1 && 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; ishellFlgs & 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=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; ioutCount = 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; iout, "Available file-controls:\n"); ++ for(i=0; iout, " .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; iout, "%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; icolSeparator); ++ 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 = ""; ++ 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=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; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; ++ sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); ++ 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( i75){ ++ 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; i3 ){ ++ return DCR_TooMany; ++ }else{ ++ int iLimit = -1; ++ n2 = strlen30(azArg[1]); ++ for(i=0; iout; /* 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; i1 && 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( aibSafeMode && !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
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; aix0 && zArg[nza-1]=='s'); ++ if( foundMode!=MODE_COUNT_OF ) goto mode_badarg; ++ for( im=0; imout; ++ 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 && imode) ){ ++ 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; iNamecolSeparator[] */ -- 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+1out, "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 && iout, "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 = ""; -- 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=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; imode==MODE_Ascii && (z==0 || z[0]==0) && i==0 ) break; -- sqlite3_bind_text(pStmt, i+1, z, -1, SQLITE_TRANSIENT); -- 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; pSLdbShell; ++ 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=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; idb, 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; i2 || 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; vixzEditor, &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; imode==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( invzNonce==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 @@@@ -13071,883 -10451,409 -10450,409 +13116,884 @@@@ DISPATCHABLE_COMMAND( session 3 2 0 ) } }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; i2 ) 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; inSession; 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; inSession; 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; iout, 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; iout, "%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;inWidth;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; iout, "%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; iout, "%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=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; ibSafeMode ) return DCR_AbortError; ++ while( aidb, 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; imaxlen ) maxlen = len; -- } -- nPrintCol = 80/(maxlen+2); -- if( nPrintCol<1 ) nPrintCol = 1; -- nPrintRow = (nRow + nPrintCol - 1)/nPrintCol; -- for(i=0; iout, "%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<1 ){ ++ for( ia=1; iabExtendedDotCmds |= 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; iobExtendedDotCmds & 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; iinumWidths ){ ++ 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, "" },*/ @@@@ -13970,1183 -10876,296 -10875,296 +14016,1174 @@@@ {"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; iout, "Available test-controls:\n"); -- for(i=0; iout, " .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; ipInSource->lineno, - aCtrl[iCtrl].zCtrlName) ){ - return DCR_Abort; ++ }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; i1 ) 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; jjeTraceType = 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; iidbShell; ++ 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; jpSpecWidths[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( rvDCR_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 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( rvDCR_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= ++COMMENT where 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( pCIcmdName)==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(extIxnumExtLoaded && 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( pCIzPattern, 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; jjeTraceType = 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)) ++ || ++ (ixm1 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; iidb, 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 diff --cc src/util.c index bed86a5690,256ec7c5c7,256ec7c5c7..256ec7c5c7 mode 100755,100644,100644..100755 --- a/src/util.c +++ b/src/util.c diff --cc src/vdbe.c index 7045a1e4bd,8255f6c4a9,8255f6c4a9..8255f6c4a9 mode 100755,100644,100644..100755 --- a/src/vdbe.c +++ b/src/vdbe.c