--- /dev/null
+/*
+** 2018-04-27
+**
+** 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.
+**
+*************************************************************************
+**
+** This file implements a simple key/value store used to hold bind
+** parameters for SQLite. The key/value store is a singleton - there
+** is exactly one per process. The store can be accessed and controlled
+** from SQLite using an eponymous virtual table.
+*/
+#if !defined(SQLITEINT_H)
+#include "sqlite3ext.h"
+#endif
+SQLITE_EXTENSION_INIT1
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+/* Each entry in the key/value store */
+typedef struct BindingEntry BindingEntry;
+struct BindingEntry {
+ char *zKey; /* Key */
+ BindingEntry *pNext; /* Next entry in the list */
+ BindingEntry *pPrev; /* Previous entry in the list */
+ int eType; /* SQLITE_INTEGER, _FLOAT, _TEXT, or _BLOB */
+ int len; /* Length for SQLITE_BLOB values */
+ union {
+ sqlite3_int64 i; /* Integer value */
+ double r; /* Real value */
+ char *z; /* Text value */
+ unsigned char *b; /* Blob value */
+ } u;
+};
+
+/* Global list of all entries */
+static BindingEntry *global_pAll = 0;
+
+/* Locate any entry with the given key. Return NULL if not found.
+*/
+static BindingEntry *shellBindingFind(const char *zKey){
+ BindingEntry *p;
+ for(p=global_pAll; p && strcmp(p->zKey,zKey)!=0; p = p->pNext){}
+ return p;
+}
+
+/* Delete any entry with the given key, if it exists.
+*/
+static void shellBindingDelete(const char *zKey){
+ BindingEntry *p;
+ p = shellBindingFind(zKey);
+ if( p ){
+ if( p->pNext ){
+ p->pNext->pPrev = p->pPrev;
+ }
+ if( p->pPrev ){
+ p->pPrev->pNext = p->pNext;
+ }else{
+ global_pAll = p->pNext;
+ }
+ free(p);
+ }
+}
+
+/* Insert a new shell binding */
+static void shellBindingInsert(BindingEntry *p){
+ p->pNext = global_pAll;
+ if( global_pAll ) global_pAll->pPrev = p;
+ global_pAll = p;
+ p->pPrev = 0;
+}
+
+/*
+** True if c is a valid ID character.
+*/
+static int shellBindIdChar(char c){
+ if( c>='a' && c<='z' ) return 1;
+ if( c>='A' && c<='Z' ) return 1;
+ if( c=='_' ) return 1;
+ if( c>='0' && c<='9' ) return 2;
+ return 0;
+}
+
+/* Create a new binding given a string of the form "KEY=VALUE". Return
+** values:
+**
+** 0: success
+** 1: out of memory
+** 2: Argument is not a valid KEY=VALUE string
+**
+** The type of VALUE is TEXT.
+*/
+int shell_bindings_new_text(const char *z){
+ int i;
+ int nKey;
+ int nData;
+ BindingEntry *p;
+ for(i=0; shellBindIdChar(z[i]); i++){}
+ if( i==0 ) return 2;
+ if( shellBindIdChar(z[0])==2 ) return 2;
+ nKey = i;
+ if( z[i]!='=' ) return 2;
+ for(nData=0; z[nKey+1+nData]; nData++){}
+ p = malloc( sizeof(*p) + nKey + nData + 2 );
+ if( p==0 ) return 1;
+ memset(p, 0, sizeof(*p));
+ p->zKey = (char*)&p[1];
+ memcpy(p->zKey, z, nKey);
+ p->zKey[nKey] = 0;
+ p->u.z = &p->zKey[nKey+1];
+ p->len = nData;
+ p->eType = SQLITE_TEXT;
+ memcpy(p->u.z, &z[nKey+1], nData+1);
+ shellBindingDelete(p->zKey);
+ shellBindingInsert(p);
+ return 0;
+}
+
+/*
+** Delete all shell bindings
+*/
+void shell_bindings_clear(void){
+ BindingEntry *pNext;
+ while( global_pAll ){
+ pNext = global_pAll->pNext;
+ free(global_pAll);
+ global_pAll = pNext;
+ }
+}
+
+/* Given a prepared statement, apply all bindings for which there are
+** known values in the k-v store
+*/
+void shell_bindings_apply(sqlite3_stmt *pStmt){
+ int n = sqlite3_bind_parameter_count(pStmt);
+ int i;
+ BindingEntry *p;
+ for(i=1; i<=n; i++){
+ const char *zKey = sqlite3_bind_parameter_name(pStmt, i);
+ if( zKey==0 || zKey[0]==0 ) continue;
+ zKey++;
+ p = shellBindingFind(zKey);
+ if( p==0 ) continue;
+ switch( p->eType ){
+ case SQLITE_INTEGER:
+ sqlite3_bind_int64(pStmt, i, p->u.i);
+ break;
+ case SQLITE_FLOAT:
+ sqlite3_bind_double(pStmt, i, p->u.r);
+ break;
+ case SQLITE_TEXT:
+ sqlite3_bind_text(pStmt, i, p->u.z, p->len, SQLITE_TRANSIENT);
+ break;
+ case SQLITE_BLOB:
+ sqlite3_bind_blob(pStmt, i, p->u.b, p->len, SQLITE_TRANSIENT);
+ break;
+ }
+ }
+}
+
+/* bindvtab_vtab is a subclass of sqlite3_vtab which is
+** underlying representation of the virtual table
+*/
+typedef struct bindvtab_vtab bindvtab_vtab;
+struct bindvtab_vtab {
+ sqlite3_vtab base; /* Base class - must be first */
+};
+
+/* bindvtab_cursor is a subclass of sqlite3_vtab_cursor which will
+** serve as the underlying representation of a cursor that scans
+** over rows of the result
+*/
+typedef struct bindvtab_cursor bindvtab_cursor;
+struct bindvtab_cursor {
+ sqlite3_vtab_cursor base; /* Base class - must be first */
+ BindingEntry *p; /* Current entry in the scan */
+};
+
+/*
+** The bindvtabConnect() method is invoked to create a new
+** template virtual table.
+**
+** Think of this routine as the constructor for bindvtab_vtab objects.
+**
+** All this routine needs to do is:
+**
+** (1) Allocate the bindvtab_vtab object and initialize all fields.
+**
+** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
+** result set of queries against the virtual table will look like.
+*/
+static int bindvtabConnect(
+ sqlite3 *db,
+ void *pAux,
+ int argc, const char *const*argv,
+ sqlite3_vtab **ppVtab,
+ char **pzErr
+){
+ bindvtab_vtab *pNew;
+ int rc;
+
+ rc = sqlite3_declare_vtab(db,
+ "CREATE TABLE shell_bindings(k TEXT PRIMARY KEY,v)"
+ " WITHOUT ROWID"
+ );
+ /* For convenience, define symbolic names for the index to each column. */
+#define BINDVTAB_KEY 0
+#define BINDVTAB_VALUE 1
+ if( rc==SQLITE_OK ){
+ pNew = sqlite3_malloc( sizeof(*pNew) );
+ *ppVtab = (sqlite3_vtab*)pNew;
+ if( pNew==0 ) return SQLITE_NOMEM;
+ memset(pNew, 0, sizeof(*pNew));
+ }
+ return rc;
+}
+
+/*
+** This method is the destructor for bindvtab_vtab objects.
+*/
+static int bindvtabDisconnect(sqlite3_vtab *pVtab){
+ bindvtab_vtab *p = (bindvtab_vtab*)pVtab;
+ sqlite3_free(p);
+ return SQLITE_OK;
+}
+
+/*
+** Constructor for a new bindvtab_cursor object.
+*/
+static int bindvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
+ bindvtab_cursor *pCur;
+ pCur = sqlite3_malloc( sizeof(*pCur) );
+ if( pCur==0 ) return SQLITE_NOMEM;
+ memset(pCur, 0, sizeof(*pCur));
+ *ppCursor = &pCur->base;
+ return SQLITE_OK;
+}
+
+/*
+** Destructor for a bindvtab_cursor.
+*/
+static int bindvtabClose(sqlite3_vtab_cursor *cur){
+ bindvtab_cursor *pCur = (bindvtab_cursor*)cur;
+ sqlite3_free(pCur);
+ return SQLITE_OK;
+}
+
+
+/*
+** Advance a bindvtab_cursor to its next row of output.
+*/
+static int bindvtabNext(sqlite3_vtab_cursor *cur){
+ bindvtab_cursor *pCur = (bindvtab_cursor*)cur;
+ pCur->p = pCur->p->pNext;
+ return SQLITE_OK;
+}
+
+/*
+** Return values of columns for the row at which the bindvtab_cursor
+** is currently pointing.
+*/
+static int bindvtabColumn(
+ sqlite3_vtab_cursor *cur, /* The cursor */
+ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
+ int i /* Which column to return */
+){
+ bindvtab_cursor *pCur = (bindvtab_cursor*)cur;
+ BindingEntry *p = pCur->p;
+ if( i==BINDVTAB_KEY ){
+ sqlite3_result_text(ctx, p->zKey, -1, SQLITE_TRANSIENT);
+ }else{
+ assert( i==BINDVTAB_VALUE );
+ switch( p->eType ){
+ case SQLITE_INTEGER:
+ sqlite3_result_int(ctx, p->u.i);
+ break;
+ case SQLITE_FLOAT:
+ sqlite3_result_double(ctx, p->u.r);
+ break;
+ case SQLITE_TEXT:
+ sqlite3_result_text(ctx, p->u.z, p->len, SQLITE_TRANSIENT);
+ break;
+ case SQLITE_BLOB:
+ sqlite3_result_blob(ctx, p->u.b, p->len, SQLITE_TRANSIENT);
+ break;
+ }
+ }
+ return SQLITE_OK;
+}
+
+/*
+** Return the rowid for the current row. In this implementation, the
+** rowid is the same as the output value.
+*/
+static int bindvtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
+ return SQLITE_OK;
+}
+
+/*
+** Return TRUE if the cursor has been moved off of the last
+** row of output.
+*/
+static int bindvtabEof(sqlite3_vtab_cursor *cur){
+ bindvtab_cursor *pCur = (bindvtab_cursor*)cur;
+ return pCur->p==0;
+}
+
+/*
+** This method is called to "rewind" the bindvtab_cursor object back
+** to the first row of output. This method is always called at least
+** once prior to any call to bindvtabColumn() or bindvtabRowid() or
+** bindvtabEof().
+*/
+static int bindvtabFilter(
+ sqlite3_vtab_cursor *pVtabCursor,
+ int idxNum, const char *idxStr,
+ int argc, sqlite3_value **argv
+){
+ bindvtab_cursor *pCur = (bindvtab_cursor *)pVtabCursor;
+ pCur->p = global_pAll;
+ return SQLITE_OK;
+}
+
+/*
+** SQLite will invoke this method one or more times while planning a query
+** that uses the virtual table. This routine needs to create
+** a query plan for each invocation and compute an estimated cost for that
+** plan.
+*/
+static int bindvtabBestIndex(
+ sqlite3_vtab *tab,
+ sqlite3_index_info *pIdxInfo
+){
+ pIdxInfo->estimatedCost = (double)10;
+ pIdxInfo->estimatedRows = 10;
+ return SQLITE_OK;
+}
+
+/*
+** Called to make changes to the shell bindings
+*/
+static int bindvtabUpdate(
+ sqlite3_vtab *pVTab,
+ int argc,
+ sqlite3_value **argv,
+ sqlite_int64 *pRowid
+){
+ const char *zKey;
+ BindingEntry *p;
+ int nKey;
+ int len;
+ int eType;
+ if( sqlite3_value_type(argv[0])!=SQLITE_NULL ){
+ zKey = (const char*)sqlite3_value_text(argv[0]);
+ if( zKey ) shellBindingDelete(zKey);
+ }
+ if( argc==1 ) return SQLITE_OK;
+ eType = sqlite3_value_type(argv[3]);
+ if( eType==SQLITE_NULL ) return SQLITE_OK;
+ zKey = (const char*)sqlite3_value_text(argv[2]);
+ if( zKey==0 ) return SQLITE_OK;
+ nKey = sqlite3_value_bytes(argv[2]);
+ shellBindingDelete(zKey);
+ if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){
+ len = sqlite3_value_bytes(argv[3]);
+ }else{
+ len = 0;
+ }
+ p = malloc( sizeof(*p) + nKey + len + 2 );
+ if( p==0 ) return SQLITE_NOMEM;
+ memset(p, 0, sizeof(*p));
+ p->zKey = (char*)&p[1];
+ memcpy(p->zKey, zKey, nKey+1);
+ p->eType = eType;
+ switch( eType ){
+ case SQLITE_INTEGER:
+ p->u.i = sqlite3_value_int64(argv[3]);
+ break;
+ case SQLITE_FLOAT:
+ p->u.r = sqlite3_value_double(argv[3]);
+ break;
+ case SQLITE_TEXT:
+ p->u.z = &p->zKey[nKey+1];
+ memcpy(p->u.z, sqlite3_value_text(argv[3]), len);
+ break;
+ case SQLITE_BLOB:
+ p->u.b = (unsigned char*)&p->zKey[nKey+1];
+ memcpy(p->u.b, sqlite3_value_blob(argv[3]), len);
+ break;
+ }
+ shellBindingInsert(p);
+ return SQLITE_OK;
+}
+
+/*
+** This following structure defines all the methods for the
+** virtual table.
+*/
+static sqlite3_module bindvtabModule = {
+ /* iVersion */ 0,
+ /* xCreate */ 0,
+ /* xConnect */ bindvtabConnect,
+ /* xBestIndex */ bindvtabBestIndex,
+ /* xDisconnect */ bindvtabDisconnect,
+ /* xDestroy */ 0,
+ /* xOpen */ bindvtabOpen,
+ /* xClose */ bindvtabClose,
+ /* xFilter */ bindvtabFilter,
+ /* xNext */ bindvtabNext,
+ /* xEof */ bindvtabEof,
+ /* xColumn */ bindvtabColumn,
+ /* xRowid */ bindvtabRowid,
+ /* xUpdate */ bindvtabUpdate,
+ /* xBegin */ 0,
+ /* xSync */ 0,
+ /* xCommit */ 0,
+ /* xRollback */ 0,
+ /* xFindMethod */ 0,
+ /* xRename */ 0,
+ /* xSavepoint */ 0,
+ /* xRelease */ 0,
+ /* xRollbackTo */ 0
+};
+
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_bindvtab_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+ rc = sqlite3_create_module(db, "shell_bindings", &bindvtabModule, 0);
+ return rc;
+}
-C Enhance\sthe\scomments\sin\sthe\stemplatevtab.c\simplementation.
-D 2018-04-27T15:17:08.924
+C Add\sthe\sability\sto\suse\sbind\sparameters\sin\sthe\sCLI.\s\sThe\snew\s".set\sKEY=VALUE"\ndot-command\sworks\sto\sset\sbindings.\s\sOr\suse\sthe\s"-Dkey=value"\scommand-line\noption.\s\sOr\suse\sthe\sbuilt-in\sshell_bindings(k,v)\svirtual\stable\sto\sset,\ndelete,\sor\schanging\sbindings.
+D 2018-04-27T17:39:22.740
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
-F Makefile.in 5ce9343cba9c189046f1afe6d2bcc1f68079439febc05267b98aec6ecc752439
+F Makefile.in da02d4d3544992af522b4ece652debef3255a5d45377ebb1b5ab8a9a5cd16859
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
-F Makefile.msc 59179295f6a9b433e3f59a6dc2fcf6db6fcac35d92015294beb5d27f2924ebb9
+F Makefile.msc a885344e4f1277b2dce6e9cfb69bc6eceb9494203fbc08b4b7a106268a1a7808
F README.md 7764d56778d567913ef11c82da9ab94aefa0826f7c243351e4e2d7adaef6f373
F VERSION b7c9d1d11cb70ef8e90cfcf3c944aa58a9f801cc2ad487eebb0a110c16dfc2df
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
F ext/misc/amatch.c 6db4607cb17c54b853a2d7c7c36046d004853f65b9b733e6f019d543d5dfae87
F ext/misc/anycollseq.c 5ffdfde9829eeac52219136ad6aa7cd9a4edb3b15f4f2532de52f4a22525eddb
F ext/misc/appendvfs.c 3777f22ec1057dc4e5fd89f2fbddcc7a29fbeef1ad038c736c54411bb1967af7
+F ext/misc/bindvtab.c e21871461a0bb4646da111a7f32f709946f76a4013d523dc183192f0879c0cb3
F ext/misc/btreeinfo.c 78c8c57d325185ccc04b7679e5b020e34a4d9c87453e6b7ac943d0a26cee3256
F ext/misc/carray.c ed96c218ea940b85c9a274c4d9c59fe9491c299147a38a8bba537687bd6c6005
F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 068618fe288bf9d93091b808be73e26a93fb2db8435328cc5760f9dd35ba168b
+F main.mk fb46a40f6573a36b1a97901eefafb287aa31e494e8737e772cc0722580025607
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
F src/resolve.c 6415381a0e9d22c0e7cba33ca4a53f81474190862f5d4838190f5eb5b0b47bc9
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
F src/select.c daf07d8defce3311f9e69f1280a874d78bc1d16c305f6aa689640f7afa02842f
-F src/shell.c.in d63f06c870ec1761ea98bd1cae651ff0ea6beadf8be892105dabd913f94cb3da
+F src/shell.c.in 3bac9ab3c0ec1b8641b9ed25c0e2cf242b8bb186202d577b56fb16408dcacf24
F src/sqlite.h.in 8e70752a57597c08f64f3d49fc1fc46926b862d2e23b038b0d23b9cc748d88ea
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 83a3c4ce93d650bedfd1aa558cb85a516bd6d094445ee989740827d0d944368d
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 9fd0faf517993587d2f54212638545fc85fbbc84a031bcfae8c1e5894825d83b
-R 408ce2695c1a501e1640fcd4bee57806
+P 05f6278a02e5cde89f76ced5af7d508e26576d7291dad7ee9e06b1a3be516cb0
+R 2fe85036f0f3b01feae669c6ee5a9387
+T *branch * shell-bindings
+T *sym-shell-bindings *
+T -sym-trunk *
U drh
-Z 4384bff1ecbee30802e6ddd37b479122
+Z 275e393030ab15b790049a2a3b9245cb
INCLUDE ../ext/misc/fileio.c
INCLUDE ../ext/misc/completion.c
INCLUDE ../ext/misc/appendvfs.c
+INCLUDE ../ext/misc/bindvtab.c
#ifdef SQLITE_HAVE_ZLIB
INCLUDE ../ext/misc/zipfile.c
INCLUDE ../ext/misc/sqlar.c
}
}
+ shell_bindings_apply(pStmt);
exec_prepared_stmt(pArg, pStmt);
explain_data_delete(pArg);
eqp_render(pArg);
#if defined(SQLITE_ENABLE_SESSION)
".session CMD ... Create or control sessions\n"
#endif
+ ".set KEY=VALUE Set bind parameter KEY to be string VALUE\n"
".sha3sum ?OPTIONS...? Compute a SHA3 hash of database content\n"
#ifndef SQLITE_NOHAVE_SYSTEM
".shell CMD ARGS... Run CMD ARGS... in a system shell\n"
sqlite3_fileio_init(p->db, 0, 0);
sqlite3_shathree_init(p->db, 0, 0);
sqlite3_completion_init(p->db, 0, 0);
+ sqlite3_bindvtab_init(p->db, 0, 0);
#ifdef SQLITE_HAVE_ZLIB
sqlite3_zipfile_init(p->db, 0, 0);
sqlite3_sqlar_init(p->db, 0, 0);
}
}else
+ if( c=='s' && n==3 && strncmp(azArg[0],"set",3)==0 ){
+ int x;
+ if( nArg<2 ){
+ raw_printf(stderr, "Usage: .set KEY=VALUE\n");
+ rc = 1;
+ goto meta_command_exit;
+ }
+ x = shell_bindings_new_text(azArg[1]);
+ if( x ){
+ utf8_printf(stderr, "Error: bad setting: %s\n", azArg[1]);
+ }
+ }else
+
+
if( c=='s' && n>=4 && strncmp(azArg[0],"sha3sum",n)==0 ){
const char *zLike = 0; /* Which table to checksum. 0 means everything */
int i; /* Loop counter */
" -column set output mode to 'column'\n"
" -cmd COMMAND run \"COMMAND\" before reading stdin\n"
" -csv set output mode to 'csv'\n"
+ " -Dkey=value set shell binding variable \"key\" to \"value\"\n"
" -echo print commands before execution\n"
" -init FILENAME read/process named file\n"
" -[no]header turn headers on or off\n"
** command, so ignore them */
break;
#endif
+ }else if( strncmp(z, "-D",2)==0 ){
+ int x;
+ x = shell_bindings_new_text(z+2);
+ if( x ){
+ utf8_printf(stderr, "Error: bad binding: %s\n", z);
+ }
}
}
verify_uninitialized();
readStdin = 0;
break;
#endif
+ }else if( strncmp(z, "-D", 2)==0 ){
+ /* Noop */
}else{
utf8_printf(stderr,"%s: Error: unknown option: %s\n", Argv0, z);
raw_printf(stderr,"Use -help for a list of options.\n");
}
sqlite3_free(data.zFreeOnClose);
find_home_dir(1);
+ shell_bindings_clear();
output_reset(&data);
data.doXdgOpen = 0;
clearTempFile(&data);