]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
More cleanups in the UDF argument and result handling, in particular int64. Consolida...
authorstephan <stephan@noemail.net>
Sun, 2 Oct 2022 22:50:04 +0000 (22:50 +0000)
committerstephan <stephan@noemail.net>
Sun, 2 Oct 2022 22:50:04 +0000 (22:50 +0000)
FossilOrigin-Name: 10ab77af952abf09f93f342a9d07a3b133f2c4c0a3588df3390cd3a923cafae4

ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api
ext/wasm/api/sqlite3-api-glue.js
ext/wasm/api/sqlite3-api-oo1.js
ext/wasm/api/sqlite3-api-prologue.js
ext/wasm/demo-123.js
ext/wasm/testing1.js
manifest
manifest.uuid

index 22ca64cf85c36d16b440c967c40648768229035b..014812946dfbf24cca9d61c7bb6837b905723111 100644 (file)
@@ -1,3 +1,4 @@
+_sqlite3_aggregate_context
 _sqlite3_bind_blob
 _sqlite3_bind_double
 _sqlite3_bind_int
@@ -61,6 +62,7 @@ _sqlite3_result_error_code
 _sqlite3_result_error_nomem
 _sqlite3_result_error_toobig
 _sqlite3_result_int
+_sqlite3_result_int64
 _sqlite3_result_null
 _sqlite3_result_text
 _sqlite3_serialize
@@ -76,9 +78,12 @@ _sqlite3_uri_boolean
 _sqlite3_uri_int64
 _sqlite3_uri_key
 _sqlite3_uri_parameter
+_sqlite3_user_data
 _sqlite3_value_blob
 _sqlite3_value_bytes
 _sqlite3_value_double
+_sqlite3_value_int
+_sqlite3_value_int64
 _sqlite3_value_text
 _sqlite3_value_type
 _sqlite3_vfs_find
index 5ca6f4ec224ca0a8828b6ffdc132f8bd6a4019e3..f80bade3852b1c57aa73252eff83b41fc1552113 100644 (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*/;
 
index f701c463f0e026846b7fb7c43c69195c139ca67c..e6685d35a766a83a87bb0b25717cfb411c25d64f 100644 (file)
@@ -784,12 +784,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
        the function is defined:
 
        - .arity: the number of arguments which SQL calls to this
-       function expect or require. The default value is the
-       callback's length property (i.e. the number of declared
-       parameters it has). A value of -1 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.
+       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.
+
+       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:
 
@@ -827,7 +836,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
       name = name.toLowerCase();
       DB.checkRc(this, capi.sqlite3_create_function_v2(
         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,
         null/*xStep*/, null/*xFinal*/, null/*xDestroy*/));
       return this;
@@ -992,10 +1003,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   const bindOne = function f(stmt,ndx,bindType,val){
     affirmUnlocked(stmt, 'bind()');
     if(!f._){
-      if(wasm.bigIntEnabled){
-        f._maxInt = BigInt("0x7fffffffffffffff");
-        f._minInt = ~f._maxInt;
-      }
+      f._tooBigInt = (v)=>toss3(
+        "BigInt value is too big to store without precision loss:", v
+      );
       /* Reminder: when not in BigInt mode, it's impossible for
          JS to represent a number out of the range we can bind,
          so we have no range checking. */
@@ -1041,15 +1051,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
           let m;
           if(util.isInt32(val)) m = capi.sqlite3_bind_int;
           else if('bigint'===typeof val){
-            if(val<f._minInt || val>f._maxInt){
-              toss3("BigInt value is out of range for storing as int64: "+val);
+            if(!util.bigIntFits64(val)){
+              f._tooBigInt(val);
             }else if(wasm.bigIntEnabled){
               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);
               m = capi.sqlite3_bind_double;
             }else{
-              toss3("BigInt value is out of range for storing as double: "+val);
+              f._tooBigInt(val);
             }
           }else{ // !int32, !bigint
             val = Number(val);
@@ -1298,8 +1308,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
           case capi.SQLITE_ROW: return this._mayGet = true;
           default:
             this._mayGet = false;
-            console.warn("sqlite3_step() rc=",rc,"SQL =",
-                         capi.sqlite3_sql(this.pointer));
+            console.warn("sqlite3_step() rc=",rc,
+                         capi.sqlite3_web_rc_str(rc),
+                         "SQL =", capi.sqlite3_sql(this.pointer));
             DB.checkRc(this.db.pointer, rc);
       }
     },
index 9f406994399d77edb12297eb6b88b7c4a0ba38a6..f21655b823d7785b0e3c1ec6e48125265e2d71bb 100644 (file)
@@ -176,10 +176,39 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
      double-type DB operations for integer values in order to keep
      more precision.
   */
-  const isInt32 = function(n){
+  const isInt32 = (n)=>{
     return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/)
       && !!(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. */
   const isTypedArray = (v)=>{
@@ -288,29 +317,52 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
 
        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.
 
-       xStep: is passed `(arrayOfValues)`. Its return value is
+       xStep: is passed `(pCtx, ...values)`. Its return value is
        ignored.
 
-       xFinal: is passed no arguments. Its return value becomes the
-       new aggragate SQL function's result.
+       xFinal: is passed `(pCtx)`. Its return value becomes the new
+       aggregate SQL function's result.
 
        xDestroy: is passed `(void*)`. Its return value is ignored. The
        pointer passed to it is the one from the 5th argument to
        sqlite3_create_function_v2().
 
-       Note that JS callback implementations have different call
-       signatures than their native counterparts (namely, they do not
-       get passed an `sqlite3_context*` argument) because practice has
-       shown that this is almost always more convenient and desirable
-       in JS code. Clients which need access to the full range of
-       native arguments will have to create a WASM-bound function
-       themselves (using wasm.installFunction() or equivalent) and
-       pass that function's WASM pointer to this function, rather than
-       passing a JS function. Be warned, however, that working with
-       UDFs at that level from JS is quite tedious.
+       Note that:
+
+       - `pCtx` in the above descriptions is a `sqlite3_context*`. 99
+         times out of a hundred, or maybe more, that initial argument
+         will be irrelevant for JS UDF bindings, but it needs to be
+         there so that the cases where it _is_ relevant, in particular
+         with window and aggregate functions, have full access to the
+         underlying sqlite3 APIs.
+
+       - 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():
 
@@ -323,19 +375,26 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
          doubles. TODO: use BigInt support if enabled. That feature
          was added after this functionality was implemented.
 
-       If any JS-side functions throw, those exceptions are
-       intercepted and converted to database-side errors with
-       the exception of xFinal(): any exception from it is
-       ignored, possibly generating a console.error() message.
-       Destructors must not throw.
+       If any JS-side bound functions throw, those exceptions are
+       intercepted and converted to database-side errors with the
+       exception of xDestroy(): any exception from it is ignored,
+       possibly generating a console.error() message.  Destructors
+       must not throw.
 
        Once installed, there is currently no way to uninstall the
-       bound methods from WASM. They can be uninstalled from the
-       database as documented in the C API, but this wrapper currently
-       has no infrastructure in place to also free the WASM-bound JS
-       wrappers, effectively resulting in a memory leak if the client
-       uninstalls the UDF. Improving that is a potential TODO, but
-       removing client-installed UDFs is rare in practice.
+       automatically-converted WASM-bound JS functions from WASM. They
+       can be uninstalled from the database as documented in the C
+       API, but this wrapper currently has no infrastructure in place
+       to also free the WASM-bound JS wrappers, effectively resulting
+       in a memory leak if the client uninstalls the UDF. Improving that
+       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
        WASM-accessible functions to the runtime requires that the
@@ -344,10 +403,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     */
     sqlite3_create_function_v2: function(
       pDb, funcName, nArg, eTextRep, pApp,
-      xFunc,   //function(arrayOfValues)
-      xStep,   //function(arrayOfValues)
-      xFinal,  //function()
-      xDestroy //function(void*)
+      xFunc, xStep, xFinal, xDestroy
     ){/*installed later*/},
     /**
        Equivalent to passing the same arguments to
@@ -355,9 +411,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     */
     sqlite3_create_function:function(
       pDb, funcName, nArg, eTextRep, pApp,
-      xFunc,   //function(arrayOfValues)
-      xStep,   //function(arrayOfValues)
-      xFinal   //function()
+      xFunc, xStep, xFinal
     ){/*installed later*/},
     /**
        The sqlite3_create_window_function() JS wrapper differs from
@@ -368,11 +422,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     */
     sqlite3_create_window_function: function(
       pDb, funcName, nArg, eTextRep, pApp,
-      xStep,   //function(arrayOfValues)
-      xFinal,  //function()
-      xValue,  //function()
-      xInverse,//function(arrayOfValues)
-      xDestroy //function(void*)
+      xStep, xFinal, xValue, xInverse, xDestroy
     ){/*installed later*/},
     /**
        The sqlite3_prepare_v3() binding handles two different uses
@@ -466,7 +516,9 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
        removed.
     */
     util:{
-      affirmBindableTypedArray, arrayToString, isBindableTypedArray,
+      affirmBindableTypedArray, arrayToString,
+      bigIntFits32, bigIntFits64, bigIntFitsDouble,
+      isBindableTypedArray,
       isInt32, isSQLableTypedArray, isTypedArray, 
       typedArrayToString,
       isMainWindow: ()=>{
@@ -680,6 +732,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
   */
   capi.wasm.bindingSignatures = [
     // Please keep these sorted by function name!
+    ["sqlite3_aggregate_context","void*", "sqlite3_context*", "int"],
     ["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"
      /* We should arguably write a custom wrapper which knows how
         to handle Blob, TypedArrays, and JS strings. */
@@ -766,11 +819,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     ["sqlite3_uri_boolean", "int", "string", "string", "int"],
     ["sqlite3_uri_key", "string", "string", "int"],
     ["sqlite3_uri_parameter", "string", "string", "string"],
-    ["sqlite3_value_blob", "*", "*"],
-    ["sqlite3_value_bytes","int", "*"],
-    ["sqlite3_value_double","f64", "*"],
-    ["sqlite3_value_text", "string", "*"],
-    ["sqlite3_value_type", "int", "*"],
+    ["sqlite3_user_data","void*", "sqlite3_context*"],
+    ["sqlite3_value_blob", "*", "sqlite3_value*"],
+    ["sqlite3_value_bytes","int", "sqlite3_value*"],
+    ["sqlite3_value_double","f64", "sqlite3_value*"],
+    ["sqlite3_value_int","int", "sqlite3_value*"],
+    ["sqlite3_value_text", "string", "sqlite3_value*"],
+    ["sqlite3_value_type", "int", "sqlite3_value*"],
     ["sqlite3_vfs_find", "*", "string"],
     ["sqlite3_vfs_register", "int", "*", "int"]
   ]/*capi.wasm.bindingSignatures*/;
@@ -794,8 +849,10 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
     ["sqlite3_malloc64", "*","i64"],
     ["sqlite3_msize", "i64", "*"],
     ["sqlite3_realloc64", "*","*", "i64"],
+    ["sqlite3_result_int64",undefined, "*", "i64"],
     ["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*"],
   ];
 
   /**
index 37dc7a7af1980bb480f8dd7c1ef848039a5411df..67c1094f02f7561ed7ca0f4fc27ce6c7414706f4 100644 (file)
       log("Create a scalar UDF...");
       db.createFunction({
         name: 'twice',
-        callback: function(arg){ // note the call arg count
+        callback: function(pCx, arg){ // note the call arg count
           return arg + arg;
         }
       });
index 053fb6f2773a71a3b4cf23fa5b49f75879abf652..7c20fdbbbd3e3f14e28894a48580deb4da02bf18 100644 (file)
   }/*testBasicSanity()*/;
 
   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)")).
       assert(5===db.selectValue("select foo(3,?)",2)).
       assert(5===db.selectValue("select foo(?,?2)",[1,4])).
       assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
     db.createFunction("bar", {
       arity: -1,
-      callback: function(){
+      callback: function(pCx){
         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;
       }
     }).createFunction({
       name: "asis",
-      callback: (arg)=>arg
+      callback: (pCx,arg)=>arg
     });
-    
+
     //log("Testing DB::selectValue() w/ UDF...");
     T.assert(0===db.selectValue("select bar()")).
       assert(1===db.selectValue("select bar(1)")).
       assert(3===db.selectValue("select bar(1,2)")).
       assert(-1===db.selectValue("select bar(1,2,-4)")).
-      assert('hi'===db.selectValue("select asis('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 ?",[null])).
-      assert(null === db.selectValue("select $a",{$a:null})).
+      assert('hi' === db.selectValue("select asis('hi')")).
+      assert('hi' === db.selectValue("select ?",'hi')).
+      assert(null === db.selectValue("select null")).
+      assert(null === db.selectValue("select asis(null)")).
+      assert(1 === db.selectValue("select ?",1)).
+      assert(2 === db.selectValue("select ?",[2])).
+      assert(3 === db.selectValue("select $a",{$a:3})).
       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...");
     let blobArg = new Uint8Array(2);
         T.assert(g64(pMin) === minMaxI64[0]).
           assert(minMaxI64[0] === db.selectValue("select ?",g64(pMin))).
           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))},
                           rxRange).
           mustThrowMatching(()=>{db.prepare("select ?").bind(minMaxI64[1] + BigInt(1))},
index f77b9db145cca2e6a91bad39ef2254dee67bbb8b..2c0461c342dfe0c5817333280a5ef12e8f255468 100644 (file)
--- a/manifest
+++ b/manifest
@@ -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.
-D 2022-10-02T20:08:53.027
+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-02T22:50:04.828
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 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/GNUmakefile b313a82060c733c990b91afa981e10f5e21a0b33a483f33b739ce932ed6bc725
 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/README.md 1e350b611465566cfa2e5eccf7c9b29a34f48ee38bbf6d5fb086dd06ce32b3ff
 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/pre-js.js 2db711eb637991b383fc6b5c0f3df65ec48a7201e5730e304beba8de2d3f9b0b
 F ext/wasm/api/sqlite3-api-cleanup.js 5d22d1d3818ecacb23bfa223d5970cd0617d8cdbb48c8bc4bbd463f05b021a99
-F ext/wasm/api/sqlite3-api-glue.js d1587736ed73fcb44e32f1ff1933e4c91a2d3b3c39acef0d13c0b3fd6859a7b1
-F ext/wasm/api/sqlite3-api-oo1.js 48d2269544301cd97726ef2d9a82e4384350d12dcf832fa417f211811ae5272d
+F ext/wasm/api/sqlite3-api-glue.js b962bad752b62366651dae26c0b969d297f81e17879685025fb12130786509cb
+F ext/wasm/api/sqlite3-api-oo1.js 484f9ea5c7140d07745f4b534a1f6dd67120c65ef34abcf7cdb3a388d73f5ef4
 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-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
 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/demo-123-worker.html e419b66495d209b5211ec64903b4cfb3ca7df20d652b41fcd28bf018a773234f
 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.js 105596bd2ccd0b1deb5fde8e99b536e8242d4bb5932fac0c8403ff3a6bc547e8
 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.js bd788e33c1807e0a6dda9c9a9d784bd3350ca49c9dd8ae2cc8719b506b6e013e
 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.js 88f40ef3cd8201bdadd120a711c36bbf0ce56cc0eab1d5e7debb71fed7822494
 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.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 435ab33384017967e46f52b70bee851a85a28808990a0e58dd5288f606b89c9c
-R 8d670d2cae99839795cb81bda9741460
+P d3bad9347c5423fa7f19ae729461636f1043c99a1f01f168efa10bebefb1cdd1
+R 46b070d886b6400d4ed82000c86b9d6e
 U stephan
-Z 74556b5dc28de703cf507dc7ef6550bd
+Z fc77e81a038294d78aff4ec7221b0be3
 # Remove this line to create a well-formed Fossil manifest.
index aca6ce8b8ab536771028eb1698abd0ccb1872f1e..e633b1fc49fe343906173947fb20040f96b4354d 100644 (file)
@@ -1 +1 @@
-d3bad9347c5423fa7f19ae729461636f1043c99a1f01f168efa10bebefb1cdd1
\ No newline at end of file
+10ab77af952abf09f93f342a9d07a3b133f2c4c0a3588df3390cd3a923cafae4
\ No newline at end of file