1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +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()*/,
/**

View File

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

View File

@ -1179,14 +1179,14 @@
assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
db.createFunction("bar", {
arity: -1,
callback: function(pCx){
var rc = 0;
for(let i = 1; i < arguments.length; ++i) rc += arguments[i];
xFunc: (pCx,...args)=>{
let rc = 0;
for(const v of args) rc += v;
return rc;
}
}).createFunction({
name: "asis",
callback: (pCx,arg)=>arg
xFunc: (pCx,arg)=>arg
});
T.assert(0===db.selectValue("select bar()")).
assert(1===db.selectValue("select bar(1)")).
@ -1225,9 +1225,64 @@
////////////////////////////////////////////////////////////////////
.t({
name: 'Aggregate UDFs (tests are TODO)',
predicate: testIsTodo
})
name: 'Aggregate UDFs',
test: function(sqlite3){
const db = this.db;
const aggState = {summer: 0, summerN: 0};
db.createFunction({
name: 'summer',
xStep: function(pCtx, n){
aggState.summer += n;
},
xFinal: function(pCtx){
const rc = aggState.summer;
aggState.summer = 0;
return rc;
}
});
let v = db.selectValue([
"with cte(v) as (",
"select 3 union all select 5 union all select 7",
") select summer(v) from cte"
]);
T.assert(15===v);
T.mustThrowMatching(()=>db.selectValue("select summer(1,2)"),
/wrong number of arguments/);
db.createFunction({
name: 'summerN',
arity: -1,
xStep: function(pCtx, ...args){
for(const v of args) aggState.summerN += v;
},
xFinal: function(pCtx){
const rc = aggState.summerN;
aggState.summerN = 0;
return rc;
}
});
T.assert(18===db.selectValue('select summerN(1,8,9)'));
T.mustThrowMatching(()=>{
db.createFunction('nope',{
xFunc: ()=>{}, xStep: ()=>{}
});
}, /scalar or aggregate\?/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xStep: ()=>{}});
}, /Missing xFinal/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xFinal: ()=>{}});
}, /Missing xStep/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{});
}, /Missing function-type properties/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xFunc:()=>{}, xDestroy:'nope'});
}, /xDestroy property must be a function/);
T.mustThrowMatching(()=>{
db.createFunction('nope',{xFunc:()=>{}, pApp:'nope'});
}, /Invalid value for pApp/);
}
}/*aggregate UDFs*/)
////////////////////////////////////////////////////////////////////
.t({

View File

@ -1,5 +1,5 @@
C JS:\sadd\sbuild-time-generated\sversion\sinfo\sto\sthe\ssqlite3.version\sobject.\sRemove\ssome\sstray\sdebug\soutput\sfrom\stester1.js.
D 2022-10-16T16:38:15.159
C Add\saggregate\sfunction\ssupport\sto\ssqlite3.oo1.DB.createFunction().\sChange\ssignature\sof\sthe\soptions\sobject\sused\sby\sthat\sfunction\sso\sthat\sthe\scallback\sproperty\snames\smatch\sthose\sof\sthe\scorresponding\sC\sAPIs.
D 2022-10-16T18:50:55.647
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -485,7 +485,7 @@ F ext/wasm/api/post-js-header.js 2e5c886398013ba2af88028ecbced1e4b22dc96a86467f1
F ext/wasm/api/pre-js.js 5b550904322d73127badd4347ca967ea525b901573559736f92d326ad9b7bb76
F ext/wasm/api/sqlite3-api-cleanup.js 4d07a7524dc9b7b050acfde57163e839243ad2383bd7ee0de0178b1b3e988588
F ext/wasm/api/sqlite3-api-glue.js 05eb701460bb72edbe3bf923bd51262551614612c37802fc597eabb4c6b83232
F ext/wasm/api/sqlite3-api-oo1.js 00f5cfce0989d2e08d7b21765d703c69234245d03a0cce8fcb32ccfcd53ffdbb
F ext/wasm/api/sqlite3-api-oo1.js f7f1fa6e6364347e99d0619ac1d3766dabab1e114cdf343259f38372576b6650
F ext/wasm/api/sqlite3-api-opfs.js 5a8ab3b76880c8ada8710ca9ba1ca5b160872edfd8bd5322e4f179a7f41cc616
F ext/wasm/api/sqlite3-api-prologue.js a17b35814c6399a2e69c7836e5fd2eaa71f755ee51f96cb69d68cbf99985d45b
F ext/wasm/api/sqlite3-api-worker1.js 7f4f46cb6b512a48572d7567233896e6a9c46570c44bdc3d13419730c7c221c8
@ -500,7 +500,7 @@ F ext/wasm/common/testing.css 53394885077edd3db22d2a0896192334dfc06fb3d1da0b646e
F ext/wasm/common/whwasmutil.js 50d2ede0b0fa01c1d467e1801fab79f5e46bb02bcbd2b0232e4fdc6090a47818
F ext/wasm/demo-123-worker.html e50b51dc7271b9d3cc830cb7c2fba294d622f56b7acb199f7257d11195a63d49
F ext/wasm/demo-123.html 7c239c9951d1b113f9f532969ac039294cf1dcfee2b3ae0a2c1ed2b3d59f8dfa
F ext/wasm/demo-123.js d563cf9d725692ccd940c46df1c026d87863e0544942a2ba2015f17fba3f6f74
F ext/wasm/demo-123.js e0cbeb3495e14103763d5c49794a24d67cf3d78e0ed5b82843be70c0c2ee4b3b
F ext/wasm/demo-kvvfs1.html 7d4f28873de67f51ac18c584b7d920825139866a96049a49c424d6f5a0ea5e7f
F ext/wasm/demo-kvvfs1.js 105596bd2ccd0b1deb5fde8e99b536e8242d4bb5932fac0c8403ff3a6bc547e8
F ext/wasm/fiddle.make 3f4efd62bc2a9c883bfcea52ae2755114a62d444d6d042df287f4aef301d6c6c
@ -527,7 +527,7 @@ F ext/wasm/test-opfs-vfs.html eb69dda21eb414b8f5e3f7c1cc0f774103cc9c0f87b2d28a33
F ext/wasm/test-opfs-vfs.js 56c3d725044c668fa7910451e96c1195d25ad95825f9ac79f747a7759d1973d0
F ext/wasm/tester1-worker.html 0af7a22025ff1da72a84765d64f8f221844a57c6e6e314acf3a30f176101fd3f
F ext/wasm/tester1.html fde0e0bdeaaa2c39877c749dc86a8c1c306f771c3d75b89a6289a5ed11243e9d
F ext/wasm/tester1.js fd333bc7608a91c86ae6147aa489840c06f4131119cc26d66976874a50da7cab
F ext/wasm/tester1.js 8161dcc4b21902dadec2d3a5dc5700cab9c1641db0603e2ea56ea2a8de6cbab3
F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
F ext/wasm/testing-worker1-promiser.js bd788e33c1807e0a6dda9c9a9d784bd3350ca49c9dd8ae2cc8719b506b6e013e
F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae
@ -2033,8 +2033,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 0f1a06e8e39a1fbc74f1aff9cc59787282dfbf847d6c5c7edb3f7d410db0e4b7
R 33971fddc9aa38f4e82f55e933f782a8
P b5f462c2d85d503f6492ec20580d57cb4c926712f6306a6be764bd09d1f5e8b8
R dc71c7ee6cfc588ad89cef2f6b6a3e75
U stephan
Z 0dd1ff3eef43ed68b7aa15bcdfe291e6
Z 295c02a265fc3379708e7d138c173321
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
b5f462c2d85d503f6492ec20580d57cb4c926712f6306a6be764bd09d1f5e8b8
a7db6e4b50beebfb1c97e0c4de49538d8199c166b18a0b1b175736c593128a00