}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"
"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));
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);
#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;
}
}
-/* 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);
/*
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);
-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
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
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
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
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.
-8402e5e78a83af3ba015bb6ab173350c1b73e04ce37ef933f9a847a7cf5e6157
\ No newline at end of file
+c9aa76bf88401d193a536bc6576405aaad06681504996916b492962d890bc9e0
\ No newline at end of file
#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;
}
sqlite3_set_authorizer(DBX(psx), safeModeAuth, psx);
}
#if SHELL_DYNAMIC_EXTENSION
- notify_subscribers(psi, NK_DbUserAppeared);
+ notify_subscribers(psi, NK_DbUserAppeared, DBX(psx));
#endif
}
}
#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;
&& (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;
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;
}
}
/* 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;
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);
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);
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. */
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) */
* 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 */
/* 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 *);
* 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
* 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 {
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 */
/* 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 */
}
}
-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;
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;
}