1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Initial support for virtual tables implemented in JavaScript.

FossilOrigin-Name: a1454744c770a30a32a6d7b7fc59ef7be48cf67348b238540592850d7c2c7757
This commit is contained in:
stephan
2022-12-08 15:00:53 +00:00
23 changed files with 1859 additions and 967 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 += $(sqlite3-api-build-version.js)
sqlite3-api.jses += $(dir.api)/sqlite3-api-oo1.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-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-vfs-opfs.c-pp.js
sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js sqlite3-api.jses += $(dir.api)/sqlite3-api-cleanup.js

View File

@ -6,12 +6,15 @@ _sqlite3_bind_int64
_sqlite3_bind_null _sqlite3_bind_null
_sqlite3_bind_parameter_count _sqlite3_bind_parameter_count
_sqlite3_bind_parameter_index _sqlite3_bind_parameter_index
_sqlite3_bind_pointer
_sqlite3_bind_text _sqlite3_bind_text
_sqlite3_busy_handler
_sqlite3_busy_timeout _sqlite3_busy_timeout
_sqlite3_changes _sqlite3_changes
_sqlite3_changes64 _sqlite3_changes64
_sqlite3_clear_bindings _sqlite3_clear_bindings
_sqlite3_close_v2 _sqlite3_close_v2
_sqlite3_collation_needed
_sqlite3_column_blob _sqlite3_column_blob
_sqlite3_column_bytes _sqlite3_column_bytes
_sqlite3_column_count _sqlite3_column_count
@ -24,14 +27,21 @@ _sqlite3_column_text
_sqlite3_column_type _sqlite3_column_type
_sqlite3_compileoption_get _sqlite3_compileoption_get
_sqlite3_compileoption_used _sqlite3_compileoption_used
_sqlite3_complete
_sqlite3_create_collation
_sqlite3_create_collation_v2
_sqlite3_create_function _sqlite3_create_function
_sqlite3_create_function_v2 _sqlite3_create_function_v2
_sqlite3_create_module
_sqlite3_create_module_v2
_sqlite3_create_window_function _sqlite3_create_window_function
_sqlite3_data_count _sqlite3_data_count
_sqlite3_db_filename _sqlite3_db_filename
_sqlite3_db_handle _sqlite3_db_handle
_sqlite3_db_name _sqlite3_db_name
_sqlite3_declare_vtab
_sqlite3_deserialize _sqlite3_deserialize
_sqlite3_drop_modules
_sqlite3_errmsg _sqlite3_errmsg
_sqlite3_error_offset _sqlite3_error_offset
_sqlite3_errstr _sqlite3_errstr
@ -42,6 +52,7 @@ _sqlite3_extended_result_codes
_sqlite3_file_control _sqlite3_file_control
_sqlite3_finalize _sqlite3_finalize
_sqlite3_free _sqlite3_free
_sqlite3_get_auxdata
_sqlite3_initialize _sqlite3_initialize
_sqlite3_libversion _sqlite3_libversion
_sqlite3_libversion_number _sqlite3_libversion_number
@ -50,6 +61,7 @@ _sqlite3_malloc64
_sqlite3_msize _sqlite3_msize
_sqlite3_open _sqlite3_open
_sqlite3_open_v2 _sqlite3_open_v2
_sqlite3_overload_function
_sqlite3_prepare_v2 _sqlite3_prepare_v2
_sqlite3_prepare_v3 _sqlite3_prepare_v3
_sqlite3_randomness _sqlite3_randomness
@ -65,16 +77,20 @@ _sqlite3_result_error_toobig
_sqlite3_result_int _sqlite3_result_int
_sqlite3_result_int64 _sqlite3_result_int64
_sqlite3_result_null _sqlite3_result_null
_sqlite3_result_pointer
_sqlite3_result_text _sqlite3_result_text
_sqlite3_result_zeroblob _sqlite3_result_zeroblob
_sqlite3_result_zeroblob64 _sqlite3_result_zeroblob64
_sqlite3_serialize _sqlite3_serialize
_sqlite3_set_auxdata
_sqlite3_shutdown _sqlite3_shutdown
_sqlite3_sourceid _sqlite3_sourceid
_sqlite3_sql _sqlite3_sql
_sqlite3_step _sqlite3_step
_sqlite3_strglob _sqlite3_strglob
_sqlite3_stricmp
_sqlite3_strlike _sqlite3_strlike
_sqlite3_strnicmp
_sqlite3_total_changes _sqlite3_total_changes
_sqlite3_total_changes64 _sqlite3_total_changes64
_sqlite3_trace_v2 _sqlite3_trace_v2
@ -88,11 +104,20 @@ _sqlite3_value_bytes
_sqlite3_value_double _sqlite3_value_double
_sqlite3_value_int _sqlite3_value_int
_sqlite3_value_int64 _sqlite3_value_int64
_sqlite3_value_pointer
_sqlite3_value_text _sqlite3_value_text
_sqlite3_value_type _sqlite3_value_type
_sqlite3_vfs_find _sqlite3_vfs_find
_sqlite3_vfs_register _sqlite3_vfs_register
_sqlite3_vfs_unregister _sqlite3_vfs_unregister
_sqlite3_vtab_collation
_sqlite3_vtab_distinct
_sqlite3_vtab_in
_sqlite3_vtab_in_first
_sqlite3_vtab_in_next
_sqlite3_vtab_nochange
_sqlite3_vtab_on_conflict
_sqlite3_vtab_rhs_value
_malloc _malloc
_free _free
_realloc _realloc

View File

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

View File

@ -31,33 +31,50 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
heap: 0 ? wasm.memory : wasm.heap8u, heap: 0 ? wasm.memory : wasm.heap8u,
alloc: wasm.alloc, alloc: wasm.alloc,
dealloc: wasm.dealloc, dealloc: wasm.dealloc,
functionTable: wasm.functionTable,
bigIntEnabled: wasm.bigIntEnabled, bigIntEnabled: wasm.bigIntEnabled,
memberPrefix: '$' memberPrefix: /* Never change this: this prefix is baked into any
amount of code and client-facing docs. */ '$'
}); });
delete self.Jaccwabyt; delete self.Jaccwabyt;
if(0){
/* "The problem" is that the following isn't even remotely
type-safe. OTOH, nothing about WASM pointers is. */
const argPointer = wasm.xWrap.argAdapter('*');
wasm.xWrap.argAdapter('StructType', (v)=>{
if(v && v.constructor && v instanceof StructBinder.StructType){
v = v.pointer;
}
return wasm.isPtr(v)
? argPointer(v)
: toss("Invalid (object) type for StructType-type argument.");
});
}
{/* Convert Arrays and certain TypedArrays to strings for {/* Convert Arrays and certain TypedArrays to strings for
'flexible-string'-type arguments */ 'string:flexible'-type arguments */
const xString = wasm.xWrap.argAdapter('string'); const xString = wasm.xWrap.argAdapter('string');
wasm.xWrap.argAdapter( wasm.xWrap.argAdapter(
'flexible-string', (v)=>xString(util.flexibleString(v)) 'string:flexible', (v)=>xString(util.flexibleString(v))
); );
}
/**
The 'string:static' argument adapter treats its argument as
either...
- WASM pointer: assumed to be a long-lived C-string which gets
returned as-is.
- Anything else: gets coerced to a JS string for use as a map
key. If a matching entry is found (as described next), it is
returned, else wasm.allocCString() is used to create a a new
string, map its pointer to (''+v) for the remainder of the
application's life, and returns that pointer value for this
call and all future calls which are passed a
string-equivalent argument.
Use case: sqlite3_bind_pointer() and sqlite3_result_pointer()
call for "a static string and preferably a string
literal". This converter is used to ensure that the string
value seen by those functions is long-lived and behaves as they
need it to.
*/
wasm.xWrap.argAdapter(
'string:static',
function(v){
if(wasm.isPtr(v)) return v;
v = ''+v;
let rc = this[v];
return rc || (rc = this[v] = wasm.allocCString(v));
}.bind(Object.create(null))
);
}/* special-case string-type argument conversions */
if(1){// WhWasmUtil.xWrap() bindings... if(1){// WhWasmUtil.xWrap() bindings...
/** /**
@ -68,15 +85,21 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`sqlite3_vfs*` via capi.sqlite3_vfs.pointer. `sqlite3_vfs*` via capi.sqlite3_vfs.pointer.
*/ */
const aPtr = wasm.xWrap.argAdapter('*'); const aPtr = wasm.xWrap.argAdapter('*');
const nilType = function(){};
wasm.xWrap.argAdapter('sqlite3_filename', aPtr) wasm.xWrap.argAdapter('sqlite3_filename', aPtr)
('sqlite3_stmt*', aPtr) ('sqlite3_stmt*', aPtr)
('sqlite3_context*', aPtr) ('sqlite3_context*', aPtr)
('sqlite3_value*', aPtr) ('sqlite3_value*', aPtr)
('void*', aPtr) ('void*', aPtr)
('sqlite3*', (v)=>{ ('sqlite3*', (v)=>
if(sqlite3.oo1 && v instanceof sqlite3.oo1.DB) v = v.pointer; aPtr((v instanceof (sqlite3?.oo1?.DB || nilType))
return aPtr(v); ? v.pointer : v))
}) ('sqlite3_index_info*', (v)=>
aPtr((v instanceof (capi.sqlite3_index_info || nilType))
? v.pointer : v))
('sqlite3_module*', (v)=>
aPtr((v instanceof (capi.sqlite3_module || nilType))
? v.pointer : v))
/** /**
`sqlite3_vfs*`: `sqlite3_vfs*`:
@ -87,14 +110,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
*/ */
('sqlite3_vfs*', (v)=>{ ('sqlite3_vfs*', (v)=>{
if('string'===typeof v){ if('string'===typeof v){
const x = capi.sqlite3_vfs_find(v);
/* A NULL sqlite3_vfs pointer will be treated as the default /* A NULL sqlite3_vfs pointer will be treated as the default
VFS in many contexts. We specifically do not want that VFS in many contexts. We specifically do not want that
behavior here. */ behavior here. */
if(!x) sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v); return capi.sqlite3_vfs_find(v)
return x; || sqlite3.SQLite3Error.toss("Unknown sqlite3_vfs name:",v);
}else if(v instanceof sqlite3.capi.sqlite3_vfs) v = v.pointer; }
return aPtr(v); return aPtr((v instanceof capi.sqlite3_vfs) ? v.pointer : v);
}); });
wasm.xWrap.resultAdapter('sqlite3*', aPtr) wasm.xWrap.resultAdapter('sqlite3*', aPtr)
@ -127,7 +149,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
: fI64Disabled(e[0]); : fI64Disabled(e[0]);
} }
/* There's no(?) need to expose bindingSignatures to clients, /* There's no need to expose bindingSignatures to clients,
implicitly making it part of the public interface. */ implicitly making it part of the public interface. */
delete wasm.bindingSignatures; delete wasm.bindingSignatures;
@ -141,21 +163,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return errCode; return errCode;
}; };
} }
}/*xWrap() bindings*/; }/*xWrap() bindings*/;
/**
When registering a VFS and its related components it may be
necessary to ensure that JS keeps a reference to them to keep
them from getting garbage collected. Simply pass each such value
to this function and a reference will be held to it for the life
of the app.
*/
capi.sqlite3_vfs_register.addReference = function f(...args){
if(!f._) f._ = [];
f._.push(...args);
};
/** /**
Internal helper to assist in validating call argument counts in Internal helper to assist in validating call argument counts in
the hand-written sqlite3_xyz() wrappers. We do this only for the hand-written sqlite3_xyz() wrappers. We do this only for
@ -167,29 +176,9 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
(1===n?"":'s')+"."); (1===n?"":'s')+".");
}; };
/**
Helper for flexible-string conversions which require a
byte-length counterpart argument. Passed a value and its
ostensible length, this function returns [V,N], where V
is either v or a transformed copy of v and N is either n,
-1, or the byte length of v (if it's a byte array).
*/
const __flexiString = function(v,n){
if('string'===typeof v){
n = -1;
}else if(util.isSQLableTypedArray(v)){
n = v.byteLength;
v = util.typedArrayToString(v);
}else if(Array.isArray(v)){
v = v.join("");
n = -1;
}
return [v, n];
};
if(1){/* Special-case handling of sqlite3_exec() */ if(1){/* Special-case handling of sqlite3_exec() */
const __exec = wasm.xWrap("sqlite3_exec", "int", const __exec = wasm.xWrap("sqlite3_exec", "int",
["sqlite3*", "flexible-string", "*", "*", "**"]); ["sqlite3*", "string:flexible", "*", "*", "**"]);
/* Documented in the api object's initializer. */ /* Documented in the api object's initializer. */
capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){ capi.sqlite3_exec = function f(pDb, sql, callback, pVoid, pErrMsg){
if(f.length!==arguments.length){ if(f.length!==arguments.length){
@ -204,8 +193,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
let aVals = [], aNames = [], i = 0, offset = 0; let aVals = [], aNames = [], i = 0, offset = 0;
for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){ for( ; i < nCols; offset += (wasm.ptrSizeof * ++i) ){
aVals.push( wasm.cstringToJs(wasm.getPtrValue(pColVals + offset)) ); aVals.push( wasm.cstrToJs(wasm.getPtrValue(pColVals + offset)) );
aNames.push( wasm.cstringToJs(wasm.getPtrValue(pColNames + offset)) ); aNames.push( wasm.cstrToJs(wasm.getPtrValue(pColNames + offset)) );
} }
rc = callback(pVoid, nCols, aVals, aNames) | 0; rc = callback(pVoid, nCols, aVals, aNames) | 0;
/* The first 2 args of the callback are useless for JS but /* The first 2 args of the callback are useless for JS but
@ -540,6 +529,26 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(1){/* Special-case handling of sqlite3_prepare_v2() and if(1){/* Special-case handling of sqlite3_prepare_v2() and
sqlite3_prepare_v3() */ sqlite3_prepare_v3() */
/**
Helper for string:flexible conversions which require a
byte-length counterpart argument. Passed a value and its
ostensible length, this function returns [V,N], where V
is either v or a transformed copy of v and N is either n,
-1, or the byte length of v (if it's a byte array).
*/
const __flexiString = (v,n)=>{
if('string'===typeof v){
n = -1;
}else if(util.isSQLableTypedArray(v)){
n = v.byteLength;
v = util.typedArrayToString(v);
}else if(Array.isArray(v)){
v = v.join("");
n = -1;
}
return [v, n];
};
/** /**
Scope-local holder of the two impls of sqlite3_prepare_v2/v3(). Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
*/ */
@ -570,7 +579,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
"int", ["sqlite3*", "*", "int", "int", "int", ["sqlite3*", "*", "int", "int",
"**", "**"]); "**", "**"]);
/* Documented in the api object's initializer. */ /* Documented in the capi object's initializer. */
capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){ capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
if(f.length!==arguments.length){ if(f.length!==arguments.length){
return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length); return __dbArgcMismatch(pDb,"sqlite3_prepare_v3",f.length);
@ -587,7 +596,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
} }
}; };
/* Documented in the api object's initializer. */ /* Documented in the capi object's initializer. */
capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){ capi.sqlite3_prepare_v2 = function f(pDb, sql, sqlLen, ppStmt, pzTail){
return (f.length===arguments.length) return (f.length===arguments.length)
? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail) ? capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail)
@ -601,15 +610,19 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
toss("Maintenance required: increase sqlite3_wasm_enum_json()'s", toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
"static buffer size!"); "static buffer size!");
} }
wasm.ctype = JSON.parse(wasm.cstringToJs(cJson)); wasm.ctype = JSON.parse(wasm.cstrToJs(cJson));
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson)); //console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
for(const t of ['access', 'blobFinalizers', 'dataTypes', const defineGroups = ['access', 'authorizer',
'encodings', 'fcntl', 'flock', 'ioCap', 'blobFinalizers', 'dataTypes',
'limits', 'encodings', 'fcntl', 'flock', 'ioCap',
'openFlags', 'prepareFlags', 'resultCodes', 'limits', 'openFlags',
'serialize', 'syncFlags', 'trace', 'udfFlags', 'prepareFlags', 'resultCodes',
'version' 'serialize', 'syncFlags', 'trace', 'udfFlags',
]){ 'version' ];
if(wasm.bigIntEnabled){
defineGroups.push('vtab');
}
for(const t of defineGroups){
for(const e of Object.entries(wasm.ctype[t])){ for(const e of Object.entries(wasm.ctype[t])){
// ^^^ [k,v] there triggers a buggy code transformation via // ^^^ [k,v] there triggers a buggy code transformation via
// one of the Emscripten-driven optimizers. // one of the Emscripten-driven optimizers.
@ -629,19 +642,37 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc]; capi.sqlite3_js_rc_str = (rc)=>__rcMap[rc];
/* Bind all registered C-side structs... */ /* Bind all registered C-side structs... */
const notThese = Object.assign(Object.create(null),{ const notThese = Object.assign(Object.create(null),{
// Structs NOT to register // For each struct to NOT register, map its name to true:
WasmTestStruct: true WasmTestStruct: true,
/* We unregister the kvvfs VFS from Worker threads below. */
sqlite3_kvvfs_methods: !util.isUIThread(),
/* sqlite3_index_info and friends require int64: */
sqlite3_index_info: !wasm.bigIntEnabled,
sqlite3_index_constraint: !wasm.bigIntEnabled,
sqlite3_index_orderby: !wasm.bigIntEnabled,
sqlite3_index_constraint_usage: !wasm.bigIntEnabled
}); });
if(!util.isUIThread()){
/* We remove the kvvfs VFS from Worker threads below. */
notThese.sqlite3_kvvfs_methods = true;
}
for(const s of wasm.ctype.structs){ for(const s of wasm.ctype.structs){
if(!notThese[s.name]){ if(!notThese[s.name]){
capi[s.name] = sqlite3.StructBinder(s); capi[s.name] = sqlite3.StructBinder(s);
} }
} }
}/*end C constant imports*/ if(capi.sqlite3_index_info){
/* Move these inner structs into sqlite3_index_info. Binding
** them to WASM requires that we create global-scope structs to
** model them with, but those are no longer needed after we've
** passed them to StructBinder. */
for(const k of ['sqlite3_index_constraint',
'sqlite3_index_orderby',
'sqlite3_index_constraint_usage']){
capi.sqlite3_index_info[k] = capi[k];
delete capi[k];
}
capi.sqlite3_vtab_config =
(pDb, op, arg=0)=>wasm.exports.sqlite3_wasm_vtab_config(
wasm.xWrap.argAdapter('sqlite3*')(pDb), op, arg);
}/* end vtab-related setup */
}/*end C constant and struct imports*/
const pKvvfs = capi.sqlite3_vfs_find("kvvfs"); const pKvvfs = capi.sqlite3_vfs_find("kvvfs");
if( pKvvfs ){/* kvvfs-specific glue */ if( pKvvfs ){/* kvvfs-specific glue */
@ -652,8 +683,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
delete capi.sqlite3_kvvfs_methods; delete capi.sqlite3_kvvfs_methods;
const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack, const kvvfsMakeKey = wasm.exports.sqlite3_wasm_kvvfsMakeKeyOnPstack,
pstack = wasm.pstack, pstack = wasm.pstack;
pAllocRaw = wasm.exports.sqlite3_wasm_pstack_alloc;
const kvvfsStorage = (zClass)=> const kvvfsStorage = (zClass)=>
((115/*=='s'*/===wasm.getMemValue(zClass)) ((115/*=='s'*/===wasm.getMemValue(zClass))
@ -672,7 +702,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
const zXKey = kvvfsMakeKey(zClass,zKey); const zXKey = kvvfsMakeKey(zClass,zKey);
if(!zXKey) return -3/*OOM*/; if(!zXKey) return -3/*OOM*/;
const jKey = wasm.cstringToJs(zXKey); const jKey = wasm.cstrToJs(zXKey);
const jV = kvvfsStorage(zClass).getItem(jKey); const jV = kvvfsStorage(zClass).getItem(jKey);
if(!jV) return -1; if(!jV) return -1;
const nV = jV.length /* Note that we are relying 100% on v being const nV = jV.length /* Note that we are relying 100% on v being
@ -701,8 +731,8 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
const zXKey = kvvfsMakeKey(zClass,zKey); const zXKey = kvvfsMakeKey(zClass,zKey);
if(!zXKey) return 1/*OOM*/; if(!zXKey) return 1/*OOM*/;
const jKey = wasm.cstringToJs(zXKey); const jKey = wasm.cstrToJs(zXKey);
kvvfsStorage(zClass).setItem(jKey, wasm.cstringToJs(zData)); kvvfsStorage(zClass).setItem(jKey, wasm.cstrToJs(zData));
return 0; return 0;
}catch(e){ }catch(e){
console.error("kvstorageWrite()",e); console.error("kvstorageWrite()",e);
@ -716,7 +746,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
try { try {
const zXKey = kvvfsMakeKey(zClass,zKey); const zXKey = kvvfsMakeKey(zClass,zKey);
if(!zXKey) return 1/*OOM*/; if(!zXKey) return 1/*OOM*/;
kvvfsStorage(zClass).removeItem(wasm.cstringToJs(zXKey)); kvvfsStorage(zClass).removeItem(wasm.cstrToJs(zXKey));
return 0; return 0;
}catch(e){ }catch(e){
console.error("kvstorageDelete()",e); console.error("kvstorageDelete()",e);

View File

@ -73,7 +73,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
if(capi.SQLITE_TRACE_STMT===t){ if(capi.SQLITE_TRACE_STMT===t){
// x == SQL, p == sqlite3_stmt* // x == SQL, p == sqlite3_stmt*
console.log("SQL TRACE #"+(++this.counter), console.log("SQL TRACE #"+(++this.counter),
wasm.cstringToJs(x)); wasm.cstrToJs(x));
} }
}.bind({counter: 0})); }.bind({counter: 0}));
@ -139,7 +139,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
console.error("Invalid DB ctor args",opt,arguments); console.error("Invalid DB ctor args",opt,arguments);
toss3("Invalid arguments for DB constructor."); toss3("Invalid arguments for DB constructor.");
} }
let fnJs = ('number'===typeof fn) ? wasm.cstringToJs(fn) : fn; let fnJs = ('number'===typeof fn) ? wasm.cstrToJs(fn) : fn;
const vfsCheck = ctor._name2vfs[fnJs]; const vfsCheck = ctor._name2vfs[fnJs];
if(vfsCheck){ if(vfsCheck){
vfsName = vfsCheck.vfs; vfsName = vfsCheck.vfs;
@ -192,13 +192,13 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
/** /**
Sets SQL which should be exec()'d on a DB instance after it is Sets SQL which should be exec()'d on a DB instance after it is
opened with the given VFS pointer. The SQL may be any type opened with the given VFS pointer. The SQL may be any type
supported by the "flexible-string" function argument supported by the "string:flexible" function argument conversion.
conversion. Alternately, the 2nd argument may be a function, in Alternately, the 2nd argument may be a function, in which case it
which case it is called with (theOo1DbObject,sqlite3Namespace) at is called with (theOo1DbObject,sqlite3Namespace) at the end of
the end of the DB() constructor. The function must throw on the DB() constructor. The function must throw on error, in which
error, in which case the db is closed and the exception is case the db is closed and the exception is propagated. This
propagated. This function is intended only for use by DB function is intended only for use by DB subclasses or sqlite3_vfs
subclasses or sqlite3_vfs implementations. implementations.
*/ */
dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){ dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){
__vfsPostOpenSql[pVfs] = sql; __vfsPostOpenSql[pVfs] = sql;
@ -473,6 +473,15 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return rc; 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 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 be null) and an sqlite3 API result code. If the result code is
@ -511,10 +520,12 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
db is closed but before auxiliary state like this.filename is db is closed but before auxiliary state like this.filename is
cleared. cleared.
Both onclose handlers are passed this object. If this db is not Both onclose handlers are passed this object, with the onclose
opened, neither of the handlers are called. Any exceptions the object as their "this," noting that the db will have been
handlers throw are ignored because "destructors must not closed when onclose.after is called. If this db is not opened
throw." when close() is called, neither of the handlers are called. Any
exceptions the handlers throw are ignored because "destructors
must not throw."
Note that garbage collection of a db handle, if it happens at Note that garbage collection of a db handle, if it happens at
all, will never trigger close(), so onclose handlers are not a all, will never trigger close(), so onclose handlers are not a
@ -591,7 +602,7 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
); );
if(pVfs){ if(pVfs){
const v = new capi.sqlite3_vfs(pVfs); const v = new capi.sqlite3_vfs(pVfs);
try{ rc = wasm.cstringToJs(v.$zName) } try{ rc = wasm.cstrToJs(v.$zName) }
finally { v.dispose() } finally { v.dispose() }
} }
return rc; return rc;
@ -1098,6 +1109,26 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
return __selectFirstRow(this, sql, bind, {}); 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 Returns the number of currently-opened Stmt handles for this db
handle, or 0 if this DB instance is closed. handle, or 0 if this DB instance is closed.

View File

@ -185,28 +185,49 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
/** /**
Constructs this object with a message depending on its arguments: Constructs this object with a message depending on its arguments:
- If it's passed only a single integer argument, it is assumed If its first argument is an integer, it is assumed to be
to be an sqlite3 C API result code. The message becomes the an SQLITE_... result code and it is passed to
result of sqlite3.capi.sqlite3_js_rc_str() or (if that returns sqlite3.capi.sqlite3_js_rc_str() to stringify it.
falsy) a synthesized string which contains that integer.
- If passed 2 arguments and the 2nd is a object, it behaves If called with exactly 2 arguments and the 2nd is an object,
like the Error(string,object) constructor except that the first that object is treated as the 2nd argument to the parent
argument is subject to the is-integer semantics from the constructor.
previous point.
- Else all arguments are concatenated with a space between each The exception's message is created by concatenating its
one, using args.join(' '), to create the error message. arguments with a space between each, except for the
two-args-with-an-objec form and that the first argument will
get coerced to a string, as described above, if it's an
integer.
If passed an integer first argument, the error object's
`resultCode` member will be set to the given integer value,
else it will be set to capi.SQLITE_ERROR.
*/ */
constructor(...args){ constructor(...args){
if(1===args.length && __isInt(args[0])){ let rc;
super(__rcStr(args[0])); if(args.length){
}else if(2===args.length && 'object'===typeof args[1]){ if(__isInt(args[0])){
if(__isInt(args[0])) super(__rcStr(args[0]), args[1]); rc = args[0];
else super(...args); if(1===args.length){
}else{ super(__rcStr(args[0]));
super(args.join(' ')); }else{
const rcStr = __rcStr(rc);
if('object'===typeof args[1]){
super(rcStr,args[1]);
}else{
args[0] = rcStr+':';
super(args.join(' '));
}
}
}else{
if(2===args.length && 'object'===typeof args[1]){
super(...args);
}else{
super(args.join(' '));
}
}
} }
this.resultCode = rc || capi.SQLITE_ERROR;
this.name = 'SQLite3Error'; this.name = 'SQLite3Error';
} }
}; };
@ -348,13 +369,13 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
/** /**
If v is-a Array, its join("") result is returned. If If v is-a Array, its join("") result is returned. If
isSQLableTypedArray(v) is true then typedArrayToString(v) is isSQLableTypedArray(v) is true then typedArrayToString(v) is
returned. If it looks like a WASM pointer, wasm.cstringToJs(v) is returned. If it looks like a WASM pointer, wasm.cstrToJs(v) is
returned. Else v is returned as-is. returned. Else v is returned as-is.
*/ */
const flexibleString = function(v){ const flexibleString = function(v){
if(isSQLableTypedArray(v)) return typedArrayToString(v); if(isSQLableTypedArray(v)) return typedArrayToString(v);
else if(Array.isArray(v)) return v.join(""); else if(Array.isArray(v)) return v.join("");
else if(wasm.isPtr(v)) v = wasm.cstringToJs(v); else if(wasm.isPtr(v)) v = wasm.cstrToJs(v);
return v; return v;
}; };
@ -602,7 +623,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
If the callback is not a JS function then this binding performs If the callback is not a JS function then this binding performs
no translation of the callback, but the sql argument is still no translation of the callback, but the sql argument is still
converted to a WASM string for the call using the converted to a WASM string for the call using the
"flexible-string" argument converter. "string:flexible" argument converter.
*/ */
sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/, sqlite3_exec: (pDb, sql, callback, pVoid, pErrMsg)=>{}/*installed later*/,
@ -884,16 +905,23 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"], ["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"], ["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"],
["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"], ["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
["sqlite3_bind_pointer", "int",
"sqlite3_stmt*", "int", "*", "string:static", "*"],
["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int" ["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"
/* We should arguably create a hand-written binding of /* We should arguably create a hand-written binding of
bind_text() which does more flexible text conversion, along bind_text() which does more flexible text conversion, along
the lines of sqlite3_prepare_v3(). The slightly problematic the lines of sqlite3_prepare_v3(). The slightly problematic
part is the final argument (text destructor). */ part is the final argument (text destructor). */
], ],
//["sqlite3_busy_handler","int", "sqlite3*", "*", "*"],
// ^^^^ TODO: custom binding which auto-converts JS function arg
// to a WASM function, noting that calling it multiple times
// would introduce a leak.
["sqlite3_busy_timeout","int", "sqlite3*", "int"], ["sqlite3_busy_timeout","int", "sqlite3*", "int"],
["sqlite3_close_v2", "int", "sqlite3*"], ["sqlite3_close_v2", "int", "sqlite3*"],
["sqlite3_changes", "int", "sqlite3*"], ["sqlite3_changes", "int", "sqlite3*"],
["sqlite3_clear_bindings","int", "sqlite3_stmt*"], ["sqlite3_clear_bindings","int", "sqlite3_stmt*"],
["sqlite3_collation_needed", "int", "sqlite3*", "*", "*"/*=>v(ppis)*/],
["sqlite3_column_blob","*", "sqlite3_stmt*", "int"], ["sqlite3_column_blob","*", "sqlite3_stmt*", "int"],
["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"], ["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"],
["sqlite3_column_count", "int", "sqlite3_stmt*"], ["sqlite3_column_count", "int", "sqlite3_stmt*"],
@ -904,9 +932,16 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
["sqlite3_column_type","int", "sqlite3_stmt*", "int"], ["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
["sqlite3_compileoption_get", "string", "int"], ["sqlite3_compileoption_get", "string", "int"],
["sqlite3_compileoption_used", "int", "string"], ["sqlite3_compileoption_used", "int", "string"],
["sqlite3_complete", "int", "string:flexible"],
/* sqlite3_create_function(), sqlite3_create_function_v2(), and /* sqlite3_create_function(), sqlite3_create_function_v2(), and
sqlite3_create_window_function() use hand-written bindings to sqlite3_create_window_function() use hand-written bindings to
simplify handling of their function-type arguments. */ simplify handling of their function-type arguments. */
["sqlite3_create_collation", "int",
"sqlite3*", "string", "int"/*SQLITE_UTF8 is the only legal value*/,
"*", "*"],
["sqlite3_create_collation_v2", "int",
"sqlite3*", "string", "int"/*SQLITE_UTF8 is the only legal value*/,
"*", "*", "*"],
["sqlite3_data_count", "int", "sqlite3_stmt*"], ["sqlite3_data_count", "int", "sqlite3_stmt*"],
["sqlite3_db_filename", "string", "sqlite3*", "string"], ["sqlite3_db_filename", "string", "sqlite3*", "string"],
["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"], ["sqlite3_db_handle", "sqlite3*", "sqlite3_stmt*"],
@ -928,6 +963,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"], ["sqlite3_file_control", "int", "sqlite3*", "string", "int", "*"],
["sqlite3_finalize", "int", "sqlite3_stmt*"], ["sqlite3_finalize", "int", "sqlite3_stmt*"],
["sqlite3_free", undefined,"*"], ["sqlite3_free", undefined,"*"],
["sqlite3_get_auxdata", "*", "sqlite3_context*", "int"],
["sqlite3_initialize", undefined], ["sqlite3_initialize", undefined],
/*["sqlite3_interrupt", undefined, "sqlite3*" /*["sqlite3_interrupt", undefined, "sqlite3*"
^^^ we cannot actually currently support this because JS is ^^^ we cannot actually currently support this because JS is
@ -945,23 +981,28 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
the range of supported argument types. */ the range of supported argument types. */
["sqlite3_realloc", "*","*","int"], ["sqlite3_realloc", "*","*","int"],
["sqlite3_reset", "int", "sqlite3_stmt*"], ["sqlite3_reset", "int", "sqlite3_stmt*"],
["sqlite3_result_blob",undefined, "sqlite3_context*", "*", "int", "*"], ["sqlite3_result_blob", undefined, "sqlite3_context*", "*", "int", "*"],
["sqlite3_result_double",undefined, "sqlite3_context*", "f64"], ["sqlite3_result_double", undefined, "sqlite3_context*", "f64"],
["sqlite3_result_error",undefined, "sqlite3_context*", "string", "int"], ["sqlite3_result_error", undefined, "sqlite3_context*", "string", "int"],
["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"], ["sqlite3_result_error_code", undefined, "sqlite3_context*", "int"],
["sqlite3_result_error_nomem", undefined, "sqlite3_context*"], ["sqlite3_result_error_nomem", undefined, "sqlite3_context*"],
["sqlite3_result_error_toobig", undefined, "sqlite3_context*"], ["sqlite3_result_error_toobig", undefined, "sqlite3_context*"],
["sqlite3_result_int",undefined, "sqlite3_context*", "int"], ["sqlite3_result_int", undefined, "sqlite3_context*", "int"],
["sqlite3_result_null",undefined, "sqlite3_context*"], ["sqlite3_result_null", undefined, "sqlite3_context*"],
["sqlite3_result_text",undefined, "sqlite3_context*", "string", "int", "*"], ["sqlite3_result_pointer", undefined,
"sqlite3_context*", "*", "string:static", "*"],
["sqlite3_result_text", undefined, "sqlite3_context*", "string", "int", "*"],
["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"], ["sqlite3_result_zeroblob", undefined, "sqlite3_context*", "int"],
["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"], ["sqlite3_serialize","*", "sqlite3*", "string", "*", "int"],
["sqlite3_set_auxdata", undefined, "sqlite3_context*", "int", "*", "*"/* => v(*) */],
["sqlite3_shutdown", undefined], ["sqlite3_shutdown", undefined],
["sqlite3_sourceid", "string"], ["sqlite3_sourceid", "string"],
["sqlite3_sql", "string", "sqlite3_stmt*"], ["sqlite3_sql", "string", "sqlite3_stmt*"],
["sqlite3_step", "int", "sqlite3_stmt*"], ["sqlite3_step", "int", "sqlite3_stmt*"],
["sqlite3_strglob", "int", "string","string"], ["sqlite3_strglob", "int", "string","string"],
["sqlite3_strlike", "int", "string","string","int"], ["sqlite3_stricmp", "int", "string", "string"],
["sqlite3_strlike", "int", "string", "string","int"],
["sqlite3_strnicmp", "int", "string", "string", "int"],
["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"], ["sqlite3_trace_v2", "int", "sqlite3*", "int", "*", "*"],
["sqlite3_total_changes", "int", "sqlite3*"], ["sqlite3_total_changes", "int", "sqlite3*"],
/* Note sqlite3_uri_...() has very specific requirements /* Note sqlite3_uri_...() has very specific requirements
@ -975,6 +1016,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
["sqlite3_value_bytes","int", "sqlite3_value*"], ["sqlite3_value_bytes","int", "sqlite3_value*"],
["sqlite3_value_double","f64", "sqlite3_value*"], ["sqlite3_value_double","f64", "sqlite3_value*"],
["sqlite3_value_int","int", "sqlite3_value*"], ["sqlite3_value_int","int", "sqlite3_value*"],
["sqlite3_value_pointer", "*", "sqlite3_value*", "string:static"],
["sqlite3_value_text", "string", "sqlite3_value*"], ["sqlite3_value_text", "string", "sqlite3_value*"],
["sqlite3_value_type", "int", "sqlite3_value*"], ["sqlite3_value_type", "int", "sqlite3_value*"],
["sqlite3_vfs_find", "*", "string"], ["sqlite3_vfs_find", "*", "string"],
@ -993,19 +1035,40 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
Functions which require BigInt (int64) support are separated from Functions which require BigInt (int64) support are separated from
the others because we need to conditionally bind them or apply the others because we need to conditionally bind them or apply
dummy impls, depending on the capabilities of the environment. dummy impls, depending on the capabilities of the environment.
Note that not all of these functions directly require int64
but are only for use with APIs which require int64. For example,
the vtab-related functions.
*/ */
wasm.bindingSignatures.int64 = [ wasm.bindingSignatures.int64 = [
["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]], ["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
["sqlite3_changes64","i64", ["sqlite3*"]], ["sqlite3_changes64","i64", ["sqlite3*"]],
["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]], ["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
["sqlite3_create_module", "int",
["sqlite3*","string","sqlite3_module*","*"]],
["sqlite3_create_module_v2", "int",
["sqlite3*","string","sqlite3_module*","*","*"]],
["sqlite3_declare_vtab", "int", ["sqlite3*", "string:flexible"]],
["sqlite3_drop_modules", "int", ["sqlite3*", "**"]],
["sqlite3_malloc64", "*","i64"], ["sqlite3_malloc64", "*","i64"],
["sqlite3_msize", "i64", "*"], ["sqlite3_msize", "i64", "*"],
["sqlite3_overload_function", "int", ["sqlite3*","string","int"]],
["sqlite3_realloc64", "*","*", "i64"], ["sqlite3_realloc64", "*","*", "i64"],
["sqlite3_result_int64",undefined, "*", "i64"], ["sqlite3_result_int64", undefined, "*", "i64"],
["sqlite3_result_zeroblob64", "int", "*", "i64"], ["sqlite3_result_zeroblob64", "int", "*", "i64"],
["sqlite3_total_changes64", "i64", ["sqlite3*"]], ["sqlite3_total_changes64", "i64", ["sqlite3*"]],
["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]], ["sqlite3_uri_int64", "i64", ["sqlite3_filename", "string", "i64"]],
["sqlite3_value_int64","i64", "sqlite3_value*"], ["sqlite3_value_int64","i64", "sqlite3_value*"],
["sqlite3_vtab_collation","string","sqlite3_index_info*","int"],
["sqlite3_vtab_distinct","int", "sqlite3_index_info*"],
["sqlite3_vtab_in","int", "sqlite3_index_info*", "int", "int"],
["sqlite3_vtab_in_first", "int", "sqlite3_value*", "**"],
["sqlite3_vtab_in_next", "int", "sqlite3_value*", "**"],
/*["sqlite3_vtab_config" is variadic and requires a hand-written
proxy.] */
["sqlite3_vtab_nochange","int", "sqlite3_context*"],
["sqlite3_vtab_on_conflict","int", "sqlite3*"],
["sqlite3_vtab_rhs_value","int", "sqlite3_index_info*", "int", "**"]
]; ];
/** /**
@ -1297,7 +1360,7 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
let pVfs = capi.sqlite3_vfs_find(0); let pVfs = capi.sqlite3_vfs_find(0);
while(pVfs){ while(pVfs){
const oVfs = new capi.sqlite3_vfs(pVfs); const oVfs = new capi.sqlite3_vfs(pVfs);
rc.push(wasm.cstringToJs(oVfs.$zName)); rc.push(wasm.cstrToJs(oVfs.$zName));
pVfs = oVfs.$pNext; pVfs = oVfs.$pNext;
oVfs.dispose(); oVfs.dispose();
} }
@ -1600,7 +1663,6 @@ self.sqlite3ApiBootstrap = function sqlite3ApiBootstrap(
some initializers. Retain them when running in test mode some initializers. Retain them when running in test mode
so that we can add tests for them. */ so that we can add tests for them. */
delete sqlite3.util; delete sqlite3.util;
delete sqlite3.VfsHelper;
delete sqlite3.StructBinder; delete sqlite3.StructBinder;
} }
return sqlite3; return sqlite3;

View File

@ -13,7 +13,7 @@
A Worker which manages asynchronous OPFS handles on behalf of a A Worker which manages asynchronous OPFS handles on behalf of a
synchronous API which controls it via a combination of Worker synchronous API which controls it via a combination of Worker
messages, SharedArrayBuffer, and Atomics. It is the asynchronous messages, SharedArrayBuffer, and Atomics. It is the asynchronous
counterpart of the API defined in sqlite3-api-opfs.js. counterpart of the API defined in sqlite3-vfs-opfs.js.
Highly indebted to: Highly indebted to:
@ -343,16 +343,6 @@ const installAsyncProxy = function(self){
const affirmNotRO = function(opName,fh){ const affirmNotRO = function(opName,fh){
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
}; };
const affirmLocked = function(opName,fh){
//if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs);
/**
Currently a no-op, as speedtest1 triggers xRead() without a
lock (that seems like a bug but it's currently uninvestigated).
This means, however, that some OPFS VFS routines may trigger
acquisition of a lock but never let it go until xUnlock() is
called (which it likely won't be if xLock() was not called).
*/
};
/** /**
We track 2 different timers: the "metrics" timer records how much We track 2 different timers: the "metrics" timer records how much
@ -393,7 +383,6 @@ const installAsyncProxy = function(self){
*/ */
let flagAsyncShutdown = false; let flagAsyncShutdown = false;
/** /**
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
methods, as well as helpers like mkdir(). Maintenance reminder: methods, as well as helpers like mkdir(). Maintenance reminder:
@ -427,11 +416,11 @@ const installAsyncProxy = function(self){
}, },
xAccess: async (filename)=>{ xAccess: async (filename)=>{
mTimeStart('xAccess'); mTimeStart('xAccess');
/* OPFS cannot support the full range of xAccess() queries sqlite3 /* OPFS cannot support the full range of xAccess() queries
calls for. We can essentially just tell if the file is sqlite3 calls for. We can essentially just tell if the file
accessible, but if it is it's automatically writable (unless is accessible, but if it is then it's automatically writable
it's locked, which we cannot(?) know without trying to open (unless it's locked, which we cannot(?) know without trying
it). OPFS does not have the notion of read-only. to open it). OPFS does not have the notion of read-only.
The return semantics of this function differ from sqlite3's The return semantics of this function differ from sqlite3's
xAccess semantics because we are limited in what we can xAccess semantics because we are limited in what we can
@ -519,7 +508,6 @@ const installAsyncProxy = function(self){
let rc = 0; let rc = 0;
wTimeStart('xFileSize'); wTimeStart('xFileSize');
try{ try{
affirmLocked('xFileSize',fh);
const sz = await (await getSyncHandle(fh,'xFileSize')).getSize(); const sz = await (await getSyncHandle(fh,'xFileSize')).getSize();
state.s11n.serialize(Number(sz)); state.s11n.serialize(Number(sz));
}catch(e){ }catch(e){
@ -615,7 +603,6 @@ const installAsyncProxy = function(self){
let rc = 0, nRead; let rc = 0, nRead;
const fh = __openFiles[fid]; const fh = __openFiles[fid];
try{ try{
affirmLocked('xRead',fh);
wTimeStart('xRead'); wTimeStart('xRead');
nRead = (await getSyncHandle(fh,'xRead')).read( nRead = (await getSyncHandle(fh,'xRead')).read(
fh.sabView.subarray(0, n), fh.sabView.subarray(0, n),
@ -659,7 +646,6 @@ const installAsyncProxy = function(self){
const fh = __openFiles[fid]; const fh = __openFiles[fid];
wTimeStart('xTruncate'); wTimeStart('xTruncate');
try{ try{
affirmLocked('xTruncate',fh);
affirmNotRO('xTruncate', fh); affirmNotRO('xTruncate', fh);
await (await getSyncHandle(fh,'xTruncate')).truncate(size); await (await getSyncHandle(fh,'xTruncate')).truncate(size);
}catch(e){ }catch(e){
@ -696,7 +682,6 @@ const installAsyncProxy = function(self){
const fh = __openFiles[fid]; const fh = __openFiles[fid];
wTimeStart('xWrite'); wTimeStart('xWrite');
try{ try{
affirmLocked('xWrite',fh);
affirmNotRO('xWrite', fh); affirmNotRO('xWrite', fh);
rc = ( rc = (
n === (await getSyncHandle(fh,'xWrite')) n === (await getSyncHandle(fh,'xWrite'))

View File

@ -0,0 +1,599 @@
/*
** 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.toss3;
const vh = Object.create(null), vt = Object.create(null);
sqlite3.VfsHelper = vh;
sqlite3.VtabHelper = vt;
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, 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.
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.
*/
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);
}
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,"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 = [];
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.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.addOnDispose(
o.struct.$zName = wasm.allocCString(o.name)
);
}
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 xAbc() 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);
};
/**
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
Has 3 distinct uses:
- wrapVtab() 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.
- wrapVtab(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 xDestroy() (if there is a distinct
xCreate()) or xDisconnect() (if xCreate() is 0 or is the same
as xConnect()), in which case use...
- wrapVtab(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.xVtab = __xWrapFactory('xVtab',capi.sqlite3_vtab);
/**
EXPERIMENTAL. DO NOT USE IN CLIENT CODE.
Works identically to wrapVtab() except that it deals with
sqlite3_cursor objects and pointers instead of sqlite3_vtab.
- wrapCursor() is intended to be called from sqlite3_module::xOpen()
- wrapCursor(pCursor) is intended to be called from all sqlite3_module
methods which take a (sqlite3_vtab_cursor*) _except_ for
xClose(), in which case use...
- wrapCursor(pCursor, true) will remove the mapping 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 from xClose() or in error handling of a
failed xOpen().
*/
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
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.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.
*/
vt.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;
};
vt.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.
*/
/** vt.methodCatcher = function(methodName, method, defaultErrRc=capi.SQLITE_ERROR){
return function(...args){
try { method(...args); }
}catch(e){ return vt.xError(methodName, e, defaultRc) }
};
*/
/**
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.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.dealloc(wasm.getPtrValue(pzErr));
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()*/);

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

@ -803,7 +803,7 @@ const installOpfsVfs = function callee(options){
const vfsSyncWrappers = { const vfsSyncWrappers = {
xAccess: function(pVfs,zName,flags,pOut){ xAccess: function(pVfs,zName,flags,pOut){
mTimeStart('xAccess'); mTimeStart('xAccess');
const rc = opRun('xAccess', wasm.cstringToJs(zName)); const rc = opRun('xAccess', wasm.cstrToJs(zName));
wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' ); wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
mTimeEnd(); mTimeEnd();
return 0; return 0;
@ -823,7 +823,7 @@ const installOpfsVfs = function callee(options){
}, },
xDelete: function(pVfs, zName, doSyncDir){ xDelete: function(pVfs, zName, doSyncDir){
mTimeStart('xDelete'); mTimeStart('xDelete');
opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false); opRun('xDelete', wasm.cstrToJs(zName), doSyncDir, false);
/* We're ignoring errors because we cannot yet differentiate /* We're ignoring errors because we cannot yet differentiate
between harmless and non-harmless failures. */ between harmless and non-harmless failures. */
mTimeEnd(); mTimeEnd();
@ -855,7 +855,7 @@ const installOpfsVfs = function callee(options){
C-string here. */ C-string here. */
opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP; opfsFlags |= state.opfsFlags.OPFS_UNLOCK_ASAP;
} }
zName = wasm.cstringToJs(zName); zName = wasm.cstrToJs(zName);
} }
const fh = Object.create(null); const fh = Object.create(null);
fh.fid = pFile; fh.fid = pFile;
@ -1156,11 +1156,8 @@ const installOpfsVfs = function callee(options){
opt.vfs = opfsVfs.$zName; opt.vfs = opfsVfs.$zName;
sqlite3.oo1.DB.dbCtorHelper.call(this, opt); sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
}; };
sqlite3.oo1.OpfsDb =
opfsUtil.OpfsDb /* sqlite3.opfs.OpfsDb => deprecated name -
will be phased out Real Soon */ =
OpfsDb;
OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
sqlite3.oo1.OpfsDb = OpfsDb;
sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
opfsVfs.pointer, opfsVfs.pointer,
function(oo1Db, sqlite3){ function(oo1Db, sqlite3){
@ -1185,7 +1182,7 @@ const installOpfsVfs = function callee(options){
], 0, 0, 0); ], 0, 0, 0);
} }
); );
} }/*extend sqlite3.oo1*/
const sanityCheck = function(){ const sanityCheck = function(){
const scope = wasm.scopedAllocPush(); const scope = wasm.scopedAllocPush();
@ -1231,7 +1228,7 @@ const installOpfsVfs = function callee(options){
const readBuf = wasm.scopedAlloc(16); const readBuf = wasm.scopedAlloc(16);
rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2); rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
wasm.setMemValue(readBuf+6,0); wasm.setMemValue(readBuf+6,0);
let jRead = wasm.cstringToJs(readBuf); let jRead = wasm.cstrToJs(readBuf);
log("xRead() got:",jRead); log("xRead() got:",jRead);
if("sanity"!==jRead) toss("Unexpected xRead() value."); if("sanity"!==jRead) toss("Unexpected xRead() value.");
if(vfsSyncWrappers.xSleep){ if(vfsSyncWrappers.xSleep){

View File

@ -368,7 +368,7 @@ void sqlite3_wasm_test_struct(WasmTestStruct * s){
*/ */
SQLITE_WASM_KEEP SQLITE_WASM_KEEP
const char * sqlite3_wasm_enum_json(void){ const char * sqlite3_wasm_enum_json(void){
static char aBuffer[1024 * 12] = {0} /* where the JSON goes */; static char aBuffer[1024 * 16] = {0} /* where the JSON goes */;
int n = 0, nChildren = 0, nStruct = 0 int n = 0, nChildren = 0, nStruct = 0
/* output counters for figuring out where commas go */; /* output counters for figuring out where commas go */;
char * zPos = &aBuffer[1] /* skip first byte for now to help protect char * zPos = &aBuffer[1] /* skip first byte for now to help protect
@ -410,6 +410,12 @@ const char * sqlite3_wasm_enum_json(void){
DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/; DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/;
} _DefGroup; } _DefGroup;
/* TODO? Authorizer... */
DefGroup(authorizer){
DefInt(SQLITE_DENY);
DefInt(SQLITE_IGNORE);
} _DefGroup;
DefGroup(blobFinalizers) { DefGroup(blobFinalizers) {
/* SQLITE_STATIC/TRANSIENT need to be handled explicitly as /* SQLITE_STATIC/TRANSIENT need to be handled explicitly as
** integers to avoid casting-related warnings. */ ** integers to avoid casting-related warnings. */
@ -681,7 +687,36 @@ const char * sqlite3_wasm_enum_json(void){
DefStr(SQLITE_VERSION); DefStr(SQLITE_VERSION);
DefStr(SQLITE_SOURCE_ID); DefStr(SQLITE_SOURCE_ID);
} _DefGroup; } _DefGroup;
DefGroup(vtab) {
DefInt(SQLITE_INDEX_SCAN_UNIQUE);
DefInt(SQLITE_INDEX_CONSTRAINT_EQ);
DefInt(SQLITE_INDEX_CONSTRAINT_GT);
DefInt(SQLITE_INDEX_CONSTRAINT_LE);
DefInt(SQLITE_INDEX_CONSTRAINT_LT);
DefInt(SQLITE_INDEX_CONSTRAINT_GE);
DefInt(SQLITE_INDEX_CONSTRAINT_MATCH);
DefInt(SQLITE_INDEX_CONSTRAINT_LIKE);
DefInt(SQLITE_INDEX_CONSTRAINT_GLOB);
DefInt(SQLITE_INDEX_CONSTRAINT_REGEXP);
DefInt(SQLITE_INDEX_CONSTRAINT_NE);
DefInt(SQLITE_INDEX_CONSTRAINT_ISNOT);
DefInt(SQLITE_INDEX_CONSTRAINT_ISNOTNULL);
DefInt(SQLITE_INDEX_CONSTRAINT_ISNULL);
DefInt(SQLITE_INDEX_CONSTRAINT_IS);
DefInt(SQLITE_INDEX_CONSTRAINT_LIMIT);
DefInt(SQLITE_INDEX_CONSTRAINT_OFFSET);
DefInt(SQLITE_INDEX_CONSTRAINT_FUNCTION);
DefInt(SQLITE_VTAB_CONSTRAINT_SUPPORT);
DefInt(SQLITE_VTAB_INNOCUOUS);
DefInt(SQLITE_VTAB_DIRECTONLY);
DefInt(SQLITE_ROLLBACK);
//DefInt(SQLITE_IGNORE); // Also used by sqlite3_authorizer() callback
DefInt(SQLITE_FAIL);
//DefInt(SQLITE_ABORT); // Also an error code
DefInt(SQLITE_REPLACE);
} _DefGroup;
#undef DefGroup #undef DefGroup
#undef DefStr #undef DefStr
#undef DefInt #undef DefInt
@ -793,6 +828,128 @@ const char * sqlite3_wasm_enum_json(void){
} _StructBinder; } _StructBinder;
#undef CurrentStruct #undef CurrentStruct
#define CurrentStruct sqlite3_vtab
StructBinder {
M(pModule, "p");
M(nRef, "i");
M(zErrMsg, "p");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_vtab_cursor
StructBinder {
M(pVtab, "p");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_module
StructBinder {
M(iVersion, "i");
M(xCreate, "i(ppippp)");
M(xConnect, "i(ppippp)");
M(xBestIndex, "i(pp)");
M(xDisconnect, "i(p)");
M(xDestroy, "i(p)");
M(xOpen, "i(pp)");
M(xClose, "i(p)");
M(xFilter, "i(pisip)");
M(xNext, "i(p)");
M(xEof, "i(p)");
M(xColumn, "i(ppi)");
M(xRowid, "i(pp)");
M(xUpdate, "i(pipp)");
M(xBegin, "i(p)");
M(xSync, "i(p)");
M(xCommit, "i(p)");
M(xRollback, "i(p)");
M(xFindFunction, "i(pispp)");
M(xRename, "i(ps)");
// ^^^ v1. v2+ follows...
M(xSavepoint, "i(pi)");
M(xRelease, "i(pi)");
M(xRollbackTo, "i(pi)");
// ^^^ v2. v3+ follows...
M(xShadowName, "i(s)");
} _StructBinder;
#undef CurrentStruct
/**
** Workaround: in order to map the various inner structs from
** sqlite3_index_info, we have to uplift those into constructs we
** can access by type name. These structs _must_ match their
** in-sqlite3_index_info counterparts byte for byte.
*/
typedef struct {
int iColumn;
unsigned char op;
unsigned char usable;
int iTermOffset;
} sqlite3_index_constraint;
typedef struct {
int iColumn;
unsigned char desc;
} sqlite3_index_orderby;
typedef struct {
int argvIndex;
unsigned char omit;
} sqlite3_index_constraint_usage;
{ /* Validate that the above struct sizeof()s match
** expectations. We could improve upon this by
** checking the offsetof() for each member. */
const sqlite3_index_info siiCheck;
#define IndexSzCheck(T,M) \
(sizeof(T) == sizeof(*siiCheck.M))
if(!IndexSzCheck(sqlite3_index_constraint,aConstraint)
|| !IndexSzCheck(sqlite3_index_orderby,aOrderBy)
|| !IndexSzCheck(sqlite3_index_constraint_usage,aConstraintUsage)){
assert(!"sizeof mismatch in sqlite3_index_... struct(s)");
return 0;
}
#undef IndexSzCheck
}
#define CurrentStruct sqlite3_index_constraint
StructBinder {
M(iColumn, "i");
M(op, "C");
M(usable, "C");
M(iTermOffset, "i");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_index_orderby
StructBinder {
M(iColumn, "i");
M(desc, "C");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_index_constraint_usage
StructBinder {
M(argvIndex, "i");
M(omit, "C");
} _StructBinder;
#undef CurrentStruct
#define CurrentStruct sqlite3_index_info
StructBinder {
M(nConstraint, "i");
M(aConstraint, "p");
M(nOrderBy, "i");
M(aOrderBy, "p");
M(aConstraintUsage, "p");
M(idxNum, "i");
M(idxStr, "p");
M(needToFreeIdxStr, "i");
M(orderByConsumed, "i");
M(estimatedCost, "d");
M(estimatedRows, "j");
M(idxFlags, "i");
M(colUsed, "j");
} _StructBinder;
#undef CurrentStruct
#if SQLITE_WASM_TESTS #if SQLITE_WASM_TESTS
#define CurrentStruct WasmTestStruct #define CurrentStruct WasmTestStruct
StructBinder { StructBinder {
@ -864,7 +1021,11 @@ sqlite3_vfs * sqlite3_wasm_db_vfs(sqlite3 *pDb, const char *zDbName){
** **
** This function resets the given db pointer's database as described at ** This function resets the given db pointer's database as described at
** **
** https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase ** https://sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigresetdatabase
**
** But beware: virtual tables destroyed that way do not have their
** xDestroy() called, so will leak if they require that function for
** proper cleanup.
** **
** Returns 0 on success, an SQLITE_xxx code on error. Returns ** Returns 0 on success, an SQLITE_xxx code on error. Returns
** SQLITE_MISUSE if pDb is NULL. ** SQLITE_MISUSE if pDb is NULL.
@ -873,6 +1034,7 @@ SQLITE_WASM_KEEP
int sqlite3_wasm_db_reset(sqlite3 *pDb){ int sqlite3_wasm_db_reset(sqlite3 *pDb){
int rc = SQLITE_MISUSE; int rc = SQLITE_MISUSE;
if( pDb ){ if( pDb ){
sqlite3_table_column_metadata(pDb, "main", 0, 0, 0, 0, 0, 0, 0);
rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); rc = sqlite3_db_config(pDb, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0);
if( 0==rc ){ if( 0==rc ){
rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0); rc = sqlite3_exec(pDb, "VACUUM", 0, 0, 0);
@ -1109,6 +1271,31 @@ sqlite3_kvvfs_methods * sqlite3_wasm_kvvfs_methods(void){
return &sqlite3KvvfsMethods; return &sqlite3KvvfsMethods;
} }
/*
** This function is NOT part of the sqlite3 public API. It is strictly
** for use by the sqlite project's own JS/WASM bindings.
**
** This is a proxy for the variadic sqlite3_vtab_config() which passes
** its argument on, or not, to sqlite3_vtab_config(), depending on the
** value of its 2nd argument. Returns the result of
** sqlite3_vtab_config(), or SQLITE_MISUSE if the 2nd arg is not a
** valid value.
*/
SQLITE_WASM_KEEP
int sqlite3_wasm_vtab_config(sqlite3 *pDb, int op, int arg){
switch(op){
case SQLITE_VTAB_DIRECTONLY:
case SQLITE_VTAB_INNOCUOUS:
return sqlite3_vtab_config(pDb, op);
case SQLITE_VTAB_CONSTRAINT_SUPPORT:
return sqlite3_vtab_config(pDb, op, arg);
default:
return SQLITE_MISUSE;
}
}
#if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS) #if defined(__EMSCRIPTEN__) && defined(SQLITE_ENABLE_WASMFS)
#include <emscripten/wasmfs.h> #include <emscripten/wasmfs.h>

View File

@ -1,4 +1,4 @@
/* emcscript-related styling, used during the module load/intialization processes... */ /* emscripten-related styling, used during the module load/intialization processes... */
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; } .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; } div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; } div.emscripten_border { border: 1px solid black; }

View File

@ -61,3 +61,9 @@ span.labeled-input {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
label[for] { cursor: pointer } label[for] { cursor: pointer }
h1 {
border-radius: 0.25em;
padding: 0.15em 0.25em;
}
h1:first-of-type {margin: 0 0 0.5em 0;}

View File

@ -725,11 +725,12 @@ self.WhWasmUtilInstaller = function(target){
Expects ptr to be a pointer into the WASM heap memory which Expects ptr to be a pointer into the WASM heap memory which
refers to a NUL-terminated C-style string encoded as UTF-8. refers to a NUL-terminated C-style string encoded as UTF-8.
Returns the length, in bytes, of the string, as for `strlen(3)`. Returns the length, in bytes, of the string, as for `strlen(3)`.
As a special case, if !ptr then it it returns `null`. Throws if As a special case, if !ptr or if it's not a pointer then it
ptr is out of range for target.heap8u(). returns `null`. Throws if ptr is out of range for
target.heap8u().
*/ */
target.cstrlen = function(ptr){ target.cstrlen = function(ptr){
if(!ptr) return null; if(!ptr || !target.isPtr(ptr)) return null;
const h = heapWrappers().HEAP8U; const h = heapWrappers().HEAP8U;
let pos = ptr; let pos = ptr;
for( ; h[pos] !== 0; ++pos ){} for( ; h[pos] !== 0; ++pos ){}
@ -753,9 +754,9 @@ self.WhWasmUtilInstaller = function(target){
refers to a NUL-terminated C-style string encoded as UTF-8. This refers to a NUL-terminated C-style string encoded as UTF-8. This
function counts its byte length using cstrlen() then returns a function counts its byte length using cstrlen() then returns a
JS-format string representing its contents. As a special case, if JS-format string representing its contents. As a special case, if
ptr is falsy, `null` is returned. ptr is falsy or not a pointer, `null` is returned.
*/ */
target.cstringToJs = function(ptr){ target.cstrToJs = function(ptr){
const n = target.cstrlen(ptr); const n = target.cstrlen(ptr);
return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : ""); return n ? __utf8Decode(heapWrappers().HEAP8U, ptr, ptr+n) : (null===n ? n : "");
}; };
@ -942,10 +943,19 @@ self.WhWasmUtilInstaller = function(target){
const __allocCStr = function(jstr, returnWithLength, allocator, funcName){ const __allocCStr = function(jstr, returnWithLength, allocator, funcName){
__affirmAlloc(target, funcName); __affirmAlloc(target, funcName);
if('string'!==typeof jstr) return null; if('string'!==typeof jstr) return null;
const n = target.jstrlen(jstr), if(0){/* older impl, possibly more widely compatible? */
ptr = allocator(n+1); const n = target.jstrlen(jstr),
target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true); ptr = allocator(n+1);
return returnWithLength ? [ptr, n] : ptr; target.jstrcpy(jstr, target.heap8u(), ptr, n+1, true);
return returnWithLength ? [ptr, n] : ptr;
}else{/* newer, (probably) faster and (certainly) simpler impl */
const u = cache.utf8Encoder.encode(jstr),
ptr = allocator(u.length+1),
heap = heapWrappers().HEAP8U;
heap.set(u, ptr);
heap[ptr + u.length] = 0;
return returnWithLength ? [ptr, u.length] : ptr;
}
}; };
/** /**
@ -1081,10 +1091,9 @@ self.WhWasmUtilInstaller = function(target){
// impl for allocMainArgv() and scopedAllocMainArgv(). // impl for allocMainArgv() and scopedAllocMainArgv().
const __allocMainArgv = function(isScoped, list){ const __allocMainArgv = function(isScoped, list){
if(!list.length) toss("Cannot allocate empty array.");
const pList = target[ const pList = target[
isScoped ? 'scopedAlloc' : 'alloc' isScoped ? 'scopedAlloc' : 'alloc'
](list.length * target.ptrSizeof); ]((list.length + 1) * target.ptrSizeof);
let i = 0; let i = 0;
list.forEach((e)=>{ list.forEach((e)=>{
target.setPtrValue(pList + (target.ptrSizeof * i++), target.setPtrValue(pList + (target.ptrSizeof * i++),
@ -1092,29 +1101,57 @@ self.WhWasmUtilInstaller = function(target){
isScoped ? 'scopedAllocCString' : 'allocCString' isScoped ? 'scopedAllocCString' : 'allocCString'
](""+e)); ](""+e));
}); });
target.setPtrValue(pList + (target.ptrSizeof * i), 0);
return pList; return pList;
}; };
/** /**
Creates an array, using scopedAlloc(), suitable for passing to a Creates an array, using scopedAlloc(), suitable for passing to a
C-level main() routine. The input is a collection with a length C-level main() routine. The input is a collection with a length
property and a forEach() method. A block of memory list.length property and a forEach() method. A block of memory
entries long is allocated and each pointer-sized block of that (list.length+1) entries long is allocated and each pointer-sized
memory is populated with a scopedAllocCString() conversion of the block of that memory is populated with a scopedAllocCString()
(""+value) of each element. Returns a pointer to the start of the conversion of the (""+value) of each element, with the exception
list, suitable for passing as the 2nd argument to a C-style that the final entry is a NULL pointer. Returns a pointer to the
main() function. start of the list, suitable for passing as the 2nd argument to a
C-style main() function.
Throws if list.length is falsy or scopedAllocPush() is not active. Throws if scopedAllocPush() is not active.
Design note: the returned array is allocated with an extra NULL
pointer entry to accommodate certain APIs, but client code which
does not need that functionality should treat the returned array
as list.length entries long.
*/ */
target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list); target.scopedAllocMainArgv = (list)=>__allocMainArgv(true, list);
/** /**
Identical to scopedAllocMainArgv() but uses alloc() instead of Identical to scopedAllocMainArgv() but uses alloc() instead of
scopedAllocMainArgv scopedAlloc().
*/ */
target.allocMainArgv = (list)=>__allocMainArgv(false, list); target.allocMainArgv = (list)=>__allocMainArgv(false, list);
/**
Expects to be given a C-style string array and its length. It
returns a JS array of strings and/or nulls: any entry in the
pArgv array which is NULL results in a null entry in the result
array. If argc is 0 then an empty array is returned.
Results are undefined if any entry in the first argc entries of
pArgv are neither 0 (NULL) nor legal UTF-format C strings.
To be clear, the expected C-style arguments to be passed to this
function are `(int, char **)` (optionally const-qualified).
*/
target.cArgvToJs = (argc, pArgv)=>{
const list = [];
for(let i = 0; i < argc; ++i){
const arg = target.getPtrValue(pArgv + (target.ptrSizeof * i));
list.push( arg ? target.cstrToJs(arg) : null );
}
return list;
};
/** /**
Wraps function call func() in a scopedAllocPush() and Wraps function call func() in a scopedAllocPush() and
scopedAllocPop() block, such that all calls to scopedAlloc() and scopedAllocPop() block, such that all calls to scopedAlloc() and
@ -1278,14 +1315,14 @@ self.WhWasmUtilInstaller = function(target){
if('string'===typeof v) return target.scopedAllocCString(v); if('string'===typeof v) return target.scopedAllocCString(v);
return v ? xcv.arg[ptrIR](v) : null; return v ? xcv.arg[ptrIR](v) : null;
}; };
xcv.result.string = xcv.result.utf8 = (i)=>target.cstringToJs(i); xcv.result.string = xcv.result.utf8 = (i)=>target.cstrToJs(i);
xcv.result['string:dealloc'] = xcv.result['utf8:dealloc'] = (i)=>{ xcv.result['string:dealloc'] = xcv.result['utf8:dealloc'] = (i)=>{
try { return i ? target.cstringToJs(i) : null } try { return i ? target.cstrToJs(i) : null }
finally{ target.dealloc(i) } finally{ target.dealloc(i) }
}; };
xcv.result.json = (i)=>JSON.parse(target.cstringToJs(i)); xcv.result.json = (i)=>JSON.parse(target.cstrToJs(i));
xcv.result['json:dealloc'] = (i)=>{ xcv.result['json:dealloc'] = (i)=>{
try{ return i ? JSON.parse(target.cstringToJs(i)) : null } try{ return i ? JSON.parse(target.cstrToJs(i)) : null }
finally{ target.dealloc(i) } finally{ target.dealloc(i) }
} }
xcv.result['void'] = (v)=>undefined; xcv.result['void'] = (v)=>undefined;
@ -1423,7 +1460,7 @@ self.WhWasmUtilInstaller = function(target){
```js ```js
target.xWrap.resultAdapter('string:my_free',(i)=>{ target.xWrap.resultAdapter('string:my_free',(i)=>{
try { return i ? target.cstringToJs(i) : null } try { return i ? target.cstrToJs(i) : null }
finally{ target.exports.my_free(i) } finally{ target.exports.my_free(i) }
}; };
``` ```

View File

@ -10,9 +10,12 @@
*********************************************************************** ***********************************************************************
The Jaccwabyt API is documented in detail in an external file. The Jaccwabyt API is documented in detail in an external file,
_possibly_ called jaccwabyt.md in the same directory as this file.
Project home: https://fossil.wanderinghorse.net/r/jaccwabyt Project homes:
- https://fossil.wanderinghorse.net/r/jaccwabyt
- https://sqlite.org/src/dir/ext/wasm/jaccwabyt
*/ */
'use strict'; 'use strict';
@ -61,7 +64,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
BigInt = self['BigInt'], BigInt = self['BigInt'],
BigInt64Array = self['BigInt64Array'], BigInt64Array = self['BigInt64Array'],
/* Undocumented (on purpose) config options: */ /* Undocumented (on purpose) config options: */
functionTable = config.functionTable/*EXPERIMENTAL, undocumented*/,
ptrSizeof = config.ptrSizeof || 4, ptrSizeof = config.ptrSizeof || 4,
ptrIR = config.ptrIR || 'i32' ptrIR = config.ptrIR || 'i32'
; ;
@ -121,6 +123,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
at SIG s[0]. Throws for an unknown SIG. */ at SIG s[0]. Throws for an unknown SIG. */
const sigIR = function(s){ const sigIR = function(s){
switch(sigLetter(s)){ switch(sigLetter(s)){
case 'c': case 'C': return 'i8';
case 'i': return 'i32'; case 'i': return 'i32';
case 'p': case 'P': case 's': return ptrIR; case 'p': case 'P': case 's': return ptrIR;
case 'j': return 'i64'; case 'j': return 'i64';
@ -129,33 +132,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
} }
toss("Unhandled signature IR:",s); toss("Unhandled signature IR:",s);
}; };
/** Returns the sizeof value for the given SIG. Throws for an
unknown SIG. */
const sigSizeof = function(s){
switch(sigLetter(s)){
case 'i': return 4;
case 'p': case 'P': case 's': return ptrSizeof;
case 'j': return 8;
case 'f': return 4 /* C-side floats, not JS-side */;
case 'd': return 8;
}
toss("Unhandled signature sizeof:",s);
};
const affirmBigIntArray = BigInt64Array const affirmBigIntArray = BigInt64Array
? ()=>true : ()=>toss('BigInt64Array is not available.'); ? ()=>true : ()=>toss('BigInt64Array is not available.');
/** Returns the (signed) TypedArray associated with the type
described by the given SIG. Throws for an unknown SIG. */
/**********
const sigTypedArray = function(s){
switch(sigIR(s)) {
case 'i32': return Int32Array;
case 'i64': return affirmBigIntArray() && BigInt64Array;
case 'float': return Float32Array;
case 'double': return Float64Array;
}
toss("Unhandled signature TypedArray:",s);
};
**************/
/** Returns the name of a DataView getter method corresponding /** Returns the name of a DataView getter method corresponding
to the given SIG. */ to the given SIG. */
const sigDVGetter = function(s){ const sigDVGetter = function(s){
@ -168,6 +147,8 @@ self.Jaccwabyt = function StructBinderFactory(config){
break; break;
} }
case 'i': return 'getInt32'; case 'i': return 'getInt32';
case 'c': return 'getInt8';
case 'C': return 'getUint8';
case 'j': return affirmBigIntArray() && 'getBigInt64'; case 'j': return affirmBigIntArray() && 'getBigInt64';
case 'f': return 'getFloat32'; case 'f': return 'getFloat32';
case 'd': return 'getFloat64'; case 'd': return 'getFloat64';
@ -186,6 +167,8 @@ self.Jaccwabyt = function StructBinderFactory(config){
break; break;
} }
case 'i': return 'setInt32'; case 'i': return 'setInt32';
case 'c': return 'setInt8';
case 'C': return 'setUint8';
case 'j': return affirmBigIntArray() && 'setBigInt64'; case 'j': return affirmBigIntArray() && 'setBigInt64';
case 'f': return 'setFloat32'; case 'f': return 'setFloat32';
case 'd': return 'setFloat64'; case 'd': return 'setFloat64';
@ -199,7 +182,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
*/ */
const sigDVSetWrapper = function(s){ const sigDVSetWrapper = function(s){
switch(sigLetter(s)) { switch(sigLetter(s)) {
case 'i': case 'f': case 'd': return Number; case 'i': case 'f': case 'c': case 'C': case 'd': return Number;
case 'j': return affirmBigIntArray() && BigInt; case 'j': return affirmBigIntArray() && BigInt;
case 'p': case 'P': case 's': case 'p': case 'P': case 's':
switch(ptrSizeof){ switch(ptrSizeof){
@ -211,36 +194,14 @@ self.Jaccwabyt = function StructBinderFactory(config){
toss("Unhandled DataView set wrapper for signature:",s); toss("Unhandled DataView set wrapper for signature:",s);
}; };
/** Returns the given struct and member name in a form suitable for
debugging and error output. */
const sPropName = (s,k)=>s+'::'+k; const sPropName = (s,k)=>s+'::'+k;
const __propThrowOnSet = function(structName,propName){ const __propThrowOnSet = function(structName,propName){
return ()=>toss(sPropName(structName,propName),"is read-only."); return ()=>toss(sPropName(structName,propName),"is read-only.");
}; };
/**
When C code passes a pointer of a bound struct to back into
a JS function via a function pointer struct member, it
arrives in JS as a number (pointer).
StructType.instanceForPointer(ptr) can be used to get the
instance associated with that pointer, and __ptrBacklinks
holds that mapping. WeakMap keys must be objects, so we
cannot use a weak map to map pointers to instances. We use
the StructType constructor as the WeakMap key, mapped to a
plain, prototype-less Object which maps the pointers to
struct instances. That arrangement gives us a
per-StructType type-safe way to resolve pointers.
*/
const __ptrBacklinks = new WeakMap();
/**
Similar to __ptrBacklinks but is scoped at the StructBinder
level and holds pointer-to-object mappings for all struct
instances created by any struct from any StructFactory
which this specific StructBinder has created. The intention
of this is to help implement more transparent handling of
pointer-type property resolution.
*/
const __ptrBacklinksGlobal = Object.create(null);
/** /**
In order to completely hide StructBinder-bound struct In order to completely hide StructBinder-bound struct
pointers from JS code, we store them in a scope-local pointers from JS code, we store them in a scope-local
@ -261,17 +222,13 @@ self.Jaccwabyt = function StructBinderFactory(config){
const __freeStruct = function(ctor, obj, m){ const __freeStruct = function(ctor, obj, m){
if(!m) m = __instancePointerMap.get(obj); if(!m) m = __instancePointerMap.get(obj);
if(m) { if(m) {
if(obj.ondispose instanceof Function){ __instancePointerMap.delete(obj);
try{obj.ondispose()} if(Array.isArray(obj.ondispose)){
catch(e){ let x;
/*do not rethrow: destructors must not throw*/ while((x = obj.ondispose.shift())){
console.warn("ondispose() for",ctor.structName,'@',
m,'threw. NOT propagating it.',e);
}
}else if(Array.isArray(obj.ondispose)){
obj.ondispose.forEach(function(x){
try{ try{
if(x instanceof Function) x.call(obj); if(x instanceof Function) x.call(obj);
else if(x instanceof StructType) x.dispose();
else if('number' === typeof x) dealloc(x); else if('number' === typeof x) dealloc(x);
// else ignore. Strings are permitted to annotate entries // else ignore. Strings are permitted to annotate entries
// to assist in debugging. // to assist in debugging.
@ -279,12 +236,16 @@ self.Jaccwabyt = function StructBinderFactory(config){
console.warn("ondispose() for",ctor.structName,'@', console.warn("ondispose() for",ctor.structName,'@',
m,'threw. NOT propagating it.',e); m,'threw. NOT propagating it.',e);
} }
}); }
}else if(obj.ondispose instanceof Function){
try{obj.ondispose()}
catch(e){
/*do not rethrow: destructors must not throw*/
console.warn("ondispose() for",ctor.structName,'@',
m,'threw. NOT propagating it.',e);
}
} }
delete obj.ondispose; delete obj.ondispose;
delete __ptrBacklinks.get(ctor)[m];
delete __ptrBacklinksGlobal[m];
__instancePointerMap.delete(obj);
if(ctor.debugFlags.__flags.dealloc){ if(ctor.debugFlags.__flags.dealloc){
log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""), log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""),
ctor.structName,"instance:", ctor.structName,"instance:",
@ -300,7 +261,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
iterable: false, value: v}}; iterable: false, value: v}};
/** Allocates obj's memory buffer based on the size defined in /** Allocates obj's memory buffer based on the size defined in
DEF.sizeof. */ ctor.structInfo.sizeof. */
const __allocStruct = function(ctor, obj, m){ const __allocStruct = function(ctor, obj, m){
let fill = !m; let fill = !m;
if(m) Object.defineProperty(obj, xPtrPropName, rop(m)); if(m) Object.defineProperty(obj, xPtrPropName, rop(m));
@ -316,8 +277,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
} }
if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof); if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof);
__instancePointerMap.set(obj, m); __instancePointerMap.set(obj, m);
__ptrBacklinks.get(ctor)[m] = obj;
__ptrBacklinksGlobal[m] = obj;
}catch(e){ }catch(e){
__freeStruct(ctor, obj, m); __freeStruct(ctor, obj, m);
throw e; throw e;
@ -339,7 +298,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
if tossIfNotFound is true, else returns undefined if not if tossIfNotFound is true, else returns undefined if not
found. The given name may be either the name of the found. The given name may be either the name of the
structInfo.members key (faster) or the key as modified by the structInfo.members key (faster) or the key as modified by the
memberPrefix/memberSuffix settings. memberPrefix and memberSuffix settings.
*/ */
const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){ const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){
let m = structInfo.members[memberName]; let m = structInfo.members[memberName];
@ -361,21 +320,11 @@ self.Jaccwabyt = function StructBinderFactory(config){
framework's native format or in Emscripten format. framework's native format or in Emscripten format.
*/ */
const __memberSignature = function f(obj,memberName,emscriptenFormat=false){ const __memberSignature = function f(obj,memberName,emscriptenFormat=false){
if(!f._) f._ = (x)=>x.replace(/[^vipPsjrd]/g,"").replace(/[pPs]/g,'i'); if(!f._) f._ = (x)=>x.replace(/[^vipPsjrdcC]/g,"").replace(/[pPscC]/g,'i');
const m = __lookupMember(obj.structInfo, memberName, true); const m = __lookupMember(obj.structInfo, memberName, true);
return emscriptenFormat ? f._(m.signature) : m.signature; return emscriptenFormat ? f._(m.signature) : m.signature;
}; };
/**
Returns the instanceForPointer() impl for the given
StructType constructor.
*/
const __instanceBacklinkFactory = function(ctor){
const b = Object.create(null);
__ptrBacklinks.set(ctor, b);
return (ptr)=>b[ptr];
};
const __ptrPropDescriptor = { const __ptrPropDescriptor = {
configurable: false, enumerable: false, configurable: false, enumerable: false,
get: function(){return __instancePointerMap.get(this)}, get: function(){return __instancePointerMap.get(this)},
@ -388,7 +337,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
/** Impl of X.memberKeys() for StructType and struct ctors. */ /** Impl of X.memberKeys() for StructType and struct ctors. */
const __structMemberKeys = rop(function(){ const __structMemberKeys = rop(function(){
const a = []; const a = [];
Object.keys(this.structInfo.members).forEach((k)=>a.push(this.memberKey(k))); for(const k of Object.keys(this.structInfo.members)){
a.push(this.memberKey(k));
}
return a; return a;
}); });
@ -454,15 +405,15 @@ self.Jaccwabyt = function StructBinderFactory(config){
Adds value v to obj.ondispose, creating ondispose, Adds value v to obj.ondispose, creating ondispose,
or converting it to an array, if needed. or converting it to an array, if needed.
*/ */
const __addOnDispose = function(obj, v){ const __addOnDispose = function(obj, ...v){
if(obj.ondispose){ if(obj.ondispose){
if(obj.ondispose instanceof Function){ if(!Array.isArray(obj.ondispose)){
obj.ondispose = [obj.ondispose]; obj.ondispose = [obj.ondispose];
}/*else assume it's an array*/ }
}else{ }else{
obj.ondispose = []; obj.ondispose = [];
} }
obj.ondispose.push(v); obj.ondispose.push(...v);
}; };
/** /**
@ -477,8 +428,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
const mem = alloc(u.length+1); const mem = alloc(u.length+1);
if(!mem) toss("Allocation error while duplicating string:",str); if(!mem) toss("Allocation error while duplicating string:",str);
const h = heap(); const h = heap();
let i = 0; //let i = 0;
for( ; i < u.length; ++i ) h[mem + i] = u[i]; //for( ; i < u.length; ++i ) h[mem + i] = u[i];
h.set(u, mem);
h[mem + u.length] = 0; h[mem + u.length] = 0;
//log("allocCString @",mem," =",u); //log("allocCString @",mem," =",u);
return mem; return mem;
@ -490,6 +442,10 @@ self.Jaccwabyt = function StructBinderFactory(config){
to free any prior memory, if appropriate. The newly-allocated to free any prior memory, if appropriate. The newly-allocated
string is added to obj.ondispose so will be freed when the object string is added to obj.ondispose so will be freed when the object
is disposed. is disposed.
The given name may be either the name of the structInfo.members
key (faster) or the key as modified by the memberPrefix and
memberSuffix settings.
*/ */
const __setMemberCString = function(obj, memberName, str){ const __setMemberCString = function(obj, memberName, str){
const m = __lookupMember(obj.structInfo, memberName, true); const m = __lookupMember(obj.structInfo, memberName, true);
@ -544,13 +500,19 @@ self.Jaccwabyt = function StructBinderFactory(config){
return __setMemberCString(this, memberName, str); return __setMemberCString(this, memberName, str);
}) })
}); });
// Function-type non-Property inherited members
Object.assign(StructType.prototype,{
addOnDispose: function(...v){
__addOnDispose(this,...v);
return this;
}
});
/** /**
"Static" properties for StructType. "Static" properties for StructType.
*/ */
Object.defineProperties(StructType, { Object.defineProperties(StructType, {
allocCString: rop(__allocCString), allocCString: rop(__allocCString),
instanceForPointer: rop((ptr)=>__ptrBacklinksGlobal[ptr]),
isA: rop((v)=>v instanceof StructType), isA: rop((v)=>v instanceof StructType),
hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]), hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]),
memberKey: __memberKeyProp memberKey: __memberKeyProp
@ -570,7 +532,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
/*cache all available getters/setters/set-wrappers for /*cache all available getters/setters/set-wrappers for
direct reuse in each accessor function. */ direct reuse in each accessor function. */
f._ = {getters: {}, setters: {}, sw:{}}; f._ = {getters: {}, setters: {}, sw:{}};
const a = ['i','p','P','s','f','d','v()']; const a = ['i','c','C','p','P','s','f','d','v()'];
if(bigIntEnabled) a.push('j'); if(bigIntEnabled) a.push('j');
a.forEach(function(v){ a.forEach(function(v){
//const ir = sigIR(v); //const ir = sigIR(v);
@ -579,8 +541,8 @@ self.Jaccwabyt = function StructBinderFactory(config){
f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values
for conversion */; for conversion */;
}); });
const rxSig1 = /^[ipPsjfd]$/, const rxSig1 = /^[ipPsjfdcC]$/,
rxSig2 = /^[vipPsjfd]\([ipPsjfd]*\)$/; rxSig2 = /^[vipPsjfdcC]\([ipPsjfdcC]*\)$/;
f.sigCheck = function(obj, name, key,sig){ f.sigCheck = function(obj, name, key,sig){
if(Object.prototype.hasOwnProperty.call(obj, key)){ if(Object.prototype.hasOwnProperty.call(obj, key)){
toss(obj.structName,'already has a property named',key+'.'); toss(obj.structName,'already has a property named',key+'.');
@ -594,7 +556,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
f.sigCheck(ctor.prototype, name, key, descr.signature); f.sigCheck(ctor.prototype, name, key, descr.signature);
descr.key = key; descr.key = key;
descr.name = name; descr.name = name;
const sizeOf = sigSizeof(descr.signature);
const sigGlyph = sigLetter(descr.signature); const sigGlyph = sigLetter(descr.signature);
const xPropName = sPropName(ctor.prototype.structName,key); const xPropName = sPropName(ctor.prototype.structName,key);
const dbg = ctor.prototype.debugFlags.__flags; const dbg = ctor.prototype.debugFlags.__flags;
@ -610,16 +571,12 @@ self.Jaccwabyt = function StructBinderFactory(config){
prop.get = function(){ prop.get = function(){
if(dbg.getter){ if(dbg.getter){
log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph), log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph),
xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf); xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof);
} }
let rc = ( let rc = (
new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
)[f._.getters[sigGlyph]](0, isLittleEndian); )[f._.getters[sigGlyph]](0, isLittleEndian);
if(dbg.getter) log("debug.getter:",xPropName,"result =",rc); if(dbg.getter) log("debug.getter:",xPropName,"result =",rc);
if(rc && isAutoPtrSig(descr.signature)){
rc = StructType.instanceForPointer(rc) || rc;
if(dbg.getter) log("debug.getter:",xPropName,"resolved =",rc);
}
return rc; return rc;
}; };
if(descr.readOnly){ if(descr.readOnly){
@ -628,7 +585,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
prop.set = function(v){ prop.set = function(v){
if(dbg.setter){ if(dbg.setter){
log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph), log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph),
xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf, v); xPropName,'@', this.pointer,'+',descr.offset,'sz',descr.sizeof, v);
} }
if(!this.pointer){ if(!this.pointer){
toss("Cannot set struct property on disposed instance."); toss("Cannot set struct property on disposed instance.");
@ -644,7 +601,7 @@ self.Jaccwabyt = function StructBinderFactory(config){
toss("Invalid value for pointer-type",xPropName+'.'); toss("Invalid value for pointer-type",xPropName+'.');
} }
( (
new DataView(heap().buffer, this.pointer + descr.offset, sizeOf) new DataView(heap().buffer, this.pointer + descr.offset, descr.sizeof)
)[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian); )[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian);
}; };
} }
@ -665,13 +622,25 @@ self.Jaccwabyt = function StructBinderFactory(config){
if(!structName) toss("Struct name is required."); if(!structName) toss("Struct name is required.");
let lastMember = false; let lastMember = false;
Object.keys(structInfo.members).forEach((k)=>{ Object.keys(structInfo.members).forEach((k)=>{
// Sanity checks of sizeof/offset info...
const m = structInfo.members[k]; const m = structInfo.members[k];
if(!m.sizeof) toss(structName,"member",k,"is missing sizeof."); if(!m.sizeof) toss(structName,"member",k,"is missing sizeof.");
else if(0!==(m.sizeof%4)){ else if(m.sizeof===1){
toss(structName,"member",k,"sizeof is not aligned."); (m.signature === 'c' || m.signature === 'C') ||
} toss("Unexpected sizeof==1 member",
else if(0!==(m.offset%4)){ sPropName(structInfo.name,k),
toss(structName,"member",k,"offset is not aligned."); "with signature",m.signature);
}else{
// sizes and offsets of size-1 members may be odd values, but
// others may not.
if(0!==(m.sizeof%4)){
console.warn("Invalid struct member description =",m,"from",structInfo);
toss(structName,"member",k,"sizeof is not aligned. sizeof="+m.sizeof);
}
if(0!==(m.offset%4)){
console.warn("Invalid struct member description =",m,"from",structInfo);
toss(structName,"member",k,"offset is not aligned. offset="+m.offset);
}
} }
if(!lastMember || lastMember.offset < m.offset) lastMember = m; if(!lastMember || lastMember.offset < m.offset) lastMember = m;
}); });
@ -697,27 +666,9 @@ self.Jaccwabyt = function StructBinderFactory(config){
}; };
Object.defineProperties(StructCtor,{ Object.defineProperties(StructCtor,{
debugFlags: debugFlags, debugFlags: debugFlags,
disposeAll: rop(function(){
const map = __ptrBacklinks.get(StructCtor);
Object.keys(map).forEach(function(ptr){
const b = map[ptr];
if(b) __freeStruct(StructCtor, b, ptr);
});
__ptrBacklinks.set(StructCtor, Object.create(null));
return StructCtor;
}),
instanceForPointer: rop(__instanceBacklinkFactory(StructCtor)),
isA: rop((v)=>v instanceof StructCtor), isA: rop((v)=>v instanceof StructCtor),
memberKey: __memberKeyProp, memberKey: __memberKeyProp,
memberKeys: __structMemberKeys, memberKeys: __structMemberKeys,
resolveToInstance: rop(function(v, throwIfNot=false){
if(!(v instanceof StructCtor)){
v = Number.isSafeInteger(v)
? StructCtor.instanceForPointer(v) : undefined;
}
if(!v && throwIfNot) toss("Value is-not-a",StructCtor.structName);
return v;
}),
methodInfoForKey: rop(function(mKey){ methodInfoForKey: rop(function(mKey){
}), }),
structInfo: rop(structInfo), structInfo: rop(structInfo),
@ -735,7 +686,6 @@ self.Jaccwabyt = function StructBinderFactory(config){
); );
return StructCtor; return StructCtor;
}; };
StructBinder.instanceForPointer = StructType.instanceForPointer;
StructBinder.StructType = StructType; StructBinder.StructType = StructType;
StructBinder.config = config; StructBinder.config = config;
StructBinder.allocCString = __allocCString; StructBinder.allocCString = __allocCString;

View File

@ -4,7 +4,6 @@ Jaccwabyt 🐇
**Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte **Jaccwabyt**: _JavaScript ⇄ C Struct Communication via WASM Byte
Arrays_ Arrays_
Welcome to Jaccwabyt, a JavaScript API which creates bindings for Welcome to Jaccwabyt, a JavaScript API which creates bindings for
WASM-compiled C structs, defining them in such a way that changes to WASM-compiled C structs, defining them in such a way that changes to
their state in JS are visible in C/WASM, and vice versa, permitting their state in JS are visible in C/WASM, and vice versa, permitting
@ -27,8 +26,28 @@ are based solely on feature compatibility tables provided at
**Formalities:** **Formalities:**
- Author: [Stephan Beal][sgb] - Author: [Stephan Beal][sgb]
- License: Public Domain - Project Homes:
- Project Home: <https://fossil.wanderinghorse.net/r/jaccwabyt> - <https://fossil.wanderinghorse.net/r/jaccwabyt>\
Is the primary home but...
- <https://sqlite.org/src/dir/ext/wasm/jaccwabyt>\
... most development happens here.
The license for both this documentation and the software it documents
is the same as [sqlite3][], the project from which this spinoff
project was spawned:
-----
> 2022-06-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.
-----
<a name='overview'></a> <a name='overview'></a>
Table of Contents Table of Contents
@ -205,7 +224,6 @@ simply look like:
The StructBinder factory function returns a function which can then be The StructBinder factory function returns a function which can then be
used to create bindings for our structs. used to create bindings for our structs.
<a name='step-2'></a> <a name='step-2'></a>
Step 2: Create a Struct Description Step 2: Create a Struct Description
------------------------------------------------------------ ------------------------------------------------------------
@ -281,21 +299,29 @@ supported letters are:
signature entry. signature entry.
- **`f`** = `float` (4 bytes) - **`f`** = `float` (4 bytes)
- **`d`** = `double` (8 bytes) - **`d`** = `double` (8 bytes)
- **`p`** = `int32` (but see below!) - **`c`** = `int8` (1 byte) char - see notes below!
- **`C`** = `uint8` (1 byte) unsigned char - see notes below!
- **`p`** = `int32` (see notes below!)
- **`P`** = Like `p` but with extra handling. Described below. - **`P`** = Like `p` but with extra handling. Described below.
- **`s`** = like `int32` but is a _hint_ that it's a pointer to a string - **`s`** = like `int32` but is a _hint_ that it's a pointer to a
so that _some_ (very limited) contexts may treat it as such, noting string so that _some_ (very limited) contexts may treat it as such,
such algorithms must, for lack of information to the contrary, noting that such algorithms must, for lack of information to the
assume both that the encoding is UTF-8 and that the pointer's member contrary, assume both that the encoding is UTF-8 and that the
is NUL-terminated. If that is _not_ the case for a given string pointer's member is NUL-terminated. If that is _not_ the case for a
member, do not use `s`: use `i` or `p` instead and do any string given string member, do not use `s`: use `i` or `p` instead and do
handling yourself. any string handling yourself.
Noting that: Noting that:
- All of these types are numeric. Attempting to set any struct-bound - **All of these types are numeric**. Attempting to set any
property to a non-numeric value will trigger an exception except in struct-bound property to a non-numeric value will trigger an
cases explicitly noted otherwise. exception except in cases explicitly noted otherwise.
- **"Char" types**: WASM does not define an `int8` type, nor does it
distinguish between signed and unsigned. This API treats `c` as
`int8` and `C` as `uint8` for purposes of getting and setting values
when using the `DataView` class. It is _not_ recommended that client
code use these types in new WASM-capable code, but they were added
for the sake of binding some immutable legacy code to WASM.
> Sidebar: Emscripten's public docs do not mention `p`, but their > Sidebar: Emscripten's public docs do not mention `p`, but their
generated code includes `p` as an alias for `i`, presumably to mean generated code includes `p` as an alias for `i`, presumably to mean
@ -317,12 +343,12 @@ Signatures in the form `x(...)` denote function-pointer members and
form `x()`. For function-type signatures, the strings are formulated form `x()`. For function-type signatures, the strings are formulated
such that they can be passed to Emscripten's `addFunction()` after such that they can be passed to Emscripten's `addFunction()` after
stripping out the `(` and `)` characters. For good measure, to match stripping out the `(` and `)` characters. For good measure, to match
the public Emscripten docs, `p` should also be replaced with `i`. In the public Emscripten docs, `p`, `c`, and `C`, should also be replaced
JavaScript that might look like: with `i`. In JavaScript that might look like:
> >
``` ```
signature.replace(/[^vipPsjfd]/g,'').replace(/[pPs]/g,'i'); signature.replace(/[^vipPsjfdcC]/g,'').replace(/[pPscC]/g,'i');
``` ```
<a name='step-2-pvsp'></a> <a name='step-2-pvsp'></a>
@ -337,12 +363,6 @@ special use of unsigned numbers). A capital `P` changes the semantics
of plain member pointers (but not, as of this writing, function of plain member pointers (but not, as of this writing, function
pointer members) as follows: pointer members) as follows:
- When a `P`-type member is **fetched** via `myStruct.x` and its value is
a non-0 integer, [`StructBinder.instanceForPointer()`][StructBinder]
is used to try to map that pointer to a struct instance. If a match
is found, the "get" operation returns that instance instead of the
integer. If no match is found, it behaves exactly as for `p`, returning
the integer value.
- When a `P`-type member is **set** via `myStruct.x=y`, if - When a `P`-type member is **set** via `myStruct.x=y`, if
[`(y instanceof StructType)`][StructType] then the value of `y.pointer` is [`(y instanceof StructType)`][StructType] then the value of `y.pointer` is
stored in `myStruct.x`. If `y` is neither a number nor stored in `myStruct.x`. If `y` is neither a number nor
@ -388,14 +408,11 @@ It is important to understand that creating a new instance allocates
memory on the WASM heap. We must not simply rely on garbage collection memory on the WASM heap. We must not simply rely on garbage collection
to clean up the instances because doing so will not free up the WASM to clean up the instances because doing so will not free up the WASM
heap memory. The correct way to free up that memory is to use the heap memory. The correct way to free up that memory is to use the
object's `dispose()` method. Alternately, there is a "nuclear option": object's `dispose()` method.
`MyBinder.disposeAll()` will free the memory allocated for _all_
instances which have not been manually disposed.
The following usage pattern offers one way to easily ensure proper The following usage pattern offers one way to easily ensure proper
cleanup of struct instances: cleanup of struct instances:
> >
```javascript ```javascript
const my = new MyStruct(); const my = new MyStruct();
@ -409,11 +426,6 @@ try {
from the byte array. */ from the byte array. */
// Pass the struct to C code which takes a MyStruct pointer: // Pass the struct to C code which takes a MyStruct pointer:
aCFunction( my.pointer ); aCFunction( my.pointer );
// Type-safely check if a pointer returned from C is a MyStruct:
const x = MyStruct.instanceForPointer( anotherCFunction() );
// If it is a MyStruct, x now refers to that object. Note, however,
// that this only works for instances created in JS, as the
// pointer mapping only exists in JS space.
} finally { } finally {
my.dispose(); my.dispose();
} }
@ -426,6 +438,16 @@ to use `try`/`finally` without a `catch`, and doing so is an ideal
match for the memory management requirements of Jaccwaby-bound struct match for the memory management requirements of Jaccwaby-bound struct
instances. instances.
It is often useful to wrap an existing instance of a C-side struct
without taking over ownership of its memory. That can be achieved by
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
// but will trigger any ondispose handler.
```
Now that we have struct instances, there are a number of things we Now that we have struct instances, there are a number of things we
can do with them, as covered in the rest of this document. can do with them, as covered in the rest of this document.
@ -549,20 +571,6 @@ The Struct Binder has the following members:
any of its "significant" configuration values may have undefined any of its "significant" configuration values may have undefined
results. results.
- `instanceForPointer(pointer)`
Given a pointer value relative to `config.memory`, if that pointer
resolves to a struct of _any type_ generated via the same Struct
Binder, this returns the struct instance associated with it, or
`undefined` if no struct object is mapped to that pointer. This
differs from the struct-type-specific member of the same name in
that this one is not "type-safe": it does not know the type of the
returned object (if any) and may return a struct of any
[StructType][] for which this Struct Binder has created a
constructor. It cannot return instances created via a different
[StructBinderFactory][] because each factory can hypothetically have
a different memory heap.
<a name='api-structtype'></a> <a name='api-structtype'></a>
API: Struct Type API: Struct Type
------------------------------------------------------------ ------------------------------------------------------------
@ -582,6 +590,14 @@ only called by the [StructBinder][]-generated
has the following "static" properties (^Which are accessible from has the following "static" properties (^Which are accessible from
individual instances via `theInstance.constructor`.): individual instances via `theInstance.constructor`.):
- `addOnDispose(...value)`\
If this object has no `ondispose` property, this function creates it
as an array and pushes the given value(s) onto it. If the object has
a function-typed `ondispose` property, this call replaces it with an
array and moves that function into the array. In all other cases,
`ondispose` is assumed to be an array and the argument(s) is/are
appended to it. Returns `this`.
- `allocCString(str)` - `allocCString(str)`
Identical to the [StructBinder][] method of the same name. Identical to the [StructBinder][] method of the same name.
@ -591,9 +607,6 @@ individual instances via `theInstance.constructor`.):
[struct's constructor][StructCtors]. If true, the memory is owned by [struct's constructor][StructCtors]. If true, the memory is owned by
someone other than the object and must outlive the object. someone other than the object and must outlive the object.
- `instanceForPointer(pointer)`
Works identically to the [StructBinder][] method of the same name.
- `isA(value)` - `isA(value)`
Returns true if its argument is a StructType instance _from the same Returns true if its argument is a StructType instance _from the same
[StructBinder][]_ as this StructType. [StructBinder][]_ as this StructType.
@ -619,14 +632,15 @@ legally be called on concrete struct instances unless noted otherwise:
- If it is a function, it is called with the struct object as its `this`. - If it is a function, it is called with the struct object as its `this`.
That method must not throw - if it does, the exception will be That method must not throw - if it does, the exception will be
ignored. ignored.
- If it is an array, it may contain functions, pointers, and/or JS - If it is an array, it may contain functions, pointers, other
strings. If an entry is a function, it is called as described [StructType] instances, and/or JS strings. If an entry is a
above. If it's a number, it's assumed to be a pointer and is function, it is called as described above. If it's a number, it's
passed to the `dealloc()` function configured for the parent assumed to be a pointer and is passed to the `dealloc()` function
[StructBinder][]. If it's a JS string, it's assumed to be a configured for the parent [StructBinder][]. If it's a
helpful description of the next entry in the list and is simply [StructType][] instance then its `dispose()` method is called. If
ignored. Strings are supported primarily for use as debugging it's a JS string, it's assumed to be a helpful description of the
information. next entry in the list and is simply ignored. Strings are
supported primarily for use as debugging information.
- Some struct APIs will manipulate the `ondispose` member, creating - Some struct APIs will manipulate the `ondispose` member, creating
it as an array or converting it from a function to array as it as an array or converting it from a function to array as
needed. needed.
@ -738,21 +752,6 @@ pointer can be taken over using something like
These constructors have the following "static" members: These constructors have the following "static" members:
- `disposeAll()`
For each instance of this struct, the equivalent of its `dispose()`
method is called. This frees all WASM-allocated memory associated
with _all_ instances and clears the `instanceForPointer()`
mappings. Returns `this`.
- `instanceForPointer(pointer)`
Given a pointer value (accessible via the `pointer` property of all
struct instances) which ostensibly refers to an instance of this
class, this returns the instance associated with it, or `undefined`
if no object _of this specific struct type_ is mapped to that
pointer. When C-side code calls back into JS code and passes a
pointer to an object, this function can be used to type-safely
"cast" that pointer back to its original object.
- `isA(value)` - `isA(value)`
Returns true if its argument was created by this constructor. Returns true if its argument was created by this constructor.
@ -762,15 +761,6 @@ These constructors have the following "static" members:
- `memberKeys(string)` - `memberKeys(string)`
Works exactly as documented for [StructType][]. Works exactly as documented for [StructType][].
- `resolveToInstance(value [,throwIfNot=false])`
Works like `instanceForPointer()` but accepts either an instance
of this struct type or a pointer which resolves to one.
It returns an instance of this struct type on success.
By default it returns a falsy value if its argument is not,
or does not resolve to, an instance of this struct type,
but if passed a truthy second argument then it will throw
instead.
- `structInfo` - `structInfo`
The structure description passed to [StructBinder][] when this The structure description passed to [StructBinder][] when this
constructor was generated. constructor was generated.

View File

@ -75,7 +75,7 @@
</li> </li>
<li>The easiest way to try different optimization levels is, <li>The easiest way to try different optimization levels is,
from this directory: from this directory:
<pre>$ rm -f speedtest1.js; make -e emcc_opt='-O2' speedtest1.js</pre> <pre>$ rm -f jswasm/speedtest1.js; make -e emcc_opt='-O2' speedtest1</pre>
Then reload this page. -O2 seems to consistently produce the fastest results. Then reload this page. -O2 seems to consistently produce the fastest results.
</li> </li>
</ul> </ul>

View File

@ -40,9 +40,9 @@
<script src="jswasm/speedtest1.js"></script> <script src="jswasm/speedtest1.js"></script>
<script>(function(){ <script>(function(){
/** /**
If this environment contains OPFS, this function initializes it and If this environment contains WASMFS with OPFS, this function
returns the name of the dir on which OPFS is mounted, else it returns initializes it and returns the name of the dir on which OPFS is
an empty string. mounted, else it returns an empty string.
*/ */
const wasmfsDir = function f(wasmUtil){ const wasmfsDir = function f(wasmUtil){
if(undefined !== f._) return f._; if(undefined !== f._) return f._;

View File

@ -7,11 +7,7 @@
<link rel="stylesheet" href="../common/emscripten.css"/> <link rel="stylesheet" href="../common/emscripten.css"/>
<link rel="stylesheet" href="../common/testing.css"/> <link rel="stylesheet" href="../common/testing.css"/>
<title>sqlite3 tester #1: Worker thread</title> <title>sqlite3 tester #1: Worker thread</title>
<style> <style></style>
body {
font-family: monospace;
}
</style>
</head> </head>
<body> <body>
<h1 id='color-target'>sqlite3 tester #1: Worker thread</h1> <h1 id='color-target'>sqlite3 tester #1: Worker thread</h1>

View File

@ -13,14 +13,9 @@ ES6 Module in UI thread
UI thread UI thread
//#endif //#endif
</title> </title>
<style> <style></style>
body {
font-family: monospace;
}
</style>
</head> </head>
<body> <body><h1 id='color-target'></h1>
<h1 id='color-target'></h1>
<div>See <a href='tester1-worker.html' target='tester1-worker.html'>tester1-worker.html</a> <div>See <a href='tester1-worker.html' target='tester1-worker.html'>tester1-worker.html</a>
for the Worker-thread variant.</div> for the Worker-thread variant.</div>
<div class='input-wrapper'> <div class='input-wrapper'>

View File

@ -22,12 +22,14 @@
groups and individual tests can be assigned a predicate function groups and individual tests can be assigned a predicate function
which determines whether to run them or not, and this is which determines whether to run them or not, and this is
specifically intended to be used to toggle certain tests on or off specifically intended to be used to toggle certain tests on or off
for the main/worker threads. for the main/worker threads or the availability (or not) of
optional features such as int64 support.
Each test group defines a state object which gets applied as each Each test group defines a single state object which gets applied as
test function's `this`. Test functions can use that to, e.g., set up the test functions' `this` for all tests in that group. Test
a db in an early test and close it in a later test. Each test gets functions can use that to, e.g., set up a db in an early test and
passed the sqlite3 namespace object as its only argument. close it in a later test. Each test gets passed the sqlite3
namespace object as its only argument.
*/ */
/* /*
This file is intended to be processed by c-pp to inject (or not) This file is intended to be processed by c-pp to inject (or not)
@ -301,14 +303,12 @@ self.sqlite3InitModule = sqlite3InitModule;
addTest: function(name, callback){ addTest: function(name, callback){
let predicate; let predicate;
if(1===arguments.length){ if(1===arguments.length){
const opt = arguments[0]; this.currentTestGroup.addTest(arguments[0]);
predicate = opt.predicate; }else{
name = opt.name; this.currentTestGroup.addTest({
callback = opt.test; name, predicate, test: callback
});
} }
this.currentTestGroup.addTest({
name, predicate, test: callback
});
return this; return this;
}, },
runTests: async function(sqlite3){ runTests: async function(sqlite3){
@ -399,12 +399,21 @@ self.sqlite3InitModule = sqlite3InitModule;
catch(e){T.assert("test ing ." === e.message)} catch(e){T.assert("test ing ." === e.message)}
try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) } try{ throw new sqlite3.SQLite3Error(capi.SQLITE_SCHEMA) }
catch(e){ T.assert('SQLITE_SCHEMA' === e.message) } catch(e){
T.assert('SQLITE_SCHEMA' === e.message)
.assert(capi.SQLITE_SCHEMA === e.resultCode);
}
try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) } try{ sqlite3.SQLite3Error.toss(capi.SQLITE_CORRUPT,{cause: true}) }
catch(e){ catch(e){
T.assert('SQLITE_CORRUPT'===e.message) T.assert('SQLITE_CORRUPT' === e.message)
.assert(capi.SQLITE_CORRUPT === e.resultCode)
.assert(true===e.cause); .assert(true===e.cause);
} }
try{ sqlite3.SQLite3Error.toss("resultCode check") }
catch(e){
T.assert(capi.SQLITE_ERROR === e.resultCode)
.assert('resultCode check' === e.message);
}
}) })
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
.t('strglob/strlike', function(sqlite3){ .t('strglob/strlike', function(sqlite3){
@ -419,6 +428,7 @@ self.sqlite3InitModule = sqlite3InitModule;
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
T.g('C/WASM Utilities') T.g('C/WASM Utilities')
.t('sqlite3.wasm namespace', function(sqlite3){ .t('sqlite3.wasm namespace', function(sqlite3){
// TODO: break this into smaller individual test functions.
const w = wasm; const w = wasm;
const chr = (x)=>x.charCodeAt(0); const chr = (x)=>x.charCodeAt(0);
//log("heap getters..."); //log("heap getters...");
@ -546,13 +556,13 @@ self.sqlite3InitModule = sqlite3InitModule;
let cpy = w.scopedAlloc(n+10); let cpy = w.scopedAlloc(n+10);
let rc = w.cstrncpy(cpy, cStr, n+10); let rc = w.cstrncpy(cpy, cStr, n+10);
T.assert(n+1 === rc). T.assert(n+1 === rc).
assert("hello" === w.cstringToJs(cpy)). assert("hello" === w.cstrToJs(cpy)).
assert(chr('o') === w.getMemValue(cpy+n-1)). assert(chr('o') === w.getMemValue(cpy+n-1)).
assert(0 === w.getMemValue(cpy+n)); assert(0 === w.getMemValue(cpy+n));
let cStr2 = w.scopedAllocCString("HI!!!"); let cStr2 = w.scopedAllocCString("HI!!!");
rc = w.cstrncpy(cpy, cStr2, 3); rc = w.cstrncpy(cpy, cStr2, 3);
T.assert(3===rc). T.assert(3===rc).
assert("HI!lo" === w.cstringToJs(cpy)). assert("HI!lo" === w.cstrToJs(cpy)).
assert(chr('!') === w.getMemValue(cpy+2)). assert(chr('!') === w.getMemValue(cpy+2)).
assert(chr('l') === w.getMemValue(cpy+3)); assert(chr('l') === w.getMemValue(cpy+3));
}finally{ }finally{
@ -660,6 +670,26 @@ self.sqlite3InitModule = sqlite3InitModule;
T.assert(rc>0 && Number.isFinite(rc)); T.assert(rc>0 && Number.isFinite(rc));
rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8'); rc = w.xCallWrapped('sqlite3_wasm_enum_json','utf8');
T.assert('string'===typeof rc).assert(rc.length>300); T.assert('string'===typeof rc).assert(rc.length>300);
{ // 'string:static' argAdapter() sanity checks...
let argAd = w.xWrap.argAdapter('string:static');
let p0 = argAd('foo'), p1 = argAd('bar');
T.assert(w.isPtr(p0) && w.isPtr(p1))
.assert(p0 !== p1)
.assert(p0 === argAd('foo'))
.assert(p1 === argAd('bar'));
}
// 'string:flexible' argAdapter() sanity checks...
w.scopedAllocCall(()=>{
const argAd = w.xWrap.argAdapter('string:flexible');
const cj = (v)=>w.cstrToJs(argAd(v));
T.assert('Hi' === cj('Hi'))
.assert('hi' === cj(['h','i']))
.assert('HI' === cj(new Uint8Array([72, 73])));
});
if(haveWasmCTests()){ if(haveWasmCTests()){
if(!sqlite3.config.useStdAlloc){ if(!sqlite3.config.useStdAlloc){
fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']); fw = w.xWrap('sqlite3_wasm_test_str_hello', 'utf8:dealloc',['i32']);
@ -689,7 +719,7 @@ self.sqlite3InitModule = sqlite3InitModule;
}/*WhWasmUtil*/) }/*WhWasmUtil*/)
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
.t('sqlite3.StructBinder (jaccwabyt)', function(sqlite3){ .t('sqlite3.StructBinder (jaccwabyt🐇)', function(sqlite3){
const S = sqlite3, W = S.wasm; const S = sqlite3, W = S.wasm;
const MyStructDef = { const MyStructDef = {
sizeof: 16, sizeof: 16,
@ -720,9 +750,6 @@ self.sqlite3InitModule = sqlite3InitModule;
assert(undefined === K.prototype.lookupMember('nope',false)). assert(undefined === K.prototype.lookupMember('nope',false)).
assert(k1 instanceof StructType). assert(k1 instanceof StructType).
assert(StructType.isA(k1)). assert(StructType.isA(k1)).
assert(K.resolveToInstance(k1.pointer)===k1).
mustThrowMatching(()=>K.resolveToInstance(null,true), /is-not-a my_struct/).
assert(k1 === StructType.instanceForPointer(k1.pointer)).
mustThrowMatching(()=>k1.$ro = 1, /read-only/); mustThrowMatching(()=>k1.$ro = 1, /read-only/);
Object.keys(MyStructDef.members).forEach(function(key){ Object.keys(MyStructDef.members).forEach(function(key){
key = K.memberKey(key); key = K.memberKey(key);
@ -732,8 +759,7 @@ self.sqlite3InitModule = sqlite3InitModule;
" from "+k1.memoryDump()); " from "+k1.memoryDump());
}); });
T.assert('number' === typeof k1.pointer). T.assert('number' === typeof k1.pointer).
mustThrowMatching(()=>k1.pointer = 1, /pointer/). mustThrowMatching(()=>k1.pointer = 1, /pointer/);
assert(K.instanceForPointer(k1.pointer) === k1);
k1.$p4 = 1; k1.$pP = 2; k1.$p4 = 1; k1.$pP = 2;
T.assert(1 === k1.$p4).assert(2 === k1.$pP); T.assert(1 === k1.$p4).assert(2 === k1.$pP);
if(MyStructDef.members.$p8){ if(MyStructDef.members.$p8){
@ -748,22 +774,13 @@ self.sqlite3InitModule = sqlite3InitModule;
assert('number' === typeof k1.$cstr). assert('number' === typeof k1.$cstr).
assert('A C-string.' === k1.memberToJsString('cstr')); assert('A C-string.' === k1.memberToJsString('cstr'));
k1.$pP = k2; k1.$pP = k2;
T.assert(k1.$pP === k2); T.assert(k1.$pP === k2.pointer);
k1.$pP = null/*null is special-cased to 0.*/; k1.$pP = null/*null is special-cased to 0.*/;
T.assert(0===k1.$pP); T.assert(0===k1.$pP);
let ptr = k1.pointer; let ptr = k1.pointer;
k1.dispose(); k1.dispose();
T.assert(undefined === k1.pointer). T.assert(undefined === k1.pointer).
assert(undefined === K.instanceForPointer(ptr)).
mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/); mustThrowMatching(()=>{k1.$pP=1}, /disposed instance/);
const k3 = new K();
ptr = k3.pointer;
T.assert(k3 === K.instanceForPointer(ptr));
K.disposeAll();
T.assert(ptr).
assert(undefined === k2.pointer).
assert(undefined === k3.pointer).
assert(undefined === K.instanceForPointer(ptr));
}finally{ }finally{
k1.dispose(); k1.dispose();
k2.dispose(); k2.dispose();
@ -793,10 +810,8 @@ self.sqlite3InitModule = sqlite3InitModule;
assert(wts instanceof WTStruct). assert(wts instanceof WTStruct).
assert(wts instanceof StructType). assert(wts instanceof StructType).
assert(StructType.isA(wts)). assert(StructType.isA(wts)).
assert(wts === StructType.instanceForPointer(wts.pointer)); assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8).
T.assert(wts.pointer>0).assert(0===wts.$v4).assert(0n===wts.$v8). assert(0===wts.$ppV).assert(0===wts.$xFunc);
assert(0===wts.$ppV).assert(0===wts.$xFunc).
assert(WTStruct.instanceForPointer(wts.pointer) === wts);
const testFunc = const testFunc =
W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/); W.xGet('sqlite3_wasm_test_struct'/*name gets mangled in -O3 builds!*/);
let counter = 0; let counter = 0;
@ -805,7 +820,6 @@ self.sqlite3InitModule = sqlite3InitModule;
/*log("This from a JS function called from C, "+ /*log("This from a JS function called from C, "+
"which itself was called from JS. arg =",arg);*/ "which itself was called from JS. arg =",arg);*/
++counter; ++counter;
T.assert(WTStruct.instanceForPointer(arg) === wts);
if(3===counter){ if(3===counter){
tossQuietly("Testing exception propagation."); tossQuietly("Testing exception propagation.");
} }
@ -829,7 +843,7 @@ self.sqlite3InitModule = sqlite3InitModule;
testFunc(wts.pointer); testFunc(wts.pointer);
//log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV); //log("wts.pointer, wts.$ppV",wts.pointer, wts.$ppV);
T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8) T.assert(1===counter).assert(20 === wts.$v4).assert(40n === wts.$v8)
.assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)) .assert(wts.$ppV === wts.pointer)
.assert('string' === typeof wts.memberToJsString('cstr')) .assert('string' === typeof wts.memberToJsString('cstr'))
.assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr')) .assert(wts.memberToJsString('cstr') === wts.memberToJsString('$cstr'))
.mustThrowMatching(()=>wts.memberToJsString('xFunc'), .mustThrowMatching(()=>wts.memberToJsString('xFunc'),
@ -837,279 +851,42 @@ self.sqlite3InitModule = sqlite3InitModule;
; ;
testFunc(wts.pointer); testFunc(wts.pointer);
T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8) T.assert(2===counter).assert(40 === wts.$v4).assert(80n === wts.$v8)
.assert(autoResolvePtr ? (wts.$ppV === wts) : (wts.$ppV === wts.pointer)); .assert(wts.$ppV === wts.pointer);
/** The 3rd call to wtsFunc throw from JS, which is called /** The 3rd call to wtsFunc throw from JS, which is called
from C, which is called from JS. Let's ensure that from C, which is called from JS. Let's ensure that
that exception propagates back here... */ that exception propagates back here... */
T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/); T.mustThrowMatching(()=>testFunc(wts.pointer),/^Testing/);
W.uninstallFunction(wts.$xFunc); W.uninstallFunction(wts.$xFunc);
wts.$xFunc = 0; wts.$xFunc = 0;
if(autoResolvePtr){ wts.$ppV = 0;
wts.$ppV = 0; T.assert(!wts.$ppV);
T.assert(!wts.$ppV); //WTStruct.debugFlags(0x03);
//WTStruct.debugFlags(0x03); wts.$ppV = wts;
wts.$ppV = wts; T.assert(wts.pointer === wts.$ppV)
T.assert(wts === wts.$ppV)
//WTStruct.debugFlags(0);
}
wts.setMemberCString('cstr', "A C-string."); wts.setMemberCString('cstr', "A C-string.");
T.assert(Array.isArray(wts.ondispose)). T.assert(Array.isArray(wts.ondispose)).
assert(wts.ondispose[0] === wts.$cstr). assert(wts.ondispose[0] === wts.$cstr).
assert('A C-string.' === wts.memberToJsString('cstr')); assert('A C-string.' === wts.memberToJsString('cstr'));
const ptr = wts.pointer; const ptr = wts.pointer;
wts.dispose(); wts.dispose();
T.assert(ptr).assert(undefined === wts.pointer). T.assert(ptr).assert(undefined === wts.pointer);
assert(undefined === WTStruct.instanceForPointer(ptr))
}finally{ }finally{
wts.dispose(); wts.dispose();
} }
if(1){ // ondispose of other struct instances
const s1 = new WTStruct, s2 = new WTStruct, s3 = new WTStruct;
T.assert(s1.lookupMember instanceof Function)
.assert(s1.addOnDispose instanceof Function);
s1.addOnDispose(s2,"testing variadic args");
T.assert(2===s1.ondispose.length);
s2.addOnDispose(s3);
s1.dispose();
T.assert(!s2.pointer,"Expecting s2 to be ondispose'd by s1.");
T.assert(!s3.pointer,"Expecting s3 to be ondispose'd by s2.");
}
}/*StructBinder*/) }/*StructBinder*/)
////////////////////////////////////////////////////////////////////
.t('sqlite3.StructBinder part 2', function(sqlite3){
// https://www.sqlite.org/c3ref/vfs.html
// https://www.sqlite.org/c3ref/io_methods.html
const sqlite3_io_methods = capi.sqlite3_io_methods,
sqlite3_vfs = capi.sqlite3_vfs,
sqlite3_file = capi.sqlite3_file;
//log("struct sqlite3_file", sqlite3_file.memberKeys());
//log("struct sqlite3_vfs", sqlite3_vfs.memberKeys());
//log("struct sqlite3_io_methods", sqlite3_io_methods.memberKeys());
const installMethod = function callee(tgt, name, func){
if(1===arguments.length){
return (n,f)=>callee(tgt,n,f);
}
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);
}
};
callee.ondisposeRemoveFunc = function(){
if(this.__ondispose){
const who = this;
this.__ondispose.forEach(
(v)=>{
if('number'===typeof v){
try{wasm.uninstallFunction(v)}
catch(e){/*ignore*/}
}else{/*wasm function wrapper property*/
delete who[v];
}
}
);
delete this.__ondispose;
}
};
}/*static init*/
const sigN = tgt.memberSignature(name),
memKey = tgt.memberKey(name);
//log("installMethod",tgt, name, sigN);
if(!tgt.__ondispose){
T.assert(undefined === tgt.ondispose);
tgt.ondispose = [callee.ondisposeRemoveFunc];
tgt.__ondispose = [];
}
const fProxy = callee.argcProxy(func, sigN);
const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
tgt[memKey] = pFunc;
/**
ACHTUNG: function pointer IDs are from a different pool than
allocation IDs, starting at 1 and incrementing in steps of 1,
so if we set tgt[memKey] to those values, we'd very likely
later misinterpret them as plain old pointer addresses unless
unless we use some silly heuristic like "all values <5k are
presumably function pointers," or actually perform a function
lookup on every pointer to first see if it's a function. That
would likely work just fine, but would be kludgy.
It turns out that "all values less than X are functions" is
essentially how it works in wasm: a function pointer is
reported to the client as its index into the
__indirect_function_table.
So... once jaccwabyt can be told how to access the
function table, it could consider all pointer values less
than that table's size to be functions. As "real" pointer
values start much, much higher than the function table size,
that would likely work reasonably well. e.g. the object
pointer address for sqlite3's default VFS is (in this local
setup) 65104, whereas the function table has fewer than 600
entries.
*/
const wrapperKey = '$'+memKey;
tgt[wrapperKey] = fProxy;
tgt.__ondispose.push(pFunc, wrapperKey);
//log("tgt.__ondispose =",tgt.__ondispose);
return (n,f)=>callee(tgt, n, f);
}/*installMethod*/;
const installIOMethods = function instm(iom){
(iom instanceof capi.sqlite3_io_methods) || toss("Invalid argument type.");
if(!instm._requireFileArg){
instm._requireFileArg = function(arg,methodName){
arg = capi.sqlite3_file.resolveToInstance(arg);
if(!arg){
err("sqlite3_io_methods::xClose() was passed a non-sqlite3_file.");
}
return arg;
};
instm._methods = {
// https://sqlite.org/c3ref/io_methods.html
xClose: /*i(P)*/function(f){
/* int (*xClose)(sqlite3_file*) */
log("xClose(",f,")");
if(!(f = instm._requireFileArg(f,'xClose'))) return capi.SQLITE_MISUSE;
f.dispose(/*noting that f has externally-owned memory*/);
return 0;
},
xRead: /*i(Ppij)*/function(f,dest,n,offset){
/* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
log("xRead(",arguments,")");
if(!(f = instm._requireFileArg(f))) return capi.SQLITE_MISUSE;
wasm.heap8().fill(0, dest + offset, n);
return 0;
},
xWrite: /*i(Ppij)*/function(f,dest,n,offset){
/* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
log("xWrite(",arguments,")");
if(!(f=instm._requireFileArg(f,'xWrite'))) return capi.SQLITE_MISUSE;
return 0;
},
xTruncate: /*i(Pj)*/function(f){
/* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
log("xTruncate(",arguments,")");
if(!(f=instm._requireFileArg(f,'xTruncate'))) return capi.SQLITE_MISUSE;
return 0;
},
xSync: /*i(Pi)*/function(f){
/* int (*xSync)(sqlite3_file*, int flags) */
log("xSync(",arguments,")");
if(!(f=instm._requireFileArg(f,'xSync'))) return capi.SQLITE_MISUSE;
return 0;
},
xFileSize: /*i(Pp)*/function(f,pSz){
/* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
log("xFileSize(",arguments,")");
if(!(f=instm._requireFileArg(f,'xFileSize'))) return capi.SQLITE_MISUSE;
wasm.setMemValue(pSz, 0/*file size*/);
return 0;
},
xLock: /*i(Pi)*/function(f){
/* int (*xLock)(sqlite3_file*, int) */
log("xLock(",arguments,")");
if(!(f=instm._requireFileArg(f,'xLock'))) return capi.SQLITE_MISUSE;
return 0;
},
xUnlock: /*i(Pi)*/function(f){
/* int (*xUnlock)(sqlite3_file*, int) */
log("xUnlock(",arguments,")");
if(!(f=instm._requireFileArg(f,'xUnlock'))) return capi.SQLITE_MISUSE;
return 0;
},
xCheckReservedLock: /*i(Pp)*/function(){
/* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
log("xCheckReservedLock(",arguments,")");
return 0;
},
xFileControl: /*i(Pip)*/function(){
/* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
log("xFileControl(",arguments,")");
return capi.SQLITE_NOTFOUND;
},
xSectorSize: /*i(P)*/function(){
/* int (*xSectorSize)(sqlite3_file*) */
log("xSectorSize(",arguments,")");
return 0/*???*/;
},
xDeviceCharacteristics:/*i(P)*/function(){
/* int (*xDeviceCharacteristics)(sqlite3_file*) */
log("xDeviceCharacteristics(",arguments,")");
return 0;
}
};
}/*static init*/
iom.$iVersion = 1;
Object.keys(instm._methods).forEach(
(k)=>installMethod(iom, k, instm._methods[k])
);
}/*installIOMethods()*/;
const iom = new sqlite3_io_methods, sfile = new sqlite3_file;
const err = console.error.bind(console);
try {
const IOM = sqlite3_io_methods, S3F = sqlite3_file;
//log("iom proto",iom,iom.constructor.prototype);
//log("sfile",sfile,sfile.constructor.prototype);
T.assert(0===sfile.$pMethods).assert(iom.pointer > 0);
//log("iom",iom);
sfile.$pMethods = iom.pointer;
T.assert(iom.pointer === sfile.$pMethods)
.assert(IOM.resolveToInstance(iom))
.assert(undefined ===IOM.resolveToInstance(sfile))
.mustThrow(()=>IOM.resolveToInstance(0,true))
.assert(S3F.resolveToInstance(sfile.pointer))
.assert(undefined===S3F.resolveToInstance(iom))
.assert(iom===IOM.resolveToInstance(sfile.$pMethods));
T.assert(0===iom.$iVersion);
installIOMethods(iom);
T.assert(1===iom.$iVersion);
//log("iom.__ondispose",iom.__ondispose);
T.assert(Array.isArray(iom.__ondispose)).assert(iom.__ondispose.length>10);
}finally{
iom.dispose();
T.assert(undefined === iom.__ondispose);
}
const dVfs = new sqlite3_vfs(capi.sqlite3_vfs_find(null));
try {
const SB = sqlite3.StructBinder;
T.assert(dVfs instanceof SB.StructType)
.assert(dVfs.pointer)
.assert('sqlite3_vfs' === dVfs.structName)
.assert(!!dVfs.structInfo)
.assert(SB.StructType.hasExternalPointer(dVfs))
.assert(dVfs.$iVersion>0)
.assert('number'===typeof dVfs.$zName)
.assert('number'===typeof dVfs.$xSleep)
.assert(wasm.functionEntry(dVfs.$xOpen))
.assert(dVfs.memberIsString('zName'))
.assert(dVfs.memberIsString('$zName'))
.assert(!dVfs.memberIsString('pAppData'))
.mustThrowMatching(()=>dVfs.memberToJsString('xSleep'),
/Invalid member type signature for C-string/)
.mustThrowMatching(()=>dVfs.memberSignature('nope'), /nope is not a mapped/)
.assert('string' === typeof dVfs.memberToJsString('zName'))
.assert(dVfs.memberToJsString('zName')===dVfs.memberToJsString('$zName'))
;
//log("Default VFS: @",dVfs.pointer);
Object.keys(sqlite3_vfs.structInfo.members).forEach(function(mname){
const mk = sqlite3_vfs.memberKey(mname), mbr = sqlite3_vfs.structInfo.members[mname],
addr = dVfs[mk], prefix = 'defaultVfs.'+mname;
if(1===mbr.signature.length){
let sep = '?', val = undefined;
switch(mbr.signature[0]){
// TODO: move this into an accessor, e.g. getPreferredValue(member)
case 'i': case 'j': case 'f': case 'd': sep = '='; val = dVfs[mk]; break
case 'p': case 'P': sep = '@'; val = dVfs[mk]; break;
case 's': sep = '=';
val = dVfs.memberToJsString(mname);
break;
}
//log(prefix, sep, val);
}else{
//log(prefix," = funcptr @",addr, wasm.functionEntry(addr));
}
});
}finally{
dVfs.dispose();
T.assert(undefined===dVfs.pointer);
}
}/*StructBinder part 2*/)
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
.t('sqlite3.wasm.pstack', function(sqlite3){ .t('sqlite3.wasm.pstack', function(sqlite3){
const P = wasm.pstack; const P = wasm.pstack;
@ -1219,7 +996,40 @@ self.sqlite3InitModule = sqlite3InitModule;
.t('Create db', function(sqlite3){ .t('Create db', function(sqlite3){
const dbFile = '/tester1.db'; const dbFile = '/tester1.db';
wasm.sqlite3_wasm_vfs_unlink(0, dbFile); 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');
db.onclose = {
disposeAfter: [],
disposeBefore: [],
before: function(db){
while(this.disposeBefore.length){
const v = this.disposeBefore.shift();
console.debug("db.onclose.before cleaning up:",v);
if(wasm.isPtr(v)) wasm.dealloc(v);
else if(v instanceof sqlite3.StructBinder.StructType){
v.dispose();
}else if(v instanceof Function){
try{ v(db) } catch(e){
console.warn("beforeDispose() callback threw:",e);
}
}
}
console.debug("db.onclose.before dropping modules");
sqlite3.capi.sqlite3_drop_modules(db.pointer, 0);
},
after: function(){
while(this.disposeAfter.length){
const v = this.disposeAfter.shift();
console.debug("db.onclose.after cleaning up:",v);
if(wasm.isPtr(v)) wasm.dealloc(v);
else if(v instanceof sqlite3.StructBinder.StructType){
v.dispose();
}else if(v instanceof Function){
try{v()} catch(e){/*ignored*/}
}
}
}
};
T.assert(Number.isInteger(db.pointer)) T.assert(Number.isInteger(db.pointer))
.mustThrowMatching(()=>db.pointer=1, /read-only/) .mustThrowMatching(()=>db.pointer=1, /read-only/)
.assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1)) .assert(0===sqlite3.capi.sqlite3_extended_result_codes(db.pointer,1))
@ -1442,6 +1252,29 @@ self.sqlite3InitModule = sqlite3InitModule;
rc = db.selectArray('select a, b from t where b=-1'); rc = db.selectArray('select a, b from t where b=-1');
T.assert(undefined === rc); 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({ .t({
@ -1750,8 +1583,8 @@ self.sqlite3InitModule = sqlite3InitModule;
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
.t({ .t({
name: 'C-side WASM tests (if compiled in)', name: 'C-side WASM tests',
predicate: haveWasmCTests, predicate: ()=>(haveWasmCTests() || "Not compiled in."),
test: function(){ test: function(){
const w = wasm, db = this.db; const w = wasm, db = this.db;
const stack = w.scopedAllocPush(); const stack = w.scopedAllocPush();
@ -1834,9 +1667,398 @@ self.sqlite3InitModule = sqlite3InitModule;
} }
}/* jaccwabyt-specific tests */) }/* jaccwabyt-specific tests */)
////////////////////////////////////////////////////////////////////////
.t({
name: 'virtual table #1',
predicate: ()=>!!capi.sqlite3_index_info,
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 args = wasm.cArgvToJs(argc, argv);
T.assert(args.length>=3)
.assert(args[0] === 'testvtab')
.assert(args[1] === 'main')
.assert(args[2] === 'testvtab');
console.debug("xConnect() args =",args);
const rc = capi.sqlite3_declare_vtab(
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
const t = vth.xVtab();
wasm.setPtrValue(ppVtab, t.pointer);
T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
}
return rc;
}catch(e){
if(!(e instanceof sqlite3.WasmAllocError)){
wasm.dealloc(wasm.getPtrValue, pzErr);
wasm.setPtrValue(pzErr, wasm.allocCString(e.message));
}
return vth.xError('xConnect',e);
}
},
xDisconnect: function(pVtab){
try {
const t = vth.xVtab(pVtab, true);
t.dispose();
return 0;
}catch(e){
return vth.xError('xDisconnect',e);
}
},
xOpen: function(pVtab, ppCursor){
try{
const t = vth.xVtab(pVtab), c = vth.xCursor();
T.assert(t instanceof capi.sqlite3_vtab)
.assert(c instanceof capi.sqlite3_vtab_cursor);
wasm.setPtrValue(ppCursor, c.pointer);
c._rowId = 0;
return 0;
}catch(e){
return vth.xError('xOpen',e);
}
},
xClose: function(pCursor){
try{
const c = vth.xCursor(pCursor,true);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
.assert(!vth.xCursor(pCursor));
c.dispose();
return 0;
}catch(e){
return vth.xError('xClose',e);
}
},
xNext: function(pCursor){
try{
const c = vth.xCursor(pCursor);
++c._rowId;
return 0;
}catch(e){
return vth.xError('xNext',e);
}
},
xColumn: function(pCursor, pCtx, iCol){
try{
const c = vth.xCursor(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.xError('xColumn',e);
}
},
xRowid: function(pCursor, ppRowid64){
try{
const c = vth.xCursor(pCursor);
vth.xRowid(ppRowid64, c._rowId);
return 0;
}catch(e){
return vth.xError('xRowid',e);
}
},
xEof: function(pCursor){
const c = vth.xCursor(pCursor),
rc = c._rowId>=10;
c.dispose();
return rc;
},
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
try{
const c = vth.xCursor(pCursor);
c._rowId = 0;
const list = vth.sqlite3ValuesToJs(argc, argv);
T.assert(argc === list.length);
//log(argc,"xFilter value(s):",list);
c.dispose();
return 0;
}catch(e){
return vth.xError('xFilter',e);
}
},
xBestIndex: function(pVtab, pIdxInfo){
try{
//const t = vth.xVtab(pVtab);
const sii = capi.sqlite3_index_info;
const pii = new sii(pIdxInfo);
pii.$estimatedRows = 10;
pii.$estimatedCost = 10.0;
//log("xBestIndex $nConstraint =",pii.$nConstraint);
if(pii.$nConstraint>0){
// Validate nthConstraint() and nthConstraintUsage()
const max = pii.$nConstraint;
for(let i=0; i < max; ++i ){
let v = pii.nthConstraint(i,true);
T.assert(wasm.isPtr(v));
v = pii.nthConstraint(i);
T.assert(v instanceof sii.sqlite3_index_constraint)
.assert(v.pointer >= pii.$aConstraint);
v.dispose();
v = pii.nthConstraintUsage(i,true);
T.assert(wasm.isPtr(v));
v = pii.nthConstraintUsage(i);
T.assert(v instanceof sii.sqlite3_index_constraint_usage)
.assert(v.pointer >= pii.$aConstraintUsage);
v.$argvIndex = i;//just to get some values into xFilter
v.dispose();
}
}
//log("xBestIndex $nOrderBy =",pii.$nOrderBy);
if(pii.$nOrderBy>0){
// Validate nthOrderBy()
const max = pii.$nOrderBy;
for(let i=0; i < max; ++i ){
let v = pii.nthOrderBy(i,true);
T.assert(wasm.isPtr(v));
v = pii.nthOrderBy(i);
T.assert(v instanceof sii.sqlite3_index_orderby)
.assert(v.pointer >= pii.$aOrderBy);
v.dispose();
}
}
pii.dispose();
return 0;
}catch(e){
return vth.xError('xBestIndex',e);
}
}
};
/**
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.$iVersion = 0;
this.db.onclose.disposeAfter.push(tmplMod);
vth.installMethods(tmplMod, tmplMethods, true);
if(tmplMethods.xCreate){
T.assert(tmplMod.$xCreate)
.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 where a<9999 and b>1 order by a, b"
/* Query is shaped so that it will ensure that some constraints
end up in xBestIndex(). */
);
T.assert(10===list.length)
.assert(1000===list[0][0])
.assert(2009===list[list.length-1][1]);
}
})/*custom vtab #1*/
////////////////////////////////////////////////////////////////////////
.t({
name: 'virtual table #2 (non-eponymous w/ automated exception wrapping)',
predicate: ()=>!!capi.sqlite3_index_info,
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.
*/
let throwOnCreate = 1 ? 0 : capi.SQLITE_CANTOPEN
/* ^^^ just for testing exception wrapping. Note that sqlite
always translates errors from a vtable to a generic
SQLITE_ERROR unless it's from xConnect()/xCreate() and that
callback sets an error string. */;
const vtabTrace = 1
? ()=>{}
: (methodName,...args)=>console.debug('sqlite3_module::'+methodName+'():',...args);
const modConfig = {
/* catchExceptions changes how the methods are wrapped */
catchExceptions: true,
name: "vtab2test",
methods:{
xCreate: function(pDb, pAux, argc, argv, ppVtab, pzErr){
vtabTrace("xCreate",...arguments);
if(throwOnCreate){
sqlite3.SQLite3Error.toss(
throwOnCreate,
"Throwing a test exception."
);
}
const args = wasm.cArgvToJs(argc, argv);
vtabTrace("xCreate","argv:",args);
T.assert(args.length>=3);
const rc = capi.sqlite3_declare_vtab(
pDb, "CREATE TABLE ignored(a,b)"
);
if(0===rc){
const t = vth.xVtab();
wasm.setPtrValue(ppVtab, t.pointer);
T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
vtabTrace("xCreate",...arguments," ppVtab =",t.pointer);
}
return rc;
},
xDestroy: function(pVtab){
vtabTrace("sqlite3_xDestroy",pVtab);
const t = vth.xVtab(pVtab, true);
T.assert(t);
t.dispose();
},
/*xConnect: function(pDb, pAux, argc, argv, ppVtab, pzErr){
vtabTrace("xConnect",...arguments);
const t = vth.xVtab();
wasm.setPtrValue(ppVtab, t.pointer);
T.assert(t === vth.xVtab(wasm.getPtrValue(ppVtab)));
vtabTrace("xConnect",...arguments,"ppVtab =",t.pointer);
},
xDisconnect: function(pVtab){
vtabTrace("xDisconnect",pVtab);
const t = vth.xVtab(pVtab, true);
T.assert(t);
t.dispose();
},*/
xOpen: function(pVtab, ppCursor){
const t = vth.xVtab(pVtab), c = vth.xCursor();
T.assert(t instanceof capi.sqlite3_vtab)
.assert(c instanceof capi.sqlite3_vtab_cursor);
wasm.setPtrValue(ppCursor, c.pointer);
vtabTrace("xOpen",...arguments," cursor =",c.pointer);
c._rowId = 0;
},
xClose: function(pCursor){
vtabTrace("xClose",...arguments);
const c = vth.xCursor(pCursor,true);
T.assert(c instanceof capi.sqlite3_vtab_cursor)
.assert(!vth.xCursor(pCursor));
c.dispose();
},
xNext: function(pCursor){
vtabTrace("xNext",...arguments);
const c = vth.xCursor(pCursor);
++c._rowId;
},
xColumn: function(pCursor, pCtx, iCol){
vtabTrace("xColumn",...arguments);
const c = vth.xCursor(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);
}
},
xRowid: function(pCursor, ppRowid64){
vtabTrace("xRowid",...arguments);
const c = vth.xCursor(pCursor);
vth.xRowid(ppRowid64, c._rowId);
},
xEof: function(pCursor){
vtabTrace("xEof",...arguments);
return vth.xCursor(pCursor)._rowId>=10;
},
xFilter: function(pCursor, idxNum, idxCStr,
argc, argv/* [sqlite3_value* ...] */){
vtabTrace("xFilter",...arguments);
const c = vth.xCursor(pCursor);
c._rowId = 0;
const list = vth.sqlite3ValuesToJs(argc, argv);
T.assert(argc === list.length);
},
xBestIndex: function(pVtab, pIdxInfo){
vtabTrace("xBestIndex",...arguments);
//const t = vth.xVtab(pVtab);
const pii = vth.xIndexInfo(pIdxInfo);
pii.$estimatedRows = 10;
pii.$estimatedCost = 10.0;
pii.dispose();
}
}/*methods*/
};
const tmplMod = vth.setupModule(modConfig);
T.assert(tmplMod instanceof capi.sqlite3_module)
.assert(1===tmplMod.$iVersion);
this.db.onclose.disposeAfter.push(tmplMod);
this.db.checkRc(capi.sqlite3_create_module(
this.db.pointer, modConfig.name, tmplMod.pointer, 0
));
this.db.exec([
"create virtual table testvtab2 using ",
modConfig.name,
"(arg1 blah, arg2 bloop)"
]);
this.db.onclose.disposeBefore.push(function(db){
console.debug("testvtab2 disposeBefore handler...");
db.exec(
/**
DROP TABLE is the only way to get xDestroy() to be called.
If we DROP TABLE at the end of the containing
test function, xDestroy() is called. If we instead
delay it until db.onclose.before(), we're getting
"no such table"?
*/
"DROP TABLE testvtab2"
);
});
let list = this.db.selectArrays(
"SELECT a,b FROM testvtab2 where a<9999 and b>1 order by a, b"
/* Query is shaped so that it will ensure that some
constraints end up in xBestIndex(). */
);
T.assert(10===list.length)
.assert(1000===list[0][0])
.assert(2009===list[list.length-1][1]);
list = this.db.selectArrays(
"SELECT a,b FROM testvtab2 where a<9999 and b>1 order by b, a limit 5"
);
T.assert(5===list.length)
.assert(1000===list[0][0])
.assert(2004===list[list.length-1][1]);
}
})/*custom vtab #2*/
////////////////////////////////////////////////////////////////////////
.t('Close db', function(){ .t('Close db', function(){
T.assert(this.db).assert(Number.isInteger(this.db.pointer)); T.assert(this.db).assert(wasm.isPtr(this.db.pointer));
wasm.exports.sqlite3_wasm_db_reset(this.db.pointer); //wasm.sqlite3_wasm_db_reset(this.db); // will leak virtual tables!
this.db.close(); this.db.close();
T.assert(!this.db.pointer); T.assert(!this.db.pointer);
}) })
@ -1856,7 +2078,8 @@ self.sqlite3InitModule = sqlite3InitModule;
}) })
.t({ .t({
name: 'kvvfs in main thread', name: 'kvvfs in main thread',
predicate: ()=>(isUIThread() ? true : "No local/sessionStorage in Worker"), predicate: ()=>(isUIThread()
|| "local/sessionStorage are unavailable in a Worker"),
test: function(sqlite3){ test: function(sqlite3){
const filename = this.kvvfsDbFile = 'session'; const filename = this.kvvfsDbFile = 'session';
const pVfs = capi.sqlite3_vfs_find('kvvfs'); const pVfs = capi.sqlite3_vfs_find('kvvfs');
@ -1901,7 +2124,7 @@ self.sqlite3InitModule = sqlite3InitModule;
delete this.kvvfsUnlink; delete this.kvvfsUnlink;
delete this.JDb; delete this.JDb;
} }
}/*kvvfs sqlite3_js_vfs_create_file()*/) }/*kvvfs sqlite3_js_vfs_create_file()*/)
;/* end kvvfs tests */ ;/* end kvvfs tests */
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,5 @@
C Describe\sthe\sSQLITE_DBCONFIG_RESET_DATABASE\sflag's\spolicy\sregarding\svirtual\stables,\sper\s/chat\sdiscussion. C Initial\ssupport\sfor\svirtual\stables\simplemented\sin\sJavaScript.
D 2022-12-08T12:51:11.019 D 2022-12-08T15:00:53.415
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -491,37 +491,37 @@ F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04 F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
F ext/wasm/EXPORTED_FUNCTIONS.fiddle.in 27450c8b8c70875a260aca55435ec927068b34cef801a96205adb81bdcefc65c 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-dist.txt 2d670b426fc7c613b90a7d2f2b05b433088fe65181abead970980f0a4a75ea20
F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9 F ext/wasm/README.md ef39861aa21632fdbca0bdd469f78f0096f6449a720f3f39642594af503030e9
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api 89af0612bad5c651f69e629c7e9689be6d3c8a92a9010da5dd90a87c1d86817a F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api ffa70413409e922ce0f761779787a1d9100b34b43c8e3106bb7ccf2786a41326
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287 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-post-js.c-pp.js 8923f76c3d2213159e12d641dc750523ead5c848185dc4996fae5cc12397f88d
F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41 F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1 F ext/wasm/api/post-js-footer.js cd0a8ec768501d9bd45d325ab0442037fb0e33d1f3b4f08902f15c34720ee4a1
F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62 F ext/wasm/api/post-js-header.js 47b6b281f39ad59fa6e8b658308cd98ea292c286a68407b35ff3ed9cfd281a62
F ext/wasm/api/pre-js.c-pp.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f9e91c1d5f 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-cleanup.js 680d5ccfff54459db136a49b2199d9f879c8405d9c99af1dda0cc5e7c29056f4
F ext/wasm/api/sqlite3-api-glue.js 6fe39964605fda3b699f69365eed565b5172d29cab2c49bc057a43f9a93f9f36 F ext/wasm/api/sqlite3-api-glue.js 8fa55af37c9880f94a803f32591dc0304750cc2f750048daf41fe942757bee64
F ext/wasm/api/sqlite3-api-oo1.js 91a7d7b9203fb0f031e6ba380a644a7f871e1798b388de399c01ed4087bac9e0 F ext/wasm/api/sqlite3-api-oo1.js e9e6da5f9e4d7d309fe4c338a22fb38575e831cddd10d6506fba7ddc180df6e6
F ext/wasm/api/sqlite3-api-prologue.js 697a5989ad52a9ba7bc60b5436589bd05885ee2201d84c38c5e9af3876af3ba4 F ext/wasm/api/sqlite3-api-prologue.js 1380e933325c11786b2afc93fc8ff88c2fd1ffeac3e0081da35e5a7317f20e09
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 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-opfs-async-proxy.js 7795b84b66a7a8dedc791340709b310bb497c3c72a80bef364fa2a58e2ddae3f
F ext/wasm/api/sqlite3-vfs-helper.js 4ad4faf02e1524bf0296be8452c00b5708dce6faf649468d0377e26a0b299263 F ext/wasm/api/sqlite3-v-helper.js ec03a222ad3551764626f14f38de1b1081bda509e098849502b498c041993a0f w ext/wasm/api/sqlite3-vfs-helper.js
F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 29d6487a26b2fb6a471cde52c37ffee7c27ed6a91914b308c247e0706f454ffb F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 8ec510fee735c646fb18a3b99f0ca5ca461f9e066c43cdc404d7144f12ae6ed6
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c b0babf8435f31d21f28454fb81433aa538c68b23d0a4a251f0666fdec4e71f59 F ext/wasm/api/sqlite3-wasm.c 97034ab4f40ec1fac71ccfaf3afffdca6b1ea2dcd95b7871527bad0f34e152b0
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54 F ext/wasm/api/sqlite3-worker1.js 1e54ea3d540161bcfb2100368a2fc0cad871a207b8336afee1c445715851ec54
F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8 F ext/wasm/batch-runner.html 4deeed44fe41496dc6898d9fb17938ea3291f40f4bfb977e29d0cef96fbbe4c8
F ext/wasm/batch-runner.js 49609e89aaac9989d6c1ad3fae268e4878e1ad7bc5fd3e5c2f44959660780b2e F ext/wasm/batch-runner.js 49609e89aaac9989d6c1ad3fae268e4878e1ad7bc5fd3e5c2f44959660780b2e
F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c0779 F ext/wasm/c-pp.c 92285f7bce67ed7b7020b40fde8ed0982c442b63dc33df9dfd4b658d4a6c0779
F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b F ext/wasm/common/SqliteTestUtil.js d8bf97ecb0705a2299765c8fc9e11b1a5ac7f10988bbf375a6558b7ca287067b
F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f F ext/wasm/common/emscripten.css 11bd104b6c0d597c67d40cc8ecc0a60dae2b965151e3b6a37fa5708bac3acd15
F ext/wasm/common/testing.css 35889709547d89a6109ff83b25c11bbc91d8dd43aab8722e428655ca98880a06 F ext/wasm/common/testing.css 0ff15602a3ab2bad8aef2c3bd120c7ee3fd1c2054ad2ace7e214187ae68d926f
F ext/wasm/common/whwasmutil.js c1bc5715cd96728929cc31d788b16152ccbd6b2e111d2e88fbc9725247e67b4f F ext/wasm/common/whwasmutil.js f0a742270b490748b9fdb0974287429b036698609b40eee81f13fe13e64358a7
F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed F ext/wasm/demo-123-worker.html a0b58d9caef098a626a1a1db567076fca4245e8d60ba94557ede8684350a81ed
F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508 F ext/wasm/demo-123.html 8c70a412ce386bd3796534257935eb1e3ea5c581e5d5aea0490b8232e570a508
F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6 F ext/wasm/demo-123.js ebae30756585bca655b4ab2553ec9236a87c23ad24fc8652115dcedb06d28df6
@ -539,23 +539,23 @@ F ext/wasm/fiddle/fiddle.js 974b995119ac443685d7d94d3b3c58c6a36540e9eb3fed7069d5
F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2 F ext/wasm/fiddle/index.html 5daf54e8f3d7777cbb1ca4f93affe28858dbfff25841cb4ab81d694efed28ec2
F ext/wasm/index-dist.html c806b6005145b71d64240606e9c6e0bf56878ee8829c66fe7486cebf34b0e6b1 F ext/wasm/index-dist.html c806b6005145b71d64240606e9c6e0bf56878ee8829c66fe7486cebf34b0e6b1
F ext/wasm/index.html f151b7c7b5cfdc066567d556acd168e769efd4e982286dc5f849a5ee69ecd0ff F ext/wasm/index.html f151b7c7b5cfdc066567d556acd168e769efd4e982286dc5f849a5ee69ecd0ff
F ext/wasm/jaccwabyt/jaccwabyt.js 95f573de1826474c9605dda620ee622fcb1673ae74f191eb324c0853aa4dcb66 F ext/wasm/jaccwabyt/jaccwabyt.js 06f2ef1ad640c26c593def3d960336e9bb789819b920516480895c38ed5f58fa
F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e F ext/wasm/jaccwabyt/jaccwabyt.md 37911f00db12cbcca73aa1ed72594430365f30aafae2fa9c886961de74e5e0eb
F ext/wasm/module-symbols.html 980680c8acfa3c8ae6a5aa223512d1b8e78040ced20f8ba2c382129bc73ec028 F ext/wasm/module-symbols.html 980680c8acfa3c8ae6a5aa223512d1b8e78040ced20f8ba2c382129bc73ec028
F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06 F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18 F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18
F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0 F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0
F ext/wasm/speedtest1-worker.html e94cecebcbb9d187647025edd04d37af9789dfba98c2cc439b549b5ae8a8bc93 F ext/wasm/speedtest1-worker.html fe6b36a63de1012bb9fb4d2fb888b6de9c589c21b0aa3ae054459b0093e077bf
F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1 F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1
F ext/wasm/speedtest1.html e4c4e5c1c8ec1ad13c995e346e4216a1df152fd2c5cd17e0793b865b2f3c5000 F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da
F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0 F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5 F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555e685bce3da8c3f
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
F ext/wasm/tester1-worker.html 29b1d87f7d51f70d61645719fee657f3787fe939bb695f27034c75404e8f1e6f F ext/wasm/tester1-worker.html d43f3c131d88f10d00aff3e328fed13c858d674ea2ff1ff90225506137f85aa9
F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399 F ext/wasm/tester1.c-pp.html d34bef3d48e5cbc1c7c06882ad240fec49bf88f5f65696cc2c72c416933aa406
F ext/wasm/tester1.c-pp.js d096a8fadfd27caa680a4311b1d529551f8fe885a63dd27457c87b6008c64632 F ext/wasm/tester1.c-pp.js ecd0ecd6c9f0c5bce7f39a3ccb28151c7ae7b1042379a0c855e930661579f2e8
F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70 F ext/wasm/tests/opfs/concurrency/index.html 86d8ac435074d1e7007b91105f4897f368c165e8cecb6a9aa3d81f5cf5dcbe70
F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d F ext/wasm/tests/opfs/concurrency/test.js a98016113eaf71e81ddbf71655aa29b0fed9a8b79a3cdd3620d1658eb1cc9a5d
F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@ -2067,8 +2067,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 212927e97e7be7d237de08359dce0dfb9211ac406b32009a6e15afd79c006475 P 53dcb81b029e4422bdc0b5cf694183854b997195427d437b6154ed4dad7ad0ba 51e3c3b569dce2097063d39dc484f44b2f98cad0a902ef66765ca4cdc3e06f47
R fada471bb8a33625833c6790242167a1 R 8761e73b7826ed273f62994737b011e2
T +closed 51e3c3b569dce2097063d39dc484f44b2f98cad0a902ef66765ca4cdc3e06f47 Closed\sby\sintegrate-merge.
U stephan U stephan
Z f05b54503032cba875652db3d0ee8337 Z b6d983dab7ab65c0caebb5e5bbac6324
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
53dcb81b029e4422bdc0b5cf694183854b997195427d437b6154ed4dad7ad0ba a1454744c770a30a32a6d7b7fc59ef7be48cf67348b238540592850d7c2c7757