** 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.
+** 3. The .tcl and .unknown 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
** 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.
+** The added .unknown dot command overrides the shell's .unknown so
+** that new dot commands can be implemented in TCL and then be run
+** from the shell in the dot command execution context.
*/
#include "shext_linkage.h"
#endif
typedef struct TclCmd TclCmd;
-typedef struct EvalCmd EvalCmd;
+typedef struct UnkCmd UnkCmd;
-static void TclCmd_Takedown(TclCmd *ptc);
+static struct InterpManage {
+ Tcl_Interp *pInterp;
+ int nRefs;
+} interpKeep = { 0, 0 };
+
+static Tcl_Interp *getInterp(){
+ assert(interpKeep.nRefs>0 && interpKeep.pInterp!=0);
+ return interpKeep.pInterp;
+}
+
+static void Tcl_TakeDown(void *pv){
+ assert(pv==&interpKeep);
+ if( --interpKeep.nRefs==0 ){
+ if( interpKeep.pInterp ){
+ Tcl_DeleteInterp(interpKeep.pInterp);
+ interpKeep.pInterp = 0;
+ Tcl_Finalize();
+ }
+ }
+}
+
+static int Tcl_BringUp(char **pzErrMsg){
+ if( ++interpKeep.nRefs==1 ){
+ const char *zShellName = pExtHelpers->shellInvokedAs();
+ const char *zShellDir = pExtHelpers->shellStartupDir();
+ if( zShellDir!=0 ){
+ char cwd[FILENAME_MAX+1];
+ if( getDir(cwd) && 0==chdir(zShellDir) ){
+ int rc;
+ Tcl_FindExecutable(zShellName);
+ rc = chdir(cwd); /* result ignored, kept only to silence gcc */
+ }
+ }
+ interpKeep.pInterp = Tcl_CreateInterp();
+ Tcl_SetSystemEncoding(interpKeep.pInterp, "utf-8");
+ Sqlite3_Init(interpKeep.pInterp);
+ if( 0==Tcl_OOInitStubs(interpKeep.pInterp) ){
+ *pzErrMsg = sqlite3_mprintf("Tcl v8.6 or higher required.\n");
+ Tcl_TakeDown(&interpKeep);
+ return SQLITE_ERROR;
+ }
+ if( Tcl_Init(interpKeep.pInterp)!=TCL_OK ){
+ *pzErrMsg = sqlite3_mprintf("Tcl interpreter startup failed.\n");
+ Tcl_TakeDown(&interpKeep);
+ return SQLITE_ERROR;
+ }
+ }
+ return (interpKeep.pInterp!=0)? SQLITE_OK : SQLITE_ERROR;
+}
/* These DERIVED_METHOD(...) macro calls' arguments were copied and
* pasted from the MetaCommand interface declaration in shext_linkage.h
*/
DERIVED_METHOD(void, destruct, MetaCommand,TclCmd, 0, ()){
- TclCmd_Takedown((TclCmd *)pThis);
+ (void)(pThis);
}
-DERIVED_METHOD(void, destruct, MetaCommand,EvalCmd, 0, ()){
+DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 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 *, name, MetaCommand,UnkCmd, 0,()){
+ return "unknown";
}
DERIVED_METHOD(const char *, help, MetaCommand,TclCmd, 1,(int more)){
" 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"
+ " 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)){
+DERIVED_METHOD(const char *, help, MetaCommand,UnkCmd, 1,(int more)){
switch( more ){
case 0: return
- ".eval SQL ... Evaluate given SQL statements as shell does.\n";
+ ",unknown ?ARGS? Retry unknown dot command if it is a TCL command\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"
- ;
+ " There is little use for this dot command without the TCL extension,\n"
+ " as the shell's version merely does some error reporting. However,\n"
+ " with it overridden, (as it is now), it provides a retry mechanism\n"
+ " whereby, if the command can be found defined in the TCL environment,\n"
+ " that command can be run with whatever arguments it was provided.\n"
+ ;
default: return 0;
}
}
(char **pzErrMsg, int nArgs, char *azArgs[])){
return DCR_Ok;
}
-DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,EvalCmd, 3,
+DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,UnkCmd, 3,
(char **pzErrMsg, int nArgs, char *azArgs[])){
return DCR_Ok;
}
-static Tcl_Interp *getInterp(TclCmd *ptc);
-
static void copy_complaint(char **pzErr, Tcl_Interp *pi){
if( pzErr ){
Tcl_Obj *po = Tcl_GetObjResult(pi);
int rc = TCL_OK;
int aix;
for( aix=0; aix<(nArgs-1) && rc==TCL_OK; ++aix ){
- rc = Tcl_EvalFile(getInterp(ptc), azArgs[aix+1]);
+ rc = Tcl_EvalFile(getInterp(), azArgs[aix+1]);
}
if( rc!=TCL_OK ){
- copy_complaint(pzErrMsg, getInterp(ptc));
+ copy_complaint(pzErrMsg, getInterp());
return DCR_Error;
}
return DCR_Ok;
}else{
/* Enter a REPL */
- return runTclREPL(getInterp(ptc), pzErrMsg);
+ return runTclREPL(getInterp(), pzErrMsg);
}
}
-DERIVED_METHOD(DotCmdRC, execute, MetaCommand,EvalCmd, 4,
+DERIVED_METHOD(DotCmdRC, execute, MetaCommand,UnkCmd, 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;
+ Tcl_Interp *interp = getInterp();
+ Tcl_Obj **ppo;
+ char zName[50];
+ int ia, rc;
+
+ if( interp==0 || nArgs==0 ) return DCR_Unknown;
+
+ sqlite3_snprintf(sizeof(zName), zName, ".%s", azArgs[0]);
+ if( !Tcl_FindCommand(interp, zName, 0, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) ){
+ if( !pExtHelpers->nowInteractive(psx) ){
+ *pzErrMsg = sqlite3_mprintf("Command %s not found.\n", zName);
+ return DCR_Unknown;
+ }else{
+ FILE *out = pExtHelpers->currentOutputFile(psx);
+ fprintf(stderr, "The %s command does not yet exist.\n", zName);
+ fprintf(out, "Run .help to see existent dot commands,"
+ " or create %s as a TCL proc.\n", zName);
+ return DCR_CmdErred;
+ }
+ }
+ ppo = sqlite3_malloc((nArgs+1)*sizeof(Tcl_Obj*));
+ if( ppo==0 ) return TCL_ERROR;
+ for( ia=0; ia<nArgs; ++ia ){
+ ppo[ia] = Tcl_NewStringObj((ia)? azArgs[ia] : zName, -1);
+ Tcl_IncrRefCount(ppo[ia]);
+ }
+ rc = Tcl_EvalObjv(interp, nArgs, ppo, TCL_EVAL_GLOBAL);
+ for( ia=0; ia<nArgs; ++ia ) Tcl_DecrRefCount(ppo[ia]);
+ sqlite3_free(ppo);
+ /* Translate TCL return to a dot command return. */
+ switch( rc ){
+ case TCL_OK:
+ return DCR_Ok;
+ case TCL_ERROR:
+ *pzErrMsg = sqlite3_mprintf("%s\n", Tcl_GetStringResult(interp));
+ return DCR_Error;
+ case TCL_RETURN: case TCL_BREAK: case TCL_CONTINUE:
+ return DCR_Return;
+ default:
+ return DCR_Exit;
+ }
}
/* Define MetaCommand v-tables initialized to reference above methods. */
MetaCommand_IMPLEMENT_VTABLE(TclCmd, tclcmd_methods);
-MetaCommand_IMPLEMENT_VTABLE(EvalCmd, evalcmd_methods);
+MetaCommand_IMPLEMENT_VTABLE(UnkCmd, unkcmd_methods);
-/* Static instances are used because that suffices and makes the
- * interpreter easy to reference without going through pointers. */
+/* Static instances are used because that suffices. */
INSTANCE_BEGIN(TclCmd);
- Tcl_Interp *interp;
+ /* no instance data */
INSTANCE_END(TclCmd) tclcmd = {
&tclcmd_methods
- , 0 /* interp pointer */
};
-INSTANCE_BEGIN(EvalCmd);
+INSTANCE_BEGIN(UnkCmd);
/* no instance data */
-INSTANCE_END(EvalCmd) evalcmd = {
- &evalcmd_methods
+INSTANCE_END(UnkCmd) unkcmd = {
+ &unkcmd_methods
};
-static Tcl_Interp *getInterp(TclCmd *ptc){
- return ptc->interp;
-}
-
-static void TclCmd_Takedown(TclCmd *ptc){
- Tcl_DeleteInterp(ptc->interp);
- ptc->interp = 0;
- Tcl_Finalize();
-}
-
/* 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
static DotCmdRC tclRunScript(void *pvState, const char *zScript,
ShellExState *p, char **pzErrMsg){
char c;
- TclCmd *ptc = (TclCmd *)pvState;
+ struct InterpManage *pim = (struct InterpManage *)pvState;
+ Tcl_Interp *interp = pim->pInterp;
+ if( interp==0 ) return DCR_Error;
while( (c=*zScript++) && (c==' '||c=='\t') ) {}
if( c=='.' && *zScript++=='.' ){
int rc, nc = strlen30(zScript);
case '.':
/* Three dots, assume user meant to run a dot command. */
one_shot_tcl:
- rc = Tcl_EvalEx(ptc->interp, zScript, /* needs no adjustment */
+ rc = Tcl_EvalEx(interp, zScript, /* needs no adjustment */
nc, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
- if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp(ptc));
+ if( rc!=TCL_OK ) copy_complaint(pzErrMsg, getInterp());
break;
case ' ': case '\t':
/* Two dots then whitespace, it's a TCL one-shot command. */
/* 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);
+ return runTclREPL(interp, pzErrMsg);
default:
/* Two dots then dark not dot, may be a dot command. */
if( *zScript>='a' && *zScript<='z' ){
* By design, this combination is never returned:
* { Empty 1 } => no input collected but valid TCL
*/
-static int getInputLineGroup(void *pvSS, Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]){
+static int getTclGroup(void *pvSS, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]){
if( objc==1 ){
static Prompts cueTcl = { "tcl% ", " > " };
ShellExState *psx = (ShellExState *)pvSS;
}
rc = Tcl_EvalObjv(interp, nArgs, ppo, TCL_EVAL_GLOBAL);
for( ia=0; ia<nArgs; ++ia ) Tcl_DecrRefCount(ppo[ia]);
+ sqlite3_free(ppo);
return rc;
}else{
/* Fail now (instead of recursing back into this handler.) */
pShExtLink = shext_link(db);
if( pShExtLink && pShExtLink->pShellExtensionAPI->numRegistrars>=1 ){
ShellExState *psx = pShExtLink->pSXS;
- MetaCommand *pmc = (MetaCommand *)&tclcmd;
- MetaCommand *pec = (MetaCommand *)&evalcmd;
- const char *zShellName, *zShellDir;
- int rc;
+ int rc = 0;
pShExtApi = & pShExtLink->pShellExtensionAPI->api.named;
pExtHelpers = & pShExtLink->pShellExtensionAPI->pExtHelpers->helpers.named;
*pzErrMsg = sqlite3_mprintf("Shell version mismatch");
return SQLITE_ERROR;
}
- zShellName = pExtHelpers->shellInvokedAs();
- zShellDir = pExtHelpers->shellStartupDir();
- if( zShellDir!=0 ){
- char cwd[FILENAME_MAX+1];
- if( getDir(cwd) && 0==chdir(zShellDir) ){
- Tcl_FindExecutable(zShellName);
- rc = chdir(cwd); /* result ignored, kept only to silence gcc */
+ rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init,
+ (MetaCommand *)&unkcmd);
+ rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init,
+ (MetaCommand *)&tclcmd);
+ if( rc==SQLITE_OK && (rc = Tcl_BringUp(pzErrMsg))==SQLITE_OK ){
+ Tcl_Interp *interp = getInterp();
+ ScriptHooks sh = {
+ &interpKeep, tclIsScriptLead, tclIsComplete, tclRunScript
+ };
+ if( TCL_OK==userDbInit(interp, psx) ){
+ UserDb *pudb = udbCreate(interp, psx);
+ pShExtLink->extensionDestruct = (void (*)(void*))udbCleanup;
+ pShExtLink->pvExtensionObject = pudb;
}
- }
- tclcmd.interp = Tcl_CreateInterp();
- Tcl_SetSystemEncoding(tclcmd.interp, "utf-8");
- Sqlite3_Init(tclcmd.interp);
- if( 0==Tcl_OOInitStubs(tclcmd.interp) ){
- *pzErrMsg = sqlite3_mprintf("Tcl v8.6 or higher required.\n");
- 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 };
- 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);
-#if TCL_REPL==2
- Tcl_CreateCommand(tclcmd.interp,
- "get_input_line", getInputLine, psx, 0);
+ pShExtApi->hookScripting(psx, sqlite3_tclshext_init, &sh);
+#if TCL_REPL==1 || TCL_REPL==2
+ Tcl_CreateCommand(interp, "get_input_line", getInputLine, psx, 0);
#endif
#if TCL_REPL==3
- Tcl_CreateObjCommand(tclcmd.interp,
- "get_tcl_group", getInputLineGroup, psx, 0);
- Tcl_Eval(tclcmd.interp, zDefineREPL);
+ Tcl_CreateObjCommand(interp, "get_tcl_group", getTclGroup, psx, 0);
+ Tcl_Eval(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);
- rc = SQLITE_ERROR;
- }
+ Tcl_CreateCommand(interp, "now_interactive", nowInteractive, psx, 0);
+ /* Rename unknown so that calls to it can be intercepted. */
+ Tcl_Eval(interp, "rename unknown "UNKNOWN_RENAME);
+ Tcl_CreateCommand(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(interp, "proc .. {} {}");
+ pShExtLink->eid = sqlite3_tclshext_init;
+ }
+ if( rc==SQLITE_OK ){
+ pShExtLink->extensionDestruct = Tcl_TakeDown;
+ pShExtLink->pvExtensionObject = &interpKeep;
}else{
- TclCmd_Takedown(&tclcmd);
+ Tcl_TakeDown(&interpKeep);
}
return rc;
}
-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
+C For\sTCL\sextension:\n\s\sCleanup\sTCL\slibrary\sinteraction\sand\sinterpreter\smanagement.\n\s\sGroup\scode\sinto\smore\spurpose\soriented\sfunctions.\n\s\sAdd\s.unknown\sdot\scommand,\sdelegating\sto\sTCL\scommands\swith\s"dot"\snames.\n\s\sDrop\splanned\s.eval\snew\sdot\scommand.\nFor\sshell:\n\s\sImplement\sundocumented\s.unknown\sdot\scommand,\sdoing\slittle.\n\s\sMake\sdispatcher\scall\s.unknown's\simplementation\sfor\sunknown\sdot\scommand.\n\s\sFix\sa\scommand\slookup\sbug\sexposed\sby\sabove\schange\sto\sunknown\shandling.\n\s\sAdd\s.eval\sdot\scommand.\n\s\sAdd\soptions\sto\s.x\scommand\saimed\sat\scontrol\sof\serrors\sin\scommand\ssequences.\s(a\sWIP)\n\s\sMake\sdispatcher\sreport\sambiguous\sdot\scommand\slookups,\seven\swith\sno\sextension.\n
+D 2022-03-29T21:11:30.607
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 db6ec20db5c11f8ca808232d569ed9cd6173ae4d5de49ddee891b980d1a181ff
+F ext/misc/tclshext.c.in f3da6a8ff02e335a021ac16d0164e7dc4d4d38691811e37419ed07289e8b181a
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 15c883304f6a757ec9b6c4e2f6720a5d9a2066ed0b88348a36bcf5c1b11c1f7c x
+F src/shell.c.in d0e2c2417ed895d40af402eea3a2a38d462c6b17a654dc8e33c417a9ff355d29 x
F src/shext_linkage.h c70f95dd0738c2cd8452ab5c47c245d7fa3b99ec26cd160c67dfb4489d5dacf9
F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 41cc84336bbf7bc64492c24e1bf5be0fccbb8a3db57498070b624af8818d0075
-R a6fcd98d9f55917ef71d90740fb27226
+P 4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
+R 9fd24b1d2a3d7020ba2ddb1e81bfc0a5
U larrybr
-Z 385f3d338e553fbadafb70a790af574d
+Z 7b481df3c4b2b2c70f760eb720278652
# Remove this line to create a well-formed Fossil manifest.
-4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
\ No newline at end of file
+7616a6f4abe20699e5aa66018233247aa94e3e87f3e6ebe1357f776a18115eb3
\ No newline at end of file
#endif
/* ctype macros that work with signed characters */
-#define IsSpace(X) isspace((unsigned char)X)
-#define IsDigit(X) isdigit((unsigned char)X)
-#define ToLower(X) (char)tolower((unsigned char)X)
+#define IsSpace(X) isspace((unsigned char)(X))
+#define IsDigit(X) isdigit((unsigned char)(X))
+#define ToLower(X) (char)tolower((unsigned char)(X))
#if defined(_WIN32) || defined(WIN32)
#if SQLITE_OS_WINRT
0, 0, &zErr);
rc2 = sqlite3_exec(psx->dbShell, "CREATE VIEW ActiveCommands AS SELECT "
"s.name AS name, max(s.extIx) AS extIx, s.cmdIx AS cmdIx "
- "FROM ShellCommands s GROUP BY name,extIx",
+ "FROM ShellCommands s GROUP BY name",
0, 0, &zErr);
if( rc1!=SQLITE_OK || rc2!=SQLITE_OK || zErr!=0 ){
utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "");
return DCR_Ok|rc;
}
-/* The undocumented ".breakpoint" command causes a call
-** to the no-op routine named test_breakpoint().
+/* The ".breakpoint" command causes a call to the no-op routine named
+ * test_breakpoint(). It is undocumented.
*/
+COLLECT_HELP_TEXT[
+ ",breakpoint calls test_breakpoint(). (a debugging aid)",
+];
DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){
test_breakpoint();
return DCR_Ok;
}
/*****************
- * The .read, .recover and .restore commands
+ * The .recover and .restore commands
*/
CONDITION_COMMAND( recover !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) );
COLLECT_HELP_TEXT[
- ".read FILE Read input from FILE",
- " If FILE begins with \"|\", it is a command that generates the input.",
".recover Recover as much data as possible from corrupt db.",
" --freelist-corrupt Assume the freelist is corrupt",
" --recovery-db NAME Store recovery metadata in database file NAME",
" that are not also INTEGER PRIMARY KEYs",
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
];
-DISPATCHABLE_COMMAND( read 3 2 2 ){
- DotCmdRC rc = DCR_Ok;
- FILE *inUse = 0;
- int (*fCloser)(FILE *) = 0;
- if( ISS(p)->bSafeMode ) return DCR_AbortError;
- if( azArg[1][0]=='|' ){
-#ifdef SQLITE_OMIT_POPEN
- *pzErr = smprintf("pipes are not supported in this OS\n");
- rc = DCR_Error;
- /* p->out = STD_OUT; This was likely not needed. To be investigated. */
-#else
- inUse = popen(azArg[1]+1, "r");
- if( inUse==0 ){
- *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
- rc = DCR_Error;
- }else{
- fCloser = pclose;
- }
-#endif
- }else if( (inUse = openChrSource(azArg[1]))==0 ){
- *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
- rc = DCR_Error;
- }else{
- fCloser = fclose;
- }
- if( inUse!=0 ){
- InSource inSourceRedir
- = INSOURCE_FILE_REDIR(inUse, azArg[1], ISS(p)->pInSource);
- ISS(p)->pInSource = &inSourceRedir;
- rc = process_input(ISS(p));
- /* If error(s) occured during process, leave complaining to them. */
- if( rc==DCR_Error ) rc = DCR_CmdErred;
- assert(fCloser!=0);
- fCloser(inUse);
- ISS(p)->pInSource = inSourceRedir.pFrom;
- }
- return rc;
-}
/*
** This command is invoked to recover data from the database. A script
return DCR_Ok;
}
+/*****************
+ * The .unknown command (undocumented)
+ */
+COLLECT_HELP_TEXT[
+ ",unknown ?ARGS? Handle attempt to invoke an unknown dot command",
+];
+DISPATCHABLE_COMMAND( unknown ? 1 0 ){
+ /* Dispatcher will call this after replacing azArg[0] with the mystery
+ * command name, leaving remaining arguments as passed to dispatcher.
+ * An extension may override this to provide new commands dynamically.
+ * This name and operation were inspired by a similar TCL feature. */
+ return DCR_Unknown|0;
+}
+
/*****************
* The .unmodule command
*/
}
/*****************
- * The .x command
+ * The .x .read and .eval commands
+ * These are together because they share some function and implementation.
*/
+
COLLECT_HELP_TEXT[
- ".x NAMES ... Excecute content of some .parameter set variable(s)",
- " Only variables whose name begins with a letter are eligible for this.",
+ ".eval ?ARGS? Process each ARG's content as shell input.",
+ ".read FILE Read input from FILE",
+ " If FILE begins with \"|\", it is a command that generates the input.",
+ ".x ?OBJS or FLAGS? ... Excecute content of objects as shell input",
+ " FLAGS can be any of {-p -s -f} specifying what subsequent arguments are.",
+ " Arguments after -p are keys in a key/value table kept by the shell DB,",
+ " for which the object content to be executed is the corresponding value.",
+ " Arguments after -f name either files (or pipes), which are to be read",
+ " and the content executed. Input pipe names begin with '|'; the rest is",
+ " an OS-shell command which can be run by the OS shell to produce output.",
+ " Arguments after -s are strings, content of which is to be executed.",
+ " The default in effect for arguments prior to any FLAG is -p .",
];
-DISPATCHABLE_COMMAND( x ? 1 0 ){
- int ia, rc, nErrors = 0;
- sqlite3_stmt *pStmt = 0;
- sqlite3 *db;
- DotCmdRC rv = DCR_Ok;
- open_db(p, 0);
- db = DBX(p);
- if( db==0 ){
- utf8_printf(STD_ERR, ".x can only be done with a database open.\n");
- return DCR_Error;
+/* Return an allocated string with trailing whitespace trimmed except
+ * for a trailing newline. If empty (or OOM), return 0. Otherwise, the
+ * caller must eventually pass the return to sqlite3_free().
+ */
+static char *zPrepForEval(const char *zVal, int ntc){
+ int ixNewline = 0;
+ char c;
+ while( ntc>0 && IsSpace(c = zVal[ntc-1]) ){
+ if( c=='\n' ) ixNewline = ntc-1;
+ --ntc;
+ }
+ if( ntc>0 ){
+ /* The trailing newline (or some other placeholder) is important
+ * because one (or some other character) will likely be put in
+ * its place during process_input() line/group handling, along
+ * with a terminating NUL character. Without it, the NULL could
+ * land past the end of the allocation made just below.
+ */
+ int nle = ixNewline>0;
+ return smprintf( "%.*s%s", ntc, zVal, "\n"+nle );
+ }else{
+ return 0;
}
- if( sqlite3_table_column_metadata(db, PARAM_TABLE_SCHEMA,
- PARAM_TABLE_NAME,
- "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){
- utf8_printf(STD_ERR, "No "PARAM_TABLE_SNAME" table exists.\n");
- return DCR_Error;
+}
+
+/* Evaluate a string as input to the CLI.
+ * zName is the name to be given to the source for error reporting.
+ * Return usual dot command return codes as filtered by process_input().
+ * No provision is made for error emission because, presumably, that
+ * has been done by whatever dot commands or SQL execution is invoked.
+ */
+static DotCmdRC shellEvalText(char *zIn, const char *zName, ShellExState *psx){
+ DotCmdRC rv;
+ ShellInState *psi = ISS(psx);
+ InSource inRedir
+ = INSOURCE_STR_REDIR(zIn, zName, psi->pInSource);
+ psi->pInSource = &inRedir;
+ rv = process_input(psi);
+ psi->pInSource = inRedir.pFrom;
+ return rv;
+}
+
+DISPATCHABLE_COMMAND( eval 3 1 0 ){
+ DotCmdRC rv = DCR_Ok;
+ int ia = 1;
+ int nErrors = 0;
+ while( ia < nArg ){
+ char *zA = azArg[ia++];
+ int nc = strlen30(zA);
+ char *zSubmit = zPrepForEval(zA, nc);
+ if( zSubmit ){
+ char zName[] = "eval arg[999]";
+ sqlite3_snprintf(sizeof(zName), zName,"eval arg[%d]", ia-1);
+ rv = shellEvalText(zSubmit, zName, p);
+ sqlite3_free(zSubmit);
+ if( rv<DCR_ArgIxMask ){
+ nErrors += (rv & DCR_Error);
+ /* Handle DCR_Return, DCR_Exit or DCR_Abort */
+ if( rv>DCR_Error ) break;
+ }
+ }
}
- rc = sqlite3_prepare_v2(db, "SELECT value FROM "PARAM_TABLE_SNAME
- " WHERE key=$1 AND uses=1", -1, &pStmt, 0);
- if( rc!=SQLITE_OK ){
- utf8_printf(STD_ERR, PARAM_TABLE_SNAME" is wrongly created.\n");
- return DCR_Error;
+ rv |= (nErrors>0);
+ /* If error to be returned, indicate that complaining about it is done. */
+ return (rv==DCR_Error)? DCR_CmdErred : rv;
+}
+
+DISPATCHABLE_COMMAND( read 3 2 2 ){
+ DotCmdRC rc = DCR_Ok;
+ FILE *inUse = 0;
+ int (*fCloser)(FILE *) = 0;
+ if( ISS(p)->bSafeMode ) return DCR_AbortError;
+ if( azArg[1][0]=='|' ){
+#ifdef SQLITE_OMIT_POPEN
+ *pzErr = smprintf("pipes are not supported in this OS\n");
+ rc = DCR_Error;
+ /* p->out = STD_OUT; This was likely not needed. To be investigated. */
+#else
+ inUse = popen(azArg[1]+1, "r");
+ if( inUse==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
+ rc = DCR_Error;
+ }else{
+ fCloser = pclose;
+ }
+#endif
+ }else if( (inUse = openChrSource(azArg[1]))==0 ){
+ *pzErr = smprintf("cannot open \"%s\"\n", azArg[1]);
+ rc = DCR_Error;
+ }else{
+ fCloser = fclose;
+ }
+ if( inUse!=0 ){
+ InSource inSourceRedir
+ = INSOURCE_FILE_REDIR(inUse, azArg[1], ISS(p)->pInSource);
+ ISS(p)->pInSource = &inSourceRedir;
+ rc = process_input(ISS(p));
+ /* If error(s) occured during process, leave complaining to them. */
+ if( rc==DCR_Error ) rc = DCR_CmdErred;
+ assert(fCloser!=0);
+ fCloser(inUse);
+ ISS(p)->pInSource = inSourceRedir.pFrom;
}
+ return rc;
+}
+
+DISPATCHABLE_COMMAND( x ? 1 0 ){
+ int ia, nErrors = 0;
+ sqlite3_stmt *pStmt = 0;
+ sqlite3 *db = 0;
+ DotCmdRC rv = DCR_Ok;
+ enum { AsParam, AsString, AsFile } evalAs = AsParam;
+
for( ia=1; ia < nArg; ++ia ){
- if( isalpha(azArg[ia][0]) ){
- rc = sqlite3_reset(pStmt);
- rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
- rc = sqlite3_step(pStmt);
- if( rc==SQLITE_ROW ){
- const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
- int nb = sqlite3_column_bytes(pStmt, 0);
- while( nb>0 && IsSpace(zValue[nb-1]) ) --nb;
- if( nb>0 ){
- /* The trailing newline (or some other placeholder) is important
- * because one (or some other character) will likely be put in
- * its place during process_input() line/group handling, along
- * with a terminating NUL character. Without it, the NULL could
- * land past the end of the allocation made just below.
- */
- int nle = zValue[nb-1]=='\n';
- char *zSubmit = smprintf( "%.*s%s", nb, zValue, "\n"+nle );
+ char *zSubmit = 0;
+ const char *zOpt = azArg[ia];
+ if ( *zOpt == '-' ){
+ static const char *azOpts[] = { "p", "s", "f" };
+ int io = ArraySize(azOpts);
+ while( io > 0 ){
+ if( optionMatch(zOpt, azOpts[--io]) ){
+ evalAs = io;
+ zOpt = 0;
+ break;
+ }
+ }
+ if( zOpt==0 ) continue;
+ }
+ switch( evalAs ){
+ case AsParam:
+ if( pStmt==0 ){
+ int rc;
+ open_db(p, 0);
+ db = DBX(p);
+ if( db==0 ){
+ utf8_printf(STD_ERR, ".x can only be done with a database open.\n");
+ return DCR_Error;
+ }
+ if( sqlite3_table_column_metadata(db, PARAM_TABLE_SCHEMA,
+ PARAM_TABLE_NAME, "key",
+ 0, 0, 0, 0, 0)!=SQLITE_OK ){
+ utf8_printf(STD_ERR, "No "PARAM_TABLE_SNAME" table exists.\n");
+ return DCR_Error;
+ }
+ rc = sqlite3_prepare_v2(db, "SELECT value FROM "PARAM_TABLE_SNAME
+ " WHERE key=$1 AND uses=1", -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ utf8_printf(STD_ERR, PARAM_TABLE_SNAME" is wrongly created.\n");
+ return DCR_Error;
+ }
+ }
+ if( isalpha(azArg[ia][0]) ){
+ int rc = sqlite3_reset(pStmt);
+ rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0);
+ rc = sqlite3_step(pStmt);
+ if( rc==SQLITE_ROW ){
ShellInState *psi = ISS(p);
- InSource inRedir
- = INSOURCE_STR_REDIR(zSubmit, azArg[ia], psi->pInSource);
+ const unsigned char *zValue = sqlite3_column_text(pStmt, 0);
+ int nb = sqlite3_column_bytes(pStmt, 0);
+ zSubmit = zPrepForEval((const char *)zValue, nb);
sqlite3_reset(pStmt); /* End the parameter read to unlock DB. */
- shell_check_oom(zSubmit);
- psi->pInSource = &inRedir;
- rv = process_input(psi);
- if( rv<DCR_ArgIxMask ){
- nErrors += (rv & DCR_Error);
- /* Handle DCR_Return, DCR_Exit or DCR_Abort */
- if( rv>DCR_Error ) break;
+ if( zSubmit ){
+ rv = shellEvalText(zSubmit, azArg[ia], p);
+ sqlite3_free(zSubmit);
+ if( rv<DCR_ArgIxMask ){
+ nErrors += (rv & DCR_Error);
+ /* Handle DCR_Return, DCR_Exit or DCR_Abort */
+ if( rv>DCR_Error ) break;
+ }
+ }else{
+ continue; /* All white (or OOM), ignore. */
}
- sqlite3_free(zSubmit);
- psi->pInSource = inRedir.pFrom;
}else{
- continue; /* All white, ignore. */
+ utf8_printf(STD_ERR,
+ "Skipping parameter '%s' (not set and executable.)\n",
+ azArg[ia]);
+ ++nErrors;
}
}else{
utf8_printf(STD_ERR,
- "Skipping parameter '%s' (not set and executable.)\n",
- azArg[ia]);
+ "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
++nErrors;
}
+ break;
+ case AsString:
+ {
+ zSubmit = zPrepForEval(zOpt, strlen30(zOpt));
+ if( zSubmit ){
+ char zName[] = "x arg[999]";
+ sqlite3_snprintf(sizeof(zName), zName,"x arg[%d]", ia);
+ rv = shellEvalText(zSubmit, zName, p);
+ sqlite3_free(zSubmit);
+ }
+ }
+ break;
+ case AsFile:
+ {
+ char *av[] = {"read", (char*)zOpt};
+ rv = readCommand(av, ArraySize(av), p, pzErr);
+ }
+ break;
+ }
+ if( rv<DCR_ArgIxMask ){
+ nErrors += (rv & DCR_Error);
+ /* Handle DCR_Return, DCR_Exit or DCR_Abort */
+ if( rv>DCR_Error ) break;
}else{
- utf8_printf(STD_ERR,
- "Skipping badly named %s. Run \".help x\"\n", azArg[ia]);
++nErrors;
+ rv = DCR_Error;
}
}
sqlite3_finalize(pStmt);
sqlite3_finalize(pStmt);
if( rc!= SQLITE_ROW ) return 0;
if( pnFound ) *pnFound = nf;
- if( nf!=1 ) return 0; /* Future: indicate ambiguity if > 1 */
+ if( nf!=1 ) return 0; /* Future: indicate collisions if > 1 */
return command_by_index(ISS(psx), extIx, cmdIx);
}else
#endif
}else if( md<0 ){
ixe = ixm-1;
}else{
- if( command_table[ixm].minLen > cmdLen ) return 0;
+ /* Have a match, see whether it's ambiguous. */
+ if( command_table[ixm].minLen > cmdLen ){
+ if( (ixm>0
+ && !strncmp(cmdName, command_table[ixm-1].cmdName, cmdLen))
+ ||
+ (ixm<ixe
+ && !strncmp(cmdName, command_table[ixm+1].cmdName, cmdLen)) ){
+ /* Yes, a neighbor matches too. */
+ if( pnFound ) *pnFound = 2; /* could be more, but >1 suffices */
+ return 0;
+ }
+ }
pci = &command_table[ixm];
+ if( pnFound ) *pnFound = 1;
break;
}
}
- if( pnFound ) *pnFound = 1;
+ if( pnFound && pci ) *pnFound = 1;
return (MetaCommand *)pci;
}
}
*/
static DotCmdRC runMetaCommand(MetaCommand *pmc, char *azArg[], int nArg,
ShellExState *psx){
- char *arg0 = azArg[0];
char *zErr = 0;
DotCmdRC dcr = pmc->pMethods->argsCheck(pmc, &zErr, nArg, azArg);
command_prep(ISS(psx));
- azArg[0] = (char *)(pmc->pMethods->name(pmc));
if( dcr==DCR_Ok ){
dcr = pmc->pMethods->execute(pmc, psx, &zErr, nArg, azArg);
}
if( dcr!=DCR_Ok ){
dcr = meta_command_errors(zErr, azArg, nArg, dcr, psx);
}
- azArg[0] = arg0;
sqlite3_free(zErr);
command_post(ISS(psx));
return dcr;
int nFound;
MetaCommand *pmc = findMetaCommand(azArg[0], psx, &nFound);
if( pmc==0 || nFound>1 ){
- dcr = (nFound>1)? DCR_Ambiguous: DCR_Unknown;
- /* Issue error for an unknown or inadequately specified dot command. */
- dcr = meta_command_errors(0, azArg, nArg, dcr, psx);
+ if( nFound==0 ){
+ pmc = findMetaCommand("unknown", psx, &nFound);
+ if( pmc && nFound<2 ) dcr = runMetaCommand(pmc, azArg, nArg, psx);
+ else dcr = DCR_Unknown;
+ }else{
+ dcr = DCR_Ambiguous;
+ }
+ if( dcr > DCR_ArgIxMask ){
+ /* Issue error for unknown or inadequately specified dot command. */
+ dcr = meta_command_errors(0, azArg, nArg, dcr, psx);
+ }
}
else{
+ char *arg0 = azArg[0];
+ azArg[0] = (char *)(pmc->pMethods->name(pmc));
/* Run found command and issue or handle any errors it may report. */
dcr = runMetaCommand(pmc, azArg, nArg, psx);
+ azArg[0] = arg0;
}
}