]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Scripting support roughed in, with a demo extension. More work is needed to make...
authorlarrybr <larrybr@noemail.net>
Wed, 23 Mar 2022 21:03:48 +0000 (21:03 +0000)
committerlarrybr <larrybr@noemail.net>
Wed, 23 Mar 2022 21:03:48 +0000 (21:03 +0000)
FossilOrigin-Name: abf0316b3f58646974ab8e4d3e68896c9fc03bdd338eb7dc7b2f5d4de7365298

ext/misc/tclshext.c.in [new file with mode: 0644]
manifest
manifest.uuid
src/loadext.c
src/shell.c.in
src/shext_linkage.h
src/sqlite3ext.h
tool/mkshellc.tcl [changed mode: 0644->0755]

diff --git a/ext/misc/tclshext.c.in b/ext/misc/tclshext.c.in
new file mode 100644 (file)
index 0000000..c7dba81
--- /dev/null
@@ -0,0 +1,350 @@
+/*
+** 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
+** Later TCL versions can be used if desired.
+**
+** This extension adds two features to the host shell:
+** 1. The .tcl dot command is added.
+** 2. TCL scripting support is added.
+**
+** The .tcl command can be run with 0 or more arguments.
+** With no arguments, it does a REPL loop until the end of input is seen.
+** The end of input is either an EOF condition or a lone '.' on a line.
+** With more arguments, files they name are interpreted as TCL script.
+** In either case, the TCL command return code is tranlated to a DotCmdRC.
+**
+** TCL scripting support is added with a ShellExtensionAPI hookScripting()
+** call in the manner documented for it and the ScriptHooks struct. This
+** support lasts until the extension destructor is called. Until then,
+** shell input groups beginning with ".." are treated as TCL input, one
+** complete TCL command at a time.
+**
+** 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 a useful set of user-defined shell enhancements.
+*/
+
+#include <limits.h>
+#include "shext_linkage.h"
+
+static struct ShExtAPI *pShExtApi = 0;
+static struct ExtHelpers *pExtHelpers = 0;
+
+SQLITE_EXTENSION_INIT1;
+
+/* This is not found in the API pointer table published for extensions: */
+#define sqlite3_enable_load_extension pExtHelpers->enable_load_extension
+
+/* Control how tclsqlite.c compiles. (REPL is effected here, not there.) */
+#undef SQLITE_AMALGAMATION
+#undef TCLSH
+#include <tclOO.h>
+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;
+
+static void TclCmd_Takedown(TclCmd *ptc);
+
+/* These DERIVED_METHOD(...) macro calls' arguments were copied and
+ * pasted from the MetaCommand interface declaration in shext_linkage.h
+ */
+DERIVED_METHOD(void, destruct, MetaCommand,TclCmd, 0, ()){
+  TclCmd_Takedown((TclCmd *)pThis);
+}
+
+DERIVED_METHOD(const char *, name, MetaCommand,TclCmd, 0,()){
+  return "tcl";
+}
+
+DERIVED_METHOD(const char *, help, MetaCommand,TclCmd, 1,(int more)){
+  switch( more ){
+  case 0: return
+      ".tcl ?FILES?   Run a TCL REPL or interpret files as TCL\n";
+  case 1: 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 on an input line or end-of-stream is encountered.\n";
+  default: return 0;
+  }
+}
+
+DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,TclCmd, 3,
+             (char **pzErrMsg, int nArgs, char *azArgs[])){
+  return DCR_Ok;
+}
+
+static Tcl_Interp *getInterp(TclCmd *ptc);
+
+static void copy_complaint(char **pzErr, Tcl_Interp *pi){
+  if( pzErr ){
+    Tcl_Obj *po = Tcl_GetObjResult(pi);
+    *pzErr = sqlite3_mprintf("%s", Tcl_GetStringFromObj(po,0));
+  }
+}
+
+DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4,
+             (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
+  FILE *out = pExtHelpers->currentOutputFile(psx);
+  TclCmd *ptc = (TclCmd *)pThis;
+  DotCmdRC rv = DCR_Ok;
+  int rc = TCL_OK;
+  if( nArgs>1 ){
+    /* Read named files into the interpreter. */
+    int aix;
+    for( aix=0; aix<(nArgs-1) && rc==TCL_OK; ++aix ){
+      rc = Tcl_EvalFile(getInterp(ptc), azArgs[aix+1]);
+    }
+  }else{
+    /* Enter a REPL */
+    static const char * const zREPL =
+#ifdef REPL_STDIN_ONLY
+      "set line {}\n"
+      "while {![eof stdin]} {\n"
+        "if {$line!=\"\"} {\n"
+          "puts -nonewline \"> \"\n"
+        "} else {\n"
+          "puts -nonewline \"% \"\n"
+        "}\n"
+        "flush stdout\n"
+        "append line [gets stdin]\n"
+        "if {$line eq \".\"} break\n"
+        "if {[info complete $line]} {\n"
+          "if {[catch {uplevel #0 $line} result]} {\n"
+            "puts stderr \"Error: $result\"\n"
+          "} elseif {$result!=\"\"} {\n"
+            "puts $result\n"
+          "}\n"
+          "set line {}\n"
+        "} else {\n"
+          "append line \\n\n"
+        "}\n"
+      "}\n"
+      "if {$line ne \".\"} {puts {}}\n"
+      "read stdin 0\n"
+#else
+      "set line {}\n"
+      "set at_end 0\n"
+      "set prompting [now_interactive]\n"
+      "while {!$at_end} {\n"
+        "if {$prompting} {\n"
+          "if {$line!=\"\"} {\n"
+            "puts -nonewline \"> \"\n"
+          "} else {\n"
+            "puts -nonewline \"% \"\n"
+          "}\n"
+        "}\n"
+        "flush stdout\n"
+        "set li [get_input_line]\n"
+        "if {$li eq \"\"} {\n"
+          "set at_end 1\n"
+        "} elseif {[string trimright $li] eq \".\"} {\n"
+          "if {$line ne \"\"} {\n"
+            "throw {NONE} {incomplete input at EOF}\n"
+          "}\n"
+          "set at_end 1\n"
+        "} else {\n" 
+          "append line $li\n"
+          "if {[string trim $line] eq \"\"} {\n"
+            "set line \"\"\n"
+            "continue\n"
+          "}\n" 
+          "if {[info complete $line]} {\n"
+            "if {[catch {uplevel #0 $line} result]} {\n"
+              "puts stderr \"Error: $result\"\n"
+            "} elseif {$result!=\"\" && $prompting} {\n"
+              "puts $result\n"
+            "}\n"
+            "set line {}\n"
+          "}\n"
+        "}\n"
+      "}\n"
+      "if {$prompting && $li ne \".\\n\"} {puts {}}\n"
+      "unset li line prompting at_end\n"
+      "read stdin 0\n"
+#endif
+      ;
+    rc = Tcl_Eval(getInterp(ptc), zREPL);
+    clearerr(stdin);
+  }
+  if( rc!=TCL_OK ){
+    copy_complaint(pzErrMsg, getInterp(ptc));
+    rv = DCR_Error;
+  }
+  return rv;
+}
+
+/* Define a MetaCommand v-table initialized to reference above methods. */
+MetaCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods);
+
+INSTANCE_BEGIN(TclCmd);
+  Tcl_Interp *interp;
+INSTANCE_END(TclCmd) tclcmd = {
+  &tclcmd_methods
+  , 0 /* interp pointer */
+};
+
+static Tcl_Interp *getInterp(TclCmd *ptc){
+  return ptc->interp;
+}
+
+static void TclCmd_Takedown(TclCmd *ptc){
+  Tcl_DeleteInterp(ptc->interp);
+  ptc->interp = 0;
+  Tcl_Finalize();
+}
+
+/* Say line is script lead-in iff its first dark is "..". */
+static int tclIsScriptLead(void *pvState, const char *zLineLead){
+  char c;
+  (void)(pvState);
+  while( (c=*zLineLead++) && (c==' '||c=='\t') ) {}
+  return (c=='.' && *zLineLead=='.');
+}
+
+static int tclIsComplete(void *pvState, const char *zScript){
+  (void)(pvState);
+  return Tcl_CommandComplete(zScript);
+}
+
+static DotCmdRC tclRunScript(void *pvState, const char *zScript,
+                             ShellExState *p, char **pzErrMsg){
+  char c;
+  TclCmd *ptc = (TclCmd *)pvState;
+  while( (c=*zScript++) && (c==' '||c=='\t') ) {}
+  if( c=='.' &&  *zScript++=='.' ){
+    int rc, nc = strlen(zScript);
+    rc = Tcl_EvalEx(ptc->interp, zScript, nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
+    if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp(ptc));
+    return DCR_Ok|(rc!=TCL_OK);
+  }
+  return DCR_Error;
+}
+
+#define GETLINE_MAXLEN 1000
+
+/* C implementation of TCL proc, get_input_line */
+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 = pExtHelpers->currentInputSource(psx);
+    if( pExtHelpers->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;
+  }
+}
+
+/* C implementation of TCL proc, now_interactive */
+int nowInteractive(void *pvSS, Tcl_Interp *interp,
+                 int nArgs, const char *azArgs[]){
+  if( nArgs==1 ){
+    ShellExState *psx = (ShellExState *)pvSS;
+    struct InSource *pis = pExtHelpers->currentInputSource(psx);
+    static const char * zAns[2] = { "0","1" };
+    int iiix = (pExtHelpers->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;
+  }
+}
+
+/* ToDo: C implementation of TCL ::unknown to reflect to dot commands */
+
+/* ToDo: ... TCL db_user command, like a (TCL) sqlite3 object except that
+ * it defers to shell's db and treats close subcommand as an error. */
+
+DEFINE_SHDB_TO_SHEXTLINK(shext_link);
+
+/*
+** Extension load function.
+*/
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_tclshext_init(
+  sqlite3 *db,
+  char **pzErrMsg,
+  const sqlite3_api_routines *pApi
+){
+  ShellExtensionLink *pShExtLink;
+  SQLITE_EXTENSION_INIT2(pApi);
+  pShExtLink = shext_link(db);
+  if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=1 ){
+    ShellExState *psx = pShExtLink->pSXS;
+    MetaCommand *pmc = (MetaCommand *)&tclcmd;
+    const char *zShellName, *zShellDir;
+    int rc;
+
+    pShExtApi = & pShExtLink->pShellExtensionAPI->api.named;
+    pExtHelpers = & pShExtLink->pShellExtensionAPI->pExtHelpers->helpers.named;
+    if( pShExtLink->pShellExtensionAPI->pExtHelpers->helperCount < 10 ){
+      *pzErrMsg = sqlite3_mprintf("Shell version mismatch");
+      return SQLITE_ERROR;
+    }
+    zShellName = pExtHelpers->shellInvokedAs();
+    zShellDir = pExtHelpers->shellStartupDir();
+    if( zShellDir!=0 ){
+      char cwd[FILENAME_MAX+1];
+      if( getDir(cwd) && 0==chdir(zShellDir) ){
+        Tcl_FindExecutable(zShellName);
+        rc = chdir(cwd); /* result ignored, kept only to silence gcc */
+      }
+    }
+    tclcmd.interp = Tcl_CreateInterp();
+    if( 0==Tcl_OOInitStubs(tclcmd.interp) ){
+      *pzErrMsg = sqlite3_mprintf("Tcl v8.6 or higher required.\n");
+      TclCmd_Takedown(&tclcmd);
+      return SQLITE_ERROR;
+    }
+    Tcl_SetSystemEncoding(tclcmd.interp, "utf-8");
+    Sqlite3_Init(tclcmd.interp);
+    rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init, pmc);
+    if( rc==SQLITE_OK ){
+      ScriptHooks sh = { pmc, tclIsScriptLead, tclIsComplete, tclRunScript };
+      pShExtApi->hookScripting(psx, sqlite3_tclshext_init, &sh);
+      Tcl_CreateCommand(tclcmd.interp,
+                        "get_input_line", getInputLine, psx, 0);
+      Tcl_CreateCommand(tclcmd.interp,
+                        "now_interactive", nowInteractive, psx, 0);
+      pShExtLink->eid = sqlite3_tclshext_init;
+    }else{
+      TclCmd_Takedown(&tclcmd);
+    }
+    return rc;
+  }
+  else{
+    *pzErrMsg
+      = sqlite3_mprintf("Bad ShellExtensionLink or registration API.\n");
+    return SQLITE_ERROR;
+  }
+}
index 9ca7448206bd2df6bcaa207bce05b0aba6a3130d..87bbba5b5a0ae947630729176a79fa3bc9fc45c4 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C macro-ize\sextension\sboiler-plate,\simprove\sexit\sprocessing
-D 2022-03-19T14:27:57.463
+C Scripting\ssupport\sroughed\sin,\swith\sa\sdemo\sextension.\sMore\swork\sis\sneeded\sto\smake\sthis\struly\suseful.\sTests\sare\smissing.
+D 2022-03-23T21:03:48.356
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -329,6 +329,7 @@ F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f
 F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6
 F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a
+F ext/misc/tclshext.c.in 9e8909361fb8e26488920acd6629753b5bf38541cd0c4e6a16f6532b7bf367c6
 F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
 F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@ -516,7 +517,7 @@ F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
 F src/insert.c d9fd15b2bd030cb9bd3119b301dbdb2912f16fff76c6e3797296cfd1500faaf4
 F src/json.c 24fcd7f5f9080b04b89722c343010d390f85e55b2ab560046cb567c9dd640f62
 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
-F src/loadext.c aa919a6a7884f8b34d7b791841b24d14b1b0ab43f45b3940f4851043b2855c0c
+F src/loadext.c 2ecb1441f9b1c22e9e022ee0776e67d259facf34b56ba892b206f0a294ee6f8c
 F src/main.c 89dfd569b4fbcab65281b3c6d636b887b2cb23cbaa16f8c6b67062862144c927
 F src/malloc.c fec841aa0a0400a6f7d20706178a5d8e8219a6bf562b6fe712c17f6c26813266
 F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
@@ -555,11 +556,11 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 4890a3cfee0bc60ff231c3a44db37968859ab0be156983dbcc0c096109832cdd
-F src/shell.c.in acb97f8ad071498a35748d31b86d5797745ba236e042d54ae31adb8c23a27581
-F src/shext_linkage.h 6c75ac9690965ae6968b5b246f83bde6bac9f13a248e5ac0f5775d2ad8a35496
+F src/shell.c.in 840a4a72dcc39fafbcc82275babc77f928c3531cb5fe7217cb0a1596ef0a4555
+F src/shext_linkage.h 71b3600ba0e20f696fb226547e99413c67cfb27c5532701df16935e2a45c152a
 F src/sqlite.h.in 5845213799feca09cd69d18ff841a85fe0df31021f46aaa1797e703e80dc1d70
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
-F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6
+F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
 F src/sqliteInt.h 2ce7d868630ccd70ffd4b15d46b59ccf7daf89198993b62ed6e4a165d3511280
 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
@@ -1883,7 +1884,7 @@ F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61
 F tool/mkopcodeh.tcl 5dab48c49a25452257494e9601702ab63adaba6bd54a9b382615fa52661c8f8c
 F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
 F tool/mkpragmatab.tcl bd07bd59d45d0f3448e123d6937e9811195f9908a51e09d774609883055bfd3d
-F tool/mkshellc.tcl 8231651ce215baea7d003d7489d962d0dc286cbabc41a858790dd188fe889651
+F tool/mkshellc.tcl a8db284614942c48e35e58f2a3a2e762f3bae0262e81acd1dbfc4813fd304d85 x
 F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97
 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f
@@ -1949,8 +1950,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 898088008e2d15f89941db433f743594aea1351f353c12ce3932a39902dfb161
-R 129774c9c2e8cac58d8036d38701569e
+P 7996d3a359ab90ef9f42f501e0ddb2efa964a906739116836cb1eafe2a96a0ed
+Q +da874180d35aacdeb9c06f5b425e8909d833e2765179c4337854d56b8a624fd5
+R c463fc7c0074cded5ea87643f5ddeef3
 U larrybr
-Z 76b79faefcf881b072c961370ce1a08d
+Z cf7f7bb9e5e070c2442801336636b4c2
 # Remove this line to create a well-formed Fossil manifest.
index 9c7218fc0bd5600e9f5a2238d014d433553d0922..3924874b4312b3bec82e92a929f73eb8da251d29 100644 (file)
@@ -1 +1 @@
-7996d3a359ab90ef9f42f501e0ddb2efa964a906739116836cb1eafe2a96a0ed
\ No newline at end of file
+abf0316b3f58646974ab8e4d3e68896c9fc03bdd338eb7dc7b2f5d4de7365298
\ No newline at end of file
index 603516e18adcc05eea78c7acedc9e45d7e7f502c..cefe2eb94cf4869a370b45b3772dfe58033782d4 100644 (file)
@@ -491,7 +491,15 @@ static const sqlite3_api_routines sqlite3Apis = {
   sqlite3_vtab_distinct,
   sqlite3_vtab_in,
   sqlite3_vtab_in_first,
-  sqlite3_vtab_in_next
+  sqlite3_vtab_in_next,
+  /* Version 3.39.0 and later */
+#ifndef SQLITE_OMIT_DESERIALIZE
+  sqlite3_deserialize,
+  sqlite3_serialize
+#else
+  0,
+  0
+#endif
 };
 
 /* True if x is the directory separator character
index c8e80184b0b72f36ed20e8d25f9300643e751e65..566bace22bf0e8898e425ae11c40e0e1d6e2ab19 100644 (file)
@@ -456,6 +456,12 @@ static volatile int seenInterrupt = 0;
 ** in a number of other places, mostly for error messages.
 */
 static char *Argv0;
+static char startupDir[PATH_MAX+1] = {0};
+#if defined(_WIN32) || defined(WIN32)
+# define initStartupDir() _getwd(startupDir)
+#else
+# define initStartupDir() getcwd(startupDir, sizeof(startupDir))
+#endif
 
 /*
 ** Prompt strings. Initialized in main. Settable with
@@ -1229,6 +1235,7 @@ struct EQPGraph {
 typedef struct ShExtInfo {
   ExtensionId extId;        /* The xInit function pointer */
   void (*extDtor)(void *);  /* Extension shutdown on exit or unload */
+  void *pvExtObj;           /* Passed to extDtor(...) at shutdown */
   /* Each shell extension library registers 0 or more of its extension
    * implementations, interfaces to which are kept in below dynamic.
    * arrays. The dbShell DB keeps indices into these arrays and into
@@ -1367,7 +1374,11 @@ typedef struct ShellInState {
 #if SHELL_DYNAMIC_EXTENSION
   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  */
+  ScriptHooks scripting; /* Hooks for scripting support from loaded extension */
+  ExtensionId scriptXid; /* Id of extension which has set scripting hooks */
 #endif
+
   ShellExState *pSXS;    /* Pointer to companion, exposed shell state */
 } ShellInState;
 
@@ -7687,38 +7698,41 @@ FROM (\
 }
 
 #if SHELL_DYNAMIC_EXTENSION
-/* Register a meta-command */
+/* 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 = 0;
+  ShExtInfo *psei = pending_ext_info(psi);
   const char *zSql
     = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, ?, ?)";
-  int nle = psi->numExtLoaded;
-  int ie;
+  int ie = psi->ixExtPending;
+  assert(psi->pShxLoaded!=0 && p->dbShell!=0);
   if( pMC==0 ) return SQLITE_ERROR;
-  assert(psi->pShxLoaded!=0 && nle>0 && p->dbShell!=0);
-  for( ie=0; ie<nle; ++ie ){
-    if( psi->pShxLoaded[ie].extId==eid ){
-      psei = &psi->pShxLoaded[ie];
-      break;
-    }
-  }
-  if( psei==0 ){
-    ShExtInfo sei = {eid,0};
-    psi->pShxLoaded = sqlite3_realloc(psi->pShxLoaded,
-                                      (nle+1)*sizeof(ShExtInfo));
-    shell_check_oom(psi->pShxLoaded);
-    psei = &psi->pShxLoaded[psi->numExtLoaded];
-    psi->pShxLoaded[psi->numExtLoaded++] = sei;
-    ie = nle;
-  }
-  {
+  else{
     const char *zName = pMC->pMethods->name(pMC);
     sqlite3_stmt *pStmt;
-    int rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0);
     int nc = psei->numMetaCommands;
-    if( rc!=SQLITE_OK ) return SQLITE_ERROR;
+    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);
@@ -7750,6 +7764,22 @@ static int register_importer(ShellExState *p,
   return SQLITE_ERROR;
 }
 
+static int hook_scripting(ShellExState *p, ExtensionId eid, ScriptHooks *pSH){
+  ShellInState *psi = ISS(p);
+  if( psi->scriptXid!=0 ){
+    /* Scripting support already provided. Only one provider is allowed. */
+    return SQLITE_BUSY;
+  }
+  if( eid==0 || pSH==0 || psi->ixExtPending==0
+      || pSH->scriptIsComplete==0 || pSH->runScript==0 ){
+    return SQLITE_MISUSE;
+  }
+  psi->scripting = *pSH;
+  psi->scriptXid = eid;
+  return SQLITE_OK;
+}
+
+
 static FILE *currentOutputFile(ShellExState *p){
   return ISS(p)->out;
 }
@@ -7762,11 +7792,19 @@ static int nowInteractive(ShellExState *p){
   return INSOURCE_IS_INTERACTIVE(ISS(p)->pInSource);
 }
 
+static const char *shellInvokedAs(void){
+  return Argv0;
+}
+
+static const char *shellStartupDir(void){
+  return startupDir;
+}
+
 static void setColumnWidths(ShellExState *p, char *azWidths[], int nWidths);
 static MetaCommand * findMetaCommand(const char *, ShellExState *, int *);
 
 static ExtensionHelpers extHelpers = {
-  7,
+  10,
   {
     failIfSafeMode,
     currentOutputFile,
@@ -7775,15 +7813,19 @@ static ExtensionHelpers extHelpers = {
     findMetaCommand,
     setColumnWidths,
     nowInteractive,
+    shellInvokedAs,
+    shellStartupDir,
+    sqlite3_enable_load_extension,
     0
   }
 };
 
 static ShellExtensionAPI shellExtAPI = {
-  &extHelpers, 3, {
+  &extHelpers, 4, {
     register_meta_command,
     register_out_mode,
     register_importer,
+    hook_scripting,
     0
   }
 };
@@ -7891,34 +7933,49 @@ static int begin_db_dispatch(ShellExState *psx){
   return SQLITE_OK;
 }
 
+/* Call one loaded extension's destructors, in reverse order
+ * of their objects' creation, then free the tracking dyna-arrays.
+ */
+static void free_one_shext_tracking(ShExtInfo *psei){
+  int j;
+  if( psei->ppMetaCommands!=0 ){
+    for( j=psei->numMetaCommands; j>0; --j ){
+      MetaCommand *pmc = psei->ppMetaCommands[j-1];
+      if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
+    }
+    sqlite3_free(psei->ppMetaCommands);
+  }
+  if( psei->ppOutModeHandlers!=0 ){
+    for( j=psei->numOutModeHandlers; j>0; --j ){
+      OutModeHandler *pomh = psei->ppOutModeHandlers[j-1];
+      if( pomh->pMethods->destruct!=0 ) pomh->pMethods->destruct(pomh);
+    }
+    sqlite3_free(psei->ppOutModeHandlers);
+  }
+  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);
+    }
+    sqlite3_free(psei->ppImportHandlers);
+  }
+  if( psei->extDtor!=0 ){
+    psei->extDtor(psei->pvExtObj);
+  }
+}
+
 /* Call all existent loaded extension destructors, in reverse order
  * of their objects' creation, then free the tracking dyna-arrays.
  */
-static void free_shext_tracking(ShellInState *psi){
-  int i, j;
+static void free_all_shext_tracking(ShellInState *psi){
   if( psi->pShxLoaded!=0 ){
-    for( i=psi->numExtLoaded; i>0; --i ){
-      ShExtInfo *psei = &psi->pShxLoaded[i-1];
-      if( psei->ppMetaCommands!=0 ){
-        for( j=psei->numMetaCommands; j>0; --j ){
-          MetaCommand *pmc = psei->ppMetaCommands[j-1];
-          if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
-        }
-        sqlite3_free(psei->ppMetaCommands);
-      }
-      if( psei->ppOutModeHandlers!=0 ){
-        for( j=psei->numOutModeHandlers; j>0; --j ){
-          OutModeHandler *pomh = psei->ppOutModeHandlers[j-1];
-          if( pomh->pMethods->destruct!=0 ) pomh->pMethods->destruct(pomh);
-        }
-        sqlite3_free(psei->ppOutModeHandlers);
-      }
-      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);
-        }
-        sqlite3_free(psei->ppImportHandlers);
+    int i = psi->numExtLoaded;
+    while( i>1 ){
+      ShExtInfo *psei = &psi->pShxLoaded[--i];
+      free_one_shext_tracking(psei);
+      if( psi->scriptXid!=0 && psi->scriptXid==psei->extId ){
+        memset(&psi->scripting, 0, sizeof(ScriptHooks));
+        psi->scriptXid = 0;
       }
     }
     sqlite3_free(psi->pShxLoaded);
@@ -7945,14 +8002,20 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
     psx, /* pSXS */
     0,   /* zErrMsg */
     0,   /* ExtensionId */
-    0    /* Extension destructor */
-  };
+    0,   /* Extension destructor */
+    0    /* Extension data ref */
+  }; //extDtor(pvExtObj)
+  ShellInState *psi = ISS(psx);
+  /* save script hooking state for possible fallback if load fails */
+  ScriptHooks shSave = psi->scripting;
+  ExtensionId siSave = psi->scriptXid;
   int rc;
   if( psx->dbShell==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);
@@ -7963,7 +8026,22 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
   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 ){
+      free_one_shext_tracking(psi->pShxLoaded+psi->ixExtPending);
+      --psi->numExtLoaded;
+    }
+    /* And unhook any scripting linkage it might have setup. */
+    psi->scripting = shSave;
+    psi->scriptXid = siSave;
   }
+  psi->ixExtPending = 0;
   return rc!=SQLITE_OK;
 }
 #endif
@@ -8991,7 +9069,7 @@ DISPATCHABLE_COMMAND( help 3 1 3 ){
     if( nArg==3 && strcmp(z, zHelpAll)==0 && strcmp(azArg[2], zHelpAll)==0 ){
       /* Show the undocumented command help */
       zPat = zHelpAll;
-    }else if( strcmp(z,"-a")==0 || strcmp(z,"-all")==0 || strcmp(z,"--all")==0 ){
+    }else if( strcmp(z,"-a")==0 || optionMatch(z, "all") ){
       zPat = "";
     }else{
       zPat = z;
@@ -13755,7 +13833,11 @@ static DotCmdRC process_input(ShellInState *psi){
     /* An ordered enum to record kind of incoming line group. Its ordering
      * means than a value greater than Comment implies something runnable.
      */
-    enum { Tbd = 0, Eof, Comment, Sql, Cmd /*, Tcl */ } inKind = Tbd;
+    enum { Tbd = 0, Eof, Comment, Sql, Cmd
+#if SHELL_DYNAMIC_EXTENSION
+      , Script
+#endif
+    } inKind = Tbd;
     /* An enum signifying the group disposition state */
     enum {
       Incoming, Runnable, Dumpable, Erroneous, Ignore
@@ -13787,26 +13869,35 @@ static DotCmdRC process_input(ShellInState *psi){
         ? skipWhite(zLineInput)-zLineInput
         : 0; /* Disallow leading whitespace for . or # in legacy mode. */
 #endif
-      switch( zLineInput[ndcLeadWhite] ){
-      case '.':
-        inKind = Cmd;
-        dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState);
-        break;
-      case '#':
-        inKind = Comment;
-        break;
-      default:
-        /* Might be SQL, or a swallowable whole SQL comment. */
-        sql_prescan(zLineInput, &sqScanState);
-        if( SSS_PLAINWHITE(sqScanState) ){
-          /* It's either all blank or a whole SQL comment. Swallowable. */
+#if SHELL_DYNAMIC_EXTENSION
+      if( psi->scripting.isScriptLeader!=0
+          && psi->scripting.isScriptLeader(psi->scripting.pvScriptingState,
+                                           zLineInput+ndcLeadWhite) ){
+        inKind = Script;
+      }else
+#endif
+      {
+        switch( zLineInput[ndcLeadWhite] ){
+        case '.':
+          inKind = Cmd;
+          dot_command_scan(zLineInput+ndcLeadWhite, &dcScanState);
+          break;
+        case '#':
           inKind = Comment;
-        }else{
-          /* Something dark, not a # comment or dot-command. Must be SQL. */
-          inKind = Sql;
+          break;
+        default:
+          /* Might be SQL, or a swallowable whole SQL comment. */
+          sql_prescan(zLineInput, &sqScanState);
+          if( SSS_PLAINWHITE(sqScanState) ){
+            /* It's either all blank or a whole SQL comment. Swallowable. */
+            inKind = Comment;
+          }else{
+            /* Something dark, not a # comment or dot-command. Must be SQL. */
+            inKind = Sql;
+          }
+          break;
         }
-        break;
-      } /* end classification switch */
+      }
     } /* end read/classify initial group input line */
 
     /* Here, if not at end of input, the initial line of group is in, and
@@ -13832,6 +13923,15 @@ static DotCmdRC process_input(ShellInState *psi){
 #endif
           disposition = Runnable; /* Legacy, any dot-command line is ready. */
         break;
+#if SHELL_DYNAMIC_EXTENSION
+      case Script:
+        if( psi->scripting.scriptIsComplete==0
+            || psi->scripting.scriptIsComplete(psi->scripting.pvScriptingState,
+                                               *pzLineUse+ndcLeadWhite) ){
+          disposition = Runnable;
+        }
+        break;
+#endif
       case Sql:
         /* Check to see if it is complete and ready to run. */
         if( SSS_SEMITERM(sqScanState) && sqlite3_complete(*pzLineUse)){
@@ -13920,6 +14020,31 @@ static DotCmdRC process_input(ShellInState *psi){
         if( dcr > termKind ) termKind = dcr;
         break;
       }
+#if SHELL_DYNAMIC_EXTENSION
+      case Script: {
+        if( psi->scripting.runScript!=0 ){
+          char *zErr = 0;
+          DotCmdRC dcr
+            = psi->scripting.runScript(psi->scripting.pvScriptingState,
+                                       *pzLineUse+ndcLeadWhite,
+                                       XSS(psi), &zErr);
+          if( dcr!=DCR_Ok || zErr!=0 ){
+            /* ToDo: Handle errors more informatively and like dot commands. */
+            nErrors += (dcr!=DCR_Ok);
+            if( zErr!=0 ){
+              utf8_printf(STD_ERR, "Error: %s\n", zErr);
+              sqlite3_free(zErr);
+            }
+          }
+        }else{
+          utf8_printf(STD_ERR, "Error: No script support;"
+                      " ignoring group at line %d of \"%s\"\n",
+                      psi->pInSource->lineno, psi->pInSource->zSourceSay);
+          ++nErrors;
+        }
+        break;
+      }
+#endif
       default:
         assert(inKind!=Tbd);
         break;
@@ -14352,6 +14477,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
 
   assert( argc>=1 && argv && argv[0] );
   Argv0 = argv[0];
+  initStartupDir();
 
 #ifdef SQLITE_SHELL_DBNAME_PROC
   {
@@ -14844,7 +14970,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   clearTempFile(&data);
   sqlite3_free(data.zEditor);
 #if SHELL_DYNAMIC_EXTENSION
-  free_shext_tracking(&data);
+  free_all_shext_tracking(&data);
   sqlite3_close(datax.dbShell);
 #endif
 #if !SQLITE_SHELL_IS_UTF8
index b1d33c4751559d22c75511ef3a60d49ef0e1a5ac..eaa8054b7db598013642c1eff7d7564ead6f541e 100644 (file)
 extern "C" {
 #endif
 
+/*****************
+ * See "Shell Extensions, Programming" for purposes and usage of the following
+ * interfaces supporting extended meta-commands and import and output modes.
+ */
+
+/* Define status codes returned by a meta-command, either during its argument
+ * checking or during its execution (to which checking may be deferred.) The
+ * code has 1 or 2 parts. The low-valued codes, below MCR_ArgIxMask, have an
+ * action part and an error flag. Higher-valued codes are bitwise-or'ed with
+ * a small integer and indicate problems with the meta-command itself.
+ */
+typedef enum DotCmdRC {
+  /* Post-execute action and success/error status (semi-ordered) */
+  DCR_Ok          = 0,    /* ordinary success and continue */
+  DCR_Error       = 1,    /* or'ed with low-valued codes upon error */
+  DCR_Return      = 2,    /* return from present input source/script */
+  DCR_ReturnError = 3,    /* return with error */
+  DCR_Exit        = 4,    /* exit shell ( process or pseudo-main() ) */
+  DCR_ExitError   = 5,    /* exit with error */
+  DCR_Abort       = 6,    /* abort for unrecoverable cause (OOM) */
+  DCR_AbortError  = 7,    /* abort with error (blocked unsafe) */
+  /* Above are in reverse-priority order for process_input() returns. */
+
+  /* Dispatch and argument errors */
+  DCR_ArgIxMask = 0xfff,  /* mask to retain/exclude argument index */
+  /* Below codes may be or'ed with the offending argument index */
+  DCR_Unknown   = 0x1000, /* unknown command, subcommand or option */
+  DCR_Ambiguous = 0x2000, /* ambiguous (sub)command (too abreviated) */
+  DCR_Unpaired  = 0x3000, /* option value indicated but missing */
+  DCR_TooMany   = 0x4000, /* excess arguments were provided */
+  DCR_TooFew    = 0x5000, /* insufficient arguments provided */
+  DCR_Missing   = 0x6000, /* required argument(s) missing */
+  DCR_ArgWrong  = 0x7000, /* non-specific argument error, nothing emitted */
+
+  /* This code indicates error and a usage message to be emitted to stderr. */
+  DCR_SayUsage  = 0x7ffd, /* usage is at *pzErr or is to be generated */
+  /* This code indicates nothing more need be put to stderr (or stdout.) */
+  DCR_CmdErred  = 0x7fff  /* non-specific error for which complaint is done */
+} DotCmdRC;
+
 /* Convey data to, from and/or between I/O handlers and meta-commands. */
 typedef struct ShellExState {
   /* A sizeof(*) to permit extensions to guard against too-old hosts */
@@ -81,46 +121,6 @@ typedef struct ShellExState {
   struct ShellInState *pSIS; /* Offset of this member is NOT STABLE. */
 } ShellExState;
 
-/*****************
- * See "Shell Extensions, Programming" for purposes and usage of the following
- * interfaces supporting extended meta-commands and import and output modes.
- */
-
-/* Define status codes returned by a meta-command, either during its argument
- * checking or during its execution (to which checking may be deferred.) The
- * code has 1 or 2 parts. The low-valued codes, below MCR_ArgIxMask, have an
- * action part and an error flag. Higher-valued codes are bitwise-or'ed with
- * a small integer and indicate problems with the meta-command itself.
- */
-typedef enum DotCmdRC {
-  /* Post-execute action and success/error status (semi-ordered) */
-  DCR_Ok          = 0,    /* ordinary success and continue */
-  DCR_Error       = 1,    /* or'ed with low-valued codes upon error */
-  DCR_Return      = 2,    /* return from present input source/script */
-  DCR_ReturnError = 3,    /* return with error */
-  DCR_Exit        = 4,    /* exit shell ( process or pseudo-main() ) */
-  DCR_ExitError   = 5,    /* exit with error */
-  DCR_Abort       = 6,    /* abort for unrecoverable cause (OOM) */
-  DCR_AbortError  = 7,    /* abort with error (blocked unsafe) */
-  /* Above are in reverse-priority order for process_input() returns. */
-
-  /* Dispatch and argument errors */
-  DCR_ArgIxMask = 0xfff,  /* mask to retain/exclude argument index */
-  /* Below codes may be or'ed with the offending argument index */
-  DCR_Unknown   = 0x1000, /* unknown command, subcommand or option */
-  DCR_Ambiguous = 0x2000, /* ambiguous (sub)command (too abreviated) */
-  DCR_Unpaired  = 0x3000, /* option value indicated but missing */
-  DCR_TooMany   = 0x4000, /* excess arguments were provided */
-  DCR_TooFew    = 0x5000, /* insufficient arguments provided */
-  DCR_Missing   = 0x6000, /* required argument(s) missing */
-  DCR_ArgWrong  = 0x7000, /* non-specific argument error, nothing emitted */
-
-  /* This code indicates error and a usage message to be emitted to stderr. */
-  DCR_SayUsage  = 0x7ffd, /* usage is at *pzErr or is to be generated */
-  /* This code indicates nothing more need be put to stderr (or stdout.) */
-  DCR_CmdErred  = 0x7fff  /* non-specific error for which complaint is done */
-} DotCmdRC;
-
 /* An object implementing below interface is registered with the
  * shell to make new or overriding meta-commands available to it.
  */
@@ -196,6 +196,30 @@ CONCRETE_END(Derived) vtname = { \
 typedef int (*ExtensionId)
   (sqlite3 *, char **, const struct sqlite3_api_routines *);
 
+/* Hooks for scripting language integration.
+ *
+ * If hookScripting(...) has been called to register an extension's
+ * scripting support, and isScriptLeader(pvSS, zLineLead) returns true,
+ * (where zLineLead is an input group's leading line), then the shell
+ * will collect input lines until scriptIsComplete(pvSS, zLineGroup)
+ * returns non-zero, whereupon, the same group is submitted to be run
+ * via runScript(pvSS, zLineGroup, ...). The default behaviors (when
+ * one of the function pointers is 0) are: return false; return true;
+ * and return DCR_Error after doing nothing.
+ *
+ * An extension which has called hookScripting() should arrange to
+ * free associated resources upon exit or when its destructor runs.
+ *
+ * The 1st member, pvScriptingState, is an arbitrary, opaque pointer.
+ */
+typedef struct ScriptHooks {
+  void *pvScriptingState; /* passed into below functions as pvSS */
+  int (*isScriptLeader)(void *pvSS, const char *zScript);
+  int (*scriptIsComplete)(void *pvSS, const char *zScript);
+  DotCmdRC (*runScript)(void *pvSS, const char *zScript,
+                        ShellExState *, char **pzErrMsg);
+} ScriptHooks;
+
 typedef struct ExtensionHelpers {
   int helperCount; /* Helper count, not including sentinel */
   union {
@@ -208,9 +232,12 @@ typedef struct ExtensionHelpers {
                                        /* out */ int *pnFound);
       void (*setColumnWidths)(ShellExState *p, char *azWidths[], int nWidths);
       int (*nowInteractive)(ShellExState *p);
+      const char * (*shellInvokedAs)(void);
+      const char * (*shellStartupDir)(void);
+      int (*enable_load_extension)(sqlite3 *db, int onoff);
       void (*sentinel)(void);
     } named ;
-    void (*nameless[5+1])(); /* Same as named but anonymous plus a sentinel. */
+    void (*nameless[10+1])(); /* Same as named but anonymous plus a sentinel. */
   } helpers;
 } ExtensionHelpers;
 
@@ -220,7 +247,7 @@ typedef struct ShellExtensionAPI {
   ExtensionHelpers * pExtHelpers;
 
   /* Functions for extension to register its implementors with shell */
-  const int numRegistrars; /* 3 for this version */
+  const int numRegistrars; /* 4 for this version */
   union {
     struct ShExtAPI {
       /* Register a meta-command */
@@ -232,6 +259,9 @@ typedef struct ShellExtensionAPI {
       /* Register an import variation from (various sources) for .import */
       int (*registerImporter)(ShellExState *p,
                               ExtensionId eid, ImportHandler *pIH);
+      /* Provide scripting support to host shell. (See ScriptHooks above.) */
+      int (*hookScripting)(ShellExState *p,
+                           ExtensionId eid, ScriptHooks *pSH);
       /* Preset to 0 at extension load, a sentinel for expansion */
       void (*sentinel)(void);
     } named;
@@ -256,10 +286,12 @@ typedef struct ShellExtensionLink {
    */
   ExtensionId eid;
 
-  /* Another init "out" parameter, a destructor for extension overall.
-   * Set to 0 on input and may be left so if no destructor is needed.
+  /* Two more init "out" parameters, a destructor for extension overall.
+   * Set to 0 on input and left so if no destructor is needed. Otherwise,
+   * upon exit or unload, extensionDestruct(pvExtensionObject) is called.
    */
-  void (*extensionDestruct)(void *);
+  void (*extensionDestruct)(void *pvExtObj);
+  void *pvExtensionObject;
 
 } ShellExtensionLink;
 
index 2eac4f3f05fdaedd26627a17d544175ad4a02594..a75dd496e78247926a519ff0cec1c134564de1f6 100644 (file)
@@ -351,6 +351,11 @@ struct sqlite3_api_routines {
   int (*vtab_in)(sqlite3_index_info*,int,int);
   int (*vtab_in_first)(sqlite3_value*,sqlite3_value**);
   int (*vtab_in_next)(sqlite3_value*,sqlite3_value**);
+  /* Version 3.39.0 and later */
+  int (*deserialize)(sqlite3*,const char*,unsigned char*,
+                     sqlite3_int64,sqlite3_int64,unsigned);
+  unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*,
+                              unsigned int);
 };
 
 /*
@@ -669,6 +674,10 @@ typedef int (*sqlite3_loadext_entry)(
 #define sqlite3_vtab_in                sqlite3_api->vtab_in
 #define sqlite3_vtab_in_first          sqlite3_api->vtab_in_first
 #define sqlite3_vtab_in_next           sqlite3_api->vtab_in_next
+#ifndef SQLITE_OMIT_DESERIALIZE
+#define sqlite3_deserialize            sqlite3_api->deserialize
+#define sqlite3_serialize              sqlite3_api->serialize
+#endif
 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
 
 #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
old mode 100644 (file)
new mode 100755 (executable)
index 65432e9..d847724
@@ -53,19 +53,19 @@ set ::headComment {/* DO NOT EDIT!
 ** This file is automatically generated by the script in the canonical
 ** SQLite source tree at tool/mkshellc.tcl.  That script combines and
 ** transforms code from various constituent source files of SQLite into
-** this single "shell.c" file to implement the SQLite command-line shell.
+** this single OUT_FILE file to implement TARGET_PROGRAM.
 **
-** Most of the code found below comes from the "src/shell.c.in" file in
+** Much of the code found below comes from the SOURCE_FILE file in
 ** the canonical SQLite source tree.  That main file contains "INCLUDE"
 ** lines that specify other files in the canonical source tree that are
 ** inserted and transformed, (via macro invocations explained by running
 ** "tool/mkshellc.tcl --help"), to generate this complete program source.
 **
-** By means of this generation process, creating this single "shell.c"
-** file, building the command-line program is made simpler and easier.
+** By means of this generation process, creating this single OUT_FILE
+** file, building the program from it is simplified.
 **
 ** To modify this program, get a copy of the canonical SQLite source tree,
-** edit file src/shell.c.in and/or some of the other files included by it,
+** edit file SOURCE_FILE and/or some of the other files it includes,
 ** then rerun the tool/mkshellc.tcl script.
 */}
 
@@ -79,30 +79,42 @@ set ::lineTags 0 ; # 0 => none, 1 => source change, 2 => line syncs, 3 => more
 set ::tclGenerate 0
 set ::verbosity 0
 set ::inFiles {}
-array set ::incTypes [list "*" "$::topDir/src/shell.c.in"]
+set ::topInfile "?"
+set ::presumedOutfile "?"
+set ::targetProgram "?"
+set ::defaultInfile "src/shell.c.in"
+array set ::incTypes [list "*" "$::topDir/$::defaultInfile"]
 array set ::ignoringCommands [list]
 
+# Shift 0'th element out of named list and return it, modifying the named list.
+proc lshift {lvar} {
+  upvar $lvar l
+  set rv [lindex $l 0]
+  set l [lrange $l 1 end]
+  return $rv
+}
+
 while  {[llength $argv] > 0} {
-  foreach {opt} $arv { set argv [lreplace $argv 1 end] ; break }
+  set opt [lshift argv]
   if {[regexp {^-{1,2}((help)|(details)|(parameters))$} $opt ma ho]} {
     set runMode $ho
   } elseif {[regexp {^-it$} $opt]} {
-    foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break }
+    set nextOpt [lshift argv]
     if {![regexp {^(\w+)=(.+)$} $nextOpt ma k v]} {
       puts stderr "Get help with --help."
       exit 1 
     }
     set ::incTypes($k) $v
   } elseif {$opt eq "-top-dir"} {
-    foreach {::topDir} $arv { set argv [lreplace $argv 1 end] ; break }
+    set ::topDir [lshift argv]
     if {::topDir eq ""} { set ::topDir . }
   } elseif {$opt eq "-source-tags"} {
-    foreach {nextOpt} $arv { set argv [lreplace $argv 1 end] ; break }
+    set nextOpt [lshift argv]
     if {![regexp {^\d$} $nextOpt ::lineTags]} {
       puts stderr "Argument following -source-tags must be a digit."
     }
   } elseif {$opt eq "-tcl"} {
-    puts stderr "Warning: Tcl extension not wholly implemented."
+    puts stderr "Warning: Tcl extension not implemented."
     set ::tclGenerate 1
   } elseif {$opt eq "-v"} {
     incr ::verbosity
@@ -114,7 +126,14 @@ while  {[llength $argv] > 0} {
 }
 if {$runMode eq "normal"} {
   if {[llength $::inFiles] == 0} {
-    lappend ::inFiles $::incTypes(*)
+    set ::targetProgram "the SQLite command-line shell"
+    set ::topInfile $::defaultInfile
+    set ::presumedOutfile {"shell.c"}
+    lappend ::inFiles $::topInfile
+  } else {
+    set ::targetProgram "this SQLite shell extension"
+    set ::topInfile [lindex $::inFiles 0]
+    set ::presumedOutfile "source"
   }
   fconfigure stdout -translation {auto lf}
   set ::outStrm stdout
@@ -470,9 +489,9 @@ proc CONDITION_COMMAND {inSrc tailCap ostrm} {
 }
 
 proc DISPATCH_CONFIG {inSrc tailCaptureEmpty ostrm} {
-  foreach { srcFile istrm srcPrecLines } $inSrc break
   # Set parameters affecting generated dispatchable command function
   # signatures and generated dispatch table entries.
+  foreach { srcFile istrm srcPrecLines } $inSrc break
   set iAte 2
   set def_disp {}
   set lx [gets $istrm]
@@ -756,7 +775,11 @@ if {$runMode == "help"} {
 
 if {$runMode == "normal"} {
   fconfigure $outStrm -translation {auto lf}
-  emit_sync [list $::headComment] $outStrm $::headCommentLines
+  set infName [lindex $::inFiles 0]
+  set hc [regsub -all {OUT_FILE} $::headComment $::presumedOutfile]
+  set hc [regsub -all {SOURCE_FILE} $hc "\"$::topInfile\""]
+  set hc [regsub -all {TARGET_PROGRAM} $hc "$::targetProgram"]
+  emit_sync [list $hc] $outStrm $::headCommentLines
   foreach {f} $::inFiles {
     process_file $f $outStrm
   }