1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

More cleanups in the UDF argument and result handling, in particular int64. Consolidate some duplicate int64/bigint range checking code. Expose the UDF low-level utilities (arg/result conversion) to client code. Add the sqlite3_context pointer to the JS-side UDF wrappers for API consistency.

FossilOrigin-Name: 10ab77af952abf09f93f342a9d07a3b133f2c4c0a3588df3390cd3a923cafae4
This commit is contained in:
stephan
2022-10-02 22:50:04 +00:00
parent 92ede964e1
commit 510a9d1c64
8 changed files with 274 additions and 124 deletions

View File

@ -19,6 +19,7 @@
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'use strict';
const toss = (...args)=>{throw new Error(args.join(' '))};
const toss3 = sqlite3.SQLite3Error.toss;
const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util;
self.WhWasmUtilInstaller(capi.wasm);
delete self.WhWasmUtilInstaller;
@ -62,8 +63,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
*/
const aPtr = wasm.xWrap.argAdapter('*');
wasm.xWrap.argAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr);
wasm.xWrap.resultAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr);
wasm.xWrap.argAdapter('sqlite3*', aPtr)
('sqlite3_stmt*', aPtr)
('sqlite3_context*', aPtr)
('sqlite3_value*', aPtr)
('void*', aPtr);
wasm.xWrap.resultAdapter('sqlite3*', aPtr)
('sqlite3_stmt*', aPtr)
('sqlite3_context*', aPtr)
('void*', aPtr);
/**
Populate api object with sqlite3_...() by binding the "raw" wasm
@ -207,38 +215,53 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/,
"*"/*xInverse*/, "*"/*xDestroy*/]
);
const __setResult = function(pCx, val){
const __setUdfResult = function(pCtx, val){
//console.warn("setUdfResult",typeof val, val);
switch(typeof val) {
case 'boolean':
capi.sqlite3_result_int(pCx, val ? 1 : 0);
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.");
}
break;
case 'number': {
(util.isInt32(val)
? capi.sqlite3_result_int
: capi.sqlite3_result_double)(pCx, val);
: capi.sqlite3_result_double)(pCtx, val);
break;
}
case 'string':
capi.sqlite3_result_text(pCx, val, -1, capi.SQLITE_TRANSIENT);
capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT);
break;
case 'object':
if(null===val/*yes, typeof null === 'object'*/) {
capi.sqlite3_result_null(pCx);
capi.sqlite3_result_null(pCtx);
break;
}else if(util.isBindableTypedArray(val)){
const pBlob = wasm.allocFromTypedArray(val);
capi.sqlite3_result_blob(
pCx, pBlob, val.byteLength,
pCtx, pBlob, val.byteLength,
wasm.exports[sqlite3.config.deallocExportName]
);
break;
}
// else fall through
default:
toss3("Don't not how to handle this UDF result value:",val);
toss3("Don't not how to handle this UDF result value:",(typeof val), val);
};
}/*__setResult()*/;
const __extractArgs = function(argc, pArgv){
}/*__setUdfResult()*/;
const __convertUdfArgs = function(argc, pArgv){
let i, pVal, valType, arg;
const tgt = [];
for(i = 0; i < argc; ++i){
@ -253,6 +276,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
valType = capi.sqlite3_value_type(pVal);
switch(valType){
case capi.SQLITE_INTEGER:
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;
case capi.SQLITE_FLOAT:
arg = capi.sqlite3_value_double(pVal);
break;
@ -262,10 +291,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case capi.SQLITE_BLOB:{
const n = capi.sqlite3_value_bytes(pVal);
const pBlob = capi.sqlite3_value_blob(pVal);
arg = new Uint8Array(n);
let i;
const heap = n ? wasm.heap8() : false;
for(i = 0; i < n; ++i) arg[i] = heap[pBlob+i];
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;
break;
}
case capi.SQLITE_NULL:
@ -278,39 +307,37 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
tgt.push(arg);
}
return tgt;
}/*__extractArgs()*/;
}/*__convertUdfArgs()*/;
const __setCxErr = (pCx, e)=>{
if(e instanceof capi.WasmAllocError){
capi.sqlite3_result_error_nomem(pCx);
const __setUdfError = (pCtx, e)=>{
if(e instanceof sqlite3.WasmAllocError){
capi.sqlite3_result_error_nomem(pCtx);
}else{
capi.sqlite3_result_error(pCx, e.message, -1);
capi.sqlite3_result_error(pCtx, e.message, -1);
}
};
/* TODO: pass on the pCx pointer to all callbacks. This requires
fixing test code and updating oodles of docs. Once that is in place,
export sqlite3_aggregate_context().
*/
const __xFunc = function(callback){
return function(pCx, argc, pArgv){
try{ __setResult(pCx, callback(...__extractArgs(argc, pArgv))) }
catch(e){ __setCxErr(pCx, e) }
return function(pCtx, argc, pArgv){
try{ __setUdfResult(pCtx, callback(pCtx, ...__convertUdfArgs(argc, pArgv))) }
catch(e){
//console.error('xFunc() caught:',e);
__setUdfError(pCtx, e);
}
};
};
const __xInverseAndStep = function(callback){
return function(pCx, argc, pArgv){
try{ callback(...__extractArgs(argc, pArgv)) }
catch(e){ __setCxErr(pCx, e) }
return function(pCtx, argc, pArgv){
try{ callback(pCtx, ...__convertUdfArgs(argc, pArgv)) }
catch(e){ __setUdfError(pCtx, e) }
};
};
const __xFinalAndValue = function(callback){
return function(pCx){
try{ __setResult(pCx, callback()) }
catch(e){ __setCxErr(pCx, e) }
return function(pCtx){
try{ __setUdfResult(pCtx, callback(pCtx)) }
catch(e){ __setUdfError(pCtx, e) }
};
};
@ -417,6 +444,57 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
}
return rc;
};
/**
A helper for UDFs implemented in JS and bound to WASM by the
client. Given a JS value, setUdfResult(pCtx,X) calls one of the
sqlite3_result_xyz(pCtx,...) routines, depending on X's data
type:
- `null`: sqlite3_result_null()
- `boolean`: sqlite3_result_int()
- `number': sqlite3_result_int() or sqlite3_result_double()
- `string`: sqlite3_result_text()
- Uint8Array or Int8Array: sqlite3_result_blob()
Anything else triggers sqlite3_result_error().
*/
capi.sqlite3_create_function_v2.setUdfResult =
capi.sqlite3_create_function.setUdfResult =
capi.sqlite3_create_window_function.setUdfResult = __setUdfResult;
/**
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
(Uint8Array()), or null.
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.
*/
capi.sqlite3_create_function_v2.convertUdfArgs =
capi.sqlite3_create_function.convertUdfArgs =
capi.sqlite3_create_window_function.convertUdfArgs = __convertUdfArgs;
/**
A helper for UDFs implemented in JS and bound to WASM by the
client. It expects to be a passed `(sqlite3_context*, Error)`
(i.e. an exception object). 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.
*/
capi.sqlite3_create_function_v2.setUdfError =
capi.sqlite3_create_function.setUdfError =
capi.sqlite3_create_window_function.setUdfError = __setUdfError;
}/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/;