" --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",
}
#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */
- /* list subcommand for .parameter dot-command */
- static void list_params(ShellState *p, ParamTableUse ptu){
+/*
+** 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<<SHEXT_DYNCMDS_BIT},
+#endif
+#if SHELL_EXTENDED_PARSING
+ {"parsing", 1<<SHEXT_PARSING_BIT},
+#endif
+#if SHELL_VARIABLE_EXPANSION
+ {"dot_vars", 1<<SHEXT_VAREXP_BIT},
+#endif
+ {"all_opts", SHELL_ALL_EXTENSIONS}
+ };
+ const char *zMoan = 0, *zAbout = 0;
+ int ia, io;
+ if( nArg>1 ){
+ for( ia=1; ia<nArg; ++ia ){
+ char cs = azArg[ia][0];
+ if( cs!='+' && cs!='-' ){
+ zMoan = "arguments must have a sign prefix.";
+ zAbout = azArg[0];
+ goto moan_error;
+ }
+ for( io=0; io<ArraySize(shopts); ++io ){
+ if( strcmp(azArg[ia]+1, shopts[io].name)==0 ){
+ if( cs=='+' ) p->bExtendedDotCmds |= 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; io<ArraySize(shopts); ++io ){
+ unsigned m = shopts[io].mask;
+ unsigned v = ((p->bExtendedDotCmds & 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;
+}
+
- 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);
++/* 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;
- 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_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, sqlite3_column_text(pStmt,0),
- zUse, sqlite3_column_text(pStmt,2));
++ 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 */
++ }
+ }
++ }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<azLim ){
++ sqlite3_str_appendf(pStr, "%sglob(%Q,%s)", zSep, *azBeg, zColName);
++ zSep = " OR ";
++ ++azBeg;
++ }
++ sqlite3_str_appendf(pStr, ")");
+ }
+}
+
+/* Append either an IN clause or an always true test to some SQL.
+ *
+ * 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 dostuff ?NAMES?" options,
+ * where a missing list means all the qualifying entries.
+ *
+ * The empty list may be signified by azBeg and azLim both 0.
+ */
+static void append_in_clause(sqlite3_str *pStr,
+ const char **azBeg, const char **azLim){
+ if( azBeg==azLim ) sqlite3_str_appendf(pStr, "NOT NULL");
+ else{
+ char cSep = '(';
+ sqlite3_str_appendf(pStr, "IN");
+ while( azBeg<azLim ){
+ sqlite3_str_appendf(pStr, "%c%Q", cSep, *azBeg);
+ cSep = ',';
+ ++azBeg;
+ }
+ sqlite3_str_appendf(pStr, ")");
+ }
+}
+
-/*
+/*
* zAutoColumn(zCol, &db, ?) => 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
** 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