Add a demonstration sqlite3_vtab/module implemented in JS, based on ext/misc/templatevtab.c. Add oo1.selectArrays() and selectObjects().

FossilOrigin-Name: 60482c97e02bc4cafefef281be0cf0bc8c5c53232162829c137f3f7a80cdc534
This commit is contained in:
stephan 2022-12-06 06:09:03 +00:00
parent 2582d418d3
commit 6b271abc98
11 changed files with 637 additions and 246 deletions

View File

@ -289,7 +289,7 @@ sqlite3-api.jses += $(dir.api)/sqlite3-api-glue.js
sqlite3-api.jses += $(sqlite3-api-build-version.js)
sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-worker1.js
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-helper.js
sqlite3-api.jses += $(dir.api)/sqlite3-v-helper.js
sqlite3-api.jses += $(dir.api)/sqlite3-vfs-opfs.c-pp.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js

View File

@ -78,11 +78,10 @@ browser client:
a Promise-based interface into the Worker #1 API. This is
a far user-friendlier way to interface with databases running
in a Worker thread.
- **`sqlite3-vfs-helper.js`**\
This internal-use-only file installs `sqlite3.VfsHelper` for use by
`sqlite3-*.js` files which create `sqlite3_vfs` implementations.
`sqlite3.VfsHelper` gets removed from the the `sqlite3` object after
the library is finished initializing.
- **`sqlite3-v-helper.js`**\
Installs `sqlite3.VfsHelper` and `sqlite3.VtabHelper` for use by
downstream code which creates `sqlite3_vfs` and `sqlite3_module`
implementations.
- **`sqlite3-vfs-opfs.c-pp.js`**\
is an sqlite3 VFS implementation which supports Google Chrome's
Origin-Private FileSystem (OPFS) as a storage layer to provide

View File

@ -473,6 +473,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc;
};
/**
Internal impl of the DB.selectArrays() and
selectObjects() methods.
*/
const __selectAll =
(db, sql, bind, rowMode)=>db.exec({
sql, bind, rowMode, returnValue: 'resultRows'
});
/**
Expects to be given a DB instance or an `sqlite3*` pointer (may
be null) and an sqlite3 API result code. If the result code is
@ -1098,6 +1107,26 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return __selectFirstRow(this, sql, bind, {});
},
/**
Runs the given SQL and returns an array of all results, with
each row represented as an array, as per the 'array' `rowMode`
option to `exec()`. An empty result set resolves
to an empty array. The second argument, if any, is treated as
the 'bind' option to a call to exec().
*/
selectArrays: function(sql,bind){
return __selectAll(this, sql, bind, 'array');
},
/**
Works identically to selectArrays() except that each value
in the returned array is an object, as per the 'object' `rowMode`
option to `exec()`.
*/
selectObjects: function(sql,bind){
return __selectAll(this, sql, bind, 'object');
},
/**
Returns the number of currently-opened Stmt handles for this db
handle, or 0 if this DB instance is closed.

View File

@ -1642,7 +1642,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
some initializers. Retain them when running in test mode
so that we can add tests for them. */
delete sqlite3.util;
delete sqlite3.VfsHelper;
delete sqlite3.StructBinder;
}
return sqlite3;

View File

@ -0,0 +1,392 @@
/*
** 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.VfsHelper, and object which exists to
assist in the creation of JavaScript implementations of sqlite3_vfs,
along with its virtual table counterpart, sqlite3.VtabHelper.
*/
'use strict';
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss;
const vh = Object.create(null), vt = Object.create(null);
sqlite3.VfsHelper = vh;
sqlite3.VtabHelper = vt;
/**
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.
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.
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
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 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.
*/
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.");
}
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
sqlite3.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," is not a function pointer. Signature =",sigN);
}
const memKey = tgt.memberKey(name);
const fProxy = applyArgcCheck
/** 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 = [];
if(!tgt.ondispose.__removeFuncList){
tgt.ondispose.push('ondispose.__removeFuncList handler',
callee.removeFuncList);
tgt.ondispose.__removeFuncList = [];
}
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*installMethod*/;
vh.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 this object. Throws on error.
*/
vh.installMethods = vt.installMethods = function(
structType, methods, applyArgcCheck = vh.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 = structType.memberKey(k);
structType[mkey] = structType[structType.memberKey(prior)];
}else{
vh.installMethod(structType, k, m, applyArgcCheck);
seen.set(m, k);
}
}
return this;
};
/**
Uses sqlite3_vfs_register() to register the
sqlite3.capi.sqlite3_vfs-type vfs, which must have already been
filled out properly. If the 2nd argument is truthy, the VFS is
registered as the default VFS, else it is not.
On success, returns this object. Throws on error.
*/
vh.registerVfs = function(vfs, asDefault=false){
if(!(vfs instanceof sqlite3.capi.sqlite3_vfs)){
toss("Expecting a sqlite3_vfs-type argument.");
}
const rc = capi.sqlite3_vfs_register(vfs.pointer, asDefault ? 1 : 0);
if(rc){
toss("sqlite3_vfs_register(",vfs,") failed with rc",rc);
}
if(vfs.pointer !== capi.sqlite3_vfs_find(vfs.$zName)){
toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
vfs);
}
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
this.installMethods().
If the `vfs` entry is set then:
- Its `struct` property is passed to this.registerVfs(). The
`vfs` entry may optionally have an `asDefault` property, which
gets passed as the 2nd 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.
On success returns this object. Throws on error.
*/
vh.installVfs = function(opt){
let count = 0;
const propList = ['io','vfs'];
for(const key of propList){
const o = opt[key];
if(o){
++count;
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. */
}
this.registerVfs(o.struct, !!o.asDefault);
}
}
}
if(!count) toss("Misuse: installVfs() options object requires at least",
"one of:", propList);
return this;
};
/**
Expects to be passed the (argc,argv) arguments of
sqlite3_module::xFilter(), or an equivalent API. This function
transforms the arguments (an array of (sqlite3_value*)) into a JS
array of equivalent JS values. It uses the same type conversions
as sqlite3_create_function_v2() and friends. Throws on error,
e.g. if it cannot figure out a sensible data conversion.
*/
vt.sqlite3ValuesToJs = capi.sqlite3_create_function_v2.udfConvertArgs;
/**
Factory function for xyz2js() impls.
*/
const __v2jsFactory = function(structType){
return function(ptr,remove=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 v2jsFactory");
}
let rc = this.get(ptr);
if(remove) this.delete(ptr);
/*arguable else if(!rc){
rc = new structType(ptr);
this.set(ptr, rc);
}*/
return rc;
}.bind(new Map);
};
/**
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
Has 3 distinct uses:
- vtab2js() instantiates a new capi.sqlite3_vtab instance, maps
its pointer for later by-pointer lookup, and returns that
object. This is intended to be called from
sqlite3_module::xConnect() or xCreate() implementations.
- vtab2js(pVtab) accepts a WASM pointer to a C-level
(sqlite3_vtab*) instance and returns the capi.sqlite3_vtab
object created by the first form of this function, or undefined
if that form has not been used. This is intended to be called
from sqlite3_module methods which take a (sqlite3_vtab*) pointer
_except_ for xDisconnect(), in which case use...
- vtab2js(pVtab,true) as for the previous form, but removes the
pointer-to-object mapping before returning. The caller must
call dispose() on the returned object. This is intended to be
called from sqlite3_module::xDisconnect() implementations or
in error handling of a failed xCreate() or xConnect().
*/
vt.vtab2js = __v2jsFactory(capi.sqlite3_vtab);
/**
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
Works identically to vtab2js() except that it deals with
sqlite3_cursor objects and pointers instead of sqlite3_vtab.
- vcur2js() is intended to be called from sqlite3_module::xOpen()
- vcur2js(pCursor) is intended to be called from all sqlite3_module
methods which take a (sqlite3_vtab_cursor*) _except_ for
xClose(), in which case use...
- vcur2js(pCursor, true) will remove the m apping of pCursor to a
capi.sqlite3_vtab_cursor object and return that object. The
caller must call dispose() on the returned object. This is
intended to be called form xClose() or in error handling of a
failed xOpen().
*/
vt.vcur2js = __v2jsFactory(capi.sqlite3_vtab_cursor);
/**
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.VtabHelper.exceptionToRc(e, sqlite3.capi.SQLITE_XYZ);
// where SQLITE_XYZ is some call-appropriate result code.
}
```
*/
/**vh.exceptionToRc = vt.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.VtabHelper.xMethodError(
'xColumn', e, sqlite3.capi.SQLITE_XYZ);
// where SQLITE_XYZ is some call-appropriate result code
// defaulting to SQLITE_ERROR.
}
```
If xMethodError.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){
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;
};
vt.xMethodError.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.
*/
/** vt.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
return function(...args){
try { method(...args); }
}catch(e){ return vt.xMethodError(methodName, e, defaultRc) }
};
*/
/**
A helper for sqlite3_vtab::xRow() 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');
}/*sqlite3ApiBootstrap.initializers.push()*/);

View File

@ -1,221 +0,0 @@
/*
** 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 sqlite.VfsHelper, an object which exists
to assist in the creation of JavaScript implementations of
sqlite3_vfs. It is NOT part of the public API, and is an
internal implemenation detail for use in this project's
own development of VFSes. It may be exposed to clients
at some point, provided there is value in doing so.
*/
'use strict';
self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const wasm = sqlite3.wasm, capi = sqlite3.capi, toss = sqlite3.util.toss;
const vh = Object.create(null);
/**
Does nothing more than holds a permanent reference to each
argument. This is useful in some cases to ensure that, e.g., a
custom sqlite3_io_methods instance does not get
garbage-collected.
Returns this object.
*/
vh.holdReference = function(...args){
for(const v of args) this.refs.add(v);
return vh;
}.bind({refs: new Set});
/**
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.
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 recommended for dev-time usage for
sanity checking. Once a VFS implementation is known to be
working, it is a given that the C API will never call it with the
wrong argument count.
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.
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.
*/
vh.installMethod = function callee(tgt, name, func,
applyArgcCheck=callee.installMethodArgcCheck){
if(!(tgt instanceof sqlite3.StructBinder.StructType)){
toss("Usage error: target object is-not-a StructType.");
}
if(1===arguments.length){
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}
if(!callee.argcProxy){
callee.argcProxy = function(func,sig){
return function(...args){
if(func.length!==arguments.length){
toss("Argument mismatch. Native signature is:",sig);
}
return func.apply(this, args);
}
};
/* An ondispose() callback for use with
sqlite3.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," is not a function pointer. Signature =",sigN);
}
const memKey = tgt.memberKey(name);
const fProxy = applyArgcCheck
/** 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(func, sigN)
: func;
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
tgt[memKey] = pFunc;
if(!tgt.ondispose) tgt.ondispose = [];
if(!tgt.ondispose.__removeFuncList){
tgt.ondispose.push('ondispose.__removeFuncList handler',
callee.removeFuncList);
tgt.ondispose.__removeFuncList = [];
}
tgt.ondispose.__removeFuncList.push(memKey, pFunc);
return (n,f)=>callee(tgt, n, f, applyArgcCheck);
}/*installMethod*/;
vh.installMethod.installMethodArgcCheck = false;
/**
Installs methods into the given StructType-type object. 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.
On success, passes its first argument to holdRefence() and
returns this object. Throws on error.
*/
vh.installMethods = function(structType, methods,
applyArgcCheck=vh.installMethod.installMethodArgcCheck){
for(const k of Object.keys(methods)){
vh.installMethod(structType, k, methods[k], applyArgcCheck);
}
return vh.holdReference(structType);
};
/**
Uses sqlite3_vfs_register() to register the
sqlite3.capi.sqlite3_vfs-type vfs, which must have already been
filled out properly. If the 2nd argument is truthy, the VFS is
registered as the default VFS, else it is not.
On success, passes its first argument to this.holdReference() and
returns this object. Throws on error.
*/
vh.registerVfs = function(vfs, asDefault=false){
if(!(vfs instanceof sqlite3.capi.sqlite3_vfs)){
toss("Expecting a sqlite3_vfs-type argument.");
}
const rc = capi.sqlite3_vfs_register(vfs.pointer, asDefault ? 1 : 0);
if(rc){
toss("sqlite3_vfs_register(",vfs,") failed with rc",rc);
}
if(vfs.pointer !== capi.sqlite3_vfs_find(vfs.$zName)){
toss("BUG: sqlite3_vfs_find(vfs.$zName) failed for just-installed VFS",
vfs);
}
return vh.holdReference(vfs);
};
/**
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.
For each of those object, this function passes its (`struct`,
`methods`, (optional) `applyArgcCheck`) properties to
this.installMethods().
If the `vfs` entry is set then:
- Its `struct` property is passed to this.registerVfs(). The
`vfs` entry may optionally have an `asDefault` property, which
gets passed as the 2nd 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.
On success returns this object. Throws on error.
*/
vh.installVfs = function(opt){
let count = 0;
const propList = ['io','vfs'];
for(const key of propList){
const o = opt[key];
if(o){
++count;
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. */
}
this.registerVfs(o.struct, !!o.asDefault);
}
}
}
if(!count) toss("Misuse: installVfs() options object requires at least",
"one of:", propList);
return this;
};
sqlite3.VfsHelper = vh;
}/*sqlite3ApiBootstrap.initializers.push()*/);

View File

@ -231,6 +231,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
obj.ondispose.forEach(function(x){
try{
if(x instanceof Function) x.call(obj);
else if(x instanceof StructType) x.dispose();
else if('number' === typeof x) dealloc(x);
// else ignore. Strings are permitted to annotate entries
// to assist in debugging.

View File

@ -426,7 +426,8 @@ simply passing a pointer to the constructor. For example:
```js
const m = new MyStruct( functionReturningASharedPtr() );
// calling m.dispose() will _not_ free the wrapped C-side instance.
// calling m.dispose() will _not_ free the wrapped C-side instance
// but will trigger any ondispose handler.
```
Now that we have struct instances, there are a number of things we

View File

@ -419,6 +419,7 @@ self.sqlite3InitModule = sqlite3InitModule;
////////////////////////////////////////////////////////////////////
T.g('C/WASM Utilities')
.t('sqlite3.wasm namespace', function(sqlite3){
// TODO: break this into smaller individual test functions.
const w = wasm;
const chr = (x)=>x.charCodeAt(0);
//log("heap getters...");
@ -974,7 +975,7 @@ self.sqlite3InitModule = sqlite3InitModule;
.t('Create db', function(sqlite3){
const dbFile = '/tester1.db';
wasm.sqlite3_wasm_vfs_unlink(0, dbFile);
const db = this.db = new sqlite3.oo1.DB(dbFile);
const db = this.db = new sqlite3.oo1.DB(dbFile, 0 ? 'ct' : 'c');
T.assert(Number.isInteger(db.pointer))
.mustThrowMatching(()=>db.pointer=1, /read-only/)
.assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
@ -1197,6 +1198,29 @@ self.sqlite3InitModule = sqlite3InitModule;
rc = db.selectArray('select a, b from t where b=-1');
T.assert(undefined === rc);
})
////////////////////////////////////////////////////////////////////////
.t('selectArrays/Objects()', function(sqlite3){
const db = this.db;
const sql = 'select a, b from t where a=? or b=? order by a';
let rc = db.selectArrays(sql, [1, 4]);
T.assert(Array.isArray(rc))
.assert(2===rc.length)
.assert(2===rc[0].length)
.assert(1===rc[0][0])
.assert(2===rc[0][1])
.assert(3===rc[1][0])
.assert(4===rc[1][1])
rc = db.selectArrays(sql, [99,99]);
T.assert(Array.isArray(rc)).assert(0===rc.length);
rc = db.selectObjects(sql, [1,4]);
T.assert(Array.isArray(rc))
.assert(2===rc.length)
.assert('object' === typeof rc[1])
.assert(1===rc[0].a)
.assert(2===rc[0].b)
.assert(3===rc[1].a)
.assert(4===rc[1].b);
})
////////////////////////////////////////////////////////////////////////
.t({
@ -1503,6 +1527,173 @@ self.sqlite3InitModule = sqlite3InitModule;
T.mustThrow(()=>db.exec("select * from foo.bar"));
})
////////////////////////////////////////////////////////////////////////
.t({
name: 'Custom virtual tables',
predicate: ()=>wasm.bigIntEnabled,
test: function(sqlite3){
warn("The vtab/module JS bindings are experimental and subject to change.");
const vth = sqlite3.VtabHelper;
const tmplCols = Object.assign(Object.create(null),{
A: 0, B: 1
});
/**
The vtab demonstrated here is a JS-ification of
ext/misc/templatevtab.c.
*/
const tmplMethods = {
xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
try{
const rc = capi.sqlite3_declare_vtab(
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
const t = vth.vtab2js();
wasm.setPtrValue(ppVtab, t.pointer);
T.assert(t === vth.vtab2js(wasm.getPtrValue(ppVtab)));
}
return rc;
}catch(e){
if(!(e instanceof sqlite3.WasmAllocError)){
wasm.setPtrValue(pzErr, wasm.allocCString(e.message));
}
return vth.xMethodError('xConnect',e);
}
},
xDisconnect: function(pVtab){
try {
const t = vth.vtab2js(pVtab, true);
t.dispose();
return 0;
}catch(e){
return vth.xMethodError('xDisconnect',e);
}
},
xOpen: function(pVtab, ppCursor){
try{
const t = vth.vtab2js(pVtab), c = vth.vcur2js();
T.assert(t instanceof capi.sqlite3_vtab);
T.assert(c instanceof capi.sqlite3_vtab_cursor);
wasm.setPtrValue(ppCursor, c.pointer);
c._rowId = 0;
return 0;
}catch(e){
return vth.xMethodError('xOpen',e);
}
},
xClose: function(pCursor){
try{
const c = vth.vcur2js(pCursor,true);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
.assert(!vth.vcur2js(pCursor));
c.dispose();
return 0;
}catch(e){
return vth.xMethodError('xClose',e);
}
},
xNext: function(pCursor){
try{
const c = vth.vcur2js(pCursor);
++c._rowId;
return 0;
}catch(e){
return vth.xMethodError('xNext',e);
}
},
xColumn: function(pCursor, pCtx, iCol){
try{
const c = vth.vcur2js(pCursor);
switch(iCol){
case tmplCols.A:
capi.sqlite3_result_int(pCtx, 1000 + c._rowId);
break;
case tmplCols.B:
capi.sqlite3_result_int(pCtx, 2000 + c._rowId);
break;
default: sqlite3.SQLite3Error.toss("Invalid column id",iCol);
}
return 0;
}catch(e){
return vth.xMethodError('xColumn',e);
}
},
xRowid: function(pCursor, ppRowid64){
try{
const c = vth.vcur2js(pCursor);
vth.setRowId(ppRowid64, c._rowId);
return 0;
}catch(e){
return vth.xMethodError('xRowid',e);
}
},
xEof: function(pCursor){
const c = vth.vcur2js(pCursor);
return c._rowId>=10;
},
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
try{
const c = vth.vcur2js(pCursor);
c._rowId = 0;
return 0;
}catch(e){
return vth.xMethodError('xFilter',e);
}
},
xBestIndex: function(pVtab, pIdxInfo){
try{
const t = vth.vtab2js(pVtab);
const pii = new capi.sqlite3_index_info(pIdxInfo);
pii.$estimatedRows = 10;
pii.$estimatedCost = 10.0;
pii.dispose();
return 0;
}catch(e){
return vth.xMethodError('xBestIndex',e);
}
}
};
/**
Problem to resolve: the vtab API places relevance on
whether xCreate and xConnect are exactly the same function
(same pointer address). Two JS-side references to the same
method will end up, without acrobatics to counter it, being
compiled as two different WASM-side bindings, i.e. two
different pointers.
In order to account for this, VtabHelper.installMethods()
checks for duplicate function entries and maps them to the
same WASM-compiled instance
*/
if(1){
tmplMethods.xCreate = tmplMethods.xConnect;
}
const tmplMod = new sqlite3.capi.sqlite3_module();
tmplMod.ondispose = [];
tmplMod.$iVersion = 0;
vth.installMethods(tmplMod, tmplMethods, true);
if(tmplMethods.xCreate){
T.assert(tmplMod.$xCreate === tmplMod.$xConnect,
"installMethods() must avoid re-compiling identical functions");
tmplMod.$xCreate = 0;
}
let rc = capi.sqlite3_create_module(
this.db, "testvtab", tmplMod, 0
);
this.db.checkRc(rc);
const list = this.db.selectArrays(
"SELECT a,b FROM testvtab order by a"
);
T.assert(10===list.length)
.assert(1000===list[0][0])
.assert(2009===list[list.length-1][1])
}
})/*vtab sanity checks*/
////////////////////////////////////////////////////////////////////
.t({
name: 'C-side WASM tests (if compiled in)',
@ -1590,8 +1781,8 @@ self.sqlite3InitModule = sqlite3InitModule;
}/* jaccwabyt-specific tests */)
.t('Close db', function(){
T.assert(this.db).assert(Number.isInteger(this.db.pointer));
wasm.exports.sqlite3_wasm_db_reset(this.db.pointer);
T.assert(this.db).assert(wasm.isPtr(this.db.pointer));
wasm.sqlite3_wasm_db_reset(this.db);
this.db.close();
T.assert(!this.db.pointer);
})

View File

@ -1,5 +1,5 @@
C Remove\stwo\sfeatures\sof\sjaccwabyt\swhich\swere\sfundamentally\sflawed,\salong\swith\sapprox.\s250\slines\sof\sunit\stests\swhich\sheavily\srelied\son\sthem.\sThankfully,\snone\sof\sthe\ssqlite3.js-level\scode\sused\sthose\sbits.
D 2022-12-05T15:05:46.306
C Add\sa\sdemonstration\ssqlite3_vtab/module\simplemented\sin\sJS,\sbased\son\sext/misc/templatevtab.c.\sAdd\soo1.selectArrays()\sand\sselectObjects().
D 2022-12-06T06:09:03.466
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -491,12 +491,12 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c
F ext/wasm/GNUmakefile bfa47f169468ca9db031105b0e336db29a88e93c3abd217d0bbb2b8731fa5413
F ext/wasm/GNUmakefile 54c0db93a5493f625c0a993c12aee5d83951440eee03b2aecfc8aeb998182998
F ext/wasm/README-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20
F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api ffa70413409e922ce0f761779787a1d9100b34b43c8e3106bb7ccf2786a41326
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
F ext/wasm/api/README.md 20a256f4aaae80035d2bb1c9e3e0a125570313a8d137d427471d7be10edde87a
F ext/wasm/api/README.md 17fb1e10335cc87e366dec496c5b17b061f3f75cdf216e825258de34d97a3e53
F ext/wasm/api/extern-post-js.c-pp.js 8923f76c3d2213159e12d641dc750523ead5c848185dc4996fae5cc12397f88d
F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1
@ -504,12 +504,12 @@ F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b
F ext/wasm/api/pre-js.c-pp.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f9e91c1d5f
F ext/wasm/api/sqlite3-api-cleanup.js 680d5ccfff54459db136a49b2199d9f879c8405d9c99af1dda0cc5e7c29056f4
F ext/wasm/api/sqlite3-api-glue.js c3a11e1d0e6fd381f68f9e76ad01f3616a6b809fbf9f5aa8e323955c128a6811
F ext/wasm/api/sqlite3-api-oo1.js 793883953d4024e7b8c5ee1c7a6cb49c18ca53a1d235a203f93746f8907d32ba
F ext/wasm/api/sqlite3-api-prologue.js 815fef5ee93e1bb11ebec5a1d6a1b8ae2e47cfeb66dc5f6e93380ccce045f194
F ext/wasm/api/sqlite3-api-oo1.js b970787aaf0bdd3a3df59cf66aeb84d0decaaa0529aed7eaf45121087181245f
F ext/wasm/api/sqlite3-api-prologue.js 31cffd8ce212fad8d316a08decd864b0f614c5fce3686153707dffe40f8279e8
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js f79dd8d98ef3e0b55c10bb2bee7a3840fa967318e1f577c156aafc34664271d1
F ext/wasm/api/sqlite3-vfs-helper.js 4ad4faf02e1524bf0296be8452c00b5708dce6faf649468d0377e26a0b299263
F ext/wasm/api/sqlite3-v-helper.js 4451763a0cd85734f0afe18b48918cb3c88ca99cef399b7c5f12119281e7b6a8 w ext/wasm/api/sqlite3-vfs-helper.js
F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 29d6487a26b2fb6a471cde52c37ffee7c27ed6a91914b308c247e0706f454ffb
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 723522a6c2a2463884a83fa1cc7ae5770deaaf0856a1058cc1023b2bfa1c898b
@ -539,8 +539,8 @@ F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5
F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2
F ext/wasm/index-dist.html c806b6005145b71d64240606e9c6e0bf56878ee8829c66fe7486cebf34b0e6b1
F ext/wasm/index.html f151b7c7b5cfdc066567d556acd168e769efd4e982286dc5f849a5ee69ecd0ff
F ext/wasm/jaccwabyt/jaccwabyt.js b7261221133cda8d363f16ddbac8e5b671fd51ce962fc34dc10e738a293b696d
F ext/wasm/jaccwabyt/jaccwabyt.md 72742a3205f1477de68086e7e4a854ed5f7d08dfcd6db54cdbea4dba4866f159
F ext/wasm/jaccwabyt/jaccwabyt.js f4fc93375e9c40ef60f56cbecca1b4dc8bf4f53fab2c3abc860ed34890c5d32d
F ext/wasm/jaccwabyt/jaccwabyt.md 4bf62f7519857cdd674594aba7436cc4fae177eefbfaabc00740e16d9a828bee
F ext/wasm/module-symbols.html 980680c8acfa3c8ae6a5aa223512d1b8e78040ced20f8ba2c382129bc73ec028
F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18
@ -555,7 +555,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
F ext/wasm/tester1-worker.html d43f3c131d88f10d00aff3e328fed13c858d674ea2ff1ff90225506137f85aa9
F ext/wasm/tester1.c-pp.html d34bef3d48e5cbc1c7c06882ad240fec49bf88f5f65696cc2c72c416933aa406
F ext/wasm/tester1.c-pp.js 8ed17c0e1f271e536cb7ccd86d3992785fc8bf2f94f9c2e0088ca670601ee087
F ext/wasm/tester1.c-pp.js d96a77dbf0d8af11e3f3adb013bee2bfb5bf9410e3f5eade528d70104451dd10
F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70
F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@ -2065,8 +2065,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P a329a809b5da135a9c251e4d5f637d45d01d0248110ac05f2ad8f01d9df38c64
R 3d4ed37777b015bac598ce3a6734f5aa
P a190abc307847174f36421eaa3f47ef349c6f84a2bb35857fa64f64bbe722708
R 5c37541d673cc882e16d46aec14f6ca8
U stephan
Z 002b2dd6406bdc41efd7e7f9f5d7918f
Z d89d1c25adf51ede4f60b7c8a7ea4184
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
a190abc307847174f36421eaa3f47ef349c6f84a2bb35857fa64f64bbe722708
60482c97e02bc4cafefef281be0cf0bc8c5c53232162829c137f3f7a80cdc534