--- /dev/null
- if {$result != "" && $interactive} {
+/*
+** 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
+
+/* 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 tclsqlite.c
+
+#if defined(_WIN32) || defined(WIN32)
+# define getDir(cArray) _getwd(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, 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, MetaCommand,TclCmd, 0, ()){
+ /* Nothing to do, instance data is static. */
+ (void)(pThis);
+}
+static DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 0, ());
+
+DERIVED_METHOD(void, destruct, ScriptSupport,TclSS, 0, ()){
+ /* Nothing to do, instance data is static. */
+ (void)(pThis);
+}
+
+DERIVED_METHOD(const char *, name, MetaCommand,TclCmd, 0,()){
+ return "tcl";
+}
+DERIVED_METHOD(const char *, name, MetaCommand,UnkCmd, 0,()){
+ return "unknown";
+}
+
+DERIVED_METHOD(const char *, help, MetaCommand,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, MetaCommand,UnkCmd, 1,(const char *zHK));
+
+DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,TclCmd, 3,
+ (char **pzErrMsg, int nArgs, char *azArgs[])){
+ return DCR_Ok;
+}
+DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,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 {$interactive && $trimmed ne "."} {puts {}}
++ 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, MetaCommand,TclCmd, 4,
+ (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
+ FILE *out = SHX_HELPER(currentOutputFile)(psx);
+ 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, MetaCommand,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{
+ FILE *out = SHX_HELPER(currentOutputFile)(psx);
+ fprintf(stderr, "The %s command does not yet exist.\n", zName);
+ fprintf(out, "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<nArgs; ++ia ){
+ ppo[ia] = Tcl_NewStringObj((ia)? azArgs[ia] : zName, -1);
+ Tcl_IncrRefCount(ppo[ia]);
+ }
+ rc = Tcl_EvalObjv(interp, nArgs, ppo, TCL_EVAL_GLOBAL);
+ for( ia=0; ia<nArgs; ++ia ) Tcl_DecrRefCount(ppo[ia]);
+ sqlite3_free(ppo);
+ /* Translate TCL return to a dot command return. */
+ switch( rc ){
+ case TCL_OK:
+ return DCR_Ok;
+ case TCL_ERROR:
+ *pzErrMsg = sqlite3_mprintf("%s\n", Tcl_GetStringResult(interp));
+ return DCR_Error;
+ case TCL_RETURN: case TCL_BREAK: case TCL_CONTINUE:
+ return DCR_Return;
+ default:
+ return DCR_Exit;
+ }
+}
+
+/* Define MetaCommand v-tables initialized to reference most above methods. */
+MetaCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods);
+MetaCommand_IMPLEMENT_VTABLE(UnkCmd, unkcmd_methods);
+/* Define ScriptSupport v-table initialized to reference the others. */
+ScriptSupport_IMPLEMENT_VTABLE(TclSS, tclss_methods);
+
+/* Static instances are used because that suffices. */
+INSTANCE_BEGIN(TclCmd);
+ /* no instance data */
+INSTANCE_END(TclCmd) tclcmd = {
+ &tclcmd_methods
+};
+INSTANCE_BEGIN(TclSS);
+ /* no instance data */
+INSTANCE_END(TclSS) tclss = {
+ &tclss_methods
+};
+
+INSTANCE_BEGIN(UnkCmd);
+ /* no instance data */
+INSTANCE_END(UnkCmd) unkcmd = {
+ &unkcmd_methods
+};
+
+static DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 0, ()){
+ (void)(pThis);
+}
+
+DERIVED_METHOD(const char *, help, MetaCommand,UnkCmd, 1,(const char *zHK)){
+ (void)(pThis);
+ if( !zHK )
+ return
+ ",unknown ?ARGS? Retry unknown dot command if it is a TCL command\n";
+ if( !*zHK )
+ return
+ " There is little use for this dot command without the TCL extension, as\n"
+ " the shell's version merely does some error reporting. However, with it\n"
+ " overridden, (as it is now), it provides a retry mechanism whereby, if\n"
+ " the command can be found defined in the TCL environment, that command\n"
+ " can be run with whatever arguments it was provided.\n"
+ "\n"
+ " If the TCL command, register_adhoc_command is run, this command's help\n"
+ " method can be made to provide help text for the registered TCL command.\n"
+ ;
+ return 0;
+}
+
+#if TCL_REPL==1 || TCL_REPL==2
+#define GETLINE_MAXLEN 1000
+
+/* C implementation of TCL proc, get_input_line */
+static int getInputLine(void *pvSS, Tcl_Interp *interp,
+ int nArgs, const char *azArgs[]){
+ if( nArgs==1 ){
+ char buffer[GETLINE_MAXLEN+1];
+ ShellExState *psx = (ShellExState *)pvSS;
+ struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
+ if( SHX_HELPER(strLineGet)(buffer, GETLINE_MAXLEN, pis) ){
+ Tcl_SetResult(interp, buffer, TCL_VOLATILE);
+ }else{
+ Tcl_SetResult(interp, 0, 0);
+ }
+ return TCL_OK;
+ }else{
+ Tcl_SetResult(interp, "too many arguments", TCL_STATIC);
+ return TCL_ERROR;
+ }
+}
+#endif
+
+#if TCL_REPL==3
+/* C implementation of TCL proc, get_tcl_group
+ * 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% ", " > " };
+ ShellExState *psx = (ShellExState *)pvSS;
+ struct InSource *pis = SHX_HELPER(currentInputSource)(psx);
+ int isComplete = 0;
+ char *zIn = 0;
+ int isContinuation = 0;
+ 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 objv[] = {
+ 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, objv));
+ }
+ 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;
+ MetaCommand *pmc = 0;
+ int nFound = 0;
+ int ia, rc;
+
+ if( name ) pmc = SHX_HELPER(findMetaCommand)(name, psx, &nFound);
+ if( pmc==(MetaCommand*)&tclcmd && nArgs==2 ){
+ /* Will not do a nested REPL, just silently semi-fake it. */
+ return TCL_OK;
+ }
+ if( pmc && nFound==1 ){
+ /* Run the dot command and interpret its returns. */
+ DotCmdRC drc = SHX_HELPER(runMetaCommand)(pmc, (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; ia<nArgs; ++ia ){
+ ppo[ia] = Tcl_NewStringObj(azArgs[ia], -1);
+ Tcl_IncrRefCount(ppo[ia]);
+ }
+ ppo[ia] = 0;
+ rc = Tcl_EvalObjv(interp, nArgs, ppo, TCL_EVAL_GLOBAL);
+ for( ia=0; ia<nArgs; ++ia ) Tcl_DecrRefCount(ppo[ia]);
+ sqlite3_free(ppo);
+ return rc;
+ }else{
+ /* Fail now (instead of recursing back into this handler.) */
+ Tcl_AppendResult(interp,
+ "Command ", azArgs[1], " does not exist.", (char *)0);
+ return TCL_ERROR;
+ }
+ }
+}
+
+/* TCL dbu command: Acts like a (TCL) sqlite3 command created object except
+ * that it defers to shell's DB and treats the close subcommand as an error.
+ * The below struct and functions through userDbInit() support this feature.
+ */
+typedef struct UserDb {
+ SqliteDb **ppSdb; /* Some tclsqlite.c "sqlite3" DB objects, held here. */
+ int numSdb; /* How many "sqlite3" objects are now being held */
+ int ixuSdb; /* Which held "sqlite3" object is the .dbUser, if any */
+ int nRef; /* TCL object sharing counter */
+ Tcl_Interp *interp; /* For creation of newly visible .dbUser DBs */
+ ShellExState *psx; /* For shell state access when .eval is run */
+} UserDb;
+
+/* Add a DB to the list. Return its index. */
+static int udbAdd(UserDb *pudb, sqlite3 *udb){
+ SqliteDb *p;
+ int nshift;
+ pudb->ppSdb
+ = (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 && ix<pudb->numSdb);
+ /* 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( ix<pudb->ixuSdb ) --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);
+ 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;
+ }
+ }
+ 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;
+ 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_GetIndexFromObj(interp, objv[1], azDoHere,
+ "subcommand", 0, &doWhat)){
+ switch( doWhat ){
+ case DDW_Close:
+ zMoan = " close is disallowd. It is a wrapped DB.\n";
+ goto complain_fail;
+ }
+ }
+ 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; ic<numDbNames; ++ic ){
+ nCreate +=
+ 0 != Tcl_CreateObjCommand(interp, azDbNames[ic],
+ (Tcl_ObjCmdProc*)UserDbObjCmd,
+ (char *)pudb, 0);
+ }
+ if( nCreate==ic ){
+ ++pudb->nRef;
+ 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; ila<pShExtLink->nLoadArgs; ++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(registerMetaCommand)(psx, sqlite3_tclshext_init,
+ (MetaCommand *)&unkcmd);
+ rc = SHX_API(registerMetaCommand)(psx, sqlite3_tclshext_init,
+ (MetaCommand *)&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;
+ }
+}
}
/*
-** Implementation of .ar "eXtract" command.
-*/
-static int arExtractCommand(ArCommand *pAr){
- const char *zSql1 =
- "SELECT "
- " ($dir || name),"
- " writefile(($dir || name), %s, mode, mtime) "
- "FROM %s WHERE (%s) AND (data IS NULL OR $dirOnly = 0)"
- " AND name NOT GLOB '*..[/\\]*'";
+** This function is a no-op if (*pRc) is not SQLITE_OK when it is called.
+** Otherwise, it allocates and returns a RecoverTable object based on the
+** final four arguments passed to this function. It is the responsibility
+** of the caller to eventually free the returned object using
+** recoverFreeTable().
+*/
+static RecoverTable *recoverNewTable(
+ int *pRc, /* IN/OUT: Error code */
+ const char *zName, /* Name of table */
+ const char *zSql, /* CREATE TABLE statement */
+ int bIntkey,
+ int nCol
+){
+ sqlite3 *dbtmp = 0; /* sqlite3 handle for testing CREATE TABLE */
+ int rc = *pRc;
+ RecoverTable *pTab = 0;
- const char *azExtraArg[] = {
- "sqlar_uncompress(data, sz)",
- "data"
- };
+ pTab = (RecoverTable*)shellMalloc(&rc, sizeof(RecoverTable));
+ if( rc==SQLITE_OK ){
+ int nSqlCol = 0;
+ int bSqlIntkey = 0;
+ sqlite3_stmt *pStmt = 0;
- sqlite3_stmt *pSql = 0;
- int rc = SQLITE_OK;
- char *zDir = 0;
- char *zWhere = 0;
- int i, j;
+ rc = sqlite3_open("", &dbtmp);
+ if( rc==SQLITE_OK ){
+ sqlite3_create_function(dbtmp, "shell_idquote", 1, SQLITE_UTF8, 0,
+ shellIdQuote, 0, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(dbtmp, "PRAGMA writable_schema = on", 0, 0, 0);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_exec(dbtmp, zSql, 0, 0, 0);
+ if( rc==SQLITE_ERROR ){
+ rc = SQLITE_OK;
+ goto finished;
+ }
+ }
+ shellPreparePrintf(dbtmp, &rc, &pStmt,
+ "SELECT count(*) FROM pragma_table_info(%Q)", zName
+ );
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ nSqlCol = sqlite3_column_int(pStmt, 0);
+ }
+ shellFinalize(&rc, pStmt);
- /* If arguments are specified, check that they actually exist within
- ** the archive before proceeding. And formulate a WHERE clause to
- ** match them. */
- rc = arCheckEntries(pAr);
- arWhereClause(&rc, pAr, &zWhere);
+ if( rc!=SQLITE_OK || nSqlCol<nCol ){
+ goto finished;
+ }
- if( rc==SQLITE_OK ){
- if( pAr->zDir ){
- zDir = sqlite3_mprintf("%s/", pAr->zDir);
- }else{
- zDir = sqlite3_mprintf("");
+ shellPreparePrintf(dbtmp, &rc, &pStmt,
+ "SELECT ("
+ " SELECT substr(data,1,1)==X'0D' FROM sqlite_dbpage WHERE pgno=rootpage"
+ ") FROM sqlite_schema WHERE name = %Q", zName
+ );
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ bSqlIntkey = sqlite3_column_int(pStmt, 0);
}
- if( zDir==0 ) rc = SQLITE_NOMEM;
- }
+ shellFinalize(&rc, pStmt);
- shellPreparePrintf(pAr->db, &rc, &pSql, zSql1,
- azExtraArg[pAr->bZip], pAr->zSrcTable, zWhere
- );
+ if( bIntkey==bSqlIntkey ){
+ int i;
+ const char *zPk = "_rowid_";
+ sqlite3_stmt *pPkFinder = 0;
- if( rc==SQLITE_OK ){
- j = sqlite3_bind_parameter_index(pSql, "$dir");
- sqlite3_bind_text(pSql, j, zDir, -1, SQLITE_STATIC);
+ /* If this is an intkey table and there is an INTEGER PRIMARY KEY,
+ ** set zPk to the name of the PK column, and pTab->iPk to the index
+ ** of the column, where columns are 0-numbered from left to right.
+ ** Or, if this is a WITHOUT ROWID table or if there is no IPK column,
+ ** leave zPk as "_rowid_" and pTab->iPk at -2. */
+ pTab->iPk = -2;
+ if( bIntkey ){
+ shellPreparePrintf(dbtmp, &rc, &pPkFinder,
+ "SELECT cid, name FROM pragma_table_info(%Q) "
+ " WHERE pk=1 AND type='integer' COLLATE nocase"
+ " AND NOT EXISTS (SELECT cid FROM pragma_table_info(%Q) WHERE pk=2)"
+ , zName, zName
+ );
+ if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPkFinder) ){
+ pTab->iPk = sqlite3_column_int(pPkFinder, 0);
+ zPk = (const char*)sqlite3_column_text(pPkFinder, 1);
+ if( zPk==0 ){ zPk = "_"; /* Defensive. Should never happen */ }
+ }
+ }
- /* Run the SELECT statement twice. The first time, writefile() is called
- ** for all archive members that should be extracted. The second time,
- ** only for the directories. This is because the timestamps for
- ** extracted directories must be reset after they are populated (as
- ** populating them changes the timestamp). */
- for(i=0; i<2; i++){
- j = sqlite3_bind_parameter_index(pSql, "$dirOnly");
- sqlite3_bind_int(pSql, j, i);
- if( pAr->bDryRun ){
- utf8_printf(pAr->p->out, "%s\n", sqlite3_sql(pSql));
+ pTab->zQuoted = shellMPrintf(&rc, "\"%w\"", zName);
+ pTab->azlCol = (char**)shellMalloc(&rc, sizeof(char*) * (nSqlCol+1));
+ pTab->nCol = nSqlCol;
+
+ if( bIntkey ){
+ pTab->azlCol[0] = shellMPrintf(&rc, "\"%w\"", zPk);
}else{
- while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
- if( i==0 && pAr->bVerbose ){
- utf8_printf(pAr->p->out, "%s\n", sqlite3_column_text(pSql, 0));
- }
- }
+ pTab->azlCol[0] = shellMPrintf(&rc, "");
}
- shellReset(&rc, pSql);
+ i = 1;
+ shellPreparePrintf(dbtmp, &rc, &pStmt,
+ "SELECT %Q || group_concat(shell_idquote(name), ', ') "
+ " FILTER (WHERE cid!=%d) OVER (ORDER BY %s cid) "
+ "FROM pragma_table_info(%Q)",
+ bIntkey ? ", " : "", pTab->iPk,
+ bIntkey ? "" : "(CASE WHEN pk=0 THEN 1000000 ELSE pk END), ",
+ zName
+ );
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ const char *zText = (const char*)sqlite3_column_text(pStmt, 0);
+ pTab->azlCol[i] = shellMPrintf(&rc, "%s%s", pTab->azlCol[0], zText);
+ i++;
+ }
+ shellFinalize(&rc, pStmt);
+
+ shellFinalize(&rc, pPkFinder);
}
- shellFinalize(&rc, pSql);
}
- sqlite3_free(zDir);
- sqlite3_free(zWhere);
- return rc;
-}
-
-/*
-** Run the SQL statement in zSql. Or if doing a --dryrun, merely print it out.
-*/
-static int arExecSql(ArCommand *pAr, const char *zSql){
- int rc;
- if( pAr->bDryRun ){
- utf8_printf(pAr->p->out, "%s\n", zSql);
- rc = SQLITE_OK;
- }else{
- char *zErr = 0;
- rc = sqlite3_exec(pAr->db, zSql, 0, 0, &zErr);
- if( zErr ){
- utf8_printf(stdout, "ERROR: %s\n", zErr);
- sqlite3_free(zErr);
- }
+ finished:
+ sqlite3_close(dbtmp);
+ *pRc = rc;
+ if( rc!=SQLITE_OK || (pTab && pTab->zQuoted==0) ){
+ recoverFreeTable(pTab);
+ pTab = 0;
}
- return rc;
+ return pTab;
}
-
/*
-** Implementation of .ar "create", "insert", and "update" commands.
-**
-** create -> Create a new SQL archive
-** insert -> Insert or reinsert all files listed
-** update -> Insert files that have changed or that were not
-** previously in the archive
-**
-** Create the "sqlar" table in the database if it does not already exist.
-** Then add each file in the azFile[] array to the archive. Directories
-** are added recursively. If argument bVerbose is non-zero, a message is
-** printed on stdout for each file archived.
+** This function is called to search the schema recovered from the
+** sqlite_schema table of the (possibly) corrupt database as part
+** of a ".recover" command. Specifically, for a table with root page
+** iRoot and at least nCol columns. Additionally, if bIntkey is 0, the
+** table must be a WITHOUT ROWID table, or if non-zero, not one of
+** those.
**
-** The create command is the same as update, except that it drops
-** any existing "sqlar" table before beginning. The "insert" command
-** always overwrites every file named on the command-line, where as
-** "update" only overwrites if the size or mtime or mode has changed.
+** If a table is found, a (RecoverTable*) object is returned. Or, if
+** no such table is found, but bIntkey is false and iRoot is the
+** root page of an index in the recovered schema, then (*pbNoop) is
+** set to true and NULL returned. Or, if there is no such table or
+** index, NULL is returned and (*pbNoop) set to 0, indicating that
+** the caller should write data to the orphans table.
*/
-static int arCreateOrUpdateCommand(
- ArCommand *pAr, /* Command arguments and options */
- int bUpdate, /* true for a --create. */
- int bOnlyIfChanged /* Only update if file has changed */
+static RecoverTable *recoverFindTable(
+ sqlite3 *db, /* DB from which to recover */
+ int *pRc, /* IN/OUT: Error code */
+ int iRoot, /* Root page of table */
+ int bIntkey, /* True for an intkey table */
+ int nCol, /* Number of columns in table */
+ int *pbNoop /* OUT: True if iRoot is root of index */
){
- const char *zCreate =
- "CREATE TABLE IF NOT EXISTS sqlar(\n"
- " name TEXT PRIMARY KEY, -- name of the file\n"
- " mode INT, -- access permissions\n"
- " mtime INT, -- last modification time\n"
- " sz INT, -- original file size\n"
- " data BLOB -- compressed content\n"
- ")";
- const char *zDrop = "DROP TABLE IF EXISTS sqlar";
- const char *zInsertFmt[2] = {
- "REPLACE INTO %s(name,mode,mtime,sz,data)\n"
- " SELECT\n"
- " %s,\n"
- " mode,\n"
- " mtime,\n"
- " CASE substr(lsmode(mode),1,1)\n"
- " WHEN '-' THEN length(data)\n"
- " WHEN 'd' THEN 0\n"
- " ELSE -1 END,\n"
- " sqlar_compress(data)\n"
- " FROM fsdir(%Q,%Q) AS disk\n"
- " WHERE lsmode(mode) NOT LIKE '?%%'%s;"
- ,
- "REPLACE INTO %s(name,mode,mtime,data)\n"
- " SELECT\n"
- " %s,\n"
- " mode,\n"
- " mtime,\n"
- " data\n"
- " FROM fsdir(%Q,%Q) AS disk\n"
- " WHERE lsmode(mode) NOT LIKE '?%%'%s;"
- };
- int i; /* For iterating through azFile[] */
- int rc; /* Return code */
- const char *zTab = 0; /* SQL table into which to insert */
- char *zSql;
- char zTemp[50];
- char *zExists = 0;
+ sqlite3_stmt *pStmt = 0;
+ RecoverTable *pRet = 0;
+ int bNoop = 0;
+ const char *zSql = 0;
+ const char *zName = 0;
+
+ /* Search the recovered schema for an object with root page iRoot. */
+ shellPreparePrintf(db, pRc, &pStmt,
+ "SELECT type, name, sql FROM recovery.schema WHERE rootpage=%d", iRoot
+ );
+ while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){
+ const char *zType = (const char*)sqlite3_column_text(pStmt, 0);
+ if( bIntkey==0 && sqlite3_stricmp(zType, "index")==0 ){
+ bNoop = 1;
+ break;
+ }
+ if( sqlite3_stricmp(zType, "table")==0 ){
+ zName = (const char*)sqlite3_column_text(pStmt, 1);
+ zSql = (const char*)sqlite3_column_text(pStmt, 2);
+ if( zName!=0 && zSql!=0 ){
+ pRet = recoverNewTable(pRc, zName, zSql, bIntkey, nCol);
+ break;
+ }
+ }
+ }
+
+ shellFinalize(pRc, pStmt);
+ *pbNoop = bNoop;
+ return pRet;
+}
- arExecSql(pAr, "PRAGMA page_size=512");
- rc = arExecSql(pAr, "SAVEPOINT ar;");
- if( rc!=SQLITE_OK ) return rc;
- zTemp[0] = 0;
- if( pAr->bZip ){
- /* Initialize the zipfile virtual table, if necessary */
- if( pAr->zFile ){
- sqlite3_uint64 r;
- sqlite3_randomness(sizeof(r),&r);
- sqlite3_snprintf(sizeof(zTemp),zTemp,"zip%016llx",r);
- zTab = zTemp;
- zSql = sqlite3_mprintf(
- "CREATE VIRTUAL TABLE temp.%s USING zipfile(%Q)",
- zTab, pAr->zFile
- );
- rc = arExecSql(pAr, zSql);
- sqlite3_free(zSql);
- }else{
- zTab = "zip";
+/*
+** Return a RecoverTable object representing the orphans table.
+*/
+static RecoverTable *recoverOrphanTable(
+ sqlite3 *db, /* DB from which to recover */
+ FILE *out, /* Where to put recovery DDL */
+ int *pRc, /* IN/OUT: Error code */
+ const char *zLostAndFound, /* Base name for orphans table */
+ int nCol /* Number of user data columns */
+){
+ RecoverTable *pTab = 0;
+ if( nCol>=0 && *pRc==SQLITE_OK ){
+ int i;
+
+ /* This block determines the name of the orphan table. The prefered
+ ** name is zLostAndFound. But if that clashes with another name
+ ** in the recovered schema, try zLostAndFound_0, zLostAndFound_1
+ ** and so on until a non-clashing name is found. */
+ int iTab = 0;
+ char *zTab = shellMPrintf(pRc, "%s", zLostAndFound);
+ sqlite3_stmt *pTest = 0;
+ shellPrepare(db, pRc,
+ "SELECT 1 FROM recovery.schema WHERE name=?", &pTest
+ );
+ if( pTest ) sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
+ while( *pRc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pTest) ){
+ shellReset(pRc, pTest);
+ sqlite3_free(zTab);
+ zTab = shellMPrintf(pRc, "%s_%d", zLostAndFound, iTab++);
+ sqlite3_bind_text(pTest, 1, zTab, -1, SQLITE_TRANSIENT);
}
- }else{
- /* Initialize the table for an SQLAR */
- zTab = "sqlar";
- if( bUpdate==0 ){
- rc = arExecSql(pAr, zDrop);
- if( rc!=SQLITE_OK ) goto end_ar_transaction;
+ shellFinalize(pRc, pTest);
+
+ pTab = (RecoverTable*)shellMalloc(pRc, sizeof(RecoverTable));
+ if( pTab ){
+ pTab->zQuoted = shellMPrintf(pRc, "\"%w\"", zTab);
+ pTab->nCol = nCol;
+ pTab->iPk = -2;
+ if( nCol>0 ){
+ pTab->azlCol = (char**)shellMalloc(pRc, sizeof(char*) * (nCol+1));
+ if( pTab->azlCol ){
+ pTab->azlCol[nCol] = shellMPrintf(pRc, "");
+ for(i=nCol-1; i>=0; i--){
+ pTab->azlCol[i] = shellMPrintf(pRc, "%s, NULL", pTab->azlCol[i+1]);
+ }
+ }
+ }
+
+ if( *pRc!=SQLITE_OK ){
+ recoverFreeTable(pTab);
+ pTab = 0;
+ }else{
+ raw_printf(out,
+ "CREATE TABLE %s(rootpgno INTEGER, "
+ "pgno INTEGER, nfield INTEGER, id INTEGER", pTab->zQuoted
+ );
+ for(i=0; i<nCol; i++){
+ raw_printf(out, ", c%d", i);
+ }
+ raw_printf(out, ");\n");
+ }
}
- rc = arExecSql(pAr, zCreate);
+ sqlite3_free(zTab);
}
- if( bOnlyIfChanged ){
- zExists = sqlite3_mprintf(
- " AND NOT EXISTS("
- "SELECT 1 FROM %s AS mem"
- " WHERE mem.name=disk.name"
- " AND mem.mtime=disk.mtime"
- " AND mem.mode=disk.mode)", zTab);
- }else{
- zExists = sqlite3_mprintf("");
+ return pTab;
+}
+#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
+
+static DotCmdRC
+writeDb( char *azArg[], int nArg, ShellExState *psx, char **pzErr ){
+ int rc = 0;
+ const char *zDestFile = 0;
+ const char *zDb = 0;
+ sqlite3 *pDest;
+ sqlite3_backup *pBackup;
+ int j;
+ int bAsync = 0;
+ const char *zVfs = 0;
+ if( ISS(psx)->bSafeMode ) return DCR_AbortError;
+ for(j=1; j<nArg; j++){
+ const char *z = azArg[j];
+ if( z[0]=='-' ){
+ if( z[1]=='-' ) z++;
+ if( strcmp(z, "-append")==0 ){
+ zVfs = "apndvfs";
+ }else if( strcmp(z, "-async")==0 ){
+ bAsync = 1;
+ }else{
+ return DCR_Unknown|j;
+ }
+ }else if( zDestFile==0 ){
+ zDestFile = azArg[j];
+ }else if( zDb==0 ){
+ zDb = zDestFile;
+ zDestFile = azArg[j];
+ }else{
+ return DCR_TooMany|j;
+ }
}
- if( zExists==0 ) rc = SQLITE_NOMEM;
- for(i=0; i<pAr->nArg && rc==SQLITE_OK; i++){
- char *zSql2 = sqlite3_mprintf(zInsertFmt[pAr->bZip], zTab,
- pAr->bVerbose ? "shell_putsnl(name)" : "name",
- pAr->azArg[i], pAr->zDir, zExists);
- rc = arExecSql(pAr, zSql2);
- sqlite3_free(zSql2);
+ if( zDestFile==0 ){
+ return DCR_Missing;
}
-end_ar_transaction:
+ if( zDb==0 ) zDb = "main";
+ rc = sqlite3_open_v2(zDestFile, &pDest,
+ SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs);
if( rc!=SQLITE_OK ){
- sqlite3_exec(pAr->db, "ROLLBACK TO ar; RELEASE ar;", 0, 0, 0);
+ *pzErr = smprintf("cannot open \"%s\"\n", zDestFile);
+ close_db(pDest);
+ return DCR_Error;
+ }
+ if( bAsync ){
+ sqlite3_exec(pDest, "PRAGMA synchronous=OFF; PRAGMA journal_mode=OFF;",
+ 0, 0, 0);
+ }
+ open_db(psx, 0);
+ pBackup = sqlite3_backup_init(pDest, "main", DBX(psx), zDb);
+ if( pBackup==0 ){
+ *pzErr = smprintf("%s failed, %s\n", azArg[0], sqlite3_errmsg(pDest));
+ close_db(pDest);
+ return DCR_Error;
+ }
+ while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){}
+ sqlite3_backup_finish(pBackup);
+ if( rc==SQLITE_DONE ){
+ rc = 0;
+ }else{
+ *pzErr = smprintf("%s\n", sqlite3_errmsg(pDest));
+ rc = 1;
+ }
+ close_db(pDest);
+ return DCR_Ok|rc;
+}
+
+/*
+ * zAutoColumn(zCol, &db, ?) => Maybe init db, add column zCol to it.
+ * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE,
+ * close db and set it to 0, and return the columns spec, to later
+ * be sqlite3_free()'ed by the caller.
+ * The return is 0 when either:
+ * (a) The db was not initialized and zCol==0 (There are no columns.)
+ * (b) zCol!=0 (Column was added, db initialized as needed.)
+ * The 3rd argument, pRenamed, references an out parameter. If the
+ * pointer is non-zero, its referent will be set to a summary of renames
+ * done if renaming was necessary, or set to 0 if none was done. The out
+ * string (if any) must be sqlite3_free()'ed by the caller.
+ */
+#ifdef SHELL_DEBUG
+#define rc_err_oom_die(rc) \
+ if( rc==SQLITE_NOMEM ) shell_check_oom(0); \
+ else if(!(rc==SQLITE_OK||rc==SQLITE_DONE)) \
+ fprintf(STD_ERR,"E:%d\n",rc), assert(0)
+#else
+static void rc_err_oom_die(int rc){
+ if( rc==SQLITE_NOMEM ) shell_check_oom(0);
+ assert(rc==SQLITE_OK||rc==SQLITE_DONE);
+}
+#endif
+
+#ifdef SHELL_COLFIX_DB /* If this is set, the DB can be in a file. */
+static char zCOL_DB[] = SHELL_STRINGIFY(SHELL_COLFIX_DB);
+#else /* Otherwise, memory is faster/better for the transient DB. */
+static const char *zCOL_DB = ":memory:";
+#endif
+
+/* Define character (as C string) to separate generated column ordinal
+ * from protected part of incoming column names. This defaults to "_"
+ * so that incoming column identifiers that did not need not be quoted
+ * remain usable without being quoted. It must be one character.
+ */
+#ifndef SHELL_AUTOCOLUMN_SEP
+# define AUTOCOLUMN_SEP "_"
+#else
+# define AUTOCOLUMN_SEP SHELL_STRINGIFY(SHELL_AUTOCOLUMN_SEP)
+#endif
+
+static char *zAutoColumn(const char *zColNew, sqlite3 **pDb, char **pzRenamed){
+ /* Queries and D{D,M}L used here */
+ static const char * const zTabMake = "\
+CREATE TABLE ColNames(\
+ cpos INTEGER PRIMARY KEY,\
+ name TEXT, nlen INT, chop INT, reps INT, suff TEXT);\
+CREATE VIEW RepeatedNames AS \
+SELECT DISTINCT t.name FROM ColNames t \
+WHERE t.name COLLATE NOCASE IN (\
+ SELECT o.name FROM ColNames o WHERE o.cpos<>t.cpos\
+);\
+";
+ static const char * const zTabFill = "\
+INSERT INTO ColNames(name,nlen,chop,reps,suff)\
+ VALUES(iif(length(?1)>0,?1,'?'),max(length(?1),1),0,0,'')\
+";
+ static const char * const zHasDupes = "\
+SELECT count(DISTINCT (substring(name,1,nlen-chop)||suff) COLLATE NOCASE)\
+ <count(name) FROM ColNames\
+";
+#ifdef SHELL_COLUMN_RENAME_CLEAN
+ static const char * const zDedoctor = "\
+UPDATE ColNames SET chop=iif(\
+ (substring(name,nlen,1) BETWEEN '0' AND '9')\
+ AND (rtrim(name,'0123456790') glob '*"AUTOCOLUMN_SEP"'),\
+ nlen-length(rtrim(name, '"AUTOCOLUMN_SEP"0123456789')),\
+ 0\
+)\
+";
+#endif
+ static const char * const zSetReps = "\
+UPDATE ColNames AS t SET reps=\
+(SELECT count(*) FROM ColNames d \
+ WHERE substring(t.name,1,t.nlen-t.chop)=substring(d.name,1,d.nlen-d.chop)\
+ COLLATE NOCASE\
+)\
+";
+#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+ static const char * const zColDigits = "\
+SELECT CAST(ceil(log(count(*)+0.5)) AS INT) FROM ColNames \
+";
+#endif
+ static const char * const zRenameRank =
+#ifdef SHELL_COLUMN_RENAME_CLEAN
+ "UPDATE ColNames AS t SET suff="
+ "iif(reps>1, printf('%c%0*d', '"AUTOCOLUMN_SEP"', $1, cpos), '')"
+#else /* ...RENAME_MINIMAL_ONE_PASS */
+"WITH Lzn(nlz) AS (" /* Find minimum extraneous leading 0's for uniqueness */
+" SELECT 0 AS nlz"
+" UNION"
+" SELECT nlz+1 AS nlz FROM Lzn"
+" WHERE EXISTS("
+" SELECT 1"
+" FROM ColNames t, ColNames o"
+" WHERE"
+" iif(t.name IN (SELECT * FROM RepeatedNames),"
+" printf('%s"AUTOCOLUMN_SEP"%s',"
+" t.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,t.cpos),2)),"
+" t.name"
+" )"
+" ="
+" iif(o.name IN (SELECT * FROM RepeatedNames),"
+" printf('%s"AUTOCOLUMN_SEP"%s',"
+" o.name, substring(printf('%.*c%0.*d',nlz+1,'0',$1,o.cpos),2)),"
+" o.name"
+" )"
+" COLLATE NOCASE"
+" AND o.cpos<>t.cpos"
+" GROUP BY t.cpos"
+" )"
+") UPDATE Colnames AS t SET"
+" chop = 0," /* No chopping, never touch incoming names. */
+" suff = iif(name IN (SELECT * FROM RepeatedNames),"
+" printf('"AUTOCOLUMN_SEP"%s', substring("
+" printf('%.*c%0.*d',(SELECT max(nlz) FROM Lzn)+1,'0',1,t.cpos),2)),"
+" ''"
+" )"
+#endif
+ ;
+ static const char * const zCollectVar = "\
+SELECT\
+ '('||x'0a'\
+ || group_concat(\
+ cname||' TEXT',\
+ ','||iif((cpos-1)%4>0, ' ', x'0a'||' '))\
+ ||')' AS ColsSpec \
+FROM (\
- SELECT cpos, printf('\"%w\"',printf('%.*s%s', nlen-chop,name,suff)) AS cname \
++ SELECT cpos, printf('\"%w\"',printf('%!.*s%s', nlen-chop,name,suff)) AS cname \
+ FROM ColNames ORDER BY cpos\
+)";
+ static const char * const zRenamesDone =
+ "SELECT group_concat("
- " printf('\"%w\" to \"%w\"',name,printf('%.*s%s', nlen-chop, name, suff)),"
++ " printf('\"%w\" to \"%w\"',name,printf('%!.*s%s', nlen-chop, name, suff)),"
+ " ','||x'0a')"
+ "FROM ColNames WHERE suff<>'' OR chop!=0"
+ ;
+ int rc;
+ sqlite3_stmt *pStmt = 0;
+ assert(pDb!=0);
+ if( zColNew ){
+ /* Add initial or additional column. Init db if necessary. */
+ if( *pDb==0 ){
+ if( SQLITE_OK!=sqlite3_open(zCOL_DB, pDb) ) return 0;
+#ifdef SHELL_COLFIX_DB
+ if(*zCOL_DB!=':')
+ sqlite3_exec(*pDb,"drop table if exists ColNames;"
+ "drop view if exists RepeatedNames;",0,0,0);
+#endif
+ rc = sqlite3_exec(*pDb, zTabMake, 0, 0, 0);
+ rc_err_oom_die(rc);
+ }
+ assert(*pDb!=0);
+ rc = sqlite3_prepare_v2(*pDb, zTabFill, -1, &pStmt, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_bind_text(pStmt, 1, zColNew, -1, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_step(pStmt);
+ rc_err_oom_die(rc);
+ sqlite3_finalize(pStmt);
+ return 0;
+ }else if( *pDb==0 ){
+ return 0;
}else{
- rc = arExecSql(pAr, "RELEASE ar;");
- if( pAr->bZip && pAr->zFile ){
- zSql = sqlite3_mprintf("DROP TABLE %s", zTemp);
- arExecSql(pAr, zSql);
- sqlite3_free(zSql);
+ /* Formulate the columns spec, close the DB, zero *pDb. */
+ char *zColsSpec = 0;
+ int hasDupes = db_int(*pDb, zHasDupes);
+#ifdef SQLITE_ENABLE_MATH_FUNCTIONS
+ int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
+#else
+# define nDigits 2
+#endif
+ if( hasDupes ){
+#ifdef SHELL_COLUMN_RENAME_CLEAN
+ rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
+ rc_err_oom_die(rc);
+#endif
+ rc = sqlite3_exec(*pDb, zSetReps, 0, 0, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_prepare_v2(*pDb, zRenameRank, -1, &pStmt, 0);
+ rc_err_oom_die(rc);
+ sqlite3_bind_int(pStmt, 1, nDigits);
+ rc = sqlite3_step(pStmt);
+ sqlite3_finalize(pStmt);
+ assert(rc==SQLITE_DONE);
}
- }
- sqlite3_free(zExists);
- return rc;
-}
-
-/*
-** Implementation of ".ar" dot command.
-*/
-static int arDotCommand(
- ShellState *pState, /* Current shell tool state */
- int fromCmdLine, /* True if -A command-line option, not .ar cmd */
- char **azArg, /* Array of arguments passed to dot command */
- int nArg /* Number of entries in azArg[] */
-){
- ArCommand cmd;
- int rc;
- memset(&cmd, 0, sizeof(cmd));
- cmd.fromCmdLine = fromCmdLine;
- rc = arParseCommand(azArg, nArg, &cmd);
- if( rc==SQLITE_OK ){
- int eDbType = SHELL_OPEN_UNSPEC;
- cmd.p = pState;
- cmd.db = pState->db;
- if( cmd.zFile ){
- eDbType = deduceDatabaseType(cmd.zFile, 1);
+ /* This assert is maybe overly cautious for above de-dup DML, but that can
+ * be replaced via #define's. So this check is made for debug builds. */
+ assert(db_int(*pDb, zHasDupes)==0);
+ rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
+ rc_err_oom_die(rc);
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_ROW ){
+ zColsSpec = smprintf("%s", sqlite3_column_text(pStmt, 0));
}else{
- eDbType = pState->openMode;
- }
- if( eDbType==SHELL_OPEN_ZIPFILE ){
- if( cmd.eCmd==AR_CMD_EXTRACT || cmd.eCmd==AR_CMD_LIST ){
- if( cmd.zFile==0 ){
- cmd.zSrcTable = sqlite3_mprintf("zip");
- }else{
- cmd.zSrcTable = sqlite3_mprintf("zipfile(%Q)", cmd.zFile);
- }
- }
- cmd.bZip = 1;
- }else if( cmd.zFile ){
- int flags;
- if( cmd.bAppend ) eDbType = SHELL_OPEN_APPENDVFS;
- if( cmd.eCmd==AR_CMD_CREATE || cmd.eCmd==AR_CMD_INSERT
- || cmd.eCmd==AR_CMD_REMOVE || cmd.eCmd==AR_CMD_UPDATE ){
- flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
- }else{
- flags = SQLITE_OPEN_READONLY;
- }
- cmd.db = 0;
- if( cmd.bDryRun ){
- utf8_printf(pState->out, "-- open database '%s'%s\n", cmd.zFile,
- eDbType==SHELL_OPEN_APPENDVFS ? " using 'apndvfs'" : "");
- }
- rc = sqlite3_open_v2(cmd.zFile, &cmd.db, flags,
- eDbType==SHELL_OPEN_APPENDVFS ? "apndvfs" : 0);
- if( rc!=SQLITE_OK ){
- utf8_printf(stderr, "cannot open file: %s (%s)\n",
- cmd.zFile, sqlite3_errmsg(cmd.db)
- );
- goto end_ar_command;
- }
- sqlite3_fileio_init(cmd.db, 0, 0);
- sqlite3_sqlar_init(cmd.db, 0, 0);
- sqlite3_create_function(cmd.db, "shell_putsnl", 1, SQLITE_UTF8, cmd.p,
- shellPutsFunc, 0, 0);
-
+ zColsSpec = 0;
}
- if( cmd.zSrcTable==0 && cmd.bZip==0 && cmd.eCmd!=AR_CMD_HELP ){
- if( cmd.eCmd!=AR_CMD_CREATE
- && sqlite3_table_column_metadata(cmd.db,0,"sqlar","name",0,0,0,0,0)
- ){
- utf8_printf(stderr, "database does not contain an 'sqlar' table\n");
- rc = SQLITE_ERROR;
- goto end_ar_command;
+ if( pzRenamed!=0 ){
+ if( !hasDupes ) *pzRenamed = 0;
+ else{
+ sqlite3_finalize(pStmt);
+ if( SQLITE_OK==sqlite3_prepare_v2(*pDb, zRenamesDone, -1, &pStmt, 0)
+ && SQLITE_ROW==sqlite3_step(pStmt) ){
+ *pzRenamed = smprintf("%s", sqlite3_column_text(pStmt, 0));
+ }else
+ *pzRenamed = 0;
}
- cmd.zSrcTable = sqlite3_mprintf("sqlar");
}
+ sqlite3_finalize(pStmt);
+ sqlite3_close(*pDb);
+ *pDb = 0;
+ return zColsSpec;
+ }
+}
- switch( cmd.eCmd ){
- case AR_CMD_CREATE:
- rc = arCreateOrUpdateCommand(&cmd, 0, 0);
- break;
+static FILE *currentOutputFile(ShellExState *p){
+ return ISS(p)->out;
+}
- case AR_CMD_EXTRACT:
- rc = arExtractCommand(&cmd);
- break;
+#if SHELL_DYNAMIC_EXTENSION
+/* Ensure there is room in loaded extension info list for one being loaded.
+ * On exit, psi->ixExtPending can be used to index into psi->pShxLoaded.
+ */
+static ShExtInfo *pending_ext_info(ShellInState *psi){
+ int ixpe = psi->ixExtPending;
+ assert(ixpe!=0);
+ if( ixpe >= psi->numExtLoaded ){
+ psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
+ (ixpe+1)*sizeof(ShExtInfo));
+ shell_check_oom(psi->pShxLoaded);
+ ++psi->numExtLoaded;
+ memset(psi->pShxLoaded+ixpe, 0, sizeof(ShExtInfo));
+ }
+ return &psi->pShxLoaded[ixpe];
+}
+
+/* Register a meta-command, to be called during extension load/init. */
+static int register_meta_command(ShellExState *p,
+ ExtensionId eid, MetaCommand *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->numMetaCommands;
+ int rc;
+ if( psei->extId!=0 && psei->extId!=eid ) return SQLITE_MISUSE;
+ psei->extId = eid;
+ rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ) return rc;
+ psei->ppMetaCommands
+ = sqlite3_realloc(psei->ppMetaCommands, (nc+1)*sizeof(MetaCommand *));
+ shell_check_oom(psei->ppMetaCommands);
+ 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->ppMetaCommands[nc++] = pMC;
+ psei->numMetaCommands = nc;
+ notify_subscribers(psi, NK_NewDotCommand, pMC);
+ if( strcmp("unknown", zName)==0 ){
+ psi->pUnknown = pMC;
+ psei->pUnknown = pMC;
+ }
+ return SQLITE_OK;
+ }else{
+ psei->ppMetaCommands[nc] = 0;
+ }
+ }
+ return SQLITE_ERROR;
+}
- case AR_CMD_LIST:
- rc = arListCommand(&cmd);
- break;
+/* Register an output data display (or other disposition) mode */
+static int register_exporter(ShellExState *p,
+ ExtensionId eid, ExportHandler *pEH){
+ return SQLITE_ERROR;
+}
- case AR_CMD_HELP:
- arUsage(pState->out);
- break;
+/* Register an import variation from (various sources) for .import */
+static int register_importer(ShellExState *p,
+ ExtensionId eid, ImportHandler *pIH){
+ return SQLITE_ERROR;
+}
- case AR_CMD_INSERT:
- rc = arCreateOrUpdateCommand(&cmd, 1, 0);
- break;
+/* 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;
+}
- case AR_CMD_REMOVE:
- rc = arRemoveCommand(&cmd);
- break;
+/* See registerAdHocCommand API in shext_linkage.h re detailed behavior.
+ * Depending on zHelp==0, either register or unregister ad-hoc treatment
+ * of zName for this extension (identified by eid.)
+ */
+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;
- default:
- assert( cmd.eCmd==AR_CMD_UPDATE );
- rc = arCreateOrUpdateCommand(&cmd, 1, 1);
- break;
- }
+ assert(psi->pShxLoaded!=0 && p->dbShell!=0);
+ for( ie=psi->numExtLoaded-1; ie>0; --ie ){
+ if( psi->pShxLoaded[ie].extId==eid ) break;
}
-end_ar_command:
- if( cmd.db!=pState->db ){
- close_db(cmd.db);
+ if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE;
+ rc = sqlite3_prepare_v2(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);
+ const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : "";
+ sqlite3_bind_text(pStmt, 3, zHelp, -1, 0);
+ sqlite3_bind_text(pStmt, 4, zLE, -1, 0);
}
- sqlite3_free(cmd.zSrcTable);
-
- return rc;
+ rc = sqlite3_step(pStmt);
+ sqlite3_finalize(pStmt);
+ return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR;
}
-/* End of the ".archive" or ".ar" command logic
-*******************************************************************************/
-#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) */
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
/*
-** If (*pRc) is not SQLITE_OK when this function is called, it is a no-op.
-** Otherwise, the SQL statement or statements in zSql are executed using
-** database connection db and the error code written to *pRc before
-** this function returns.
-*/
-static void shellExec(sqlite3 *db, int *pRc, const char *zSql){
- int rc = *pRc;
- if( rc==SQLITE_OK ){
- char *zErr = 0;
- rc = sqlite3_exec(db, zSql, 0, 0, &zErr);
- if( rc!=SQLITE_OK ){
- raw_printf(stderr, "SQL error: %s\n", zErr);
+ * 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{
+ ++pes;
+ }
}
- sqlite3_free(zErr);
- *pRc = rc;
+ if( psi->numSubscriptions==0 ){
+ sqlite3_free(psi->pSubscriptions);
+ psi->pSubscriptions = 0;
+ }
+ 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;
+ }
+ }
+ assert(pes==pesLim);
+ pes = sqlite3_realloc(pes, (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;
}
}