From: larrybr Date: Thu, 20 Jan 2022 05:20:27 +0000 (+0000) Subject: A WIP checkin, progress toward what .help promises X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b6265f854ce71ec47df39fe471c89f752a1b9157;p=thirdparty%2Fsqlite.git A WIP checkin, progress toward what .help promises FossilOrigin-Name: 4688e6dff88527dbff436b811512206d31a9695095776c4e1fdff95da0b0c4d4 --- diff --git a/manifest b/manifest index 56a8967ee0..c4baec7ba3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initial\shelp\schanges\sfor\s.script\s(and\senhanced\s.parameter) -D 2022-01-19T21:11:23.464 +C A\sWIP\scheckin,\sprogress\stoward\swhat\s.help\spromises +D 2022-01-20T05:20:27.018 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -552,7 +552,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c 359bc0e445d427583d2ab6110433a5dc777f64a0ecdf8d24826d8b475233ead9 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c a4a23a70f0a24a1103ac9698f6be181a6ec7ff6c19e03e8899c43cb6d2af09d6 -F src/shell.c.in 89463e86281322630ff531bd6a44f4d53f939b336d879394bc7adfad20f6fb37 +F src/shell.c.in 732a2ee32b11825b797754bacd77cb4b09ff813c2f77786fc5d9f3a803fa4997 F src/sqlite.h.in a5e0d6bd47e67aabf1475986d36bdcc7bfa9e06566790ebf8e3aa7fa551c9f99 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 01eb85e4f2759a5ee79c183f4b2877889d4ffdc49d27ae74529c9579e3c8c0ef @@ -1938,8 +1938,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 e799a35f2bf85ce43b476738bfbd9b6b378bbf02fa0708dda0deba71dd37f608 -R 64292f6d5c9ab9e96ed89164c0ac1a9a +P a94ab403eb836d3fcb9710d22da5129f58db05d3be145bc77ce1c017761e7894 +R df6ba47c6584db70fd023a69e282f313 U larrybr -Z 69f6a83be4ba8df24b2fde157c9e5bc8 +Z 2d0ff93e3043918f6083e9631f60e14c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index a81b853b32..a2de2b2869 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a94ab403eb836d3fcb9710d22da5129f58db05d3be145bc77ce1c017761e7894 \ No newline at end of file +4688e6dff88527dbff436b811512206d31a9695095776c4e1fdff95da0b0c4d4 \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index 7a0eb785de..712ffe4734 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -1109,6 +1109,7 @@ struct ShellState { sqlite3_int64 szMax; /* --maxsize argument to .open */ char *zDestTable; /* Name of destination table when MODE_Insert */ char *zTempFile; /* Temporary file that might need deleting */ + char *zEditor; /* Name of editor if non-zero, then deletable*/ char zTestcase[30]; /* Name of current test case */ char colSeparator[20]; /* Column separator character for several modes */ char rowSeparator[20]; /* Row separator character for MODE_Ascii */ @@ -3029,8 +3030,14 @@ static void restore_debug_trace_modes(void){ sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, 3, &savedWhereTrace); } -/* Create the TEMP table used to store parameter bindings */ -static void bind_table_init(ShellState *p){ +/* Partition the temp.sqlite_parameters key namespace according to use. */ +enum ParamTableUses { PTU_Binding = 0, PTU_Script = 1 }; +#define PARAM_TABLE_NAME "sqlite_parameters" +#define PARAM_TABLE_SCHEMA "temp" +#define PARAM_TABLE_SNAME PARAM_TABLE_SCHEMA"."PARAM_TABLE_NAME + +/* Create the TEMP table used to store parameter bindings and SQL statements */ +static void param_table_init(ShellState *p){ int wrSchema = 0; int defensiveMode = 0; sqlite3_db_config(p->db, SQLITE_DBCONFIG_DEFENSIVE, -1, &defensiveMode); @@ -3040,7 +3047,8 @@ static void bind_table_init(ShellState *p){ sqlite3_exec(p->db, "CREATE TABLE IF NOT EXISTS temp.sqlite_parameters(\n" " key TEXT PRIMARY KEY,\n" - " value\n" + " value,\n" + " uses INT DEFAULT (0)" /* aka PTU_Binding */ ") WITHOUT ROWID;", 0, 0, 0); sqlite3_db_config(p->db, SQLITE_DBCONFIG_WRITABLE_SCHEMA, wrSchema, 0); @@ -3052,12 +3060,14 @@ static void bind_table_init(ShellState *p){ ** ** Parameter bindings are taken from a TEMP table of the form: ** -** CREATE TEMP TABLE sqlite_parameters(key TEXT PRIMARY KEY, value) +** CREATE TEMP TABLE +** sqlite_parameters(key TEXT PRIMARY KEY, value, uses INT) ** WITHOUT ROWID; ** ** No bindings occur if this table does not exist. The name of the table ** begins with "sqlite_" so that it will not collide with ordinary application -** tables. The table must be in the TEMP schema. +** tables. The table must be in the TEMP schema. Only rows with +** uses=PTU_Binding are eligible for parameter binding. */ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ int nVar; @@ -3073,8 +3083,9 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ } rc = sqlite3_prepare_v2(pArg->db, "SELECT value FROM temp.sqlite_parameters" - " WHERE key=?1", -1, &pQ, 0); + " WHERE key=?1 AND uses=0", -1, &pQ, 0); if( rc || pQ==0 ) return; + assert( PTU_Binding==0 ); /* instead of working symbol value into query */ for(i=1; i<=nVar; i++){ char zNum[30]; const char *zVar = sqlite3_bind_parameter_name(pStmt, i); @@ -7615,6 +7626,79 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ } #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +struct param_row { char * value; int uses; int hits; }; + +static int param_find_callback(void *pData, int nc, char **pV, char **pC){ + assert(nc>=1); + assert(strcmp(pC[0],"value")==0); + 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 = sqlite3_mprintf("%s", pV[0]); /* source owned by statement */ + if( nc>1 ) pParam->uses = (int)integerValue(pV[1]); + ++pParam->hits; + return 0; +} +/* 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_param(sqlite3 *db, char *name, int eval, + int uses, const char * zEditor){ + struct param_row paramVU = {0,0,0}; + char * zSql = sqlite3_mprintf + ("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( eval!=0 ){ + /* ToDo: Run an eval-like update, leaving as-is if it's a bad expr. */ + // int rv = edit_one_param(db, name, 0, uses); + } + if( paramVU.hits==1 && paramVU.uses==uses){ + /* Editing an existing value of same kind. */ + sqlite3_free(paramVU.value); + zSql = sqlite3_mprintf + ("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(paramVU.value==0); + zSql = sqlite3_mprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,edit('-- %q%s', %Q),%d)", + name, name, "\n", zEditor, uses); + } + shell_check_oom(zSql); + sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + return 0; /* ToDo: add some error checks */ +} + +static void append_in_clause(sqlite3_str *pStr, char **azBeg, char **azLim){ + /* An empty IN list is the same as always true (for non-NULL LHS) + * for this clause, which assumes a trailing LHS operand and space. + * If that is not the right result, guard the call against it. + * This is used for .parameter and .script ?NAMES? options, + * where a missing list means all the qualifying entries. + */ + if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL"); + else{ + char cSep = '('; + sqlite3_str_appendf(pStr, "IN"); + while( azBegdb, "DROP TABLE IF EXISTS temp.sqlite_parameters;", - 0, 0, 0); + if( nArg>=2 && strcmp(azArg[1],"clear")==0 + && + sqlite3_table_column_metadata + (p->db, PARAM_TABLE_SCHEMA, PARAM_TABLE_NAME, + 0, 0, 0, 0, 0, 0)==SQLITE_OK ){ + sqlite3_str *sbDML = sqlite3_str_new(p->db); + char *zSql; + sqlite3_str_appendf + (sbDML, "DELETE FROM "PARAM_TABLE_SNAME" WHERE uses=0 AND key "); + append_in_clause(sbDML, &azArg[2], &azArg[nArg]); + zSql = sqlite3_str_finish(sbDML); + shell_check_oom(zSql); + sqlite3_exec(p->db, zSql, 0, 0, 0); + sqlite3_free(zSql); + }else + + /* .parameter edit ?NAMES? + ** Edit the named bind parameters. Any that do not exist are created. + */ + if( nArg>=2 && strcmp(azArg[1],"edit")==0 ){ + int ia = 2; + int eval = 0; + param_table_init(p); + if( p->zEditor==0 ){ + const char *zE = getenv("VISUAL"); + if( zE!=0 ) p->zEditor = sqlite3_mprintf("%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(p->zEditor); + /* Accept an initial -editor=? option. */ + p->zEditor = sqlite3_mprintf("%s", zArg+7); + ++ia; + } + } + if( p->zEditor==0 ){ + /* This is klutzy, but edit is for interactive use. So this + * problem, due to not heeding the .parameter doc, can be + * fixed with a modest inconvenience to the casual user. + */ + utf8_printf(stderr, + "Either set env-var VISUAL to name an" + " editor and restart, or rerun\n " + ".parameter edit with an initial " + "edit option, --editor=EDITOR_NAME .\n"); + rc = 1; + goto meta_command_exit; + } + /* ToDo: Allow an option whereby new value can be evaluated + * the way that .parameter set ... does. + */ + while( ia < nArg ){ + 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(stderr, "Error: bad .parameter name: %s\n", + azArg[--ia]); + rc = 1; + goto meta_command_exit; + } + } + rc = edit_one_param(p->db, azArg[ia], eval, PTU_Binding, p->zEditor); + ++ia; + if( rc!=0 ) goto meta_command_exit; + } }else /* .parameter list @@ -9241,7 +9392,7 @@ static int do_meta_command(char *zLine, ShellState *p){ ** Create it if necessary. */ if( nArg==2 && strcmp(azArg[1],"init")==0 ){ - bind_table_init(p); + param_table_init(p); }else /* .parameter set NAME VALUE @@ -9256,7 +9407,7 @@ static int do_meta_command(char *zLine, ShellState *p){ sqlite3_stmt *pStmt; const char *zKey = azArg[2]; const char *zValue = azArg[3]; - bind_table_init(p); + param_table_init(p); zSql = sqlite3_mprintf( "REPLACE INTO temp.sqlite_parameters(key,value)" "VALUES(%Q,%s);", zKey, zValue); @@ -11872,6 +12023,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ output_reset(&data); data.doXdgOpen = 0; clearTempFile(&data); + sqlite3_free(data.zEditor); #if !SQLITE_SHELL_IS_UTF8 for(i=0; i