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

@ -1,3 +1,4 @@
_sqlite3_aggregate_context
_sqlite3_bind_blob _sqlite3_bind_blob
_sqlite3_bind_double _sqlite3_bind_double
_sqlite3_bind_int _sqlite3_bind_int
@ -61,6 +62,7 @@ _sqlite3_result_error_code
_sqlite3_result_error_nomem _sqlite3_result_error_nomem
_sqlite3_result_error_toobig _sqlite3_result_error_toobig
_sqlite3_result_int _sqlite3_result_int
_sqlite3_result_int64
_sqlite3_result_null _sqlite3_result_null
_sqlite3_result_text _sqlite3_result_text
_sqlite3_serialize _sqlite3_serialize
@ -76,9 +78,12 @@ _sqlite3_uri_boolean
_sqlite3_uri_int64 _sqlite3_uri_int64
_sqlite3_uri_key _sqlite3_uri_key
_sqlite3_uri_parameter _sqlite3_uri_parameter
_sqlite3_user_data
_sqlite3_value_blob _sqlite3_value_blob
_sqlite3_value_bytes _sqlite3_value_bytes
_sqlite3_value_double _sqlite3_value_double
_sqlite3_value_int
_sqlite3_value_int64
_sqlite3_value_text _sqlite3_value_text
_sqlite3_value_type _sqlite3_value_type
_sqlite3_vfs_find _sqlite3_vfs_find

View File

@ -19,6 +19,7 @@
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
'use strict'; 'use strict';
const toss = (...args)=>{throw new Error(args.join(' '))}; const toss = (...args)=>{throw new Error(args.join(' '))};
const toss3 = sqlite3.SQLite3Error.toss;
const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util; const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util;
self.WhWasmUtilInstaller(capi.wasm); self.WhWasmUtilInstaller(capi.wasm);
delete self.WhWasmUtilInstaller; delete self.WhWasmUtilInstaller;
@ -62,8 +63,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer. `sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
*/ */
const aPtr = wasm.xWrap.argAdapter('*'); const aPtr = wasm.xWrap.argAdapter('*');
wasm.xWrap.argAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr); wasm.xWrap.argAdapter('sqlite3*', aPtr)
wasm.xWrap.resultAdapter('sqlite3*', aPtr)('sqlite3_stmt*', 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 Populate api object with sqlite3_...() by binding the "raw" wasm
@ -207,38 +215,53 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/, "*"/*xStep*/,"*"/*xFinal*/, "*"/*xValue*/,
"*"/*xInverse*/, "*"/*xDestroy*/] "*"/*xInverse*/, "*"/*xDestroy*/]
); );
const __setResult = function(pCx, val){
const __setUdfResult = function(pCtx, val){
//console.warn("setUdfResult",typeof val, val);
switch(typeof val) { switch(typeof val) {
case 'boolean': 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; break;
case 'number': { case 'number': {
(util.isInt32(val) (util.isInt32(val)
? capi.sqlite3_result_int ? capi.sqlite3_result_int
: capi.sqlite3_result_double)(pCx, val); : capi.sqlite3_result_double)(pCtx, val);
break; break;
} }
case 'string': case 'string':
capi.sqlite3_result_text(pCx, val, -1, capi.SQLITE_TRANSIENT); capi.sqlite3_result_text(pCtx, val, -1, capi.SQLITE_TRANSIENT);
break; break;
case 'object': case 'object':
if(null===val/*yes, typeof null === 'object'*/) { if(null===val/*yes, typeof null === 'object'*/) {
capi.sqlite3_result_null(pCx); capi.sqlite3_result_null(pCtx);
break; break;
}else if(util.isBindableTypedArray(val)){ }else if(util.isBindableTypedArray(val)){
const pBlob = wasm.allocFromTypedArray(val); const pBlob = wasm.allocFromTypedArray(val);
capi.sqlite3_result_blob( capi.sqlite3_result_blob(
pCx, pBlob, val.byteLength, pCtx, pBlob, val.byteLength,
wasm.exports[sqlite3.config.deallocExportName] wasm.exports[sqlite3.config.deallocExportName]
); );
break; break;
} }
// else fall through // else fall through
default: 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()*/; }/*__setUdfResult()*/;
const __extractArgs = function(argc, pArgv){
const __convertUdfArgs = function(argc, pArgv){
let i, pVal, valType, arg; let i, pVal, valType, arg;
const tgt = []; const tgt = [];
for(i = 0; i < argc; ++i){ for(i = 0; i < argc; ++i){
@ -253,6 +276,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
valType = capi.sqlite3_value_type(pVal); valType = capi.sqlite3_value_type(pVal);
switch(valType){ switch(valType){
case capi.SQLITE_INTEGER: 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: case capi.SQLITE_FLOAT:
arg = capi.sqlite3_value_double(pVal); arg = capi.sqlite3_value_double(pVal);
break; break;
@ -262,10 +291,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case capi.SQLITE_BLOB:{ case capi.SQLITE_BLOB:{
const n = capi.sqlite3_value_bytes(pVal); const n = capi.sqlite3_value_bytes(pVal);
const pBlob = capi.sqlite3_value_blob(pVal); const pBlob = capi.sqlite3_value_blob(pVal);
arg = new Uint8Array(n); if(n && !pBlob) sqlite3.WasmAllocError.toss(
let i; "Cannot allocate memory for blob argument of",n,"byte(s)"
const heap = n ? wasm.heap8() : false; );
for(i = 0; i < n; ++i) arg[i] = heap[pBlob+i]; arg = n ? wasm.heap8u().slice(pBlob, pBlob + Number(n)) : null;
break; break;
} }
case capi.SQLITE_NULL: case capi.SQLITE_NULL:
@ -278,39 +307,37 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
tgt.push(arg); tgt.push(arg);
} }
return tgt; return tgt;
}/*__extractArgs()*/; }/*__convertUdfArgs()*/;
const __setCxErr = (pCx, e)=>{ const __setUdfError = (pCtx, e)=>{
if(e instanceof capi.WasmAllocError){ if(e instanceof sqlite3.WasmAllocError){
capi.sqlite3_result_error_nomem(pCx); capi.sqlite3_result_error_nomem(pCtx);
}else{ }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){ const __xFunc = function(callback){
return function(pCx, argc, pArgv){ return function(pCtx, argc, pArgv){
try{ __setResult(pCx, callback(...__extractArgs(argc, pArgv))) } try{ __setUdfResult(pCtx, callback(pCtx, ...__convertUdfArgs(argc, pArgv))) }
catch(e){ __setCxErr(pCx, e) } catch(e){
//console.error('xFunc() caught:',e);
__setUdfError(pCtx, e);
}
}; };
}; };
const __xInverseAndStep = function(callback){ const __xInverseAndStep = function(callback){
return function(pCx, argc, pArgv){ return function(pCtx, argc, pArgv){
try{ callback(...__extractArgs(argc, pArgv)) } try{ callback(pCtx, ...__convertUdfArgs(argc, pArgv)) }
catch(e){ __setCxErr(pCx, e) } catch(e){ __setUdfError(pCtx, e) }
}; };
}; };
const __xFinalAndValue = function(callback){ const __xFinalAndValue = function(callback){
return function(pCx){ return function(pCtx){
try{ __setResult(pCx, callback()) } try{ __setUdfResult(pCtx, callback(pCtx)) }
catch(e){ __setCxErr(pCx, e) } catch(e){ __setUdfError(pCtx, e) }
}; };
}; };
@ -417,6 +444,57 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
} }
return rc; 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*/; }/*sqlite3_create_function_v2() and sqlite3_create_window_function() proxies*/;

View File

@ -784,12 +784,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
the function is defined: the function is defined:
- .arity: the number of arguments which SQL calls to this - .arity: the number of arguments which SQL calls to this
function expect or require. The default value is the function expect or require. The default value is
callback's length property (i.e. the number of declared `callback.length` (i.e. the number of declared parameters it
parameters it has). A value of -1 means that the function has) **MINUS 1** (see below for why). As a special case, if
is variadic and may accept any number of arguments, up to callback.length is 0, its arity is also 0 instead of -1. A
sqlite3's compile-time limits. sqlite3 will enforce the negative arity value means that the function is variadic and
argument count if is zero or greater. 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 properties correspond to flags documented at: The following properties correspond to flags documented at:
@ -827,7 +836,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
name = name.toLowerCase(); name = name.toLowerCase();
DB.checkRc(this, capi.sqlite3_create_function_v2( DB.checkRc(this, capi.sqlite3_create_function_v2(
this.pointer, name, this.pointer, name,
(opt.hasOwnProperty('arity') ? +opt.arity : callback.length), (opt.hasOwnProperty('arity')
? +opt.arity
: (callback.length ? callback.length-1/*for pCtx arg*/ : 0)),
capi.SQLITE_UTF8 | fFlags, null/*pApp*/, callback, capi.SQLITE_UTF8 | fFlags, null/*pApp*/, callback,
null/*xStep*/, null/*xFinal*/, null/*xDestroy*/)); null/*xStep*/, null/*xFinal*/, null/*xDestroy*/));
return this; return this;
@ -992,10 +1003,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const bindOne = function f(stmt,ndx,bindType,val){ const bindOne = function f(stmt,ndx,bindType,val){
affirmUnlocked(stmt, 'bind()'); affirmUnlocked(stmt, 'bind()');
if(!f._){ if(!f._){
if(wasm.bigIntEnabled){ f._tooBigInt = (v)=>toss3(
f._maxInt = BigInt("0x7fffffffffffffff"); "BigInt value is too big to store without precision loss:", v
f._minInt = ~f._maxInt; );
}
/* Reminder: when not in BigInt mode, it's impossible for /* Reminder: when not in BigInt mode, it's impossible for
JS to represent a number out of the range we can bind, JS to represent a number out of the range we can bind,
so we have no range checking. */ so we have no range checking. */
@ -1041,15 +1051,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
let m; let m;
if(util.isInt32(val)) m = capi.sqlite3_bind_int; if(util.isInt32(val)) m = capi.sqlite3_bind_int;
else if('bigint'===typeof val){ else if('bigint'===typeof val){
if(val<f._minInt || val>f._maxInt){ if(!util.bigIntFits64(val)){
toss3("BigInt value is out of range for storing as int64: "+val); f._tooBigInt(val);
}else if(wasm.bigIntEnabled){ }else if(wasm.bigIntEnabled){
m = capi.sqlite3_bind_int64; m = capi.sqlite3_bind_int64;
}else if(val >= Number.MIN_SAFE_INTEGER && val <= Number.MAX_SAFE_INTEGER){ }else if(util.bigIntFitsDouble(val)){
val = Number(val); val = Number(val);
m = capi.sqlite3_bind_double; m = capi.sqlite3_bind_double;
}else{ }else{
toss3("BigInt value is out of range for storing as double: "+val); f._tooBigInt(val);
} }
}else{ // !int32, !bigint }else{ // !int32, !bigint
val = Number(val); val = Number(val);
@ -1298,8 +1308,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
case capi.SQLITE_ROW: return this._mayGet = true; case capi.SQLITE_ROW: return this._mayGet = true;
default: default:
this._mayGet = false; this._mayGet = false;
console.warn("sqlite3_step() rc=",rc,"SQL =", console.warn("sqlite3_step() rc=",rc,
capi.sqlite3_sql(this.pointer)); capi.sqlite3_web_rc_str(rc),
"SQL =", capi.sqlite3_sql(this.pointer));
DB.checkRc(this.db.pointer, rc); DB.checkRc(this.db.pointer, rc);
} }
}, },

View File

@ -176,10 +176,39 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
double-type DB operations for integer values in order to keep double-type DB operations for integer values in order to keep
more precision. more precision.
*/ */
const isInt32 = function(n){ const isInt32 = (n)=>{
return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/) return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/)
&& !!(n===(n|0) && n<=2147483647 && n>=-2147483648); && !!(n===(n|0) && n<=2147483647 && n>=-2147483648);
}; };
/**
Returns true if the given BigInt value is small enough to fit
into an int64 value, else false.
*/
const bigIntFits64 = function f(b){
if(!f._max){
f._max = BigInt("0x7fffffffffffffff");
f._min = ~f._max;
}
return b >= f._min && b <= f._max;
};
/**
Returns true if the given BigInt value is small enough to fit
into an int32, else false.
*/
const bigIntFits32 = (b)=>(b >= (-0x7fffffffn - 1n) && b <= 0x7fffffffn);
/**
Returns true if the given BigInt value is small enough to fit
into a double value without loss of precision, else false.
*/
const bigIntFitsDouble = function f(b){
if(!f._min){
f._min = Number.MIN_SAFE_INTEGER;
f._max = Number.MAX_SAFE_INTEGER;
}
return b >= f._min && b <= f._max;
};
/** Returns v if v appears to be a TypedArray, else false. */ /** Returns v if v appears to be a TypedArray, else false. */
const isTypedArray = (v)=>{ const isTypedArray = (v)=>{
@ -288,29 +317,52 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
The semantics of JS functions are: The semantics of JS functions are:
xFunc: is passed `(arrayOfValues)`. Its return value becomes xFunc: is passed `(pCtx, ...values)`. Its return value becomes
the new SQL function's result. the new SQL function's result.
xStep: is passed `(arrayOfValues)`. Its return value is xStep: is passed `(pCtx, ...values)`. Its return value is
ignored. ignored.
xFinal: is passed no arguments. Its return value becomes the xFinal: is passed `(pCtx)`. Its return value becomes the new
new aggragate SQL function's result. aggregate SQL function's result.
xDestroy: is passed `(void*)`. Its return value is ignored. The xDestroy: is passed `(void*)`. Its return value is ignored. The
pointer passed to it is the one from the 5th argument to pointer passed to it is the one from the 5th argument to
sqlite3_create_function_v2(). sqlite3_create_function_v2().
Note that JS callback implementations have different call Note that:
signatures than their native counterparts (namely, they do not
get passed an `sqlite3_context*` argument) because practice has - `pCtx` in the above descriptions is a `sqlite3_context*`. 99
shown that this is almost always more convenient and desirable times out of a hundred, or maybe more, that initial argument
in JS code. Clients which need access to the full range of will be irrelevant for JS UDF bindings, but it needs to be
native arguments will have to create a WASM-bound function there so that the cases where it _is_ relevant, in particular
themselves (using wasm.installFunction() or equivalent) and with window and aggregate functions, have full access to the
pass that function's WASM pointer to this function, rather than underlying sqlite3 APIs.
passing a JS function. Be warned, however, that working with
UDFs at that level from JS is quite tedious. - When wrapping JS functions, the remaining arguments arrive as
positional arguments, not as an array of arguments, because
that allows callback definitions to be more JS-idiomatic than
C-like, for example `(pCtx,a,b)=>a+b` is more intuitive and
legible than `(pCtx,args)=>args[0]+args[1]`. For cases where
an array of arguments would be more convenient, the callbacks
simply need to be declared like `(pCtx,...args)=>{...}`, in
which case `args` will be an array.
- If a JS wrapper throws, it gets translated to
sqlite3_result_error() or sqlite3_result_error_nomem(),
depending on whether the exception is an
sqlite3.WasmAllocError object or not.
- When passing on WASM function pointers, arguments are _not_
converted or reformulated. They are passed on as-is in raw
pointer form using their native C signatures. Only JS
functions passed in to this routine, and thus wrapped by this
routine, get automatic conversions of arguments and result
values. The routines which perform those conversions are
exposed for client-side use as
sqlite3_create_function_v2.convertUdfArgs() and
sqlite3_create_function_v2.setUdfResult(). sqlite3_create_function()
and sqlite3_create_window_function() have those same methods.
For xFunc(), xStep(), and xFinal(): For xFunc(), xStep(), and xFinal():
@ -323,19 +375,26 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
doubles. TODO: use BigInt support if enabled. That feature doubles. TODO: use BigInt support if enabled. That feature
was added after this functionality was implemented. was added after this functionality was implemented.
If any JS-side functions throw, those exceptions are If any JS-side bound functions throw, those exceptions are
intercepted and converted to database-side errors with intercepted and converted to database-side errors with the
the exception of xFinal(): any exception from it is exception of xDestroy(): any exception from it is ignored,
ignored, possibly generating a console.error() message. possibly generating a console.error() message. Destructors
Destructors must not throw. must not throw.
Once installed, there is currently no way to uninstall the Once installed, there is currently no way to uninstall the
bound methods from WASM. They can be uninstalled from the automatically-converted WASM-bound JS functions from WASM. They
database as documented in the C API, but this wrapper currently can be uninstalled from the database as documented in the C
has no infrastructure in place to also free the WASM-bound JS API, but this wrapper currently has no infrastructure in place
wrappers, effectively resulting in a memory leak if the client to also free the WASM-bound JS wrappers, effectively resulting
uninstalls the UDF. Improving that is a potential TODO, but in a memory leak if the client uninstalls the UDF. Improving that
removing client-installed UDFs is rare in practice. is a potential TODO, but removing client-installed UDFs is rare
in practice. If this factor is relevant for a given client,
they can create WASM-bound JS functions themselves, hold on to their
pointers, and pass the pointers in to here. Later on, they can
free those pointers (using `wasm.uninstallFunction()` or
equivalent).
C reference: https://www.sqlite.org/c3ref/create_function.html
Maintenance reminder: the ability to add new Maintenance reminder: the ability to add new
WASM-accessible functions to the runtime requires that the WASM-accessible functions to the runtime requires that the
@ -344,10 +403,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/ */
sqlite3_create_function_v2: function( sqlite3_create_function_v2: function(
pDb, funcName, nArg, eTextRep, pApp, pDb, funcName, nArg, eTextRep, pApp,
xFunc, //function(arrayOfValues) xFunc, xStep, xFinal, xDestroy
xStep, //function(arrayOfValues)
xFinal, //function()
xDestroy //function(void*)
){/*installed later*/}, ){/*installed later*/},
/** /**
Equivalent to passing the same arguments to Equivalent to passing the same arguments to
@ -355,9 +411,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/ */
sqlite3_create_function:function( sqlite3_create_function:function(
pDb, funcName, nArg, eTextRep, pApp, pDb, funcName, nArg, eTextRep, pApp,
xFunc, //function(arrayOfValues) xFunc, xStep, xFinal
xStep, //function(arrayOfValues)
xFinal //function()
){/*installed later*/}, ){/*installed later*/},
/** /**
The sqlite3_create_window_function() JS wrapper differs from The sqlite3_create_window_function() JS wrapper differs from
@ -368,11 +422,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/ */
sqlite3_create_window_function: function( sqlite3_create_window_function: function(
pDb, funcName, nArg, eTextRep, pApp, pDb, funcName, nArg, eTextRep, pApp,
xStep, //function(arrayOfValues) xStep, xFinal, xValue, xInverse, xDestroy
xFinal, //function()
xValue, //function()
xInverse,//function(arrayOfValues)
xDestroy //function(void*)
){/*installed later*/}, ){/*installed later*/},
/** /**
The sqlite3_prepare_v3() binding handles two different uses The sqlite3_prepare_v3() binding handles two different uses
@ -466,7 +516,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
removed. removed.
*/ */
util:{ util:{
affirmBindableTypedArray, arrayToString, isBindableTypedArray, affirmBindableTypedArray, arrayToString,
bigIntFits32, bigIntFits64, bigIntFitsDouble,
isBindableTypedArray,
isInt32, isSQLableTypedArray, isTypedArray, isInt32, isSQLableTypedArray, isTypedArray,
typedArrayToString, typedArrayToString,
isMainWindow: ()=>{ isMainWindow: ()=>{
@ -680,6 +732,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
*/ */
capi.wasm.bindingSignatures = [ capi.wasm.bindingSignatures = [
// Please keep these sorted by function name! // Please keep these sorted by function name!
["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"],
["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*" ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"
/* We should arguably write a custom wrapper which knows how /* We should arguably write a custom wrapper which knows how
to handle Blob, TypedArrays, and JS strings. */ to handle Blob, TypedArrays, and JS strings. */
@ -766,11 +819,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
["sqlite3_uri_boolean", "int", "string", "string", "int"], ["sqlite3_uri_boolean", "int", "string", "string", "int"],
["sqlite3_uri_key", "string", "string", "int"], ["sqlite3_uri_key", "string", "string", "int"],
["sqlite3_uri_parameter", "string", "string", "string"], ["sqlite3_uri_parameter", "string", "string", "string"],
["sqlite3_value_blob", "*", "*"], ["sqlite3_user_data","void*", "sqlite3_context*"],
["sqlite3_value_bytes","int", "*"], ["sqlite3_value_blob", "*", "sqlite3_value*"],
["sqlite3_value_double","f64", "*"], ["sqlite3_value_bytes","int", "sqlite3_value*"],
["sqlite3_value_text", "string", "*"], ["sqlite3_value_double","f64", "sqlite3_value*"],
["sqlite3_value_type", "int", "*"], ["sqlite3_value_int","int", "sqlite3_value*"],
["sqlite3_value_text", "string", "sqlite3_value*"],
["sqlite3_value_type", "int", "sqlite3_value*"],
["sqlite3_vfs_find", "*", "string"], ["sqlite3_vfs_find", "*", "string"],
["sqlite3_vfs_register", "int", "*", "int"] ["sqlite3_vfs_register", "int", "*", "int"]
]/*capi.wasm.bindingSignatures*/; ]/*capi.wasm.bindingSignatures*/;
@ -794,8 +849,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
["sqlite3_malloc64", "*","i64"], ["sqlite3_malloc64", "*","i64"],
["sqlite3_msize", "i64", "*"], ["sqlite3_msize", "i64", "*"],
["sqlite3_realloc64", "*","*", "i64"], ["sqlite3_realloc64", "*","*", "i64"],
["sqlite3_result_int64",undefined, "*", "i64"],
["sqlite3_total_changes64", "i64", ["sqlite3*"]], ["sqlite3_total_changes64", "i64", ["sqlite3*"]],
["sqlite3_uri_int64", "i64", ["string", "string", "i64"]] ["sqlite3_uri_int64", "i64", ["string", "string", "i64"]],
["sqlite3_value_int64","i64", "sqlite3_value*"],
]; ];
/** /**

View File

@ -160,7 +160,7 @@
log("Create a scalar UDF..."); log("Create a scalar UDF...");
db.createFunction({ db.createFunction({
name: 'twice', name: 'twice',
callback: function(arg){ // note the call arg count callback: function(pCx, arg){ // note the call arg count
return arg + arg; return arg + arg;
} }
}); });

View File

@ -194,21 +194,21 @@
}/*testBasicSanity()*/; }/*testBasicSanity()*/;
const testUDF = function(db){ const testUDF = function(db){
db.createFunction("foo",function(a,b){return a+b}); db.createFunction("foo",(pCx,a,b)=>a+b);
T.assert(7===db.selectValue("select foo(3,4)")). T.assert(7===db.selectValue("select foo(3,4)")).
assert(5===db.selectValue("select foo(3,?)",2)). assert(5===db.selectValue("select foo(3,?)",2)).
assert(5===db.selectValue("select foo(?,?2)",[1,4])). assert(5===db.selectValue("select foo(?,?2)",[1,4])).
assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5})); assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
db.createFunction("bar", { db.createFunction("bar", {
arity: -1, arity: -1,
callback: function(){ callback: function(pCx){
var rc = 0; var rc = 0;
for(let i = 0; i < arguments.length; ++i) rc += arguments[i]; for(let i = 1; i < arguments.length; ++i) rc += arguments[i];
return rc; return rc;
} }
}).createFunction({ }).createFunction({
name: "asis", name: "asis",
callback: (arg)=>arg callback: (pCx,arg)=>arg
}); });
//log("Testing DB::selectValue() w/ UDF..."); //log("Testing DB::selectValue() w/ UDF...");
@ -216,16 +216,15 @@
assert(1===db.selectValue("select bar(1)")). assert(1===db.selectValue("select bar(1)")).
assert(3===db.selectValue("select bar(1,2)")). assert(3===db.selectValue("select bar(1,2)")).
assert(-1===db.selectValue("select bar(1,2,-4)")). assert(-1===db.selectValue("select bar(1,2,-4)")).
assert('hi'===db.selectValue("select asis('hi')")); assert('hi' === db.selectValue("select asis('hi')")).
assert('hi' === db.selectValue("select ?",'hi')).
T.assert('hi' === db.selectValue("select ?",'hi')). assert(null === db.selectValue("select null")).
assert(null===db.selectValue("select null")). assert(null === db.selectValue("select asis(null)")).
assert(null === db.selectValue("select ?",null)). assert(1 === db.selectValue("select ?",1)).
assert(null === db.selectValue("select ?",[null])). assert(2 === db.selectValue("select ?",[2])).
assert(null === db.selectValue("select $a",{$a:null})). assert(3 === db.selectValue("select $a",{$a:3})).
assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))). assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1"))).
assert(eqApprox(1.3,db.selectValue("select asis(1 + 0.3)"))) assert(eqApprox(1.3,db.selectValue("select asis(1 + 0.3)")));
;
//log("Testing binding and UDF propagation of blobs..."); //log("Testing binding and UDF propagation of blobs...");
let blobArg = new Uint8Array(2); let blobArg = new Uint8Array(2);
@ -354,7 +353,7 @@
T.assert(g64(pMin) === minMaxI64[0]). T.assert(g64(pMin) === minMaxI64[0]).
assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))). assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax))); assert(minMaxI64[1] === db.selectValue("select ?",g64(pMax)));
const rxRange = /out of range for storing as int64/; const rxRange = /too big/;
T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))}, T.mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[0] - BigInt(1))},
rxRange). rxRange).
mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))}, mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},

View File

@ -1,5 +1,5 @@
C JS:\sclean\sup\screate_function()\swrapper\sand\sadd\ssupport\sfor\screate_window_function().\sEliminate\san\sextraneous\sblob\scopy\swhen\sa\sUDF\sreturns\sa\sblob.\sMake\suse\sof\snewfound\sJS-fu\sto\sclean\sup\show\ssqlite3ApiBootstrap()\sconfig\sis\sinitialized. C More\scleanups\sin\sthe\sUDF\sargument\sand\sresult\shandling,\sin\sparticular\sint64.\sConsolidate\ssome\sduplicate\sint64/bigint\srange\schecking\scode.\sExpose\sthe\sUDF\slow-level\sutilities\s(arg/result\sconversion)\sto\sclient\scode.\sAdd\sthe\ssqlite3_context\spointer\sto\sthe\sJS-side\sUDF\swrappers\sfor\sAPI\sconsistency.
D 2022-10-02T20:08:53.027 D 2022-10-02T22:50:04.828
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -476,7 +476,7 @@ F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34ce
F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle 0e88c8cfc3719e4b7e74980d9da664c709e68acf863e48386cda376edfd3bfb0 F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle 0e88c8cfc3719e4b7e74980d9da664c709e68acf863e48386cda376edfd3bfb0
F ext/wasm/GNUmakefile b313a82060c733c990b91afa981e10f5e21a0b33a483f33b739ce932ed6bc725 F ext/wasm/GNUmakefile b313a82060c733c990b91afa981e10f5e21a0b33a483f33b739ce932ed6bc725
F ext/wasm/README.md 1e5b28158b74ab3ffc9d54fcbc020f0bbeb82c2ff8bbd904214c86c70e8a3066 F ext/wasm/README.md 1e5b28158b74ab3ffc9d54fcbc020f0bbeb82c2ff8bbd904214c86c70e8a3066
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api e35ddfbfcde83571a1169a910dbcb59bf598a3a8f5283b42d88555b9ccaa6042 F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 9b16040f37805ee7c30f922a970a57d3f2a822d0675a8f5d70f15061e300c4ce
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
F ext/wasm/api/README.md 1e350b611465566cfa2e5eccf7c9b29a34f48ee38bbf6d5fb086dd06ce32b3ff F ext/wasm/api/README.md 1e350b611465566cfa2e5eccf7c9b29a34f48ee38bbf6d5fb086dd06ce32b3ff
F ext/wasm/api/extern-post-js.js dc68cbf552d8ea085181400a6963907c32e0b088b03ffd8969b1869fea246629 F ext/wasm/api/extern-post-js.js dc68cbf552d8ea085181400a6963907c32e0b088b03ffd8969b1869fea246629
@ -485,10 +485,10 @@ F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba814
F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1ecc5ba9e64ef90a8b F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1ecc5ba9e64ef90a8b
F ext/wasm/api/pre-js.js 2db711eb637991b383fc6b5c0f3df65ec48a7201e5730e304beba8de2d3f9b0b F ext/wasm/api/pre-js.js 2db711eb637991b383fc6b5c0f3df65ec48a7201e5730e304beba8de2d3f9b0b
F ext/wasm/api/sqlite3-api-cleanup.js 5d22d1d3818ecacb23bfa223d5970cd0617d8cdbb48c8bc4bbd463f05b021a99 F ext/wasm/api/sqlite3-api-cleanup.js 5d22d1d3818ecacb23bfa223d5970cd0617d8cdbb48c8bc4bbd463f05b021a99
F ext/wasm/api/sqlite3-api-glue.js d1587736ed73fcb44e32f1ff1933e4c91a2d3b3c39acef0d13c0b3fd6859a7b1 F ext/wasm/api/sqlite3-api-glue.js b962bad752b62366651dae26c0b969d297f81e17879685025fb12130786509cb
F ext/wasm/api/sqlite3-api-oo1.js 48d2269544301cd97726ef2d9a82e4384350d12dcf832fa417f211811ae5272d F ext/wasm/api/sqlite3-api-oo1.js 484f9ea5c7140d07745f4b534a1f6dd67120c65ef34abcf7cdb3a388d73f5ef4
F ext/wasm/api/sqlite3-api-opfs.js 1b097808b7b081b0f0700cf97d49ef19760e401706168edff9cd45cf9169f541 F ext/wasm/api/sqlite3-api-opfs.js 1b097808b7b081b0f0700cf97d49ef19760e401706168edff9cd45cf9169f541
F ext/wasm/api/sqlite3-api-prologue.js d71ad817cdef8a9b3b64a394b781a8f64872d4983eac583167e29f9f96ef8e4e F ext/wasm/api/sqlite3-api-prologue.js bf270c17e759814decf57f6dd29fee9b5e44dd89a798a1ba9ba1e34d6f76ceaf
F ext/wasm/api/sqlite3-api-worker1.js 7f4f46cb6b512a48572d7567233896e6a9c46570c44bdc3d13419730c7c221c8 F ext/wasm/api/sqlite3-api-worker1.js 7f4f46cb6b512a48572d7567233896e6a9c46570c44bdc3d13419730c7c221c8
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 2a0f9e4bf1b141a787918951360601128d6a0a190a31a8e5cfe237c99fa640c6 F ext/wasm/api/sqlite3-wasm.c 2a0f9e4bf1b141a787918951360601128d6a0a190a31a8e5cfe237c99fa640c6
@ -500,7 +500,7 @@ F ext/wasm/common/testing.css 3a5143699c2b73a85b962271e1a9b3241b30d90e30d895e4f5
F ext/wasm/common/whwasmutil.js 427eb8b695bd5f38497601a6bda933e83d1a5900b75f5f1af8dbb381898d2ee4 F ext/wasm/common/whwasmutil.js 427eb8b695bd5f38497601a6bda933e83d1a5900b75f5f1af8dbb381898d2ee4
F ext/wasm/demo-123-worker.html e419b66495d209b5211ec64903b4cfb3ca7df20d652b41fcd28bf018a773234f F ext/wasm/demo-123-worker.html e419b66495d209b5211ec64903b4cfb3ca7df20d652b41fcd28bf018a773234f
F ext/wasm/demo-123.html aa281d33b7eefa755f3122b7b5a18f39a42dc5fb69c8879171bf14b4c37c4ec4 F ext/wasm/demo-123.html aa281d33b7eefa755f3122b7b5a18f39a42dc5fb69c8879171bf14b4c37c4ec4
F ext/wasm/demo-123.js 536579fd587974c2511c5bf82034b253d4fdeceabb726927ad7599ef6b7578e8 F ext/wasm/demo-123.js 9fbc5cd3af842d361e9f8353ae4af9f471c2b2517e55446474406620485b9ee6
F ext/wasm/demo-kvvfs1.html 7d4f28873de67f51ac18c584b7d920825139866a96049a49c424d6f5a0ea5e7f F ext/wasm/demo-kvvfs1.html 7d4f28873de67f51ac18c584b7d920825139866a96049a49c424d6f5a0ea5e7f
F ext/wasm/demo-kvvfs1.js 105596bd2ccd0b1deb5fde8e99b536e8242d4bb5932fac0c8403ff3a6bc547e8 F ext/wasm/demo-kvvfs1.js 105596bd2ccd0b1deb5fde8e99b536e8242d4bb5932fac0c8403ff3a6bc547e8
F ext/wasm/fiddle.make 3f4efd62bc2a9c883bfcea52ae2755114a62d444d6d042df287f4aef301d6c6c F ext/wasm/fiddle.make 3f4efd62bc2a9c883bfcea52ae2755114a62d444d6d042df287f4aef301d6c6c
@ -530,7 +530,7 @@ F ext/wasm/test-opfs-vfs.js a59ff9210b17d46b0c6fbf6a0ba60143c033327865f2e556e14f
F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893 F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
F ext/wasm/testing-worker1-promiser.js bd788e33c1807e0a6dda9c9a9d784bd3350ca49c9dd8ae2cc8719b506b6e013e F ext/wasm/testing-worker1-promiser.js bd788e33c1807e0a6dda9c9a9d784bd3350ca49c9dd8ae2cc8719b506b6e013e
F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae
F ext/wasm/testing1.js bdea170b16189028c1f63023c620df52ddf31ed416bad56d729c60031b1e27ae F ext/wasm/testing1.js 0f87073086eff3a152f013874f1c9a710e63d2e069f90dfeb8333ffe82476d04
F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3 F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
F ext/wasm/testing2.js 88f40ef3cd8201bdadd120a711c36bbf0ce56cc0eab1d5e7debb71fed7822494 F ext/wasm/testing2.js 88f40ef3cd8201bdadd120a711c36bbf0ce56cc0eab1d5e7debb71fed7822494
F ext/wasm/wasmfs.make 3cce1820006196de140f90f2da4b4ea657083fb5bfee7d125be43f7a85748c8f F ext/wasm/wasmfs.make 3cce1820006196de140f90f2da4b4ea657083fb5bfee7d125be43f7a85748c8f
@ -2029,8 +2029,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 435ab33384017967e46f52b70bee851a85a28808990a0e58dd5288f606b89c9c P d3bad9347c5423fa7f19ae729461636f1043c99a1f01f168efa10bebefb1cdd1
R 8d670d2cae99839795cb81bda9741460 R 46b070d886b6400d4ed82000c86b9d6e
U stephan U stephan
Z 74556b5dc28de703cf507dc7ef6550bd Z fc77e81a038294d78aff4ec7221b0be3
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
d3bad9347c5423fa7f19ae729461636f1043c99a1f01f168efa10bebefb1cdd1 10ab77af952abf09f93f342a9d07a3b133f2c4c0a3588df3390cd3a923cafae4