mirror of
https://github.com/sqlite/sqlite.git
synced 2026-10-26 21:59:27 +03:00
version of the .set command. All bindings from .set and -D are still string. FossilOrigin-Name: 7b2a65a65475b80669af4ff5ac7adb6b310cd8275a36e672a3f5c88a259693c0
477 lines
13 KiB
C
477 lines
13 KiB
C
/*
|
|
** 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;
|
|
}
|