]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
TCL extension polished and more smoothly integrated. A couple of bugs related to...
authorlarrybr <larrybr@noemail.net>
Mon, 28 Mar 2022 21:42:16 +0000 (21:42 +0000)
committerlarrybr <larrybr@noemail.net>
Mon, 28 Mar 2022 21:42:16 +0000 (21:42 +0000)
FossilOrigin-Name: 4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7

ext/misc/tclshext.c.in
manifest
manifest.uuid
src/shell.c.in
src/shext_linkage.h

index 1e700d1bcd6e5aa51c23a75a9e955a956f1b37f4..f9ac21b123143b2320926040e0b3e55f8c1f0830 100644 (file)
        -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.
+** This extension adds these features to the host shell:
+** 1. TCL scripting support is added.
+** 2. TCL commands added: udb shdb now_interactive get_tcl_group ..
+** 3. The .tcl and .eval dot commands are added.
 **
 ** 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.
+** shell input groups beginning with ".." are treated as TCL input, in
+** various ways, specifically: (1) When a bare ".." is entered, a TCL
+** REPL loop is run until the end of input is seen; (2) When "..X ..."
+** is entered, (where "X" is a dot-command name), the X dot command will
+** be run in its normal fashion, but its arguments will be collected
+** according to TCL parsing rules and will be expanded as is usual for
+** TCL commands; and (3) when ".. T ..." is entered, (where "T" is a TCL
+** command name), that TCL command and its arguments will be collected
+** and expanded according to TCL parsing rules, and the command will be
+** run in the TCL execution environment (in the root namespace), but the
+** shell execution environment remains in effect afterward. (Note that
+** cases 2 and 3 differ in whether space occurs after the leading "..".)
+**
+** The phrase "end of input" means either: end-of-file is seen on a file,
+** pipe or string stream input, or a lone "." on the first and only line
+** of an input line group is seen. This convention is useful in scripting
+** when it is expedient to switch execution environments from within the
+** same input stream. 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 a useful set of user-defined shell enhancements.
+** 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 ; # which expose the user DB and shell DB for access via TCL
+**   now_interactive ; # which indicates whether input is interactive
+**   get_tcl_group ; # which gets a single TCL input line group
+**   .. ; # which 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. This 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 .eval dot command may be used from the TCL environment to
+** run a SQL query or statement and obtain the shell's nice display or
+** other disposition of results. While the udb and shdb command allow
+** convenient access to the DB(s), they are not setup for display.
 */
 
-#include <limits.h>
 #include "shext_linkage.h"
 
 static struct ShExtAPI *pShExtApi = 0;
@@ -63,6 +115,7 @@ INCLUDE tclsqlite.c
 #endif
 
 typedef struct TclCmd TclCmd;
+typedef struct EvalCmd EvalCmd;
 
 static void TclCmd_Takedown(TclCmd *ptc);
 
@@ -72,19 +125,47 @@ static void TclCmd_Takedown(TclCmd *ptc);
 DERIVED_METHOD(void, destruct, MetaCommand,TclCmd, 0, ()){
   TclCmd_Takedown((TclCmd *)pThis);
 }
+DERIVED_METHOD(void, destruct, MetaCommand,EvalCmd, 0, ()){
+  (void)(pThis);
+}
 
 DERIVED_METHOD(const char *, name, MetaCommand,TclCmd, 0,()){
   return "tcl";
 }
+DERIVED_METHOD(const char *, name, MetaCommand,EvalCmd, 0,()){
+  return "eval";
+}
 
 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";
+    ".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 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"
+    ;
+  default: return 0;
+  }
+}
+DERIVED_METHOD(const char *, help, MetaCommand,EvalCmd, 1,(int more)){
+  switch( more ){
+  case 0: return
+    ".eval SQL ...            Evaluate given SQL statements as shell does.\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 \".\"\n"
-      "   is entered on an input line or end-of-stream is encountered.\n";
+    "   There is little use for this dot command without the TCL extension,\n"
+    "   as it merely does what can be done in the shell by direct entry of\n"
+    "   the same statements. Its utility is in the TCL environment, where\n"
+    "   statements may be computed or derived in a variety of ways, and\n"
+    "   one wishes to use the shell's result output rendering capability.\n"
+    ;
   default: return 0;
   }
 }
@@ -93,6 +174,10 @@ DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,TclCmd, 3,
              (char **pzErrMsg, int nArgs, char *azArgs[])){
   return DCR_Ok;
 }
+DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,EvalCmd, 3,
+             (char **pzErrMsg, int nArgs, char *azArgs[])){
+  return DCR_Ok;
+}
 
 static Tcl_Interp *getInterp(TclCmd *ptc);
 
@@ -103,7 +188,7 @@ static void copy_complaint(char **pzErr, Tcl_Interp *pi){
   }
 }
 
-/* The .tcl REPL script is one of the 3 following string literals,
+/* 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.
@@ -114,6 +199,7 @@ static void copy_complaint(char **pzErr, Tcl_Interp *pi){
  *    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
@@ -196,18 +282,19 @@ static const char * const zREPL =
   "namespace delete ::REPL\n"
   "read stdin 0\n"
 #elif TCL_REPL==3
+# define SHELL_REPL_CMDNAME "sqlite_shell_REPL"
   /* using shell's input collection with line editing (if configured) */
-  "sqlite_shell_REPL"
+  SHELL_REPL_CMDNAME
 #else
   "throw {NONE} {not built for REPL}\n"
 #endif
   ; /* zREPL */
 
 static const char * const zDefineREPL =
-  "proc sqlite_shell_REPL {} {\n"
+  "proc "SHELL_REPL_CMDNAME" {} {\n"
     "set interactive [now_interactive]\n"
     "while {1} {\n"
-      "foreach {group ready} [get_input_line_group] {}\n"
+      "foreach {group ready} [get_tcl_group] {}\n"
       "set trimmed [string trim $group]\n"
       "if {$group eq \"\" && !$ready} break\n"
       "if {$trimmed eq \"\"} continue\n"
@@ -231,39 +318,62 @@ static const char * const zDefineREPL =
   "}\n"
   ;
 
+/* 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 = 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 rc = TCL_OK;
     int aix;
     for( aix=0; aix<(nArgs-1) && rc==TCL_OK; ++aix ){
       rc = Tcl_EvalFile(getInterp(ptc), azArgs[aix+1]);
     }
+    if( rc!=TCL_OK ){
+      copy_complaint(pzErrMsg, getInterp(ptc));
+      return DCR_Error;
+    }
+    return DCR_Ok;
   }else{
     /* Enter a REPL */
-    rc = Tcl_Eval(getInterp(ptc), zREPL);
-    clearerr(stdin); /* Cure issue where stdin gets stuck after keyboard EOF. */
+    return runTclREPL(getInterp(ptc), pzErrMsg);
   }
-  if( rc!=TCL_OK ){
-    copy_complaint(pzErrMsg, getInterp(ptc));
-    rv = DCR_Error;
-  }
-  return rv;
+}
+DERIVED_METHOD(DotCmdRC, execute, MetaCommand,EvalCmd, 4,
+             (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){
+  FILE *out = pExtHelpers->currentOutputFile(psx);
+  fprintf(out, "The .eval command does nothing, yet.\n");
+  return DCR_Ok;
 }
 
-/* Define a MetaCommand v-table initialized to reference above methods. */
+/* Define MetaCommand v-tables initialized to reference above methods. */
 MetaCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods);
+MetaCommand_IMPLEMENT_VTABLE(EvalCmd, evalcmd_methods);
 
+/* Static instances are used because that suffices and makes the
+ * interpreter easy to reference without going through pointers. */
 INSTANCE_BEGIN(TclCmd);
   Tcl_Interp *interp;
 INSTANCE_END(TclCmd) tclcmd = {
   &tclcmd_methods
   , 0 /* interp pointer */
 };
+INSTANCE_BEGIN(EvalCmd);
+  /* no instance data */
+INSTANCE_END(EvalCmd) evalcmd = {
+  &evalcmd_methods
+};
 
 static Tcl_Interp *getInterp(TclCmd *ptc){
   return ptc->interp;
@@ -275,7 +385,11 @@ static void TclCmd_Takedown(TclCmd *ptc){
   Tcl_Finalize();
 }
 
-/* Say line is script lead-in iff its first dark is "..". */
+/* 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.
+ */
 static int tclIsScriptLead(void *pvState, const char *zLineLead){
   char c;
   (void)(pvState);
@@ -283,11 +397,13 @@ static int tclIsScriptLead(void *pvState, const char *zLineLead){
   return (c=='.' && *zLineLead=='.');
 }
 
+/* Say line group is complete if it passes muster as ready-to-go TCL. */
 static int tclIsComplete(void *pvState, const char *zScript){
   (void)(pvState);
   return Tcl_CommandComplete(zScript);
 }
 
+/* Run as TCL after some jiggering with the leading dots. */
 static DotCmdRC tclRunScript(void *pvState, const char *zScript,
                              ShellExState *p, char **pzErrMsg){
   char c;
@@ -295,11 +411,40 @@ static DotCmdRC tclRunScript(void *pvState, const char *zScript,
   while( (c=*zScript++) && (c==' '||c=='\t') ) {}
   if( c=='.' &&  *zScript++=='.' ){
     int rc, nc = strlen30(zScript);
-    rc = Tcl_EvalEx(ptc->interp, zScript, nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
-    if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp(ptc));
+    /* 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(ptc->interp, zScript, /* needs no adjustment */
+                      nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
+      if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp(ptc));
+      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(ptc->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;
+  return DCR_Error; /* Silent error because it should not happen. */
 }
 
 #define GETLINE_MAXLEN 1000
@@ -326,7 +471,7 @@ static int getInputLine(void *pvSS, Tcl_Interp *interp,
 #endif
 
 #if TCL_REPL==3
-/* C implementation of TCL proc, get_input_line_group 
+/* 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
@@ -565,9 +710,9 @@ static int udbEventHandle(void *pv, NoticeKind nk, void *pvSubject,
   if( nk==NK_ShutdownImminent ){
     udbCleanup(pudb);
   }else if( nk==NK_Unsubscribe ){
-    assert(pudb==0 || (pudb->nRef==0 && pudb->ppSdb==0));
+    assert(pudb==0 || pudb->numSdb==0);
   }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing
-            || nk==NK_DbAboutToClose ){
+            || nk==NK_DbAboutToClose || nk==NK_ExtensionUnload ){
     sqlite3 *dbSubject = (sqlite3*)pvSubject;
     int ix = udbIndexOfDb(pudb, dbSubject);
     switch( nk ){
@@ -578,6 +723,10 @@ static int udbEventHandle(void *pv, NoticeKind nk, void *pvSubject,
     case NK_DbUserVanishing:
       if( ix>=0 ) pudb->ixuSdb = -1;
       break;
+    case NK_ExtensionUnload:
+      pShExtApi->subscribeEvents(psx, sqlite3_tclshext_init, 0,
+                                 NK_Unsubscribe, udbEventHandle);
+      /* fall thru */
     case NK_DbAboutToClose:
       if( ix>=0 ) udbRemove(pudb, ix);
       break;
@@ -703,6 +852,7 @@ int sqlite3_tclshext_init(
   if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=1 ){
     ShellExState *psx = pShExtLink->pSXS;
     MetaCommand *pmc = (MetaCommand *)&tclcmd;
+    MetaCommand *pec = (MetaCommand *)&evalcmd;
     const char *zShellName, *zShellDir;
     int rc;
 
@@ -729,6 +879,7 @@ int sqlite3_tclshext_init(
       TclCmd_Takedown(&tclcmd);
       return SQLITE_ERROR;
     }
+    rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init, pec);
     rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init, pmc);
     if( rc==SQLITE_OK ){
       ScriptHooks sh = { pmc, tclIsScriptLead, tclIsComplete, tclRunScript };
@@ -746,14 +897,18 @@ int sqlite3_tclshext_init(
 #endif
 #if TCL_REPL==3
         Tcl_CreateObjCommand(tclcmd.interp,
-                             "get_input_line_group", getInputLineGroup, psx, 0);
+                             "get_tcl_group", getInputLineGroup, psx, 0);
         Tcl_Eval(tclcmd.interp, zDefineREPL);
 #endif
         Tcl_CreateCommand(tclcmd.interp,
                           "now_interactive", nowInteractive, psx, 0);
+        /* Rename unknown so that calls to it can be intercepted. */
         Tcl_Eval(tclcmd.interp, "rename unknown "UNKNOWN_RENAME);
         Tcl_CreateCommand(tclcmd.interp,
                           "unknown", unknownDotDelegate, 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(tclcmd.interp, "proc .. {} {}");
         pShExtLink->eid = sqlite3_tclshext_init;
       }else{
         TclCmd_Takedown(&tclcmd);
index 33baad8e453a1cbadea1d8e7ba4cce6abb3e9e6b..a213c86ef463925b973d151a63a0d006edb5a1cb 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C In\sTCL\sshell\sextension,\swrap\sshell\sDB\sfor\suse\sfrom\sTCL.
-D 2022-03-28T05:57:07.930
+C TCL\sextension\spolished\sand\smore\ssmoothly\sintegrated.\sA\scouple\sof\sbugs\srelated\sto\stakedown\sfixed.\sHelp\sfor\sadded\sfeatures.\sCode\scleaned\sup\sand\scommented.
+D 2022-03-28T21:42:16.234
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -329,7 +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 c78715b514667807c152c4cd1bccc054f848ca43f3c3367047e1a0a78cb1c5ce
+F ext/misc/tclshext.c.in db6ec20db5c11f8ca808232d569ed9cd6173ae4d5de49ddee891b980d1a181ff
 F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4
 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b
 F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b
@@ -556,8 +556,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c c366c05e48e836ea04f8ecefb9c1225745dc250c3f01bdb39e9cbb0dc25e3610
-F src/shell.c.in c95182f95dc2b1278214812e02fb5131a847851c93b95c548a8a825b87f47aa1 x
-F src/shext_linkage.h 68aca955c86a0ef7d1f007a4e776a7e9c17542cfde66195db1d7333695fcef7d
+F src/shell.c.in 15c883304f6a757ec9b6c4e2f6720a5d9a2066ed0b88348a36bcf5c1b11c1f7c x
+F src/shext_linkage.h c70f95dd0738c2cd8452ab5c47c245d7fa3b99ec26cd160c67dfb4489d5dacf9
 F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
@@ -1951,8 +1951,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P fbf0eb0d12932513bba4c6a6ef31d9972d704ab38690c806098504a4cd67786d
-R d234ea22bba902ea03fc077d174e9a9f
+P 41cc84336bbf7bc64492c24e1bf5be0fccbb8a3db57498070b624af8818d0075
+R a6fcd98d9f55917ef71d90740fb27226
 U larrybr
-Z cd9ddf3319fb6a9468c36fb31583d848
+Z 385f3d338e553fbadafb70a790af574d
 # Remove this line to create a well-formed Fossil manifest.
index 4b8f0e7cf77d7a057e0bbcaecea4d0d339a66675..10d82be7d25f42acb636cc2314d945a9075cb1d8 100644 (file)
@@ -1 +1 @@
-41cc84336bbf7bc64492c24e1bf5be0fccbb8a3db57498070b624af8818d0075
\ No newline at end of file
+4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
\ No newline at end of file
index 961a380e70fae7f2bd5888444cb30e833fec0970..99c1a7b4dd8d6e53ef2f2279df2342c8e918879f 100755 (executable)
@@ -7785,6 +7785,7 @@ static int register_meta_command(ShellExState *p,
     if( rc==SQLITE_DONE ){
       psei->ppMetaCommands[nc++] = pMC;
       psei->numMetaCommands = nc;
+      notify_subscribers(psi, NK_NewDotCommand, pMC);
       return SQLITE_OK;
     }else{
       psei->ppMetaCommands[nc] = 0;
@@ -7840,7 +7841,7 @@ static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
       if( (eventHandler==0 || eventHandler==pes->eventHandler)
           && (pes->eid==0 || eid==eid)
           && (eid!=0 ||eventHandler!=0 ||/* for shell use */ pvUserData==p ) ){
-        int nLeft = pesLim - pesLim;
+        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));
@@ -7877,6 +7878,26 @@ static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
     return SQLITE_OK;
   }
 }
+
+/* 
+ * Unsubscribe all event listeners having an ExtensionId > 0. This is
+ * done just prior closing the shell DB (when dynamic extensions will
+ * be unloaded and accessing them in any way is good for a crash.)
+ */
+static void unsubscribe_extensions(ShellInState *psi){
+  ShellExState *psx = XSS(psi);
+  int esix = 0;
+
+  if( psi->numExtLoaded<=1 ) return; /* Ignore shell pseudo-extension. */
+  while( esix<psi->numSubscriptions ){
+    struct EventSubscription *pes = psi->pSubscriptions+esix;
+    if( pes->eid > 0 ){
+      int nsin = psi->numSubscriptions;
+      subscribe_events(psx, pes->eid, psx, NK_Unsubscribe, 0);
+      esix = esix + 1 + (psi->numSubscriptions - nsin);
+    }else ++esix;
+  }
+}
 #endif
 
 static FILE *currentOutputFile(ShellExState *p){
@@ -13439,8 +13460,7 @@ static DotCmdRC meta_command_errors(char *zErr, char *azArg[], int nArg,
       pArg1st = azArg[0];
       break;
     default:
-      assert(0);
-      z = "?";
+      z = "? Bizarre return from %s";
       break;
     }
     if( !zErr ){
@@ -15146,9 +15166,20 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   sqlite3_free(data.zEditor);
 #if SHELL_DYNAMIC_EXTENSION
   notify_subscribers(&data, NK_DbAboutToClose, datax.dbShell);
+  /* It is necessary that the shell DB be closed after the user DBs.
+   * This is because loaded extensions are held by the shell DB and
+   * are therefor (automatically) unloaded when it is closed. */
+  notify_subscribers(&data, NK_ExtensionUnload, datax.dbShell);
+  /* Forcefully unsubscribe any extension which ignored above or did
+   * not unsubscribe upon getting above event. */
+  unsubscribe_extensions(&data);
+  /* This must be done before any extensions unload. */
+  free_all_shext_tracking(&data);
+  /* Unload extensions and free the DB used for dealing with them. */
   sqlite3_close(datax.dbShell);
+  /* This notification can only reach statically linked extensions. */
   notify_subscribers(&data, NK_ShutdownImminent, 0);
-  free_all_shext_tracking(&data);
+  /* Forcefull unsubscribe static extension event listeners. */
   subscribe_events(&datax, 0, &datax, NK_Unsubscribe, 0);
 #endif
 #if !SQLITE_SHELL_IS_UTF8
index bc67988811aec58d67322c2424ece1262159d83c..4793992af5f1587ee3735a3166aef68c34ff6c74 100644 (file)
@@ -277,8 +277,13 @@ typedef enum {
                         * pvSubject is the newly set .dbUser value. */
   NK_DbUserVanishing,  /* Current ShellExState .dbUser will soon vanish,
                         * pvSubject is the vanishing .dbUser value. */
-  NK_DbAboutToClose,   /* A ShellExState-visible DB will soon be closed,
-                        * pvSubject is the sqlite3 pointer soon to close. */
+  NK_DbAboutToClose,   /* A possibly ShellExState-visible DB will soon be
+                        * closed, pvSubject is the DB's sqlite3 pointer. */
+  NK_ExtensionUnload,  /* The ShellExState .dbShell DB will soon be closed,
+                        * soon to be followed by unloading of all dynamic
+                        * extensions; pvSubject is the DB's sqlite3 pointer. */
+  NK_NewDotCommand,    /* A new MetaCommand has been registered, pvSubject
+                        * is the just-added MetaCommand object (pointer). */
   NK_CountOf           /* Present count of preceding members (evolves) */
 } NoticeKind;