]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
In TCL shell extension, add TCL command "udb". This gets the TCL environment to near...
authorlarrybr <larrybr@noemail.net>
Sun, 27 Mar 2022 03:08:02 +0000 (03:08 +0000)
committerlarrybr <larrybr@noemail.net>
Sun, 27 Mar 2022 03:08:02 +0000 (03:08 +0000)
FossilOrigin-Name: c9aa76bf88401d193a536bc6576405aaad06681504996916b492962d890bc9e0

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

index 2362e6bb9aa17f1934b2f6a263bcf7685ad8feaa..a5a4127be5eb317b6ed51189ba4a52bd339fc3f1 100644 (file)
@@ -118,7 +118,7 @@ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4,
   }else{
     /* Enter a REPL */
     static const char * const zREPL =
-#ifdef REPL_STDIN_ONLY
+#ifdef TCL_REPL_STDIN_ONLY /* a fallback for debug */
       "set line {}\n"
       "while {![eof stdin]} {\n"
         "if {$line!=\"\"} {\n"
@@ -190,9 +190,9 @@ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4,
       "namespace delete ::REPL\n"
       "read stdin 0\n"
 #endif
-      ;
+      ; //... ToDo: Get line editing working here. Reuse shell's line entry.
     rc = Tcl_Eval(getInterp(ptc), zREPL);
-    clearerr(stdin);
+    clearerr(stdin); /* Cure issue where stdin gets stuck after keyboard EOF. */
   }
   if( rc!=TCL_OK ){
     copy_complaint(pzErrMsg, getInterp(ptc));
@@ -240,7 +240,7 @@ static DotCmdRC tclRunScript(void *pvState, const char *zScript,
   TclCmd *ptc = (TclCmd *)pvState;
   while( (c=*zScript++) && (c==' '||c=='\t') ) {}
   if( c=='.' &&  *zScript++=='.' ){
-    int rc, nc = strlen(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));
     return DCR_Ok|(rc!=TCL_OK);
@@ -287,7 +287,7 @@ static int nowInteractive(void *pvSS, Tcl_Interp *interp,
 
 #define UNKNOWN_RENAME "::_original_unknown"
 
-/* C implementation of TCL ::unknown to delegate to dot commands */
+/* 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;
@@ -337,9 +337,219 @@ static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp,
   }
 }
 
-/* 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. */
+/* Forward reference for use as ExtensionId */
+int sqlite3_tclshext_init(sqlite3*, char**, const sqlite3_api_routines*);
 
+/* 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 ixSdb;          /* Which held "sqlite3" object is the .userDb, 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, but 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. */
+  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->ixSdb ) pudb->ixSdb = -1;
+  else if( ix<pudb->ixSdb ) --pudb->ixSdb;
+}
+
+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->ixSdb = -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->nRef==0 && pudb->ppSdb==0));
+  }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing
+            || nk==NK_DbAboutToClose ){
+    sqlite3 *dbSubject = (sqlite3*)pvSubject;
+    int ix = udbIndexOfDb(pudb, dbSubject);
+    switch( nk ){
+    case NK_DbUserAppeared:
+      if( ix>=0 ) pudb->ixSdb = ix;
+      else pudb->ixSdb = udbAdd(pudb, dbSubject);
+      break;
+    case NK_DbUserVanishing:
+      if( ix>=0 ) pudb->ixSdb = -1;
+      break;
+    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 *sdb = psx->dbUser;
+    rv = &udb;
+    rv->interp = interp;
+    rv->psx = psx;
+    rv->ppSdb = (SqliteDb**)Tcl_Alloc(5*sizeof(SqliteDb*));
+    memset(rv->ppSdb, 0, 5*sizeof(SqliteDb*));
+    if( sdb!=0 ){
+      udbAdd(rv, sdb);
+      rv->ixSdb = 0;
+    } else rv->ixSdb = -1;
+    rv->nRef = 1;
+    /* Arrange that this object tracks lifetimes and visibility of the
+     * ShellExState .dbUser member values which udb purports to wrap.
+     * This subscription eventually leads to a udbCleanup() call. */
+    pShExtApi->subscribeEvents(psx, sqlite3_tclshext_init,
+                               rv, NK_CountOf, udbEventHandle);
+  }
+  return rv;
+}
+
+/* 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", ".eval", 0 };
+  enum DbDoWhat { DDW_Close, DDW_ShellEval } doWhat;
+
+  /* Delegate all subcommands except above to the now-visible SqliteDb. */
+  if( objc>=2 && !Tcl_GetIndexFromObj(interp, objv[1], azDoHere,
+                                      "subcommand", 0, (int*)&doWhat)){
+    switch( doWhat ){
+    case DDW_Close:
+      Tcl_AppendResult(interp, "Directly closing udb is disallowed.\n", 0);
+      return TCL_ERROR;
+    case DDW_ShellEval:
+      Tcl_AppendResult(interp, "Faking .eval ...\n", 0);
+      //... ToDo: Implement this.
+      return TCL_OK;
+    }
+  }
+  if( pudb->numSdb==0 || pudb->ixSdb<0 ){
+    Tcl_AppendResult(interp, Tcl_GetStringFromObj(objv[0], (int*)0),
+                     " references no DB yet.\n", 0);
+    return TCL_ERROR;
+  }
+  return DbObjCmd(pudb->ppSdb[pudb->ixSdb], interp, objc, objv);
+}
+
+/* Get the udb command subsystem initialized and create "udb" TCL command. */
+static int userDbInit(Tcl_Interp *interp, ShellExState *psx){
+  UserDb *pudb = udbCreate(interp, psx);
+  if( Tcl_CreateObjCommand(interp, "udb", (Tcl_ObjCmdProc*)UserDbObjCmd,
+                           (char *)pudb, 0) ){
+    ++pudb->nRef;
+    return TCL_OK;
+  }
+  return TCL_ERROR;
+}
+
+/* Extension boiler-plate to grab ShellExtensionLink pointer from db. */
 DEFINE_SHDB_TO_SHEXTLINK(shext_link);
 
 /*
@@ -390,6 +600,11 @@ int sqlite3_tclshext_init(
       ScriptHooks sh = { pmc, tclIsScriptLead, tclIsComplete, tclRunScript };
       int irc = Tcl_Init(tclcmd.interp);
       if( irc==TCL_OK ){
+        if( TCL_OK==userDbInit(tclcmd.interp, psx) ){
+          UserDb *pudb = udbCreate(tclcmd.interp, psx);
+          pShExtLink->extensionDestruct = (void (*)(void*))udbCleanup;
+          pShExtLink->pvExtensionObject = pudb;
+        }
         pShExtApi->hookScripting(psx, sqlite3_tclshext_init, &sh);
         Tcl_CreateCommand(tclcmd.interp,
                           "get_input_line", getInputLine, psx, 0);
index bb0787a778cf52f5d40b2f0917bc72b4ce7d87b8..d79f156e619c65f4773e39c8ec5dad9b7b2d3586 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Sync\sw/trunk
-D 2022-03-25T20:39:09.123
+C In\sTCL\sshell\sextension,\sadd\sTCL\scommand\s"udb".\sThis\sgets\sthe\sTCL\senvironment\sto\snear\sparity\swith\spre-extended\sutility,\sexcept\sfor\squery\sresult\sdisplay\s(TBD\ssoon).
+D 2022-03-27T03:08:02.170
 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 d8f1a894edb7670341b8eb319686f4833f0d5545405c093d4d95b53966bbf5ff
+F ext/misc/tclshext.c.in b5d5cd1b1ade04ec4ed6346ac72ddb19dc7ba1082386475ca2b140c9cb779f67
 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 0c3dd6f397b01770c8c5366574b5fbccb59bfac88a09ecfc4f92a2ce5658d9ab x
-F src/shext_linkage.h 113e2e22378002999e297747b5651d3550cdd5b573a1977d274be5a7390d00c0
+F src/shell.c.in de495287d860d9d26150c09fa3cb6d0de020046618df05b17e74bcc6089d27f6 x
+F src/shext_linkage.h 4fd85ce40389c576b66cc2d142cb3b4f4e7c2c8c7fa5a8b6fe7cc7973dbb6495
 F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e
@@ -606,7 +606,7 @@ F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d
 F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b
 F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b
 F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe
-F src/test_shellext.c c864d87b4a1dc9fb47e8aeaa69d8a7f236a1346b1187e248634bb797afc3892a
+F src/test_shellext.c dabab63e70d3e0d5a911b3fb0c9a51a5b2c06ac79651553d46c2b99f3d482934
 F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a
 F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e
 F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939
@@ -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 57501e7fbda84a22cd8ab15beff8af4766a3851aa3c6c705484fe1eab808caa9 1a6328f2a5b4973094e5f85787145d652119822c86ec01a61f3f985c9d2903f2
-R c21a00b1e4b1c40d66b41b4b18ac61fa
+P 8402e5e78a83af3ba015bb6ab173350c1b73e04ce37ef933f9a847a7cf5e6157
+R 26d8ade198b7b866e1cc4ce801f7977d
 U larrybr
-Z a8882d9824db5961956d1a3e112d82e7
+Z a49fe933efd58515fcb9ecde174d1264
 # Remove this line to create a well-formed Fossil manifest.
index c06e0b1470027c1c49da77742fa93f466a44ac9a..141070bbe444be1e17dc4fb5c56aae8fd3b70d0e 100644 (file)
@@ -1 +1 @@
-8402e5e78a83af3ba015bb6ab173350c1b73e04ce37ef933f9a847a7cf5e6157
\ No newline at end of file
+c9aa76bf88401d193a536bc6576405aaad06681504996916b492962d890bc9e0
\ No newline at end of file
index 71d1fdcb2d73ef0347b4f10a527021d530ac0bcb..b9f9b100885d8e084edb895f3b5de0af4eb16b77 100755 (executable)
@@ -4690,13 +4690,13 @@ static int session_filter(void *pCtx, const char *zTab){
 #endif
 
 #if SHELL_DYNAMIC_EXTENSION
-static int notify_subscribers(ShellInState *psi, NoticeKind nk) {
+static int notify_subscribers(ShellInState *psi, NoticeKind nk, void *pvs) {
   int six = 0;
   int rcFlags = 0;
   ShellExState *psx = XSS(psi);
   while( six < psi->numSubscriptions ){
     struct EventSubscription *pes = psi->pSubscriptions + six++;
-    rcFlags |= pes->eventHandler(pes->pvUserData, nk, psx);
+    rcFlags |= pes->eventHandler(pes->pvUserData, nk, pvs, psx);
   }
   return rcFlags;
 }
@@ -5138,7 +5138,7 @@ static void open_db(ShellExState *psx, int openFlags){
       sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
     }
 #if SHELL_DYNAMIC_EXTENSION
-    notify_subscribers(psi, NK_DbUserAppeared);
+    notify_subscribers(psi, NK_DbUserAppeared, DBX(psx));
 #endif
   }
 }
@@ -5419,8 +5419,8 @@ static int sql_trace_callback(
 #endif
 
 /*
-** A no-op routine that runs with the ".breakpoint" doc-command.  This is
-** a useful spot to set a debugger breakpoint.
+** A no-op routine that runs with the ".breakpoint" doc-command.
+** This is a useful spot to set a debugger breakpoint.
 */
 static void test_breakpoint(void){
   static int nCall = 0;
@@ -7828,7 +7828,7 @@ static int subscribe_events(ShellExState *p, ExtensionId eid, void *pvUserData,
           && (eid!=0 ||eventHandler!=0 ||/* for shell use */ pvUserData==p ) ){
         int nLeft = pesLim - pesLim;
         assert(pes->eventHandler!=0);
-        pes->eventHandler(pes->pvUserData, NK_Unsubscribe, p);
+        pes->eventHandler(pes->pvUserData, NK_Unsubscribe, pes->eid, p);
         if( nLeft>1 ) memmove(pes, pes+1, (nLeft-1)*sizeof(*pes));
         --pesLim;
         --psi->numSubscriptions;
@@ -8392,11 +8392,11 @@ DISPATCHABLE_COMMAND( connection ? 1 4 ){
       psi->pAuxDb->db = DBI(psi);
       psi->pAuxDb = &psi->aAuxDb[i];
 #if SHELL_DYNAMIC_EXTENSION
-      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserVanishing);
+      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserVanishing, DBI(psi));
 #endif
       globalDb = DBI(psi) = psi->pAuxDb->db;
 #if SHELL_DYNAMIC_EXTENSION
-      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserAppeared);
+      if( DBI(psi)!=0 ) notify_subscribers(psi, NK_DbUserAppeared, DBI(psi));
 #endif
       psi->pAuxDb->db = 0;
     }
@@ -10194,10 +10194,10 @@ DISPATCHABLE_COMMAND( open 3 1 0 ){
   }
 
   /* Close the existing database */
+  session_close_all(psi, -1);
 #if SHELL_DYNAMIC_EXTENSION
-  if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbUserVanishing);
+  if( DBX(p)!=0 ) notify_subscribers(psi, NK_DbAboutToClose, DBX(p));
 #endif
-  session_close_all(psi, -1);
   close_db(DBX(p));
   DBX(p) = 0;
   psi->pAuxDb->zDbFilename = 0;
@@ -15106,10 +15106,10 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
  shell_bail:
   set_table_name(&datax, 0);
   if( datax.dbUser ){
+    session_close_all(&data, -1);
 #if SHELL_DYNAMIC_EXTENSION
-    notify_subscribers(&data, NK_DbUserVanishing);
+    notify_subscribers(&data, NK_DbAboutToClose, datax.dbUser);
 #endif
-    session_close_all(&data, -1);
     close_db(datax.dbUser);
   }
   sqlite3_mutex_free(pGlobalDbLock);
@@ -15127,7 +15127,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   clearTempFile(&data);
   sqlite3_free(data.zEditor);
 #if SHELL_DYNAMIC_EXTENSION
-  notify_subscribers(&data, NK_ShutdownImminent);
+  notify_subscribers(&data, NK_ShutdownImminent, 0);
   free_all_shext_tracking(&data);
   subscribe_events(&datax, 0, &datax, NK_Unsubscribe, 0);
   sqlite3_close(datax.dbShell);
@@ -15146,7 +15146,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   memset(&datax, 0, sizeof(datax));
   /* Process exit codes to yield single shell exit code.
    * rc == 2 is a quit signal, resulting in no error by itself.
-   * datax.shellAbruptExit conveyed either an normal (success or error)
+   * datax.shellAbruptExit conveyed either a normal (success or error)
    * exit code or an abnormal exit code. Its abnormal values take priority.
    */
   /* Check for an abnormal exit, and issue error if so. */
index 6e49a36f4db5239595ca680ecf6b2edf7a79abcd..ec72d5c9829b21b1ce7d602908471d8b7e9448f0 100644 (file)
@@ -68,14 +68,20 @@ typedef struct ShellExState {
   void (*freeHandlerData)(void *);
 
   /* The user's currently open and primary DB connection
-   * Extensions may use this DB, but must not modify this pointer.
-   */
+   * Extensions may use this DB, but must not modify this pointer
+   * and must never close the database. The shell is exclusively
+   * responsible for creation and termination of this connection.
+   * Extensions should not store a copy of this pointer without
+   * provisions for maintaining validity of the copy. The shell
+   * may alter this pointer apart from opening or closing a DB.
+   * See ShellEvenNotify, NoticeKind and subscribeEvents below
+   * for means of maintaining valid copies.  */
   sqlite3 *dbUser;
+
   /* DB connection for shell dynamical data and extension management
    * Extensions may use this DB, but should not alter content created
    * by the shell nor depend upon its schema. Names with prefix "Shell"
-   * or "shext_" are reserved for the shell's use.
-   */
+   * or "shext_" are reserved for the shell's use. */
   sqlite3 *dbShell;
 
   /* Output stream to which shell's text output to be written (reference) */
@@ -86,8 +92,7 @@ typedef struct ShellExState {
    * 0x100 => a non-error (0) exit
    * 0x100|other => exit with process exit code other
    * Any value greater than 0x1ff indicates an abnormal exit.
-   * For embedded shell, "exit" means "return from REPL function".
-   */
+   * For embedded shell, "exit" means "return from top-level REPL function". */
   int shellAbruptExit;
 
   /* Number of lines written during a query result output */
@@ -192,6 +197,7 @@ CONCRETE_END(Derived) vtname = { \
 
 /* This function pointer has the same signature as the sqlite3_X_init()
  * function that is called as SQLite3 completes loading an extension.
+ * It is used as a process-unique identifier for a loaded extention.
  */
 typedef int (*ExtensionId)
   (sqlite3 *, char **, const struct sqlite3_api_routines *);
@@ -204,7 +210,7 @@ typedef int (*ExtensionId)
  * 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;
+ * any 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
@@ -247,21 +253,30 @@ typedef struct ExtensionHelpers {
  * change value across successive shell versions, except for NK_CountOf. An
  * extension which is built to rely upon particular notifications can pass
  * an NK_CountOf value upon which it relies to subscribe(...) as nkMin,
- * which will fail if the hosting shell's NK_CountOf value is lower.
+ * which call will fail if the hosting shell's NK_CountOf value is lower.
  */
 typedef enum {
-  NK_Unsubscribe,      /* event handler is being unsubsribed
+  NK_Unsubscribe,      /* Event handler is being unsubsribed, pvSubject
+                        * is the ExtensionId used to subscribe. Sent last.
+                        * All event handlers eventually get this event, so
+                        * it can be used to free a handler's resources.
                         * Also passed to subscribeEvents(...) as nkMin
-                        * to unsubscribe event handler(s) */
-  NK_ShutdownImminent, /* a shell exit (or return) will soon occur */
-  NK_DbUserAppeared,   /* a new ShellExState .dbUser value has been set */
-  NK_DbUserVanishing,  /* current ShellExState .dbUser will soon vanish */
-  NK_CountOf           /* present count of preceding members (evolves) */
+                        * to unsubscribe some/all event handler(s). */
+  NK_ShutdownImminent, /* Shell or module will soon be shut down, pvSubject
+                        * is NULL. Event sent prior to above and extension
+                        * destructor calls, and sent after all below, */
+  NK_DbUserAppeared,   /* A new ShellExState .dbUser value has been set,
+                        * pvSubject is the newly set .dbUser value. */
+  NK_DbUserVanishing,  /* Current ShellExState .dbUser will soon vanish,
+                        * pvSubject is the vanishing .dbUser value. */
+  NK_DbAboutToClose,   /* A maybe-user-visible DB will soon be closed,
+                        * pvSubject is the sqlite3 pointer soon to close. */
+  NK_CountOf           /* Present count of preceding members (evolves) */
 } NoticeKind;
 
 /* Callback signature for shell event handlers. */
-typedef
-int (*ShellEventNotify)(void *pvUserData, NoticeKind nk, ShellExState *psx);
+typedef int (*ShellEventNotify)(void *pvUserData, NoticeKind nk,
+                                void *pvSubject, ShellExState *psx);
 
 /* Various shell extension helpers and feature registration functions */
 typedef struct ShellExtensionAPI {
@@ -269,7 +284,7 @@ typedef struct ShellExtensionAPI {
   ExtensionHelpers * pExtHelpers;
 
   /* Functions for an extension to register its implementors with shell */
-  const int numRegistrars; /* 4 for this version */
+  const int numRegistrars; /* 5 for this version */
   union {
     struct ShExtAPI {
       /* Register a meta-command */
@@ -284,20 +299,21 @@ typedef struct ShellExtensionAPI {
       /* Provide scripting support to host shell. (See ScriptHooks above.) */
       int (*hookScripting)(ShellExState *p,
                            ExtensionId eid, ScriptHooks *pSH);
-      /* Subscribe to (or unsubscribe from) messages about various changes. */
+      /* Subscribe to (or unsubscribe from) messages about various changes.
+       * See above NoticeKind enum and ShellEventNotify callback typedef. */
       int (*subscribeEvents)(ShellExState *p, ExtensionId eid, void *pvUserData,
                              NoticeKind nkMin, ShellEventNotify eventHandler);
       /* Preset to 0 at extension load, a sentinel for expansion */
       void (*sentinel)(void);
     } named;
-    void (*pFunctions[4])(); /* 0-terminated sequence of function pointers */
+    void (*pFunctions[5+1])(); /* 0-terminated sequence of function pointers */
   } api;
 } ShellExtensionAPI;
 
 /* Struct passed to extension init function to establish linkage. The
  * lifetime of instances spans only the init call itself. Extensions
  * should make a copy, if needed, of pShellExtensionAPI for later use.
- * Its referant is static, persisting for the process duration.
+ * Its referent is static, persisting for the process duration.
  */
 typedef struct ShellExtensionLink {
   int sizeOfThis;        /* sizeof(ShellExtensionLink) for expansion */
index 67a1d0fe972a9878917ca48db143b73870dd7d18..23125ab096feeda9f91ea4beaaefb15b37aae7f3 100644 (file)
@@ -93,7 +93,8 @@ static void sayHowMany( BatBeing *pbb, FILE *out, ShellExState *psx ){
   }
 }
 
-static int shellEventHandle(void *pv, NoticeKind nk, ShellExState *psx){
+static int shellEventHandle(void *pv, NoticeKind nk,
+                            void *pvSubject, ShellExState *psx){
   FILE *out = pExtHelpers->currentOutputFile(psx);
   if( nk==NK_ShutdownImminent ){
     BatBeing *pbb = (BatBeing *)pv;
@@ -103,7 +104,10 @@ static int shellEventHandle(void *pv, NoticeKind nk, ShellExState *psx){
     fprintf(out, "BatBeing incommunicado.\n");
   }else if( nk==NK_DbUserAppeared || nk==NK_DbUserVanishing ){
     const char *zWhat = (nk==NK_DbUserAppeared)? "appeared" : "vanishing";
-    fprintf(out, "dbUser (%p) %s\n", psx->dbUser, zWhat);
+    fprintf(out, "dbUser(%p) %s\n", pvSubject, zWhat);
+    if( psx->dbUser != pvSubject ) fprintf(out, "not dbx(%p)\n", psx->dbUser);
+  }else if( nk==NK_DbAboutToClose ){
+    fprintf(out, "db(%p) closing\n", pvSubject);
   }
   return 0;
 }