blob: a7ce8bec435d295b1944015df62230aeb1d757fa [file] [log] [blame] [edit]
/*
** 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.
**
** This is used to do parameter binding in the command-line shell.
**
** The ".set key=value" command and the "-Dkey=value" command-line option
** invoke shell_bindings_new_text() on the argument ("key=value") in order
** to create entries in the store. The CLI then invokes
** shell_bindings_apply() on each prepared statement just prior to
** running it.
**
** All bindings are accessible through an eponymous-only virtual table
** named shell_bindings. Ex:
**
** INSERT INTO shell_bindings(k,v) VALUES('p1',12345);
** SELECT $p1, typeof($p1);
**
** The above results in an answer of 12345,'integer'. Bindings generated
** using the virtual table can have any type other than NULL. But bindings
** generated by the .set command and the -D command-line option are always
** text.
**
** The CLI is single-threaded, so there is no attempt to make this code
** threadsafe.
**
** The key/value store is kept in a global list, and uses malloc/free rather
** than sqlite3_malloc64/sqlite3_free so that it can be completely independent
** of SQLite, can exist both before sqlite3_initialize() and after
** sqlite3_shutdown(), and so that it will persist across multiple
** connections created using ".open".
**
** The number of parameters is expected to be small, so they are stored
** on a simple linked list. If this proves to be too inefficient, some other
** algorithm can be substituted in the future without changing the interface.
*/
#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;
}