aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/api
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api')
-rw-r--r--ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api1
-rw-r--r--ext/wasm/api/sqlite3-api-glue.js545
-rw-r--r--ext/wasm/api/sqlite3-api-oo1.js142
-rw-r--r--ext/wasm/api/sqlite3-api-prologue.js139
-rw-r--r--ext/wasm/api/sqlite3-wasm.c17
5 files changed, 510 insertions, 334 deletions
diff --git a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
index da4cc5bd2..a6559cbb3 100644
--- a/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
+++ b/ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
@@ -32,6 +32,7 @@ _sqlite3_column_value
_sqlite3_compileoption_get
_sqlite3_compileoption_used
_sqlite3_complete
+_sqlite3_context_db_handle
_sqlite3_create_collation
_sqlite3_create_collation_v2
_sqlite3_create_function
diff --git a/ext/wasm/api/sqlite3-api-glue.js b/ext/wasm/api/sqlite3-api-glue.js
index 25201f580..fc5e2823d 100644
--- a/ext/wasm/api/sqlite3-api-glue.js
+++ b/ext/wasm/api/sqlite3-api-glue.js
@@ -24,6 +24,33 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
self.WhWasmUtilInstaller(wasm);
delete self.WhWasmUtilInstaller;
+ {
+ /**
+ Find a mapping for SQLITE_WASM_DEALLOC, which the API
+ guarantees is a WASM pointer to the same underlying function as
+ wasm.dealloc() (noting that wasm.dealloc() is permitted to be a
+ JS wrapper around the WASM function). There is unfortunately no
+ O(1) algorithm for finding this pointer: we have to walk the
+ WASM indirect function table to find it. However, experience
+ indicates that that particular function is always very close to
+ the front of the table (it's been entry #3 in all relevant
+ tests).
+ */
+ const dealloc = wasm.exports[sqlite3.config.deallocExportName];
+ const nFunc = wasm.functionTable().length;
+ let i;
+ for(i = 0; i < nFunc; ++i){
+ const e = wasm.functionEntry(i);
+ if(dealloc === e){
+ capi.SQLITE_WASM_DEALLOC = i;
+ break;
+ }
+ }
+ if(dealloc !== wasm.functionEntry(capi.SQLITE_WASM_DEALLOC)){
+ toss("Internal error: cannot find function pointer for SQLITE_WASM_DEALLOC.");
+ }
+ }
+
/**
Signatures for the WASM-exported C-side functions. Each entry
is an array with 2+ elements:
@@ -42,10 +69,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
wasm.bindingSignatures = [
// Please keep these sorted by function name!
["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"],
- ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"
- /* TODO: we should arguably write a custom wrapper which knows
- how to handle Blob, TypedArrays, and JS strings. */
- ],
+ /* sqlite3_bind_blob() and sqlite3_bind_text() have hand-written
+ bindings to permit more flexible inputs. */
["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"],
["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"],
["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
@@ -53,19 +78,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
["sqlite3_bind_pointer", "int",
"sqlite3_stmt*", "int", "*", "string:static", "*"],
- ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "*"
- /* We should arguably create a hand-written binding of
- bind_text() which does more flexible text conversion, along
- the lines of sqlite3_prepare_v3(). The slightly problematic
- part is the final argument (text destructor). */
- ],
["sqlite3_busy_handler","int", [
"sqlite3*",
new wasm.xWrap.FuncPtrAdapter({
- name: 'sqlite3_busy_handler',
signature: 'i(pi)',
- bindScope: 'context',
- contextKey: (argIndex,argv)=>'sqlite3@'+argv[0]
+ contextKey: (argv,argIndex)=>argv[0/* sqlite3* */]
}),
"*"
]],
@@ -86,6 +103,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_compileoption_get", "string", "int"],
["sqlite3_compileoption_used", "int", "string"],
["sqlite3_complete", "int", "string:flexible"],
+ ["sqlite3_context_db_handle", "sqlite3*", "sqlite3_context*"],
+
/* sqlite3_create_function(), sqlite3_create_function_v2(), and
sqlite3_create_window_function() use hand-written bindings to
simplify handling of their function-type arguments. */
@@ -137,18 +156,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
for those, depending on how their SQL argument is provided. */
/* sqlite3_randomness() uses a hand-written wrapper to extend
the range of supported argument types. */
- [
- "sqlite3_progress_handler", undefined, [
- "sqlite3*", "int",
- new wasm.xWrap.FuncPtrAdapter({
- name: 'xProgressHandler',
- signature: 'i(p)',
- bindScope: 'context',
- contextKey: (argIndex,argv)=>'sqlite3@'+argv[0]
- }),
- "*"
- ]
- ],
+ ["sqlite3_progress_handler", undefined, [
+ "sqlite3*", "int", new wasm.xWrap.FuncPtrAdapter({
+ name: 'xProgressHandler',
+ signature: 'i(p)',
+ bindScope: 'context',
+ contextKey: (argv,argIndex)=>argv[0/* sqlite3* */]
+ }), "*"
+ ]],
["sqlite3_realloc", "*","*","int"],
["sqlite3_reset", "int", "sqlite3_stmt*"],
["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"],
@@ -164,7 +179,34 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_result_subtype", undefined, "sqlite3_value*", "int"],
["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"],
["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"],
- ["sqlite3_set_auxdata", undefined, "sqlite3_context*", "int", "*", "*"/* => v(*) */],
+ ["sqlite3_set_authorizer", "int", [
+ "sqlite3*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: "sqlite3_set_authorizer::xAuth",
+ signature: "i(pi"+"ssss)",
+ contextKey: (argv, argIndex)=>argv[0/*(sqlite3*)*/],
+ callProxy: (callback)=>{
+ return (pV, iCode, s0, s1, s2, s3)=>{
+ try{
+ s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1);
+ s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3);
+ return callback(pV, iCode, s0, s1, s2, s3) || 0;
+ }catch(e){
+ return e.resultCode || capi.SQLITE_ERROR;
+ }
+ }
+ }
+ }),
+ "*"/*pUserData*/
+ ]],
+ ["sqlite3_set_auxdata", undefined, [
+ "sqlite3_context*", "int", "*",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'xDestroyAuxData',
+ signature: 'v(*)',
+ contextKey: (argv, argIndex)=>argv[0/* sqlite3_context* */]
+ })
+ ]],
["sqlite3_shutdown", undefined],
["sqlite3_sourceid", "string"],
["sqlite3_sql", "string", "sqlite3_stmt*"],
@@ -181,12 +223,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"sqlite3*", "string", "string", "string",
"**", "**", "*", "*", "*"],
["sqlite3_total_changes", "int", "sqlite3*"],
- ["sqlite3_trace_v2", "int", "sqlite3*", "int",
- new wasm.xWrap.FuncPtrAdapter({
- name: 'sqlite3_trace_v2::callback',
- signature: 'i(ippp)',
- contextKey: (argIndex, argv)=>'sqlite3@'+argv[0]
- }), "*"],
+ ["sqlite3_trace_v2", "int", [
+ "sqlite3*", "int",
+ new wasm.xWrap.FuncPtrAdapter({
+ name: 'sqlite3_trace_v2::callback',
+ signature: 'i(ippp)',
+ contextKey: (argv,argIndex)=>argv[0/* sqlite3* */]
+ }),
+ "*"
+ ]],
["sqlite3_txn_state", "int", ["sqlite3*","string"]],
/* Note that sqlite3_uri_...() have very specific requirements for
their first C-string arguments, so we cannot perform any value
@@ -252,8 +297,6 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
["sqlite3_result_int64", undefined, "*", "i64"],
["sqlite3_result_zeroblob64", "int", "*", "i64"],
["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"],
- /* sqlite3_set_authorizer() requires a hand-written binding for
- string conversions, so is defined elsewhere. */
["sqlite3_set_last_insert_rowid", undefined, ["sqlite3*", "i64"]],
["sqlite3_status64", "int", "int", "*", "*", "int"],
["sqlite3_total_changes64", "i64", ["sqlite3*"]],
@@ -667,28 +710,24 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
);
};
- if(1){/* Bindings for sqlite3_create_collation[_v2]() */
- const __collationContextKey = (argIndex,argv)=>{
- return 'argv['+argIndex+']:sqlite3@'+argv[0]+
- ':'+wasm.cstrToJs(argv[1]).toLowerCase()
+ {/* Bindings for sqlite3_create_collation[_v2]() */
+ // contextKey() impl for wasm.xWrap.FuncPtrAdapter
+ const contextKey = (argv,argIndex)=>{
+ return 'argv['+argIndex+']:'+argv[0/* sqlite3* */]+
+ ':'+wasm.cstrToJs(argv[1/* collation name */]).toLowerCase()
};
- const __ccv2 = wasm.xWrap(
- 'sqlite3_create_collation_v2', 'int',
- 'sqlite3*','string','int','*',
- new wasm.xWrap.FuncPtrAdapter({
- /* int(*xCompare)(void*,int,const void*,int,const void*) */
- name: 'sqlite3_create_collation_v2::xCompare',
- signature: 'i(pipip)',
- bindScope: 'context',
- contextKey: __collationContextKey
- }),
- new wasm.xWrap.FuncPtrAdapter({
- /* void(*xDestroy(void*) */
- name: 'sqlite3_create_collation_v2::xDestroy',
- signature: 'v(p)',
- bindScope: 'context',
- contextKey: __collationContextKey
- })
+ const __sqlite3CreateCollationV2 = wasm.xWrap(
+ 'sqlite3_create_collation_v2', 'int', [
+ 'sqlite3*', 'string', 'int', '*',
+ new wasm.xWrap.FuncPtrAdapter({
+ /* int(*xCompare)(void*,int,const void*,int,const void*) */
+ name: 'xCompare', signature: 'i(pipip)', contextKey
+ }),
+ new wasm.xWrap.FuncPtrAdapter({
+ /* void(*xDestroy(void*) */
+ name: 'xDestroy', signature: 'v(p)', contextKey
+ })
+ ]
);
/**
@@ -722,13 +761,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
return __errEncoding(pDb);
}
- let rc, pfCompare, pfDestroy;
try{
- rc = __ccv2(pDb, zName, eTextRep, pArg, xCompare, xDestroy);
+ return __sqlite3CreateCollationV2(pDb, zName, eTextRep, pArg, xCompare, xDestroy);
}catch(e){
- rc = util.sqlite3_wasm_db_error(pDb, e);
+ return util.sqlite3_wasm_db_error(pDb, e);
}
- return rc;
};
capi.sqlite3_create_collation = (pDb,zName,eTextRep,pArg,xCompare)=>{
@@ -739,7 +776,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}/*sqlite3_create_collation() and friends*/
- if(1){/* Special-case handling of sqlite3_exec() */
+ {/* Special-case handling of sqlite3_exec() */
const __exec = wasm.xWrap("sqlite3_exec", "int",
["sqlite3*", "string:flexible",
new wasm.xWrap.FuncPtrAdapter({
@@ -755,25 +792,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
/* Wrap the callback in a WASM-bound function and convert the callback's
`(char**)` arguments to arrays of strings... */
+ let aNames;
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.cstrToJs(wasm.peekPtr(pColVals + offset)) );
- aNames.push( wasm.cstrToJs(wasm.peekPtr(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. */
+ const aVals = wasm.cArgvToJs(nCols, pColVals);
+ if(!aNames) aNames = wasm.cArgvToJs(nCols, pColNames);
+ return callback(aVals, aNames) | 0;
}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. */
+ /* 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. Though
+ we make an effort to report OOM here, sqlite3_exec()
+ translates that into SQLITE_ABORT as well. */
+ return e.resultCode || capi.SQLITE_ERROR;
}
- return rc;
};
let rc;
try{
@@ -786,83 +819,92 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
};
}/*sqlite3_exec() proxy*/;
- if(1){/* Special-case handling of sqlite3_create_function_v2()
- and sqlite3_create_window_function() */
- /* Maintenance reminder: FuncPtrAdapter is not expressive enough
- to be able to perform these mappings. */
- const sqlite3CreateFunction = wasm.xWrap(
- "sqlite3_create_function_v2", "int",
- ["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*/]
- );
-
- const __xFunc = function(callback){
- return function(pCtx, argc, pArgv){
- try{
- capi.sqlite3_result_js(
- pCtx,
- callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv))
- );
- }catch(e){
- //console.error('xFunc() caught:',e);
- capi.sqlite3_result_error_js(pCtx, e);
- }
- };
- };
-
- const __xInverseAndStep = function(callback){
- return function(pCtx, argc, pArgv){
- try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) }
- catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
- };
- };
-
- const __xFinalAndValue = function(callback){
- return function(pCtx){
- try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) }
- catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
- };
- };
-
- const __xDestroy = function(callback){
- return function(pVoid){
- try{ callback(pVoid) }
- catch(e){ console.error("UDF xDestroy method threw:",e) }
- };
+ {/* Special-case handling of sqlite3_create_function_v2()
+ and sqlite3_create_window_function(). */
+ /**
+ FuncPtrAdapter for contextKey() for sqlite3_create_function().
+ */
+ const contextKey = function(argv,argIndex){
+ return (
+ argv[0/* sqlite3* */]
+ +':'+argIndex
+ +':'+wasm.cstrToJs(argv[1]).toLowerCase()
+ )
};
- 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}
- });
-
- /* Internal helper for sqlite3_create_function() and friends. */
- const __xWrapFuncs = function(theKeys, theFuncs, tgtUninst){
- const rc = []
- for(const k of theKeys){
- let fArg = theFuncs[k];
- if('function'===typeof fArg){
- const w = __xMap[k] || toss3("Internal error in __xWrapFuncs: invalid key:",k);
- fArg = wasm.installFunction(w.sig, w.f(fArg));
- tgtUninst.push(fArg);
+ /**
+ JS proxies for the various sqlite3_create[_window]_function()
+ callbacks, structured in a form usable by wasm.xWrap.FuncPtrAdapter.
+ */
+ const __cfProxy = Object.assign(Object.create(null), {
+ xInverseAndStep: {
+ signature:'v(pip)', contextKey,
+ callProxy: (callback)=>{
+ return (pCtx, argc, pArgv)=>{
+ try{ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv)) }
+ catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
+ };
+ }
+ },
+ xFinalAndValue: {
+ signature:'v(p)', contextKey,
+ callProxy: (callback)=>{
+ return (pCtx)=>{
+ try{ capi.sqlite3_result_js(pCtx, callback(pCtx)) }
+ catch(e){ capi.sqlite3_result_error_js(pCtx, e) }
+ };
+ }
+ },
+ xFunc: {
+ signature:'v(pip)', contextKey,
+ callProxy: (callback)=>{
+ return (pCtx, argc, pArgv)=>{
+ try{
+ capi.sqlite3_result_js(
+ pCtx,
+ callback(pCtx, ...capi.sqlite3_values_to_js(argc, pArgv))
+ );
+ }catch(e){
+ //console.error('xFunc() caught:',e);
+ capi.sqlite3_result_error_js(pCtx, e);
+ }
+ };
+ }
+ },
+ xDestroy: {
+ signature:'v(p)', contextKey,
+ //Arguable: a well-behaved destructor doesn't require a proxy.
+ callProxy: (callback)=>{
+ return (pVoid)=>{
+ try{ callback(pVoid) }
+ catch(e){ console.error("UDF xDestroy method threw:",e) }
+ };
}
- rc.push(fArg);
}
- return rc;
- };
+ })/*__cfProxy*/;
+
+ const __sqlite3CreateFunction = wasm.xWrap(
+ "sqlite3_create_function_v2", "int", [
+ "sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
+ "int"/*eTextRep*/, "*"/*pApp*/,
+ new wasm.xWrap.FuncPtrAdapter({name: 'xFunc', ...__cfProxy.xFunc}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy})
+ ]
+ );
+
+ const __sqlite3CreateWindowFunction = wasm.xWrap(
+ "sqlite3_create_window_function", "int", [
+ "sqlite3*", "string"/*funcName*/, "int"/*nArg*/,
+ "int"/*eTextRep*/, "*"/*pApp*/,
+ new wasm.xWrap.FuncPtrAdapter({name: 'xStep', ...__cfProxy.xInverseAndStep}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xFinal', ...__cfProxy.xFinalAndValue}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xValue', ...__cfProxy.xFinalAndValue}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xInverse', ...__cfProxy.xInverseAndStep}),
+ new wasm.xWrap.FuncPtrAdapter({name: 'xDestroy', ...__cfProxy.xDestroy})
+ ]
+ );
/* Documented in the api object's initializer. */
capi.sqlite3_create_function_v2 = function f(
@@ -879,26 +921,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
return __errEncoding(pDb);
}
- /* Wrap the callbacks in a WASM-bound functions... */
- const uninstall = [/*funcs to uninstall on error*/];
- let rc;
try{
- const funcArgs = __xWrapFuncs(['xFunc','xStep','xFinal','xDestroy'],
- {xFunc, xStep, xFinal, xDestroy},
- uninstall);
- rc = sqlite3CreateFunction(pDb, funcName, nArg, eTextRep,
- pApp, ...funcArgs);
+ return __sqlite3CreateFunction(pDb, funcName, nArg, eTextRep,
+ pApp, xFunc, xStep, xFinal, xDestroy);
}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 util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
}
- return rc;
};
+ /* Documented in the api object's initializer. */
capi.sqlite3_create_function = function f(
pDb, funcName, nArg, eTextRep, pApp,
xFunc, xStep, xFinal
@@ -914,8 +946,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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**)
+ xValue, //void (*xValue)(sqlite3_context*)
+ xInverse,//void (*xInverse)(sqlite3_context*,int,sqlite3_value**)
xDestroy //void (*xDestroy)(void*)
){
if( f.length!==arguments.length ){
@@ -925,24 +957,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}else if( capi.SQLITE_UTF8 !== (eTextRep & 0xf) ){
return __errEncoding(pDb);
}
- /* 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'],
- {xStep, xFinal, xValue, xInverse, xDestroy},
- uninstall);
- rc = sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep,
- pApp, ...funcArgs);
+ return __sqlite3CreateWindowFunction(pDb, funcName, nArg, eTextRep,
+ pApp, xStep, xFinal, xValue,
+ xInverse, xDestroy);
}catch(e){
console.error("sqlite3_create_window_function() 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 util.sqlite3_wasm_db_error(pDb, e, "Creation of UDF threw: "+e);
}
- return rc;
};
/**
A _deprecated_ alias for capi.sqlite3_result_js() which
@@ -972,6 +994,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(1){/* Special-case handling of sqlite3_prepare_v2() and
sqlite3_prepare_v3() */
+
/**
Helper for string:flexible conversions which require a
byte-length counterpart argument. Passed a value and its
@@ -995,32 +1018,33 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/**
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",
- "**", "**"]);
+ const __prepare = {
+ /**
+ 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.
+ */
+ 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.
+ */
+ full: wasm.xWrap('sqlite3_prepare_v3',
+ "int", ["sqlite3*", "*", "int", "int",
+ "**", "**"])
+ };
/* Documented in the capi object's initializer. */
capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
@@ -1045,38 +1069,86 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
: __dbArgcMismatch(pDb,"sqlite3_prepare_v2",f.length);
};
- }/*sqlite3_prepare_v2/v3()*/;
- {/* sqlite3_set_authorizer() */
- const __ssa = wasm.xWrap("sqlite3_set_authorizer", 'int', [
- "sqlite3*",
- new wasm.xWrap.FuncPtrAdapter({
- name: "sqlite3_set_authorizer::xAuth",
- signature: "i(pi"+"ssss)",
- contextKey: (argIndex, argv)=>argv[0/*(sqlite3*)*/]
- }),
- "*"
+ }/*sqlite3_prepare_v2/v3()*/
+
+ {/*sqlite3_bind_text/blob()*/
+ const __bindText = wasm.xWrap("sqlite3_bind_text", "int", [
+ "sqlite3_stmt*", "int", "string", "int", "*"
]);
- capi.sqlite3_set_authorizer = function(pDb, xAuth, pUserData){
- if(3!==arguments.length) return __dbArgcMismatch(pDb, 'sqlite3_set_authorizer', 3);
- if(xAuth instanceof Function){
- const xProxy = xAuth;
- /* Create a proxy which will receive the C-strings from WASM
- and convert them to JS strings for the client-supplied
- function. */
- xAuth = function(pV, iCode, s0, s1, s2, s3){
- try{
- s0 = s0 && wasm.cstrToJs(s0); s1 = s1 && wasm.cstrToJs(s1);
- s2 = s2 && wasm.cstrToJs(s2); s3 = s3 && wasm.cstrToJs(s3);
- return xProxy(pV, iCode, s0, s1, s2, s3) || 0;
- }catch(e){
- return util.sqlite3_wasm_db_error(pDb, e);
- }
- };
+ const __bindBlob = wasm.xWrap("sqlite3_bind_blob", "int", [
+ "sqlite3_stmt*", "int", "*", "int", "*"
+ ]);
+
+ /** Documented in the capi object's initializer. */
+ capi.sqlite3_bind_text = function f(pStmt, iCol, text, nText, xDestroy){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt),
+ "sqlite3_bind_text", f.length);
+ }else if(wasm.isPtr(text) || null===text){
+ return __bindText(pStmt, iCol, text, nText, xDestroy);
+ }else if(text instanceof ArrayBuffer){
+ text = new Uint8Array(text);
+ }else if(Array.isArray(pMem)){
+ text = pMem.join('');
}
- return __ssa(pDb, xAuth, pUserData);
- };
- }/* sqlite3_set_authorizer() */
+ let p, n;
+ try{
+ if(util.isSQLableTypedArray(text)){
+ p = wasm.allocFromTypedArray(text);
+ n = text.byteLength;
+ }else if('string'===typeof text){
+ [p, n] = wasm.allocCString(text);
+ }else{
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
+ "Invalid 3rd argument type for sqlite3_bind_text()."
+ );
+ }
+ return __bindText(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
+ }catch(e){
+ wasm.dealloc(p);
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), e
+ );
+ }
+ }/*sqlite3_bind_text()*/;
+
+ /** Documented in the capi object's initializer. */
+ capi.sqlite3_bind_blob = function f(pStmt, iCol, pMem, nMem, xDestroy){
+ if(f.length!==arguments.length){
+ return __dbArgcMismatch(capi.sqlite3_db_handle(pStmt),
+ "sqlite3_bind_blob", f.length);
+ }else if(wasm.isPtr(pMem) || null===pMem){
+ return __bindBlob(pStmt, iCol, pMem, nMem, xDestroy);
+ }else if(pMem instanceof ArrayBuffer){
+ pMem = new Uint8Array(pMem);
+ }else if(Array.isArray(pMem)){
+ pMem = pMem.join('');
+ }
+ let p, n;
+ try{
+ if(util.isBindableTypedArray(pMem)){
+ p = wasm.allocFromTypedArray(pMem);
+ n = nMem>=0 ? nMem : pMem.byteLength;
+ }else if('string'===typeof pMem){
+ [p, n] = wasm.allocCString(pMem);
+ }else{
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), capi.SQLITE_MISUSE,
+ "Invalid 3rd argument type for sqlite3_bind_blob()."
+ );
+ }
+ return __bindBlob(pStmt, iCol, p, n, capi.SQLITE_WASM_DEALLOC);
+ }catch(e){
+ wasm.dealloc(p);
+ return util.sqlite3_wasm_db_error(
+ capi.sqlite3_db_handle(pStmt), e
+ );
+ }
+ }/*sqlite3_bind_blob()*/;
+
+ }/*sqlite3_bind_text/blob()*/
{/* sqlite3_config() */
/**
@@ -1130,8 +1202,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
"static buffer size!");
}
- wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
+ wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
+ // Groups of SQLITE_xyz macros...
const defineGroups = ['access', 'authorizer',
'blobFinalizers', 'changeset',
'config', 'dataTypes',
@@ -1139,13 +1212,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'encodings', 'fcntl', 'flock', 'ioCap',
'limits', 'openFlags',
'prepareFlags', 'resultCodes',
- 'serialize', 'session',
'sqlite3Status',
'stmtStatus', 'syncFlags',
'trace', 'txnState', 'udfFlags',
'version' ];
if(wasm.bigIntEnabled){
- defineGroups.push('vtab');
+ defineGroups.push('serialize', 'session', 'vtab');
}
for(const t of defineGroups){
for(const e of Object.entries(wasm.ctype[t])){
@@ -1297,4 +1369,5 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
}/*pKvvfs*/
+ wasm.xWrap.FuncPtrAdapter.warnOnUse = true;
});
diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js
index 1776cb327..16f5f00b1 100644
--- a/ext/wasm/api/sqlite3-api-oo1.js
+++ b/ext/wasm/api/sqlite3-api-oo1.js
@@ -337,7 +337,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/
const Stmt = function(){
if(BindTypes!==arguments[2]){
- toss3("Do not call the Stmt constructor directly. Use DB.prepare().");
+ toss3(capi.SQLITE_MISUSE, "Do not call the Stmt constructor directly. Use DB.prepare().");
}
this.db = arguments[0];
__ptrMap.set(this, arguments[1]);
@@ -439,21 +439,22 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(util.isInt32(opt.rowMode)){
out.cbArg = (stmt)=>stmt.get(opt.rowMode);
break;
- }else if('string'===typeof opt.rowMode && opt.rowMode.length>1){
+ }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. */
- if('$'===opt.rowMode[0]){
- out.cbArg = function(stmt){
- const rc = stmt.get(this.obj)[this.colName];
- return (undefined===rc) ? toss3("exec(): unknown result column:",this.colName) : rc;
- }.bind({
- obj:Object.create(null),
- colName: opt.rowMode.substr(1)
- });
- break;
- }
+ 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);
}
@@ -608,7 +609,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try{ rc = wasm.cstrToJs(v.$zName) }
finally { v.dispose() }
}
- return rc;
+ return rc;
},
/**
Compiles the given SQL and returns a prepared Stmt. This is
@@ -697,21 +698,27 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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
+ - `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).
+ 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.
+ 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 ...
@@ -799,7 +806,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
Array.isArray(opt.resultRows) ? opt.resultRows : undefined;
let stmt;
let bind = opt.bind;
- let evalFirstResult = !!(arg.cbArg || opt.columnNames) /* true to evaluate the first result-returning query */;
+ 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{
@@ -810,9 +819,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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 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;
@@ -848,11 +858,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(Array.isArray(opt.columnNames)){
stmt.getColumnNames(opt.columnNames);
}
- while(!!arg.cbArg && stmt.step()){
- stmt._isLocked = true;
- const row = arg.cbArg(stmt);
- if(resultRows) resultRows.push(row);
- if(callback) callback.call(opt, row, stmt);
+ if(arg.cbArg || resultRows){
+ for(; stmt.step(); stmt._isLocked = false){
+ stmt._isLocked = true;
+ const row = arg.cbArg(stmt);
+ if(resultRows) resultRows.push(row);
+ if(callback && false === callback.call(opt, row, stmt)){
+ break;
+ }
+ }
stmt._isLocked = false;
}
}else{
@@ -873,10 +887,11 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
return arg.returnVal();
}/*exec()*/,
+
/**
- Creates a new scalar UDF (User-Defined Function) which is
- accessible via SQL code. This function may be called in any
- of the following forms:
+ 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)
@@ -892,10 +907,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
functions. Creating an aggregate or window function requires
the options-object form (see below for details).
- UDFs cannot currently be removed from a DB handle after they're
- added. More correctly, they can be removed as documented for
- sqlite3_create_function_v2(), but doing so will "leak" the
- JS-created WASM binding of those functions.
+ 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.
@@ -1213,7 +1230,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/* else fall through */
default:
//console.log("isSupportedBindType",t,v);
- return util.isBindableTypedArray(v) ? BindTypes.blob : undefined;
+ return (util.isBindableTypedArray(v) || (v instanceof ArrayBuffer))
+ ? BindTypes.blob : undefined;
}
};
@@ -1267,7 +1285,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
success.
*/
const bindOne = function f(stmt,ndx,bindType,val){
- affirmUnlocked(stmt, 'bind()');
+ affirmUnlocked(affirmStmtOpen(stmt), 'bind()');
if(!f._){
f._tooBigInt = (v)=>toss3(
"BigInt value is too big to store without precision loss:", v
@@ -1277,14 +1295,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
so we have no range checking. */
f._ = {
string: function(stmt, ndx, val, asBlob){
- const stack = wasm.scopedAllocPush();
- try{
- const [pStr, n] = wasm.scopedAllocCString(val, true);
- const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
- return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_TRANSIENT);
- }finally{
- wasm.scopedAllocPop(stack);
- }
+ 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 */
@@ -1329,29 +1342,17 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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, or Int8Array.");
- }else if(1){
- /* _Hypothetically_ more efficient than the impl in the 'else' block. */
- const stack = wasm.scopedAllocPush();
- try{
- const pBlob = wasm.scopedAlloc(val.byteLength || 1);
- wasm.heap8().set(val.byteLength ? val : [0], pBlob)
- rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
- capi.SQLITE_TRANSIENT);
- }finally{
- wasm.scopedAllocPop(stack);
- }
- }else{
- const pBlob = wasm.allocFromTypedArray(val);
- try{
- rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
- capi.SQLITE_TRANSIENT);
- }finally{
- wasm.dealloc(pBlob);
- }
+ "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:
@@ -1359,6 +1360,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
toss3("Unsupported bind() argument type: "+(typeof val));
}
if(rc) DB.checkRc(stmt.db.pointer, rc);
+ stmt._mayGet = false;
return stmt;
};
@@ -1446,8 +1448,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- Strings are bound as strings (use bindAsBlob() to force
blob binding).
- - Uint8Array and Int8Array instances are bound as blobs.
- (TODO: binding the other TypedArray types.)
+ - Uint8Array, Int8Array, and ArrayBuffer instances are bound as
+ blobs. (TODO? binding the other TypedArray types.)
If passed an array, each element of the array is bound at
the parameter index equal to the array index plus 1
@@ -1502,8 +1504,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
return this;
+ }else if(arg instanceof ArrayBuffer){
+ arg = new Uint8Array(arg);
}
- else if('object'===typeof arg/*null was checked above*/
+ 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){
@@ -1525,7 +1529,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
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.
+ 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.
@@ -1541,9 +1545,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
&& BindTypes.null !== t){
toss3("Invalid value type for bindAsBlob()");
}
- bindOne(this, ndx, BindTypes.blob, arg);
- this._mayGet = false;
- return this;
+ return bindOne(this, ndx, BindTypes.blob, arg);
},
/**
Steps the statement one time. If the result indicates that a
diff --git a/ext/wasm/api/sqlite3-api-prologue.js b/ext/wasm/api/sqlite3-api-prologue.js
index 6c50e99b0..7804c0458 100644
--- a/ext/wasm/api/sqlite3-api-prologue.js
+++ b/ext/wasm/api/sqlite3-api-prologue.js
@@ -321,14 +321,17 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
};
/**
- Returns true if v appears to be one of our bind()-able TypedArray
- types: Uint8Array or Int8Array. Support for TypedArrays with
- element sizes >1 is a potential TODO just waiting on a use case
- to justify them.
+ Returns v if v appears to be one of our bind()-able TypedArray
+ types: Uint8Array or Int8Array or ArrayBuffer. Support for
+ TypedArrays with element sizes >1 is a potential TODO just
+ waiting on a use case to justify them. Until then, their `buffer`
+ property can be used to pass them as an ArrayBuffer. If it's not
+ a bindable array type, a falsy value is returned.
*/
const isBindableTypedArray = (v)=>{
- return v && (v instanceof Uint8Array || v instanceof Int8Array);
- //v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT);
+ return v && (v instanceof Uint8Array
+ || v instanceof Int8Array
+ || v instanceof ArrayBuffer);
};
/**
@@ -341,8 +344,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
isSQLableTypedArray() list.
*/
const isSQLableTypedArray = (v)=>{
- return v && (v instanceof Uint8Array || v instanceof Int8Array);
- //v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT);
+ return v && (v instanceof Uint8Array
+ || v instanceof Int8Array
+ || v instanceof ArrayBuffer);
};
/** Returns true if isBindableTypedArray(v) does, else throws with a message
@@ -401,6 +405,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
}else{
super("Allocation failed.");
}
+ this.resultCode = capi.SQLITE_NOMEM;
this.name = 'WasmAllocError';
}
};
@@ -418,6 +423,92 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
Object.assign(capi, {
/**
+ sqlite3_bind_blob() works exactly like its C counterpart unless
+ its 3rd argument is one of:
+
+ - JS string: the 3rd argument is converted to a C string, the
+ 4th argument is ignored, and the C-string's length is used
+ in its place.
+
+ - Array: converted to a string as defined for "flexible
+ strings" and then it's treated as a JS string.
+
+ - Int8Array or Uint8Array: wasm.allocFromTypedArray() is used to
+ conver the memory to the WASM heap. If the 4th argument is
+ 0 or greater, it is used as-is, otherwise the array's byteLength
+ value is used. This is an exception to the C API's undefined
+ behavior for a negative 4th argument, but results are undefined
+ if the given 4th argument value is greater than the byteLength
+ of the input array.
+
+ - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
+ treated as that type.
+
+ In all of those cases, the final argument (destructor) is
+ ignored and capi.SQLITE_WASM_DEALLOC is assumed.
+
+ A 3rd argument of `null` is treated as if it were a WASM pointer
+ of 0.
+
+ If the 3rd argument is neither a WASM pointer nor one of the
+ above-described types, capi.SQLITE_MISUSE is returned.
+
+ The first argument may be either an `sqlite3_stmt*` WASM
+ pointer or an sqlite3.oo1.Stmt instance.
+
+ For consistency with the C API, it requires the same number of
+ arguments. It returns capi.SQLITE_MISUSE if passed any other
+ argument count.
+ */
+ sqlite3_bind_blob: undefined/*installed later*/,
+
+ /**
+ sqlite3_bind_text() works exactly like its C counterpart unless
+ its 3rd argument is one of:
+
+ - JS string: the 3rd argument is converted to a C string, the
+ 4th argument is ignored, and the C-string's length is used
+ in its place.
+
+ - Array: converted to a string as defined for "flexible
+ strings". The 4th argument is ignored and a value of -1
+ is assumed.
+
+ - Int8Array or Uint8Array: is assumed to contain UTF-8 text, is
+ converted to a string. The 4th argument is ignored, replaced
+ by the array's byteLength value.
+
+ - If it's an ArrayBuffer, it gets wrapped in a Uint8Array and
+ treated as that type.
+
+ In each of those cases, the final argument (text destructor) is
+ ignored and capi.SQLITE_WASM_DEALLOC is assumed.
+
+ A 3rd argument of `null` is treated as if it were a WASM pointer
+ of 0.
+
+ If the 3rd argument is neither a WASM pointer nor one of the
+ above-described types, capi.SQLITE_MISUSE is returned.
+
+ The first argument may be either an `sqlite3_stmt*` WASM
+ pointer or an sqlite3.oo1.Stmt instance.
+
+ For consistency with the C API, it requires the same number of
+ arguments. It returns capi.SQLITE_MISUSE if passed any other
+ argument count.
+
+ If client code needs to bind partial strings, it needs to
+ either parcel the string up before passing it in here or it
+ must pass in a WASM pointer for the 3rd argument and a valid
+ 4th-argument value, taking care not to pass a value which
+ truncates a multi-byte UTF-8 character. When passing
+ WASM-format strings, it is important that the final argument be
+ valid or unexpected content can result can result, or even a
+ crash if the application reads past the WASM heap bounds.
+ */
+ sqlite3_bind_text: undefined/*installed later*/,
+
+ /**
sqlite3_create_function_v2() differs from its native
counterpart only in the following ways:
@@ -525,18 +616,18 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH`
flag.
*/
- sqlite3_create_function_v2: function(
+ sqlite3_create_function_v2: (
pDb, funcName, nArg, eTextRep, pApp,
xFunc, xStep, xFinal, xDestroy
- ){/*installed later*/},
+ )=>{/*installed later*/},
/**
Equivalent to passing the same arguments to
sqlite3_create_function_v2(), with 0 as the final argument.
*/
- sqlite3_create_function:function(
+ sqlite3_create_function: (
pDb, funcName, nArg, eTextRep, pApp,
xFunc, xStep, xFinal
- ){/*installed later*/},
+ )=>{/*installed later*/},
/**
The sqlite3_create_window_function() JS wrapper differs from
its native implementation in the exact same way that
@@ -544,10 +635,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
xInverse(), is treated identically to xStep() by the wrapping
layer.
*/
- sqlite3_create_window_function: function(
+ sqlite3_create_window_function: (
pDb, funcName, nArg, eTextRep, pApp,
xStep, xFinal, xValue, xInverse, xDestroy
- ){/*installed later*/},
+ )=>{/*installed later*/},
/**
The sqlite3_prepare_v3() binding handles two different uses
with differing JS/WASM semantics:
@@ -669,7 +760,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
toss3,
typedArrayPart
};
-
+
Object.assign(wasm, {
/**
Emscripten APIs have a deep-seated assumption that all pointers
@@ -858,7 +949,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
const m = f._rx.exec(opt);
rv[0] = (m ? m[1] : opt);
rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
- };
+ };
}
const rc = {}, ov = [0,0];
let i = 0, k;
@@ -1559,7 +1650,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
capi.sqlite3_result_error(pCtx, ''+e, -1);
}
};
-
+
/**
This function passes its 2nd argument to one of the
sqlite3_result_xyz() routines, depending on the type of that
@@ -1575,7 +1666,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
- `bigint`: similar to `number` but will trigger an error if the
value is too big to store in an int64.
- `string`: `sqlite3_result_text()`
- - Uint8Array or Int8Array: `sqlite3_result_blob()`
+ - Uint8Array or Int8Array or ArrayBuffer: `sqlite3_result_blob()`
- `undefined`: is a no-op provided to simplify certain use cases.
Anything else triggers `sqlite3_result_error()` with a
@@ -1626,9 +1717,11 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
f(pCtx, val);
break;
}
- case 'string':
- capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT);
+ case 'string': {
+ const [p, n] = wasm.allocCString(val,true);
+ capi.sqlite3_result_text(pCtx, p, n, capi.SQLITE_WASM_DEALLOC);
break;
+ }
case 'object':
if(null===val/*yes, typeof null === 'object'*/) {
capi.sqlite3_result_null(pCtx);
@@ -1637,7 +1730,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
const pBlob = wasm.allocFromTypedArray(val);
capi.sqlite3_result_blob(
pCtx, pBlob, val.byteLength,
- wasm.exports[sqlite3.config.deallocExportName]
+ capi.SQLITE_WASM_DEALLOC
);
break;
}
@@ -1657,8 +1750,8 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
argument of sqlite3_value_to_js(). If the sqlite3_column_value()
returns NULL (e.g. because the column index is out of range),
this function returns `undefined`, regardless of the 3rd
- argument. 3rd argument is falsy and conversion fails, `undefined`
- will be returned.
+ argument. If the 3rd argument is falsy and conversion fails,
+ `undefined` will be returned.
Note that sqlite3_column_value() returns an "unprotected" value
object, but in a single-threaded environment (like this one)
diff --git a/ext/wasm/api/sqlite3-wasm.c b/ext/wasm/api/sqlite3-wasm.c
index 3f0d40e5c..b39e252b1 100644
--- a/ext/wasm/api/sqlite3-wasm.c
+++ b/ext/wasm/api/sqlite3-wasm.c
@@ -329,11 +329,13 @@ SQLITE_WASM_KEEP int sqlite3_wasm_pstack_quota(void){
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_db_error(sqlite3*db, int err_code, const char *zMsg){
- if( 0!=zMsg ){
- const int nMsg = sqlite3Strlen30(zMsg);
- sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
- }else{
- sqlite3ErrorWithMsg(db, err_code, NULL);
+ if( db!=0 ){
+ if( 0!=zMsg ){
+ const int nMsg = sqlite3Strlen30(zMsg);
+ sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
+ }else{
+ sqlite3ErrorWithMsg(db, err_code, NULL);
+ }
}
return err_code;
}
@@ -1649,6 +1651,11 @@ int sqlite3_wasm_test_intptr(int * p){
}
SQLITE_WASM_KEEP
+void * sqlite3_wasm_test_voidptr(void * p){
+ return p;
+}
+
+SQLITE_WASM_KEEP
int64_t sqlite3_wasm_test_int64_max(void){
return (int64_t)0x7fffffffffffffff;
}