From: larrybr Date: Tue, 8 Mar 2022 16:44:55 +0000 (+0000) Subject: (WIP) Add DB-dispatch for dot commands upon shell extension load. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8526f8738a4d3ef1c85e6e35561870068517d763;p=thirdparty%2Fsqlite.git (WIP) Add DB-dispatch for dot commands upon shell extension load. FossilOrigin-Name: 19f2a747b8f4d84d8695beb9b23a455f1dead24c94aa90466510555a796027d7 --- diff --git a/manifest b/manifest index 8e4ca1a7ac..202128825b 100644 --- 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. diff --git a/manifest.uuid b/manifest.uuid index b0eb5bb581..16e195cea3 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -16af0e4560349f720d4e2fbb4c08211e6a9a573c1fb4eb287d8bc2aefc3c7ce0 \ No newline at end of file +19f2a747b8f4d84d8695beb9b23a455f1dead24c94aa90466510555a796027d7 \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index cd2e731c10..7380434a3d 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -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; icpShxLoaded[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; icpMethods->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; inumExtLoaded; ++i ){ + ShExtInfo *psei = &psi->pShxLoaded[i]; + if( psei->ppMetaCommands!=0 ){ + if( i>0 ){ /* built-in commands need no freeing */ + for( j=0; jnumMetaCommands; ++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; jnumOutModeHandlers; ++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; jnumImportHandlers; ++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 && extIxnumExtLoaded ){ + ShExtInfo *psei = & psi->pShxLoaded[extIx]; + if( cmdIx>=0 && cmdIxnumMetaCommands ){ + 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 diff --git a/src/shext_linkage.h b/src/shext_linkage.h index 8be1f3a959..e9a2e71700 100644 --- a/src/shext_linkage.h +++ b/src/shext_linkage.h @@ -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;