# 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 */
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;
/* 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()
}
}
+#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 = {
};
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,
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);
}
** 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 {
* 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;
}
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;
}
*/
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;
break;
}
}
+ *nFound = 1;
return (MetaCommand *)pci;
}
}
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]);
clearTempFile(&data);
sqlite3_free(data.zEditor);
#if SHELL_DYNAMIC_EXTENSION
+ free_shext_tracking(&data);
sqlite3_close(datax.dbShell);
#endif
#if !SQLITE_SHELL_IS_UTF8