]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
(WIP) Add DB-dispatch for dot commands upon shell extension load.
authorlarrybr <larrybr@noemail.net>
Tue, 8 Mar 2022 16:44:55 +0000 (16:44 +0000)
committerlarrybr <larrybr@noemail.net>
Tue, 8 Mar 2022 16:44:55 +0000 (16:44 +0000)
FossilOrigin-Name: 19f2a747b8f4d84d8695beb9b23a455f1dead24c94aa90466510555a796027d7

manifest
manifest.uuid
src/shell.c.in
src/shext_linkage.h

index 8e4ca1a7ac9601a7d733cd6040324e9d6b05a58c..202128825b20cb982e1a1d385fc87a04d37256b3 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C CLI\sregularization\sand\scode\scleanup
-D 2022-03-08T01:02:37.922
+C (WIP)\sAdd\sDB-dispatch\sfor\sdot\scommands\supon\sshell\sextension\sload.
+D 2022-03-08T16:44:55.714
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -555,8 +555,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 4890a3cfee0bc60ff231c3a44db37968859ab0be156983dbcc0c096109832cdd
-F src/shell.c.in 52936caf57e2765e5647c70ccc8932fe510879f0abf2c331c1e83459e7778959
-F src/shext_linkage.h 76a8deb0ffc3b5ed4396f71fd7beb6c63477457d457cab873d7e2f6469819b13
+F src/shell.c.in be543c63ec129a39321dc1ba906f97913d849444bb5b0613c155861ed8db3891
+F src/shext_linkage.h d93e4c1df29eac40bdc268d006c9632d9235a6cfe7cb8bc3e04e9855b5ca8b26
 F src/sqlite.h.in e82ac380b307659d0892f502b742f825504e78729f4edaadce946003b9c00816
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6
@@ -1949,8 +1949,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 1194093a58978b99c7afee08483d0590ce9aff4647a46dc9a338f022647fdb42
-R 1cbe480cc9660306a182a58b06b04872
+P 16af0e4560349f720d4e2fbb4c08211e6a9a573c1fb4eb287d8bc2aefc3c7ce0
+R ba0fed400154a7ccccdadff6ecb8ddc3
 U larrybr
-Z eab8f0e1073c744044f30e201854ce42
+Z 5a4c35db10a1fd0fdc6db78a8ff5892d
 # Remove this line to create a well-formed Fossil manifest.
index b0eb5bb5810a9452c6271e0fa925221cce96eaaa..16e195cea37c6e549d89b3fb86fbf9f429c04b49 100644 (file)
@@ -1 +1 @@
-16af0e4560349f720d4e2fbb4c08211e6a9a573c1fb4eb287d8bc2aefc3c7ce0
\ No newline at end of file
+19f2a747b8f4d84d8695beb9b23a455f1dead24c94aa90466510555a796027d7
\ No newline at end of file
index cd2e731c106b69d7ba74ee9961ba1338c055bb9f..7380434a3d24a2e433a75a84b17ad61efa4efd79 100644 (file)
@@ -1218,6 +1218,31 @@ struct EQPGraph {
 # define SHEXT_VAREXP(psi) 0
 #endif
 
+#if SHELL_DYNAMIC_EXTENSION
+/* Tracking and use info for loaded shell extensions
+ * An instance is kept for each shell extension that is currently loaded.
+ * They are kept in a simple list (aka dynamic array), index into which
+ * is used internally to get the extension's object. These indices are
+ * kept in the dbShell and updated there as the list content changes.
+ */
+typedef struct ShExtInfo {
+  ExtensionId extId;        /* The xInit function pointer */
+  void *pExtHandle;         /* Used for unloading (if possible) */
+  void (*extDtor)(void *);  /* Extension shutdown on exit or unload */
+  /* Each shell extension library registers 0 or more of its extension
+   * implementations, interfaces to which are kept in below dynamic.
+   * arrays. The dbShell DB keeps indices into these arrays and into
+   * an array of this struct's instances to facilitate lookup by name
+   * of pointers to the implementations. */
+  int numMetaCommands;
+  MetaCommand **ppMetaCommands;
+  int numOutModeHandlers;
+  OutModeHandler **ppOutModeHandlers;
+  int numImportHandlers;
+  ImportHandler **ppImportHandlers;
+} ShExtInfo;
+#endif
+
 /* Parameters affecting columnar mode result display (defaulting together) */
 typedef struct ColModeOpts {
   int iWrap;            /* In columnar modes, wrap lines reaching this limit */
@@ -1339,6 +1364,10 @@ typedef struct ShellInState {
   EQPGraph sGraph;       /* Information for the graphical EXPLAIN QUERY PLAN */
   ExpertInfo expert;     /* Valid if previous command was ".expert OPT..." */
 
+#if SHELL_DYNAMIC_EXTENSION
+  int numExtLoaded;      /* Number of extensions presently loaded or emulated */
+  ShExtInfo *pShxLoaded; /* Tracking and use info for loaded shell extensions */
+#endif
   ShellExState *pSXS;    /* Pointer to companion, exposed shell state */
 } ShellInState;
 
@@ -4616,6 +4645,7 @@ DISPATCH_CONFIG[
 /* Forward references */
 static int showHelp(FILE *out, const char *zPattern, ShellExState *);
 static int process_input(ShellInState *psx);
+static MetaCommand *builtInCommand(int ix);
 
 /*
 ** Read the content of file zName into memory obtained from sqlite3_malloc64()
@@ -7797,6 +7827,118 @@ static void shell_linkage(
   }
 }
 
+#ifndef SHELL_DB_FILE
+# define SHELL_DB_STORE ":memory:"
+#else
+# define SHELL_DB_STORE SHELL_STRINGIFY(SHELL_DB_FILE)
+#endif
+
+static int begin_db_dispatch(ShellExState *psx){
+  ShellInState *psi = ISS(psx);
+  sqlite3_stmt *pStmt = 0;
+  int ic, rc;
+  char *zErr = 0;
+  const char *zSql;
+  ShExtInfo sei = {0};
+  /* Consider: Store these dynamic arrays in the DB as indexed-into blobs. */
+  assert(psx->dbShell==0 && psi->numExtLoaded==0 && psi->pShxLoaded==0);
+  psi->pShxLoaded = (ShExtInfo *)sqlite3_malloc(2*sizeof(ShExtInfo));
+  sei.ppMetaCommands
+    = (MetaCommand **)sqlite3_malloc((numCommands+2)*sizeof(MetaCommand *));
+  sei.ppOutModeHandlers
+    = (OutModeHandler **)sqlite3_malloc(2*sizeof(OutModeHandler *));
+  sei.ppImportHandlers
+    = (ImportHandler **)sqlite3_malloc(2*sizeof(ImportHandler *));
+  if( sei.ppMetaCommands==0||sei.ppOutModeHandlers==0||sei.ppImportHandlers==0
+      || psi->pShxLoaded==0 ){
+    shell_out_of_memory();
+  }
+  sei.numOutModeHandlers = 0;
+  sei.numImportHandlers = 0;
+  for( ic=0; ic<numCommands; ++ic ){
+    sei.ppMetaCommands[ic] = builtInCommand(ic);
+  }
+  sei.numMetaCommands = ic;
+  psi->pShxLoaded[psi->numExtLoaded++] = sei;
+  rc = sqlite3_open(SHELL_DB_STORE, &psx->dbShell);
+  if( rc!=SQLITE_OK ) return 1;
+#ifdef SHELL_DB_FILE
+  sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS ShellCommands", 0,0,0);
+#endif
+  rc = sqlite3_exec(psx->dbShell, "CREATE TABLE ShellCommands("
+                    "name TEXT, extIx INT, cmdIx INT,"
+                    "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID",
+                    0, 0, &zErr);
+  if( rc!=SQLITE_OK || zErr!=0 ){
+    utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "");
+    return 1;
+  }
+  zSql = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, 0, ?)";
+  rc = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0);
+  if( rc!=SQLITE_OK ) return 1;
+  rc = sqlite3_exec(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr);
+  for( ic=0; ic<sei.numMetaCommands; ++ic ){
+    MetaCommand *pmc = sei.ppMetaCommands[ic];
+    const char *zName = pmc->pMethods->name(pmc);
+    sqlite3_reset(pStmt);
+    sqlite3_bind_text(pStmt, 1, zName, -1, 0);
+    sqlite3_bind_int(pStmt, 2, ic);
+    rc = sqlite3_step(pStmt);
+    if( rc!=SQLITE_DONE ){
+      sqlite3_exec(psx->dbShell, "ABORT", 0, 0, 0);
+      break;
+    }
+  }
+  sqlite3_finalize(pStmt);
+  if( rc!=SQLITE_DONE ) return 1;
+  rc = sqlite3_exec(psx->dbShell, "COMMIT", 0, 0, &zErr);
+  sqlite3_enable_load_extension(psx->dbShell, 1);
+  return SQLITE_OK;
+}
+
+static void free_shext_tracking(ShellInState *psi){
+  int i, j;
+  if( psi->pShxLoaded!=0 ){
+    for( i=0; i<psi->numExtLoaded; ++i ){
+      ShExtInfo *psei = &psi->pShxLoaded[i];
+      if( psei->ppMetaCommands!=0 ){
+        if( i>0 ){ /* built-in commands need no freeing */
+          for( j=0; j<psei->numMetaCommands; ++j ){
+            MetaCommand *pmc = psei->ppMetaCommands[j];
+            if( pmc->pMethods->destruct!=0 ) pmc->pMethods->destruct(pmc);
+          }
+        }
+        sqlite3_free(psei->ppMetaCommands);
+      }
+      if( psei->ppOutModeHandlers!=0 ){
+        for( j=0; j<psei->numOutModeHandlers; ++j ){
+          OutModeHandler *pomh = psei->ppOutModeHandlers[j];
+          if( pomh->pMethods->destruct!=0 ) pomh->pMethods->destruct(pomh);
+        }
+        sqlite3_free(psei->ppOutModeHandlers);
+      }
+      if( psei->ppImportHandlers!=0 ){
+        for( j=0; j<psei->numImportHandlers; ++j ){
+          ImportHandler *pih = psei->ppImportHandlers[j];
+          if( pih->pMethods->destruct!=0 ) pih->pMethods->destruct(pih);
+        }
+        sqlite3_free(psei->ppImportHandlers);
+      }
+    }
+    sqlite3_free(psi->pShxLoaded);
+  }
+}
+
+static MetaCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){
+  if( extIx>=0 && extIx<psi->numExtLoaded ){
+    ShExtInfo *psei = & psi->pShxLoaded[extIx];
+    if( cmdIx>=0 && cmdIx<psei->numMetaCommands ){
+      return psei->ppMetaCommands[cmdIx];
+    }
+  }
+  return 0;
+}
+
 static int load_shell_extension(ShellExState *psx, const char *zFile,
                                 const char *zProc, char **pzErr){
   ShellExtensionLink shxLink = {
@@ -7808,9 +7950,9 @@ static int load_shell_extension(ShellExState *psx, const char *zFile,
   };
   int rc;
   if( psx->dbShell==0 ){
-    rc = sqlite3_open(":memory:", &psx->dbShell);
-    if( rc!=SQLITE_OK ) return 1;
-    sqlite3_enable_load_extension(psx->dbShell, 1);
+    rc = begin_db_dispatch(psx);
+    if( rc!=SQLITE_OK ) return rc;
+    assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0);
   }
   sqlite3_create_function(psx->dbShell, "shext_pointer", 1,
                           SQLITE_DIRECTONLY|SQLITE_UTF8,
@@ -12683,6 +12825,11 @@ static struct CommandInfo {
 static unsigned numCommands
   = sizeof(command_table)/sizeof(struct CommandInfo) - 1;
 
+static MetaCommand *builtInCommand(int ix){
+  if( ix<0 || ix>=numCommands ) return 0;
+  return (MetaCommand *)&command_table[ix];
+}
+
 static void MetaCommand_dtor(MetaCommand *pMe){
   UNUSED_PARAMETER(pMe);
 }
@@ -12718,6 +12865,7 @@ static int MetaCommand_execute(MetaCommand *pMe, ShellExState *pssx,
 ** returned MetaMatchIter must eventually be passed to freeMetaMatchIter().
 */
 typedef struct MetaMatchIter {
+  ShellExState *psx;
   /* 0 indicates prepared statement; non-0 is the glob pattern. */
   const char *zPattern;
   union {
@@ -12730,13 +12878,20 @@ typedef struct MetaMatchIter {
  * pointers whose referents names match the given cmdFragment. */
 static MetaMatchIter findMatchingMetaCmds(const char *cmdFragment,
                                           ShellExState *psx){
-  MetaMatchIter rv = { 0, 0 };
+  MetaMatchIter rv = { psx, 0, 0 };
   if( psx->dbShell==0 ){
     rv.zPattern = sqlite3_mprintf("%s*", cmdFragment? cmdFragment : "");
     shell_check_oom((void *)rv.zPattern);
     rv.pMC = (MetaCommand *)command_table;
   }else{
     /* Prepare rv.stmt to yield results glob-matching cmdFragment. */
+    /* ToDo: Verify this query is right in face of overridden commands. */
+    const char *zSql = "SELECT extIx, cmdIx FROM ("
+      " SELECT name,max(extIx),cmdIx FROM ShellCommands GROUP BY name,extIx"
+      ") WHERE name glob (?||'*')";
+    if( cmdFragment==0 ) cmdFragment = "";
+    sqlite3_prepare_v2(psx->dbShell, zSql, -1, &rv.stmt, 0);
+    sqlite3_bind_text(rv.stmt, 1, cmdFragment, -1, 0);
   }
   return rv;
 }
@@ -12752,7 +12907,15 @@ static MetaCommand * nextMatchingMetaCmd(MetaMatchIter *pMMI){
       if( rv!=0 ) break;
     }
   }else{
-    /* Future: Step the query finding matches once dbShell is loaded. */
+    int rc = sqlite3_step(pMMI->stmt);
+    if( rc==SQLITE_ROW ){
+      int extIx = sqlite3_column_int(pMMI->stmt, 0);
+      int cmdIx = sqlite3_column_int(pMMI->stmt, 1);
+      return command_by_index(ISS(pMMI->psx), extIx, cmdIx);
+    }else{
+      sqlite3_finalize(pMMI->stmt);
+      pMMI->stmt = 0;
+    }
   }
   return rv;
 }
@@ -12784,11 +12947,29 @@ static void freeMetaMatchIter(MetaMatchIter *pMMI){
 */
 MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx,
                              /* out */ int *nFound){
+  *nFound = 0;
+#if SHELL_DYNAMIC_EXTENSION
   if( psx->dbShell!=0 ){
-    /* Future: Actually look it up (once registration is working.) */
-    *nFound = 0;
-    return 0;
-  }else{
+    int rc;
+    int extIx = -1, cmdIx = -1, nf;
+    sqlite3_stmt *pStmt = 0;
+    /* FixMe ToDo: This is not yet right where commands have been overridden. */
+    const char *zSql = "SELECT COUNT(*), max(extIx), cmdIx"
+      " FROM ShellCommands WHERE name glob (?||'*')";
+    rc = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0);
+    sqlite3_bind_text(pStmt, 1, cmdName, -1, 0);
+    rc = sqlite3_step(pStmt);
+    nf = sqlite3_column_int(pStmt, 0);
+    extIx = sqlite3_column_int(pStmt, 1);
+    cmdIx = sqlite3_column_int(pStmt, 2);
+    sqlite3_finalize(pStmt);
+    if( rc!= SQLITE_ROW ) return 0;
+    *nFound = nf;
+    if( nf!=1 ) return 0;
+    return command_by_index(ISS(psx), extIx, cmdIx);
+  }else
+#endif
+  {
     int cmdLen = strlen30(cmdName);
     struct CommandInfo *pci = 0;
     int ixb = 0, ixe = numCommands-1;
@@ -12805,6 +12986,7 @@ MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx,
         break;
       }
     }
+    *nFound = 1;
     return (MetaCommand *)pci;
   }
 }
@@ -12991,7 +13173,8 @@ static int do_meta_command(char *zLine, ShellExState *psx){
     utf8_printf(STD_ERR, "Error: unknown command: \"%s\"\n", azArg[0]);
     if( stdin_is_interactive )
       utf8_printf(STD_ERR, "  Enter \".help\" for a list of commands.\n");
-    rc = 1;
+    if( psx->dbShell!=0 && sqlite3_strnicmp(azArg[0],"quit",n)==0 ) rc = 2;
+    else rc = 1;
     break;
   case SHELL_INVALID_ARGS:
     utf8_printf(STD_ERR, "Error: invalid arguments for \".%s\"\n", azArg[0]);
@@ -14520,6 +14703,7 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){
   clearTempFile(&data);
   sqlite3_free(data.zEditor);
 #if SHELL_DYNAMIC_EXTENSION
+  free_shext_tracking(&data);
   sqlite3_close(datax.dbShell);
 #endif
 #if !SQLITE_SHELL_IS_UTF8
index 8be1f3a959e7f3cb39db7391a2a8d402dbaa6fe6..e9a2e71700766c2185da34b9cc52bb7c93053a57 100644 (file)
@@ -33,7 +33,7 @@ typedef struct ShellExState {
   sqlite3 *dbUser;
   /* DB connection for shell dynamical data and extension management
    * Extensions may use this DB, but should not alter content created
-   * by the shell nor depend upon its schema. Names with prefix "shell_"
+   * by the shell nor depend upon its schema. Names with prefix "Shell"
    * or "shext_" are reserved for the shell's use.
    */
   sqlite3 *dbShell;