2022-08-10 14:26:08 +03:00
|
|
|
/*
|
|
|
|
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 glues together disparate pieces of JS which are loaded in
|
|
|
|
previous steps of the sqlite3-api.js bootstrapping process:
|
|
|
|
sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It
|
|
|
|
initializes the main API pieces so that the downstream components
|
|
|
|
(e.g. sqlite3-api-oo1.js) have all that they need.
|
|
|
|
*/
|
2022-08-22 16:34:13 +03:00
|
|
|
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
2022-08-10 14:26:08 +03:00
|
|
|
'use strict';
|
|
|
|
const toss = (...args)=>{throw new Error(args.join(' '))};
|
2022-10-03 01:50:04 +03:00
|
|
|
const toss3 = sqlite3.SQLite3Error.toss;
|
2022-10-29 10:54:10 +03:00
|
|
|
const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util;
|
|
|
|
self.WhWasmUtilInstaller(wasm);
|
2022-08-10 14:26:08 +03:00
|
|
|
delete self.WhWasmUtilInstaller;
|
|
|
|
|
2022-10-03 12:21:37 +03:00
|
|
|
/**
|
|
|
|
Install JS<->C struct bindings for the non-opaque struct types we
|
|
|
|
need... */
|
|
|
|
sqlite3.StructBinder = self.Jaccwabyt({
|
|
|
|
heap: 0 ? wasm.memory : wasm.heap8u,
|
|
|
|
alloc: wasm.alloc,
|
|
|
|
dealloc: wasm.dealloc,
|
|
|
|
functionTable: wasm.functionTable,
|
|
|
|
bigIntEnabled: wasm.bigIntEnabled,
|
|
|
|
memberPrefix: '$'
|
|
|
|
});
|
|
|
|
delete self.Jaccwabyt;
|
|
|
|
|
2022-08-10 14:26:08 +03:00
|
|
|
if(0){
|
2022-10-03 12:21:37 +03:00
|
|
|
/* "The problem" is that the following isn't even remotely
|
|
|
|
type-safe. OTOH, nothing about WASM pointers is. */
|
|
|
|
const argPointer = wasm.xWrap.argAdapter('*');
|
|
|
|
wasm.xWrap.argAdapter('StructType', (v)=>{
|
|
|
|
if(v && v.constructor && v instanceof StructBinder.StructType){
|
|
|
|
v = v.pointer;
|
2022-08-10 14:26:08 +03:00
|
|
|
}
|
2022-10-27 06:03:16 +03:00
|
|
|
return wasm.isPtr(v)
|
2022-10-03 12:21:37 +03:00
|
|
|
? argPointer(v)
|
|
|
|
: toss("Invalid (object) type for StructType-type argument.");
|
|
|
|
});
|
|
|
|
}
|
2022-08-10 14:26:08 +03:00
|
|
|
|
2022-09-30 23:35:37 +03:00
|
|
|
if(1){/* Convert Arrays and certain TypedArrays to strings for
|
|
|
|
'flexible-string'-type arguments */
|
|
|
|
const xString = wasm.xWrap.argAdapter('string');
|
|
|
|
wasm.xWrap.argAdapter(
|
2022-10-03 11:21:06 +03:00
|
|
|
'flexible-string', (v)=>xString(util.flexibleString(v))
|
2022-09-30 23:35:37 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(1){// WhWasmUtil.xWrap() bindings...
|
2022-08-10 14:26:08 +03:00
|
|
|
/**
|
2022-10-02 06:11:13 +03:00
|
|
|
Add some descriptive xWrap() aliases for '*' intended to (A)
|
|
|
|
initially improve readability/correctness of capi.signatures
|
|
|
|
and (B) eventually perhaps provide automatic conversion from
|
|
|
|
higher-level representations, e.g. capi.sqlite3_vfs to
|
|
|
|
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
|
2022-08-10 14:26:08 +03:00
|
|
|
*/
|
|
|
|
const aPtr = wasm.xWrap.argAdapter('*');
|
2022-10-03 01:50:04 +03:00
|
|
|
wasm.xWrap.argAdapter('sqlite3*', aPtr)
|
|
|
|
('sqlite3_stmt*', aPtr)
|
|
|
|
('sqlite3_context*', aPtr)
|
|
|
|
('sqlite3_value*', aPtr)
|
2022-10-20 08:14:37 +03:00
|
|
|
('sqlite3_vfs*', aPtr)
|
2022-10-03 01:50:04 +03:00
|
|
|
('void*', aPtr);
|
|
|
|
wasm.xWrap.resultAdapter('sqlite3*', aPtr)
|
|
|
|
('sqlite3_context*', aPtr)
|
2022-10-20 08:14:37 +03:00
|
|
|
('sqlite3_stmt*', aPtr)
|
|
|
|
('sqlite3_vfs*', aPtr)
|
2022-10-03 01:50:04 +03:00
|
|
|
('void*', aPtr);
|
2022-08-10 14:26:08 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
Populate api object with sqlite3_...() by binding the "raw" wasm
|
|
|
|
exports into type-converting proxies using wasm.xWrap().
|
|
|
|
*/
|
|
|
|
for(const e of wasm.bindingSignatures){
|
|
|
|
capi[e[0]] = wasm.xWrap.apply(null, e);
|
|
|
|
}
|
2022-09-11 19:59:40 +03:00
|
|
|
for(const e of wasm.bindingSignatures.wasm){
|
2022-10-29 10:54:10 +03:00
|
|
|
wasm[e[0]] = wasm.xWrap.apply(null, e);
|
2022-09-11 19:59:40 +03:00
|
|
|
}
|
2022-08-10 14:26:08 +03:00
|
|
|
|
2022-08-22 16:34:13 +03:00
|
|
|
/* For C API functions which cannot work properly unless
|
2022-08-10 14:26:08 +03:00
|
|
|
wasm.bigIntEnabled is true, install a bogus impl which
|
|
|
|
throws if called when bigIntEnabled is false. */
|
|
|
|
const fI64Disabled = function(fname){
|
|
|
|
return ()=>toss(fname+"() disabled due to lack",
|
|
|
|
"of BigInt support in this build.");
|
|
|
|
};
|
|
|
|
for(const e of wasm.bindingSignatures.int64){
|
|
|
|
capi[e[0]] = wasm.bigIntEnabled
|
|
|
|
? wasm.xWrap.apply(null, e)
|
|
|
|
: fI64Disabled(e[0]);
|
|
|
|
}
|
|
|
|
|
2022-10-28 14:40:46 +03:00
|
|
|
/* There's no(?) need to expose bindingSignatures to clients,
|
|
|
|
implicitly making it part of the public interface. */
|
|
|
|
delete wasm.bindingSignatures;
|
|
|
|
|
2022-08-10 14:26:08 +03:00
|
|
|
if(wasm.exports.sqlite3_wasm_db_error){
|
2022-10-29 10:54:10 +03:00
|
|
|
util.sqlite3_wasm_db_error = wasm.xWrap(
|
2022-08-10 14:26:08 +03:00
|
|
|
'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
|
|
|
|
);
|
|
|
|
}else{
|
|
|
|
util.sqlite3_wasm_db_error = function(pDb,errCode,msg){
|
|
|
|
console.warn("sqlite3_wasm_db_error() is not exported.",arguments);
|
|
|
|
return errCode;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
}/*xWrap() bindings*/;
|
|
|
|
|
2022-10-28 14:40:46 +03:00
|
|
|
/**
|
|
|
|
When registering a VFS and its related components it may be
|
|
|
|
necessary to ensure that JS keeps a reference to them to keep
|
|
|
|
them from getting garbage collected. Simply pass each such value
|
|
|
|
to this function and a reference will be held to it for the life
|
|
|
|
of the app.
|
|
|
|
*/
|
|
|
|
capi.sqlite3_vfs_register.addReference = function f(...args){
|
|
|
|
if(!f._) f._ = [];
|
|
|
|
f._.push(...args);
|
|
|
|
};
|
|
|
|
|
2022-08-10 14:26:08 +03:00
|
|
|
/**
|
2022-09-30 23:35:37 +03:00
|
|
|
Internal helper to assist in validating call argument counts in
|
|
|
|
the hand-written sqlite3_xyz() wrappers. We do this only for
|
|
|
|
consistency with non-special-case wrappings.
|
2022-08-10 14:26:08 +03:00
|
|
|
*/
|
2022-09-30 23:35:37 +03:00
|
|
|
const __dbArgcMismatch = (pDb,f,n)=>{
|
|
|
|
return sqlite3.util.sqlite3_wasm_db_error(pDb, capi.SQLITE_MISUSE,
|
|
|
|
f+"() requires "+n+" argument"+
|
|
|
|
(1===n?'':'s')+".");
|
|
|
|
};
|
|
|
|
|
2022-08-10 14:26:08 +03:00
|
|
|
/**
|
2022-09-30 23:35:37 +03:00
|
|
|
Helper for flexible-string conversions which require a
|
|
|
|
byte-length counterpart argument. Passed a value and its
|
|
|
|
ostensible length, this function returns [V,N], where V
|
|
|
|
is either v or a transformed copy of v and N is either n,
|
|
|
|
-1, or the byte length of v (if it's a byte array).
|
2022-08-10 14:26:08 +03:00
|
|
|
*/
|
2022-09-30 23:35:37 +03:00
|
|
|
const __flexiString = function(v,n){
|
|
|
|
if('string'===typeof v){
|
|
|
|
n = -1;
|
|
|
|
}else if(util.isSQLableTypedArray(v)){
|
|
|
|
n = v.byteLength;
|
|
|
|
v = util.typedArrayToString(v);
|
|
|
|
}else if(Array.isArray(v)){
|
|
|
|
v = v.join('');
|
|
|
|
n = -1;
|
2022-08-10 14:26:08 +03:00
|
|
|
}
|
2022-09-30 23:35:37 +03:00
|
|
|
return [v, n];
|
2022-08-10 14:26:08 +03:00
|
|
|
};
|
|
|
|
|
2022-09-30 23:35:37 +03:00
|
|
|
if(1){/* Special-case handling of sqlite3_exec() */
|
|
|
|
const __exec = wasm.xWrap("sqlite3_exec", "int",
|
|
|
|
["sqlite3*", "flexible-string", "*", "*", "**"]);
|
|
|
|
/* Documented in the api object's initializer. */
|
2022-10-02 23:08:53 +03:00
|
|
|
capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){
|
|
|
|
if(f.length!==arguments.length){
|
|
|
|
return __dbArgcMismatch(pDb,"sqlite3_exec",f.length);
|
2022-09-30 23:35:37 +03:00
|
|
|
}else if('function' !== typeof callback){
|
|
|
|
return __exec(pDb, sql, callback, pVoid, pErrMsg);
|
|
|
|
}
|
|
|
|
/* Wrap the callback in a WASM-bound function and convert the callback's
|
|
|
|
`(char**)` arguments to arrays of strings... */
|
|
|
|
const cbwrap = function(pVoid, nCols, pColVals, pColNames){
|
|
|
|
let rc = capi.SQLITE_ERROR;
|
|
|
|
try {
|
|
|
|
let aVals = [], aNames = [], i = 0, offset = 0;
|
|
|
|
for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){
|
|
|
|
aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) );
|
|
|
|
aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) );
|
|
|
|
}
|
|
|
|
rc = callback(pVoid, nCols, aVals, aNames) | 0;
|
|
|
|
/* The first 2 args of the callback are useless for JS but
|
|
|
|
we want the JS mapping of the C API to be as close to the
|
|
|
|
C API as possible. */
|
|
|
|
}catch(e){
|
|
|
|
/* If we set the db error state here, the higher-level exec() call
|
|
|
|
replaces it with its own, so we have no way of reporting the
|
|
|
|
exception message except the console. We must not propagate
|
|
|
|
exceptions through the C API. */
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
};
|
|
|
|
let pFunc, rc;
|
|
|
|
try{
|
|
|
|
pFunc = wasm.installFunction("ipipp", cbwrap);
|
|
|
|
rc = __exec(pDb, sql, pFunc, pVoid, pErrMsg);
|
|
|
|
}catch(e){
|
|
|
|
rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
|
|
|
|
"Error running exec(): "+e.message);
|
|
|
|
}finally{
|
|
|
|
if(pFunc) wasm.uninstallFunction(pFunc);
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
};
|
|
|
|
}/*sqlite3_exec() proxy*/;
|
|
|
|
|
2022-10-02 23:08:53 +03:00
|
|
|
if(1){/* Special-case handling of sqlite3_create_function_v2()
|
|
|
|
and sqlite3_create_window_function() */
|
2022-10-02 21:47:39 +03:00
|
|
|
const sqlite3CreateFunction = wasm.xWrap(
|
|
|
|
"sqlite3_create_function_v2", "int",
|
2022-10-02 23:08:53 +03:00
|
|
|
["sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
|
|
|
|
"int"/*eTextRep*/, "*"/*pApp*/,
|
|
|
|
"*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xDestroy*/]
|
|
|
|
);
|
|
|
|
const sqlite3CreateWindowFunction = wasm.xWrap(
|
|
|
|
"sqlite3_create_window_function", "int",
|
|
|
|
["sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
|
|
|
|
"int"/*eTextRep*/, "*"/*pApp*/,
|
|
|
|
"*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/,
|
|
|
|
"*"/*xInverse*/, "*"/*xDestroy*/]
|
2022-10-02 21:47:39 +03:00
|
|
|
);
|
2022-10-03 01:50:04 +03:00
|
|
|
|
2022-10-03 11:21:06 +03:00
|
|
|
const __udfSetResult = function(pCtx, val){
|
|
|
|
//console.warn("udfSetResult",typeof val, val);
|
2022-10-02 21:47:39 +03:00
|
|
|
switch(typeof val) {
|
2022-10-21 02:48:38 +03:00
|
|
|
case 'undefined':
|
|
|
|
/* Assume that the client already called sqlite3_result_xxx(). */
|
|
|
|
break;
|
2022-10-02 21:47:39 +03:00
|
|
|
case 'boolean':
|
2022-10-03 01:50:04 +03:00
|
|
|
capi.sqlite3_result_int(pCtx, val ? 1 : 0);
|
|
|
|
break;
|
|
|
|
case 'bigint':
|
|
|
|
if(wasm.bigIntEnabled){
|
|
|
|
if(util.bigIntFits64(val)) capi.sqlite3_result_int64(pCtx, val);
|
|
|
|
else toss3("BigInt value",val.toString(),"is too BigInt for int64.");
|
|
|
|
}else if(util.bigIntFits32(val)){
|
|
|
|
capi.sqlite3_result_int(pCtx, Number(val));
|
|
|
|
}else if(util.bigIntFitsDouble(val)){
|
|
|
|
capi.sqlite3_result_double(pCtx, Number(val));
|
|
|
|
}else{
|
|
|
|
toss3("BigInt value",val.toString(),"is too BigInt.");
|
|
|
|
}
|
2022-10-02 21:47:39 +03:00
|
|
|
break;
|
|
|
|
case 'number': {
|
|
|
|
(util.isInt32(val)
|
|
|
|
? capi.sqlite3_result_int
|
2022-10-03 01:50:04 +03:00
|
|
|
: capi.sqlite3_result_double)(pCtx, val);
|
2022-10-02 21:47:39 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 'string':
|
2022-10-03 01:50:04 +03:00
|
|
|
capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT);
|
2022-10-02 21:47:39 +03:00
|
|
|
break;
|
|
|
|
case 'object':
|
2022-10-02 23:08:53 +03:00
|
|
|
if(null===val/*yes, typeof null === 'object'*/) {
|
2022-10-03 01:50:04 +03:00
|
|
|
capi.sqlite3_result_null(pCtx);
|
2022-10-02 21:47:39 +03:00
|
|
|
break;
|
|
|
|
}else if(util.isBindableTypedArray(val)){
|
|
|
|
const pBlob = wasm.allocFromTypedArray(val);
|
2022-10-02 23:08:53 +03:00
|
|
|
capi.sqlite3_result_blob(
|
2022-10-03 01:50:04 +03:00
|
|
|
pCtx, pBlob, val.byteLength,
|
2022-10-02 23:08:53 +03:00
|
|
|
wasm.exports[sqlite3.config.deallocExportName]
|
|
|
|
);
|
2022-10-02 21:47:39 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
// else fall through
|
|
|
|
default:
|
2022-10-03 01:50:04 +03:00
|
|
|
toss3("Don't not how to handle this UDF result value:",(typeof val), val);
|
2022-10-02 21:47:39 +03:00
|
|
|
};
|
2022-10-03 11:21:06 +03:00
|
|
|
}/*__udfSetResult()*/;
|
2022-10-03 01:50:04 +03:00
|
|
|
|
2022-10-03 11:21:06 +03:00
|
|
|
const __udfConvertArgs = function(argc, pArgv){
|
2022-10-02 21:47:39 +03:00
|
|
|
let i, pVal, valType, arg;
|
|
|
|
const tgt = [];
|
|
|
|
for(i = 0; i < argc; ++i){
|
|
|
|
pVal = wasm.getPtrValue(pArgv + (wasm.ptrSizeof * i));
|
|
|
|
/**
|
|
|
|
Curiously: despite ostensibly requiring 8-byte
|
|
|
|
alignment, the pArgv array is parcelled into chunks of
|
|
|
|
4 bytes (1 pointer each). The values those point to
|
|
|
|
have 8-byte alignment but the individual argv entries
|
|
|
|
do not.
|
|
|
|
*/
|
|
|
|
valType = capi.sqlite3_value_type(pVal);
|
|
|
|
switch(valType){
|
|
|
|
case capi.SQLITE_INTEGER:
|
2022-10-03 01:50:04 +03:00
|
|
|
if(wasm.bigIntEnabled){
|
|
|
|
arg = capi.sqlite3_value_int64(pVal);
|
|
|
|
if(util.bigIntFitsDouble(arg)) arg = Number(arg);
|
|
|
|
}
|
|
|
|
else arg = capi.sqlite3_value_double(pVal)/*yes, double, for larger integers*/;
|
|
|
|
break;
|
2022-10-02 21:47:39 +03:00
|
|
|
case capi.SQLITE_FLOAT:
|
|
|
|
arg = capi.sqlite3_value_double(pVal);
|
|
|
|
break;
|
|
|
|
case capi.SQLITE_TEXT:
|
|
|
|
arg = capi.sqlite3_value_text(pVal);
|
|
|
|
break;
|
|
|
|
case capi.SQLITE_BLOB:{
|
|
|
|
const n = capi.sqlite3_value_bytes(pVal);
|
|
|
|
const pBlob = capi.sqlite3_value_blob(pVal);
|
2022-10-03 01:50:04 +03:00
|
|
|
if(n && !pBlob) sqlite3.WasmAllocError.toss(
|
|
|
|
"Cannot allocate memory for blob argument of",n,"byte(s)"
|
|
|
|
);
|
|
|
|
arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null;
|
2022-10-02 21:47:39 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case capi.SQLITE_NULL:
|
|
|
|
arg = null; break;
|
|
|
|
default:
|
|
|
|
toss3("Unhandled sqlite3_value_type()",valType,
|
|
|
|
"is possibly indicative of incorrect",
|
|
|
|
"pointer size assumption.");
|
|
|
|
}
|
|
|
|
tgt.push(arg);
|
|
|
|
}
|
|
|
|
return tgt;
|
2022-10-03 11:21:06 +03:00
|
|
|
}/*__udfConvertArgs()*/;
|
2022-10-02 21:47:39 +03:00
|
|
|
|
2022-10-03 11:21:06 +03:00
|
|
|
const __udfSetError = (pCtx, e)=>{
|
2022-10-03 01:50:04 +03:00
|
|
|
if(e instanceof sqlite3.WasmAllocError){
|
|
|
|
capi.sqlite3_result_error_nomem(pCtx);
|
2022-10-02 21:47:39 +03:00
|
|
|
}else{
|
2022-10-21 02:48:38 +03:00
|
|
|
const msg = ('string'===typeof e) ? e : e.message;
|
|
|
|
capi.sqlite3_result_error(pCtx, msg, -1);
|
2022-10-02 21:47:39 +03:00
|
|
|
}
|
|
|
|
};
|
2022-10-02 23:08:53 +03:00
|
|
|
|
2022-10-02 21:47:39 +03:00
|
|
|
const __xFunc = function(callback){
|
2022-10-03 01:50:04 +03:00
|
|
|
return function(pCtx, argc, pArgv){
|
2022-10-03 11:21:06 +03:00
|
|
|
try{ __udfSetResult(pCtx, callback(pCtx, ...__udfConvertArgs(argc, pArgv))) }
|
2022-10-03 01:50:04 +03:00
|
|
|
catch(e){
|
|
|
|
//console.error('xFunc() caught:',e);
|
2022-10-03 11:21:06 +03:00
|
|
|
__udfSetError(pCtx, e);
|
2022-10-03 01:50:04 +03:00
|
|
|
}
|
2022-10-02 21:47:39 +03:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-10-02 23:08:53 +03:00
|
|
|
const __xInverseAndStep = function(callback){
|
2022-10-03 01:50:04 +03:00
|
|
|
return function(pCtx, argc, pArgv){
|
2022-10-03 11:21:06 +03:00
|
|
|
try{ callback(pCtx, ...__udfConvertArgs(argc, pArgv)) }
|
|
|
|
catch(e){ __udfSetError(pCtx, e) }
|
2022-10-02 21:47:39 +03:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-10-02 23:08:53 +03:00
|
|
|
const __xFinalAndValue = function(callback){
|
2022-10-03 01:50:04 +03:00
|
|
|
return function(pCtx){
|
2022-10-03 11:21:06 +03:00
|
|
|
try{ __udfSetResult(pCtx, callback(pCtx)) }
|
|
|
|
catch(e){ __udfSetError(pCtx, e) }
|
2022-10-02 21:47:39 +03:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const __xDestroy = function(callback){
|
|
|
|
return function(pVoid){
|
|
|
|
try{ callback(pVoid) }
|
2022-10-02 23:08:53 +03:00
|
|
|
catch(e){ console.error("UDF xDestroy method threw:",e) }
|
2022-10-02 21:47:39 +03:00
|
|
|
};
|
|
|
|
};
|
2022-10-02 23:08:53 +03:00
|
|
|
|
|
|
|
const __xMap = Object.assign(Object.create(null), {
|
|
|
|
xFunc: {sig:'v(pip)', f:__xFunc},
|
|
|
|
xStep: {sig:'v(pip)', f:__xInverseAndStep},
|
|
|
|
xInverse: {sig:'v(pip)', f:__xInverseAndStep},
|
|
|
|
xFinal: {sig:'v(p)', f:__xFinalAndValue},
|
|
|
|
xValue: {sig:'v(p)', f:__xFinalAndValue},
|
|
|
|
xDestroy: {sig:'v(p)', f:__xDestroy}
|
|
|
|
});
|
|
|
|
|
|
|
|
const __xWrapFuncs = function(theFuncs, tgtUninst){
|
|
|
|
const rc = []
|
|
|
|
let k;
|
|
|
|
for(k in theFuncs){
|
|
|
|
let fArg = theFuncs[k];
|
|
|
|
if('function'===typeof fArg){
|
|
|
|
const w = __xMap[k];
|
|
|
|
fArg = wasm.installFunction(w.sig, w.f(fArg));
|
|
|
|
tgtUninst.push(fArg);
|
|
|
|
}
|
|
|
|
rc.push(fArg);
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
};
|
|
|
|
|
2022-10-02 21:47:39 +03:00
|
|
|
/* Documented in the api object's initializer. */
|
|
|
|
capi.sqlite3_create_function_v2 = function f(
|
|
|
|
pDb, funcName, nArg, eTextRep, pApp,
|
|
|
|
xFunc, //void (*xFunc)(sqlite3_context*,int,sqlite3_value**)
|
|
|
|
xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
|
|
|
|
xFinal, //void (*xFinal)(sqlite3_context*)
|
|
|
|
xDestroy //void (*xDestroy)(void*)
|
|
|
|
){
|
2022-10-02 23:08:53 +03:00
|
|
|
if(f.length!==arguments.length){
|
|
|
|
return __dbArgcMismatch(pDb,"sqlite3_create_function_v2",f.length);
|
2022-10-02 21:47:39 +03:00
|
|
|
}
|
|
|
|
/* Wrap the callbacks in a WASM-bound functions... */
|
2022-10-02 23:08:53 +03:00
|
|
|
const uninstall = [/*funcs to uninstall on error*/];
|
2022-10-02 21:47:39 +03:00
|
|
|
let rc;
|
|
|
|
try{
|
2022-10-02 23:08:53 +03:00
|
|
|
const funcArgs = __xWrapFuncs({xFunc, xStep, xFinal, xDestroy},
|
|
|
|
uninstall);
|
2022-10-02 21:47:39 +03:00
|
|
|
rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep,
|
|
|
|
pApp, ...funcArgs);
|
|
|
|
}catch(e){
|
|
|
|
console.error("sqlite3_create_function_v2() setup threw:",e);
|
|
|
|
for(let v of uninstall){
|
|
|
|
wasm.uninstallFunction(v);
|
|
|
|
}
|
|
|
|
rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
|
|
|
|
"Creation of UDF threw: "+e.message);
|
|
|
|
}
|
|
|
|
return rc;
|
|
|
|
};
|
|
|
|
|
2022-10-02 23:08:53 +03:00
|
|
|
capi.sqlite3_create_function = function f(
|
2022-10-02 21:47:39 +03:00
|
|
|
pDb, funcName, nArg, eTextRep, pApp,
|
|
|
|
xFunc, xStep, xFinal
|
|
|
|
){
|
2022-10-02 23:08:53 +03:00
|
|
|
return (f.length===arguments.length)
|
|
|
|
? capi.sqlite3_create_function_v2(pDb, funcName, nArg, eTextRep,
|
|
|
|
pApp, xFunc, xStep, xFinal, 0)
|
|
|
|
: __dbArgcMismatch(pDb,"sqlite3_create_function",f.length);
|
|
|
|
};
|
2022-10-02 21:47:39 +03:00
|
|
|
|
2022-10-02 23:08:53 +03:00
|
|
|
/* Documented in the api object's initializer. */
|
|
|
|
capi.sqlite3_create_window_function = function f(
|
|
|
|
pDb, funcName, nArg, eTextRep, pApp,
|
|
|
|
xStep, //void (*xStep)(sqlite3_context*,int,sqlite3_value**)
|
|
|
|
xFinal, //void (*xFinal)(sqlite3_context*)
|
|
|
|
xValue, //void (*xFinal)(sqlite3_context*)
|
|
|
|
xInverse,//void (*xStep)(sqlite3_context*,int,sqlite3_value**)
|
|
|
|
xDestroy //void (*xDestroy)(void*)
|
|
|
|
){
|
|
|
|
if(f.length!==arguments.length){
|
|
|
|
return __dbArgcMismatch(pDb,"sqlite3_create_window_function",f.length);
|
|
|
|
}
|
|
|
|
/* Wrap the callbacks in a WASM-bound functions... */
|
|
|
|
const uninstall = [/*funcs to uninstall on error*/];
|
|
|
|
let rc;
|
|
|
|
try{
|
|
|
|
const funcArgs = __xWrapFuncs({xStep, xFinal, xValue, xInverse, xDestroy},
|
|
|
|
uninstall);
|
2022-10-21 08:27:40 +03:00
|
|
|
rc = sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep,
|
|
|
|
pApp, ...funcArgs);
|
2022-10-02 23:08:53 +03:00
|
|
|
}catch(e){
|
2022-10-21 08:27:40 +03:00
|
|
|
console.error("sqlite3_create_window_function() setup threw:",e);
|
2022-10-02 23:08:53 +03:00
|
|
|
for(let v of uninstall){
|
|
|
|
wasm.uninstallFunction(v);
|
|
|
|
}
|
|
|
|
rc = util.sqlite3_wasm_db_error(pDb, capi.SQLITE_ERROR,
|
|
|
|
"Creation of UDF threw: "+e.message);
|
|
|
|
}
|
|
|
|
return rc;
|
2022-10-02 21:47:39 +03:00
|
|
|
};
|
2022-10-03 01:50:04 +03:00
|
|
|
/**
|
|
|
|
A helper for UDFs implemented in JS and bound to WASM by the
|
2022-10-03 11:21:06 +03:00
|
|
|
client. Given a JS value, udfSetResult(pCtx,X) calls one of the
|
2022-10-03 01:50:04 +03:00
|
|
|
sqlite3_result_xyz(pCtx,...) routines, depending on X's data
|
|
|
|
type:
|
|
|
|
|
|
|
|
- `null`: sqlite3_result_null()
|
|
|
|
- `boolean`: sqlite3_result_int()
|
2022-10-04 01:38:00 +03:00
|
|
|
- `number`: sqlite3_result_int() or sqlite3_result_double()
|
2022-10-03 01:50:04 +03:00
|
|
|
- `string`: sqlite3_result_text()
|
|
|
|
- Uint8Array or Int8Array: sqlite3_result_blob()
|
2022-10-21 02:48:38 +03:00
|
|
|
- `undefined`: indicates that the UDF called one of the
|
|
|
|
`sqlite3_result_xyz()` routines on its own, making this
|
|
|
|
function a no-op. Results are _undefined_ if this function is
|
|
|
|
passed the `undefined` value but did _not_ call one of the
|
|
|
|
`sqlite3_result_xyz()` routines.
|
2022-10-03 01:50:04 +03:00
|
|
|
|
|
|
|
Anything else triggers sqlite3_result_error().
|
|
|
|
*/
|
2022-10-03 11:21:06 +03:00
|
|
|
capi.sqlite3_create_function_v2.udfSetResult =
|
|
|
|
capi.sqlite3_create_function.udfSetResult =
|
|
|
|
capi.sqlite3_create_window_function.udfSetResult = __udfSetResult;
|
2022-10-03 01:50:04 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
A helper for UDFs implemented in JS and bound to WASM by the
|
|
|
|
client. When passed the
|
|
|
|
(argc,argv) values from the UDF-related functions which receive
|
|
|
|
them (xFunc, xStep, xInverse), it creates a JS array
|
|
|
|
representing those arguments, converting each to JS in a manner
|
|
|
|
appropriate to its data type: numeric, text, blob
|
2022-10-04 01:38:00 +03:00
|
|
|
(Uint8Array), or null.
|
2022-10-03 01:50:04 +03:00
|
|
|
|
|
|
|
Results are undefined if it's passed anything other than those
|
|
|
|
two arguments from those specific contexts.
|
|
|
|
|
|
|
|
Thus an argc of 4 will result in a length-4 array containing
|
|
|
|
the converted values from the corresponding argv.
|
|
|
|
|
|
|
|
The conversion will throw only on allocation error or an internal
|
|
|
|
error.
|
|
|
|
*/
|
2022-10-03 11:21:06 +03:00
|
|
|
capi.sqlite3_create_function_v2.udfConvertArgs =
|
|
|
|
capi.sqlite3_create_function.udfConvertArgs =
|
|
|
|
capi.sqlite3_create_window_function.udfConvertArgs = __udfConvertArgs;
|
2022-10-03 01:50:04 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
A helper for UDFs implemented in JS and bound to WASM by the
|
|
|
|
client. It expects to be a passed `(sqlite3_context*, Error)`
|
2022-10-21 02:48:38 +03:00
|
|
|
(an exception object or message string). And it sets the
|
|
|
|
current UDF's result to sqlite3_result_error_nomem() or
|
|
|
|
sqlite3_result_error(), depending on whether the 2nd argument
|
|
|
|
is a sqlite3.WasmAllocError object or not.
|
2022-10-03 01:50:04 +03:00
|
|
|
*/
|
2022-10-03 11:21:06 +03:00
|
|
|
capi.sqlite3_create_function_v2.udfSetError =
|
|
|
|
capi.sqlite3_create_function.udfSetError =
|
|
|
|
capi.sqlite3_create_window_function.udfSetError = __udfSetError;
|
2022-10-02 23:08:53 +03:00
|
|
|
|
|
|
|
}/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/;
|
2022-10-02 21:47:39 +03:00
|
|
|
|
2022-09-30 23:35:37 +03:00
|
|
|
if(1){/* Special-case handling of sqlite3_prepare_v2() and
|
2022-10-02 23:08:53 +03:00
|
|
|
sqlite3_prepare_v3() */
|
2022-09-30 23:35:37 +03:00
|
|
|
/**
|
|
|
|
Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
|
|
|
|
*/
|
|
|
|
const __prepare = Object.create(null);
|
|
|
|
/**
|
|
|
|
This binding expects a JS string as its 2nd argument and
|
|
|
|
null as its final argument. In order to compile multiple
|
|
|
|
statements from a single string, the "full" impl (see
|
|
|
|
below) must be used.
|
|
|
|
*/
|
|
|
|
__prepare.basic = wasm.xWrap('sqlite3_prepare_v3',
|
|
|
|
"int", ["sqlite3*", "string",
|
|
|
|
"int"/*ignored for this impl!*/,
|
|
|
|
"int", "**",
|
|
|
|
"**"/*MUST be 0 or null or undefined!*/]);
|
|
|
|
/**
|
|
|
|
Impl which requires that the 2nd argument be a pointer
|
|
|
|
to the SQL string, instead of being converted to a
|
|
|
|
string. This variant is necessary for cases where we
|
|
|
|
require a non-NULL value for the final argument
|
|
|
|
(exec()'ing multiple statements from one input
|
|
|
|
string). For simpler cases, where only the first
|
|
|
|
statement in the SQL string is required, the wrapper
|
|
|
|
named sqlite3_prepare_v2() is sufficient and easier to
|
|
|
|
use because it doesn't require dealing with pointers.
|
|
|
|
*/
|
|
|
|
__prepare.full = wasm.xWrap('sqlite3_prepare_v3',
|
|
|
|
"int", ["sqlite3*", "*", "int", "int",
|
|
|
|
"**", "**"]);
|
|
|
|
|
|
|
|
/* Documented in the api object's initializer. */
|
|
|
|
capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
|
2022-10-02 23:08:53 +03:00
|
|
|
if(f.length!==arguments.length){
|
|
|
|
return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length);
|
2022-09-30 23:35:37 +03:00
|
|
|
}
|
|
|
|
const [xSql, xSqlLen] = __flexiString(sql, sqlLen);
|
|
|
|
switch(typeof xSql){
|
|
|
|
case 'string': return __prepare.basic(pDb, xSql, xSqlLen, prepFlags, ppStmt, null);
|
|
|
|
case 'number': return __prepare.full(pDb, xSql, xSqlLen, prepFlags, ppStmt, pzTail);
|
|
|
|
default:
|
|
|
|
return util.sqlite3_wasm_db_error(
|
|
|
|
pDb, capi.SQLITE_MISUSE,
|
|
|
|
"Invalid SQL argument type for sqlite3_prepare_v2/v3()."
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Documented in the api object's initializer. */
|
2022-10-02 23:08:53 +03:00
|
|
|
capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){
|
|
|
|
return (f.length===arguments.length)
|
2022-09-30 23:35:37 +03:00
|
|
|
? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
|
2022-10-02 23:08:53 +03:00
|
|
|
: __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length);
|
2022-09-30 23:35:37 +03:00
|
|
|
};
|
|
|
|
}/*sqlite3_prepare_v2/v3()*/;
|
2022-08-10 14:26:08 +03:00
|
|
|
|
|
|
|
{/* Import C-level constants and structs... */
|
|
|
|
const cJson = wasm.xCall('sqlite3_wasm_enum_json');
|
|
|
|
if(!cJson){
|
|
|
|
toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
|
|
|
|
"static buffer size!");
|
|
|
|
}
|
|
|
|
wasm.ctype = JSON.parse(wasm.cstringToJs(cJson));
|
|
|
|
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
|
|
|
|
for(const t of ['access', 'blobFinalizers', 'dataTypes',
|
2022-09-13 22:27:03 +03:00
|
|
|
'encodings', 'fcntl', 'flock', 'ioCap',
|
2022-08-10 14:26:08 +03:00
|
|
|
'openFlags', 'prepareFlags', 'resultCodes',
|
2022-10-03 16:03:41 +03:00
|
|
|
'serialize', 'syncFlags', 'trace', 'udfFlags',
|
2022-09-30 13:55:28 +03:00
|
|
|
'version'
|
2022-08-10 14:26:08 +03:00
|
|
|
]){
|
2022-09-21 23:24:12 +03:00
|
|
|
for(const e of Object.entries(wasm.ctype[t])){
|
|
|
|
// ^^^ [k,v] there triggers a buggy code transormation via one
|
|
|
|
// of the Emscripten-driven optimizers.
|
|
|
|
capi[e[0]] = e[1];
|
2022-08-10 14:26:08 +03:00
|
|
|
}
|
|
|
|
}
|
2022-09-26 14:38:58 +03:00
|
|
|
const __rcMap = Object.create(null);
|
|
|
|
for(const t of ['resultCodes']){
|
|
|
|
for(const e of Object.entries(wasm.ctype[t])){
|
|
|
|
__rcMap[e[1]] = e[0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
For the given integer, returns the SQLITE_xxx result code as a
|
|
|
|
string, or undefined if no such mapping is found.
|
|
|
|
*/
|
2022-10-20 21:31:32 +03:00
|
|
|
capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc];
|
2022-08-10 14:26:08 +03:00
|
|
|
/* Bind all registered C-side structs... */
|
2022-10-13 19:48:35 +03:00
|
|
|
const notThese = Object.assign(Object.create(null),{
|
|
|
|
// Structs NOT to register
|
|
|
|
WasmTestStruct: true
|
|
|
|
});
|
2022-10-21 08:27:40 +03:00
|
|
|
if(!util.isUIThread()){
|
|
|
|
/* We remove the kvvfs VFS from Worker threads below. */
|
|
|
|
notThese.sqlite3_kvvfs_methods = true;
|
|
|
|
}
|
2022-08-10 14:26:08 +03:00
|
|
|
for(const s of wasm.ctype.structs){
|
2022-10-13 19:48:35 +03:00
|
|
|
if(!notThese[s.name]){
|
|
|
|
capi[s.name] = sqlite3.StructBinder(s);
|
|
|
|
}
|
2022-08-10 14:26:08 +03:00
|
|
|
}
|
2022-09-26 14:38:58 +03:00
|
|
|
}/*end C constant imports*/
|
2022-09-30 19:49:03 +03:00
|
|
|
|
2022-10-21 08:27:40 +03:00
|
|
|
const pKvvfs = capi.sqlite3_vfs_find("kvvfs");
|
|
|
|
if( pKvvfs ){/* kvvfs-specific glue */
|
|
|
|
if(util.isUIThread()){
|
|
|
|
const kvvfsMethods = new capi.sqlite3_kvvfs_methods(
|
|
|
|
wasm.exports.sqlite3_wasm_kvvfs_methods()
|
|
|
|
);
|
|
|
|
delete capi.sqlite3_kvvfs_methods;
|
|
|
|
|
|
|
|
const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
|
|
|
|
pstack = wasm.pstack,
|
|
|
|
pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc;
|
|
|
|
|
|
|
|
const kvvfsStorage = (zClass)=>
|
|
|
|
((115/*=='s'*/===wasm.getMemValue(zClass))
|
|
|
|
? sessionStorage : localStorage);
|
|
|
|
|
|
|
|
const kvvfsImpls = {
|
|
|
|
xRead: (zClass, zKey, zBuf, nBuf)=>{
|
|
|
|
const stack = pstack.pointer,
|
|
|
|
astack = wasm.scopedAllocPush();
|
|
|
|
try {
|
|
|
|
const zXKey = kvvfsMakeKey(zClass,zKey);
|
|
|
|
if(!zXKey) return -3/*OOM*/;
|
|
|
|
const jKey = wasm.cstringToJs(zXKey);
|
|
|
|
const jV = kvvfsStorage(zClass).getItem(jKey);
|
|
|
|
if(!jV) return -1;
|
|
|
|
const nV = jV.length /* Note that we are relying 100% on v being
|
|
|
|
ASCII so that jV.length is equal to the
|
|
|
|
C-string's byte length. */;
|
|
|
|
if(nBuf<=0) return nV;
|
|
|
|
else if(1===nBuf){
|
|
|
|
wasm.setMemValue(zBuf, 0);
|
|
|
|
return nV;
|
|
|
|
}
|
|
|
|
const zV = wasm.scopedAllocCString(jV);
|
|
|
|
if(nBuf > nV + 1) nBuf = nV + 1;
|
|
|
|
wasm.heap8u().copyWithin(zBuf, zV, zV + nBuf - 1);
|
|
|
|
wasm.setMemValue(zBuf + nBuf - 1, 0);
|
|
|
|
return nBuf - 1;
|
|
|
|
}catch(e){
|
|
|
|
console.error("kvstorageRead()",e);
|
|
|
|
return -2;
|
|
|
|
}finally{
|
|
|
|
pstack.restore(stack);
|
|
|
|
wasm.scopedAllocPop(astack);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
xWrite: (zClass, zKey, zData)=>{
|
|
|
|
const stack = pstack.pointer;
|
|
|
|
try {
|
|
|
|
const zXKey = kvvfsMakeKey(zClass,zKey);
|
|
|
|
if(!zXKey) return 1/*OOM*/;
|
|
|
|
const jKey = wasm.cstringToJs(zXKey);
|
|
|
|
kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData));
|
|
|
|
return 0;
|
|
|
|
}catch(e){
|
|
|
|
console.error("kvstorageWrite()",e);
|
|
|
|
return capi.SQLITE_IOERR;
|
|
|
|
}finally{
|
|
|
|
pstack.restore(stack);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
xDelete: (zClass, zKey)=>{
|
|
|
|
const stack = pstack.pointer;
|
|
|
|
try {
|
|
|
|
const zXKey = kvvfsMakeKey(zClass,zKey);
|
|
|
|
if(!zXKey) return 1/*OOM*/;
|
|
|
|
kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey));
|
|
|
|
return 0;
|
|
|
|
}catch(e){
|
|
|
|
console.error("kvstorageDelete()",e);
|
|
|
|
return capi.SQLITE_IOERR;
|
|
|
|
}finally{
|
|
|
|
pstack.restore(stack);
|
2022-10-09 16:26:15 +03:00
|
|
|
}
|
|
|
|
}
|
2022-10-21 08:27:40 +03:00
|
|
|
}/*kvvfsImpls*/;
|
|
|
|
for(let k of Object.keys(kvvfsImpls)){
|
|
|
|
kvvfsMethods[kvvfsMethods.memberKey(k)] =
|
|
|
|
wasm.installFunction(
|
|
|
|
kvvfsMethods.memberSignature(k),
|
|
|
|
kvvfsImpls[k]
|
|
|
|
);
|
2022-10-09 16:26:15 +03:00
|
|
|
}
|
2022-10-21 08:27:40 +03:00
|
|
|
}else{
|
|
|
|
/* Worker thread: unregister kvvfs to avoid it being used
|
|
|
|
for anything other than local/sessionStorage. It "can"
|
|
|
|
be used that way but it's not really intended to be. */
|
|
|
|
capi.sqlite3_vfs_unregister(pKvvfs);
|
2022-10-09 16:26:15 +03:00
|
|
|
}
|
2022-10-21 08:27:40 +03:00
|
|
|
}/*pKvvfs*/
|
2022-10-09 16:26:15 +03:00
|
|
|
|
2022-08-22 16:34:13 +03:00
|
|
|
});
|