diff options
author | stephan <stephan@noemail.net> | 2022-10-16 18:50:55 +0000 |
---|---|---|
committer | stephan <stephan@noemail.net> | 2022-10-16 18:50:55 +0000 |
commit | 824bb5b8db88d064640a8e9d72b2204b916433e0 (patch) | |
tree | ef8c990a2b41a3d951a2f1047ba789e12385a802 /ext/wasm/api/sqlite3-api-oo1.js | |
parent | 8ffc98999d4fae6598aff02cabf63ba4c3829c95 (diff) | |
download | sqlite-824bb5b8db88d064640a8e9d72b2204b916433e0.tar.gz sqlite-824bb5b8db88d064640a8e9d72b2204b916433e0.zip |
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
Diffstat (limited to 'ext/wasm/api/sqlite3-api-oo1.js')
-rw-r--r-- | ext/wasm/api/sqlite3-api-oo1.js | 111 |
1 files changed, 79 insertions, 32 deletions
diff --git a/ext/wasm/api/sqlite3-api-oo1.js b/ext/wasm/api/sqlite3-api-oo1.js index fa6c6d8e5..f77540675 100644 --- a/ext/wasm/api/sqlite3-api-oo1.js +++ b/ext/wasm/api/sqlite3-api-oo1.js @@ -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()*/, /** |