mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Work on an alternate (slightly simpler) approach to binding JS vtabs. Non-eponymous vtabs are not working, for reasons as yet unknown.
FossilOrigin-Name: 6a0fefb93bcccd950df211cf5c2f49660c7b92115dd01b2b508a4ab9e3ab3d23
This commit is contained in:
@ -16,7 +16,7 @@
|
||||
*/
|
||||
'use strict';
|
||||
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss;
|
||||
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss3;
|
||||
const vh = Object.create(null), vt = Object.create(null);
|
||||
|
||||
sqlite3.VfsHelper = vh;
|
||||
@ -72,21 +72,29 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
/**
|
||||
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
|
||||
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.
|
||||
|
||||
Returns a proxy for this function which is bound to tgt and takes
|
||||
2 args (name,func). That function returns the same thing,
|
||||
permitting calls to be chained.
|
||||
As a special case, if the given function is a pointer, it is
|
||||
assumed to be an existing WASM-bound function pointer and is used
|
||||
as-is with no extra level of proxying or cleanup. Results are
|
||||
undefined if it's a pointer and it's _not_ a function pointer.
|
||||
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.
|
||||
|
||||
If tgt.ondispose is set before this is called then it _must_
|
||||
be an array, to which this function will append entries.
|
||||
|
||||
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
|
||||
@ -94,20 +102,20 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
this function do not throw. The exception, as it were, to that
|
||||
rule is...
|
||||
|
||||
If applyArgcCheck is true then each method 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. For non-dev-time use, it is a given that the C API will
|
||||
never call one of the generated function wrappers with the wrong
|
||||
argument count.
|
||||
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.
|
||||
*/
|
||||
vh.installMethod = vt.installMethod = function callee(
|
||||
tgt, name, func, applyArgcCheck = callee.installMethodArgcCheck
|
||||
){
|
||||
if(!(tgt instanceof sqlite3.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);
|
||||
@ -143,26 +151,33 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
}/*static init*/
|
||||
const sigN = tgt.memberSignature(name);
|
||||
if(sigN.length<2){
|
||||
toss("Member",name," is not a function pointer. Signature =",sigN);
|
||||
toss("Member",name,"does not have a function pointer signature:",sigN);
|
||||
}
|
||||
const memKey = tgt.memberKey(name);
|
||||
const fProxy = applyArgcCheck
|
||||
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;
|
||||
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
|
||||
tgt[memKey] = pFunc;
|
||||
if(!tgt.ondispose) tgt.ondispose = [];
|
||||
else if(!Array.isArray(tgt.ondispose)) tgt.ondispose = [tgt.ondispose];
|
||||
if(!tgt.ondispose.__removeFuncList){
|
||||
tgt.ondispose.push('ondispose.__removeFuncList handler',
|
||||
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 = [];
|
||||
else if(!Array.isArray(tgt.ondispose)) tgt.ondispose = [tgt.ondispose];
|
||||
if(!tgt.ondispose || !tgt.ondispose.__removeFuncList){
|
||||
tgt.addOnDispose('ondispose.__removeFuncList handler',
|
||||
callee.removeFuncList);
|
||||
tgt.ondispose.__removeFuncList = [];
|
||||
tgt.ondispose.__removeFuncList = [];
|
||||
}
|
||||
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
|
||||
}
|
||||
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
|
||||
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
|
||||
}/*installMethod*/;
|
||||
vh.installMethod.installMethodArgcCheck = false;
|
||||
@ -269,8 +284,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
this.installMethods(o.struct, o.methods, !!o.applyArgcCheck);
|
||||
if('vfs'===key){
|
||||
if(!o.struct.$zName && 'string'===typeof o.name){
|
||||
o.struct.$zName = wasm.allocCString(o.name);
|
||||
/* Note that we leak that C-string. */
|
||||
o.struct.addOnDispose(
|
||||
o.struct.$zName = wasm.allocCString(o.name)
|
||||
);
|
||||
}
|
||||
this.registerVfs(o.struct, !!o.asDefault);
|
||||
}
|
||||
@ -292,24 +308,20 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
vt.sqlite3ValuesToJs = capi.sqlite3_create_function_v2.udfConvertArgs;
|
||||
|
||||
/**
|
||||
Factory function for wrapXyz() impls.
|
||||
Factory function for xAbc() impls.
|
||||
*/
|
||||
const __xWrapFactory = function(structType){
|
||||
return function(ptr,remove=false){
|
||||
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 xWrapFactory");
|
||||
sqlite3.SQLite3Error.toss("Invalid argument to",methodName+"()");
|
||||
}
|
||||
let rc = this.get(ptr);
|
||||
if(remove) this.delete(ptr);
|
||||
/*arguable else if(!rc){
|
||||
rc = new structType(ptr);
|
||||
this.set(ptr, rc);
|
||||
}*/
|
||||
if(removeMapping) this.delete(ptr);
|
||||
return rc;
|
||||
}.bind(new Map);
|
||||
};
|
||||
@ -336,7 +348,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
called from sqlite3_module::xDisconnect() implementations or
|
||||
in error handling of a failed xCreate() or xConnect().
|
||||
*/
|
||||
vt.xWrapVtab = __xWrapFactory(capi.sqlite3_vtab);
|
||||
vt.xVtab = __xWrapFactory('xVtab',capi.sqlite3_vtab);
|
||||
|
||||
/**
|
||||
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
|
||||
@ -356,7 +368,16 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
intended to be called from xClose() or in error handling of a
|
||||
failed xOpen().
|
||||
*/
|
||||
vt.xWrapCursor = __xWrapFactory(capi.sqlite3_vtab_cursor);
|
||||
vt.xCursor = __xWrapFactory('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.
|
||||
*/
|
||||
vt.xIndexInfo = (pIdxInfo)=>new capi.sqlite3_index_info(pIdxInfo);
|
||||
|
||||
/**
|
||||
Given an error object, this function returns
|
||||
@ -394,27 +415,38 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
let rc = ...
|
||||
return rc;
|
||||
}catch(e){
|
||||
return sqlite3.VtabHelper.xMethodError(
|
||||
return sqlite3.VtabHelper.xError(
|
||||
'xColumn', e, sqlite3.capi.SQLITE_XYZ);
|
||||
// where SQLITE_XYZ is some call-appropriate result code
|
||||
// defaulting to SQLITE_ERROR.
|
||||
// where SQLITE_XYZ is some call-appropriate result code.
|
||||
}
|
||||
```
|
||||
|
||||
If xMethodError.errorReporter is a function, it is called in
|
||||
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.
|
||||
*/
|
||||
vt.xMethodError = function f(methodName, err, defaultRc=capi.SQLITE_ERROR){
|
||||
vt.xError = function f(methodName, err, defaultRc){
|
||||
if(f.errorReporter instanceof Function){
|
||||
try{f.errorReporter("sqlite3_module::"+methodName+"(): "+err.message);}
|
||||
catch(e){/*ignored*/}
|
||||
}
|
||||
return (err instanceof sqlite3.WasmAllocError)
|
||||
? capi.SQLITE_NOMEM
|
||||
: defaultRc;
|
||||
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;
|
||||
};
|
||||
vt.xMethodError.errorReporter = 1 ? console.error.bind(console) : false;
|
||||
vt.xError.errorReporter = 1 ? console.error.bind(console) : false;
|
||||
|
||||
/**
|
||||
"The problem" with this is that it introduces an outer function with
|
||||
@ -426,15 +458,139 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
|
||||
/** vt.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
|
||||
return function(...args){
|
||||
try { method(...args); }
|
||||
}catch(e){ return vt.xMethodError(methodName, e, defaultRc) }
|
||||
}catch(e){ return vt.xError(methodName, e, defaultRc) }
|
||||
};
|
||||
*/
|
||||
|
||||
/**
|
||||
A helper for sqlite3_vtab::xRow() implementations. It must be
|
||||
A helper for sqlite3_vtab::xRowid() implementations. It must be
|
||||
passed that function's 2nd argument and the value for that
|
||||
pointer. Returns the same as wasm.setMemValue() and will throw
|
||||
if the 1st or 2nd arguments are invalid for that function.
|
||||
*/
|
||||
vt.setRowId = (ppRowid64, value)=>wasm.setMemValue(ppRowid64, value, 'i64');
|
||||
vt.xRowid = (ppRowid64, value)=>wasm.setMemValue(ppRowid64, value, 'i64');
|
||||
|
||||
/**
|
||||
Sets 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 and (on success)
|
||||
added to the object.
|
||||
|
||||
- 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. (VtabHelper.xError() is intended to assist in reporting
|
||||
such exceptions.)
|
||||
|
||||
If `methods.xConnect` is `true` then the value of
|
||||
`methods.xCreate` 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. Note that
|
||||
sqlite treats those two functions specially if they are exactly
|
||||
the same function (same pointer value). The
|
||||
`catchExceptions`-installed handlers will account for identical
|
||||
references to those two 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. Returns the sqlite3_module object on success.
|
||||
*/
|
||||
vt.setupModule = function(opt){
|
||||
const mod = opt.struct || new capi.sqlite3_module();
|
||||
try{
|
||||
const methods = opt.methods || toss("Missing 'methods' object.");
|
||||
if(true===methods.xConnect) methods.xConnect = methods.xCreate;
|
||||
else if(true===methods.xCreate) methods.xCreate = methods.xConnect;
|
||||
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.setPtrValue(pzErr, wasm.allocCString(e.message));
|
||||
}
|
||||
return vt.xError(methodName, e);
|
||||
}
|
||||
};
|
||||
}else{
|
||||
return function(...args){
|
||||
try{return func(...args) || 0;}
|
||||
catch(e){
|
||||
return vt.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);
|
||||
}
|
||||
}
|
||||
this.installMethods(mod, remethods, false);
|
||||
}else{
|
||||
this.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(!opt.struct) mod.dispose();
|
||||
throw e;
|
||||
}
|
||||
if(!opt.struct) opt.struct = mod;
|
||||
return mod;
|
||||
}/*setupModule()*/;
|
||||
}/*sqlite3ApiBootstrap.initializers.push()*/);
|
||||
|
Reference in New Issue
Block a user