SQLITE_TEXT: 3,
SQLITE_BLOB: 4,
SQLITE_NULL: 5,
+ /* create_function() flags */
+ SQLITE_DETERMINISTIC: 0x000000800,
+ SQLITE_DIRECTONLY: 0x000080000,
+ SQLITE_INNOCUOUS: 0x000200000,
/* sqlite encodings, used for creating UDFs, noting that we
will only support UTF8. */
SQLITE_UTF8: 1
this.checkRc(S.sqlite3_open(name, pPtrArg));
this._pDb = getValue(pPtrArg, "i32");
this.filename = name;
- this._statements = {/*map of open Stmt _pointers_*/};
+ this._statements = {/*map of open Stmt _pointers_ to Stmt*/};
+ this._udfs = {/*map of UDF names to wasm function _pointers_*/};
};
/**
return db;
};
+ /** Returns true if n is a 32-bit (signed) integer,
+ else false. */
+ const isInt32 = function(n){
+ return (n===n|0 && n<0xFFFFFFFF) ? true : undefined;
+ };
+
/**
Expects to be passed (arguments) from DB.exec() and
DB.execMulti(). Does the argument processing/validation, throws
return out;
};
+ /** 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;
+
DB.prototype = {
/**
Expects to be given an sqlite3 API result code. If it is
delete that._statements[k];
if(s && s._pStmt) s.finalize();
});
+ Object.values(this._udfs).forEach(Module.removeFunction);
+ delete this._udfs;
+ delete this._statements;
S.sqlite3_close_v2(this._pDb);
delete this._pDb;
}
stackRestore(stack);
}
return this;
- }/*execMulti()*/
+ }/*execMulti()*/,
+ /**
+ Creates a new scalar UDF (User-Defined Function) which is
+ accessible via SQL code. This function may be called in any
+ of the following forms:
+
+ - (name, function)
+ - (name, function, optionsObject)
+ - (name, optionsObject)
+ - (optionsObject)
+
+ In the final two cases, the function must be defined as the
+ 'callback' property of the options object. In the final
+ case, the function's name must be the 'name' property.
+
+ This can only be used to create scalar functions, not
+ aggregate or window functions. UDFs cannot be removed from
+ a DB handle after they're added.
+
+ On success, returns this object. Throws on error.
+
+ When called from SQL, arguments to the UDF, and its result,
+ will be converted between JS and SQL with as much fidelity
+ as is feasible, triggering an exception if a type
+ conversion cannot be determined. Some freedom is afforded
+ to numeric conversions due to friction between the JS and C
+ worlds: integers which are larger than 32 bits will be
+ treated as doubles, as JS does not support 64-bit integers
+ and it is (as of this writing) illegal to use WASM
+ functions which take or return 64-bit integers from JS.
+
+ 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 the
+ callback's length property. 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.
+
+ The following properties correspond to flags documented at:
+
+ https://sqlite.org/c3ref/create_function.html
+
+ - .deterministic = SQLITE_DETERMINISTIC
+ - .directOnly = SQLITE_DIRECTONLY
+ - .innocuous = SQLITE_INNOCUOUS
+
+
+ Maintenance reminder: the ability to add new
+ WASM-accessible functions to the runtime requires that the
+ WASM build is compiled with emcc's `-sALLOW_TABLE_GROWTH`
+ flag.
+ */
+ createFunction: function f(name, callback,opt){
+ switch(arguments.length){
+ case 1: /* (optionsObject) */
+ opt = name;
+ name = opt.name;
+ callback = opt.callback;
+ break;
+ case 2: /* (name, callback|optionsObject) */
+ if(!(callback instanceof Function)){
+ opt = callback;
+ callback = opt.callback;
+ }
+ break;
+ default: break;
+ }
+ if(!opt) opt = {};
+ if(!(callback instanceof Function)){
+ toss("Invalid arguments: expecting a callback function.");
+ }else if('string' !== typeof name){
+ toss("Invalid arguments: missing function name.");
+ }
+ if(!f._extractArgs){
+ /* Static init */
+ f._extractArgs = function(argc, pArgv){
+ let i, pVal, valType, arg;
+ const tgt = [];
+ for(i = 0; i < argc; ++i){
+ pVal = getValue(pArgv + (4 * i), "i32");
+ valType = S.sqlite3_value_type(pVal);
+ switch(valType){
+ case S.SQLITE_INTEGER:
+ case S.SQLITE_FLOAT:
+ arg = S.sqlite3_value_double(pVal);
+ break;
+ case SQLITE_TEXT:
+ arg = S.sqlite3_value_text(pVal);
+ break;
+ case SQLITE_BLOB:{
+ const n = S.sqlite3_value_bytes(ptr);
+ const pBlob = S.sqlite3_value_blob(ptr);
+ arg = new Uint8Array(n);
+ let i;
+ for(i = 0; i < n; ++i) arg[i] = HEAP8[pBlob+i];
+ break;
+ }
+ default:
+ arg = null; break;
+ }
+ tgt.push(arg);
+ }
+ return tgt;
+ }/*_extractArgs()*/;
+ f._setResult = function(pCx, val){
+ switch(typeof val) {
+ case 'boolean':
+ S.sqlite3_result_int(pCx, val ? 1 : 0);
+ break;
+ case 'number': {
+ (isInt32(val)
+ ? S.sqlite3_result_int
+ : S.sqlite3_result_double)(pCx, val);
+ break;
+ }
+ case 'string':
+ S.sqlite3_result_text(pCx, val, -1,
+ -1/*==SQLITE_TRANSIENT*/);
+ break;
+ case 'object':
+ if(null===val) {
+ S.sqlite3_result_null(pCx);
+ break;
+ }else if(undefined!==val.length){
+ const pBlob = Module.allocate(val, ALLOC_NORMAL);
+ S.sqlite3_result_blob(pCx, pBlob, val.length, -1/*==SQLITE_TRANSIENT*/);
+ Module._free(blobptr);
+ break;
+ }
+ // else fall through
+ default:
+ toss("Don't not how to handle this UDF result value:",val);
+ };
+ }/*_setResult()*/;
+ }/*static init*/
+ const wrapper = function(pCx, argc, pArgv){
+ try{
+ f._setResult(pCx, callback.apply(null, f._extractArgs(argc, pArgv)));
+ }catch(e){
+ S.sqlite3_result_error(pCx, e.message, -1);
+ }
+ };
+ const pUdf = Module.addFunction(wrapper, "viii");
+ let fFlags = 0;
+ if(getOwnOption(opt, 'deterministic')) fFlags |= S.SQLITE_DETERMINISTIC;
+ if(getOwnOption(opt, 'directOnly')) fFlags |= S.SQLITE_DIRECTONLY;
+ if(getOwnOption(opt, 'innocuous')) fFlags |= S.SQLITE_INNOCUOUS;
+ name = name.toLowerCase();
+ try {
+ this.checkRc(S.sqlite3_create_function_v2(
+ this._pDb, name,
+ (opt.hasOwnProperty('arity') ? +opt.arity : callback.length),
+ S.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf,
+ null/*xStep*/, null/*xFinal*/, null/*xDestroy*/));
+ }catch(e){
+ Module.removeFunction(pUdf);
+ throw e;
+ }
+ if(this._udfs.hasOwnProperty(name)){
+ Module.removeFunction(this._udfs[name]);
+ }
+ this._udfs[name] = pUdf;
+ return this;
+ }/*createFunction()*/,
+ selectValue: function(sql,bind){
+ let stmt, rc;
+ try {
+ stmt = this.prepare(sql);
+ stmt.bind(bind);
+ if(stmt.step()) rc = stmt.get(0);
+ }finally{
+ if(stmt) stmt.finalize();
+ }
+ return rc;
+ }
}/*DB.prototype*/;
f._ = {
string: function(stmt, ndx, val, asBlob){
const bytes = intArrayFromString(val,true);
- const pStr = allocate(bytes, ALLOC_NORMAL);
+ const pStr = Module.allocate(bytes, ALLOC_NORMAL);
stmt._allocs.push(pStr);
const func = asBlob ? S.sqlite3_bind_blob : S.sqlite3_bind_text;
return func(stmt._pStmt, ndx, pStr, bytes.length, 0);
break;
}
case BindTypes.number: {
- const m = ((val === (val|0))
- ? ((val & 0x00000000/*>32 bits*/)
- ? S.sqlite3_bind_double
- /*It's illegal to bind a 64-bit int
- from here*/
- : S.sqlite3_bind_int)
+ const m = (isInt32(val)
+ ? S.sqlite3_bind_int
+ /*It's illegal to bind a 64-bit int
+ from here*/
: S.sqlite3_bind_double);
rc = m(stmt._pStmt, ndx, val);
break;
toss("Binding a value as a blob requires",
"that it have a length member.");
}
- const pBlob = allocate(val, ALLOC_NORMAL);
+ const pBlob = Module.allocate(val, ALLOC_NORMAL);
stmt._allocs.push(pBlob);
rc = S.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0);
}
const freeBindMemory = function(stmt){
let m;
while(undefined !== (m = stmt._allocs.pop())){
- _free(m);
+ Module._free(m);
}
return stmt;
};
Bindable value types:
- - null or undefined is bound as NULL.
+ - null is bound as NULL.
+
+ - undefined as a standalone value is a no-op intended to
+ simplify certain client-side use cases: passing undefined
+ as a value to this function will not actually bind
+ anything. undefined as an array or object property when
+ binding an array/object is treated as null.
- Numbers are bound as either doubles or integers: doubles
if they are larger than 32 bits, else double or int32,
- The statement has been finalized.
*/
- bind: function(/*[ndx,] value*/){
- if(!affirmStmtOpen(this).parameterCount){
- toss("This statement has no bindable parameters.");
- }
- this._mayGet = false;
+ bind: function(/*[ndx,] arg*/){
+ affirmStmtOpen(this);
let ndx, arg;
switch(arguments.length){
case 1: ndx = 1; arg = arguments[0]; break;
case 2: ndx = arguments[0]; arg = arguments[1]; break;
default: toss("Invalid bind() arguments.");
}
- if(null===arg || undefined===arg){
+ this._mayGet = false;
+ if(undefined===arg){
+ /* It might seem intuitive to bind undefined as NULL
+ but this approach simplifies certain client-side
+ uses when passing on arguments between 2+ levels of
+ functions. */
+ return this;
+ }else if(!this.parameterCount){
+ toss("This statement has no bindable parameters.");
+ }else if(null===arg){
/* bind NULL */
return bindOne(this, ndx, BindTypes.null, arg);
}
-C wasm:\sminor\srefactoring\sand\sdoc\supdates.
-D 2022-05-23T19:38:57.101
+C wasm/JS:\sadded\ssupport\sfor\sscalar\sUDFs.\sFixed\sa\sdeallocation\sproblem\swith\sbind()ed\sstrings/blobs.
+D 2022-05-24T00:22:10.054
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in a192a8de35ba61e6d695a3bd430b021e7cbf7ea473497028540801fe7b659282
+F Makefile.in dd31c34eb86a7869660f9697694d52c8f1c9705fede9717c59559530b6f0cb87
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f
-F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 5816adc4d4715b410a9df971c70f55fca610d3a240bd85d2ec34e75483cb54bb
-F ext/fiddle/EXPORTED_RUNTIME_METHODS 91d5dcb0168ee056fa1a340cb8ab3c23d922622f8dad39d28919dd8af2b3ade0
+F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 07b573a1830cb2d38ed347cf2a4139ec3b9c0f69748da6a2d8356b426c807694
+F ext/fiddle/EXPORTED_RUNTIME_METHODS ff64aea52779b0d4a838268275fe02adf6f2fdf4d9ce21c22d104bf3d7597398
F ext/fiddle/Makefile 9277c73e208b9c8093659256c9f07409c877e366480c7c22ec545ee345451d95
F ext/fiddle/SqliteTestUtil.js e3094833660a6ddd40766b802901b5861b37f0b89c6c577ee0ce4c9d36399e61
F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/fiddle/fiddle.html 657c6c3f860c322fba3c69fa4f7a1209e2d2ce44b4bc65a3e154e3a97c047a7c
F ext/fiddle/fiddle.js 68f5bb45fc1ae7f8ae3f6b85f465257db514d12bf50ec492259685178c452a88
F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
-F ext/fiddle/sqlite3-api.js c684fc5ce6b6c3e70f33699de2fc4bf9eaf045a217a30125a9da31737a9ca9e7
+F ext/fiddle/sqlite3-api.js 43d750c13ca2426580a57c1f0c8b4e529a1d8af45eda92dcdde6b5d5e4031fcd
F ext/fiddle/testing-common.js 723aada13d90a5ee3f0f8f5b5b88e46954becae5d2b04ded811d90106057f4ac
F ext/fiddle/testing1.html 026502e5d5e6a250e4101f8e8948708a1295ce831a094d741839ecaf788d8533
-F ext/fiddle/testing1.js c3d529379f901846907b00f62dffe752ff5724fb39791d47b421c4afdab0f58b
+F ext/fiddle/testing1.js 7365c6dac4f680f8ebd6ecfcf6475c5c0a0afd61cdaff5b5281e473b79c7424e
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 107e3497869d757265f2a4235082bf324ba1220075d1096c2a82021a5d348a6c
-R e327ce077052e8c8f39099637b67690c
+P 6044605b2a712da73600cabb967797a03ed1915dc0ab0b10edbd52525e548196
+R ceb272ebfbe3295f001f5fea9e36326e
U stephan
-Z 572c126f779e5a94cb3892ddd8508065
+Z 228a27040854e49e332f67b566615c59
# Remove this line to create a well-formed Fossil manifest.