TCL extension polished and more smoothly integrated. A couple of bugs related to takedown fixed. Help for added features. Code cleaned up and commented.

FossilOrigin-Name: 4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
This commit is contained in:
larrybr
2022-03-28 21:42:16 +00:00
parent 1a59843b39
commit c3a9ba08c1
5 changed files with 246 additions and 55 deletions
+194 -39
View File
@@ -16,29 +16,81 @@
-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 \".\"\n"
" is entered on an input line or end-of-stream is encountered.\n";
" 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
" 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);
+8 -8
View File
@@ -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.
+1 -1
View File
@@ -1 +1 @@
41cc84336bbf7bc64492c24e1bf5be0fccbb8a3db57498070b624af8818d0075
4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
+36 -5
View File
@@ -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);
sqlite3_close(datax.dbShell);
notify_subscribers(&data, NK_ShutdownImminent, 0);
/* 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);
/* Forcefull unsubscribe static extension event listeners. */
subscribe_events(&datax, 0, &datax, NK_Unsubscribe, 0);
#endif
#if !SQLITE_SHELL_IS_UTF8
+7 -2
View 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;