faf10c521f
FossilOrigin-Name: 6cd9f55a975b5237efee8776efce7e7836b41905ca750f82be3b90aa04c1dff2
2171 lines
83 KiB
JavaScript
2171 lines
83 KiB
JavaScript
//#ifnot omit-oo1
|
|
/*
|
|
2022-07-22
|
|
|
|
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 contains the so-called OO #1 API wrapper for the sqlite3
|
|
WASM build. It requires that sqlite3-api-glue.js has already run
|
|
and it installs its deliverable as globalThis.sqlite3.oo1.
|
|
*/
|
|
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
|
const toss = (...args)=>{throw new Error(args.join(' '))};
|
|
const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)};
|
|
|
|
const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util;
|
|
/* What follows is colloquially known as "OO API #1". It is a
|
|
binding of the sqlite3 API which is designed to be run within
|
|
the same thread (main or worker) as the one in which the
|
|
sqlite3 WASM binding was initialized. This wrapper cannot use
|
|
the sqlite3 binding if, e.g., the wrapper is in the main thread
|
|
and the sqlite3 API is in a worker. */
|
|
|
|
/**
|
|
In order to keep clients from manipulating, perhaps
|
|
inadvertently, the underlying pointer values of DB and Stmt
|
|
instances, we'll gate access to them via the `pointer` property
|
|
accessor and store their real values in this map. Keys = DB/Stmt
|
|
objects, values = pointer values. This also unifies how those are
|
|
accessed, for potential use downstream via custom
|
|
wasm.xWrap() function signatures which know how to extract
|
|
it.
|
|
*/
|
|
const __ptrMap = new WeakMap();
|
|
/**
|
|
Map of DB instances to objects, each object being a map of Stmt
|
|
wasm pointers to Stmt objects.
|
|
*/
|
|
const __stmtMap = new WeakMap();
|
|
|
|
/** If object opts has _its own_ property named p then that
|
|
property's value is returned, else dflt is returned. */
|
|
const getOwnOption = (opts, p, dflt)=>{
|
|
const d = Object.getOwnPropertyDescriptor(opts,p);
|
|
return d ? d.value : dflt;
|
|
};
|
|
|
|
// Documented in DB.checkRc()
|
|
const checkSqlite3Rc = function(dbPtr, sqliteResultCode){
|
|
if(sqliteResultCode){
|
|
if(dbPtr instanceof DB) dbPtr = dbPtr.pointer;
|
|
toss3(
|
|
sqliteResultCode,
|
|
"sqlite3 result code",sqliteResultCode+":",
|
|
(dbPtr
|
|
? capi.sqlite3_errmsg(dbPtr)
|
|
: capi.sqlite3_errstr(sqliteResultCode))
|
|
);
|
|
}
|
|
return arguments[0];
|
|
};
|
|
|
|
/**
|
|
sqlite3_trace_v2() callback which gets installed by the DB ctor
|
|
if its open-flags contain "t".
|
|
*/
|
|
const __dbTraceToConsole =
|
|
wasm.installFunction('i(ippp)', function(t,c,p,x){
|
|
if(capi.SQLITE_TRACE_STMT===t){
|
|
// x == SQL, p == sqlite3_stmt*
|
|
console.log("SQL TRACE #"+(++this.counter)+' via sqlite3@'+c+':',
|
|
wasm.cstrToJs(x));
|
|
}
|
|
}.bind({counter: 0}));
|
|
|
|
/**
|
|
A map of sqlite3_vfs pointers to SQL code or a callback function
|
|
to run when the DB constructor opens a database with the given
|
|
VFS. In the latter case, the call signature is
|
|
(theDbObject,sqlite3Namespace) and the callback is expected to
|
|
throw on error.
|
|
*/
|
|
const __vfsPostOpenCallback = Object.create(null);
|
|
|
|
//#if enable-see
|
|
/**
|
|
Converts ArrayBuffer or Uint8Array ba into a string of hex
|
|
digits.
|
|
*/
|
|
const byteArrayToHex = function(ba){
|
|
if( ba instanceof ArrayBuffer ){
|
|
ba = new Uint8Array(ba);
|
|
}
|
|
const li = [];
|
|
const digits = "0123456789abcdef";
|
|
for( const d of ba ){
|
|
li.push( digits[(d & 0xf0) >> 4], digits[d & 0x0f] );
|
|
}
|
|
return li.join('');
|
|
};
|
|
|
|
/**
|
|
Internal helper to apply an SEE key to a just-opened
|
|
database. Requires that db be-a DB object which has just been
|
|
opened, opt be the options object processed by its ctor, and opt
|
|
must have either the key, hexkey, or textkey properties, either
|
|
as a string, an ArrayBuffer, or a Uint8Array.
|
|
|
|
This is a no-op in non-SEE builds. It throws on error and returns
|
|
without side effects if none of the key/textkey/hexkey options
|
|
are set. It throws if more than one is set or if any are set to
|
|
values of an invalid type.
|
|
|
|
Returns true if it applies the key, else an unspecified falsy
|
|
value. Note that applying the key does not imply that the key is
|
|
correct, only that it was passed on to the db.
|
|
*/
|
|
const dbCtorApplySEEKey = function(db,opt){
|
|
if( !capi.sqlite3_key_v2 ) return;
|
|
let keytype;
|
|
let key;
|
|
const check = (opt.key ? 1 : 0) + (opt.hexkey ? 1 : 0) + (opt.textkey ? 1 : 0);
|
|
if( !check ) return;
|
|
else if( check>1 ){
|
|
toss3(capi.SQLITE_MISUSE,
|
|
"Only ONE of (key, hexkey, textkey) may be provided.");
|
|
}
|
|
if( opt.key ){
|
|
/* It is not legal to bind an argument to PRAGMA key=?, so we
|
|
convert it to a hexkey... */
|
|
keytype = 'key';
|
|
key = opt.key;
|
|
if('string'===typeof key){
|
|
key = new TextEncoder('utf-8').encode(key);
|
|
}
|
|
if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
|
|
key = byteArrayToHex(key);
|
|
keytype = 'hexkey';
|
|
}else{
|
|
toss3(capi.SQLITE_MISUSE,
|
|
"Invalid value for the 'key' option. Expecting a string,",
|
|
"ArrayBuffer, or Uint8Array.");
|
|
return;
|
|
}
|
|
}else if( opt.textkey ){
|
|
/* For textkey we need it to be in string form, so convert it to
|
|
a string if it's a byte array... */
|
|
keytype = 'textkey';
|
|
key = opt.textkey;
|
|
if(key instanceof ArrayBuffer){
|
|
key = new Uint8Array(key);
|
|
}
|
|
if(key instanceof Uint8Array){
|
|
key = new TextDecoder('utf-8').decode(key);
|
|
}else if('string'!==typeof key){
|
|
toss3(capi.SQLITE_MISUSE,
|
|
"Invalid value for the 'textkey' option. Expecting a string,",
|
|
"ArrayBuffer, or Uint8Array.");
|
|
}
|
|
}else if( opt.hexkey ){
|
|
keytype = 'hexkey';
|
|
key = opt.hexkey;
|
|
if((key instanceof ArrayBuffer) || (key instanceof Uint8Array)){
|
|
key = byteArrayToHex(key);
|
|
}else if('string'!==typeof key){
|
|
toss3(capi.SQLITE_MISUSE,
|
|
"Invalid value for the 'hexkey' option. Expecting a string,",
|
|
"ArrayBuffer, or Uint8Array.");
|
|
}
|
|
/* else assume it's valid hex codes */
|
|
}else{
|
|
return;
|
|
}
|
|
let stmt;
|
|
try{
|
|
stmt = db.prepare("PRAGMA "+keytype+"="+util.sqlite3__wasm_qfmt_token(key, 1));
|
|
stmt.step();
|
|
return true;
|
|
}finally{
|
|
if(stmt) stmt.finalize();
|
|
}
|
|
};
|
|
//#endif enable-see
|
|
|
|
/**
|
|
A proxy for DB class constructors. It must be called with the
|
|
being-construct DB object as its "this". See the DB constructor
|
|
for the argument docs. This is split into a separate function
|
|
in order to enable simple creation of special-case DB constructors,
|
|
e.g. JsStorageDb and OpfsDb.
|
|
|
|
Expects to be passed a configuration object with the following
|
|
properties:
|
|
|
|
- `.filename`: the db filename. It may be a special name like ":memory:"
|
|
or "".
|
|
|
|
- `.flags`: as documented in the DB constructor.
|
|
|
|
- `.vfs`: as documented in the DB constructor.
|
|
|
|
It also accepts those as the first 3 arguments.
|
|
*/
|
|
const dbCtorHelper = function ctor(...args){
|
|
if(!ctor._name2vfs){
|
|
/**
|
|
Map special filenames which we handle here (instead of in C)
|
|
to some helpful metadata...
|
|
|
|
As of 2022-09-20, the C API supports the names :localStorage:
|
|
and :sessionStorage: for kvvfs. However, C code cannot
|
|
determine (without embedded JS code, e.g. via Emscripten's
|
|
EM_JS()) whether the kvvfs is legal in the current browser
|
|
context (namely the main UI thread). In order to help client
|
|
code fail early on, instead of it being delayed until they
|
|
try to read or write a kvvfs-backed db, we'll check for those
|
|
names here and throw if they're not legal in the current
|
|
context.
|
|
*/
|
|
ctor._name2vfs = Object.create(null);
|
|
const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/)
|
|
? (n)=>toss3("The VFS for",n,"is only available in the main window thread.")
|
|
: false;
|
|
ctor._name2vfs[':localStorage:'] = {
|
|
vfs: 'kvvfs', filename: isWorkerThread || (()=>'local')
|
|
};
|
|
ctor._name2vfs[':sessionStorage:'] = {
|
|
vfs: 'kvvfs', filename: isWorkerThread || (()=>'session')
|
|
};
|
|
}
|
|
const opt = ctor.normalizeArgs(...args);
|
|
let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
|
|
if(('string'!==typeof fn && 'number'!==typeof fn)
|
|
|| 'string'!==typeof flagsStr
|
|
|| (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
|
|
sqlite3.config.error("Invalid DB ctor args",opt,arguments);
|
|
toss3("Invalid arguments for DB constructor.");
|
|
}
|
|
let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
|
|
const vfsCheck = ctor._name2vfs[fnJs];
|
|
if(vfsCheck){
|
|
vfsName = vfsCheck.vfs;
|
|
fn = fnJs = vfsCheck.filename(fnJs);
|
|
}
|
|
let pDb, oflags = 0;
|
|
if( flagsStr.indexOf('c')>=0 ){
|
|
oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
|
|
}
|
|
if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
|
|
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
|
|
oflags |= capi.SQLITE_OPEN_EXRESCODE;
|
|
const stack = wasm.pstack.pointer;
|
|
try {
|
|
const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
|
|
let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
|
|
pDb = wasm.peekPtr(pPtr);
|
|
checkSqlite3Rc(pDb, rc);
|
|
capi.sqlite3_extended_result_codes(pDb, 1);
|
|
if(flagsStr.indexOf('t')>=0){
|
|
capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
|
|
__dbTraceToConsole, pDb);
|
|
}
|
|
}catch( e ){
|
|
if( pDb ) capi.sqlite3_close_v2(pDb);
|
|
throw e;
|
|
}finally{
|
|
wasm.pstack.restore(stack);
|
|
}
|
|
this.filename = fnJs;
|
|
__ptrMap.set(this, pDb);
|
|
__stmtMap.set(this, Object.create(null));
|
|
try{
|
|
//#if enable-see
|
|
dbCtorApplySEEKey(this,opt);
|
|
//#endif
|
|
// Check for per-VFS post-open SQL/callback...
|
|
const pVfs = capi.sqlite3_js_db_vfs(pDb)
|
|
|| toss3("Internal error: cannot get VFS for new db handle.");
|
|
const postInitSql = __vfsPostOpenCallback[pVfs];
|
|
if(postInitSql){
|
|
/**
|
|
Reminder: if this db is encrypted and the client did _not_ pass
|
|
in the key, any init code will fail, causing the ctor to throw.
|
|
We don't actually know whether the db is encrypted, so we cannot
|
|
sensibly apply any heuristics which skip the init code only for
|
|
encrypted databases for which no key has yet been supplied.
|
|
*/
|
|
if(postInitSql instanceof Function){
|
|
postInitSql(this, sqlite3);
|
|
}else{
|
|
checkSqlite3Rc(
|
|
pDb, capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0)
|
|
);
|
|
}
|
|
}
|
|
}catch(e){
|
|
this.close();
|
|
throw e;
|
|
}
|
|
};
|
|
|
|
/**
|
|
Sets a callback which should be called after a db is opened with
|
|
the given sqlite3_vfs pointer. The 2nd argument must be a
|
|
function, which gets called with
|
|
(theOo1DbObject,sqlite3Namespace) at the end of the DB()
|
|
constructor. The function must throw on error, in which case the
|
|
db is closed and the exception is propagated. This function is
|
|
intended only for use by DB subclasses or sqlite3_vfs
|
|
implementations.
|
|
|
|
Prior to 2024-07-22, it was legal to pass SQL code as the second
|
|
argument, but that can interfere with a client's ability to run
|
|
pragmas which must be run before anything else, namely (pragma
|
|
locking_mode=exclusive) for use with WAL mode. That capability
|
|
had only ever been used as an internal detail of the two OPFS
|
|
VFSes, and they no longer use it that way.
|
|
*/
|
|
dbCtorHelper.setVfsPostOpenCallback = function(pVfs, callback){
|
|
if( !(callback instanceof Function)){
|
|
toss3("dbCtorHelper.setVfsPostOpenCallback() should not be used with "+
|
|
"a non-function argument.",arguments);
|
|
}
|
|
__vfsPostOpenCallback[pVfs] = callback;
|
|
};
|
|
|
|
/**
|
|
A helper for DB constructors. It accepts either a single
|
|
config-style object or up to 3 arguments (filename, dbOpenFlags,
|
|
dbVfsName). It returns a new object containing:
|
|
|
|
{ filename: ..., flags: ..., vfs: ... }
|
|
|
|
If passed an object, any additional properties it has are copied
|
|
as-is into the new object.
|
|
*/
|
|
dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){
|
|
const arg = {};
|
|
if(1===arguments.length && arguments[0] && 'object'===typeof arguments[0]){
|
|
Object.assign(arg, arguments[0]);
|
|
if(undefined===arg.flags) arg.flags = 'c';
|
|
if(undefined===arg.vfs) arg.vfs = null;
|
|
if(undefined===arg.filename) arg.filename = ':memory:';
|
|
}else{
|
|
arg.filename = filename;
|
|
arg.flags = flags;
|
|
arg.vfs = vfs;
|
|
}
|
|
return arg;
|
|
};
|
|
/**
|
|
The DB class provides a high-level OO wrapper around an sqlite3
|
|
db handle.
|
|
|
|
The given db filename must be resolvable using whatever
|
|
filesystem layer (virtual or otherwise) is set up for the default
|
|
sqlite3 VFS.
|
|
|
|
Note that the special sqlite3 db names ":memory:" and ""
|
|
(temporary db) have their normal special meanings here and need
|
|
not resolve to real filenames, but "" uses an on-storage
|
|
temporary database and requires that the VFS support that.
|
|
|
|
The second argument specifies the open/create mode for the
|
|
database. It must be string containing a sequence of letters (in
|
|
any order, but case sensitive) specifying the mode:
|
|
|
|
- "c": create if it does not exist, else fail if it does not
|
|
exist. Implies the "w" flag.
|
|
|
|
- "w": write. Implies "r": a db cannot be write-only.
|
|
|
|
- "r": read-only if neither "w" nor "c" are provided, else it
|
|
is ignored.
|
|
|
|
- "t": enable tracing of SQL executed on this database handle,
|
|
sending it to `console.log()`. To disable it later, call
|
|
`sqlite3.capi.sqlite3_trace_v2(thisDb.pointer, 0, 0, 0)`.
|
|
|
|
If "w" is not provided, the db is implicitly read-only, noting
|
|
that "rc" is meaningless
|
|
|
|
Any other letters are currently ignored. The default is
|
|
"c". These modes are ignored for the special ":memory:" and ""
|
|
names and _may_ be ignored altogether for certain VFSes.
|
|
|
|
The final argument is analogous to the final argument of
|
|
sqlite3_open_v2(): the name of an sqlite3 VFS. Pass a falsy value,
|
|
or none at all, to use the default. If passed a value, it must
|
|
be the string name of a VFS.
|
|
|
|
The constructor optionally (and preferably) takes its arguments
|
|
in the form of a single configuration object with the following
|
|
properties:
|
|
|
|
- `filename`: database file name
|
|
- `flags`: open-mode flags
|
|
- `vfs`: the VFS fname
|
|
|
|
//#if enable-see
|
|
|
|
SEE-capable builds optionally support ONE of the following
|
|
additional options:
|
|
|
|
- `key`, `hexkey`, or `textkey`: encryption key as a string,
|
|
ArrayBuffer, or Uint8Array. These flags function as documented
|
|
for the SEE pragmas of the same names. Using a byte array for
|
|
`hexkey` is equivalent to the same series of hex codes in
|
|
string form, so `'666f6f'` is equivalent to
|
|
`Uint8Array([0x66,0x6f,0x6f])`. A `textkey` byte array is
|
|
assumed to be UTF-8. A `key` string is transformed into a UTF-8
|
|
byte array, and a `key` byte array is transformed into a
|
|
`hexkey` with the same bytes.
|
|
|
|
In non-SEE builds, these options are ignored. In SEE builds,
|
|
`PRAGMA key/textkey/hexkey=X` is executed immediately after
|
|
opening the db. If more than one of the options is provided,
|
|
or any option has an invalid argument type, an exception is
|
|
thrown.
|
|
|
|
Note that some DB subclasses may run post-initialization SQL
|
|
code, e.g. to set a busy-handler timeout or tweak the page cache
|
|
size. Such code is run _after_ the SEE key is applied. If no key
|
|
is supplied and the database is encrypted, execution of the
|
|
post-initialization SQL will fail, causing the constructor to
|
|
throw.
|
|
|
|
//#endif enable-see
|
|
|
|
The `filename` and `vfs` arguments may be either JS strings or
|
|
C-strings allocated via WASM. `flags` is required to be a JS
|
|
string (because it's specific to this API, which is specific
|
|
to JS).
|
|
|
|
For purposes of passing a DB instance to C-style sqlite3
|
|
functions, the DB object's read-only `pointer` property holds its
|
|
`sqlite3*` pointer value. That property can also be used to check
|
|
whether this DB instance is still open: it will evaluate to
|
|
`undefined` after the DB object's close() method is called.
|
|
|
|
In the main window thread, the filenames `":localStorage:"` and
|
|
`":sessionStorage:"` are special: they cause the db to use either
|
|
localStorage or sessionStorage for storing the database using
|
|
the kvvfs. If one of these names are used, they trump
|
|
any vfs name set in the arguments.
|
|
*/
|
|
const DB = function(...args){
|
|
dbCtorHelper.apply(this, args);
|
|
};
|
|
DB.dbCtorHelper = dbCtorHelper;
|
|
|
|
/**
|
|
Internal-use enum for mapping JS types to DB-bindable types.
|
|
These do not (and need not) line up with the SQLITE_type
|
|
values. All values in this enum must be truthy and distinct
|
|
but they need not be numbers.
|
|
*/
|
|
const BindTypes = {
|
|
null: 1,
|
|
number: 2,
|
|
string: 3,
|
|
boolean: 4,
|
|
blob: 5
|
|
};
|
|
BindTypes['undefined'] == BindTypes.null;
|
|
if(wasm.bigIntEnabled){
|
|
BindTypes.bigint = BindTypes.number;
|
|
}
|
|
|
|
/**
|
|
This class wraps sqlite3_stmt. Calling this constructor
|
|
directly will trigger an exception. Use DB.prepare() to create
|
|
new instances.
|
|
|
|
For purposes of passing a Stmt instance to C-style sqlite3
|
|
functions, its read-only `pointer` property holds its `sqlite3_stmt*`
|
|
pointer value.
|
|
|
|
Other non-function properties include:
|
|
|
|
- `db`: the DB object which created the statement.
|
|
|
|
- `columnCount`: the number of result columns in the query, or 0
|
|
for queries which cannot return results. This property is a proxy
|
|
for sqlite3_column_count() and its use in loops should be avoided
|
|
because of the call overhead associated with that. The
|
|
`columnCount` is not cached when the Stmt is created because a
|
|
schema change made via a separate db connection between this
|
|
statement's preparation and when it is stepped may invalidate it.
|
|
|
|
- `parameterCount`: the number of bindable parameters in the query.
|
|
|
|
As a general rule, most methods of this class will throw if
|
|
called on an instance which has been finalized. For brevity's
|
|
sake, the method docs do not all repeat this warning.
|
|
*/
|
|
const Stmt = function(){
|
|
if(BindTypes!==arguments[2]){
|
|
toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare().");
|
|
}
|
|
this.db = arguments[0];
|
|
__ptrMap.set(this, arguments[1]);
|
|
this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer);
|
|
};
|
|
|
|
/** Throws if the given DB has been closed, else it is returned. */
|
|
const affirmDbOpen = function(db){
|
|
if(!db.pointer) toss3("DB has been closed.");
|
|
return db;
|
|
};
|
|
|
|
/** Throws if ndx is not an integer or if it is out of range
|
|
for stmt.columnCount, else returns stmt.
|
|
|
|
Reminder: this will also fail after the statement is finalized
|
|
but the resulting error will be about an out-of-bounds column
|
|
index rather than a statement-is-finalized error.
|
|
*/
|
|
const affirmColIndex = function(stmt,ndx){
|
|
if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){
|
|
toss3("Column index",ndx,"is out of range.");
|
|
}
|
|
return stmt;
|
|
};
|
|
|
|
/**
|
|
Expects to be passed the `arguments` object from DB.exec(). Does
|
|
the argument processing/validation, throws on error, and returns
|
|
a new object on success:
|
|
|
|
{ sql: the SQL, opt: optionsObj, cbArg: function}
|
|
|
|
The opt object is a normalized copy of any passed to this
|
|
function. The sql will be converted to a string if it is provided
|
|
in one of the supported non-string formats.
|
|
|
|
cbArg is only set if the opt.callback or opt.resultRows are set,
|
|
in which case it's a function which expects to be passed the
|
|
current Stmt and returns the callback argument of the type
|
|
indicated by the input arguments.
|
|
*/
|
|
const parseExecArgs = function(db, args){
|
|
const out = Object.create(null);
|
|
out.opt = Object.create(null);
|
|
switch(args.length){
|
|
case 1:
|
|
if('string'===typeof args[0] || util.isSQLableTypedArray(args[0])){
|
|
out.sql = args[0];
|
|
}else if(Array.isArray(args[0])){
|
|
out.sql = args[0];
|
|
}else if(args[0] && 'object'===typeof args[0]){
|
|
out.opt = args[0];
|
|
out.sql = out.opt.sql;
|
|
}
|
|
break;
|
|
case 2:
|
|
out.sql = args[0];
|
|
out.opt = args[1];
|
|
break;
|
|
default: toss3("Invalid argument count for exec().");
|
|
};
|
|
out.sql = util.flexibleString(out.sql);
|
|
if('string'!==typeof out.sql){
|
|
toss3("Missing SQL argument or unsupported SQL value type.");
|
|
}
|
|
const opt = out.opt;
|
|
switch(opt.returnValue){
|
|
case 'resultRows':
|
|
if(!opt.resultRows) opt.resultRows = [];
|
|
out.returnVal = ()=>opt.resultRows;
|
|
break;
|
|
case 'saveSql':
|
|
if(!opt.saveSql) opt.saveSql = [];
|
|
out.returnVal = ()=>opt.saveSql;
|
|
break;
|
|
case undefined:
|
|
case 'this':
|
|
out.returnVal = ()=>db;
|
|
break;
|
|
default:
|
|
toss3("Invalid returnValue value:",opt.returnValue);
|
|
}
|
|
if(!opt.callback && !opt.returnValue && undefined!==opt.rowMode){
|
|
if(!opt.resultRows) opt.resultRows = [];
|
|
out.returnVal = ()=>opt.resultRows;
|
|
}
|
|
if(opt.callback || opt.resultRows){
|
|
switch((undefined===opt.rowMode) ? 'array' : opt.rowMode) {
|
|
case 'object':
|
|
out.cbArg = (stmt,cache)=>{
|
|
if( !cache.columnNames ) cache.columnNames = stmt.getColumnNames([]);
|
|
/* https://sqlite.org/forum/forumpost/3632183d2470617d:
|
|
conversion of rows to objects (key/val pairs) is
|
|
somewhat expensive for large data sets because of the
|
|
native-to-JS conversion of the column names. If we
|
|
instead cache the names and build objects from that
|
|
list of strings, it can run twice as fast. The
|
|
difference is not noticeable for small data sets but
|
|
becomes human-perceivable when enough rows are
|
|
involved. */
|
|
const row = stmt.get([]);
|
|
const rv = Object.create(null);
|
|
for( const i in cache.columnNames ) rv[cache.columnNames[i]] = row[i];
|
|
return rv;
|
|
};
|
|
break;
|
|
case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
|
|
case 'stmt':
|
|
if(Array.isArray(opt.resultRows)){
|
|
toss3("exec(): invalid rowMode for a resultRows array: must",
|
|
"be one of 'array', 'object',",
|
|
"a result column number, or column name reference.");
|
|
}
|
|
out.cbArg = (stmt)=>stmt;
|
|
break;
|
|
default:
|
|
if(util.isInt32(opt.rowMode)){
|
|
out.cbArg = (stmt)=>stmt.get(opt.rowMode);
|
|
break;
|
|
}else if('string'===typeof opt.rowMode
|
|
&& opt.rowMode.length>1
|
|
&& '$'===opt.rowMode[0]){
|
|
/* "$X": fetch column named "X" (case-sensitive!). Prior
|
|
to 2022-12-14 ":X" and "@X" were also permitted, but
|
|
having so many options is unnecessary and likely to
|
|
cause confusion. */
|
|
const $colName = opt.rowMode.substr(1);
|
|
out.cbArg = (stmt)=>{
|
|
const rc = stmt.get(Object.create(null))[$colName];
|
|
return (undefined===rc)
|
|
? toss3(capi.SQLITE_NOTFOUND,
|
|
"exec(): unknown result column:",$colName)
|
|
: rc;
|
|
};
|
|
break;
|
|
}
|
|
toss3("Invalid rowMode:",opt.rowMode);
|
|
}
|
|
}
|
|
return out;
|
|
};
|
|
|
|
/**
|
|
Internal impl of the DB.selectValue(), selectArray(), and
|
|
selectObject() methods.
|
|
*/
|
|
const __selectFirstRow = (db, sql, bind, ...getArgs)=>{
|
|
const stmt = db.prepare(sql);
|
|
try {
|
|
const rc = stmt.bind(bind).step() ? stmt.get(...getArgs) : undefined;
|
|
stmt.reset(/*for INSERT...RETURNING locking case*/);
|
|
return rc;
|
|
}finally{
|
|
stmt.finalize();
|
|
}
|
|
};
|
|
|
|
/**
|
|
Internal impl of the DB.selectArrays() and selectObjects()
|
|
methods.
|
|
*/
|
|
const __selectAll =
|
|
(db, sql, bind, rowMode)=>db.exec({
|
|
sql, bind, rowMode, returnValue: 'resultRows'
|
|
});
|
|
|
|
/**
|
|
Expects to be given a DB instance or an `sqlite3*` pointer (may
|
|
be null) and an sqlite3 API result code. If the result code is
|
|
not falsy, this function throws an SQLite3Error with an error
|
|
message from sqlite3_errmsg(), using db (or, if db is-a DB,
|
|
db.pointer) as the db handle, or sqlite3_errstr() if db is
|
|
falsy. Note that if it's passed a non-error code like SQLITE_ROW
|
|
or SQLITE_DONE, it will still throw but the error string might be
|
|
"Not an error." The various non-0 non-error codes need to be
|
|
checked for in client code where they are expected.
|
|
|
|
The thrown exception's `resultCode` property will be the value of
|
|
the second argument to this function.
|
|
|
|
If it does not throw, it returns its first argument.
|
|
*/
|
|
DB.checkRc = (db,resultCode)=>checkSqlite3Rc(db,resultCode);
|
|
|
|
DB.prototype = {
|
|
/** Returns true if this db handle is open, else false. */
|
|
isOpen: function(){
|
|
return !!this.pointer;
|
|
},
|
|
/** Throws if this given DB has been closed, else returns `this`. */
|
|
affirmOpen: function(){
|
|
return affirmDbOpen(this);
|
|
},
|
|
/**
|
|
Finalizes all open statements and closes this database
|
|
connection. This is a no-op if the db has already been
|
|
closed. After calling close(), `this.pointer` will resolve to
|
|
`undefined`, so that can be used to check whether the db
|
|
instance is still opened.
|
|
|
|
If this.onclose.before is a function then it is called before
|
|
any close-related cleanup.
|
|
|
|
If this.onclose.after is a function then it is called after the
|
|
db is closed but before auxiliary state like this.filename is
|
|
cleared.
|
|
|
|
Both onclose handlers are passed this object, with the onclose
|
|
object as their "this," noting that the db will have been
|
|
closed when onclose.after is called. If this db is not opened
|
|
when close() is called, neither of the handlers are called. Any
|
|
exceptions the handlers throw are ignored because "destructors
|
|
must not throw."
|
|
|
|
Note that garbage collection of a db handle, if it happens at
|
|
all, will never trigger close(), so onclose handlers are not a
|
|
reliable way to implement close-time cleanup or maintenance of
|
|
a db.
|
|
*/
|
|
close: function(){
|
|
if(this.pointer){
|
|
if(this.onclose && (this.onclose.before instanceof Function)){
|
|
try{this.onclose.before(this)}
|
|
catch(e){/*ignore*/}
|
|
}
|
|
const pDb = this.pointer;
|
|
Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
|
|
if(s && s.pointer){
|
|
try{s.finalize()}
|
|
catch(e){/*ignore*/}
|
|
}
|
|
});
|
|
__ptrMap.delete(this);
|
|
__stmtMap.delete(this);
|
|
capi.sqlite3_close_v2(pDb);
|
|
if(this.onclose && (this.onclose.after instanceof Function)){
|
|
try{this.onclose.after(this)}
|
|
catch(e){/*ignore*/}
|
|
}
|
|
delete this.filename;
|
|
}
|
|
},
|
|
/**
|
|
Returns the number of changes, as per sqlite3_changes()
|
|
(if the first argument is false) or sqlite3_total_changes()
|
|
(if it's true). If the 2nd argument is true, it uses
|
|
sqlite3_changes64() or sqlite3_total_changes64(), which
|
|
will trigger an exception if this build does not have
|
|
BigInt support enabled.
|
|
*/
|
|
changes: function(total=false,sixtyFour=false){
|
|
const p = affirmDbOpen(this).pointer;
|
|
if(total){
|
|
return sixtyFour
|
|
? capi.sqlite3_total_changes64(p)
|
|
: capi.sqlite3_total_changes(p);
|
|
}else{
|
|
return sixtyFour
|
|
? capi.sqlite3_changes64(p)
|
|
: capi.sqlite3_changes(p);
|
|
}
|
|
},
|
|
/**
|
|
Similar to the this.filename but returns the
|
|
sqlite3_db_filename() value for the given database name,
|
|
defaulting to "main". The argument may be either a JS string
|
|
or a pointer to a WASM-allocated C-string.
|
|
*/
|
|
dbFilename: function(dbName='main'){
|
|
return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
|
|
},
|
|
/**
|
|
Returns the name of the given 0-based db number, as documented
|
|
for sqlite3_db_name().
|
|
*/
|
|
dbName: function(dbNumber=0){
|
|
return capi.sqlite3_db_name(affirmDbOpen(this).pointer, dbNumber);
|
|
},
|
|
/**
|
|
Returns the name of the sqlite3_vfs used by the given database
|
|
of this connection (defaulting to 'main'). The argument may be
|
|
either a JS string or a WASM C-string. Returns undefined if the
|
|
given db name is invalid. Throws if this object has been
|
|
close()d.
|
|
*/
|
|
dbVfsName: function(dbName=0){
|
|
let rc;
|
|
const pVfs = capi.sqlite3_js_db_vfs(
|
|
affirmDbOpen(this).pointer, dbName
|
|
);
|
|
if(pVfs){
|
|
const v = new capi.sqlite3_vfs(pVfs);
|
|
try{ rc = wasm.cstrToJs(v.$zName) }
|
|
finally { v.dispose() }
|
|
}
|
|
return rc;
|
|
},
|
|
/**
|
|
Compiles the given SQL and returns a prepared Stmt. This is
|
|
the only way to create new Stmt objects. Throws on error.
|
|
|
|
The given SQL must be a string, a Uint8Array holding SQL, a
|
|
WASM pointer to memory holding the NUL-terminated SQL string,
|
|
or an array of strings. In the latter case, the array is
|
|
concatenated together, with no separators, to form the SQL
|
|
string (arrays are often a convenient way to formulate long
|
|
statements). If the SQL contains no statements, an
|
|
SQLite3Error is thrown.
|
|
|
|
Design note: the C API permits empty SQL, reporting it as a 0
|
|
result code and a NULL stmt pointer. Supporting that case here
|
|
would cause extra work for all clients: any use of the Stmt API
|
|
on such a statement will necessarily throw, so clients would be
|
|
required to check `stmt.pointer` after calling `prepare()` in
|
|
order to determine whether the Stmt instance is empty or not.
|
|
Long-time practice (with other sqlite3 script bindings)
|
|
suggests that the empty-prepare case is sufficiently rare that
|
|
supporting it here would simply hurt overall usability.
|
|
*/
|
|
prepare: function(sql){
|
|
affirmDbOpen(this);
|
|
const stack = wasm.pstack.pointer;
|
|
let ppStmt, pStmt;
|
|
try{
|
|
ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */;
|
|
DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null));
|
|
pStmt = wasm.peekPtr(ppStmt);
|
|
}
|
|
finally {
|
|
wasm.pstack.restore(stack);
|
|
}
|
|
if(!pStmt) toss3("Cannot prepare empty SQL.");
|
|
const stmt = new Stmt(this, pStmt, BindTypes);
|
|
__stmtMap.get(this)[pStmt] = stmt;
|
|
return stmt;
|
|
},
|
|
/**
|
|
Executes one or more SQL statements in the form of a single
|
|
string. Its arguments must be either (sql,optionsObject) or
|
|
(optionsObject). In the latter case, optionsObject.sql must
|
|
contain the SQL to execute. By default it returns this object
|
|
but that can be changed via the `returnValue` option as
|
|
described below. Throws on error.
|
|
|
|
If no SQL is provided, or a non-string is provided, an
|
|
exception is triggered. Empty SQL, on the other hand, is
|
|
simply a no-op.
|
|
|
|
The optional options object may contain any of the following
|
|
properties:
|
|
|
|
- `sql` = the SQL to run (unless it's provided as the first
|
|
argument). This must be of type string, Uint8Array, or an array
|
|
of strings. In the latter case they're concatenated together
|
|
as-is, _with no separator_ between elements, before evaluation.
|
|
The array form is often simpler for long hand-written queries.
|
|
|
|
- `bind` = a single value valid as an argument for
|
|
Stmt.bind(). This is _only_ applied to the _first_ non-empty
|
|
statement in the SQL which has any bindable parameters. (Empty
|
|
statements are skipped entirely.)
|
|
|
|
- `saveSql` = an optional array. If set, the SQL of each
|
|
executed statement is appended to this array before the
|
|
statement is executed (but after it is prepared - we don't have
|
|
the string until after that). Empty SQL statements are elided
|
|
but can have odd effects in the output. e.g. SQL of: `"select
|
|
1; -- empty\n; select 2"` will result in an array containing
|
|
`["select 1;", "--empty \n; select 2"]`. That's simply how
|
|
sqlite3 records the SQL for the 2nd statement.
|
|
|
|
==================================================================
|
|
The following options apply _only_ to the _first_ statement
|
|
which has a non-zero result column count, regardless of whether
|
|
the statement actually produces any result rows.
|
|
==================================================================
|
|
|
|
- `columnNames`: if this is an array, the column names of the
|
|
result set are stored in this array before the callback (if
|
|
any) is triggered (regardless of whether the query produces any
|
|
result rows). If no statement has result columns, this value is
|
|
unchanged. Achtung: an SQL result may have multiple columns
|
|
with identical names.
|
|
|
|
- `callback` = a function which gets called for each row of the
|
|
result set, but only if that statement has any result rows. The
|
|
callback's "this" is the options object, noting that this
|
|
function synthesizes one if the caller does not pass one to
|
|
exec(). The second argument passed to the callback is always
|
|
the current Stmt object, as it's needed if the caller wants to
|
|
fetch the column names or some such (noting that they could
|
|
also be fetched via `this.columnNames`, if the client provides
|
|
the `columnNames` option). If the callback returns a literal
|
|
`false` (as opposed to any other falsy value, e.g. an implicit
|
|
`undefined` return), any ongoing statement-`step()` iteration
|
|
stops without an error. The return value of the callback is
|
|
otherwise ignored.
|
|
|
|
ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
|
|
any of the Stmt.get() variants, Stmt.getColumnName(), or
|
|
similar, is legal, but calling step() or finalize() is
|
|
not. Member methods which are illegal in this context will
|
|
trigger an exception, but clients must also refrain from using
|
|
any lower-level (C-style) APIs which might modify the
|
|
statement.
|
|
|
|
The first argument passed to the callback defaults to an array of
|
|
values from the current result row but may be changed with ...
|
|
|
|
- `rowMode` = specifies the type of he callback's first argument.
|
|
It may be any of...
|
|
|
|
A) A string describing what type of argument should be passed
|
|
as the first argument to the callback:
|
|
|
|
A.1) `'array'` (the default) causes the results of
|
|
`stmt.get([])` to be passed to the `callback` and/or appended
|
|
to `resultRows`.
|
|
|
|
A.2) `'object'` causes the results of
|
|
`stmt.get(Object.create(null))` to be passed to the
|
|
`callback` and/or appended to `resultRows`. Achtung: an SQL
|
|
result may have multiple columns with identical names. In
|
|
that case, the right-most column will be the one set in this
|
|
object!
|
|
|
|
A.3) `'stmt'` causes the current Stmt to be passed to the
|
|
callback, but this mode will trigger an exception if
|
|
`resultRows` is an array because appending the transient
|
|
statement to the array would be downright unhelpful.
|
|
|
|
B) An integer, indicating a zero-based column in the result
|
|
row. Only that one single value will be passed on.
|
|
|
|
C) A string with a minimum length of 2 and leading character of
|
|
'$' will fetch the row as an object, extract that one field,
|
|
and pass that field's value to the callback. Note that these
|
|
keys are case-sensitive so must match the case used in the
|
|
SQL. e.g. `"select a A from t"` with a `rowMode` of `'$A'`
|
|
would work but `'$a'` would not. A reference to a column not in
|
|
the result set will trigger an exception on the first row (as
|
|
the check is not performed until rows are fetched). Note also
|
|
that `$` is a legal identifier character in JS so need not be
|
|
quoted.
|
|
|
|
Any other `rowMode` value triggers an exception.
|
|
|
|
- `resultRows`: if this is an array, it functions similarly to
|
|
the `callback` option: each row of the result set (if any),
|
|
with the exception that the `rowMode` 'stmt' is not legal. It
|
|
is legal to use both `resultRows` and `callback`, but
|
|
`resultRows` is likely much simpler to use for small data sets
|
|
and can be used over a WebWorker-style message interface.
|
|
exec() throws if `resultRows` is set and `rowMode` is 'stmt'.
|
|
|
|
- `returnValue`: is a string specifying what this function
|
|
should return:
|
|
|
|
A) The default value is (usually) `"this"`, meaning that the
|
|
DB object itself should be returned. The exception is if
|
|
the caller passes neither of `callback` nor `returnValue`
|
|
but does pass an explicit `rowMode` then the default
|
|
`returnValue` is `"resultRows"`, described below.
|
|
|
|
B) `"resultRows"` means to return the value of the
|
|
`resultRows` option. If `resultRows` is not set, this
|
|
function behaves as if it were set to an empty array.
|
|
|
|
C) `"saveSql"` means to return the value of the
|
|
`saveSql` option. If `saveSql` is not set, this
|
|
function behaves as if it were set to an empty array.
|
|
|
|
Potential TODOs:
|
|
|
|
- `bind`: permit an array of arrays/objects to bind. The first
|
|
sub-array would act on the first statement which has bindable
|
|
parameters (as it does now). The 2nd would act on the next such
|
|
statement, etc.
|
|
|
|
- `callback` and `resultRows`: permit an array entries with
|
|
semantics similar to those described for `bind` above.
|
|
|
|
*/
|
|
exec: function(/*(sql [,obj]) || (obj)*/){
|
|
affirmDbOpen(this);
|
|
const arg = parseExecArgs(this, arguments);
|
|
if(!arg.sql){
|
|
return toss3("exec() requires an SQL string.");
|
|
}
|
|
const opt = arg.opt;
|
|
const callback = opt.callback;
|
|
const resultRows =
|
|
Array.isArray(opt.resultRows) ? opt.resultRows : undefined;
|
|
let stmt;
|
|
let bind = opt.bind;
|
|
let evalFirstResult = !!(
|
|
arg.cbArg || opt.columnNames || resultRows
|
|
) /* true to step through the first result-returning statement */;
|
|
const stack = wasm.scopedAllocPush();
|
|
const saveSql = Array.isArray(opt.saveSql) ? opt.saveSql : undefined;
|
|
try{
|
|
const isTA = util.isSQLableTypedArray(arg.sql)
|
|
/* Optimization: if the SQL is a TypedArray we can save some string
|
|
conversion costs. */;
|
|
/* Allocate the two output pointers (ppStmt, pzTail) and heap
|
|
space for the SQL (pSql). When prepare_v2() returns, pzTail
|
|
will point to somewhere in pSql. */
|
|
let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql);
|
|
const ppStmt = wasm.scopedAlloc(
|
|
/* output (sqlite3_stmt**) arg and pzTail */
|
|
(2 * wasm.ptrSizeof) + (sqlByteLen + 1/* SQL + NUL */)
|
|
);
|
|
const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */;
|
|
let pSql = pzTail + wasm.ptrSizeof;
|
|
const pSqlEnd = pSql + sqlByteLen;
|
|
if(isTA) wasm.heap8().set(arg.sql, pSql);
|
|
else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
|
|
wasm.poke(pSql + sqlByteLen, 0/*NUL terminator*/);
|
|
while(pSql && wasm.peek(pSql, 'i8')
|
|
/* Maintenance reminder:^^^ _must_ be 'i8' or else we
|
|
will very likely cause an endless loop. What that's
|
|
doing is checking for a terminating NUL byte. If we
|
|
use i32 or similar then we read 4 bytes, read stuff
|
|
around the NUL terminator, and get stuck in and
|
|
endless loop at the end of the SQL, endlessly
|
|
re-preparing an empty statement. */ ){
|
|
wasm.pokePtr([ppStmt, pzTail], 0);
|
|
DB.checkRc(this, capi.sqlite3_prepare_v3(
|
|
this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
|
|
));
|
|
const pStmt = wasm.peekPtr(ppStmt);
|
|
pSql = wasm.peekPtr(pzTail);
|
|
sqlByteLen = pSqlEnd - pSql;
|
|
if(!pStmt) continue;
|
|
if(saveSql) saveSql.push(capi.sqlite3_sql(pStmt).trim());
|
|
stmt = new Stmt(this, pStmt, BindTypes);
|
|
if(bind && stmt.parameterCount){
|
|
stmt.bind(bind);
|
|
bind = null;
|
|
}
|
|
if(evalFirstResult && stmt.columnCount){
|
|
/* Only forward SELECT-style results for the FIRST query
|
|
in the SQL which potentially has them. */
|
|
let gotColNames = Array.isArray(
|
|
opt.columnNames
|
|
/* As reported in
|
|
https://sqlite.org/forum/forumpost/7774b773937cbe0a
|
|
we need to delay fetching of the column names until
|
|
after the first step() (if we step() at all) because
|
|
a schema change between the prepare() and step(), via
|
|
another connection, may invalidate the column count
|
|
and names. */) ? 0 : 1;
|
|
evalFirstResult = false;
|
|
if(arg.cbArg || resultRows){
|
|
const cbArgCache = Object.create(null)
|
|
/* 2nd arg for arg.cbArg, used by (at least) row-to-object
|
|
converter */;
|
|
for(; stmt.step(); stmt._lockedByExec = false){
|
|
if(0===gotColNames++){
|
|
stmt.getColumnNames(cbArgCache.columnNames = (opt.columnNames || []));
|
|
}
|
|
stmt._lockedByExec = true;
|
|
const row = arg.cbArg(stmt,cbArgCache);
|
|
if(resultRows) resultRows.push(row);
|
|
if(callback && false === callback.call(opt, row, stmt)){
|
|
break;
|
|
}
|
|
}
|
|
stmt._lockedByExec = false;
|
|
}
|
|
if(0===gotColNames){
|
|
/* opt.columnNames was provided but we visited no result rows */
|
|
stmt.getColumnNames(opt.columnNames);
|
|
}
|
|
}else{
|
|
stmt.step();
|
|
}
|
|
stmt.reset(
|
|
/* In order to trigger an exception in the
|
|
INSERT...RETURNING locking scenario:
|
|
https://sqlite.org/forum/forumpost/36f7a2e7494897df
|
|
*/).finalize();
|
|
stmt = null;
|
|
}/*prepare() loop*/
|
|
}/*catch(e){
|
|
sqlite3.config.warn("DB.exec() is propagating exception",opt,e);
|
|
throw e;
|
|
}*/finally{
|
|
wasm.scopedAllocPop(stack);
|
|
if(stmt){
|
|
delete stmt._lockedByExec;
|
|
stmt.finalize();
|
|
}
|
|
}
|
|
return arg.returnVal();
|
|
}/*exec()*/,
|
|
|
|
/**
|
|
Creates a new UDF (User-Defined Function) which is accessible
|
|
via SQL code. This function may be called in any of the
|
|
following forms:
|
|
|
|
- (name, function)
|
|
- (name, function, optionsObject)
|
|
- (name, optionsObject)
|
|
- (optionsObject)
|
|
|
|
In the final two cases, the function must be defined as the
|
|
`callback` property of the options object (optionally called
|
|
`xFunc` to align with the C API documentation). In the final
|
|
case, the function's name must be the 'name' property.
|
|
|
|
The first two call forms can only be used for creating scalar
|
|
functions. Creating an aggregate or window function requires
|
|
the options-object form (see below for details).
|
|
|
|
UDFs can be removed as documented for
|
|
sqlite3_create_function_v2() and
|
|
sqlite3_create_window_function(), but doing so will "leak" the
|
|
JS-created WASM binding of those functions (meaning that their
|
|
entries in the WASM indirect function table still
|
|
exist). Eliminating that potential leak is a pending TODO.
|
|
|
|
On success, returns this object. Throws on error.
|
|
|
|
When called from SQL arguments to the UDF, and its result,
|
|
will be converted between JS and SQL with as much fidelity as
|
|
is feasible, triggering an exception if a type conversion
|
|
cannot be determined. The docs for sqlite3_create_function_v2()
|
|
describe the conversions in more detail.
|
|
|
|
The values set in the options object differ for scalar and
|
|
aggregate functions:
|
|
|
|
- Scalar: set the `xFunc` function-type property to the UDF
|
|
function.
|
|
|
|
- Aggregate: set the `xStep` and `xFinal` function-type
|
|
properties to the "step" and "final" callbacks for the
|
|
aggregate. Do not set the `xFunc` property.
|
|
|
|
- Window: set the `xStep`, `xFinal`, `xValue`, and `xInverse`
|
|
function-type properties. Do not set the `xFunc` property.
|
|
|
|
The options object may optionally have an `xDestroy`
|
|
function-type property, as per sqlite3_create_function_v2().
|
|
Its argument will be the WASM-pointer-type value of the `pApp`
|
|
property, and this function will throw if `pApp` is defined but
|
|
is not null, undefined, or a numeric (WASM pointer)
|
|
value. i.e. `pApp`, if set, must be value suitable for use as a
|
|
WASM pointer argument, noting that `null` or `undefined` will
|
|
translate to 0 for that purpose.
|
|
|
|
The options object may contain flags to modify how
|
|
the function is defined:
|
|
|
|
- `arity`: the number of arguments which SQL calls to this
|
|
function expect or require. The default value is `xFunc.length`
|
|
or `xStep.length` (i.e. the number of declared parameters it
|
|
has) **MINUS 1** (see below for why). As a special case, if the
|
|
`length` is 0, its arity is also 0 instead of -1. A negative
|
|
arity value means that the function is variadic and may accept
|
|
any number of arguments, up to sqlite3's compile-time
|
|
limits. sqlite3 will enforce the argument count if is zero or
|
|
greater. The callback always receives a pointer to an
|
|
`sqlite3_context` object as its first argument. Any arguments
|
|
after that are from SQL code. The leading context argument does
|
|
_not_ count towards the function's arity. See the docs for
|
|
sqlite3.capi.sqlite3_create_function_v2() for why that argument
|
|
is needed in the interface.
|
|
|
|
The following options-object properties correspond to flags
|
|
documented at:
|
|
|
|
https://sqlite.org/c3ref/create_function.html
|
|
|
|
- `deterministic` = sqlite3.capi.SQLITE_DETERMINISTIC
|
|
- `directOnly` = sqlite3.capi.SQLITE_DIRECTONLY
|
|
- `innocuous` = sqlite3.capi.SQLITE_INNOCUOUS
|
|
|
|
Sidebar: the ability to add new WASM-accessible functions to
|
|
the runtime requires that the WASM build is compiled with the
|
|
equivalent functionality as that provided by Emscripten's
|
|
`-sALLOW_TABLE_GROWTH` flag.
|
|
*/
|
|
createFunction: function f(name, xFunc, opt){
|
|
const isFunc = (f)=>(f instanceof Function);
|
|
switch(arguments.length){
|
|
case 1: /* (optionsObject) */
|
|
opt = name;
|
|
name = opt.name;
|
|
xFunc = opt.xFunc || 0;
|
|
break;
|
|
case 2: /* (name, callback|optionsObject) */
|
|
if(!isFunc(xFunc)){
|
|
opt = xFunc;
|
|
xFunc = opt.xFunc || 0;
|
|
}
|
|
break;
|
|
case 3: /* name, xFunc, opt */
|
|
break;
|
|
default: break;
|
|
}
|
|
if(!opt) opt = {};
|
|
if('string' !== typeof name){
|
|
toss3("Invalid arguments: missing function name.");
|
|
}
|
|
let xStep = opt.xStep || 0;
|
|
let xFinal = opt.xFinal || 0;
|
|
const xValue = opt.xValue || 0;
|
|
const xInverse = opt.xInverse || 0;
|
|
let isWindow = undefined;
|
|
if(isFunc(xFunc)){
|
|
isWindow = false;
|
|
if(isFunc(xStep) || isFunc(xFinal)){
|
|
toss3("Ambiguous arguments: scalar or aggregate?");
|
|
}
|
|
xStep = xFinal = null;
|
|
}else if(isFunc(xStep)){
|
|
if(!isFunc(xFinal)){
|
|
toss3("Missing xFinal() callback for aggregate or window UDF.");
|
|
}
|
|
xFunc = null;
|
|
}else if(isFunc(xFinal)){
|
|
toss3("Missing xStep() callback for aggregate or window UDF.");
|
|
}else{
|
|
toss3("Missing function-type properties.");
|
|
}
|
|
if(false === isWindow){
|
|
if(isFunc(xValue) || isFunc(xInverse)){
|
|
toss3("xValue and xInverse are not permitted for non-window UDFs.");
|
|
}
|
|
}else if(isFunc(xValue)){
|
|
if(!isFunc(xInverse)){
|
|
toss3("xInverse must be provided if xValue is.");
|
|
}
|
|
isWindow = true;
|
|
}else if(isFunc(xInverse)){
|
|
toss3("xValue must be provided if xInverse is.");
|
|
}
|
|
const pApp = opt.pApp;
|
|
if(undefined!==pApp &&
|
|
null!==pApp &&
|
|
(('number'!==typeof pApp) || !util.isInt32(pApp))){
|
|
toss3("Invalid value for pApp property. Must be a legal WASM pointer value.");
|
|
}
|
|
const xDestroy = opt.xDestroy || 0;
|
|
if(xDestroy && !isFunc(xDestroy)){
|
|
toss3("xDestroy property must be a function.");
|
|
}
|
|
let fFlags = 0 /*flags for sqlite3_create_function_v2()*/;
|
|
if(getOwnOption(opt, 'deterministic')) fFlags |= capi.SQLITE_DETERMINISTIC;
|
|
if(getOwnOption(opt, 'directOnly')) fFlags |= capi.SQLITE_DIRECTONLY;
|
|
if(getOwnOption(opt, 'innocuous')) fFlags |= capi.SQLITE_INNOCUOUS;
|
|
name = name.toLowerCase();
|
|
const xArity = xFunc || xStep;
|
|
const arity = getOwnOption(opt, 'arity');
|
|
const arityArg = ('number'===typeof arity
|
|
? arity
|
|
: (xArity.length ? xArity.length-1/*for pCtx arg*/ : 0));
|
|
let rc;
|
|
if( isWindow ){
|
|
rc = capi.sqlite3_create_window_function(
|
|
this.pointer, name, arityArg,
|
|
capi.SQLITE_UTF8 | fFlags, pApp || 0,
|
|
xStep, xFinal, xValue, xInverse, xDestroy);
|
|
}else{
|
|
rc = capi.sqlite3_create_function_v2(
|
|
this.pointer, name, arityArg,
|
|
capi.SQLITE_UTF8 | fFlags, pApp || 0,
|
|
xFunc, xStep, xFinal, xDestroy);
|
|
}
|
|
DB.checkRc(this, rc);
|
|
return this;
|
|
}/*createFunction()*/,
|
|
/**
|
|
Prepares the given SQL, step()s it one time, and returns
|
|
the value of the first result column. If it has no results,
|
|
undefined is returned.
|
|
|
|
If passed a second argument, it is treated like an argument
|
|
to Stmt.bind(), so may be any type supported by that
|
|
function. Passing the undefined value is the same as passing
|
|
no value, which is useful when...
|
|
|
|
If passed a 3rd argument, it is expected to be one of the
|
|
SQLITE_{typename} constants. Passing the undefined value is
|
|
the same as not passing a value.
|
|
|
|
Throws on error (e.g. malformed SQL).
|
|
*/
|
|
selectValue: function(sql,bind,asType){
|
|
return __selectFirstRow(this, sql, bind, 0, asType);
|
|
},
|
|
|
|
/**
|
|
Runs the given query and returns an array of the values from
|
|
the first result column of each row of the result set. The 2nd
|
|
argument is an optional value for use in a single-argument call
|
|
to Stmt.bind(). The 3rd argument may be any value suitable for
|
|
use as the 2nd argument to Stmt.get(). If a 3rd argument is
|
|
desired but no bind data are needed, pass `undefined` for the 2nd
|
|
argument.
|
|
|
|
If there are no result rows, an empty array is returned.
|
|
*/
|
|
selectValues: function(sql,bind,asType){
|
|
const stmt = this.prepare(sql), rc = [];
|
|
try {
|
|
stmt.bind(bind);
|
|
while(stmt.step()) rc.push(stmt.get(0,asType));
|
|
stmt.reset(/*for INSERT...RETURNING locking case*/);
|
|
}finally{
|
|
stmt.finalize();
|
|
}
|
|
return rc;
|
|
},
|
|
|
|
/**
|
|
Prepares the given SQL, step()s it one time, and returns an
|
|
array containing the values of the first result row. If it has
|
|
no results, `undefined` is returned.
|
|
|
|
If passed a second argument other than `undefined`, it is
|
|
treated like an argument to Stmt.bind(), so may be any type
|
|
supported by that function.
|
|
|
|
Throws on error (e.g. malformed SQL).
|
|
*/
|
|
selectArray: function(sql,bind){
|
|
return __selectFirstRow(this, sql, bind, []);
|
|
},
|
|
|
|
/**
|
|
Prepares the given SQL, step()s it one time, and returns an
|
|
object containing the key/value pairs of the first result
|
|
row. If it has no results, `undefined` is returned.
|
|
|
|
Note that the order of returned object's keys is not guaranteed
|
|
to be the same as the order of the fields in the query string.
|
|
|
|
If passed a second argument other than `undefined`, it is
|
|
treated like an argument to Stmt.bind(), so may be any type
|
|
supported by that function.
|
|
|
|
Throws on error (e.g. malformed SQL).
|
|
*/
|
|
selectObject: function(sql,bind){
|
|
return __selectFirstRow(this, sql, bind, {});
|
|
},
|
|
|
|
/**
|
|
Runs the given SQL and returns an array of all results, with
|
|
each row represented as an array, as per the 'array' `rowMode`
|
|
option to `exec()`. An empty result set resolves
|
|
to an empty array. The second argument, if any, is treated as
|
|
the 'bind' option to a call to exec().
|
|
*/
|
|
selectArrays: function(sql,bind){
|
|
return __selectAll(this, sql, bind, 'array');
|
|
},
|
|
|
|
/**
|
|
Works identically to selectArrays() except that each value
|
|
in the returned array is an object, as per the 'object' `rowMode`
|
|
option to `exec()`.
|
|
*/
|
|
selectObjects: function(sql,bind){
|
|
return __selectAll(this, sql, bind, 'object');
|
|
},
|
|
|
|
/**
|
|
Returns the number of currently-opened Stmt handles for this db
|
|
handle, or 0 if this DB instance is closed. Note that only
|
|
handles prepared via this.prepare() are counted, and not
|
|
handles prepared using capi.sqlite3_prepare_v3() (or
|
|
equivalent).
|
|
*/
|
|
openStatementCount: function(){
|
|
return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0;
|
|
},
|
|
|
|
/**
|
|
Starts a transaction, calls the given callback, and then either
|
|
rolls back or commits the savepoint, depending on whether the
|
|
callback throws. The callback is passed this db object as its
|
|
only argument. On success, returns the result of the
|
|
callback. Throws on error.
|
|
|
|
Note that transactions may not be nested, so this will throw if
|
|
it is called recursively. For nested transactions, use the
|
|
savepoint() method or manually manage SAVEPOINTs using exec().
|
|
|
|
If called with 2 arguments, the first must be a keyword which
|
|
is legal immediately after a BEGIN statement, e.g. one of
|
|
"DEFERRED", "IMMEDIATE", or "EXCLUSIVE". Though the exact list
|
|
of supported keywords is not hard-coded here, in order to be
|
|
future-compatible, if the argument does not look like a single
|
|
keyword then an exception is triggered with a description of
|
|
the problem.
|
|
*/
|
|
transaction: function(/* [beginQualifier,] */callback){
|
|
let opener = 'BEGIN';
|
|
if(arguments.length>1){
|
|
if(/[^a-zA-Z]/.test(arguments[0])){
|
|
toss3(capi.SQLITE_MISUSE, "Invalid argument for BEGIN qualifier.");
|
|
}
|
|
opener += ' '+arguments[0];
|
|
callback = arguments[1];
|
|
}
|
|
affirmDbOpen(this).exec(opener);
|
|
try {
|
|
const rc = callback(this);
|
|
this.exec("COMMIT");
|
|
return rc;
|
|
}catch(e){
|
|
this.exec("ROLLBACK");
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
/**
|
|
This works similarly to transaction() but uses sqlite3's SAVEPOINT
|
|
feature. This function starts a savepoint (with an unspecified name)
|
|
and calls the given callback function, passing it this db object.
|
|
If the callback returns, the savepoint is released (committed). If
|
|
the callback throws, the savepoint is rolled back. If it does not
|
|
throw, it returns the result of the callback.
|
|
*/
|
|
savepoint: function(callback){
|
|
affirmDbOpen(this).exec("SAVEPOINT oo1");
|
|
try {
|
|
const rc = callback(this);
|
|
this.exec("RELEASE oo1");
|
|
return rc;
|
|
}catch(e){
|
|
this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
|
|
throw e;
|
|
}
|
|
},
|
|
|
|
/**
|
|
A convenience form of DB.checkRc(this,resultCode). If it does
|
|
not throw, it returns this object.
|
|
*/
|
|
checkRc: function(resultCode){
|
|
return checkSqlite3Rc(this, resultCode);
|
|
}
|
|
}/*DB.prototype*/;
|
|
|
|
|
|
/** Throws if the given Stmt has been finalized, else stmt is
|
|
returned. */
|
|
const affirmStmtOpen = function(stmt){
|
|
if(!stmt.pointer) toss3("Stmt has been closed.");
|
|
return stmt;
|
|
};
|
|
|
|
/** Returns an opaque truthy value from the BindTypes
|
|
enum if v's type is a valid bindable type, else
|
|
returns a falsy value. As a special case, a value of
|
|
undefined is treated as a bind type of null. */
|
|
const isSupportedBindType = function(v){
|
|
let t = BindTypes[(null===v||undefined===v) ? 'null' : typeof v];
|
|
switch(t){
|
|
case BindTypes.boolean:
|
|
case BindTypes.null:
|
|
case BindTypes.number:
|
|
case BindTypes.string:
|
|
return t;
|
|
case BindTypes.bigint:
|
|
if(wasm.bigIntEnabled) return t;
|
|
/* else fall through */
|
|
default:
|
|
return util.isBindableTypedArray(v) ? BindTypes.blob : undefined;
|
|
}
|
|
};
|
|
|
|
/**
|
|
If isSupportedBindType(v) returns a truthy value, this
|
|
function returns that value, else it throws.
|
|
*/
|
|
const affirmSupportedBindType = function(v){
|
|
//sqlite3.config.log('affirmSupportedBindType',v);
|
|
return isSupportedBindType(v) || toss3("Unsupported bind() argument type:",typeof v);
|
|
};
|
|
|
|
/**
|
|
If key is a number and within range of stmt's bound parameter
|
|
count, key is returned.
|
|
|
|
If key is not a number then it is checked against named
|
|
parameters. If a match is found, its index is returned.
|
|
|
|
Else it throws.
|
|
*/
|
|
const affirmParamIndex = function(stmt,key){
|
|
const n = ('number'===typeof key)
|
|
? key : capi.sqlite3_bind_parameter_index(stmt.pointer, key);
|
|
if(0===n || !util.isInt32(n)){
|
|
toss3("Invalid bind() parameter name: "+key);
|
|
}
|
|
else if(n<1 || n>stmt.parameterCount) toss3("Bind index",key,"is out of range.");
|
|
return n;
|
|
};
|
|
|
|
/**
|
|
If stmt._lockedByExec is truthy, this throws an exception
|
|
complaining that the 2nd argument (an operation name,
|
|
e.g. "bind()") is not legal while the statement is "locked".
|
|
Locking happens before an exec()-like callback is passed a
|
|
statement, to ensure that the callback does not mutate or
|
|
finalize the statement. If it does not throw, it returns stmt.
|
|
*/
|
|
const affirmNotLockedByExec = function(stmt,currentOpName){
|
|
if(stmt._lockedByExec){
|
|
toss3("Operation is illegal when statement is locked:",currentOpName);
|
|
}
|
|
return stmt;
|
|
};
|
|
|
|
/**
|
|
Binds a single bound parameter value on the given stmt at the
|
|
given index (numeric or named) using the given bindType (see
|
|
the BindTypes enum) and value. Throws on error. Returns stmt on
|
|
success.
|
|
*/
|
|
const bindOne = function f(stmt,ndx,bindType,val){
|
|
affirmNotLockedByExec(affirmStmtOpen(stmt), 'bind()');
|
|
if(!f._){
|
|
f._tooBigInt = (v)=>toss3(
|
|
"BigInt value is too big to store without precision loss:", v
|
|
);
|
|
f._ = {
|
|
string: function(stmt, ndx, val, asBlob){
|
|
const [pStr, n] = wasm.allocCString(val, true);
|
|
const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
|
|
return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_WASM_DEALLOC);
|
|
}
|
|
};
|
|
}/* static init */
|
|
affirmSupportedBindType(val);
|
|
ndx = affirmParamIndex(stmt,ndx);
|
|
let rc = 0;
|
|
switch((null===val || undefined===val) ? BindTypes.null : bindType){
|
|
case BindTypes.null:
|
|
rc = capi.sqlite3_bind_null(stmt.pointer, ndx);
|
|
break;
|
|
case BindTypes.string:
|
|
rc = f._.string(stmt, ndx, val, false);
|
|
break;
|
|
case BindTypes.number: {
|
|
let m;
|
|
if(util.isInt32(val)) m = capi.sqlite3_bind_int;
|
|
else if('bigint'===typeof val){
|
|
if(!util.bigIntFits64(val)){
|
|
f._tooBigInt(val);
|
|
}else if(wasm.bigIntEnabled){
|
|
m = capi.sqlite3_bind_int64;
|
|
}else if(util.bigIntFitsDouble(val)){
|
|
val = Number(val);
|
|
m = capi.sqlite3_bind_double;
|
|
}else{
|
|
f._tooBigInt(val);
|
|
}
|
|
}else{ // !int32, !bigint
|
|
val = Number(val);
|
|
if(wasm.bigIntEnabled && Number.isInteger(val)){
|
|
m = capi.sqlite3_bind_int64;
|
|
}else{
|
|
m = capi.sqlite3_bind_double;
|
|
}
|
|
}
|
|
rc = m(stmt.pointer, ndx, val);
|
|
break;
|
|
}
|
|
case BindTypes.boolean:
|
|
rc = capi.sqlite3_bind_int(stmt.pointer, ndx, val ? 1 : 0);
|
|
break;
|
|
case BindTypes.blob: {
|
|
if('string'===typeof val){
|
|
rc = f._.string(stmt, ndx, val, true);
|
|
break;
|
|
}else if(val instanceof ArrayBuffer){
|
|
val = new Uint8Array(val);
|
|
}else if(!util.isBindableTypedArray(val)){
|
|
toss3("Binding a value as a blob requires",
|
|
"that it be a string, Uint8Array, Int8Array, or ArrayBuffer.");
|
|
}
|
|
const pBlob = wasm.alloc(val.byteLength || 1);
|
|
wasm.heap8().set(val.byteLength ? val : [0], pBlob)
|
|
rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
|
|
capi.SQLITE_WASM_DEALLOC);
|
|
break;
|
|
}
|
|
default:
|
|
sqlite3.config.warn("Unsupported bind() argument type:",val);
|
|
toss3("Unsupported bind() argument type: "+(typeof val));
|
|
}
|
|
if(rc) DB.checkRc(stmt.db.pointer, rc);
|
|
stmt._mayGet = false;
|
|
return stmt;
|
|
};
|
|
|
|
Stmt.prototype = {
|
|
/**
|
|
"Finalizes" this statement. This is a no-op if the statement
|
|
has already been finalized. Returns the result of
|
|
sqlite3_finalize() (0 on success, non-0 on error), or the
|
|
undefined value if the statement has already been
|
|
finalized. Regardless of success or failure, most methods in
|
|
this class will throw if called after this is.
|
|
|
|
This method always throws if called when it is illegal to do
|
|
so. Namely, when triggered via a per-row callback handler of a
|
|
DB.exec() call.
|
|
*/
|
|
finalize: function(){
|
|
if(this.pointer){
|
|
affirmNotLockedByExec(this,'finalize()');
|
|
const rc = capi.sqlite3_finalize(this.pointer);
|
|
delete __stmtMap.get(this.db)[this.pointer];
|
|
__ptrMap.delete(this);
|
|
delete this._mayGet;
|
|
delete this.parameterCount;
|
|
delete this._lockedByExec;
|
|
delete this.db;
|
|
return rc;
|
|
}
|
|
},
|
|
/**
|
|
Clears all bound values. Returns this object. Throws if this
|
|
statement has been finalized or if modification of the
|
|
statement is currently illegal (e.g. in the per-row callback of
|
|
a DB.exec() call).
|
|
*/
|
|
clearBindings: function(){
|
|
affirmNotLockedByExec(affirmStmtOpen(this), 'clearBindings()')
|
|
capi.sqlite3_clear_bindings(this.pointer);
|
|
this._mayGet = false;
|
|
return this;
|
|
},
|
|
/**
|
|
Resets this statement so that it may be step()ed again from the
|
|
beginning. Returns this object. Throws if this statement has
|
|
been finalized, if it may not legally be reset because it is
|
|
currently being used from a DB.exec() callback, or if the
|
|
underlying call to sqlite3_reset() returns non-0.
|
|
|
|
If passed a truthy argument then this.clearBindings() is
|
|
also called, otherwise any existing bindings, along with
|
|
any memory allocated for them, are retained.
|
|
|
|
In versions 3.42.0 and earlier, this function did not throw if
|
|
sqlite3_reset() returns non-0, but it was discovered that
|
|
throwing (or significant extra client-side code) is necessary
|
|
in order to avoid certain silent failure scenarios, as
|
|
discussed at:
|
|
|
|
https://sqlite.org/forum/forumpost/36f7a2e7494897df
|
|
*/
|
|
reset: function(alsoClearBinds){
|
|
affirmNotLockedByExec(this,'reset()');
|
|
if(alsoClearBinds) this.clearBindings();
|
|
const rc = capi.sqlite3_reset(affirmStmtOpen(this).pointer);
|
|
this._mayGet = false;
|
|
checkSqlite3Rc(this.db, rc);
|
|
return this;
|
|
},
|
|
/**
|
|
Binds one or more values to its bindable parameters. It
|
|
accepts 1 or 2 arguments:
|
|
|
|
If passed a single argument, it must be either an array, an
|
|
object, or a value of a bindable type (see below).
|
|
|
|
If passed 2 arguments, the first one is the 1-based bind
|
|
index or bindable parameter name and the second one must be
|
|
a value of a bindable type.
|
|
|
|
Bindable value types:
|
|
|
|
- null is bound as NULL.
|
|
|
|
- undefined as a standalone value is a no-op intended to
|
|
simplify certain client-side use cases: passing undefined as
|
|
a value to this function will not actually bind anything and
|
|
this function will skip confirmation that binding is even
|
|
legal. (Those semantics simplify certain client-side uses.)
|
|
Conversely, a value of undefined as an array or object
|
|
property when binding an array/object (see below) is treated
|
|
the same as null.
|
|
|
|
- Numbers are bound as either doubles or integers: doubles if
|
|
they are larger than 32 bits, else double or int32, depending
|
|
on whether they have a fractional part. Booleans are bound as
|
|
integer 0 or 1. It is not expected the distinction of binding
|
|
doubles which have no fractional parts and integers is
|
|
significant for the majority of clients due to sqlite3's data
|
|
typing model. If BigInt support is enabled then this routine
|
|
will bind BigInt values as 64-bit integers if they'll fit in
|
|
64 bits. If that support disabled, it will store the BigInt
|
|
as an int32 or a double if it can do so without loss of
|
|
precision. If the BigInt is _too BigInt_ then it will throw.
|
|
|
|
- Strings are bound as strings (use bindAsBlob() to force
|
|
blob binding).
|
|
|
|
- Uint8Array, Int8Array, and ArrayBuffer instances are bound as
|
|
blobs.
|
|
|
|
If passed an array, each element of the array is bound at
|
|
the parameter index equal to the array index plus 1
|
|
(because arrays are 0-based but binding is 1-based).
|
|
|
|
If passed an object, each object key is treated as a
|
|
bindable parameter name. The object keys _must_ match any
|
|
bindable parameter names, including any `$`, `@`, or `:`
|
|
prefix. Because `$` is a legal identifier chararacter in
|
|
JavaScript, that is the suggested prefix for bindable
|
|
parameters: `stmt.bind({$a: 1, $b: 2})`.
|
|
|
|
It returns this object on success and throws on
|
|
error. Errors include:
|
|
|
|
- Any bind index is out of range, a named bind parameter
|
|
does not match, or this statement has no bindable
|
|
parameters.
|
|
|
|
- Any value to bind is of an unsupported type.
|
|
|
|
- Passed no arguments or more than two.
|
|
|
|
- The statement has been finalized.
|
|
*/
|
|
bind: function(/*[ndx,] arg*/){
|
|
affirmStmtOpen(this);
|
|
let ndx, arg;
|
|
switch(arguments.length){
|
|
case 1: ndx = 1; arg = arguments[0]; break;
|
|
case 2: ndx = arguments[0]; arg = arguments[1]; break;
|
|
default: toss3("Invalid bind() arguments.");
|
|
}
|
|
if(undefined===arg){
|
|
/* It might seem intuitive to bind undefined as NULL
|
|
but this approach simplifies certain client-side
|
|
uses when passing on arguments between 2+ levels of
|
|
functions. */
|
|
return this;
|
|
}else if(!this.parameterCount){
|
|
toss3("This statement has no bindable parameters.");
|
|
}
|
|
this._mayGet = false;
|
|
if(null===arg){
|
|
/* bind NULL */
|
|
return bindOne(this, ndx, BindTypes.null, arg);
|
|
}
|
|
else if(Array.isArray(arg)){
|
|
/* bind each entry by index */
|
|
if(1!==arguments.length){
|
|
toss3("When binding an array, an index argument is not permitted.");
|
|
}
|
|
arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
|
|
return this;
|
|
}else if(arg instanceof ArrayBuffer){
|
|
arg = new Uint8Array(arg);
|
|
}
|
|
if('object'===typeof arg/*null was checked above*/
|
|
&& !util.isBindableTypedArray(arg)){
|
|
/* Treat each property of arg as a named bound parameter. */
|
|
if(1!==arguments.length){
|
|
toss3("When binding an object, an index argument is not permitted.");
|
|
}
|
|
Object.keys(arg)
|
|
.forEach(k=>bindOne(this, k,
|
|
affirmSupportedBindType(arg[k]),
|
|
arg[k]));
|
|
return this;
|
|
}else{
|
|
return bindOne(this, ndx, affirmSupportedBindType(arg), arg);
|
|
}
|
|
toss3("Should not reach this point.");
|
|
},
|
|
/**
|
|
Special case of bind() which binds the given value using the
|
|
BLOB binding mechanism instead of the default selected one for
|
|
the value. The ndx may be a numbered or named bind index. The
|
|
value must be of type string, null/undefined (both get treated
|
|
as null), or a TypedArray of a type supported by the bind()
|
|
API. This API cannot bind numbers as blobs.
|
|
|
|
If passed a single argument, a bind index of 1 is assumed and
|
|
the first argument is the value.
|
|
*/
|
|
bindAsBlob: function(ndx,arg){
|
|
affirmStmtOpen(this);
|
|
if(1===arguments.length){
|
|
arg = ndx;
|
|
ndx = 1;
|
|
}
|
|
const t = affirmSupportedBindType(arg);
|
|
if(BindTypes.string !== t && BindTypes.blob !== t
|
|
&& BindTypes.null !== t){
|
|
toss3("Invalid value type for bindAsBlob()");
|
|
}
|
|
return bindOne(this, ndx, BindTypes.blob, arg);
|
|
},
|
|
/**
|
|
Steps the statement one time. If the result indicates that a
|
|
row of data is available, a truthy value is returned. If no
|
|
row of data is available, a falsy value is returned. Throws on
|
|
error.
|
|
*/
|
|
step: function(){
|
|
affirmNotLockedByExec(this, 'step()');
|
|
const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer);
|
|
switch(rc){
|
|
case capi.SQLITE_DONE: return this._mayGet = false;
|
|
case capi.SQLITE_ROW: return this._mayGet = true;
|
|
default:
|
|
this._mayGet = false;
|
|
sqlite3.config.warn("sqlite3_step() rc=",rc,
|
|
capi.sqlite3_js_rc_str(rc),
|
|
"SQL =", capi.sqlite3_sql(this.pointer));
|
|
DB.checkRc(this.db.pointer, rc);
|
|
}
|
|
},
|
|
/**
|
|
Functions exactly like step() except that...
|
|
|
|
1) On success, it calls this.reset() and returns this object.
|
|
|
|
2) On error, it throws and does not call reset().
|
|
|
|
This is intended to simplify constructs like:
|
|
|
|
```
|
|
for(...) {
|
|
stmt.bind(...).stepReset();
|
|
}
|
|
```
|
|
|
|
Note that the reset() call makes it illegal to call this.get()
|
|
after the step.
|
|
*/
|
|
stepReset: function(){
|
|
this.step();
|
|
return this.reset();
|
|
},
|
|
/**
|
|
Functions like step() except that it calls finalize() on this
|
|
statement immediately after stepping, even if the step() call
|
|
throws.
|
|
|
|
On success, it returns true if the step indicated that a row of
|
|
data was available, else it returns a falsy value.
|
|
|
|
This is intended to simplify use cases such as:
|
|
|
|
```
|
|
aDb.prepare("insert into foo(a) values(?)").bind(123).stepFinalize();
|
|
```
|
|
*/
|
|
stepFinalize: function(){
|
|
try{
|
|
const rc = this.step();
|
|
this.reset(/*for INSERT...RETURNING locking case*/);
|
|
return rc;
|
|
}finally{
|
|
try{this.finalize()}
|
|
catch(e){/*ignored*/}
|
|
}
|
|
},
|
|
|
|
/**
|
|
Fetches the value from the given 0-based column index of
|
|
the current data row, throwing if index is out of range.
|
|
|
|
Requires that step() has just returned a truthy value, else
|
|
an exception is thrown.
|
|
|
|
By default it will determine the data type of the result
|
|
automatically. If passed a second argument, it must be one
|
|
of the enumeration values for sqlite3 types, which are
|
|
defined as members of the sqlite3 module: SQLITE_INTEGER,
|
|
SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value,
|
|
except for undefined, will trigger an exception. Passing
|
|
undefined is the same as not passing a value. It is legal
|
|
to, e.g., fetch an integer value as a string, in which case
|
|
sqlite3 will convert the value to a string.
|
|
|
|
If ndx is an array, this function behaves a differently: it
|
|
assigns the indexes of the array, from 0 to the number of
|
|
result columns, to the values of the corresponding column,
|
|
and returns that array.
|
|
|
|
If ndx is a plain object, this function behaves even
|
|
differentlier: it assigns the properties of the object to
|
|
the values of their corresponding result columns and returns
|
|
that object.
|
|
|
|
Blobs are returned as Uint8Array instances.
|
|
|
|
Potential TODO: add type ID SQLITE_JSON, which fetches the
|
|
result as a string and passes it (if it's not null) to
|
|
JSON.parse(), returning the result of that. Until then,
|
|
getJSON() can be used for that.
|
|
*/
|
|
get: function(ndx,asType){
|
|
if(!affirmStmtOpen(this)._mayGet){
|
|
toss3("Stmt.step() has not (recently) returned true.");
|
|
}
|
|
if(Array.isArray(ndx)){
|
|
let i = 0;
|
|
const n = this.columnCount;
|
|
while(i<n){
|
|
ndx[i] = this.get(i++);
|
|
}
|
|
return ndx;
|
|
}else if(ndx && 'object'===typeof ndx){
|
|
let i = 0;
|
|
const n = this.columnCount;
|
|
while(i<n){
|
|
ndx[capi.sqlite3_column_name(this.pointer,i)] = this.get(i++);
|
|
}
|
|
return ndx;
|
|
}
|
|
affirmColIndex(this, ndx);
|
|
switch(undefined===asType
|
|
? capi.sqlite3_column_type(this.pointer, ndx)
|
|
: asType){
|
|
case capi.SQLITE_NULL: return null;
|
|
case capi.SQLITE_INTEGER:{
|
|
if(wasm.bigIntEnabled){
|
|
const rc = capi.sqlite3_column_int64(this.pointer, ndx);
|
|
if(rc>=Number.MIN_SAFE_INTEGER && rc<=Number.MAX_SAFE_INTEGER){
|
|
/* Coerce "normal" number ranges to normal number values,
|
|
and only return BigInt-type values for numbers out of this
|
|
range. */
|
|
return Number(rc).valueOf();
|
|
}
|
|
return rc;
|
|
}else{
|
|
const rc = capi.sqlite3_column_double(this.pointer, ndx);
|
|
if(rc>Number.MAX_SAFE_INTEGER || rc<Number.MIN_SAFE_INTEGER){
|
|
/* Throwing here is arguable but, since we're explicitly
|
|
extracting an SQLITE_INTEGER-type value, it seems fair to throw
|
|
if the extracted number is out of range for that type.
|
|
This policy may be laxened to simply pass on the number and
|
|
hope for the best, as the C API would do. */
|
|
toss3("Integer is out of range for JS integer range: "+rc);
|
|
}
|
|
//sqlite3.config.log("get integer rc=",rc,isInt32(rc));
|
|
return util.isInt32(rc) ? (rc | 0) : rc;
|
|
}
|
|
}
|
|
case capi.SQLITE_FLOAT:
|
|
return capi.sqlite3_column_double(this.pointer, ndx);
|
|
case capi.SQLITE_TEXT:
|
|
return capi.sqlite3_column_text(this.pointer, ndx);
|
|
case capi.SQLITE_BLOB: {
|
|
const n = capi.sqlite3_column_bytes(this.pointer, ndx),
|
|
ptr = capi.sqlite3_column_blob(this.pointer, ndx),
|
|
rc = new Uint8Array(n);
|
|
//heap = n ? wasm.heap8() : false;
|
|
if(n) rc.set(wasm.heap8u().slice(ptr, ptr+n), 0);
|
|
//for(let i = 0; i < n; ++i) rc[i] = heap[ptr + i];
|
|
if(n && this.db._blobXfer instanceof Array){
|
|
/* This is an optimization soley for the
|
|
Worker-based API. These values will be
|
|
transfered to the main thread directly
|
|
instead of being copied. */
|
|
this.db._blobXfer.push(rc.buffer);
|
|
}
|
|
return rc;
|
|
}
|
|
default: toss3("Don't know how to translate",
|
|
"type of result column #"+ndx+".");
|
|
}
|
|
toss3("Not reached.");
|
|
},
|
|
/** Equivalent to get(ndx) but coerces the result to an
|
|
integer. */
|
|
getInt: function(ndx){return this.get(ndx,capi.SQLITE_INTEGER)},
|
|
/** Equivalent to get(ndx) but coerces the result to a
|
|
float. */
|
|
getFloat: function(ndx){return this.get(ndx,capi.SQLITE_FLOAT)},
|
|
/** Equivalent to get(ndx) but coerces the result to a
|
|
string. */
|
|
getString: function(ndx){return this.get(ndx,capi.SQLITE_TEXT)},
|
|
/** Equivalent to get(ndx) but coerces the result to a
|
|
Uint8Array. */
|
|
getBlob: function(ndx){return this.get(ndx,capi.SQLITE_BLOB)},
|
|
/**
|
|
A convenience wrapper around get() which fetches the value
|
|
as a string and then, if it is not null, passes it to
|
|
JSON.parse(), returning that result. Throws if parsing
|
|
fails. If the result is null, null is returned. An empty
|
|
string, on the other hand, will trigger an exception.
|
|
*/
|
|
getJSON: function(ndx){
|
|
const s = this.get(ndx, capi.SQLITE_STRING);
|
|
return null===s ? s : JSON.parse(s);
|
|
},
|
|
// Design note: the only reason most of these getters have a 'get'
|
|
// prefix is for consistency with getVALUE_TYPE(). The latter
|
|
// arguably really need that prefix for API readability and the
|
|
// rest arguably don't, but consistency is a powerful thing.
|
|
/**
|
|
Returns the result column name of the given index, or
|
|
throws if index is out of bounds or this statement has been
|
|
finalized. This can be used without having run step()
|
|
first.
|
|
*/
|
|
getColumnName: function(ndx){
|
|
return capi.sqlite3_column_name(
|
|
affirmColIndex(affirmStmtOpen(this),ndx).pointer, ndx
|
|
);
|
|
},
|
|
/**
|
|
If this statement potentially has result columns, this function
|
|
returns an array of all such names. If passed an array, it is
|
|
used as the target and all names are appended to it. Returns
|
|
the target array. Throws if this statement cannot have result
|
|
columns. This object's columnCount property holds the number of
|
|
columns.
|
|
*/
|
|
getColumnNames: function(tgt=[]){
|
|
affirmColIndex(affirmStmtOpen(this),0);
|
|
const n = this.columnCount;
|
|
for(let i = 0; i < n; ++i){
|
|
tgt.push(capi.sqlite3_column_name(this.pointer, i));
|
|
}
|
|
return tgt;
|
|
},
|
|
/**
|
|
If this statement has named bindable parameters and the
|
|
given name matches one, its 1-based bind index is
|
|
returned. If no match is found, 0 is returned. If it has no
|
|
bindable parameters, the undefined value is returned.
|
|
*/
|
|
getParamIndex: function(name){
|
|
return (affirmStmtOpen(this).parameterCount
|
|
? capi.sqlite3_bind_parameter_index(this.pointer, name)
|
|
: undefined);
|
|
},
|
|
/**
|
|
If this statement has named bindable parameters and the given
|
|
index refers to one, its name is returned, else null is
|
|
returned. If this statement has no bound parameters, undefined
|
|
is returned.
|
|
|
|
Added in 3.47.
|
|
*/
|
|
getParamName: function(ndx){
|
|
return (affirmStmtOpen(this).parameterCount
|
|
? capi.sqlite3_bind_parameter_name(this.pointer, ndx)
|
|
: undefined);
|
|
},
|
|
|
|
/**
|
|
Behaves like sqlite3_stmt_busy() but throws if this statement
|
|
is closed and returns a value of type boolean instead of integer.
|
|
|
|
Added in 3.47.
|
|
*/
|
|
isBusy: function(){
|
|
return 0!==capi.sqlite3_stmt_busy(affirmStmtOpen(this));
|
|
},
|
|
|
|
/**
|
|
Behaves like sqlite3_stmt_readonly() but throws if this statement
|
|
is closed and returns a value of type boolean instead of integer.
|
|
|
|
Added in 3.47.
|
|
*/
|
|
isReadOnly: function(){
|
|
return 0!==capi.sqlite3_stmt_readonly(affirmStmtOpen(this));
|
|
}
|
|
}/*Stmt.prototype*/;
|
|
|
|
{/* Add the `pointer` property to DB and Stmt. */
|
|
const prop = {
|
|
enumerable: true,
|
|
get: function(){return __ptrMap.get(this)},
|
|
set: ()=>toss3("The pointer property is read-only.")
|
|
}
|
|
Object.defineProperty(Stmt.prototype, 'pointer', prop);
|
|
Object.defineProperty(DB.prototype, 'pointer', prop);
|
|
}
|
|
/**
|
|
Stmt.columnCount is an interceptor for sqlite3_column_count().
|
|
|
|
This requires an unfortunate performance hit compared to caching
|
|
columnCount when the Stmt is created/prepared (as was done in
|
|
SQLite <=3.42.0), but is necessary in order to handle certain
|
|
corner cases, as described in
|
|
https://sqlite.org/forum/forumpost/7774b773937cbe0a.
|
|
*/
|
|
Object.defineProperty(Stmt.prototype, 'columnCount', {
|
|
enumerable: false,
|
|
get: function(){return capi.sqlite3_column_count(this.pointer)},
|
|
set: ()=>toss3("The columnCount property is read-only.")
|
|
});
|
|
|
|
/** The OO API's public namespace. */
|
|
sqlite3.oo1 = {
|
|
DB,
|
|
Stmt
|
|
}/*oo1 object*/;
|
|
|
|
if(util.isUIThread()){
|
|
/**
|
|
Functionally equivalent to DB(storageName,'c','kvvfs') except
|
|
that it throws if the given storage name is not one of 'local'
|
|
or 'session'.
|
|
|
|
As of version 3.46, the argument may optionally be an options
|
|
object in the form:
|
|
|
|
{
|
|
filename: 'session'|'local',
|
|
... etc. (all options supported by the DB ctor)
|
|
}
|
|
|
|
noting that the 'vfs' option supported by main DB
|
|
constructor is ignored here: the vfs is always 'kvvfs'.
|
|
*/
|
|
sqlite3.oo1.JsStorageDb = function(storageName='session'){
|
|
const opt = dbCtorHelper.normalizeArgs(...arguments);
|
|
storageName = opt.filename;
|
|
if('session'!==storageName && 'local'!==storageName){
|
|
toss3("JsStorageDb db name must be one of 'session' or 'local'.");
|
|
}
|
|
opt.vfs = 'kvvfs';
|
|
dbCtorHelper.call(this, opt);
|
|
};
|
|
const jdb = sqlite3.oo1.JsStorageDb;
|
|
jdb.prototype = Object.create(DB.prototype);
|
|
/** Equivalent to sqlite3_js_kvvfs_clear(). */
|
|
jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
|
|
/**
|
|
Clears this database instance's storage or throws if this
|
|
instance has been closed. Returns the number of
|
|
database blocks which were cleaned up.
|
|
*/
|
|
jdb.prototype.clearStorage = function(){
|
|
return jdb.clearStorage(affirmDbOpen(this).filename);
|
|
};
|
|
/** Equivalent to sqlite3_js_kvvfs_size(). */
|
|
jdb.storageSize = capi.sqlite3_js_kvvfs_size;
|
|
/**
|
|
Returns the _approximate_ number of bytes this database takes
|
|
up in its storage or throws if this instance has been closed.
|
|
*/
|
|
jdb.prototype.storageSize = function(){
|
|
return jdb.storageSize(affirmDbOpen(this).filename);
|
|
};
|
|
}/*main-window-only bits*/
|
|
|
|
});
|
|
//#else
|
|
/* Built with the omit-oo1 flag. */
|
|
//#endif ifnot omit-oo1
|