From ccf4a2bc69140450b5ea71139d2b46de4423b648 Mon Sep 17 00:00:00 2001 From: larrybr Date: Sun, 27 Mar 2022 03:08:02 +0000 Subject: [PATCH] In TCL shell extension, add TCL command "udb". This gets the TCL environment to near parity with pre-extended utility, except for query result display (TBD soon). FossilOrigin-Name: c9aa76bf88401d193a536bc6576405aaad06681504996916b492962d890bc9e0 --- ext/misc/tclshext.c.in | 229 +++++++++++++++++++++++++++++++++++++++-- manifest | 18 ++-- manifest.uuid | 2 +- src/shell.c.in | 28 ++--- src/shext_linkage.h | 56 ++++++---- src/test_shellext.c | 8 +- 6 files changed, 288 insertions(+), 53 deletions(-) diff --git a/ext/misc/tclshext.c.in b/ext/misc/tclshext.c.in index 2362e6bb9a..a5a4127be5 100644 --- a/ext/misc/tclshext.c.in +++ b/ext/misc/tclshext.c.in @@ -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 && ixnumSdb); + /* 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( ixixSdb ) --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); diff --git a/manifest b/manifest index bb0787a778..d79f156e61 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index c06e0b1470..141070bbe4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -8402e5e78a83af3ba015bb6ab173350c1b73e04ce37ef933f9a847a7cf5e6157 \ No newline at end of file +c9aa76bf88401d193a536bc6576405aaad06681504996916b492962d890bc9e0 \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index 71d1fdcb2d..b9f9b10088 100755 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -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. */ diff --git a/src/shext_linkage.h b/src/shext_linkage.h index 6e49a36f4d..ec72d5c982 100644 --- a/src/shext_linkage.h +++ b/src/shext_linkage.h @@ -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 */ diff --git a/src/test_shellext.c b/src/test_shellext.c index 67a1d0fe97..23125ab096 100644 --- a/src/test_shellext.c +++ b/src/test_shellext.c @@ -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; } -- 2.47.3