summaryrefslogtreecommitdiffstats
path: root/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/api/sqlite3-vtab-helper.c-pp.js')
-rw-r--r--ext/wasm/api/sqlite3-vtab-helper.c-pp.js423
1 files changed, 423 insertions, 0 deletions
diff --git a/ext/wasm/api/sqlite3-vtab-helper.c-pp.js b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
new file mode 100644
index 0000000..7359ea3
--- /dev/null
+++ b/ext/wasm/api/sqlite3-vtab-helper.c-pp.js
@@ -0,0 +1,423 @@
+/*
+** 2022-11-30
+**
+** The author disclaims copyright to this source code. In place of a
+** legal notice, here is a blessing:
+**
+** * May you do good and not evil.
+** * May you find forgiveness for yourself and forgive others.
+** * May you share freely, never taking more than you give.
+*/
+
+/**
+ This file installs sqlite3.vtab, a namespace of helpers for use in
+ the creation of JavaScript implementations virtual tables.
+*/
+'use strict';
+globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
+ const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
+ const vtab = Object.create(null);
+ sqlite3.vtab = vtab;
+
+ const sii = capi.sqlite3_index_info;
+ /**
+ If n is >=0 and less than this.$nConstraint, this function
+ returns either a WASM pointer to the 0-based nth entry of
+ this.$aConstraint (if passed a truthy 2nd argument) or an
+ sqlite3_index_info.sqlite3_index_constraint object wrapping that
+ address (if passed a falsy value or no 2nd argument). Returns a
+ falsy value if n is out of range.
+ */
+ sii.prototype.nthConstraint = function(n, asPtr=false){
+ if(n<0 || n>=this.$nConstraint) return false;
+ const ptr = this.$aConstraint + (
+ sii.sqlite3_index_constraint.structInfo.sizeof * n
+ );
+ return asPtr ? ptr : new sii.sqlite3_index_constraint(ptr);
+ };
+
+ /**
+ Works identically to nthConstraint() but returns state from
+ this.$aConstraintUsage, so returns an
+ sqlite3_index_info.sqlite3_index_constraint_usage instance
+ if passed no 2nd argument or a falsy 2nd argument.
+ */
+ sii.prototype.nthConstraintUsage = function(n, asPtr=false){
+ if(n<0 || n>=this.$nConstraint) return false;
+ const ptr = this.$aConstraintUsage + (
+ sii.sqlite3_index_constraint_usage.structInfo.sizeof * n
+ );
+ return asPtr ? ptr : new sii.sqlite3_index_constraint_usage(ptr);
+ };
+
+ /**
+ If n is >=0 and less than this.$nOrderBy, this function
+ returns either a WASM pointer to the 0-based nth entry of
+ this.$aOrderBy (if passed a truthy 2nd argument) or an
+ sqlite3_index_info.sqlite3_index_orderby object wrapping that
+ address (if passed a falsy value or no 2nd argument). Returns a
+ falsy value if n is out of range.
+ */
+ sii.prototype.nthOrderBy = function(n, asPtr=false){
+ if(n<0 || n>=this.$nOrderBy) return false;
+ const ptr = this.$aOrderBy + (
+ sii.sqlite3_index_orderby.structInfo.sizeof * n
+ );
+ return asPtr ? ptr : new sii.sqlite3_index_orderby(ptr);
+ };
+
+ /**
+ Internal factory function for xVtab and xCursor impls.
+ */
+ const __xWrapFactory = function(methodName,StructType){
+ return function(ptr,removeMapping=false){
+ if(0===arguments.length) ptr = new StructType;
+ if(ptr instanceof StructType){
+ //T.assert(!this.has(ptr.pointer));
+ this.set(ptr.pointer, ptr);
+ return ptr;
+ }else if(!wasm.isPtr(ptr)){
+ sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()");
+ }
+ let rc = this.get(ptr);
+ if(removeMapping) this.delete(ptr);
+ return rc;
+ }.bind(new Map);
+ };
+
+ /**
+ A factory function which implements a simple lifetime manager for
+ mappings between C struct pointers and their JS-level wrappers.
+ The first argument must be the logical name of the manager
+ (e.g. 'xVtab' or 'xCursor'), which is only used for error
+ reporting. The second must be the capi.XYZ struct-type value,
+ e.g. capi.sqlite3_vtab or capi.sqlite3_vtab_cursor.
+
+ Returns an object with 4 methods: create(), get(), unget(), and
+ dispose(), plus a StructType member with the value of the 2nd
+ argument. The methods are documented in the body of this
+ function.
+ */
+ const StructPtrMapper = function(name, StructType){
+ const __xWrap = __xWrapFactory(name,StructType);
+ /**
+ This object houses a small API for managing mappings of (`T*`)
+ to StructType<T> objects, specifically within the lifetime
+ requirements of sqlite3_module methods.
+ */
+ return Object.assign(Object.create(null),{
+ /** The StructType object for this object's API. */
+ StructType,
+ /**
+ Creates a new StructType object, writes its `pointer`
+ value to the given output pointer, and returns that
+ object. Its intended usage depends on StructType:
+
+ sqlite3_vtab: to be called from sqlite3_module::xConnect()
+ or xCreate() implementations.
+
+ sqlite3_vtab_cursor: to be called from xOpen().
+
+ This will throw if allocation of the StructType instance
+ fails or if ppOut is not a pointer-type value.
+ */
+ create: (ppOut)=>{
+ const rc = __xWrap();
+ wasm.pokePtr(ppOut, rc.pointer);
+ return rc;
+ },
+ /**
+ Returns the StructType object previously mapped to the
+ given pointer using create(). Its intended usage depends
+ on StructType:
+
+ sqlite3_vtab: to be called from sqlite3_module methods which
+ take a (sqlite3_vtab*) pointer _except_ for
+ xDestroy()/xDisconnect(), in which case unget() or dispose().
+
+ sqlite3_vtab_cursor: to be called from any sqlite3_module methods
+ which take a `sqlite3_vtab_cursor*` argument except xClose(),
+ in which case use unget() or dispose().
+
+ Rule to remember: _never_ call dispose() on an instance
+ returned by this function.
+ */
+ get: (pCObj)=>__xWrap(pCObj),
+ /**
+ Identical to get() but also disconnects the mapping between the
+ given pointer and the returned StructType object, such that
+ future calls to this function or get() with the same pointer
+ will return the undefined value. Its intended usage depends
+ on StructType:
+
+ sqlite3_vtab: to be called from sqlite3_module::xDisconnect() or
+ xDestroy() implementations or in error handling of a failed
+ xCreate() or xConnect().
+
+ sqlite3_vtab_cursor: to be called from xClose() or during
+ cleanup in a failed xOpen().
+
+ Calling this method obligates the caller to call dispose() on
+ the returned object when they're done with it.
+ */
+ unget: (pCObj)=>__xWrap(pCObj,true),
+ /**
+ Works like unget() plus it calls dispose() on the
+ StructType object.
+ */
+ dispose: (pCObj)=>{
+ const o = __xWrap(pCObj,true);
+ if(o) o.dispose();
+ }
+ });
+ };
+
+ /**
+ A lifetime-management object for mapping `sqlite3_vtab*`
+ instances in sqlite3_module methods to capi.sqlite3_vtab
+ objects.
+
+ The API docs are in the API-internal StructPtrMapper().
+ */
+ vtab.xVtab = StructPtrMapper('xVtab', capi.sqlite3_vtab);
+
+ /**
+ A lifetime-management object for mapping `sqlite3_vtab_cursor*`
+ instances in sqlite3_module methods to capi.sqlite3_vtab_cursor
+ objects.
+
+ The API docs are in the API-internal StructPtrMapper().
+ */
+ vtab.xCursor = StructPtrMapper('xCursor', capi.sqlite3_vtab_cursor);
+
+ /**
+ Convenience form of creating an sqlite3_index_info wrapper,
+ intended for use in xBestIndex implementations. Note that the
+ caller is expected to call dispose() on the returned object
+ before returning. Though not _strictly_ required, as that object
+ does not own the pIdxInfo memory, it is nonetheless good form.
+ */
+ vtab.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
+
+ /**
+ Given an sqlite3_module method name and error object, this
+ function returns sqlite3.capi.SQLITE_NOMEM if (e instanceof
+ sqlite3.WasmAllocError), else it returns its second argument. Its
+ intended usage is in the methods of a sqlite3_vfs or
+ sqlite3_module:
+
+ ```
+ try{
+ let rc = ...
+ return rc;
+ }catch(e){
+ return sqlite3.vtab.xError(
+ 'xColumn', e, sqlite3.capi.SQLITE_XYZ);
+ // where SQLITE_XYZ is some call-appropriate result code.
+ }
+ ```
+
+ If no 3rd argument is provided, its default depends on
+ the error type:
+
+ - An sqlite3.WasmAllocError always resolves to capi.SQLITE_NOMEM.
+
+ - If err is an SQLite3Error then its `resultCode` property
+ is used.
+
+ - If all else fails, capi.SQLITE_ERROR is used.
+
+ If xError.errorReporter is a function, it is called in
+ order to report the error, else the error is not reported.
+ If that function throws, that exception is ignored.
+ */
+ vtab.xError = function f(methodName, err, defaultRc){
+ if(f.errorReporter instanceof Function){
+ try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);}
+ catch(e){/*ignored*/}
+ }
+ let rc;
+ if(err instanceof sqlite3.WasmAllocError) rc = capi.SQLITE_NOMEM;
+ else if(arguments.length>2) rc = defaultRc;
+ else if(err instanceof sqlite3.SQLite3Error) rc = err.resultCode;
+ return rc || capi.SQLITE_ERROR;
+ };
+ vtab.xError.errorReporter = 1 ? console.error.bind(console) : false;
+
+ /**
+ A helper for sqlite3_vtab::xRowid() and xUpdate()
+ implementations. It must be passed the final argument to one of
+ those methods (an output pointer to an int64 row ID) and the
+ value to store at the output pointer's address. Returns the same
+ as wasm.poke() and will throw if the 1st or 2nd arguments
+ are invalid for that function.
+
+ Example xRowid impl:
+
+ ```
+ const xRowid = (pCursor, ppRowid64)=>{
+ const c = vtab.xCursor(pCursor);
+ vtab.xRowid(ppRowid64, c.myRowId);
+ return 0;
+ };
+ ```
+ */
+ vtab.xRowid = (ppRowid64, value)=>wasm.poke(ppRowid64, value, 'i64');
+
+ /**
+ A helper to initialize and set up an sqlite3_module object for
+ later installation into individual databases using
+ sqlite3_create_module(). Requires an object with the following
+ properties:
+
+ - `methods`: an object containing a mapping of properties with
+ the C-side names of the sqlite3_module methods, e.g. xCreate,
+ xBestIndex, etc., to JS implementations for those functions.
+ Certain special-case handling is performed, as described below.
+
+ - `catchExceptions` (default=false): if truthy, the given methods
+ are not mapped as-is, but are instead wrapped inside wrappers
+ which translate exceptions into result codes of SQLITE_ERROR or
+ SQLITE_NOMEM, depending on whether the exception is an
+ sqlite3.WasmAllocError. In the case of the xConnect and xCreate
+ methods, the exception handler also sets the output error
+ string to the exception's error string.
+
+ - OPTIONAL `struct`: a sqlite3.capi.sqlite3_module() instance. If
+ not set, one will be created automatically. If the current
+ "this" is-a sqlite3_module then it is unconditionally used in
+ place of `struct`.
+
+ - OPTIONAL `iVersion`: if set, it must be an integer value and it
+ gets assigned to the `$iVersion` member of the struct object.
+ If it's _not_ set, and the passed-in `struct` object's `$iVersion`
+ is 0 (the default) then this function attempts to define a value
+ for that property based on the list of methods it has.
+
+ If `catchExceptions` is false, it is up to the client to ensure
+ that no exceptions escape the methods, as doing so would move
+ them through the C API, leading to undefined
+ behavior. (vtab.xError() is intended to assist in reporting
+ such exceptions.)
+
+ Certain methods may refer to the same implementation. To simplify
+ the definition of such methods:
+
+ - If `methods.xConnect` is `true` then the value of
+ `methods.xCreate` is used in its place, and vice versa. sqlite
+ treats xConnect/xCreate functions specially if they are exactly
+ the same function (same pointer value).
+
+ - If `methods.xDisconnect` is true then the value of
+ `methods.xDestroy` is used in its place, and vice versa.
+
+ This is to facilitate creation of those methods inline in the
+ passed-in object without requiring the client to explicitly get a
+ reference to one of them in order to assign it to the other
+ one.
+
+ The `catchExceptions`-installed handlers will account for
+ identical references to the above functions and will install the
+ same wrapper function for both.
+
+ The given methods are expected to return integer values, as
+ expected by the C API. If `catchExceptions` is truthy, the return
+ value of the wrapped function will be used as-is and will be
+ translated to 0 if the function returns a falsy value (e.g. if it
+ does not have an explicit return). If `catchExceptions` is _not_
+ active, the method implementations must explicitly return integer
+ values.
+
+ Throws on error. On success, returns the sqlite3_module object
+ (`this` or `opt.struct` or a new sqlite3_module instance,
+ depending on how it's called).
+ */
+ vtab.setupModule = function(opt){
+ let createdMod = false;
+ const mod = (this instanceof capi.sqlite3_module)
+ ? this : (opt.struct || (createdMod = new capi.sqlite3_module()));
+ try{
+ const methods = opt.methods || toss("Missing 'methods' object.");
+ for(const e of Object.entries({
+ // -----^ ==> [k,v] triggers a broken code transformation in
+ // some versions of the emsdk toolchain.
+ xConnect: 'xCreate', xDisconnect: 'xDestroy'
+ })){
+ // Remap X=true to X=Y for certain X/Y combinations
+ const k = e[0], v = e[1];
+ if(true === methods[k]) methods[k] = methods[v];
+ else if(true === methods[v]) methods[v] = methods[k];
+ }
+ if(opt.catchExceptions){
+ const fwrap = function(methodName, func){
+ if(['xConnect','xCreate'].indexOf(methodName) >= 0){
+ return function(pDb, pAux, argc, argv, ppVtab, pzErr){
+ try{return func(...arguments) || 0}
+ catch(e){
+ if(!(e instanceof sqlite3.WasmAllocError)){
+ wasm.dealloc(wasm.peekPtr(pzErr));
+ wasm.pokePtr(pzErr, wasm.allocCString(e.message));
+ }
+ return vtab.xError(methodName, e);
+ }
+ };
+ }else{
+ return function(...args){
+ try{return func(...args) || 0}
+ catch(e){
+ return vtab.xError(methodName, e);
+ }
+ };
+ }
+ };
+ const mnames = [
+ 'xCreate', 'xConnect', 'xBestIndex', 'xDisconnect',
+ 'xDestroy', 'xOpen', 'xClose', 'xFilter', 'xNext',
+ 'xEof', 'xColumn', 'xRowid', 'xUpdate',
+ 'xBegin', 'xSync', 'xCommit', 'xRollback',
+ 'xFindFunction', 'xRename', 'xSavepoint', 'xRelease',
+ 'xRollbackTo', 'xShadowName'
+ ];
+ const remethods = Object.create(null);
+ for(const k of mnames){
+ const m = methods[k];
+ if(!(m instanceof Function)) continue;
+ else if('xConnect'===k && methods.xCreate===m){
+ remethods[k] = methods.xCreate;
+ }else if('xCreate'===k && methods.xConnect===m){
+ remethods[k] = methods.xConnect;
+ }else{
+ remethods[k] = fwrap(k, m);
+ }
+ }
+ mod.installMethods(remethods, false);
+ }else{
+ // No automatic exception handling. Trust the client
+ // to not throw.
+ mod.installMethods(
+ methods, !!opt.applyArgcCheck/*undocumented option*/
+ );
+ }
+ if(0===mod.$iVersion){
+ let v;
+ if('number'===typeof opt.iVersion) v = opt.iVersion;
+ else if(mod.$xShadowName) v = 3;
+ else if(mod.$xSavePoint || mod.$xRelease || mod.$xRollbackTo) v = 2;
+ else v = 1;
+ mod.$iVersion = v;
+ }
+ }catch(e){
+ if(createdMod) createdMod.dispose();
+ throw e;
+ }
+ return mod;
+ }/*setupModule()*/;
+
+ /**
+ Equivalent to calling vtab.setupModule() with this sqlite3_module
+ object as the call's `this`.
+ */
+ capi.sqlite3_module.prototype.setupModule = function(opt){
+ return vtab.setupModule.call(this, opt);
+ };
+}/*sqlite3ApiBootstrap.initializers.push()*/);