Index: ext/misc/tclshext.c.in ================================================================== --- ext/misc/tclshext.c.in +++ ext/misc/tclshext.c.in @@ -14,21 +14,21 @@ tool/mkshellc.tcl ext/misc/tclshext.c.in > tclshext.c gcc -shared -fPIC -O2 -I. -Isrc -I/usr/include/tcl8.6 tclshext.c \ -o tclshext.so -ltcl8.6 ** Later TCL versions can be used if desired. "TCL scripting support is added with a registerScripting() call in the\n" - "ShellExtensionAPI, per ScriptingSupport interface requirements. This\n" - "support lasts until the scripting object destructor is called.\n" + "ShellExtensionAPI, as documented for ScriptingSupport interface. This\n" + "support lasts until the scripting object destructor is called. Until\n" */ static const char * const zTclHelp = "This extension adds these features to the host shell:\n" " 1. TCL scripting support is added.\n" " 2. TCL commands are added: udb shdb now_interactive get_tcl_group ..\n" " 3. The .tcl and .unknown dot commands are added.\n" - " 4. If built with Tk capability, the gui TCL command will be added if\n" - " this extension was loaded using the shell via: .load ... -shext -tk .\n" - " Any other arguments beyond -shext are in TCL's argv variable.\n" + " 4. If built with Tk capability, a run_gui TCL command may be added if\n" + " the extension is loaded by the shell via .load ... -shext -tk . Any\n" + " other arguments beyond -shext are in TCL's argv variable.\n" "Operation:\n" " Shell input groups beginning with \"..\" are treated as TCL input, in\n" " these ways: (1) When a bare \"..\" is entered, a TCL REPL loop is run\n" " until the end of input is seen; (2) When \"..D ...\" is entered, (where\n" " \"D\" is a dot-command name), the D dot command will be run in its normal\n" @@ -206,11 +206,11 @@ DERIVED_METHOD(const char *, help, ScriptSupport,TclSS, 1,( int more )){ (void)(pThis); switch( more ){ case 0: return "Provides TCL scripting support for SQLite extensible shell.\n"; - case 1: return zTclHelp; + case 1: return zTclHelp; /* ToDo: Rewrite this help. */ } return 0; } /* Not doing this yet. */ @@ -685,67 +685,16 @@ return TCL_ERROR; } } #ifndef SHELL_OMIT_TK -static int numEventLoops = 0; -static int inOuterLoop = 0; - -static int exitThisTkGUI(void *pvSS, Tcl_Interp *interp, - int nArgs, const char *azArgs[]){ - if( numEventLoops==0 && !inOuterLoop ){ - int ec = 0; - if( nArgs>=2 ){ - if( azArgs[1] && sscanf(azArgs[1], "%d", &ec)!=1 ){ - ec = 1; - fprintf(stderr, "Exit: %d\n", ec); - }else{ - const char *zA = (azArgs[1])? azArgs[1] : "null"; - fprintf(stderr, "Exit: \"%s\"\n", azArgs[1]); - } - }else{ - fprintf(stderr, "Exit without argument\n"); - } - fprintf(stderr, "Exit: \"%s\"\n", azArgs[1]); - // exit(ec); - } - --numEventLoops; - return TCL_BREAK; -} - -static void runTclEventLoop(void){ - int inOuter = inOuterLoop; - int nmw = Tk_GetNumMainWindows(); +static int runTkGUI(void *pvSS, Tcl_Interp *interp, + int nArgs, const char *azArgs[]){ + ShellExState *psx = (ShellExState *)pvSS; /* This runs without looking at stdin. So it cannot be a REPL, yet. * Unless user has created something for it to do, it does nothing. */ - /* Tk_MapWindow(Tk_MainWindow(interpKeep.pInterp)); */ - ++numEventLoops; - inOuterLoop = 1; - while( nmw > 0 ) { - Tcl_DoOneEvent(0); - nmw = Tk_GetNumMainWindows(); - /* if( nmw==1 ){ */ - /* Tk_UnmapWindow(Tk_MainWindow(interpKeep.pInterp)); */ - /* nmw = Tk_GetNumMainWindows(); */ - /* break; */ - /* } */ - } - if( nmw==0 ){ - fprintf(stderr, - "Tk application and its root window destroyed. Restarting Tk.\n"); - Tk_Init(interpKeep.pInterp); - } - --numEventLoops; - inOuterLoop = inOuter; -} - -static int runTkGUI(void *pvSS, Tcl_Interp *interp, - int nArgs, const char *azArgs[]){ - (void)(pvSS); /* ShellExState *psx = (ShellExState *)pvSS; */ - Tcl_SetMainLoop(runTclEventLoop); - runTclEventLoop(); - return TCL_OK; + Tk_MainLoop(); } #endif #define UNKNOWN_RENAME "::_original_unknown" @@ -1110,108 +1059,21 @@ /* 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 .. {} {}"); #ifndef SHELL_OMIT_TK if( ldTk ){ - /* Create a proc to launch GUI programs, in faint mimicry of wish. - * - * Its first argument, pgmName is the name to be given to the GUI - * program that may be launched, used for error reporting and to - * become the value of the ::argv0 that it sees. - * - * Its second argument, pgmSetup, will be executed as a list (of - * a command and its arguments) to setup the GUI program. It may - * do anything necessary to prepare for the GUI program to be - * run by running a Tk event loop. It may be an empty list, in - * which case pgmName must name a list serving the same purpose. - * - * Subsequent arguments to this proc will be passed to the GUI - * program in the ::argv/::argc variable pair it sees. - * - * If only two empty arguments are provided to this proc, (whether - * as defaulted or explictly passed), the GUI event loop will be - * run with whatever conditions have been setup prior to the call. - * (This is perfectly legitimate; this "gui" proc provides a way - * to package GUI preparation and separate it from GUI run.) - * - * It is the responsibility of whatever setup code is run, if any, - * to leave Tk objects and variables set so that when a GUI event - * loop is run, some useful GUI program runs and can terminate. - * - * Before running the setup code, a variable, ::isHost, is set - * true to possibly inform the setup code that it should avoid - * exit and exec calls. Setup code which is designed for either - * hosted or standalone use, when run with $::isHost!=0, may opt - * to leave variables ::exitCode and ::resultValue set which are - * taken to indicate pseudo-exit status and a string result to - * be used for error reporting or possibly other purposes. - * - * If the above responsibilities cannot be met, setup code should - * fail in some way so that its execution produces a TCL error or - * follows the ::exitCode and ::resultValue convention. Otherwise, - * annoying sqlite3 shell hangs or abrupt exits may result. - */ - TCL_CSTR_LITERAL(const char * const zGui =){ - proc gui {{pgmName ""} {pgmSetup {}} args} { - unset -nocomplain ::exitCode - set ::tcl_interactive [now_interactive] - set saveArgs [list $::argv0 $::argc $::argv] - if {"$pgmName" ne ""} { - set ::argv0 $pgmName - } else {set ::argv0 "?"} - set ::argv $args - set ::argc [llength $args] - if {[llength $pgmSetup] == 0 && $pgmName ne ""} { - if { [catch {set ::programSetup [subst "\$$pgmName"]}] } { - foreach {::argv0 ::argc ::argv} $saveArgs {} - return -code 1 "Error: pgmSetup empty, and pgmName does not\ - name a list that might be\n executed in\ - its place. Consult tclshext doc on using the gui command." - } - } elseif {[llength $pgmSetup] == 0 && $pgmName eq ""} { - unset -nocomplain ::programSetup - } else { - set ::programSetup $pgmSetup - } - if {[info exists ::programSetup] && [llength $::programSetup] > 0} { - set rc [catch {uplevel #0 { - {*}$::programSetup - }} result options] - if {$rc==1} { - puts stderr "gui setup failed: $result" - puts stderr [dict get $options -errorinfo] - } elseif {[info exists ::exitCode] && $::exitCode!=0} { - puts stderr "gui setup failed: $::resultValue" - } else { run_gui_event_loop } - } else { - run_gui_event_loop - } - foreach {::argv0 ::argc ::argv} $saveArgs {} - } - }; - /* Create a command which nearly emuluates Tk_MainLoop(). It runs a - * GUI event loop, so does not return until either: all Tk top level - * windows are destroyed, which causes and error return, or the Tk - * app has called the replacement exit routine described next. */ - Tcl_CreateCommand(interp, "run_gui_event_loop", runTkGUI, psx, 0); - Tcl_Eval(interp, "rename exit process_exit"); - Tcl_CreateCommand(interp, "exit", exitThisTkGUI, psx, 0); - Tcl_Eval(interp, zGui); - Tcl_SetMainLoop(runTclEventLoop); + /* Create a command which wraps Tk_MainLoop(). It runs a GUI event + * loop, so does not return until all of its Tk windows are closed. */ + Tcl_CreateCommand(interp, "run_gui", runTkGUI, psx, 0); zAppName = "tclshext_tk"; } #endif - Tcl_SetVar2Ex(interp, "::argv0", NULL, + Tcl_SetVar2Ex(interp, "argv0", NULL, Tcl_NewStringObj(zAppName,-1), TCL_GLOBAL_ONLY); - Tcl_SetVar2Ex(interp, "::argc", NULL, + Tcl_SetVar2Ex(interp, "argc", NULL, Tcl_NewIntObj(tnarg), TCL_GLOBAL_ONLY); - Tcl_SetVar2Ex(interp, "::argv", NULL, targv, TCL_GLOBAL_ONLY); - Tcl_SetVar2Ex(interp, "::tcl_interactive", NULL, - Tcl_NewIntObj(pExtHelpers->nowInteractive(psx)), - TCL_GLOBAL_ONLY); - Tcl_SetVar2Ex(interp, "::isHosted", NULL, - Tcl_NewIntObj(1), TCL_GLOBAL_ONLY); + Tcl_SetVar2Ex(interp, "argv", NULL, targv, TCL_GLOBAL_ONLY); pShExtLink->eid = sqlite3_tclshext_init; } if( rc==SQLITE_OK ){ pShExtLink->extensionDestruct = Tcl_TakeDown; pShExtLink->pvExtensionObject = &interpKeep; Index: src/shell.c.in ================================================================== --- src/shell.c.in +++ src/shell.c.in @@ -1398,11 +1398,10 @@ struct EventSubscription { ExtensionId eid; void *pvUserData; ShellEventNotify eventHandler; } *pSubscriptions; /* The current shell event subscriptions */ - u8 bDbDispatch; /* Cache fact of dbShell dispatch table */ #endif ShellExState *pSXS; /* Pointer to companion, exposed shell state */ } ShellInState; @@ -1412,12 +1411,12 @@ */ #define MAX_INPUT_NESTING 25 /* ** This procedure updates the bSafeMode flag after completion of any -** operation (meta-command, SQL submission, or script execution) that -** counts as one for which safe mode might be suspended. +** operation (meta-command or SQL submission) that counts as one for +** which safe mode might be suspended. ** bSafeModeFuture has 3 states salient here: ** equal 0 => Safe mode is and will remain inactive. ** equal 1 => Safe mode is and will remain active. ** N > 1 => Safe mode is suspended for N-1 operations. */ @@ -2412,78 +2411,10 @@ } return 0; } #endif /* SQLITE_OMIT_PROGRESS_CALLBACK */ -/* For debugging or experimentation, the shell DB can be made file-based. */ -#ifndef SHELL_DB_FILE -# define SHELL_DB_STORE ":memory:" -#else -# define SHELL_DB_STORE SHELL_STRINGIFY(SHELL_DB_FILE) -#endif - -#define SHELL_DISP_SCHEMA "main" -#define SHELL_DISP_TAB "ShellCommands" -#define SHELL_DISP_VIEW "ShellActiveCmds" - -/* -** Ensure dbShell exists and return SQLITE_OK, -** or complain and return SQLITE_ERROR. -*/ -static int ensure_shell_db(ShellExState *psx){ - if( psx->dbShell!=0 ) return SQLITE_OK; - else{ - int rc = sqlite3_open(SHELL_DB_STORE, &psx->dbShell); - if( rc!=SQLITE_OK ){ - utf8_printf(STD_ERR, "Shell DB open failure: %s\n", sqlite3_errstr(rc)); - return SQLITE_ERROR; - } -#ifndef SQLITE_NOHAVE_SYSTEM - sqlite3_create_function(psx->dbShell, "edit", 1, - SQLITE_UTF8, 0, editFunc, 0, 0); - sqlite3_create_function(psx->dbShell, "edit", 2, - SQLITE_UTF8, 0, editFunc, 0, 0); -#endif - return rc; - } -} - -/* Tell whether the above-created table exists, return true iff exists. */ -static int dispatch_table_exists(sqlite3 *dbs){ - return sqlite3_table_column_metadata - (dbs, SHELL_DISP_SCHEMA, SHELL_DISP_TAB, 0, 0, 0, 0, 0, 0)==SQLITE_OK; -} - -static int ensure_dispatch_table(ShellExState *psx){ - int rc = ensure_shell_db(psx); - if( rc==SQLITE_OK ){ - char *zErr = 0; - int rc1, rc2; - if( dispatch_table_exists(psx->dbShell) ) return rc; - /* Create the dispatch table and view on it. */ -#ifdef SHELL_DB_FILE - sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_DISP_TAB, 0,0,0); - sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_DISP_VIEW, 0,0,0); -#endif - rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_DISP_TAB"(" - "name TEXT, extIx INT, cmdIx INT," - "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", 0, 0, &zErr); - rc2 = sqlite3_exec(psx->dbShell, - "CREATE VIEW "SHELL_DISP_VIEW - " AS SELECT s.name AS name," - " max(s.extIx) AS extIx, s.cmdIx AS cmdIx" - " FROM "SHELL_DISP_TAB" 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 : "?"); - rc = SQLITE_ERROR; - }else rc = SQLITE_OK; - sqlite3_free(zErr); - } - return rc; -} - /* ** Skip over whitespace, returning remainder. */ static const char *skipWhite( const char *z ){ while( IsSpace(*z) ) ++z; @@ -3560,70 +3491,25 @@ case '$': case ':': case '@': case '?': return PTU_Binding; default: return isalpha(c)? PTU_Script : PTU_Nil; } } -#ifndef SQLITE_NOHAVE_SYSTEM -/* Possibly using a -editor=X argument and env-var VISUAL, attempt - * to get the zEditor shell state member set iff not already set. - * If there is no such argument, the env-var is retrieved if set. - * If the argument is -editor=X or --editor=X, use that and leave - * the zEditor member set accordingly. Returns are: - * 0 => editor set, zEd was not the -editor option - * 1 => editor set, zEd consumed as -editor option - * -1 => editor not set, and error/advice message issued. - * - * This implements an undocumented fall-back for the .vars and - * .parameters edit subcommands, so that users need not restart - * a shell session to get an editor specified upon need for it. */ -int attempt_editor_set(ShellInState *psi, char *zDot, const char *zEd){ - if( psi->zEditor==0 ){ - const char *zE = getenv("VISUAL"); - if( zE!=0 ) psi->zEditor = smprintf("%s", zE); - } - if( zEd && zEd[0]=='-' ){ - zEd += 1 + (zEd[1]=='-'); - if( strncmp(zEd,"editor=",7)==0 ){ - sqlite3_free(psi->zEditor); - /* Accept an initial -editor=? option. */ - psi->zEditor = smprintf("%s", zEd+7); - return 1; - } - } - if( psi->zEditor==0 ){ - utf8_printf(STD_ERR, - "Either set env-var VISUAL to name an" - " editor and restart, or rerun\n " - ".%s edit with an initial edit option," - " --editor=EDITOR_COMMAND .\n", zDot); - return -1; - } - return 0; -} -#endif - -/* The table kept for user DBs if .parameter command is used usefully. */ #define PARAM_TABLE_NAME "sqlite_parameters" #define PARAM_TABLE_SCHEMA "temp" #define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME -/* The table kept for the shell DB if .vars command is used usefully. */ -#define SHVAR_TABLE_NAME "sqlite_variables" -#define SHVAR_TABLE_SCHEMA "main" -#define SHVAR_TABLE_SNAME SHVAR_TABLE_SCHEMA"."SHVAR_TABLE_NAME - -#ifndef SH_KV_STORE_NAME +#ifndef PARAM_STORE_NAME /* Name for table keeping user's saved parameters */ -# define SH_KV_STORE_NAME "SQLiteShell_KeyValuePairs" +# define PARAM_STORE_NAME "SQLiteShellParameters" #endif -#ifndef SH_KV_STORE_SCHEMA +#ifndef PARAM_STORE_SCHEMA /* Schema name used to attach saved parameters DB during load/save */ -# define SH_KV_STORE_SCHEMA "SQLiteShell" +# define PARAM_STORE_SCHEMA "SQLiteShell" #endif -#define SH_KV_STORE_SNAME SH_KV_STORE_SCHEMA"."SH_KV_STORE_NAME +#define PARAM_STORE_SNAME PARAM_STORE_SCHEMA"."PARAM_STORE_NAME -/* Create the TEMP table used to store parameter bindings */ +/* Create the TEMP table used to store parameter bindings and SQL statements */ static void param_table_init(sqlite3 *db){ DbProtectState dps = allow_sys_schema_change(db); sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS "PARAM_TABLE_SNAME"(\n" " key TEXT PRIMARY KEY,\n" @@ -3638,36 +3524,10 @@ static int param_table_exists( sqlite3 *db ){ return sqlite3_table_column_metadata (db, PARAM_TABLE_SCHEMA, PARAM_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK; } -/* Create the shell DB table used to store shell variables or scripts */ -static int shvars_table_init(sqlite3 *db){ - DbProtectState dps = allow_sys_schema_change(db); - int rc = sqlite3_exec(db, - "CREATE TABLE IF NOT EXISTS "SHVAR_TABLE_SNAME"(\n" - " key TEXT PRIMARY KEY,\n" - " value,\n" - " uses INT DEFAULT (0)" /* aka PTU_Binding */ - ") WITHOUT ROWID;", - 0, 0, 0); - restore_sys_schema_protection( db, &dps ); - return rc!=SQLITE_OK; -} - -/* Tell whether the above-created table exists, return true iff exists. */ -static int shvars_table_exists( sqlite3 *db ){ - return sqlite3_table_column_metadata - (db, SHVAR_TABLE_SCHEMA, SHVAR_TABLE_NAME, 0, 0, 0, 0, 0, 0)==SQLITE_OK; -} - -/* Make shell vars table exist. */ -static int ensure_shvars_table(sqlite3 *dbs){ - if( shvars_table_exists(dbs) ) return SQLITE_OK; - else return shvars_table_init(dbs); -} - /* ** Bind parameters on a prepared statement. ** ** Parameter bindings are taken from a TEMP table of the form: ** @@ -7899,11 +7759,11 @@ static int register_meta_command(ShellExState *p, ExtensionId eid, MetaCommand *pMC){ ShellInState *psi = ISS(p); ShExtInfo *psei = pending_ext_info(psi); const char *zSql - = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, ?, ?)"; + = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, ?, ?)"; int ie = psi->ixExtPending; assert(psi->pShxLoaded!=0 && p->dbShell!=0); if( pMC==0 ) return SQLITE_ERROR; else{ const char *zName = pMC->pMethods->name(pMC); @@ -7947,16 +7807,15 @@ } static int register_scripting(ShellExState *p, ExtensionId eid, ScriptSupport *pSS){ ShellInState *psi = ISS(p); - if( psi->scriptXid!=0 || psi->script!=0 ){ + if( psi->scriptXid!=0 ){ /* Scripting support already provided. Only one provider is allowed. */ return SQLITE_BUSY; } if( eid==0 || pSS==0 || psi->ixExtPending==0 ){ - /* Scripting addition allowed only when sqlite3_*_init() runs. */ return SQLITE_MISUSE; } psi->script = pSS; psi->scriptXid = eid; return SQLITE_OK; @@ -8127,31 +7986,29 @@ pv = 0; } if( pv==0 ) sqlite3_result_null(context); else sqlite3_result_pointer(context, pv, SHELLEXT_API_POINTERS, 0); } + +#ifndef SHELL_DB_FILE +# define SHELL_DB_STORE ":memory:" +#else +# define SHELL_DB_STORE SHELL_STRINGIFY(SHELL_DB_FILE) +#endif /* Do the initialization needed for use of dbShell for command lookup * and dispatch and for I/O handler lookup and dispatch. */ static int begin_db_dispatch(ShellExState *psx){ ShellInState *psi = ISS(psx); sqlite3_stmt *pStmt = 0; - int ic, rc1, rc2; - int rc = 0; + int ic, rc, rc1, rc2; 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)); - rc = ensure_shell_db(psx); - if( rc!=SQLITE_OK ){ - utf8_printf(STD_ERR, "Error: Shell DB uncreatable. Best to .quit soon.\n"); - return SQLITE_ERROR; - } - if( ensure_dispatch_table(psx)!=SQLITE_OK ) return 1; - + 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.ppExportHandlers = (ExportHandler **)sqlite3_malloc(2*sizeof(ExportHandler *)); @@ -8166,11 +8023,29 @@ for( ic=0; icpShxLoaded[psi->numExtLoaded++] = sei; - zSql = "INSERT INTO "SHELL_DISP_TAB"(name, extIx, cmdIx) VALUES(?, 0, ?)"; + 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); + sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS ActiveCommands", 0,0,0); +#endif + rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE ShellCommands(" + "name TEXT, extIx INT, cmdIx INT," + "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", + 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", + 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 1; + } + zSql = "INSERT INTO ShellCommands (name, extIx, cmdIx) VALUES(?, 0, ?)"; rc1 = sqlite3_prepare_v2(psx->dbShell, zSql, -1, &pStmt, 0); rc2 = sqlite3_exec(psx->dbShell, "BEGIN TRANSACTION", 0, 0, &zErr); if( rc1!=SQLITE_OK || rc2!=SQLITE_OK ) return 1; assert(sei.numMetaCommands>0); for( ic=0; icdbShell, "COMMIT", 0, 0, &zErr); sqlite3_enable_load_extension(psx->dbShell, 1); - sqlite3_free(zErr); - psi->bDbDispatch = 1; - return SQLITE_OK; } /* Call one loaded extension's destructors, in reverse order * of their objects' creation, then free the tracking dyna-arrays. @@ -8279,11 +8151,11 @@ ScriptSupport *pssSave = psi->script; ExtensionId ssiSave = psi->scriptXid; int rc; if( pzErr ) *pzErr = 0; - if( psx->dbShell==0 || ISS(psx)->numExtLoaded==0 ){ + if( psx->dbShell==0 ){ rc = begin_db_dispatch(psx); if( rc!=SQLITE_OK ) return rc; assert(ISS(psx)->numExtLoaded==1 && psx->dbShell!=0); } psi->ixExtPending = psi->numExtLoaded; @@ -10462,19 +10334,19 @@ sqlite3_snprintf(sizeof(ISS(p)->nullValue), ISS(p)->nullValue, "%.*s", (int)ArraySize(ISS(p)->nullValue)-1, azArg[1]); return DCR_Ok; } -/* Helper functions for .parameter and .vars commands +/* Helper functions for .parameter command */ -struct keyval_row { char * value; int uses; int hits; }; +struct param_row { char * value; int uses; int hits; }; -static int kv_find_callback(void *pData, int nc, char **pV, char **pC){ +static int param_find_callback(void *pData, int nc, char **pV, char **pC){ assert(nc>=1); assert(strcmp(pC[0],"value")==0); - struct keyval_row *pParam = (struct keyval_row *)pData; + struct param_row *pParam = (struct param_row *)pData; assert(pParam->value==0); /* key values are supposedly unique. */ if( pParam->value!=0 ) sqlite3_free( pParam->value ); pParam->value = smprintf("%s", pV[0]); /* source owned by statement */ if( nc>1 ) pParam->uses = (int)integerValue(pV[1]); ++pParam->hits; @@ -10523,51 +10395,43 @@ * It is not an error to specify a name that cannot be transferred * because it does not exist in the source table. * * Returns are SQLITE_OK for success, or other codes for failure. */ -static int kv_xfr_table(sqlite3 *db, const char *zStoreDbName, - int bSaveNotLoad, ParamTableUse ptu, - const char *azNames[], int nNames){ +static int param_xfr_table(sqlite3 *db, const char *zStoreDbName, + int bSaveNotLoad, const char *azNames[], int nNames){ int rc = 0; char *zSql = 0; /* to be sqlite3_free()'ed */ sqlite3_str *sbCopy = 0; - const char *zHere = 0; - const char *zThere = SH_KV_STORE_SNAME; - const char *zTo; - const char *zFrom; + const char *zHere = PARAM_TABLE_SNAME; + const char *zThere = PARAM_STORE_SNAME; + const char *zTo = (bSaveNotLoad)? zThere : zHere; + const char *zFrom = (bSaveNotLoad)? zHere : zThere; sqlite3 *dbStore = 0; int openFlags = (bSaveNotLoad) ? SQLITE_OPEN_URI|SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE : SQLITE_OPEN_READONLY; - switch( ptu ){ - case PTU_Binding: zHere = PARAM_TABLE_SNAME; break; - case PTU_Script: zHere = SHVAR_TABLE_SNAME; break; - default: assert(0); return 1; - } - zTo = (bSaveNotLoad)? zThere : zHere; - zFrom = (bSaveNotLoad)? zHere : zThere; /* Ensure store DB can be opened and/or created appropriately. */ rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0); if( rc!=SQLITE_OK ){ - utf8_printf(STD_ERR, "Error: Cannot %s key/value store DB %s\n", + utf8_printf(STD_ERR, "Error: Cannot %s parameter store DB %s\n", bSaveNotLoad? "open/create" : "read", zStoreDbName); return rc; } - /* Ensure it has the kv store table, or handle its absence. */ + /* Ensure it has the parameter store table, or handle its absence. */ assert(dbStore!=0); if( sqlite3_table_column_metadata - (dbStore, "main", SH_KV_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){ + (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){ if( !bSaveNotLoad ){ - utf8_printf(STD_ERR, "Error: No key/value pairs ever stored in DB %s\n", + utf8_printf(STD_ERR, "Error: No parameters ever stored in DB %s\n", zStoreDbName); rc = 1; }else{ /* The saved parameters table is not there yet; create it. */ const char *zCT = - "CREATE TABLE IF NOT EXISTS "SH_KV_STORE_NAME"(\n" + "CREATE TABLE IF NOT EXISTS "PARAM_STORE_NAME"(\n" " key TEXT PRIMARY KEY,\n" " value,\n" " uses INT\n" ") WITHOUT ROWID;"; rc = sqlite3_exec(dbStore, zCT, 0, 0, 0); @@ -10577,11 +10441,11 @@ } } sqlite3_close(dbStore); if( rc!=0 ) return rc; - zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, SH_KV_STORE_SCHEMA); + zSql = smprintf("ATTACH %Q AS %s;", zStoreDbName, PARAM_STORE_SCHEMA); shell_check_oom(zSql); rc = sqlite3_exec(db, zSql, 0, 0, 0); sqlite3_free(zSql); if( rc!=SQLITE_OK ) return rc; @@ -10593,131 +10457,121 @@ zSql = sqlite3_str_finish(sbCopy); shell_check_oom(zSql); rc = sqlite3_exec(db, zSql, 0, 0, 0); sqlite3_free(zSql); - sqlite3_exec(db, "DETACH "SH_KV_STORE_SCHEMA";", 0, 0, 0); + sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0); return rc; } -/* Default locations of kv store DBs for .parameters and .vars save/load. */ +/* Default location of parameters store DB for .parameters save/load. */ static const char *zDefaultParamStore = "~/sqlite_params.sdb"; -static const char *zDefaultVarStore = "~/sqlite_vars.sdb"; /* Possibly generate a derived path from input spec, with defaulting * and conversion of leading (or only) tilde as home directory. * The above-set default is used for zSpec NULL, "" or "~". * When return is 0, there is an error; what needs doing cannnot be done. * If the return is exactly the input, it must not be sqlite3_free()'ed. * If the return differs from the input, it must be sqlite3_free()'ed. */ - static const char *kv_store_path(const char *zSpec, ParamTableUse ptu){ +static const char *params_store_path(const char *zSpec){ if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){ - const char *zDef; - switch( ptu ){ - case PTU_Binding: zDef = zDefaultParamStore; break; - case PTU_Script: zDef = zDefaultVarStore; break; - default: return 0; - } - return home_based_path(zDef); + return home_based_path(zDefaultParamStore); }else if ( zSpec[0]=='~' ){ return home_based_path(zSpec); } return zSpec; } -/* Load some or all kv pairs. Arguments are "load FILE ?NAMES?". */ -static int kv_pairs_load(sqlite3 *db, ParamTableUse ptu, - const char *azArg[], int nArg){ - const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu); +/* Load some or all parameters. Arguments are "load FILE ?NAMES?". */ +static int parameters_load(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); if( zStore==0 ){ utf8_printf(STD_ERR, "Cannot form parameter load path. Nothing loaded.\n"); return DCR_Error; }else{ const char **pzFirst = (nArg>2)? azArg+2 : 0; int nNames = (nArg>2)? nArg-2 : 0; - int rc = kv_xfr_table(db, zStore, 0, ptu, pzFirst, nNames); + int rc = param_xfr_table(db, zStore, 0, pzFirst, nNames); if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); return rc; } } /* Save some or all parameters. Arguments are "save FILE ?NAMES?". */ -static int kv_pairs_save(sqlite3 *db, ParamTableUse ptu, - const char *azArg[], int nArg){ - const char *zStore = kv_store_path((nArg>1)? azArg[1] : 0, ptu); +static int parameters_save(sqlite3 *db, const char *azArg[], int nArg){ + const char *zStore = params_store_path((nArg>1)? azArg[1] : 0); if( zStore==0 ){ utf8_printf(STD_ERR, "Cannot form parameter save path. Nothing saved.\n"); return DCR_Error; }else{ const char **pzFirst = (nArg>2)? azArg+2 : 0; int nNames = (nArg>2)? nArg-2 : 0; - int rc = kv_xfr_table(db, zStore, 1, ptu, pzFirst, nNames); + int rc = param_xfr_table(db, zStore, 1, pzFirst, nNames); if( nArg>1 && zStore!=azArg[1] ) sqlite3_free((void*)zStore); return rc; } } #ifndef SQLITE_NOHAVE_SYSTEM /* - * Edit one named value in the parameters or shell variables table. - * If it does not yet exist, create it. If eval is true, the value - * is treated as a bare expression, otherwise it is a text value. - * The "uses" argument sets the 3rd column in the selected table, - * and serves to select which of the two tables is modified. + * Edit one named parameter in the parameters table. If it does not + * yet exist, create it. If eval is true, the value is treated as a + * bare expression, otherwise it is a text value. The uses argument + * sets the 3rd column in the parameters table, and may also serve + * to partition the key namespace. (This is not done now.) */ -static int edit_one_kvalue(sqlite3 *db, char *name, int eval, - ParamTableUse uses, const char * zEditor){ - struct keyval_row kvRow = {0,0,0}; +static int edit_one_param(sqlite3 *db, char *name, int eval, + ParamTableUse uses, const char * zEditor){ + struct param_row paramVU = {0,0,0}; int rc; char * zVal = 0; - const char *zTab = 0; - char * zSql = 0; - - switch( uses ){ - case PTU_Script: zTab = SHVAR_TABLE_SNAME; break; - case PTU_Binding: zTab = PARAM_TABLE_SNAME; break; - default: assert(0); - } - zSql = smprintf("SELECT value, uses FROM %s " - "WHERE key=%Q AND uses=%d", zTab, name, uses); - shell_check_oom(zSql); - sqlite3_exec(db, zSql, kv_find_callback, &kvRow, 0); - sqlite3_free(zSql); - assert(kvRow.hits<2); - if( kvRow.hits==1 && kvRow.uses==uses){ - /* Editing an existing value of same kind. */ - sqlite3_free(kvRow.value); - if( eval!=0 ){ - zSql = smprintf("SELECT edit(value, %Q) FROM %s " - "WHERE key=%Q AND uses=%d", zEditor, zTab, name, uses); - shell_check_oom(zSql); + char * zSql = smprintf + ("SELECT value, uses FROM " PARAM_TABLE_SNAME " WHERE key=%Q", name); + shell_check_oom(zSql); + sqlite3_exec(db, zSql, param_find_callback, ¶mVU, 0); + sqlite3_free(zSql); + assert(paramVU.hits<2); + if( paramVU.hits==1 && paramVU.uses==uses){ + /* Editing an existing value of same kind. */ + sqlite3_free(paramVU.value); + if( eval!=0 ){ + zSql = smprintf + ("SELECT edit(value, %Q) FROM " PARAM_TABLE_SNAME + " WHERE key=%Q AND uses=%d", zEditor, name, uses); zVal = db_text(db, zSql, 1); sqlite3_free(zSql); - zSql = smprintf("UPDATE %s SET value=(SELECT %s) " - "WHERE key=%Q AND uses=%d", zTab, zVal, name, uses); + zSql = smprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE" + " key=%Q AND uses=%d", zVal, name, uses); }else{ - zSql = smprintf("UPDATE %s SET value=edit(value, %Q) WHERE" - " key=%Q AND uses=%d", zTab, zEditor, name, uses); + zSql = smprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=edit(value, %Q) WHERE" + " key=%Q AND uses=%d", zEditor, name, uses); } }else{ /* Editing a new value of same kind. */ - assert(kvRow.value==0 || kvRow.uses!=uses); + assert(paramVU.value==0 || paramVU.uses!=uses); if( eval!=0 ){ - zSql = smprintf("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); + zSql = smprintf + ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); zVal = db_text(db, zSql, 1); sqlite3_free(zSql); - zSql = smprintf("INSERT INTO %s(key,value,uses)" + zSql = smprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" " VALUES (%Q,(SELECT %s LIMIT 1),%d)", - zTab, name, zVal, uses); + name, zVal, uses); }else{ - zSql = smprintf("INSERT INTO %s(key,value,uses)" - " VALUES (%Q,edit('-- %q;%s', %Q),%d)", - zTab, name, name, "\n", zEditor, uses); + zSql = smprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,edit('-- %q%s', %Q),%d)", + name, name, "\n", zEditor, uses); } } shell_check_oom(zSql); + if( eval!=0 ){ + } rc = sqlite3_exec(db, zSql, 0, 0, 0); sqlite3_free(zSql); sqlite3_free(zVal); return rc!=SQLITE_OK; } @@ -10732,10 +10586,33 @@ zSep = " "; ++valBeg; } return z; } + +/* Get a named parameter value in form of stepped prepared statement, + * ready to have its value taken from the 0th column. If the name + * cannot be found for the given ParamTableUse, 0 is returned. + * The caller is responsible for calling sqlite3_finalize(pStmt), + * where pStmt is the return from this function. + */ +static sqlite3_stmt *get_param_value(sqlite3 *db, char *name, + ParamTableUse ptu){ + sqlite3_stmt *rv = 0; + int rc; + char *zSql = smprintf + ( "SELECT value FROM "PARAM_TABLE_SNAME + " WHERE key=%Q AND uses=%d", name, ptu ); + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &rv, 0); + sqlite3_free(zSql); + if( SQLITE_OK==rc ){ + if( SQLITE_ROW==sqlite3_step(rv) ) return rv; + sqlite3_finalize(rv); + } + return 0; +} static struct ParamSetOpts { const char cCast; const char *zTypename; int evalKind; @@ -10759,36 +10636,15 @@ if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0]; } return 0; } -/* Most of .vars set - * Return SQLITE_OK on success, else SQLITE_ERROR. - */ -static int shvar_set(sqlite3 *db, char *name, char **valBeg, char **valLim){ - int rc = SQLITE_OK; - char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0; - sqlite3_stmt *pStmtSet = 0; - char *zValue = (zValGlom==0)? *valBeg : zValGlom; - char *zSql - = smprintf("REPLACE INTO "SHVAR_TABLE_SNAME"(key,value,uses)" - "VALUES(%Q,%Q,%d);", name, zValue, PTU_Script); - shell_check_oom(zSql); - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); - assert(rc==SQLITE_OK); - sqlite3_free(zSql); - rc = (SQLITE_DONE==sqlite3_step(pStmtSet))? SQLITE_OK : SQLITE_ERROR; - sqlite3_finalize(pStmtSet); - sqlite3_free(zValGlom); - return rc; -} - - -/* Most of the .parameter set subcommand (per help text) +/* The set subcommand (per help text) */ static int param_set(sqlite3 *db, char cCast, - char *name, char **valBeg, char **valLim){ + char *name, char **valBeg, char **valLim, + ParamTableUse ptu){ char *zSql = 0; int rc = SQLITE_OK, retries = 0, needsEval = 1; char *zValGlom = (valLim-valBeg>1)? values_join(valBeg, valLim) : 0; sqlite3_stmt *pStmtSet = 0; const char *zCastTo = 0; @@ -10805,16 +10661,15 @@ } if( needsEval ){ if( zCastTo!=0 ){ zSql = smprintf ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" - " VALUES(%Q,CAST((%s) AS %s),%d);", - name, zValue, zCastTo, PTU_Binding ); + " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu ); }else{ zSql = smprintf ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" - "VALUES(%Q,(%s),%d);", name, zValue, PTU_Binding ); + "VALUES(%Q,(%s),%d);", name, zValue, ptu ); } shell_check_oom(zSql); rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); sqlite3_free(zSql); } @@ -10822,11 +10677,11 @@ /* Reach here when value either requested to be cast to text, or must be. */ sqlite3_finalize(pStmtSet); pStmtSet = 0; zSql = smprintf ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" - "VALUES(%Q,%Q,%d);", name, zValue, PTU_Binding ); + "VALUES(%Q,%Q,%d);", name, zValue, ptu ); shell_check_oom(zSql); rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); assert(rc==SQLITE_OK); sqlite3_free(zSql); } @@ -10834,36 +10689,21 @@ sqlite3_finalize(pStmtSet); sqlite3_free(zValGlom); return rc; } -/* list or ls subcommand for .parameter and .vars dot-commands */ -static void list_pov_entries(ShellExState *psx, ParamTableUse ptu, u8 bShort, - char **pzArgs, int nArg){ +/* list or ls subcommand for .parameter dot-command */ +static void list_params(ShellExState *psx, ParamTableUse ptu, u8 bShort, + char **pzArgs, int nArg){ sqlite3_stmt *pStmt = 0; - sqlite3_str *sbList; + sqlite3 *db = DBX(psx); + sqlite3_str *sbList = sqlite3_str_new(db); int len = 0, rc; char *zFromWhere = 0; char *zSql = 0; - sqlite3 *db; - const char *zTab; - - switch( ptu ){ - case PTU_Binding: - db = DBX(psx); - zTab = PARAM_TABLE_SNAME; - break; - case PTU_Script: - db = psx->dbShell; - zTab = SHVAR_TABLE_NAME; - break; - default: assert(0); return; - } - sbList = sqlite3_str_new(db); - sqlite3_str_appendf(sbList, "FROM "); - sqlite3_str_appendf(sbList, zTab); - sqlite3_str_appendf(sbList, " WHERE (uses=?1) AND "); + sqlite3_str_appendf(sbList, "FROM "PARAM_TABLE_SNAME + " WHERE (?1=3 OR uses=?1) AND "); append_glob_terms(sbList, "key", (const char **)pzArgs, (const char **)pzArgs+nArg); zFromWhere = sqlite3_str_finish(sbList); shell_check_oom(zFromWhere); zSql = smprintf("SELECT max(length(key)) %s", zFromWhere); @@ -10981,20 +10821,21 @@ #ifndef SQLITE_NOHAVE_SYSTEM " edit ?OPT? NAME ... Use edit() to create or alter parameter NAME", " OPT may be -t to use edited value as text or -e to evaluate it.", #endif " init Initialize TEMP table for bindings and scripts", - " list ?PATTERNS? List current DB parameters table binding values", + " list ?PATTERNS? List parameters table binding and script values", " Alternatively, to list just some or all names: ls ?PATTERNS?", " load ?FILE? ?NAMES? Load some or all named parameters from FILE", " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb", " save ?FILE? ?NAMES? Save some or all named parameters into FILE", " If FILE missing, empty or '~', it defaults to ~/sqlite_params.sdb", " set ?TOPT? NAME VALUE Give SQL parameter NAME a value of VALUE", - " NAME must begin with one of $,:,@,? for bindings, VALUE is the space-", - " joined argument list. TOPT may be one of {-b -i -n -r -t} to cast the", - " effective value to BLOB, INT, NUMERIC, REAL or TEXT respectively.", + " NAME must begin with one of $,:,@,? for bindings, or with a letter", + " to be executable; value is the space-joined argument list.", + " Option TOPT may be one of {-b -i -n -r -t} to cast effective value", + " to BLOB, INT, NUMERIC, REAL or TEXT respectively.", " unset ?NAMES? Remove named parameter(s) from parameters table", ]; DISPATCHABLE_COMMAND( parameter 2 2 0 ){ int rc = 0; open_db(p,0); @@ -11019,52 +10860,69 @@ } }else #ifndef SQLITE_NOHAVE_SYSTEM /* .parameter edit ?NAMES? ** Edit the named parameters. Any that do not exist are created. + ** New ones get a uses tag auto-selected by their leading char. */ if( strcmp(azArg[1],"edit")==0 ){ ShellInState *psi = ISS(p); int ia = 2; int eval = 0; - int edSet; - if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){ utf8_printf(STD_ERR, "Error: " ".parameter edit can only be used interactively.\n"); return DCR_Error; } param_table_init(db); - edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 ); - if( edSet < 0 ) return DCR_Error; - else ia += edSet; + if( psi->zEditor==0 ){ + const char *zE = getenv("VISUAL"); + if( zE!=0 ) psi->zEditor = smprintf("%s", zE); + } + if( nArg>=3 && azArg[2][0]=='-' ){ + char *zArg = (azArg[2][1]=='-')? azArg[2]+2 : azArg[2]+1; + if( strncmp(zArg,"editor=",7)==0 ){ + sqlite3_free(psi->zEditor); + /* Accept an initial -editor=? option. */ + psi->zEditor = smprintf("%s", zArg+7); + ++ia; + } + } + if( psi->zEditor==0 ){ + utf8_printf(STD_ERR, + "Either set env-var VISUAL to name an" + " editor and restart, or rerun\n " + ".parameter edit with an initial " + "edit option, --editor=EDITOR_COMMAND .\n"); + return DCR_Error; + } /* Future: Allow an option whereby new value can be evaluated * the way that .parameter set ... does. */ while( ia < nArg ){ ParamTableUse ptu; - char *zA = azArg[ia]; - char cf = (zA[0]=='-')? zA[1] : 0; - if( cf!=0 && zA[2]==0 ){ + char cf = (azArg[ia][0]=='-')? azArg[ia][1] : 0; + if( cf!=0 && azArg[ia][2]==0 ){ ++ia; switch( cf ){ case 'e': eval = 1; continue; case 't': eval = 0; continue; default: - utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", zA); + utf8_printf(STD_ERR, "Error: bad .parameter name: %s\n", + azArg[--ia]); return DCR_Error; } } - ptu = classify_param_name(zA); - if( ptu!=PTU_Binding ){ - utf8_printf(STD_ERR, "Error: " - "%s cannot be a binding parameter name.\n", zA); + ptu = classify_param_name(azArg[ia]); + if( ptu==PTU_Nil ){ + utf8_printf(STD_ERR, "Error: %s cannot be a binding or executable" + " parameter name.\n", azArg[ia]); return DCR_Error; } - rc = edit_one_kvalue(db, zA, eval, ptu, psi->zEditor); + rc = edit_one_param(db, azArg[ia], eval, ptu, psi->zEditor); ++ia; - if( rc!=0 ) return DCR_Error; + if( rc!=0 ) return rc; } }else #endif /* .parameter init @@ -11080,26 +10938,26 @@ ** list displays names, values and uses. ** ls displays just the names. */ if( nArg>=2 && ((strcmp(azArg[1],"list")==0) || (strcmp(azArg[1],"ls")==0)) ){ - list_pov_entries(p, PTU_Binding, azArg[1][1]=='s', azArg+2, nArg-2); + list_params(p, PTU_Nil, azArg[1][1]=='s', azArg+2, nArg-2); }else /* .parameter load ** Load all or named parameters from specified or default (DB) file. */ if( strcmp(azArg[1],"load")==0 ){ param_table_init(db); - rc = kv_pairs_load(db, PTU_Binding, (const char **)azArg+1, nArg-1); + rc = parameters_load(db, (const char **)azArg+1, nArg-1); }else /* .parameter save ** Save all or named parameters into specified or default (DB) file. */ if( strcmp(azArg[1],"save")==0 ){ - rc = kv_pairs_save(db, PTU_Binding, (const char **)azArg+1, nArg-1); + rc = parameters_save(db, (const char **)azArg+1, nArg-1); }else /* .parameter set NAME VALUE ** Set or reset a bind parameter. NAME should be the full parameter ** name exactly as it appears in the query. (ex: $abc, @def). The @@ -11108,17 +10966,18 @@ */ if( nArg>=4 && strcmp(azArg[1],"set")==0 ){ char cCast = option_char(azArg[2]); int inv = 2 + (cCast != 0); ParamTableUse ptu = classify_param_name(azArg[inv]); - if( ptu!=PTU_Binding ){ + if( ptu==PTU_Nil ){ utf8_printf(STD_ERR, "Error: %s is not a usable parameter name.\n", azArg[inv]); rc = 1; }else{ param_table_init(db); - rc = param_set(db, cCast, azArg[inv], &azArg[inv+1], &azArg[nArg]); + rc = param_set(db, cCast, azArg[inv], + &azArg[inv+1], &azArg[nArg], ptu); if( rc!=SQLITE_OK ){ utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(db)); rc = 1; } } @@ -11127,11 +10986,11 @@ { /* If no command name and arg count matches, show a syntax error */ showHelp(ISS(p)->out, "parameter", p); return DCR_CmdErred; } - return DCR_Ok | (rc!=0); + return rc; } /***************** * The .print, .progress and .prompt commands */ @@ -13015,139 +12874,10 @@ goto teach_fail; } return DCR_Ok; } -/***************** - * The .vars command - */ -COLLECT_HELP_TEXT[ - ".vars ?OPTIONS? ... Manipulate and display shell variables", - " clear ?NAMES? Erase all or only given named variables", -#ifndef SQLITE_NOHAVE_SYSTEM - " edit ?-e? NAME Use edit() to create or alter variable NAME", - " With a -e option, the edited value is evaluated as a SQL expression.", -#endif - " list ?PATTERNS? List shell variables table values", - " Alternatively, to list just some or all names: ls ?PATTERNS?", - " load ?FILE? ?NAMES? Load some or all named variables from FILE", - " If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb", - " save ?FILE? ?NAMES? Save some or all named variables into FILE", - " If FILE missing, empty or '~', it defaults to ~/sqlite_vars.sdb", - " set NAME VALUE Give shell variable NAME a value of VALUE", - " NAME must begin with a letter to be executable by .x, Other leading", - " characters have special uses. VALUE is the space-joined arguments.", - " unset ?NAMES? Remove named variables(s) from variables table", -]; -DISPATCHABLE_COMMAND( vars 2 1 0 ){ - DotCmdRC rv = DCR_Ok; - sqlite3 *dbs = p->dbShell; - const char *zCmd = (nArg>1)? azArg[1] : "ls"; - int rc = 0; - int ncCmd = strlen30(zCmd); - - if( *zCmd>'e' && ncCmd<2 ) return DCR_Ambiguous|1; -#define SUBCMD(scn) (strncmp(zCmd, scn, ncCmd)==0) - - /* This could be done lazily, but with more code. */ - if( (dbs && ensure_shvars_table(dbs)!=SQLITE_OK) ){ - return DCR_Error; - }else{ - if( ensure_shell_db(p)!=SQLITE_OK ) return DCR_Error; - dbs = p->dbShell; - assert(dbs!=0); - if( ensure_shvars_table(dbs)!=SQLITE_OK ) return DCR_Error; - } - - /* .vars clear and .vars unset ?NAMES? - ** Delete some or all key/value pairs from the shell variables table. - ** Without any arguments, clear deletes them all and unset does nothing. - */ - if( SUBCMD("clear") || SUBCMD("unset") ){ - if( (nArg>2 || zCmd[0]=='c') ){ - sqlite3_str *sbZap = sqlite3_str_new(dbs); - char *zSql; - sqlite3_str_appendf - (sbZap, "DELETE FROM "SHVAR_TABLE_SNAME" WHERE key "); - append_in_clause(sbZap, - (const char **)&azArg[2], (const char **)&azArg[nArg]); - zSql = sqlite3_str_finish(sbZap); - shell_check_oom(zSql); - rc = sqlite3_exec(dbs, zSql, 0, 0, 0); - sqlite3_free(zSql); - } -#ifndef SQLITE_NOHAVE_SYSTEM - }else if( SUBCMD("edit") ){ - ShellInState *psi = ISS(p); - int ia = 2; - int eval = 0; - int edSet; - - if( !INSOURCE_IS_INTERACTIVE(psi->pInSource) ){ - utf8_printf(STD_ERR, "Error: " - ".vars edit can only be used interactively.\n"); - return DCR_Error; - } - edSet = attempt_editor_set(psi, azArg[0], (nArg>2)? azArg[2] : 0 ); - if( edSet < 0 ) return DCR_Error; - else ia += edSet; - while( ia < nArg ){ - ParamTableUse ptu; - char *zA = azArg[ia]; - char cf = (zA[0]=='-')? zA[1] : 0; - if( cf!=0 && zA[2]==0 ){ - ++ia; - switch( cf ){ - case 'e': eval = 1; continue; - case 't': eval = 0; continue; - default: - utf8_printf(STD_ERR, "Error: bad .vars edit option: %s\n", zA); - return DCR_Error; - } - } - ptu = classify_param_name(zA); - if( ptu!=PTU_Script ){ - utf8_printf(STD_ERR, - "Error: %s cannot be a shell variable name.\n", zA); - return DCR_Error; - } - rc = edit_one_kvalue(dbs, zA, eval, ptu, psi->zEditor); - ++ia; - if( rc!=0 ) return DCR_Error; - } -#endif - }else if( SUBCMD("list") || SUBCMD("ls") ){ - int nTailArgs = nArg - 1 - (nArg>1); - char **pzTailArgs = azArg + 1 + (nArg>1); - list_pov_entries(p, PTU_Script, zCmd[1]=='s', pzTailArgs, nTailArgs); - }else if( SUBCMD("load") ){ - rc = kv_pairs_load(dbs, PTU_Script, (const char **)azArg+1, nArg-1); - }else if( SUBCMD("save") ){ - rc = kv_pairs_save(dbs, PTU_Script, (const char **)azArg+1, nArg-1); - }else if( SUBCMD("set") ){ - ParamTableUse ptu; - if( nArg<4 ) return DCR_Missing; - ptu = classify_param_name(azArg[2]); - if( ptu!=PTU_Script ){ - utf8_printf(STD_ERR, - "Error: %s is not a valid shell variable name.\n", azArg[2]); - rc = 1; - }else{ - rc = shvar_set(dbs, azArg[2], &azArg[3], &azArg[nArg]); - if( rc!=SQLITE_OK ){ - utf8_printf(STD_ERR, "Error: %s\n", sqlite3_errmsg(dbs)); - rc = 1; - } - } - }else{ - showHelp(ISS(p)->out, "vars", p); - return DCR_CmdErred; - } - return DCR_Ok | (rv!=0) | (rc!=0); -#undef SUBCMD -} - /***************** * The .vfsinfo, .vfslist, .vfsname and .version commands */ COLLECT_HELP_TEXT[ ".version Show a variety of version info", @@ -13380,13 +13110,13 @@ } DISPATCHABLE_COMMAND( x ? 1 0 ){ int ia, nErrors = 0; sqlite3_stmt *pStmt = 0; - sqlite3 *dbs = p->dbShell; + sqlite3 *db = 0; DotCmdRC rv = DCR_Ok; - enum { AsVar, AsString, AsFile } evalAs = AsVar; + enum { AsParam, AsString, AsFile } evalAs = AsParam; for( ia=1; ia < nArg; ++ia ){ char *zSubmit = 0; const char *zOpt = azArg[ia]; if ( *zOpt == '-' ){ @@ -13400,22 +13130,29 @@ } } if( zOpt==0 ) continue; } switch( evalAs ){ - case AsVar: + case AsParam: if( pStmt==0 ){ int rc; - if( dbs==0 || !shvars_table_exists(dbs) ){ - utf8_printf(STD_ERR, - "\".x vname\" can only be done after .var set ... .\n"); + 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(dbs, "SELECT value FROM "SHVAR_TABLE_SNAME + 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, SHVAR_TABLE_SNAME" is wrongly created.\n"); + utf8_printf(STD_ERR, PARAM_TABLE_SNAME" is wrongly created.\n"); return DCR_Error; } } if( isalpha(azArg[ia][0]) ){ int rc = sqlite3_reset(pStmt); @@ -13438,11 +13175,11 @@ }else{ continue; /* All white (or OOM), ignore. */ } }else{ utf8_printf(STD_ERR, - "Skipping var '%s' (not set and executable.)\n", + "Skipping parameter '%s' (not set and executable.)\n", azArg[ia]); ++nErrors; } }else{ utf8_printf(STD_ERR, @@ -13598,18 +13335,18 @@ /* Prepare an iterator that will produce a sequence of MetaCommand * pointers whose referents names match the given cmdFragment. */ static MetaMatchIter findMatchingMetaCmds(const char *cmdFragment, ShellExState *psx){ MetaMatchIter rv = { psx, 0, 0 }; - if( psx->dbShell==0 || ISS(psx)->bDbDispatch==0 ){ + if( psx->dbShell==0 ){ rv.zPattern = smprintf("%s*", cmdFragment? cmdFragment : ""); shell_check_oom((void *)rv.zPattern); rv.pMC = (MetaCommand *)command_table; }else{ /* Prepare rv.stmt to yield results glob-matching cmdFragment. */ const char *zSql = - "SELECT name, extIx, cmdIx FROM "SHELL_DISP_VIEW" " + "SELECT name, extIx, cmdIx FROM ActiveCommands " "WHERE name glob (?||'*') ORDER BY name"; sqlite3_prepare_v2(psx->dbShell, zSql, -1, &rv.stmt, 0); sqlite3_bind_text(rv.stmt, 1, cmdFragment? cmdFragment : "", -1, 0); } return rv; @@ -13666,16 +13403,16 @@ */ MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx, /* out */ int *pnFound){ if( pnFound ) *pnFound = 0; #if SHELL_DYNAMIC_EXTENSION - if( ISS(psx)->bDbDispatch ){ + if( psx->dbShell!=0 ){ int rc; int extIx = -1, cmdIx = -1, nf = 0; sqlite3_stmt *pStmt = 0; const char *zSql = "SELECT COUNT(*), extIx, cmdIx" - " FROM "SHELL_DISP_VIEW" WHERE name glob (?||'*')"; + " FROM ActiveCommands 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); @@ -14638,11 +14375,11 @@ /* Here, the group is fully collected or known to be incomplete forever. */ switch( disposition ){ case Dumpable: echo_group_input(psi, *pzLineUse); #if SHELL_DYNAMIC_EXTENSION - if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS); + if( inKind==Script && pSS!=0 ) pSS->pMethods->restartCompletionScan(pSS); #endif break; case Runnable: switch( inKind ){ case Sql: @@ -14663,11 +14400,11 @@ #if SHELL_DYNAMIC_EXTENSION case Script: { char *zErr = 0; DotCmdRC dcr; assert(pSS!=0); - pSS->pMethods->resetCompletionScan(pSS); + pSS->pMethods->restartCompletionScan(pSS); dcr = pSS->pMethods->runScript(pSS, *pzLineUse+nLeadWhite, XSS(psi), &zErr); if( dcr!=DCR_Ok || zErr!=0 ){ /* ToDo: Handle errors more informatively and like dot commands. */ nErrors += (dcr!=DCR_Ok); @@ -14689,11 +14426,11 @@ break; case Erroneous: utf8_printf(STD_ERR, "Error: Input incomplete at line %d of \"%s\"\n", psi->pInSource->lineno, psi->pInSource->zSourceSay); #if SHELL_DYNAMIC_EXTENSION - if( inKind==Script && pSS!=0 ) pSS->pMethods->resetCompletionScan(pSS); + if( inKind==Script && pSS!=0 ) pSS->pMethods->restartCompletionScan(pSS); #endif ++nErrors; break; case Ignore: break; Index: src/shext_linkage.h ================================================================== --- src/shext_linkage.h +++ src/shext_linkage.h @@ -276,11 +276,11 @@ DECORATE_METHOD(Derived,execute) \ } /* This function pointer has the same signature as the sqlite3_X_init() * function that is called as SQLite3 completes loading an extension. - * It is used as a process-unique identifier for a loaded extension. + * It is used as a process-unique identifier for a loaded extention. */ typedef int (*ExtensionId) (sqlite3 *, char **, const struct sqlite3_api_routines *); typedef struct Prompts { Index: tool/mkshellc.tcl ================================================================== --- tool/mkshellc.tcl +++ tool/mkshellc.tcl @@ -75,11 +75,10 @@ set runMode normal set ::lineTags 0 ; # 0 => none, 1 => source change, 2 => line syncs, 3 => more set ::tclGenerate 0 -set ::invokeLeadWhite "" set ::verbosity 0 set ::inFiles {} set ::topInfile "?" set ::presumedOutfile "?" set ::targetProgram "?" @@ -366,11 +365,10 @@ DISPATCH_CONFIG {^\[} \ DISPATCHABLE_COMMAND {^\(([\w\? ]+)\)(\S)\s*$} \ EMIT_METACMD_INIT {^\((\d*)\)} \ INCLUDE {^(?:\(\s*(\w+)\s*\))|(?:\s+([\w./\\]+)\M)} \ IGNORE_COMMANDS {^\(\s*([-+\w ]*)\)\s*;\s*} \ - TCL_CSTR_LITERAL {^\(([ \w*=]+)\)(\S)\s*$} \ ] # Names of the subcaptures as formal parameter to macro procs. # COMMENT tailCapture_Commentary # CONDITION_COMMAND tailCapture_Cmd_Condition # CONFIGURE_DISPATCH tailCapture_Empty @@ -384,20 +382,18 @@ COLLECT_HELP_TEXT "\[\n \n \];" \ COMMENT " " \ CONDITION_COMMAND "( name pp_expr );" \ DISPATCH_CONFIG "\[\n \n \];" \ DISPATCHABLE_COMMAND \ - "( name args... ){\n \n }" \ + "( name args... ){\n \n }" \ EMIT_METACMD_INIT "( indent );" \ INCLUDE {( )} \ SKIP_COMMANDS "( );" \ - TCL_CSTR_LITERAL \ - "( C string literal decl = ) {\n \n };" \ ] # RE for early discard of non-macro lines, matching all above keywords set ::macroKeywordTailRE \ - {^\s{0,40}((?:(?:CO)|(?:DI)|(?:EM)|(?:IN)|(?:TCL)|(?:SK))[A-Z_]+)\M(.+)$} + {^\s{0,8}((?:(?:CO)|(?:DI)|(?:EM)|(?:IN)|(?:SK))[A-Z_]+)\M(.+)$} ######## # Macro procs, general signature and usage: # inSrc is a triple, { input_filename open_input_stream input_lines_consumed }. # Arg 2 is the macro tail as RE-captured by one of ::macroTailREs . @@ -510,60 +506,10 @@ } } return $iAte } -# Translate a TCL line into a quoted/escaped C string literal line. -set ::bs "\\" -set ::dbs "\\\\" -set ::dq "\"" -proc tcl2c {line} { - regsub -all $::dbs $line {\\\\} line - regsub -all $::dq $line "$::bs$::dq" line - regsub {^\s*} $line "&$::dq" line - return "$line${::bs}n$::dq" -} - -proc TCL_CSTR_LITERAL {inSrc tailCapture ostrm} { - # Collect and translate TCL code into a C string literal definition. - # The macro call, TCL_CSTR_LITERAL( ... = ){, will name and declare - # the type of the literal, and introduce the TCL lines, while lines - # that follow, upto but not including one beginning with }, will be - # quoted and escaped as needed to get the same logical content as a - # multi-line initializer for the declared C string variable. - foreach { srcFile istrm srcPrecLines } $inSrc break - set litdef [lindex $tailCapture 0] - set tc [lindex $tailCapture 1] - if {$tc ne $::lb} { - yap_usage "TCL_CSTR_LITERAL($litdef)$tc" TCL_CSTR_LITERAL - incr $::iShuffleErrors - return 0 - } - set iAte 1 - set oIndent "" - set firstIndent -1 - set clLines [list "$::invokeLeadWhite$litdef"] - while {![eof $istrm]} { - set tclLine [gets $istrm] - incr iAte - if {[regexp "^\\s*$::rb\\s*\;$" $tclLine]} { - regsub "^(\\s*)$::rb" $tclLine {\1} tclLine - lappend clLines $tclLine - break - } - set lrt [string trimright $tclLine] - set lt [string trimleft $lrt] - if {$lt eq ""} { - lappend clLines "" - continue - } - lappend clLines [tcl2c $tclLine] - } - emit_sync $clLines $ostrm $srcPrecLines $srcFile - return $iAte; -} - proc DISPATCHABLE_COMMAND {inSrc tailCapture ostrm} { # Generate and emit a function definition, maybe wrapped as set by # CONDITION_COMMAND(), and generate/collect its dispatch table entry, # as determined by its actual arguments and DISPATCH_CONFIG parameters. foreach { srcFile istrm srcPrecLines } $inSrc break @@ -676,15 +622,12 @@ if {![regexp $::macroKeywordTailRE $lx ma macro tail] \ || ![info exists ::macroTailREs($macro)]} { return 0 } # It's an attempted macro invocation line. Process or fail and yap. - # First, capture its leading space for those macros respecting indent. - regexp {^(\s*)} $lx ma ::invokeLeadWhite - # Second, separate the tail according to this macro's RE for such. set tailCap [regexp -inline $::macroTailREs($macro) $tail] - # Third, call like-named proc with args captured by the corresponding RE. + # Call like-named proc with any args captured by the corresponding RE. return [$macro $inSrc [lrange $tailCap 1 end] $ostrm] } array set ::typedefsSeen {} array set ::includesDone {}