4 ** The author disclaims copyright to this source code. In place of a
5 ** legal notice, here is a blessing:
7 ** * May you do good and not evil.
8 ** * May you find forgiveness for yourself and forgive others.
9 ** * May you share freely, never taking more than you give.
13 This file installs sqlite3.vfs, and object which exists to assist
14 in the creation of JavaScript implementations of sqlite3_vfs, along
15 with its virtual table counterpart, sqlite3.vtab.
18 globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
19 const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
20 const vfs = Object.create(null), vtab = Object.create(null);
22 const StructBinder = sqlite3.StructBinder
23 /* we require a local alias b/c StructBinder is removed from the sqlite3
24 object during the final steps of the API cleanup. */;
28 const sii = capi.sqlite3_index_info;
30 If n is >=0 and less than this.$nConstraint, this function
31 returns either a WASM pointer to the 0-based nth entry of
32 this.$aConstraint (if passed a truthy 2nd argument) or an
33 sqlite3_index_info.sqlite3_index_constraint object wrapping that
34 address (if passed a falsy value or no 2nd argument). Returns a
35 falsy value if n is out of range.
37 sii.prototype.nthConstraint = function(n, asPtr=false){
38 if(n<0 || n>=this.$nConstraint) return false;
39 const ptr = this.$aConstraint + (
40 sii.sqlite3_index_constraint.structInfo.sizeof * n
42 return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr);
46 Works identically to nthConstraint() but returns state from
47 this.$aConstraintUsage, so returns an
48 sqlite3_index_info.sqlite3_index_constraint_usage instance
49 if passed no 2nd argument or a falsy 2nd argument.
51 sii.prototype.nthConstraintUsage = function(n, asPtr=false){
52 if(n<0 || n>=this.$nConstraint) return false;
53 const ptr = this.$aConstraintUsage + (
54 sii.sqlite3_index_constraint_usage.structInfo.sizeof * n
56 return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr);
60 If n is >=0 and less than this.$nOrderBy, this function
61 returns either a WASM pointer to the 0-based nth entry of
62 this.$aOrderBy (if passed a truthy 2nd argument) or an
63 sqlite3_index_info.sqlite3_index_orderby object wrapping that
64 address (if passed a falsy value or no 2nd argument). Returns a
65 falsy value if n is out of range.
67 sii.prototype.nthOrderBy = function(n, asPtr=false){
68 if(n<0 || n>=this.$nOrderBy) return false;
69 const ptr = this.$aOrderBy + (
70 sii.sqlite3_index_orderby.structInfo.sizeof * n
72 return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr);
76 Installs a StructBinder-bound function pointer member of the
77 given name and function in the given StructType target object.
79 It creates a WASM proxy for the given function and arranges for
80 that proxy to be cleaned up when tgt.dispose() is called. Throws
81 on the slightest hint of error, e.g. tgt is-not-a StructType,
82 name does not map to a struct-bound member, etc.
84 As a special case, if the given function is a pointer, then
85 `wasm.functionEntry()` is used to validate that it is a known
86 function. If so, it is used as-is with no extra level of proxying
87 or cleanup, else an exception is thrown. It is legal to pass a
88 value of 0, indicating a NULL pointer, with the caveat that 0
89 _is_ a legal function pointer in WASM but it will not be accepted
90 as such _here_. (Justification: the function at address zero must
91 be one which initially came from the WASM module, not a method we
92 want to bind to a virtual table or VFS.)
94 This function returns a proxy for itself which is bound to tgt
95 and takes 2 args (name,func). That function returns the same
96 thing as this one, permitting calls to be chained.
98 If called with only 1 arg, it has no side effects but returns a
99 func with the same signature as described above.
101 ACHTUNG: because we cannot generically know how to transform JS
102 exceptions into result codes, the installed functions do no
103 automatic catching of exceptions. It is critical, to avoid
104 undefined behavior in the C layer, that methods mapped via
105 this function do not throw. The exception, as it were, to that
108 If applyArgcCheck is true then each JS function (as opposed to
109 function pointers) gets wrapped in a proxy which asserts that it
110 is passed the expected number of arguments, throwing if the
111 argument count does not match expectations. That is only intended
112 for dev-time usage for sanity checking, and will leave the C
113 environment in an undefined state.
115 const installMethod = function callee(
116 tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
118 if(!(tgt instanceof StructBinder.StructType)){
119 toss("Usage error: target object is-not-a StructType.");
120 }else if(!(func instanceof Function) && !wasm.isPtr(func)){
121 toss("Usage errror: expecting a Function or WASM pointer to one.");
123 if(1===arguments.length){
124 return (n,f)=>callee(tgt, n, f, applyArgcCheck);
126 if(!callee.argcProxy){
127 callee.argcProxy = function(tgt, funcName, func,sig){
128 return function(...args){
129 if(func.length!==arguments.length){
130 toss("Argument mismatch for",
131 tgt.structInfo.name+"::"+funcName
132 +": Native signature is:",sig);
134 return func.apply(this, args);
137 /* An ondispose() callback for use with
138 StructBinder-created types. */
139 callee.removeFuncList = function(){
140 if(this.ondispose.__removeFuncList){
141 this.ondispose.__removeFuncList.forEach(
143 if('number'===typeof v){
144 try{wasm.uninstallFunction(v)}
147 /* else it's a descriptive label for the next number in
151 delete this.ondispose.__removeFuncList;
155 const sigN = tgt.memberSignature(name);
157 toss("Member",name,"does not have a function pointer signature:",sigN);
159 const memKey = tgt.memberKey(name);
160 const fProxy = (applyArgcCheck && !wasm.isPtr(func))
161 /** This middle-man proxy is only for use during development, to
162 confirm that we always pass the proper number of
163 arguments. We know that the C-level code will always use the
164 correct argument count. */
165 ? callee.argcProxy(tgt, memKey, func, sigN)
167 if(wasm.isPtr(fProxy)){
168 if(fProxy && !wasm.functionEntry(fProxy)){
169 toss("Pointer",fProxy,"is not a WASM function table entry.");
171 tgt[memKey] = fProxy;
173 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
175 if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
176 tgt.addOnDispose('ondispose.__removeFuncList handler',
177 callee.removeFuncList);
178 tgt.ondispose.__removeFuncList = [];
180 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
182 return (n,f)=>callee(tgt, n, f, applyArgcCheck);
184 installMethod.installMethodArgcCheck = false;
187 Installs methods into the given StructType-type instance. Each
188 entry in the given methods object must map to a known member of
189 the given StructType, else an exception will be triggered. See
190 installMethod() for more details, including the semantics of the
193 As an exception to the above, if any two or more methods in the
194 2nd argument are the exact same function, installMethod() is
195 _not_ called for the 2nd and subsequent instances, and instead
196 those instances get assigned the same method pointer which is
197 created for the first instance. This optimization is primarily to
198 accommodate special handling of sqlite3_module::xConnect and
201 On success, returns its first argument. Throws on error.
203 const installMethods = function(
204 structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
206 const seen = new Map /* map of <Function, memberName> */;
207 for(const k of Object.keys(methods)){
208 const m = methods[k];
209 const prior = seen.get(m);
211 const mkey = structInstance.memberKey(k);
212 structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
214 installMethod(structInstance, k, m, applyArgcCheck);
218 return structInstance;
222 Equivalent to calling installMethod(this,...arguments) with a
223 first argument of this object. If called with 1 or 2 arguments
224 and the first is an object, it's instead equivalent to calling
225 installMethods(this,...arguments).
227 StructBinder.StructType.prototype.installMethod = function callee(
228 name, func, applyArgcCheck = installMethod.installMethodArgcCheck
230 return (arguments.length < 3 && name && 'object'===typeof name)
231 ? installMethods(this, ...arguments)
232 : installMethod(this, ...arguments);
236 Equivalent to calling installMethods() with a first argument
239 StructBinder.StructType.prototype.installMethods = function(
240 methods, applyArgcCheck = installMethod.installMethodArgcCheck
242 return installMethods(this, methods, applyArgcCheck);
246 Uses sqlite3_vfs_register() to register this
247 sqlite3.capi.sqlite3_vfs. This object must have already been
248 filled out properly. If the first argument is truthy, the VFS is
249 registered as the default VFS, else it is not.
251 On success, returns this object. Throws on error.
253 capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
254 if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
255 toss("Expecting a sqlite3_vfs-type argument.");
257 const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
259 toss("sqlite3_vfs_register(",this,") failed with rc",rc);
261 if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
262 toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
269 A wrapper for installMethods() or registerVfs() to reduce
270 installation of a VFS and/or its I/O methods to a single
273 Accepts an object which contains the properties "io" and/or
274 "vfs", each of which is itself an object with following properties:
276 - `struct`: an sqlite3.StructType-type struct. This must be a
277 populated (except for the methods) object of type
278 sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
281 - `methods`: an object mapping sqlite3_io_methods method names
282 (e.g. 'xClose') to JS implementations of those methods. The JS
283 implementations must be call-compatible with their native
286 For each of those object, this function passes its (`struct`,
287 `methods`, (optional) `applyArgcCheck`) properties to
290 If the `vfs` entry is set then:
292 - Its `struct` property's registerVfs() is called. The
293 `vfs` entry may optionally have an `asDefault` property, which
294 gets passed as the argument to registerVfs().
296 - If `struct.$zName` is falsy and the entry has a string-type
297 `name` property, `struct.$zName` is set to the C-string form of
298 that `name` value before registerVfs() is called. That string
299 gets added to the on-dispose state of the struct.
301 On success returns this object. Throws on error.
303 vfs.installVfs = function(opt){
305 const propList = ['io','vfs'];
306 for(const key of propList){
310 installMethods(o.struct, o.methods, !!o.applyArgcCheck);
312 if(!o.struct.$zName && 'string'===typeof o.name){
313 o.struct.addOnDispose(
314 o.struct.$zName = wasm.allocCString(o.name)
317 o.struct.registerVfs(!!o.asDefault);
321 if(!count) toss("Misuse: installVfs() options object requires at least",
322 "one of:", propList);
327 Internal factory function for xVtab and xCursor impls.
329 const __xWrapFactory = function(methodName,StructType){
330 return function(ptr,removeMapping=false){
331 if(0===arguments.length) ptr = new StructType;
332 if(ptr instanceof StructType){
333 //T.assert(!this.has(ptr.pointer));
334 this.set(ptr.pointer, ptr);
336 }else if(!wasm.isPtr(ptr)){
337 sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()");
339 let rc = this.get(ptr);
340 if(removeMapping) this.delete(ptr);
346 A factory function which implements a simple lifetime manager for
347 mappings between C struct pointers and their JS-level wrappers.
348 The first argument must be the logical name of the manager
349 (e.g. 'xVtab' or 'xCursor'), which is only used for error
350 reporting. The second must be the capi.XYZ struct-type value,
351 e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor.
353 Returns an object with 4 methods: create(), get(), unget(), and
354 dispose(), plus a StructType member with the value of the 2nd
355 argument. The methods are documented in the body of this
358 const StructPtrMapper = function(name, StructType){
359 const __xWrap = __xWrapFactory(name,StructType);
361 This object houses a small API for managing mappings of (`T*`)
362 to StructType<T> objects, specifically within the lifetime
363 requirements of sqlite3_module methods.
365 return Object.assign(Object.create(null),{
366 /** The StructType object for this object's API. */
369 Creates a new StructType object, writes its `pointer`
370 value to the given output pointer, and returns that
371 object. Its intended usage depends on StructType:
373 sqlite3_vtab: to be called from sqlite3_module::xConnect()
374 or xCreate() implementations.
376 sqlite3_vtab_cursor: to be called from xOpen().
378 This will throw if allocation of the StructType instance
379 fails or if ppOut is not a pointer-type value.
382 const rc = __xWrap();
383 wasm.pokePtr(ppOut, rc.pointer);
387 Returns the StructType object previously mapped to the
388 given pointer using create(). Its intended usage depends
391 sqlite3_vtab: to be called from sqlite3_module methods which
392 take a (sqlite3_vtab*) pointer _except_ for
393 xDestroy()/xDisconnect(), in which case unget() or dispose().
395 sqlite3_vtab_cursor: to be called from any sqlite3_module methods
396 which take a `sqlite3_vtab_cursor*` argument except xClose(),
397 in which case use unget() or dispose().
399 Rule to remember: _never_ call dispose() on an instance
400 returned by this function.
402 get: (pCObj)=>__xWrap(pCObj),
404 Identical to get() but also disconnects the mapping between the
405 given pointer and the returned StructType object, such that
406 future calls to this function or get() with the same pointer
407 will return the undefined value. Its intended usage depends
410 sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or
411 xDestroy() implementations or in error handling of a failed
412 xCreate() or xConnect().
414 sqlite3_vtab_cursor: to be called from xClose() or during
415 cleanup in a failed xOpen().
417 Calling this method obligates the caller to call dispose() on
418 the returned object when they're done with it.
420 unget: (pCObj)=>__xWrap(pCObj,true),
422 Works like unget() plus it calls dispose() on the
426 const o = __xWrap(pCObj,true);
433 A lifetime-management object for mapping `sqlite3_vtab*`
434 instances in sqlite3_module methods to capi.sqlite3_vtab
437 The API docs are in the API-internal StructPtrMapper().
439 vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab);
442 A lifetime-management object for mapping `sqlite3_vtab_cursor*`
443 instances in sqlite3_module methods to capi.sqlite3_vtab_cursor
446 The API docs are in the API-internal StructPtrMapper().
448 vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor);
451 Convenience form of creating an sqlite3_index_info wrapper,
452 intended for use in xBestIndex implementations. Note that the
453 caller is expected to call dispose() on the returned object
454 before returning. Though not _strictly_ required, as that object
455 does not own the pIdxInfo memory, it is nonetheless good form.
457 vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
460 Given an error object, this function returns
461 sqlite3.capi.SQLITE_NOMEM if (e instanceof
462 sqlite3.WasmAllocError), else it returns its
463 second argument. Its intended usage is in the methods
464 of a sqlite3_vfs or sqlite3_module:
471 return sqlite3.vtab.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ);
472 // where SQLITE_XYZ is some call-appropriate result code.
476 /**vfs.exceptionToRc = vtab.exceptionToRc =
477 (e, defaultRc=capi.SQLITE_ERROR)=>(
478 (e instanceof sqlite3.WasmAllocError)
484 Given an sqlite3_module method name and error object, this
485 function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof
486 sqlite3.WasmAllocError), else it returns its second argument. Its
487 intended usage is in the methods of a sqlite3_vfs or
495 return sqlite3.vtab.xError(
496 'xColumn', e, sqlite3.capi.SQLITE_XYZ);
497 // where SQLITE_XYZ is some call-appropriate result code.
501 If no 3rd argument is provided, its default depends on
504 - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM.
506 - If err is an SQLite3Error then its `resultCode` property
509 - If all else fails, capi.SQLITE_ERROR is used.
511 If xError.errorReporter is a function, it is called in
512 order to report the error, else the error is not reported.
513 If that function throws, that exception is ignored.
515 vtab.xError = function f(methodName, err, defaultRc){
516 if(f.errorReporter instanceof Function){
517 try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);}
518 catch(e){/*ignored*/}
521 if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM;
522 else if(arguments.length>2) rc = defaultRc;
523 else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode;
524 return rc || capi.SQLITE_ERROR;
526 vtab.xError.errorReporter = 1 ? console.error.bind(console) : false;
529 "The problem" with this is that it introduces an outer function with
530 a different arity than the passed-in method callback. That means we
531 cannot do argc validation on these. Additionally, some methods (namely
532 xConnect) may have call-specific error handling. It would be a shame to
533 hard-coded that per-method support in this function.
535 /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
536 return function(...args){
537 try { method(...args); }
538 }catch(e){ return vtab.xError(methodName, e, defaultRc) }
543 A helper for sqlite3_vtab::xRowid() and xUpdate()
544 implementations. It must be passed the final argument to one of
545 those methods (an output pointer to an int64 row ID) and the
546 value to store at the output pointer's address. Returns the same
547 as wasm.poke() and will throw if the 1st or 2nd arguments
548 are invalid for that function.
553 const xRowid = (pCursor, ppRowid64)=>{
554 const c = vtab.xCursor(pCursor);
555 vtab.xRowid(ppRowid64, c.myRowId);
560 vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64');
563 A helper to initialize and set up an sqlite3_module object for
564 later installation into individual databases using
565 sqlite3_create_module(). Requires an object with the following
568 - `methods`: an object containing a mapping of properties with
569 the C-side names of the sqlite3_module methods, e.g. xCreate,
570 xBestIndex, etc., to JS implementations for those functions.
571 Certain special-case handling is performed, as described below.
573 - `catchExceptions` (default=false): if truthy, the given methods
574 are not mapped as-is, but are instead wrapped inside wrappers
575 which translate exceptions into result codes of SQLITE_ERROR or
576 SQLITE_NOMEM, depending on whether the exception is an
577 sqlite3.WasmAllocError. In the case of the xConnect and xCreate
578 methods, the exception handler also sets the output error
579 string to the exception's error string.
581 - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If
582 not set, one will be created automatically. If the current
583 "this" is-a sqlite3_module then it is unconditionally used in
586 - OPTIONAL `iVersion`: if set, it must be an integer value and it
587 gets assigned to the `$iVersion` member of the struct object.
588 If it's _not_ set, and the passed-in `struct` object's `$iVersion`
589 is 0 (the default) then this function attempts to define a value
590 for that property based on the list of methods it has.
592 If `catchExceptions` is false, it is up to the client to ensure
593 that no exceptions escape the methods, as doing so would move
594 them through the C API, leading to undefined
595 behavior. (vtab.xError() is intended to assist in reporting
598 Certain methods may refer to the same implementation. To simplify
599 the definition of such methods:
601 - If `methods.xConnect` is `true` then the value of
602 `methods.xCreate` is used in its place, and vice versa. sqlite
603 treats xConnect/xCreate functions specially if they are exactly
604 the same function (same pointer value).
606 - If `methods.xDisconnect` is true then the value of
607 `methods.xDestroy` is used in its place, and vice versa.
609 This is to facilitate creation of those methods inline in the
610 passed-in object without requiring the client to explicitly get a
611 reference to one of them in order to assign it to the other
614 The `catchExceptions`-installed handlers will account for
615 identical references to the above functions and will install the
616 same wrapper function for both.
618 The given methods are expected to return integer values, as
619 expected by the C API. If `catchExceptions` is truthy, the return
620 value of the wrapped function will be used as-is and will be
621 translated to 0 if the function returns a falsy value (e.g. if it
622 does not have an explicit return). If `catchExceptions` is _not_
623 active, the method implementations must explicitly return integer
626 Throws on error. On success, returns the sqlite3_module object
627 (`this` or `opt.struct` or a new sqlite3_module instance,
628 depending on how it's called).
630 vtab.setupModule = function(opt){
631 let createdMod = false;
632 const mod = (this instanceof capi.sqlite3_module)
633 ? this : (opt.struct || (createdMod = new capi.sqlite3_module()));
635 const methods = opt.methods || toss("Missing 'methods' object.");
636 for(const e of Object.entries({
637 // -----^ ==> [k,v] triggers a broken code transformation in
638 // some versions of the emsdk toolchain.
639 xConnect: 'xCreate', xDisconnect: 'xDestroy'
641 // Remap X=true to X=Y for certain X/Y combinations
642 const k = e[0], v = e[1];
643 if(true === methods[k]) methods[k] = methods[v];
644 else if(true === methods[v]) methods[v] = methods[k];
646 if(opt.catchExceptions){
647 const fwrap = function(methodName, func){
648 if(['xConnect','xCreate'].indexOf(methodName) >= 0){
649 return function(pDb, pAux, argc, argv, ppVtab, pzErr){
650 try{return func(...arguments) || 0}
652 if(!(e instanceof sqlite3.WasmAllocError)){
653 wasm.dealloc(wasm.peekPtr(pzErr));
654 wasm.pokePtr(pzErr, wasm.allocCString(e.message));
656 return vtab.xError(methodName, e);
660 return function(...args){
661 try{return func(...args) || 0}
663 return vtab.xError(methodName, e);
669 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect',
670 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext',
671 'xEof', 'xColumn', 'xRowid', 'xUpdate',
672 'xBegin', 'xSync', 'xCommit', 'xRollback',
673 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease',
674 'xRollbackTo', 'xShadowName'
676 const remethods = Object.create(null);
677 for(const k of mnames){
678 const m = methods[k];
679 if(!(m instanceof Function)) continue;
680 else if('xConnect'===k && methods.xCreate===m){
681 remethods[k] = methods.xCreate;
682 }else if('xCreate'===k && methods.xConnect===m){
683 remethods[k] = methods.xConnect;
685 remethods[k] = fwrap(k, m);
688 installMethods(mod, remethods, false);
690 // No automatic exception handling. Trust the client
693 mod, methods, !!opt.applyArgcCheck/*undocumented option*/
696 if(0===mod.$iVersion){
698 if('number'===typeof opt.iVersion) v = opt.iVersion;
699 else if(mod.$xShadowName) v = 3;
700 else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2;
705 if(createdMod) createdMod.dispose();
712 Equivalent to calling vtab.setupModule() with this sqlite3_module
713 object as the call's `this`.
715 capi.sqlite3_module.prototype.setupModule = function(opt){
716 return vtab.setupModule.call(this, opt);
718 }/*sqlite3ApiBootstrap.initializers.push()*/);