]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
For TCL extension:
authorlarrybr <larrybr@noemail.net>
Tue, 29 Mar 2022 21:11:30 +0000 (21:11 +0000)
committerlarrybr <larrybr@noemail.net>
Tue, 29 Mar 2022 21:11:30 +0000 (21:11 +0000)
  Cleanup TCL library interaction and interpreter management.
  Group code into more purpose oriented functions.
  Add .unknown dot command, delegating to TCL commands with "dot" names.
  Drop planned .eval new dot command.
For shell:
  Implement undocumented .unknown dot command, doing little.
  Make dispatcher call .unknown's implementation for unknown dot command.
  Fix a command lookup bug exposed by above change to unknown handling.
  Add .eval dot command.
  Add options to .x command aimed at control of errors in command sequences. (a WIP)
  Make dispatcher report ambiguous dot command lookups, even with no extension.

FossilOrigin-Name: 7616a6f4abe20699e5aa66018233247aa94e3e87f3e6ebe1357f776a18115eb3

ext/misc/tclshext.c.in
manifest
manifest.uuid
src/shell.c.in

index f9ac21b123143b2320926040e0b3e55f8c1f0830..55558167614418312b235d0568b0c1f0eed9baaa 100644 (file)
@@ -19,7 +19,7 @@
 ** 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"
@@ -115,25 +114,73 @@ INCLUDE tclsqlite.c
 #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)){
@@ -149,23 +196,23 @@ 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;
   }
 }
@@ -174,13 +221,11 @@ DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,TclCmd, 3,
              (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);
@@ -338,53 +383,79 @@ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4,
     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
@@ -407,7 +478,9 @@ static int tclIsComplete(void *pvState, const char *zScript){
 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);
@@ -416,9 +489,9 @@ static DotCmdRC tclRunScript(void *pvState, const char *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. */
@@ -427,7 +500,7 @@ static DotCmdRC tclRunScript(void *pvState, const char *zScript,
       /* 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' ){
@@ -485,8 +558,8 @@ static int getInputLine(void *pvSS, Tcl_Interp *interp,
  * 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;
@@ -580,6 +653,7 @@ static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp,
       }
       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.) */
@@ -851,10 +925,7 @@ int sqlite3_tclshext_init(
   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;
@@ -862,60 +933,42 @@ int sqlite3_tclshext_init(
       *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;
   }
index a213c86ef463925b973d151a63a0d006edb5a1cb..0ede31bc2a4874ae4fccd458b3747b16acb40f42 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-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
@@ -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 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
@@ -556,7 +556,7 @@ 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 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
@@ -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 41cc84336bbf7bc64492c24e1bf5be0fccbb8a3db57498070b624af8818d0075
-R a6fcd98d9f55917ef71d90740fb27226
+P 4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
+R 9fd24b1d2a3d7020ba2ddb1e81bfc0a5
 U larrybr
-Z 385f3d338e553fbadafb70a790af574d
+Z 7b481df3c4b2b2c70f760eb720278652
 # Remove this line to create a well-formed Fossil manifest.
index 10d82be7d25f42acb636cc2314d945a9075cb1d8..9ad49d34f34245bffb87cdbd6683f073e850890f 100644 (file)
@@ -1 +1 @@
-4ee7df118033af368854992763d96f860f8bd218b28b9ad27dd7ad4291cabfd7
\ No newline at end of file
+7616a6f4abe20699e5aa66018233247aa94e3e87f3e6ebe1357f776a18115eb3
\ No newline at end of file
index 99c1a7b4dd8d6e53ef2f2279df2342c8e918879f..d14e7654c4e3cdb8b2db08948b3a65cf201a3f96 100755 (executable)
@@ -193,9 +193,9 @@ typedef unsigned short u16;
 #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
@@ -8037,7 +8037,7 @@ static int begin_db_dispatch(ShellExState *psx){
                      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 : "");
@@ -8350,9 +8350,12 @@ DISPATCHABLE_COMMAND( cd ? 2 2 ){
   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;
@@ -11028,12 +11031,10 @@ DISPATCHABLE_COMMAND( prompt ? 1 3 ){
 }
 
 /*****************
- * 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",
@@ -11042,44 +11043,6 @@ COLLECT_HELP_TEXT[
   "                            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
@@ -12772,6 +12735,20 @@ DISPATCHABLE_COMMAND( trace ? 0 0 ){
   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
  */
@@ -12973,81 +12950,234 @@ DISPATCHABLE_COMMAND( wheretrace ? 1 2 ){
 }
 
 /*****************
- * 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);
@@ -13256,7 +13386,7 @@ MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx,
     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
@@ -13272,12 +13402,24 @@ MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx,
       }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;
   }
 }
@@ -13530,19 +13672,16 @@ static DotCmdRC meta_command_errors(char *zErr, char *azArg[], int nArg,
  */
 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;
@@ -13601,13 +13740,24 @@ static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){
     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;
     }
   }