From: larrybr Date: Tue, 22 Feb 2022 22:28:37 +0000 (+0000) Subject: Sync w/3.38, add .parameter ls to CLI X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b524e6741a200fec66519d7484e536be5b6592e1;p=thirdparty%2Fsqlite.git Sync w/3.38, add .parameter ls to CLI FossilOrigin-Name: 8c9a5fb26ba045edef1269c5f5e8c8d87fa890b88ddb1121be72514a389a845d --- b524e6741a200fec66519d7484e536be5b6592e1 diff --cc manifest index 9aeb5cef06,5ca957c244..e5f4607332 --- a/manifest +++ b/manifest @@@ -1,5 -1,5 +1,5 @@@ - C Sync\swith\strunk - D 2022-02-15T17:04:37.699 -C Version\s3.38.0 -D 2022-02-22T18:58:40.488 ++C Sync\sw/3.38,\sadd\s.parameter\sls\sto\sCLI ++D 2022-02-22T22:28:37.952 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@@ -553,8 -553,8 +553,8 @@@ F src/random.c 097dc8b31b8fba5a9aca1697 F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 3baa9dd8cf240654773c7974e2bcce398ac9dd24419c36684156963defe43b35 - F src/shell.c.in c87d57f80663efb5aff3935bda7e6264d25636f41108a58edca9b628d9bc3b02 - F src/sqlite.h.in 7047c4b60fa550264d6363bb1d983540e7828fb19d2d1e5aa43b52ca13144807 -F src/shell.c.in 14cdfba32c73cb06169e50cd448632c28359f2bab2a0f803dc4a7f46dfc5b6fa ++F src/shell.c.in 5a8a5350b7284f185aed21c140e6c81a33b54873f89a3568600cf6a37dd4acac + F src/sqlite.h.in e30cedf008d9c51511f4027a3739b727a588da553424748b48d2393f85dbde41 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h a95cb9ed106e3d39e2118e4dcc15a14faec3fa50d0093425083d340d9dfd96e6 F src/sqliteInt.h f8814239fb1f95056555e2d7fa475750e64681cac4221fb03610d1fde0b79d53 @@@ -1390,13 -1390,12 +1390,13 @@@ F test/shared_err.test 32634e404a3317ee F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 F test/shell1.test b224e0793c5f48aa3749e65d8c64b93a30731bd206f2e41e6c5f1bee1bdb16c6 F test/shell2.test 89e4b2db062d52baed75022227b462d085cff495809de1699652779d8e0257d6 -F test/shell3.test a50628ab1d78d90889d9d3f32fb2c084ee15674771e96afe954aaa0accd1de3c -F test/shell4.test 8f6c0fce4abed19a8a7f7262517149812a04caa905d01bdc8f5e92573504b759 +F test/shell3.test 4ddea2bd182e7e03249911b23ae249e7cb8a91cdc86e695198725affabe8ecd3 +F test/shell4.test 867e0675d7b096d6b93de534541e07c7f5ffafa3e6612695ecf55180802e1115 - F test/shell5.test 3be444397eb1e91619ce289a6216a8df9ac690cc45d5e9595f60e750a944161f + F test/shell5.test 2b521446f55146c9aafccd0946bdb44ae288b0d25bd48f722e041974fdeeb04a F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f F test/shell8.test 388471d16e4de767333107e30653983f186232c0e863f4490bb230419e830aae - F test/shell9.test b6f07789fef57b5d194a3b2b8aba8292d363f23273cdc4541de7626ea659bc91 ++F test/shell9.test 03e28206c016b27b3c79f43c3b09c843468dd8961f980e60d37117ff1ce32908 F test/shmlock.test 3dbf017d34ab0c60abe6a44e447d3552154bd0c87b41eaf5ceacd408dd13fda5 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@@ -1945,8 -1944,10 +1945,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 - P 9c664984fd61d5858b436952d876bf3560333ee0edd8e1956cfe74cf9649511d 9edaeed56f2282fd4da935454178c38ab49d259aed96d4e720aae09050a53006 - R 241a1161ccd2087a62b9ac8bd7212d06 -P 7e3c9594390ac8defaf9825e14b4c19ef8c123b747971dd3d4df16110f443d3b -R 2222d568cabc2aa5ac864df535d62eb2 -T +sym-release * -T +sym-version-3.38.0 * -U drh -Z 2297eaa4fd3e9ed1fd7ae8af6c650431 ++P 2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241 40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab ++R 5520524d725423f68aaec6a9faa61a16 +U larrybr - Z bc8dcf303171aacc369bf09b47a22c1e ++Z 1b4999d38bc60cdea13bcadad5fd8a8e # Remove this line to create a well-formed Fossil manifest. diff --cc manifest.uuid index 92ab286c2c,b9b6e8d9ec..f2c7191aa1 --- a/manifest.uuid +++ b/manifest.uuid @@@ -1,1 -1,1 +1,1 @@@ - 2b4a295c58a908288840157c3e08289af8c1d287f72e5f14b7adfd98065bf241 -40fa792d359f84c3b9e9d6623743e1a59826274e221df1bde8f47086968a1bab ++8c9a5fb26ba045edef1269c5f5e8c8d87fa890b88ddb1121be72514a389a845d diff --cc src/shell.c.in index 35b5d75c8f,354c9a849b..543e76149f --- a/src/shell.c.in +++ b/src/shell.c.in @@@ -4649,25 -4365,14 +4650,26 @@@ static const char *(azHelp[]) = " --bom Prefix output with a UTF8 byte-order mark", " -e Send output to the system text editor", " -x Send output as CSV to a spreadsheet", - ".parameter CMD ... Manage SQL parameter bindings", - " clear Erase all bindings", - " init Initialize the TEMP table that holds bindings", - " list List the current parameter bindings", - " set PARAMETER VALUE Given SQL parameter PARAMETER a value of VALUE", - " PARAMETER should start with one of: $ : @ ?", - " unset PARAMETER Remove PARAMETER from the binding table", - ".print STRING... Print literal STRING", + ".parameter CMD ... Manage SQL parameter bindings and scripts table", + " clear ?NAMES? Erase all or only given named parameters", +#ifndef SQLITE_NOHAVE_SYSTEM + " edit ?OPT? NAME ... Use edit() to create or alter parameter NAME", + " OPT may be -t or -e to use edited value as text or evaluate it first.", +#endif + " init Initialize TEMP table for bindings and scripts", - " list List parameters table binding and script 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, or with a letter", + " to be executable; the value is following argument list, space-joined.", + " 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", + ".print STRING... Print literal STRING, then a newline", #ifndef SQLITE_OMIT_PROGRESS_CALLBACK ".progress N Invoke progress handler after every N opcodes", " --limit N Interrupt after N progress callbacks", @@@ -8190,544 -7826,8 +8192,602 @@@ static int recoverDatabaseCmd(ShellStat } #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +/* +** The .shxopts command, for setting or listing shell extension options. + */ +#if SHELL_EXTENSIONS +static int shxoptsCommand(char *azArg[], int nArg, ShellState *p, char **pzE){ + static struct { const char *name; u8 mask; } shopts[] = { +#if SHELL_DYNAMIC_COMMANDS + {"dyn_cmds", 1<1 ){ + for( ia=1; iabExtendedDotCmds |= shopts[io].mask; + else p->bExtendedDotCmds &= ~shopts[io].mask; + break; + } + } + if( io==ArraySize(shopts) ){ + zAbout = azArg[ia]; + zMoan = "is not a recognized option name"; + goto moan_error; + } + } + }else{ + raw_printf(p->out, + " name value \"-shxopts set\"\n" + " -------- ----- ---------------\n"); + for( io=0; iobExtendedDotCmds & m) == m)? 1 : 0; + raw_printf(p->out, + " %9s %2d \"-shxopts 0x%02X\"\n", + shopts[io].name, v, m); + } + } + return 0; + moan_error: + raw_printf(stderr, "Error: %s %s\n", zAbout, zMoan); + return 1; +} +#endif + +static int execute_variables(char *azArg[], int nArg, ShellState *p){ + int ia, rc, nErrors = 0; + sqlite3_stmt *pStmt = 0; + open_db(p, 0); + if( p->db==0 ){ + utf8_printf(stderr, ".x can only be done with a database open.\n"); + return 1; + } + if( sqlite3_table_column_metadata(p->db, PARAM_TABLE_SCHEMA, + PARAM_TABLE_NAME, + "key", 0, 0, 0, 0, 0)!=SQLITE_OK ){ + utf8_printf(stderr, "No "PARAM_TABLE_SNAME" table exists.\n"); + return 1; + } + rc = sqlite3_prepare_v2 + (p->db, "SELECT value FROM "PARAM_TABLE_SNAME + " WHERE key=$1 AND uses=1", + -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, PARAM_TABLE_SNAME" is wrongly created.\n"); + return 1; + } + for( ia=1; ia < nArg; ++ia ){ + if( isalpha(azArg[ia][0]) ){ + rc = sqlite3_reset(pStmt); + rc = sqlite3_bind_text(pStmt, 1, azArg[ia], -1, 0); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + const unsigned char *zValue = sqlite3_column_text(pStmt, 0); + int nb = sqlite3_column_bytes(pStmt, 0); + while( nb>0 && IsSpace(zValue[nb-1]) ) --nb; + if( nb>0 ){ + /* The trailing newline (or some other placeholder) is important + * because one (or some other character) will likely be put in + * its place during process_input() line/group handling, along + * with a terminating NUL character. Without it, the NULL could + * land past the end of the allocation made just below. + */ + int nle = zValue[nb-1]=='\n'; + char *zSubmit = sqlite3_mprintf( "%.*s%s", nb, zValue, "\n"+nle ); + InSource inRedir + = INSOURCE_STR_REDIR(zSubmit, azArg[ia], p->pInSource); + shell_check_oom(zSubmit); + p->pInSource = &inRedir; + rc = process_input(p); + sqlite3_free(zSubmit); + p->pInSource = inRedir.pFrom; + }else{ + continue; /* All white, ignore. */ + } + }else{ + utf8_printf(stderr, + "Skipping parameter '%s' (not set and executable.)\n", + azArg[ia]); + ++nErrors; + } + }else{ + utf8_printf(stderr, + "Skipping badly named %s. Run \".help x\"\n", azArg[ia]); + ++nErrors; + } + } + sqlite3_finalize(pStmt); + return (rc==2)? 2 : nErrors>0; +} + +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; +} + +static void append_in_clause(sqlite3_str *pStr, + const char **azBeg, const char **azLim); ++static void append_glob_terms(sqlite3_str *pStr, const char *zColName, ++ const char **azBeg, const char **azLim); +static char *find_home_dir(int clearFlag); + +/* Create a home-relative pathname from ~ prefixed path. + * Return it, or 0 for any error. + * Caller must sqlite3_free() it. + */ +static char *home_based_path( const char *zPath ){ + char *zHome = find_home_dir(0); + char *zErr = 0; + assert( zPath[0]=='~' ); + if( zHome==0 ){ + zErr = "Cannot find home directory."; + }else if( zPath[0]==0 || (zPath[1]!='/' +#if defined(_WIN32) || defined(WIN32) + && zPath[1]!='\\' +#endif + ) ){ + zErr = "Malformed pathname"; + }else{ + return sqlite3_mprintf("%s%s", zHome, zPath+1); + } + utf8_printf(stderr, "Error: %s\n", zErr); + return 0; +} + +/* Transfer selected parameters between two parameter tables, for save/load. + * Argument bSaveNotLoad determines transfer direction and other actions. + * If it is true, the store DB will be created if not existent, and its + * table for keeping parameters will be created. Or failure is returned. + * If it is false, the store DB will be opened for read and its presumed + * table for keeping parameters will be read. Or failure is returned. + * + * Arguments azNames and nNames reference the ?NAMES? save/load arguments. + * If it is an empty list, all parameters will be saved or loaded. + * Otherwise, only the named parameters are transferred, if they exist. + * 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 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 = 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; + + /* Ensure store DB can be opened and/or created appropriately. */ + rc = sqlite3_open_v2(zStoreDbName, &dbStore, openFlags, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "Error: Cannot %s parameter store DB %s\n", + bSaveNotLoad? "open/create" : "read", zStoreDbName); + return rc; + } + /* Ensure it has the parameter store table, or handle its absence. */ + assert(dbStore!=0); + if( sqlite3_table_column_metadata + (dbStore, "main", PARAM_STORE_NAME, 0, 0, 0, 0, 0, 0)!=SQLITE_OK ){ + if( !bSaveNotLoad ){ + utf8_printf(stderr, "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 "PARAM_STORE_NAME"(\n" + " key TEXT PRIMARY KEY,\n" + " value,\n" + " uses INT\n" + ") WITHOUT ROWID;"; + rc = sqlite3_exec(dbStore, zCT, 0, 0, 0); + if( rc!=SQLITE_OK ){ + utf8_printf(stderr, "Cannot create table %s. Nothing saved.", zThere); + } + } + } + sqlite3_close(dbStore); + if( rc!=0 ) return rc; + + zSql = sqlite3_mprintf("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; + + sbCopy = sqlite3_str_new(db); + sqlite3_str_appendf + (sbCopy, "INSERT OR REPLACE INTO %s(key,value,uses)" + "SELECT key, value, uses FROM %s WHERE key ", zTo, zFrom); + append_in_clause(sbCopy, azNames, azNames+nNames); + zSql = sqlite3_str_finish(sbCopy); + shell_check_oom(zSql); + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + + sqlite3_exec(db, "DETACH "PARAM_STORE_SCHEMA";", 0, 0, 0); + return rc; +} + +/* Default location of parameters store DB for .parameters save/load. */ +static const char *zDefaultParamStore = "~/sqlite_params.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 *params_store_path(const char *zSpec){ + if( zSpec==0 || zSpec[0]==0 || strcmp(zSpec,"~")==0 ){ + return home_based_path(zDefaultParamStore); + }else if ( zSpec[0]=='~' ){ + return home_based_path(zSpec); + } + return zSpec; +} + +/* 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(stderr, "Cannot form parameter load path. Nothing loaded.\n"); + return 1; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + 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 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(stderr, "Cannot form parameter save path. Nothing saved.\n"); + return 1; + }else{ + const char **pzFirst = (nArg>2)? azArg+2 : 0; + int nNames = (nArg>2)? nArg-2 : 0; + 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 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, + ParamTableUse uses, const char * zEditor){ + struct param_row paramVU = {0,0,0}; + int rc; + char * zVal = 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( paramVU.hits==1 && paramVU.uses==uses){ + /* Editing an existing value of same kind. */ + sqlite3_free(paramVU.value); + if( eval!=0 ){ + zSql = sqlite3_mprintf + ("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 = sqlite3_mprintf + ("UPDATE "PARAM_TABLE_SNAME" SET value=(SELECT %s) WHERE" + " key=%Q AND uses=%d", zVal, name, uses); + }else{ + 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 || paramVU.uses!=uses); + if( eval!=0 ){ + zSql = sqlite3_mprintf + ("SELECT edit('-- %q%s', %Q)", name, "\n", zEditor); + zVal = db_text(db, zSql, 1); + sqlite3_free(zSql); + zSql = sqlite3_mprintf + ("INSERT INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES (%Q,(SELECT %s LIMIT 1),%d)", + name, zVal, uses); + }else{ + 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); + if( eval!=0 ){ + } + rc = sqlite3_exec(db, zSql, 0, 0, 0); + sqlite3_free(zSql); + sqlite3_free(zVal); + return rc!=SQLITE_OK; +} +#endif + +/* Space-join values in an argument list. *valLim is not included. */ +char *values_join( char **valBeg, char **valLim ){ + char *z = 0; + const char *zSep = 0; + while( valBeg < valLim ){ + z = sqlite3_mprintf("%z%s%s", z, zSep, *valBeg); + 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 = sqlite3_mprintf + ( "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; +} param_set_opts[] = { + /* { 'q', 0, 2 }, */ + /* { 'x', 0, 1 }, */ + { 'i', "INT", 1 }, + { 'r', "REAL", 1 }, + { 'b', "BLOB", 1 }, + { 't', "TEXT", 0 }, + { 'n', "NUMERIC", 1 } +}; + +/* Return an option character if it is single and prefixed by - or --, + * else return 0. + */ +static char option_char(char *zArg){ + if( zArg[0]=='-' ){ + ++zArg; + if( zArg[0]=='-' ) ++zArg; + if( zArg[0]!=0 && zArg[1]==0 ) return zArg[0]; + } + return 0; +} + +static int param_set(sqlite3 *db, char cCast, + 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; + char *zValue = (zValGlom==0)? *valBeg : zValGlom; + if( cCast ){ + struct ParamSetOpts *pSO = param_set_opts; + for(; pSO-param_set_opts < ArraySize(param_set_opts); ++pSO ){ + if( cCast==pSO->cCast ){ + zCastTo = pSO->zTypename; + needsEval = pSO->evalKind > 0; + break; + } + } + } + if( needsEval ){ + if( zCastTo!=0 ){ + zSql = sqlite3_mprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + " VALUES(%Q,CAST((%s) AS %s),%d);", name, zValue, zCastTo, ptu ); + }else{ + zSql = sqlite3_mprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "VALUES(%Q,(%s),%d);", name, zValue, ptu ); + } + shell_check_oom(zSql); + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmtSet, 0); + sqlite3_free(zSql); + } + if( !needsEval || rc!=SQLITE_OK ){ + /* Reach here when value either requested to be cast to text, or must be. */ + sqlite3_finalize(pStmtSet); + pStmtSet = 0; + zSql = sqlite3_mprintf + ( "REPLACE INTO "PARAM_TABLE_SNAME"(key,value,uses)" + "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); + } + sqlite3_step(pStmtSet); + sqlite3_finalize(pStmtSet); + sqlite3_free(zValGlom); + return rc; +} + - /* list subcommand for .parameter dot-command */ - static void list_params(ShellState *p, ParamTableUse ptu){ ++/* list or ls subcommand for .parameter dot-command */ ++static void list_params(ShellState *p, ParamTableUse ptu, u8 bShort, ++ char **pzArgs, int nArg){ + sqlite3_stmt *pStmt = 0; - int len = 0; - int rc = sqlite3_prepare_v2 - (p->db, "SELECT max(length(key)) FROM " - PARAM_TABLE_SNAME" WHERE ?1=3 OR uses=?1", -1, &pStmt, 0); ++ sqlite3_str *sbList = sqlite3_str_new(p->db); ++ int len = 0, rc; ++ char *zFromWhere = 0; ++ char *zSql = 0; ++ 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 = sqlite3_mprintf("SELECT max(length(key)) %s", zFromWhere); ++ shell_check_oom(zSql); ++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int(pStmt, 1, ptu); + if( sqlite3_step(pStmt)==SQLITE_ROW ){ + len = sqlite3_column_int(pStmt, 0); + if( len>40 ) len = 40; + if( len<4 ) len = 4; + } + } + sqlite3_finalize(pStmt); + pStmt = 0; + if( len ){ - utf8_printf(p->out, "%-*s %-8s %s\n", len, "name", "usage", "value"); - rc = sqlite3_prepare_v2 - (p->db, "SELECT key, uses, quote(value) FROM " PARAM_TABLE_SNAME - " WHERE ?1=3 OR uses=?1 ORDER BY key", -1, &pStmt, 0); - sqlite3_bind_int(pStmt, 1, ptu); - while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ - ParamTableUse ptux = sqlite3_column_int(pStmt,1); - const char *zUse; - switch( ptux ){ - case PTU_Binding: zUse = "binding"; break; - case PTU_Script: zUse = "script"; break; - default: zUse = "unknown"; ++ sqlite3_free(zSql); ++ if( !bShort ){ ++ int nBindings = 0, nScripts = 0; ++ zSql = sqlite3_mprintf("SELECT key, uses, iif(uses, value, quote(value))" ++ " %z ORDER BY uses, key", zFromWhere); ++ shell_check_oom(zSql); ++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); ++ sqlite3_bind_int(pStmt, 1, ptu); ++ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ ++ ParamTableUse ptux = sqlite3_column_int(pStmt,1); ++ switch( ptux ){ ++ case PTU_Binding: ++ if( nBindings++ == 0 ){ ++ utf8_printf(p->out, "Binding Values:\n%-*s %s\n", ++ len, "name", "value"); ++ } ++ utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), ++ sqlite3_column_text(pStmt,2)); ++ break; ++ case PTU_Script: ++ if( nScripts++ == 0 ){ ++ utf8_printf(p->out, "Scripts\n%-*s %s\n", len, "name", "value"); ++ } ++ utf8_printf(p->out, "%-*s %s\n", len, sqlite3_column_text(pStmt,0), ++ sqlite3_column_text(pStmt,2)); ++ break; ++ default: break; /* Ignore */ ++ } + } - utf8_printf(p->out, "%-*s %-8s %s\n", len, sqlite3_column_text(pStmt,0), - zUse, sqlite3_column_text(pStmt,2)); ++ }else{ ++ int nc = 0, ncw = 78/(len+2); ++ zSql = sqlite3_mprintf("SELECT key %z ORDER BY key", zFromWhere); ++ shell_check_oom(zSql); ++ rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); ++ sqlite3_bind_int(pStmt, 1, ptu); ++ while( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ ++ utf8_printf(p->out, "%s %-*s", ((++nc%ncw==0)? "\n" : ""), ++ len, sqlite3_column_text(pStmt,0)); ++ } ++ if( nc>0 ) utf8_printf(p->out, "\n"); + } + sqlite3_finalize(pStmt); ++ }else{ ++ sqlite3_free(zFromWhere); ++ } ++ sqlite3_free(zSql); ++} ++ ++/* Append an OR'ed series of GLOB terms comparing a given column ++ * name to a series of patterns. Result is an appended expression. ++ * For an empty pattern series, expression is true for non-NULL. ++ */ ++static void append_glob_terms(sqlite3_str *pStr, const char *zColName, ++ const char **azBeg, const char **azLim){ ++ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "%s NOT NULL", zColName); ++ else{ ++ char *zSep = "("; ++ while( azBeg Maybe init db, add column zCol to it. * zAutoColumn(0, &db, ?) => (db!=0) Form columns spec for CREATE TABLE, * close db and set it to 0, and return the columns spec, to later @@@ -10705,30 -9735,7 +10765,32 @@@ static int do_meta_command(char *zLine ** Create it if necessary. */ if( nArg==2 && strcmp(azArg[1],"init")==0 ){ - bind_table_init(p); + param_table_init(p); + }else + - /* .parameter list - ** List all bind parameters. ++ /* .parameter list|ls ++ ** List all or selected bind parameters. ++ ** list displays names, values and uses. ++ ** ls displays just the names. + */ - if( nArg==2 && strcmp(azArg[1],"list")==0 ){ - /* Future: Allow selection of categories. */ - list_params(p, PTU_Nil); ++ if( nArg>=2 && ((strcmp(azArg[1],"list")==0) ++ || (strcmp(azArg[1],"ls")==0)) ){ ++ 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(p); + rc = parameters_load(p->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 = parameters_save(p->db, (const char **)azArg+1, nArg-1); }else /* .parameter set NAME VALUE diff --cc test/shell9.test index d5df53d586,0000000000..6113ce748a mode 100644,000000..100644 --- a/test/shell9.test +++ b/test/shell9.test @@@ -1,253 -1,0 +1,260 @@@ +# 2022 Feb 5 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# The focus of this file is testing the CLI shell tool enhanced parsing, +# new .parameter subcommands and uses, and the new .x meta-command. +# +# + +# Test plan: +# +# shell9-1.*: command line parsing and acting accordingly +# shell9-2.*: Basic "dot" command, cross-line token parsing +# shell9-3.*: .parameter set options and types +# shell9-4.*: .parameter save/load operation +# shell9-5.*: Ensure "dot" commands and SQL intermix ok. +# shell9-6.*: .x command operation and refusal +# +set testdir [file dirname $argv0] +source $testdir/tester.tcl +set CLI [test_find_cli] +db close +forcedelete test.db test.db-journal test.db-wal +forcedelete x.db xn.db + +sqlite3 db test.db + +#---------------------------------------------------------------------------- +# Test cases shell9-1.*: command line parsing and acting accordingly + +do_test shell9-1.1 { + set res [catchcmd ":memory: -cmd .quit" ""] +} {0 {}} + +do_test shell9-1.2 { + set res [catchcmd ":memory: -shxopts 1 -cmd .shxopts -cmd .quit" ""] +} {0 { name value "-shxopts set" + -------- ----- --------------- + parsing 1 "-shxopts 0x01" + all_opts 0 "-shxopts 0x07"}} + +do_test shell9-1.3 { + set res [catchcmd ":memory: -cmd .shxopts -cmd .quit" ""] +} {0 { name value "-shxopts set" + -------- ----- --------------- + parsing 0 "-shxopts 0x01" + all_opts 0 "-shxopts 0x07"}} + +#---------------------------------------------------------------------------- +# Test cases shell9-2.*: Basic "dot" command, cross-line token parsing + +set cmds ".print 'l1\nl2'\n.print 'a\\\nb'" +do_test shell9-2.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {l1 +l2 +ab}} + +set cmds " .print \"l1\nl2\"\n .print \"a\\\nb\" \n# c\n ## c" +do_test shell9-2.2 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {l1 +l2 +ab}} + +set cmds ".echo on\n.seeargs 'a'\\\n'b'\n#!" +do_test shell9-2.3 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.seeargs 'a''b' +a|b|}} + +set cmds ".echo on\n.seeargs a\\\nb\n#!" +do_test shell9-2.4 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.seeargs ab +ab|}} + +set cmds ".echo 1\n.print \"\\\"\nq\\\"\"" +do_test shell9-2.5 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {.print "\" +q\"" +" +q"}} + +#---------------------------------------------------------------------------- +# Test cases shell9-3.*: .parameter set options and types + +set cmds { +.pa set -b b x'a5a5' +.pa set -i ii 33-11 +.pa set -i ir 3.3-1.1 +.pa set -n ni 3-1 +.pa set -n nr 3.3-1.1 +.pa set -r ri 1 +.pa set -r rr 1.2 +.pa set -t t 123 +.mode list +select typeof(value) from temp.sqlite_parameters order by key; +} +do_test shell9-3.1 { + set res [catchcmd ":memory:" $cmds] +} {0 {blob +integer +integer +integer +real +real +real +text}} + +set cmds { +.pa set expr 1 + 2 * 3 +.pa set text "'1 + 2*3'" +.pa set -t ttext 1 + 2*3 +.pa list +} +do_test shell9-3.2 { + set res [catchcmd ":memory:" $cmds] - } {0 {name usage value - expr script 7 - text script '1 + 2*3' - ttext script '1 + 2*3'}} ++} {0 {Scripts ++name value ++expr 7 ++text 1 + 2*3 ++ttext 1 + 2*3}} + +set cmds { +.pa set a "'a'" +.pa set b "'b'" +.pa set c "'c'" +.pa unset +.pa list +.pa clear a +.pa list +.pa unset b +.pa list +.pa clear +.pa list +.pa set d "'e'" +.pa set e "'e'" +.pa unset d e +.pa list +} +do_test shell9-3.3 { + set res [catchcmd ":memory:" $cmds] - } {0 {name usage value - a script 'a' - b script 'b' - c script 'c' - name usage value - b script 'b' - c script 'c' - name usage value - c script 'c' - name usage value - name usage value}} ++} {0 {Scripts ++name value ++a a ++b b ++c c ++Scripts ++name value ++b b ++c c ++Scripts ++name value ++c c}} + +if {$::tcl_platform(platform)=="unix"} { + proc set_ed {sayWhat} { + global env + set env(VISUAL) "echo SELECT $sayWhat ';' >" + return 1 + } +} elseif {$::tcl_platform(platform)=="windows"} { + proc set_ed {sayWhat} { + global env + set env(VISUAL) "echo SELECT $sayWhat ; >" + return 1 + } +} else { return 0 } + +if {[set_ed @name]} { + set cmds { +.pa set @name Fido +.pa edit -t dog +.x dog + } + do_test shell9-3.4 { + set res [catchcmd ":memory: -quiet 1 -shxopts 1 -interactive" $cmds] + } {0 {.pa set @name Fido +.pa edit -t dog +.x dog +Fido + }} +} + +#---------------------------------------------------------------------------- +# Test cases shell9-4.*: .parameter save/load operation + +set cmds { + .pa set -t x '.print Ex' + .pa set -i $n 7 + .pa save xn.db + .pa save x.db x + .pa clear + .pa load xn.db + .pa list + .pa clear + .pa load x.db + .pa list + .pa clear + .pa load xn.db $n + .pa list +} +do_test shell9-4.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] - } {0 {name usage value - $n binding 7 - x script '.print Ex' - name usage value - x script '.print Ex' - name usage value - $n binding 7}} ++} {0 {Binding Values: ++name value ++$n 7 ++Scripts ++name value ++x .print Ex ++Scripts ++name value ++x .print Ex ++Binding Values: ++name value ++$n 7}} + +forcedelete x.db xn.db + +#---------------------------------------------------------------------------- +# Test cases shell9-5.*: Ensure "dot" commands and SQL intermix ok. + +set cmds { + .pa set -t mixed " + .print Hi. + select 'Hi.'; + .print 'Good\ + Bye.' + select 'Good'|| + ' Bye.'; + " + .x mixed +} +do_test shell9-5.1 { + set res [catchcmd ":memory: -shxopts 1" $cmds] +} {0 {Hi. +Hi. +Good Bye. +Good Bye.}} + +#---------------------------------------------------------------------------- +# Test cases shell9-6.*: .x command operation and refusal +set cmds { + .pa set -t $v '.print Ok' + .x $v +} +do_test shell9-6.1 { + set res [catchcmd ":memory: -bail -shxopts 1" $cmds] +} {1 {Skipping badly named $v. Run ".help x"}} + +finish_test