mirror of
				https://github.com/sqlite/sqlite.git
				synced 2025-10-28 19:36:04 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			719 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			719 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
| ** 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.vfs, and object which exists to assist
 | |
|    in the creation of JavaScript implementations of sqlite3_vfs, along
 | |
|    with its virtual table counterpart, sqlite3.vtab.
 | |
| */
 | |
| 'use strict';
 | |
| globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
 | |
|   const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
 | |
|   const vfs = Object.create(null), vtab = Object.create(null);
 | |
| 
 | |
|   const StructBinder = sqlite3.StructBinder
 | |
|   /* we require a local alias b/c StructBinder is removed from the sqlite3
 | |
|      object during the final steps of the API cleanup. */;
 | |
|   sqlite3.vfs = vfs;
 | |
|   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);
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|      Installs a StructBinder-bound function pointer member of the
 | |
|      given name and function in the given StructType target object.
 | |
| 
 | |
|      It creates a WASM proxy for the given function and arranges for
 | |
|      that proxy to be cleaned up when tgt.dispose() is called. Throws
 | |
|      on the slightest hint of error, e.g. tgt is-not-a StructType,
 | |
|      name does not map to a struct-bound member, etc.
 | |
| 
 | |
|      As a special case, if the given function is a pointer, then
 | |
|      `wasm.functionEntry()` is used to validate that it is a known
 | |
|      function. If so, it is used as-is with no extra level of proxying
 | |
|      or cleanup, else an exception is thrown. It is legal to pass a
 | |
|      value of 0, indicating a NULL pointer, with the caveat that 0
 | |
|      _is_ a legal function pointer in WASM but it will not be accepted
 | |
|      as such _here_. (Justification: the function at address zero must
 | |
|      be one which initially came from the WASM module, not a method we
 | |
|      want to bind to a virtual table or VFS.)
 | |
| 
 | |
|      This function returns a proxy for itself which is bound to tgt
 | |
|      and takes 2 args (name,func). That function returns the same
 | |
|      thing as this one, permitting calls to be chained.
 | |
| 
 | |
|      If called with only 1 arg, it has no side effects but returns a
 | |
|      func with the same signature as described above.
 | |
| 
 | |
|      ACHTUNG: because we cannot generically know how to transform JS
 | |
|      exceptions into result codes, the installed functions do no
 | |
|      automatic catching of exceptions. It is critical, to avoid
 | |
|      undefined behavior in the C layer, that methods mapped via
 | |
|      this function do not throw. The exception, as it were, to that
 | |
|      rule is...
 | |
| 
 | |
|      If applyArgcCheck is true then each JS function (as opposed to
 | |
|      function pointers) gets wrapped in a proxy which asserts that it
 | |
|      is passed the expected number of arguments, throwing if the
 | |
|      argument count does not match expectations. That is only intended
 | |
|      for dev-time usage for sanity checking, and will leave the C
 | |
|      environment in an undefined state.
 | |
|   */
 | |
|   const installMethod = function callee(
 | |
|     tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
 | |
|   ){
 | |
|     if(!(tgt instanceof StructBinder.StructType)){
 | |
|       toss("Usage error: target object is-not-a StructType.");
 | |
|     }else if(!(func instanceof Function) && !wasm.isPtr(func)){
 | |
|       toss("Usage errror: expecting a Function or WASM pointer to one.");
 | |
|     }
 | |
|     if(1===arguments.length){
 | |
|       return (n,f)=>callee(tgt, n, f, applyArgcCheck);
 | |
|     }
 | |
|     if(!callee.argcProxy){
 | |
|       callee.argcProxy = function(tgt, funcName, func,sig){
 | |
|         return function(...args){
 | |
|           if(func.length!==arguments.length){
 | |
|             toss("Argument mismatch for",
 | |
|                  tgt.structInfo.name+"::"+funcName
 | |
|                  +": Native signature is:",sig);
 | |
|           }
 | |
|           return func.apply(this, args);
 | |
|         }
 | |
|       };
 | |
|       /* An ondispose() callback for use with
 | |
|          StructBinder-created types. */
 | |
|       callee.removeFuncList = function(){
 | |
|         if(this.ondispose.__removeFuncList){
 | |
|           this.ondispose.__removeFuncList.forEach(
 | |
|             (v,ndx)=>{
 | |
|               if('number'===typeof v){
 | |
|                 try{wasm.uninstallFunction(v)}
 | |
|                 catch(e){/*ignore*/}
 | |
|               }
 | |
|               /* else it's a descriptive label for the next number in
 | |
|                  the list. */
 | |
|             }
 | |
|           );
 | |
|           delete this.ondispose.__removeFuncList;
 | |
|         }
 | |
|       };
 | |
|     }/*static init*/
 | |
|     const sigN = tgt.memberSignature(name);
 | |
|     if(sigN.length<2){
 | |
|       toss("Member",name,"does not have a function pointer signature:",sigN);
 | |
|     }
 | |
|     const memKey = tgt.memberKey(name);
 | |
|     const fProxy = (applyArgcCheck && !wasm.isPtr(func))
 | |
|     /** This middle-man proxy is only for use during development, to
 | |
|         confirm that we always pass the proper number of
 | |
|         arguments. We know that the C-level code will always use the
 | |
|         correct argument count. */
 | |
|           ? callee.argcProxy(tgt, memKey, func, sigN)
 | |
|           : func;
 | |
|     if(wasm.isPtr(fProxy)){
 | |
|       if(fProxy && !wasm.functionEntry(fProxy)){
 | |
|         toss("Pointer",fProxy,"is not a WASM function table entry.");
 | |
|       }
 | |
|       tgt[memKey] = fProxy;
 | |
|     }else{
 | |
|       const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
 | |
|       tgt[memKey] = pFunc;
 | |
|       if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
 | |
|         tgt.addOnDispose('ondispose.__removeFuncList handler',
 | |
|                          callee.removeFuncList);
 | |
|         tgt.ondispose.__removeFuncList = [];
 | |
|       }
 | |
|       tgt.ondispose.__removeFuncList.push(memKey, pFunc);
 | |
|     }
 | |
|     return (n,f)=>callee(tgt, n, f, applyArgcCheck);
 | |
|   }/*installMethod*/;
 | |
|   installMethod.installMethodArgcCheck = false;
 | |
| 
 | |
|   /**
 | |
|      Installs methods into the given StructType-type instance. Each
 | |
|      entry in the given methods object must map to a known member of
 | |
|      the given StructType, else an exception will be triggered.  See
 | |
|      installMethod() for more details, including the semantics of the
 | |
|      3rd argument.
 | |
| 
 | |
|      As an exception to the above, if any two or more methods in the
 | |
|      2nd argument are the exact same function, installMethod() is
 | |
|      _not_ called for the 2nd and subsequent instances, and instead
 | |
|      those instances get assigned the same method pointer which is
 | |
|      created for the first instance. This optimization is primarily to
 | |
|      accommodate special handling of sqlite3_module::xConnect and
 | |
|      xCreate methods.
 | |
| 
 | |
|      On success, returns its first argument. Throws on error.
 | |
|   */
 | |
|   const installMethods = function(
 | |
|     structInstance, methods, applyArgcCheck = installMethod.installMethodArgcCheck
 | |
|   ){
 | |
|     const seen = new Map /* map of <Function, memberName> */;
 | |
|     for(const k of Object.keys(methods)){
 | |
|       const m = methods[k];
 | |
|       const prior = seen.get(m);
 | |
|       if(prior){
 | |
|         const mkey = structInstance.memberKey(k);
 | |
|         structInstance[mkey] = structInstance[structInstance.memberKey(prior)];
 | |
|       }else{
 | |
|         installMethod(structInstance, k, m, applyArgcCheck);
 | |
|         seen.set(m, k);
 | |
|       }
 | |
|     }
 | |
|     return structInstance;
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|      Equivalent to calling installMethod(this,...arguments) with a
 | |
|      first argument of this object. If called with 1 or 2 arguments
 | |
|      and the first is an object, it's instead equivalent to calling
 | |
|      installMethods(this,...arguments).
 | |
|   */
 | |
|   StructBinder.StructType.prototype.installMethod = function callee(
 | |
|     name, func, applyArgcCheck = installMethod.installMethodArgcCheck
 | |
|   ){
 | |
|     return (arguments.length < 3 && name && 'object'===typeof name)
 | |
|       ? installMethods(this, ...arguments)
 | |
|       : installMethod(this, ...arguments);
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|      Equivalent to calling installMethods() with a first argument
 | |
|      of this object.
 | |
|   */
 | |
|   StructBinder.StructType.prototype.installMethods = function(
 | |
|     methods, applyArgcCheck = installMethod.installMethodArgcCheck
 | |
|   ){
 | |
|     return installMethods(this, methods, applyArgcCheck);
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|      Uses sqlite3_vfs_register() to register this
 | |
|      sqlite3.capi.sqlite3_vfs. This object must have already been
 | |
|      filled out properly. If the first argument is truthy, the VFS is
 | |
|      registered as the default VFS, else it is not.
 | |
| 
 | |
|      On success, returns this object. Throws on error.
 | |
|   */
 | |
|   capi.sqlite3_vfs.prototype.registerVfs = function(asDefault=false){
 | |
|     if(!(this instanceof sqlite3.capi.sqlite3_vfs)){
 | |
|       toss("Expecting a sqlite3_vfs-type argument.");
 | |
|     }
 | |
|     const rc = capi.sqlite3_vfs_register(this, asDefault ? 1 : 0);
 | |
|     if(rc){
 | |
|       toss("sqlite3_vfs_register(",this,") failed with rc",rc);
 | |
|     }
 | |
|     if(this.pointer !== capi.sqlite3_vfs_find(this.$zName)){
 | |
|       toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
 | |
|            this);
 | |
|     }
 | |
|     return this;
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|      A wrapper for installMethods() or registerVfs() to reduce
 | |
|      installation of a VFS and/or its I/O methods to a single
 | |
|      call.
 | |
| 
 | |
|      Accepts an object which contains the properties "io" and/or
 | |
|      "vfs", each of which is itself an object with following properties:
 | |
| 
 | |
|      - `struct`: an sqlite3.StructType-type struct. This must be a
 | |
|        populated (except for the methods) object of type
 | |
|        sqlite3_io_methods (for the "io" entry) or sqlite3_vfs (for the
 | |
|        "vfs" entry).
 | |
| 
 | |
|      - `methods`: an object mapping sqlite3_io_methods method names
 | |
|        (e.g. 'xClose') to JS implementations of those methods. The JS
 | |
|        implementations must be call-compatible with their native
 | |
|        counterparts.
 | |
| 
 | |
|      For each of those object, this function passes its (`struct`,
 | |
|      `methods`, (optional) `applyArgcCheck`) properties to
 | |
|      installMethods().
 | |
| 
 | |
|      If the `vfs` entry is set then:
 | |
| 
 | |
|      - Its `struct` property's registerVfs() is called. The
 | |
|        `vfs` entry may optionally have an `asDefault` property, which
 | |
|        gets passed as the argument to registerVfs().
 | |
| 
 | |
|      - If `struct.$zName` is falsy and the entry has a string-type
 | |
|        `name` property, `struct.$zName` is set to the C-string form of
 | |
|        that `name` value before registerVfs() is called. That string
 | |
|        gets added to the on-dispose state of the struct.
 | |
| 
 | |
|      On success returns this object. Throws on error.
 | |
|   */
 | |
|   vfs.installVfs = function(opt){
 | |
|     let count = 0;
 | |
|     const propList = ['io','vfs'];
 | |
|     for(const key of propList){
 | |
|       const o = opt[key];
 | |
|       if(o){
 | |
|         ++count;
 | |
|         installMethods(o.struct, o.methods, !!o.applyArgcCheck);
 | |
|         if('vfs'===key){
 | |
|           if(!o.struct.$zName && 'string'===typeof o.name){
 | |
|             o.struct.addOnDispose(
 | |
|               o.struct.$zName = wasm.allocCString(o.name)
 | |
|             );
 | |
|           }
 | |
|           o.struct.registerVfs(!!o.asDefault);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if(!count) toss("Misuse: installVfs() options object requires at least",
 | |
|                     "one of:", propList);
 | |
|     return this;
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|      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 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.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ);
 | |
|        // where SQLITE_XYZ is some call-appropriate result code.
 | |
|      }
 | |
|      ```
 | |
|   */
 | |
|   /**vfs.exceptionToRc = vtab.exceptionToRc =
 | |
|     (e, defaultRc=capi.SQLITE_ERROR)=>(
 | |
|       (e instanceof sqlite3.WasmAllocError)
 | |
|         ? capi.SQLITE_NOMEM
 | |
|         : defaultRc
 | |
|     );*/
 | |
| 
 | |
|   /**
 | |
|      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;
 | |
| 
 | |
|   /**
 | |
|      "The problem" with this is that it introduces an outer function with
 | |
|      a different arity than the passed-in method callback. That means we
 | |
|      cannot do argc validation on these. Additionally, some methods (namely
 | |
|      xConnect) may have call-specific error handling. It would be a shame to
 | |
|      hard-coded that per-method support in this function.
 | |
|   */
 | |
|   /** vtab.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
 | |
|     return function(...args){
 | |
|       try { method(...args); }
 | |
|       }catch(e){ return vtab.xError(methodName, e, defaultRc) }
 | |
|   };
 | |
|   */
 | |
| 
 | |
|   /**
 | |
|      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);
 | |
|           }
 | |
|         }
 | |
|         installMethods(mod, remethods, false);
 | |
|       }else{
 | |
|         // No automatic exception handling. Trust the client
 | |
|         // to not throw.
 | |
|         installMethods(
 | |
|           mod, 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()*/);
 |