1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-27 20:41:58 +03:00

Add aggregate function support to sqlite3.oo1.DB.createFunction(). Change signature of the options object used by that function so that the callback property names match those of the corresponding C APIs.

FossilOrigin-Name: a7db6e4b50beebfb1c97e0c4de49538d8199c166b18a0b1b175736c593128a00
This commit is contained in:
stephan
2022-10-16 18:50:55 +00:00
parent 8ffc98999d
commit 824bb5b8db
5 changed files with 151 additions and 49 deletions

View File

@ -45,8 +45,10 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/** If object opts has _its own_ property named p then that
property's value is returned, else dflt is returned. */
const getOwnOption = (opts, p, dflt)=>
opts.hasOwnProperty(p) ? opts[p] : dflt;
const getOwnOption = (opts, p, dflt)=>{
const d = Object.getOwnPropertyDescriptor(opts,p);
return d ? d.value : dflt;
};
// Documented in DB.checkRc()
const checkSqlite3Rc = function(dbPtr, sqliteResultCode){
@ -810,12 +812,14 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`xFunc` to align with the C API documentation). In the final
case, the function's name must be the 'name' property.
This can currently only be used to create scalar functions, not
aggregate or window functions (requires only a bit of
refactoring to support aggregates and window functions).
The first two call forms can only be used for creating scalar
functions. Creating an aggregate function requires the
options-object form (see below for details).
UDFs cannot currently be removed from a DB handle after they're
added.
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.
On success, returns this object. Throws on error.
@ -825,18 +829,35 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
cannot be determined. The docs for sqlite3_create_function_v2()
describe the conversions in more detail.
The values set in the options object differ for scalar and
aggregate functions:
- Scalar: set the `xFunc` function-type property to the UDF
function.
- Aggregate: set the `xStep` and `xFinal` function-type
properties to the "step" and "final" callbacks for the
aggregate. Do not set the `xFunc` property.
The options object may optionally have an `xDestroy`
function-type property, as per
sqlite3_create_function_v2(). Its argument will be the
WASM-pointer-type value of the `pApp` property, and this
function will throw if `pApp` is defined but is not null,
undefined, or a numeric (WASM pointer) value.
The optional options object may contain flags to modify how
the function is defined:
- .arity: the number of arguments which SQL calls to this
function expect or require. The default value is
`callback.length` (i.e. the number of declared parameters it
has) **MINUS 1** (see below for why). As a special case, if
callback.length is 0, its arity is also 0 instead of -1. A
negative arity value means that the function is variadic and
may accept any number of arguments, up to sqlite3's
compile-time limits. sqlite3 will enforce the argument count if
is zero or greater.
- `arity`: the number of arguments which SQL calls to this
function expect or require. The default value is `xFunc.length`
or `xStep.length` (i.e. the number of declared parameters it
has) **MINUS 1** (see below for why). As a special case, if the
`length` is 0, its arity is also 0 instead of -1. A negative
arity value means that the function is variadic and may accept
any number of arguments, up to sqlite3's compile-time
limits. sqlite3 will enforce the argument count if is zero or
greater.
The callback always receives a pointer to an `sqlite3_context`
object as its first argument. Any arguments after that are from
@ -852,44 +873,70 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
- .deterministic = SQLITE_DETERMINISTIC
- .directOnly = SQLITE_DIRECTONLY
- .innocuous = SQLITE_INNOCUOUS
TODO: for the (optionsObject) form, accept callbacks for
aggregate and window functions.
*/
createFunction: function f(name, callback, opt){
createFunction: function f(name, xFunc, opt){
let xStep, xFinal;
const isFunc = (f)=>(f instanceof Function);
switch(arguments.length){
case 1: /* (optionsObject) */
opt = name;
name = opt.name;
callback = opt.xFunc || opt.callback;
xFunc = opt.xFunc;
break;
case 2: /* (name, callback|optionsObject) */
if(!(callback instanceof Function)){
opt = callback;
callback = opt.xFunc || opt.callback;
if(!isFunc(xFunc)){
opt = xFunc;
xFunc = opt.xFunc;
}
break;
case 3: /* name, xFunc, opt */
break;
default: break;
}
if(!opt) opt = {};
if(!(callback instanceof Function)){
toss3("Invalid arguments: expecting a callback function.");
}else if('string' !== typeof name){
if('string' !== typeof name){
toss3("Invalid arguments: missing function name.");
}
xStep = opt.xStep;
xFinal = opt.xFinal;
if(isFunc(xFunc)){
if(isFunc(xStep) || isFunc(xFinal)){
toss3("Ambiguous arguments: scalar or aggregate?");
}
xStep = xFinal = null;
}else if(isFunc(xStep)){
if(!isFunc(xFinal)){
toss3("Missing xFinal() callback for aggregate UDF.");
}
xFunc = null;
}else if(isFunc(xFinal)){
toss3("Missing xStep() callback for aggregate UDF.");
}else{
toss3("Missing function-type properties.");
}
const pApp = opt.pApp;
if(undefined!==pApp && (('number'!==typeof pApp)
|| !capi.util.isInt32(pApp))){
toss3("Invalid value for pApp property. Must be a legal WASM pointer value.");
}
const xDestroy = opt.xDestroy;
if(xDestroy && !isFunc(xDestroy)){
toss3("xDestroy property must be a function.");
}
let fFlags = 0 /*flags for sqlite3_create_function_v2()*/;
if(getOwnOption(opt, 'deterministic')) fFlags |= capi.SQLITE_DETERMINISTIC;
if(getOwnOption(opt, 'directOnly')) fFlags |= capi.SQLITE_DIRECTONLY;
if(getOwnOption(opt, 'innocuous')) fFlags |= capi.SQLITE_INNOCUOUS;
name = name.toLowerCase();
const xArity = xFunc || xStep;
const arity = getOwnOption(opt, 'arity');
DB.checkRc(this, capi.sqlite3_create_function_v2(
this.pointer, name,
(opt.hasOwnProperty('arity')
? +opt.arity
: (callback.length ? callback.length-1/*for pCtx arg*/ : 0)),
capi.SQLITE_UTF8 | fFlags, null/*pApp*/, callback,
null/*xStep*/, null/*xFinal*/, null/*xDestroy*/));
('number'===typeof arity
? arity
: (xArity.length ? xArity.length-1/*for pCtx arg*/ : 0)),
capi.SQLITE_UTF8 | fFlags, pApp,
xFunc, xStep, xFinal, xDestroy));
return this;
}/*createFunction()*/,
/**