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:
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
@ -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'))
|
||||||
|
599
ext/wasm/api/sqlite3-v-helper.js
Normal file
599
ext/wasm/api/sqlite3-v-helper.js
Normal 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()*/);
|
@ -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()*/);
|
|
@ -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){
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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; }
|
||||||
|
@ -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;}
|
||||||
|
@ -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) }
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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>
|
||||||
|
@ -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._;
|
||||||
|
@ -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>
|
||||||
|
@ -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'>
|
||||||
|
@ -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 */
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
51
manifest
51
manifest
@ -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.
|
||||||
|
@ -1 +1 @@
|
|||||||
53dcb81b029e4422bdc0b5cf694183854b997195427d437b6154ed4dad7ad0ba
|
a1454744c770a30a32a6d7b7fc59ef7be48cf67348b238540592850d7c2c7757
|
Reference in New Issue
Block a user