mirror of https://github.com/sqlite/sqlite
Merge in wasm-cleanups branch, reorganizing and updating the wasm-related components.
FossilOrigin-Name: c072594d3de3d6893c5d4a9d68439b84d043325f105b0d065575765a6e66c196
This commit is contained in:
commit
9289c47df7
82
Makefile.in
82
Makefile.in
|
@ -1524,67 +1524,51 @@ sqlite3.dll: $(REAL_LIBOBJ) sqlite3.def
|
|||
#
|
||||
# fiddle/wasm section
|
||||
#
|
||||
fiddle_dir = ext/fiddle
|
||||
fiddle_dir_abs = $(TOP)/$(fiddle_dir)
|
||||
wasm_dir = ext/wasm
|
||||
wasm_dir_abs = $(TOP)/ext/wasm
|
||||
# ^^^ some emcc opts require absolute paths
|
||||
fiddle_html = $(fiddle_dir)/fiddle.html
|
||||
fiddle_dir = $(wasm_dir)/fiddle
|
||||
fiddle_dir_abs = $(TOP)/$(fiddle_dir)
|
||||
fiddle_module_js = $(fiddle_dir)/fiddle-module.js
|
||||
sqlite3_wasm_js = $(fiddle_dir)/sqlite3.js
|
||||
sqlite3_wasm = $(fiddle_dir)/sqlite3.wasm
|
||||
#emcc_opt = -O0
|
||||
#emcc_opt = -O1
|
||||
#emcc_opt = -O2
|
||||
#emcc_opt = -O3
|
||||
emcc_opt = -Oz
|
||||
emcc_flags = $(emcc_opt) -sALLOW_TABLE_GROWTH -sSTRICT_JS \
|
||||
-sENVIRONMENT=web -sMODULARIZE \
|
||||
-sEXPORTED_RUNTIME_METHODS=@$(fiddle_dir_abs)/EXPORTED_RUNTIME_METHODS \
|
||||
emcc_flags = $(emcc_opt) \
|
||||
-sALLOW_TABLE_GROWTH \
|
||||
-sABORTING_MALLOC \
|
||||
-sSTRICT_JS \
|
||||
-sENVIRONMENT=web \
|
||||
-sMODULARIZE \
|
||||
-sEXPORTED_RUNTIME_METHODS=@$(wasm_dir_abs)/EXPORTED_RUNTIME_METHODS.fiddle \
|
||||
-sDYNAMIC_EXECUTION=0 \
|
||||
-I. $(SHELL_OPT)
|
||||
--minify 0 \
|
||||
-I. $(SHELL_OPT) \
|
||||
-DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_UTF16 -DSQLITE_OMIT_DEPRECATED
|
||||
$(fiddle_module_js): Makefile sqlite3.c shell.c \
|
||||
$(fiddle_dir)/EXPORTED_RUNTIME_METHODS \
|
||||
$(fiddle_dir)/EXPORTED_FUNCTIONS.fiddle
|
||||
$(wasm_dir)/EXPORTED_RUNTIME_METHODS.fiddle \
|
||||
$(wasm_dir)/EXPORTED_FUNCTIONS.fiddle
|
||||
emcc -o $@ $(emcc_flags) \
|
||||
-sEXPORT_NAME=initFiddleModule \
|
||||
-sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.fiddle \
|
||||
-sEXPORTED_FUNCTIONS=@$(wasm_dir_abs)/EXPORTED_FUNCTIONS.fiddle \
|
||||
-DSQLITE_SHELL_FIDDLE \
|
||||
sqlite3.c shell.c
|
||||
gzip < $@ > $@.gz
|
||||
gzip < $(fiddle_dir)/fiddle-module.wasm > $(fiddle_dir)/fiddle-module.wasm.gz
|
||||
$(sqlite3_wasm_js): Makefile sqlite3.c \
|
||||
$(fiddle_dir)/sqlite3-api.js \
|
||||
$(fiddle_dir)/EXPORTED_RUNTIME_METHODS \
|
||||
$(fiddle_dir)/EXPORTED_FUNCTIONS.sqlite3-api
|
||||
emcc -o $@ $(emcc_flags) \
|
||||
-sEXPORT_NAME=initSqlite3Module \
|
||||
-sEXPORTED_FUNCTIONS=@$(fiddle_dir_abs)/EXPORTED_FUNCTIONS.sqlite3-api \
|
||||
--post-js=$(fiddle_dir)/sqlite3-api.js \
|
||||
--no-entry \
|
||||
sqlite3.c
|
||||
gzip < $@ > $@.gz
|
||||
gzip < $(sqlite3_wasm) > $(sqlite3_wasm).gz
|
||||
gzip < $(fiddle_dir)/sqlite3-api.js > $(fiddle_dir)/sqlite3-api.js.gz
|
||||
$(fiddle_dir)/fiddle.js.gz: $(fiddle_dir)/fiddle.js
|
||||
gzip < $< > $@
|
||||
$(fiddle_dir)/sqlite3-api.js.gz: $(fiddle_dir)/sqlite3-api.js
|
||||
gzip < $< > $@
|
||||
|
||||
fiddle_generated = $(fiddle_module_js) $(fiddle_module_js).gz \
|
||||
$(fiddle_dir)/fiddle-module.wasm \
|
||||
$(fiddle_dir)/fiddle-module.wasm.gz \
|
||||
$(fiddle_dir)/fiddle.js.gz
|
||||
sqlite3_wasm_generated = \
|
||||
$(sqlite3_wasm) $(sqlite3_wasm).gz \
|
||||
$(sqlite3_wasm_js) $(sqlite3_wasm_js).gz \
|
||||
$(fiddle_dir)/sqlite3.js.gz \
|
||||
$(fiddle_dir)/sqlite3-api.js.gz
|
||||
|
||||
clean-wasm:
|
||||
rm -f $(fiddle_generated) $(sqlite3_wasm_generated)
|
||||
clean: clean-wasm
|
||||
clean-fiddle:
|
||||
rm -f $(fiddle_generated)
|
||||
clean: clean-fiddle
|
||||
fiddle: $(fiddle_module_js) $(fiddle_dir)/fiddle.js.gz
|
||||
sqlite3-wasm: $(sqlite3_wasm_js)
|
||||
wasm: fiddle sqlite3-wasm
|
||||
wasm: fiddle
|
||||
########################################################################
|
||||
# Explanation of the emcc build flags follows. Full docs for these can
|
||||
# be found at:
|
||||
|
@ -1598,7 +1582,7 @@ wasm: fiddle sqlite3-wasm
|
|||
#
|
||||
# -sMODULARIZE: changes how the generated code is structured to avoid
|
||||
# declaring a global Module object and instead installing a function
|
||||
# which loads and initialized the module. The function is named...
|
||||
# which loads and initializes the module. The function is named...
|
||||
#
|
||||
# -sEXPORT_NAME=jsFunctionName (see -sMODULARIZE)
|
||||
#
|
||||
|
@ -1618,7 +1602,15 @@ wasm: fiddle sqlite3-wasm
|
|||
# results in build errors.
|
||||
#
|
||||
# -sALLOW_TABLE_GROWTH is required for (at a minimum) the UDF-binding
|
||||
# feature.
|
||||
# feature. Without it, JS functions cannot be made to proxy C-side
|
||||
# callbacks.
|
||||
#
|
||||
# -sABORTING_MALLOC causes the JS-bound _malloc() to abort rather than
|
||||
# return 0 on OOM. If set to 0 then all code which uses _malloc()
|
||||
# must, just like in C, check the result before using it, else
|
||||
# they're likely to corrupt the JS/WASM heap by writing to its
|
||||
# address of 0. It is, as of this writing, enabled in Emscripten by
|
||||
# default but we enable it explicitly in case that default changes.
|
||||
#
|
||||
# -sDYNAMIC_EXECUTION=0 disables eval() and the Function constructor.
|
||||
# If the build runs without these, it's preferable to use this flag
|
||||
|
@ -1650,4 +1642,16 @@ wasm: fiddle sqlite3-wasm
|
|||
# primarily because -Oz will shrink the wasm file notably. JS-side
|
||||
# minification makes little difference in terms of overall
|
||||
# distributable size.
|
||||
#
|
||||
# --minify 0: disables minification of the generated JS code,
|
||||
# regardless of optimization level. Minification of the JS has
|
||||
# minimal overall effect in the larger scheme of things and results
|
||||
# in JS files which can neither be edited nor viewed as text files in
|
||||
# Fossil (which flags them as binary because of their extreme line
|
||||
# lengths). Interestingly, whether or not the comments in the
|
||||
# generated JS file get stripped is unaffected by this setting and
|
||||
# depends entirely on the optimization level. Higher optimization
|
||||
# levels reduce the size of the JS considerably even without
|
||||
# minification.
|
||||
#
|
||||
########################################################################
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
# This GNU makefile exists primarily to simplify/speed up development
|
||||
# from emacs. It is not part of the canonical build process.
|
||||
default:
|
||||
$(MAKE) -C ../.. wasm -e emcc_opt=-O0
|
||||
|
||||
clean:
|
||||
$(MAKE) -C ../../ clean-wasm
|
||||
|
||||
fiddle_files = emscripten.css fiddle.html \
|
||||
fiddle.js fiddle-module.js \
|
||||
fiddle-module.wasm fiddle-worker.js \
|
||||
$(wildcard *.wasm.gz) $(wildcard *.js.gz)
|
||||
|
||||
# fiddle_remote is the remote destination for the fiddle app. It
|
||||
# must be a [user@]HOST:/path for rsync.
|
||||
# Note that the target "should probably" contain a symlink of
|
||||
# index.html -> fiddle.html.
|
||||
fiddle_remote ?=
|
||||
ifeq (,$(fiddle_remote))
|
||||
ifneq (,$(wildcard /home/stephan))
|
||||
fiddle_remote = wh2:www/wh/sqlite3/.
|
||||
else ifneq (,$(wildcard /home/drh))
|
||||
#fiddle_remote = if appropriate, add that user@host:/path here
|
||||
endif
|
||||
endif
|
||||
|
||||
$(fiddle_files): default
|
||||
|
||||
push-fiddle: $(fiddle_files)
|
||||
@if [ x = "x$(fiddle_remote)" ]; then \
|
||||
echo "fiddle_remote must be a [user@]HOST:/path for rsync"; \
|
||||
exit 1; \
|
||||
fi
|
||||
rsync -va $(fiddle_files) $(fiddle_remote)
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
2022-05-22
|
||||
|
||||
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 contains bootstrapping code used by various test scripts
|
||||
which live in this file's directory.
|
||||
*/
|
||||
(function(){
|
||||
/* querySelectorAll() proxy */
|
||||
const EAll = function(/*[element=document,] cssSelector*/){
|
||||
return (arguments.length>1 ? arguments[0] : document)
|
||||
.querySelectorAll(arguments[arguments.length-1]);
|
||||
};
|
||||
/* querySelector() proxy */
|
||||
const E = function(/*[element=document,] cssSelector*/){
|
||||
return (arguments.length>1 ? arguments[0] : document)
|
||||
.querySelector(arguments[arguments.length-1]);
|
||||
};
|
||||
|
||||
/**
|
||||
Helpers for writing sqlite3-specific tests.
|
||||
*/
|
||||
self/*window or worker*/.SqliteTestUtil = {
|
||||
/** Running total of the number of tests run via
|
||||
this API. */
|
||||
counter: 0,
|
||||
/**
|
||||
If expr is a function, it is called and its result
|
||||
is returned, coerced to a bool, else expr, coerced to
|
||||
a bool, is returned.
|
||||
*/
|
||||
toBool: function(expr){
|
||||
return (expr instanceof Function) ? !!expr() : !!expr;
|
||||
},
|
||||
/** abort() if expr is false. If expr is a function, it
|
||||
is called and its result is evaluated.
|
||||
*/
|
||||
assert: function f(expr, msg){
|
||||
if(!f._){
|
||||
f._ = ('undefined'===typeof abort
|
||||
? (msg)=>{throw new Error(msg)}
|
||||
: abort);
|
||||
}
|
||||
++this.counter;
|
||||
if(!this.toBool(expr)){
|
||||
f._(msg || "Assertion failed.");
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/** Identical to assert() but throws instead of calling
|
||||
abort(). */
|
||||
affirm: function(expr, msg){
|
||||
++this.counter;
|
||||
if(!this.toBool(expr)) throw new Error(msg || "Affirmation failed.");
|
||||
return this;
|
||||
},
|
||||
/** Calls f() and squelches any exception it throws. If it
|
||||
does not throw, this function throws. */
|
||||
mustThrow: function(f, msg){
|
||||
++this.counter;
|
||||
let err;
|
||||
try{ f(); } catch(e){err=e;}
|
||||
if(!err) throw new Error(msg || "Expected exception.");
|
||||
return this;
|
||||
},
|
||||
/** Throws if expr is truthy or expr is a function and expr()
|
||||
returns truthy. */
|
||||
throwIf: function(expr, msg){
|
||||
++this.counter;
|
||||
if(this.toBool(expr)) throw new Error(msg || "throwIf() failed");
|
||||
return this;
|
||||
},
|
||||
/** Throws if expr is falsy or expr is a function and expr()
|
||||
returns falsy. */
|
||||
throwUnless: function(expr, msg){
|
||||
++this.counter;
|
||||
if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
This is a module object for use with the emscripten-installed
|
||||
initSqlite3Module() factory function.
|
||||
*/
|
||||
self.sqlite3TestModule = {
|
||||
postRun: [
|
||||
/* function(theModule){...} */
|
||||
],
|
||||
//onRuntimeInitialized: function(){},
|
||||
/* Proxy for C-side stdout output. */
|
||||
print: function(){
|
||||
console.log.apply(console, Array.prototype.slice.call(arguments));
|
||||
},
|
||||
/* Proxy for C-side stderr output. */
|
||||
printErr: function(){
|
||||
console.error.apply(console, Array.prototype.slice.call(arguments));
|
||||
},
|
||||
/**
|
||||
Called by the module init bits to report loading
|
||||
progress. It gets passed an empty argument when loading is
|
||||
done (after onRuntimeInitialized() and any this.postRun
|
||||
callbacks have been run).
|
||||
*/
|
||||
setStatus: function f(text){
|
||||
if(!f.last){
|
||||
f.last = { text: '', step: 0 };
|
||||
f.ui = {
|
||||
status: E('#module-status'),
|
||||
progress: E('#module-progress'),
|
||||
spinner: E('#module-spinner')
|
||||
};
|
||||
}
|
||||
if(text === f.last.text) return;
|
||||
f.last.text = text;
|
||||
if(f.ui.progress){
|
||||
f.ui.progress.value = f.last.step;
|
||||
f.ui.progress.max = f.last.step + 1;
|
||||
}
|
||||
++f.last.step;
|
||||
if(text) {
|
||||
f.ui.status.classList.remove('hidden');
|
||||
f.ui.status.innerText = text;
|
||||
}else{
|
||||
if(f.ui.progress){
|
||||
f.ui.progress.remove();
|
||||
f.ui.spinner.remove();
|
||||
delete f.ui.progress;
|
||||
delete f.ui.spinner;
|
||||
}
|
||||
f.ui.status.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
})(self/*window or worker*/);
|
File diff suppressed because it is too large
Load Diff
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
2022-05-23
|
||||
|
||||
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 is a JS Worker file for the main sqlite3 api. It loads
|
||||
sqlite3.js, initializes the module, and postMessage()'s a message
|
||||
after the module is initialized:
|
||||
|
||||
{type: 'sqlite3-api', data: 'ready'}
|
||||
|
||||
This seemingly superfluous level of indirection is necessary when
|
||||
loading sqlite3.js via a Worker. Loading sqlite3.js from the main
|
||||
window thread elides the Worker-specific API. Instantiating a worker
|
||||
with new Worker("sqlite.js") will not (cannot) call
|
||||
initSqlite3Module() to initialize the module due to a
|
||||
timing/order-of-operations conflict (and that symbol is not exported
|
||||
in a way that a Worker loading it that way can see it). Thus JS
|
||||
code wanting to load the sqlite3 Worker-specific API needs to pass
|
||||
_this_ file (or equivalent) to the Worker constructor and then
|
||||
listen for an event in the form shown above in order to know when
|
||||
the module has completed initialization. sqlite3.js will fire a
|
||||
similar event, with data:'loaded' as the final step in its loading
|
||||
process. Whether or not we _really_ need both 'loaded' and 'ready'
|
||||
events is unclear, but they are currently separate events primarily
|
||||
for the sake of clarity in the timing of when it's okay to use the
|
||||
loaded module. At the time the 'loaded' event is fired, it's
|
||||
possible (but unknown and unknowable) that the emscripten-generated
|
||||
module-setup infrastructure still has work to do. Thus it is
|
||||
hypothesized that client code is better off waiting for the 'ready'
|
||||
even before using the API.
|
||||
*/
|
||||
"use strict";
|
||||
importScripts('sqlite3.js');
|
||||
initSqlite3Module().then(function(){
|
||||
setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'ready'}), 0);
|
||||
});
|
|
@ -1,185 +0,0 @@
|
|||
/*
|
||||
2022-05-22
|
||||
|
||||
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 basic test script for sqlite3-api.js. This file must be run in
|
||||
main JS thread and sqlite3.js must have been loaded before it.
|
||||
*/
|
||||
(function(){
|
||||
const T = self.SqliteTestUtil;
|
||||
const log = console.log.bind(console);
|
||||
|
||||
const assert = function(condition, text) {
|
||||
if (!condition) {
|
||||
throw new Error('Assertion failed' + (text ? ': ' + text : ''));
|
||||
}
|
||||
};
|
||||
|
||||
const test1 = function(db,sqlite3){
|
||||
const api = sqlite3.api;
|
||||
log("Basic sanity tests...");
|
||||
T.assert(db._pDb);
|
||||
let st = db.prepare("select 3 as a");
|
||||
//log("statement =",st);
|
||||
T.assert(st._pStmt)
|
||||
.assert(!st._mayGet)
|
||||
.assert('a' === st.getColumnName(0))
|
||||
.assert(st === db._statements[st._pStmt])
|
||||
.assert(1===st.columnCount)
|
||||
.assert(0===st.parameterCount)
|
||||
.mustThrow(()=>st.bind(1,null))
|
||||
.assert(true===st.step())
|
||||
.assert(3 === st.get(0))
|
||||
.mustThrow(()=>st.get(1))
|
||||
.mustThrow(()=>st.get(0,~api.SQLITE_INTEGER))
|
||||
.assert(3 === st.get(0,api.SQLITE_INTEGER))
|
||||
.assert(3 === st.getInt(0))
|
||||
.assert('3' === st.get(0,api.SQLITE_TEXT))
|
||||
.assert('3' === st.getString(0))
|
||||
.assert(3.0 === st.get(0,api.SQLITE_FLOAT))
|
||||
.assert(3.0 === st.getFloat(0))
|
||||
.assert(st.get(0,api.SQLITE_BLOB) instanceof Uint8Array)
|
||||
.assert(st.getBlob(0) instanceof Uint8Array)
|
||||
.assert(3 === st.get([])[0])
|
||||
.assert(3 === st.get({}).a)
|
||||
.assert(3 === st.getJSON(0))
|
||||
.assert(st._mayGet)
|
||||
.assert(false===st.step())
|
||||
.assert(!st._mayGet)
|
||||
;
|
||||
let pId = st._pStmt;
|
||||
st.finalize();
|
||||
T.assert(!st._pStmt)
|
||||
.assert(!db._statements[pId]);
|
||||
|
||||
let list = [];
|
||||
db.exec({
|
||||
sql:`CREATE TABLE t(a,b);
|
||||
INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
|
||||
multi: true,
|
||||
saveSql: list,
|
||||
bind: [5,6]
|
||||
});
|
||||
T.assert(2 === list.length);
|
||||
//log("Exec'd SQL:", list);
|
||||
let counter = 0, colNames = [];
|
||||
list.length = 0;
|
||||
db.exec("SELECT a a, b b FROM t",{
|
||||
rowMode: 'object',
|
||||
resultRows: list,
|
||||
columnNames: colNames,
|
||||
callback: function(row,stmt){
|
||||
++counter;
|
||||
T.assert(row.a%2 && row.a<6);
|
||||
}
|
||||
});
|
||||
T.assert(2 === colNames.length)
|
||||
.assert('a' === colNames[0])
|
||||
.assert(3 === counter)
|
||||
.assert(3 === list.length);
|
||||
list.length = 0;
|
||||
db.exec("SELECT a a, b b FROM t",{
|
||||
rowMode: 'array',
|
||||
callback: function(row,stmt){
|
||||
++counter;
|
||||
T.assert(Array.isArray(row))
|
||||
.assert(0===row[1]%2 && row[1]<7);
|
||||
}
|
||||
});
|
||||
T.assert(6 === counter);
|
||||
};
|
||||
|
||||
const testUDF = function(db){
|
||||
log("Testing UDF...");
|
||||
db.createFunction("foo",function(a,b){return a+b});
|
||||
T.assert(7===db.selectValue("select foo(3,4)")).
|
||||
assert(5===db.selectValue("select foo(3,?)",2)).
|
||||
assert(5===db.selectValue("select foo(?,?)",[1,4])).
|
||||
assert(5===db.selectValue("select foo($a,$b)",{$a:0,$b:5}));
|
||||
db.createFunction("bar", {
|
||||
arity: -1,
|
||||
callback: function(){
|
||||
var rc = 0;
|
||||
for(let i = 0; i < arguments.length; ++i) rc += arguments[i];
|
||||
return rc;
|
||||
}
|
||||
});
|
||||
|
||||
log("Testing DB::selectValue() w/ UDF...");
|
||||
T.assert(0===db.selectValue("select bar()")).
|
||||
assert(1===db.selectValue("select bar(1)")).
|
||||
assert(3===db.selectValue("select bar(1,2)")).
|
||||
assert(-1===db.selectValue("select bar(1,2,-4)"));
|
||||
|
||||
const eqApprox = function(v1,v2,factor=0.05){
|
||||
return v1>=(v2-factor) && v1<=(v2+factor);
|
||||
};
|
||||
|
||||
T.assert('hi' === db.selectValue("select ?",'hi')).
|
||||
assert(null===db.selectValue("select null")).
|
||||
assert(null === db.selectValue("select ?",null)).
|
||||
assert(null === db.selectValue("select ?",[null])).
|
||||
assert(null === db.selectValue("select $a",{$a:null})).
|
||||
assert(eqApprox(3.1,db.selectValue("select 3.0 + 0.1")))
|
||||
;
|
||||
};
|
||||
|
||||
const testAttach = function(db){
|
||||
log("Testing ATTACH...");
|
||||
db.exec({
|
||||
sql:[
|
||||
"attach 'foo.db' as foo",
|
||||
"create table foo.bar(a)",
|
||||
"insert into foo.bar(a) values(1),(2),(3)"
|
||||
].join(';'),
|
||||
multi: true
|
||||
});
|
||||
T.assert(2===db.selectValue('select a from foo.bar where a>1 order by a'));
|
||||
db.exec("detach foo");
|
||||
T.mustThrow(()=>db.exec("select * from foo.bar"));
|
||||
};
|
||||
|
||||
const runTests = function(Module){
|
||||
T.assert(Module._free instanceof Function).
|
||||
assert(Module.allocate instanceof Function).
|
||||
assert(Module.addFunction instanceof Function).
|
||||
assert(Module.removeFunction instanceof Function);
|
||||
const sqlite3 = Module.sqlite3;
|
||||
const api = sqlite3.api;
|
||||
const oo = sqlite3.SQLite3;
|
||||
console.log("Loaded module:",api.sqlite3_libversion(),
|
||||
api.sqlite3_sourceid());
|
||||
log("Build options:",oo.compileOptionUsed());
|
||||
const db = new oo.DB();
|
||||
try {
|
||||
log("DB:",db.filename);
|
||||
[
|
||||
test1, testUDF, testAttach
|
||||
].forEach((f)=>{
|
||||
const t = T.counter;
|
||||
f(db, sqlite3);
|
||||
log("Test count:",T.counter - t);
|
||||
});
|
||||
}finally{
|
||||
db.close();
|
||||
}
|
||||
log("Total Test count:",T.counter);
|
||||
};
|
||||
|
||||
initSqlite3Module(self.sqlite3TestModule).then(function(theModule){
|
||||
/** Use a timeout so that we are (hopefully) out from
|
||||
under the module init stack when our setup gets
|
||||
run. Just on principle, not because we _need_ to
|
||||
be. */
|
||||
//console.debug("theModule =",theModule);
|
||||
setTimeout(()=>runTests(theModule), 0);
|
||||
});
|
||||
})();
|
|
@ -1,235 +0,0 @@
|
|||
/*
|
||||
2022-05-22
|
||||
|
||||
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 basic test script for sqlite3-worker.js.
|
||||
*/
|
||||
(function(){
|
||||
const T = self.SqliteTestUtil;
|
||||
const SW = new Worker("sqlite3-worker.js");
|
||||
/** Posts a worker message as {type:type, data:data}. */
|
||||
const wMsg = function(type,data){
|
||||
SW.postMessage({type, data});
|
||||
return SW;
|
||||
};
|
||||
const log = console.log.bind(console);
|
||||
const warn = console.warn.bind(console);
|
||||
const error = console.error.bind(console);
|
||||
|
||||
SW.onerror = function(event){
|
||||
error("onerror",event);
|
||||
};
|
||||
|
||||
/**
|
||||
A queue for callbacks which are to be run in response to async
|
||||
DB commands. See the notes in runTests() for why we need
|
||||
this. The event-handling plumbing of this file requires that
|
||||
any DB command which includes a `messageId` property also have
|
||||
a queued callback entry, as the existence of that property in
|
||||
response payloads is how it knows whether or not to shift an
|
||||
entry off of the queue.
|
||||
*/
|
||||
const MsgHandlerQueue = {
|
||||
queue: [],
|
||||
id: 0,
|
||||
push: function(type,callback){
|
||||
this.queue.push(callback);
|
||||
return type + '-' + (++this.id);
|
||||
},
|
||||
shift: function(){
|
||||
return this.queue.shift();
|
||||
}
|
||||
};
|
||||
|
||||
const testCount = ()=>log("Total test count:",T.counter);
|
||||
|
||||
const runOneTest = function(eventType, eventData, callback){
|
||||
T.assert(eventData && 'object'===typeof eventData);
|
||||
/* ^^^ that is for the testing and messageId-related code, not
|
||||
a hard requirement of all of the Worker-exposed APIs. */
|
||||
eventData.messageId = MsgHandlerQueue.push(eventType,function(ev){
|
||||
log("runOneTest",eventType,"result",ev.data);
|
||||
if(callback instanceof Function){
|
||||
callback(ev);
|
||||
testCount();
|
||||
}
|
||||
});
|
||||
wMsg(eventType, eventData);
|
||||
};
|
||||
|
||||
/** Methods which map directly to onmessage() event.type keys.
|
||||
They get passed the inbound event.data. */
|
||||
const dbMsgHandler = {
|
||||
open: function(ev){
|
||||
log("open result",ev.data);
|
||||
},
|
||||
exec: function(ev){
|
||||
log("exec result",ev.data);
|
||||
},
|
||||
export: function(ev){
|
||||
log("exec result",ev.data);
|
||||
},
|
||||
error: function(ev){
|
||||
error("ERROR from the worker:",ev.data);
|
||||
},
|
||||
resultRowTest1: function f(ev){
|
||||
if(undefined === f.counter) f.counter = 0;
|
||||
if(ev.data) ++f.counter;
|
||||
//log("exec() result row:",ev.data);
|
||||
T.assert(null===ev.data || 'number' === typeof ev.data.b);
|
||||
}
|
||||
};
|
||||
|
||||
const runTests = function(){
|
||||
const mustNotReach = ()=>{
|
||||
throw new Error("This is not supposed to be reached.");
|
||||
};
|
||||
/**
|
||||
"The problem" now is that the test results are async. We
|
||||
know, however, that the messages posted to the worker will
|
||||
be processed in the order they are passed to it, so we can
|
||||
create a queue of callbacks to handle them. The problem
|
||||
with that approach is that it's not error-handling
|
||||
friendly, in that an error can cause us to bypass a result
|
||||
handler queue entry. We have to perform some extra
|
||||
acrobatics to account for that.
|
||||
*/
|
||||
runOneTest('open', {filename:'testing2.sqlite3'}, function(ev){
|
||||
//log("open result",ev);
|
||||
T.assert('testing2.sqlite3'===ev.data.filename)
|
||||
.assert(ev.data.messageId);
|
||||
});
|
||||
runOneTest('exec',{
|
||||
sql: ["create table t(a,b)",
|
||||
"insert into t(a,b) values(1,2),(3,4),(5,6)"
|
||||
].join(';'),
|
||||
multi: true,
|
||||
resultRows: [], columnNames: []
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(0===ev.resultRows.length)
|
||||
.assert(0===ev.columnNames.length);
|
||||
});
|
||||
runOneTest('exec',{
|
||||
sql: 'select a a, b b from t order by a',
|
||||
resultRows: [], columnNames: [],
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(3===ev.resultRows.length)
|
||||
.assert(1===ev.resultRows[0][0])
|
||||
.assert(6===ev.resultRows[2][1])
|
||||
.assert(2===ev.columnNames.length)
|
||||
.assert('b'===ev.columnNames[1]);
|
||||
});
|
||||
runOneTest('exec',{
|
||||
sql: 'select a a, b b from t order by a',
|
||||
resultRows: [], columnNames: [],
|
||||
rowMode: 'object'
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(3===ev.resultRows.length)
|
||||
.assert(1===ev.resultRows[0].a)
|
||||
.assert(6===ev.resultRows[2].b)
|
||||
});
|
||||
runOneTest('exec',{sql:'intentional_error'}, mustNotReach);
|
||||
// Ensure that the message-handler queue survives ^^^ that error...
|
||||
runOneTest('exec',{
|
||||
sql:'select 1',
|
||||
resultRows: [],
|
||||
//rowMode: 'array', // array is the default in the Worker interface
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(1 === ev.resultRows.length)
|
||||
.assert(1 === ev.resultRows[0][0]);
|
||||
});
|
||||
runOneTest('exec',{
|
||||
sql: 'select a a, b b from t order by a',
|
||||
callback: 'resultRowTest1',
|
||||
rowMode: 'object'
|
||||
}, function(ev){
|
||||
T.assert(3===dbMsgHandler.resultRowTest1.counter);
|
||||
dbMsgHandler.resultRowTest1.counter = 0;
|
||||
});
|
||||
runOneTest('exec',{sql: 'delete from t where a>3'});
|
||||
runOneTest('exec',{
|
||||
sql: 'select count(a) from t',
|
||||
resultRows: []
|
||||
},function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(1===ev.resultRows.length)
|
||||
.assert(2===ev.resultRows[0][0]);
|
||||
});
|
||||
runOneTest('export',{}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert('string' === typeof ev.filename)
|
||||
.assert(ev.buffer instanceof Uint8Array)
|
||||
.assert(ev.buffer.length > 1024)
|
||||
.assert('application/x-sqlite3' === ev.mimetype);
|
||||
});
|
||||
|
||||
/***** close() tests must come last. *****/
|
||||
runOneTest('close',{unlink:true},function(ev){
|
||||
ev = ev.data;
|
||||
T.assert('string' === typeof ev.filename);
|
||||
});
|
||||
runOneTest('close',{unlink:true},function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(undefined === ev.filename);
|
||||
});
|
||||
};
|
||||
|
||||
SW.onmessage = function(ev){
|
||||
if(!ev.data || 'object'!==typeof ev.data){
|
||||
warn("Unknown sqlite3-worker message type:",ev);
|
||||
return;
|
||||
}
|
||||
ev = ev.data/*expecting a nested object*/;
|
||||
//log("main window onmessage:",ev);
|
||||
if(ev.data && ev.data.messageId){
|
||||
/* We're expecting a queued-up callback handler. */
|
||||
const f = MsgHandlerQueue.shift();
|
||||
if('error'===ev.type){
|
||||
dbMsgHandler.error(ev);
|
||||
return;
|
||||
}
|
||||
T.assert(f instanceof Function);
|
||||
f(ev);
|
||||
return;
|
||||
}
|
||||
switch(ev.type){
|
||||
case 'sqlite3-api':
|
||||
switch(ev.data){
|
||||
case 'loaded':
|
||||
log("Message:",ev); return;
|
||||
case 'ready':
|
||||
log("Message:",ev);
|
||||
self.sqlite3TestModule.setStatus(null);
|
||||
setTimeout(runTests, 0);
|
||||
return;
|
||||
default:
|
||||
warn("Unknown sqlite3-api message type:",ev);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
if(dbMsgHandler.hasOwnProperty(ev.type)){
|
||||
try{dbMsgHandler[ev.type](ev);}
|
||||
catch(err){
|
||||
error("Exception while handling db result message",
|
||||
ev,":",err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
warn("Unknown sqlite3-api message type:",ev);
|
||||
}
|
||||
};
|
||||
|
||||
log("Init complete, but async init bits may still be running.");
|
||||
})();
|
|
@ -1,14 +1,14 @@
|
|||
ALLOC_NORMAL
|
||||
FS
|
||||
UTF8ToString
|
||||
addFunction
|
||||
allocate
|
||||
allocateUTF8OnStack
|
||||
ccall
|
||||
cwrap
|
||||
getValue
|
||||
intArrayFromString
|
||||
lengthBytesUTF8
|
||||
removeFunction
|
||||
setValue
|
||||
stackAlloc
|
||||
stackRestore
|
||||
stackSave
|
||||
stringToUTF8Array
|
|
@ -0,0 +1,287 @@
|
|||
# This GNU makefile exists primarily to simplify/speed up development
|
||||
# of the sqlite3 WASM components. It is not part of the canonical
|
||||
# build process.
|
||||
#
|
||||
# Maintenance notes: the fiddle build is currently performed in the
|
||||
# top-level ../../Makefile.in. It may be moved into this file at some
|
||||
# point, as GNU Make has been deemed acceptable for the WASM-related
|
||||
# components (whereas POSIX Make is required for the more conventional
|
||||
# components).
|
||||
SHELL := $(shell which bash 2>/dev/null)
|
||||
all:
|
||||
|
||||
.PHONY: fiddle
|
||||
ifneq (,$(wildcard /home/stephan))
|
||||
fiddle_opt ?= -O0
|
||||
else
|
||||
fiddle_opt = -Os
|
||||
endif
|
||||
fiddle:
|
||||
$(MAKE) -C ../.. fiddle -e emcc_opt=$(fiddle_opt)
|
||||
|
||||
clean:
|
||||
$(MAKE) -C ../../ clean-fiddle
|
||||
-rm -f $(CLEAN_FILES)
|
||||
|
||||
MAKEFILE := $(lastword $(MAKEFILE_LIST))
|
||||
dir.top := ../..
|
||||
# Reminder: some Emscripten flags require absolute paths
|
||||
dir.wasm := $(patsubst %/,%,$(dir $(abspath $(MAKEFILE))))
|
||||
dir.api := api
|
||||
dir.jacc := jaccwabyt
|
||||
dir.common := common
|
||||
CLEAN_FILES := *~ $(dir.jacc)/*~ $(dir.api)/*~ $(dir.common)/*~
|
||||
|
||||
SQLITE_OPT = \
|
||||
-DSQLITE_ENABLE_FTS4 \
|
||||
-DSQLITE_ENABLE_RTREE \
|
||||
-DSQLITE_ENABLE_EXPLAIN_COMMENTS \
|
||||
-DSQLITE_ENABLE_UNKNOWN_SQL_FUNCTION \
|
||||
-DSQLITE_ENABLE_STMTVTAB \
|
||||
-DSQLITE_ENABLE_DBPAGE_VTAB \
|
||||
-DSQLITE_ENABLE_DBSTAT_VTAB \
|
||||
-DSQLITE_ENABLE_BYTECODE_VTAB \
|
||||
-DSQLITE_ENABLE_OFFSET_SQL_FUNC \
|
||||
-DSQLITE_OMIT_LOAD_EXTENSION \
|
||||
-DSQLITE_OMIT_DEPRECATED \
|
||||
-DSQLITE_OMIT_UTF16 \
|
||||
-DSQLITE_THREADSAFE=0
|
||||
#SQLITE_OPT += -DSQLITE_ENABLE_MEMSYS5
|
||||
$(dir.top)/sqlite3.c:
|
||||
$(MAKE) -C $(dir.top) sqlite3.c
|
||||
|
||||
# SQLITE_OMIT_LOAD_EXTENSION: if this is true, sqlite3_vfs::xDlOpen
|
||||
# and friends may be NULL.
|
||||
|
||||
emcc_opt ?= -O0
|
||||
.PHONY: release
|
||||
release:
|
||||
$(MAKE) 'emcc_opt=-Os -g3'
|
||||
# ^^^^^ target-specific vars, e.g.:
|
||||
# release: emcc_opt=...
|
||||
# apparently only work for file targets, not PHONY targets?
|
||||
#
|
||||
# ^^^^ -O3, -Oz, -Os minify symbol names and there appears to be no
|
||||
# way around that except to use -g3, but -g3 causes the binary file
|
||||
# size to absolutely explode (approx. 5x larger). This minification
|
||||
# utterly breaks the resulting module, making it unsable except as
|
||||
# self-contained/self-referential-only code, as ALL of the exported
|
||||
# symbols get minified names.
|
||||
#
|
||||
# However, we have an option for using -Oz or -Os:
|
||||
#
|
||||
# Build with (-Os -g3) or (-Oz -g3) then use wasm-strip, from the wabt
|
||||
# tools package (https://github.com/WebAssembly/wabt), to strip the
|
||||
# debugging symbols. That results in a small build with unmangled
|
||||
# symbol names. -Oz gives ever-so-slightly better compression than
|
||||
# -Os: not quite 1% in some completely unscientific tests. Runtime
|
||||
# speed for the unit tests is all over the place either way so it's
|
||||
# difficult to say whether -Os gives any speed benefit over -Oz.
|
||||
########################################################################
|
||||
|
||||
# Emscripten SDK home dir and related binaries...
|
||||
EMSDK_HOME ?= $(word 1,$(wildcard $(HOME)/src/emsdk $(HOME)/emsdk))
|
||||
emcc.bin ?= $(word 1,$(wildcard $(shell which emcc) $(EMSDK_HOME)/upstream/emscripten/emcc))
|
||||
ifeq (,$(emcc.bin))
|
||||
$(error Cannot find emcc.)
|
||||
endif
|
||||
|
||||
wasm-strip ?= $(shell which wasm-strip 2>/dev/null)
|
||||
ifeq (,$(filter clean,$(MAKECMDGOALS)))
|
||||
ifeq (,$(wasm-strip))
|
||||
$(info WARNING: *******************************************************************)
|
||||
$(info WARNING: builds using -O3/-Os/-Oz will minify WASM-exported names,)
|
||||
$(info WARNING: breaking _All The Things_. The workaround for that is to build)
|
||||
$(info WARNING: with -g3 (which explodes the file size) and then strip the debug)
|
||||
$(info WARNING: info after compilation, using wasm-strip, to shrink the wasm file.)
|
||||
$(info WARNING: wasm-strip was not found in the PATH so we cannot strip those.)
|
||||
$(info WARNING: If this build uses any optimization level higher than -O2 then)
|
||||
$(info WARNING: the ***resulting WASM binary WILL NOT BE USABLE***.)
|
||||
$(info WARNING: wasm-strip is part of the wabt package:)
|
||||
$(info WARNING: https://github.com/WebAssembly/wabt)
|
||||
$(info WARNING: on Ubuntu-like systems it can be installed with:)
|
||||
$(info WARNING: sudo apt install wabt)
|
||||
$(info WARNING: *******************************************************************)
|
||||
endif
|
||||
endif # 'make clean' check
|
||||
|
||||
ifeq (release,$(filter release,$(MAKECMDGOALS)))
|
||||
ifeq (,$(wasm-strip))
|
||||
$(error Cannot make release-quality binary because wasm-strip is not available. \
|
||||
See notes in the warning above)
|
||||
endif
|
||||
else
|
||||
$(info Development build. Use '$(MAKE) release' for a smaller release build.)
|
||||
endif
|
||||
|
||||
EXPORTED_FUNCTIONS.api.in := $(dir.api)/EXPORTED_FUNCTIONS.sqlite3-api \
|
||||
$(dir.jacc)/jaccwabyt_test.exports
|
||||
|
||||
EXPORTED_FUNCTIONS.api: $(EXPORTED_FUNCTIONS.api.in) $(MAKEFILE)
|
||||
cat $(EXPORTED_FUNCTIONS.api.in) > $@
|
||||
CLEAN_FILES += EXPORTED_FUNCTIONS.api
|
||||
|
||||
sqlite3-api.jses := \
|
||||
$(dir.api)/sqlite3-api-prologue.js \
|
||||
$(dir.common)/whwasmutil.js \
|
||||
$(dir.jacc)/jaccwabyt.js \
|
||||
$(dir.api)/sqlite3-api-glue.js \
|
||||
$(dir.api)/sqlite3-api-oo1.js \
|
||||
$(dir.api)/sqlite3-api-worker.js \
|
||||
$(dir.api)/sqlite3-api-opfs.js \
|
||||
$(dir.api)/sqlite3-api-cleanup.js
|
||||
|
||||
sqlite3-api.js := $(dir.api)/sqlite3-api.js
|
||||
CLEAN_FILES += $(sqlite3-api.js)
|
||||
$(sqlite3-api.js): $(sqlite3-api.jses) $(MAKEFILE)
|
||||
@echo "Making $@..."
|
||||
@for i in $(sqlite3-api.jses); do \
|
||||
echo "/* BEGIN FILE: $$i */"; \
|
||||
cat $$i; \
|
||||
echo "/* END FILE: $$i */"; \
|
||||
done > $@
|
||||
|
||||
post-js.js := $(dir.api)/post-js.js
|
||||
CLEAN_FILES += $(post-js.js)
|
||||
post-jses := \
|
||||
$(dir.api)/post-js-header.js \
|
||||
$(sqlite3-api.js) \
|
||||
$(dir.api)/post-js-footer.js
|
||||
|
||||
$(post-js.js): $(post-jses) $(MAKEFILE)
|
||||
@echo "Making $@..."
|
||||
@for i in $(post-jses); do \
|
||||
echo "/* BEGIN FILE: $$i */"; \
|
||||
cat $$i; \
|
||||
echo "/* END FILE: $$i */"; \
|
||||
done > $@
|
||||
|
||||
|
||||
########################################################################
|
||||
# emcc flags for .c/.o/.wasm.
|
||||
emcc.flags =
|
||||
#emcc.flags += -v # _very_ loud but also informative about what it's doing
|
||||
|
||||
########################################################################
|
||||
# emcc flags for .c/.o.
|
||||
emcc.cflags :=
|
||||
emcc.cflags += -std=c99 -fPIC
|
||||
# -------------^^^^^^^^ we currently need c99 for WASM-specific sqlite3 APIs.
|
||||
emcc.cflags += -I. -I$(dir.top) # $(SQLITE_OPT)
|
||||
|
||||
########################################################################
|
||||
# emcc flags specific to building the final .js/.wasm file...
|
||||
emcc.jsflags := -fPIC
|
||||
emcc.jsflags += --no-entry
|
||||
emcc.jsflags += -sENVIRONMENT=web
|
||||
emcc.jsflags += -sMODULARIZE
|
||||
emcc.jsflags += -sSTRICT_JS
|
||||
emcc.jsflags += -sDYNAMIC_EXECUTION=0
|
||||
emcc.jsflags += -sNO_POLYFILL
|
||||
emcc.jsflags += -sEXPORTED_FUNCTIONS=@$(dir.wasm)/EXPORTED_FUNCTIONS.api
|
||||
emcc.jsflags += -sEXPORTED_RUNTIME_METHODS=FS,wasmMemory # wasmMemory==>for -sIMPORTED_MEMORY
|
||||
emcc.jsflags += -sUSE_CLOSURE_COMPILER=0
|
||||
emcc.jsflags += -sIMPORTED_MEMORY
|
||||
#emcc.jsflags += -sINITIAL_MEMORY=13107200
|
||||
#emcc.jsflags += -sTOTAL_STACK=4194304
|
||||
emcc.jsflags += -sEXPORT_NAME=sqlite3InitModule
|
||||
emcc.jsflags += -sGLOBAL_BASE=4096 # HYPOTHETICALLY keep func table indexes from overlapping w/ heap addr.
|
||||
emcc.jsflags +=--post-js=$(post-js.js)
|
||||
#emcc.jsflags += -sSTRICT # fails due to missing __syscall_...()
|
||||
#emcc.jsflags += -sALLOW_UNIMPLEMENTED_SYSCALLS
|
||||
#emcc.jsflags += -sFILESYSTEM=0 # only for experimentation. sqlite3 needs the FS API
|
||||
#emcc.jsflags += -sABORTING_MALLOC
|
||||
emcc.jsflags += -sALLOW_MEMORY_GROWTH
|
||||
emcc.jsflags += -sALLOW_TABLE_GROWTH
|
||||
emcc.jsflags += -Wno-limited-postlink-optimizations
|
||||
# ^^^^^ it likes to warn when we have "limited optimizations" via the -g3 flag.
|
||||
#emcc.jsflags += -sMALLOC=emmalloc
|
||||
#emcc.jsflags += -sMALLOC=dlmalloc # a good 8k larger than emmalloc
|
||||
#emcc.jsflags += -sSTANDALONE_WASM # causes OOM errors, not sure why
|
||||
#emcc.jsflags += --import=foo_bar
|
||||
#emcc.jsflags += --no-gc-sections
|
||||
# https://lld.llvm.org/WebAssembly.html
|
||||
emcc.jsflags += -sERROR_ON_UNDEFINED_SYMBOLS=0
|
||||
emcc.jsflags += -sLLD_REPORT_UNDEFINED
|
||||
#emcc.jsflags += --allow-undefined
|
||||
emcc.jsflags += --import-undefined
|
||||
#emcc.jsflags += --unresolved-symbols=import-dynamic --experimental-pic
|
||||
#emcc.jsflags += --experimental-pic --unresolved-symbols=ingore-all --import-undefined
|
||||
#emcc.jsflags += --unresolved-symbols=ignore-all
|
||||
enable_bigint ?= 1
|
||||
ifneq (0,$(enable_bigint))
|
||||
emcc.jsflags += -sWASM_BIGINT
|
||||
endif
|
||||
emcc.jsflags += -sMEMORY64=0
|
||||
# ^^^^ MEMORY64=1 fails to load, erroring with:
|
||||
# invalid memory limits flags 0x5
|
||||
# (enable via --experimental-wasm-memory64)
|
||||
#
|
||||
# ^^^^ MEMORY64=2 builds and loads but dies when we do things like:
|
||||
#
|
||||
# new Uint8Array(heapWrappers().HEAP8U.buffer, ptr, n)
|
||||
#
|
||||
# because ptr is now a BigInt, so is invalid for passing to arguments
|
||||
# which have strict must-be-a-number requirements.
|
||||
########################################################################
|
||||
|
||||
|
||||
sqlite3.js := $(dir.api)/sqlite3.js
|
||||
sqlite3.wasm := $(dir.api)/sqlite3.wasm
|
||||
$(dir.api)/sqlite3-wasm.o: emcc.cflags += $(SQLITE_OPT)
|
||||
$(dir.api)/sqlite3-wasm.o: $(dir.top)/sqlite3.c
|
||||
$(dir.api)/wasm_util.o: emcc.cflags += $(SQLITE_OPT)
|
||||
sqlite3.wasm.c := $(dir.api)/sqlite3-wasm.c \
|
||||
$(dir.jacc)/jaccwabyt_test.c
|
||||
# ^^^ FIXME (how?): jaccwabyt_test.c is only needed for the test
|
||||
# apps. However, we want to test the release builds with those apps,
|
||||
# so we cannot simply elide that file in release builds. That
|
||||
# component is critical to the VFS bindings so needs to be tested
|
||||
# along with the core APIs.
|
||||
define WASM_C_COMPILE
|
||||
$(1).o := $$(subst .c,.o,$(1))
|
||||
sqlite3.wasm.obj += $$($(1).o)
|
||||
$$($(1).o): $$(MAKEFILE) $(1)
|
||||
$$(emcc.bin) $$(emcc_opt) $$(emcc.flags) $$(emcc.cflags) -c $(1) -o $$@
|
||||
CLEAN_FILES += $$($(1).o)
|
||||
endef
|
||||
$(foreach c,$(sqlite3.wasm.c),$(eval $(call WASM_C_COMPILE,$(c))))
|
||||
$(sqlite3.js):
|
||||
$(sqlite3.js): $(MAKEFILE) $(sqlite3.wasm.obj) \
|
||||
EXPORTED_FUNCTIONS.api \
|
||||
$(post-js.js)
|
||||
$(emcc.bin) -o $@ $(emcc_opt) $(emcc.flags) $(emcc.jsflags) $(sqlite3.wasm.obj)
|
||||
chmod -x $(sqlite3.wasm)
|
||||
ifneq (,$(wasm-strip))
|
||||
$(wasm-strip) $(sqlite3.wasm)
|
||||
endif
|
||||
@ls -la $@ $(sqlite3.wasm)
|
||||
|
||||
CLEAN_FILES += $(sqlite3.js) $(sqlite3.wasm)
|
||||
all: $(sqlite3.js)
|
||||
# End main Emscripten-based module build
|
||||
########################################################################
|
||||
|
||||
|
||||
########################################################################
|
||||
# fiddle_remote is the remote destination for the fiddle app. It
|
||||
# must be a [user@]HOST:/path for rsync.
|
||||
# Note that the target "should probably" contain a symlink of
|
||||
# index.html -> fiddle.html.
|
||||
fiddle_remote ?=
|
||||
ifeq (,$(fiddle_remote))
|
||||
ifneq (,$(wildcard /home/stephan))
|
||||
fiddle_remote = wh:www/wh/sqlite3/.
|
||||
else ifneq (,$(wildcard /home/drh))
|
||||
#fiddle_remote = if appropriate, add that user@host:/path here
|
||||
endif
|
||||
endif
|
||||
$(fiddle_files): default
|
||||
push-fiddle: $(fiddle_files)
|
||||
@if [ x = "x$(fiddle_remote)" ]; then \
|
||||
echo "fiddle_remote must be a [user@]HOST:/path for rsync"; \
|
||||
exit 1; \
|
||||
fi
|
||||
rsync -va fiddle/ $(fiddle_remote)
|
||||
# end fiddle remote push
|
||||
########################################################################
|
|
@ -1,7 +1,5 @@
|
|||
This directory houses a "fiddle"-style application which embeds a
|
||||
[Web Assembly (WASM)](https://en.wikipedia.org/wiki/WebAssembly)
|
||||
build of the sqlite3 shell app into an HTML page, effectively running
|
||||
the shell in a client-side browser.
|
||||
This directory houses the [Web Assembly (WASM)](https://en.wikipedia.org/wiki/WebAssembly)
|
||||
parts of the sqlite3 build.
|
||||
|
||||
It requires [emscripten][] and that the build environment be set up for
|
||||
emscripten. A mini-HOWTO for setting that up follows...
|
||||
|
@ -22,8 +20,15 @@ $ ./emsdk install latest
|
|||
$ ./emsdk activate latest
|
||||
```
|
||||
|
||||
Those parts only need to be run once. The following needs to be run for each
|
||||
shell instance which needs the `emcc` compiler:
|
||||
Those parts only need to be run once, but the SDK can be updated using:
|
||||
|
||||
```
|
||||
$ git pull
|
||||
$ ./emsdk activate latest
|
||||
```
|
||||
|
||||
The following needs to be run for each shell instance which needs the
|
||||
`emcc` compiler:
|
||||
|
||||
```
|
||||
# Activate PATH and other environment variables in the current terminal:
|
||||
|
@ -33,8 +38,11 @@ $ which emcc
|
|||
/path/to/emsdk/upstream/emscripten/emcc
|
||||
```
|
||||
|
||||
That `env` script needs to be sourced for building this application from the
|
||||
top of the sqlite3 build tree:
|
||||
Optionally, add that to your login shell's resource file (`~/.bashrc`
|
||||
or equivalent).
|
||||
|
||||
That `env` script needs to be sourced for building this application
|
||||
from the top of the sqlite3 build tree:
|
||||
|
||||
```
|
||||
$ make fiddle
|
||||
|
@ -43,29 +51,30 @@ $ make fiddle
|
|||
Or:
|
||||
|
||||
```
|
||||
$ cd ext/fiddle
|
||||
$ cd ext/wasm
|
||||
$ make
|
||||
```
|
||||
|
||||
That will generate the fiddle application under
|
||||
[ext/fiddle](/dir/ext/fiddle), as `fiddle.html`. That application
|
||||
[ext/fiddle](/dir/ext/wasm/fiddle), as `fiddle.html`. That application
|
||||
cannot, due to XMLHttpRequest security limitations, run if the HTML
|
||||
file is opened directly in the browser (i.e. if it is opened using a
|
||||
`file://` URL), so it needs to be served via an HTTP server. For
|
||||
example, using [althttpd][]:
|
||||
|
||||
```
|
||||
$ cd ext/fiddle
|
||||
$ althttpd -debug 1 -jail 0 -port 9090 -root .
|
||||
$ cd ext/wasm/fiddle
|
||||
$ althttpd -page fiddle.html
|
||||
```
|
||||
|
||||
Then browse to `http://localhost:9090/fiddle.html`.
|
||||
That will open the system's browser and run the fiddle app's page.
|
||||
|
||||
Note that when serving this app via [althttpd][], it must be a version
|
||||
from 2022-05-17 or newer so that it recognizes the `.wasm` file
|
||||
extension and responds with the mimetype `application/wasm`, as the
|
||||
WASM loader is pedantic about that detail.
|
||||
|
||||
|
||||
# Known Quirks and Limitations
|
||||
|
||||
Some "impedence mismatch" between C and WASM/JavaScript is to be
|
|
@ -7,6 +7,7 @@ _sqlite3_bind_parameter_count
|
|||
_sqlite3_bind_parameter_index
|
||||
_sqlite3_bind_text
|
||||
_sqlite3_changes
|
||||
_sqlite3_changes64
|
||||
_sqlite3_clear_bindings
|
||||
_sqlite3_close_v2
|
||||
_sqlite3_column_blob
|
||||
|
@ -24,28 +25,48 @@ _sqlite3_compileoption_used
|
|||
_sqlite3_create_function_v2
|
||||
_sqlite3_data_count
|
||||
_sqlite3_db_filename
|
||||
_sqlite3_db_name
|
||||
_sqlite3_errmsg
|
||||
_sqlite3_error_offset
|
||||
_sqlite3_errstr
|
||||
_sqlite3_exec
|
||||
_sqlite3_expanded_sql
|
||||
_sqlite3_extended_errcode
|
||||
_sqlite3_extended_result_codes
|
||||
_sqlite3_finalize
|
||||
_sqlite3_initialize
|
||||
_sqlite3_interrupt
|
||||
_sqlite3_libversion
|
||||
_sqlite3_libversion_number
|
||||
_sqlite3_open
|
||||
_sqlite3_open_v2
|
||||
_sqlite3_prepare_v2
|
||||
_sqlite3_prepare_v2
|
||||
_sqlite3_prepare_v3
|
||||
_sqlite3_reset
|
||||
_sqlite3_result_blob
|
||||
_sqlite3_result_double
|
||||
_sqlite3_result_error
|
||||
_sqlite3_result_error_code
|
||||
_sqlite3_result_error_nomem
|
||||
_sqlite3_result_error_toobig
|
||||
_sqlite3_result_int
|
||||
_sqlite3_result_null
|
||||
_sqlite3_result_text
|
||||
_sqlite3_sourceid
|
||||
_sqlite3_sql
|
||||
_sqlite3_step
|
||||
_sqlite3_strglob
|
||||
_sqlite3_strlike
|
||||
_sqlite3_total_changes
|
||||
_sqlite3_total_changes64
|
||||
_sqlite3_value_blob
|
||||
_sqlite3_value_bytes
|
||||
_sqlite3_value_double
|
||||
_sqlite3_value_text
|
||||
_sqlite3_value_type
|
||||
_sqlite3_vfs_find
|
||||
_sqlite3_vfs_register
|
||||
_sqlite3_wasm_db_error
|
||||
_sqlite3_wasm_enum_json
|
||||
_malloc
|
||||
_free
|
|
@ -0,0 +1,3 @@
|
|||
FS
|
||||
wasmMemory
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
/* The current function scope was opened via post-js-header.js, which
|
||||
gets prepended to this at build-time. */
|
||||
})/*postRun.push(...)*/;
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
post-js-header.js is to be prepended to other code to create
|
||||
post-js.js for use with Emscripten's --post-js flag. This code
|
||||
requires that it be running in that context. The Emscripten
|
||||
environment must have been set up already but it will not have
|
||||
loaded its WASM when the code in this file is run. The function it
|
||||
installs will be run after the WASM module is loaded, at which
|
||||
point the sqlite3 WASM API bits will be set up.
|
||||
*/
|
||||
if(!Module.postRun) Module.postRun = [];
|
||||
Module.postRun.push(function(Module/*the Emscripten-style module object*/){
|
||||
'use strict';
|
||||
/* This function will contain:
|
||||
|
||||
- post-js-header.js (this file)
|
||||
- sqlite3-api-prologue.js => Bootstrapping bits to attach the rest to
|
||||
- sqlite3-api-whwasmutil.js => Replacements for much of Emscripten's glue
|
||||
- sqlite3-api-jaccwabyt.js => Jaccwabyt (C/JS struct binding)
|
||||
- sqlite3-api-glue.js => glues previous parts together
|
||||
- sqlite3-api-oo.js => SQLite3 OO API #1.
|
||||
- sqlite3-api-worker.js => Worker-based API
|
||||
- sqlite3-api-cleanup.js => final API cleanup
|
||||
- post-js-footer.js => closes this postRun() function
|
||||
|
||||
Whew!
|
||||
*/
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
2022-07-22
|
||||
|
||||
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 is the tail end of the sqlite3-api.js constellation,
|
||||
intended to be appended after all other files so that it can clean
|
||||
up any global systems temporarily used for setting up the API's
|
||||
various subsystems.
|
||||
*/
|
||||
'use strict';
|
||||
self.sqlite3.postInit.forEach(
|
||||
self.importScripts/*global is a Worker*/
|
||||
? function(f){
|
||||
/** We try/catch/report for the sake of failures which happen in
|
||||
a Worker, as those exceptions can otherwise get completely
|
||||
swallowed, leading to confusing downstream errors which have
|
||||
nothing to do with this failure. */
|
||||
try{ f(self, self.sqlite3) }
|
||||
catch(e){
|
||||
console.error("Error in postInit() function:",e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
: (f)=>f(self, self.sqlite3)
|
||||
);
|
||||
delete self.sqlite3.postInit;
|
||||
if(self.location && +self.location.port > 1024){
|
||||
console.warn("Installing sqlite3 bits as global S for dev-testing purposes.");
|
||||
self.S = self.sqlite3;
|
||||
}
|
||||
/* Clean up temporary global-scope references to our APIs... */
|
||||
self.sqlite3.config.Module.sqlite3 = self.sqlite3
|
||||
/* ^^^^ Currently needed by test code and Worker API setup */;
|
||||
delete self.sqlite3.capi.util /* arguable, but these are (currently) internal-use APIs */;
|
||||
delete self.sqlite3 /* clean up our global-scope reference */;
|
||||
//console.warn("Module.sqlite3 =",Module.sqlite3);
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
2022-07-22
|
||||
|
||||
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 glues together disparate pieces of JS which are loaded in
|
||||
previous steps of the sqlite3-api.js bootstrapping process:
|
||||
sqlite3-api-prologue.js, whwasmutil.js, and jaccwabyt.js. It
|
||||
initializes the main API pieces so that the downstream components
|
||||
(e.g. sqlite3-api-oo1.js) have all that they need.
|
||||
*/
|
||||
(function(self){
|
||||
'use strict';
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
|
||||
self.sqlite3 = self.sqlite3ApiBootstrap({
|
||||
Module: Module /* ==> Emscripten-style Module object. Currently
|
||||
needs to be exposed here for test code. NOT part
|
||||
of the public API. */,
|
||||
exports: Module['asm'],
|
||||
memory: Module.wasmMemory /* gets set if built with -sIMPORT_MEMORY */,
|
||||
bigIntEnabled: !!self.BigInt64Array,
|
||||
allocExportName: 'malloc',
|
||||
deallocExportName: 'free'
|
||||
});
|
||||
delete self.sqlite3ApiBootstrap;
|
||||
|
||||
const sqlite3 = self.sqlite3;
|
||||
const capi = sqlite3.capi, wasm = capi.wasm, util = capi.util;
|
||||
self.WhWasmUtilInstaller(capi.wasm);
|
||||
delete self.WhWasmUtilInstaller;
|
||||
|
||||
if(0){
|
||||
/* "The problem" is that the following isn't type-safe.
|
||||
OTOH, nothing about WASM pointers is. */
|
||||
/**
|
||||
Add the `.pointer` xWrap() signature entry to extend
|
||||
the `pointer` arg handler to check for a `pointer`
|
||||
property. This can be used to permit, e.g., passing
|
||||
an SQLite3.DB instance to a C-style sqlite3_xxx function
|
||||
which takes an `sqlite3*` argument.
|
||||
*/
|
||||
const oldP = wasm.xWrap.argAdapter('pointer');
|
||||
const adapter = function(v){
|
||||
if(v && 'object'===typeof v && v.constructor){
|
||||
const x = v.pointer;
|
||||
if(Number.isInteger(x)) return x;
|
||||
else toss("Invalid (object) type for pointer-type argument.");
|
||||
}
|
||||
return oldP(v);
|
||||
};
|
||||
wasm.xWrap.argAdapter('.pointer', adapter);
|
||||
}
|
||||
|
||||
// WhWasmUtil.xWrap() bindings...
|
||||
{
|
||||
/**
|
||||
Add some descriptive xWrap() aliases for '*' intended to
|
||||
(A) initially improve readability/correctness of capi.signatures
|
||||
and (B) eventually perhaps provide some sort of type-safety
|
||||
in their conversions.
|
||||
*/
|
||||
const aPtr = wasm.xWrap.argAdapter('*');
|
||||
wasm.xWrap.argAdapter('sqlite3*', aPtr)('sqlite3_stmt*', aPtr);
|
||||
|
||||
/**
|
||||
Populate api object with sqlite3_...() by binding the "raw" wasm
|
||||
exports into type-converting proxies using wasm.xWrap().
|
||||
*/
|
||||
for(const e of wasm.bindingSignatures){
|
||||
capi[e[0]] = wasm.xWrap.apply(null, e);
|
||||
}
|
||||
|
||||
/* For functions which cannot work properly unless
|
||||
wasm.bigIntEnabled is true, install a bogus impl which
|
||||
throws if called when bigIntEnabled is false. */
|
||||
const fI64Disabled = function(fname){
|
||||
return ()=>toss(fname+"() disabled due to lack",
|
||||
"of BigInt support in this build.");
|
||||
};
|
||||
for(const e of wasm.bindingSignatures.int64){
|
||||
capi[e[0]] = wasm.bigIntEnabled
|
||||
? wasm.xWrap.apply(null, e)
|
||||
: fI64Disabled(e[0]);
|
||||
}
|
||||
|
||||
if(wasm.exports.sqlite3_wasm_db_error){
|
||||
util.sqlite3_wasm_db_error = capi.wasm.xWrap(
|
||||
'sqlite3_wasm_db_error', 'int', 'sqlite3*', 'int', 'string'
|
||||
);
|
||||
}else{
|
||||
util.sqlite3_wasm_db_error = function(pDb,errCode,msg){
|
||||
console.warn("sqlite3_wasm_db_error() is not exported.",arguments);
|
||||
return errCode;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
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);
|
||||
};
|
||||
|
||||
}/*xWrap() bindings*/;
|
||||
|
||||
/**
|
||||
Scope-local holder of the two impls of sqlite3_prepare_v2/v3().
|
||||
*/
|
||||
const __prepare = Object.create(null);
|
||||
/**
|
||||
This binding expects a JS string as its 2nd argument and
|
||||
null as its final argument. In order to compile multiple
|
||||
statements from a single string, the "full" impl (see
|
||||
below) must be used.
|
||||
*/
|
||||
__prepare.basic = wasm.xWrap('sqlite3_prepare_v3',
|
||||
"int", ["sqlite3*", "string",
|
||||
"int"/*MUST always be negative*/,
|
||||
"int", "**",
|
||||
"**"/*MUST be 0 or null or undefined!*/]);
|
||||
/**
|
||||
Impl which requires that the 2nd argument be a pointer
|
||||
to the SQL string, instead of being converted to a
|
||||
string. This variant is necessary for cases where we
|
||||
require a non-NULL value for the final argument
|
||||
(exec()'ing multiple statements from one input
|
||||
string). For simpler cases, where only the first
|
||||
statement in the SQL string is required, the wrapper
|
||||
named sqlite3_prepare_v2() is sufficient and easier to
|
||||
use because it doesn't require dealing with pointers.
|
||||
*/
|
||||
__prepare.full = wasm.xWrap('sqlite3_prepare_v3',
|
||||
"int", ["sqlite3*", "*", "int", "int",
|
||||
"**", "**"]);
|
||||
|
||||
/* Documented in the api object's initializer. */
|
||||
capi.sqlite3_prepare_v3 = function f(pDb, sql, sqlLen, prepFlags, ppStmt, pzTail){
|
||||
/* 2022-07-08: xWrap() 'string' arg handling may be able do this
|
||||
special-case handling for us. It needs to be tested. Or maybe
|
||||
not: we always want to treat pzTail as null when passed a
|
||||
non-pointer SQL string and the argument adapters don't have
|
||||
enough state to know that. Maybe they could/should, by passing
|
||||
the currently-collected args as an array as the 2nd arg to the
|
||||
argument adapters? Or maybe we collect all args in an array,
|
||||
pass that to an optional post-args-collected callback, and give
|
||||
it a chance to manipulate the args before we pass them on? */
|
||||
if(util.isSQLableTypedArray(sql)) sql = util.typedArrayToString(sql);
|
||||
switch(typeof sql){
|
||||
case 'string': return __prepare.basic(pDb, sql, -1, prepFlags, ppStmt, null);
|
||||
case 'number': return __prepare.full(pDb, sql, sqlLen||-1, prepFlags, ppStmt, pzTail);
|
||||
default:
|
||||
return util.sqlite3_wasm_db_error(
|
||||
pDb, capi.SQLITE_MISUSE,
|
||||
"Invalid SQL argument type for sqlite3_prepare_v2/v3()."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
capi.sqlite3_prepare_v2 =
|
||||
(pDb, sql, sqlLen, ppStmt, pzTail)=>capi.sqlite3_prepare_v3(pDb, sql, sqlLen, 0, ppStmt, pzTail);
|
||||
|
||||
/**
|
||||
Install JS<->C struct bindings for the non-opaque struct types we
|
||||
need... */
|
||||
sqlite3.StructBinder = self.Jaccwabyt({
|
||||
heap: 0 ? wasm.memory : wasm.heap8u,
|
||||
alloc: wasm.alloc,
|
||||
dealloc: wasm.dealloc,
|
||||
functionTable: wasm.functionTable,
|
||||
bigIntEnabled: wasm.bigIntEnabled,
|
||||
memberPrefix: '$'
|
||||
});
|
||||
delete self.Jaccwabyt;
|
||||
|
||||
{/* Import C-level constants and structs... */
|
||||
const cJson = wasm.xCall('sqlite3_wasm_enum_json');
|
||||
if(!cJson){
|
||||
toss("Maintenance required: increase sqlite3_wasm_enum_json()'s",
|
||||
"static buffer size!");
|
||||
}
|
||||
wasm.ctype = JSON.parse(wasm.cstringToJs(cJson));
|
||||
//console.debug('wasm.ctype length =',wasm.cstrlen(cJson));
|
||||
for(const t of ['access', 'blobFinalizers', 'dataTypes',
|
||||
'encodings', 'flock', 'ioCap',
|
||||
'openFlags', 'prepareFlags', 'resultCodes',
|
||||
'syncFlags', 'udfFlags', 'version'
|
||||
]){
|
||||
for(const [k,v] of Object.entries(wasm.ctype[t])){
|
||||
capi[k] = v;
|
||||
}
|
||||
}
|
||||
/* Bind all registered C-side structs... */
|
||||
for(const s of wasm.ctype.structs){
|
||||
capi[s.name] = sqlite3.StructBinder(s);
|
||||
}
|
||||
}
|
||||
|
||||
})(self);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
2022-07-22
|
||||
|
||||
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 contains extensions to the sqlite3 WASM API related to the
|
||||
Origin-Private FileSystem (OPFS). It is intended to be appended to
|
||||
the main JS deliverable somewhere after sqlite3-api-glue.js and
|
||||
before sqlite3-api-cleanup.js.
|
||||
|
||||
Significant notes and limitations:
|
||||
|
||||
- As of this writing, OPFS is still very much in flux and only
|
||||
available in bleeding-edge versions of Chrome (v102+, noting that
|
||||
that number will increase as the OPFS API matures).
|
||||
|
||||
- The _synchronous_ family of OPFS features (which is what this API
|
||||
requires) are only available in non-shared Worker threads. This
|
||||
file tries to detect that case and becomes a no-op if those
|
||||
features do not seem to be available.
|
||||
*/
|
||||
|
||||
// FileSystemHandle
|
||||
// FileSystemDirectoryHandle
|
||||
// FileSystemFileHandle
|
||||
// FileSystemFileHandle.prototype.createSyncAccessHandle
|
||||
self.sqlite3.postInit.push(function(self, sqlite3){
|
||||
const warn = console.warn.bind(console),
|
||||
error = console.error.bind(console);
|
||||
if(!self.importScripts || !self.FileSystemFileHandle
|
||||
|| !self.FileSystemFileHandle.prototype.createSyncAccessHandle){
|
||||
warn("OPFS not found or its sync API is not available in this environment.");
|
||||
return;
|
||||
}else if(!sqlite3.capi.wasm.bigIntEnabled){
|
||||
error("OPFS requires BigInt support but sqlite3.capi.wasm.bigIntEnabled is false.");
|
||||
return;
|
||||
}
|
||||
//warn('self.FileSystemFileHandle =',self.FileSystemFileHandle);
|
||||
//warn('self.FileSystemFileHandle.prototype =',self.FileSystemFileHandle.prototype);
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
const capi = sqlite3.capi,
|
||||
wasm = capi.wasm;
|
||||
const sqlite3_vfs = capi.sqlite3_vfs
|
||||
|| toss("Missing sqlite3.capi.sqlite3_vfs object.");
|
||||
const sqlite3_file = capi.sqlite3_file
|
||||
|| toss("Missing sqlite3.capi.sqlite3_file object.");
|
||||
const sqlite3_io_methods = capi.sqlite3_io_methods
|
||||
|| toss("Missing sqlite3.capi.sqlite3_io_methods object.");
|
||||
const StructBinder = sqlite3.StructBinder || toss("Missing sqlite3.StructBinder.");
|
||||
const debug = console.debug.bind(console),
|
||||
log = console.log.bind(console);
|
||||
warn("UNDER CONSTRUCTION: setting up OPFS VFS...");
|
||||
|
||||
const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
|
||||
const dVfs = pDVfs
|
||||
? new sqlite3_vfs(pDVfs)
|
||||
: null /* dVfs will be null when sqlite3 is built with
|
||||
SQLITE_OS_OTHER. Though we cannot currently handle
|
||||
that case, the hope is to eventually be able to. */;
|
||||
const oVfs = new sqlite3_vfs();
|
||||
const oIom = new sqlite3_io_methods();
|
||||
oVfs.$iVersion = 2/*yes, two*/;
|
||||
oVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
|
||||
oVfs.$mxPathname = 1024/*sure, why not?*/;
|
||||
oVfs.$zName = wasm.allocCString("opfs");
|
||||
oVfs.ondispose = [
|
||||
'$zName', oVfs.$zName,
|
||||
'cleanup dVfs', ()=>(dVfs ? dVfs.dispose() : null)
|
||||
];
|
||||
if(dVfs){
|
||||
oVfs.$xSleep = dVfs.$xSleep;
|
||||
oVfs.$xRandomness = dVfs.$xRandomness;
|
||||
}
|
||||
// All C-side memory of oVfs is zeroed out, but just to be explicit:
|
||||
oVfs.$xDlOpen = oVfs.$xDlError = oVfs.$xDlSym = oVfs.$xDlClose = null;
|
||||
|
||||
/**
|
||||
Pedantic sidebar about oVfs.ondispose: the entries in that array
|
||||
are items to clean up when oVfs.dispose() is called, but in this
|
||||
environment it will never be called. The VFS instance simply
|
||||
hangs around until the WASM module instance is cleaned up. We
|
||||
"could" _hypothetically_ clean it up by "importing" an
|
||||
sqlite3_os_end() impl into the wasm build, but the shutdown order
|
||||
of the wasm engine and the JS one are undefined so there is no
|
||||
guaranty that the oVfs instance would be available in one
|
||||
environment or the other when sqlite3_os_end() is called (_if_ it
|
||||
gets called at all in a wasm build, which is undefined).
|
||||
*/
|
||||
|
||||
/**
|
||||
Installs a StructBinder-bound function pointer member of the
|
||||
given name and function in the given StructType target object.
|
||||
It creates a WASM proxy for the given function and arranges for
|
||||
that proxy to be cleaned up when tgt.dispose() is called. Throws
|
||||
on the slightest hint of error (e.g. tgt is-not-a StructType,
|
||||
name does not map to a struct-bound member, etc.).
|
||||
|
||||
Returns a proxy for this function which is bound to tgt and takes
|
||||
2 args (name,func). That function returns the same thing,
|
||||
permitting calls to be chained.
|
||||
|
||||
If called with only 1 arg, it has no side effects but returns a
|
||||
func with the same signature as described above.
|
||||
*/
|
||||
const installMethod = function callee(tgt, name, func){
|
||||
if(!(tgt instanceof StructBinder.StructType)){
|
||||
toss("Usage error: target object is-not-a StructType.");
|
||||
}
|
||||
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.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);
|
||||
//log("installMethod",tgt, name, sigN);
|
||||
const fProxy = 1
|
||||
// We can remove this proxy middle-man once the VFS is working
|
||||
? 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);
|
||||
}/*installMethod*/;
|
||||
|
||||
/**
|
||||
Map of sqlite3_file pointers to OPFS handles.
|
||||
*/
|
||||
const __opfsHandles = Object.create(null);
|
||||
|
||||
const randomFilename = function f(len=16){
|
||||
if(!f._chars){
|
||||
f._chars = "abcdefghijklmnopqrstuvwxyz"+
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
|
||||
"012346789";
|
||||
f._n = f._chars.length;
|
||||
}
|
||||
const a = [];
|
||||
let i = 0;
|
||||
for( ; i < len; ++i){
|
||||
const ndx = Math.random() * (f._n * 64) % f._n | 0;
|
||||
a[i] = f._chars[ndx];
|
||||
}
|
||||
return a.join('');
|
||||
};
|
||||
|
||||
//const rootDir = await navigator.storage.getDirectory();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Set up OPFS VFS methods...
|
||||
let inst = installMethod(oVfs);
|
||||
inst('xOpen', function(pVfs, zName, pFile, flags, pOutFlags){
|
||||
const f = new sqlite3_file(pFile);
|
||||
f.$pMethods = oIom.pointer;
|
||||
__opfsHandles[pFile] = f;
|
||||
f.opfsHandle = null /* TODO */;
|
||||
if(capi.SQLITE_OPEN_DELETEONCLOSE){
|
||||
f.deleteOnClose = true;
|
||||
}
|
||||
f.filename = zName ? wasm.cstringToJs(zName) : randomFilename();
|
||||
error("OPFS sqlite3_vfs::xOpen is not yet full implemented.");
|
||||
return capi.SQLITE_IOERR;
|
||||
})
|
||||
('xFullPathname', function(pVfs,zName,nOut,pOut){
|
||||
/* Until/unless we have some notion of "current dir"
|
||||
in OPFS, simply copy zName to pOut... */
|
||||
const i = wasm.cstrncpy(pOut, zName, nOut);
|
||||
return i<nOut ? 0 : capi.SQLITE_CANTOPEN
|
||||
/*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
|
||||
})
|
||||
('xAccess', function(pVfs,zName,flags,pOut){
|
||||
error("OPFS sqlite3_vfs::xAccess is not yet implemented.");
|
||||
let fileExists = 0;
|
||||
switch(flags){
|
||||
case capi.SQLITE_ACCESS_EXISTS: break;
|
||||
case capi.SQLITE_ACCESS_READWRITE: break;
|
||||
case capi.SQLITE_ACCESS_READ/*docs say this is never used*/:
|
||||
default:
|
||||
error("Unexpected flags value for sqlite3_vfs::xAccess():",flags);
|
||||
return capi.SQLITE_MISUSE;
|
||||
}
|
||||
wasm.setMemValue(pOut, fileExists, 'i32');
|
||||
return 0;
|
||||
})
|
||||
('xDelete', function(pVfs, zName, doSyncDir){
|
||||
error("OPFS sqlite3_vfs::xDelete is not yet implemented.");
|
||||
return capi.SQLITE_IOERR;
|
||||
})
|
||||
('xGetLastError', function(pVfs,nOut,pOut){
|
||||
debug("OPFS sqlite3_vfs::xGetLastError() has nothing sensible to return.");
|
||||
return 0;
|
||||
})
|
||||
('xCurrentTime', function(pVfs,pOut){
|
||||
/* If it turns out that we need to adjust for timezone, see:
|
||||
https://stackoverflow.com/a/11760121/1458521 */
|
||||
wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
|
||||
'double');
|
||||
return 0;
|
||||
})
|
||||
('xCurrentTimeInt64',function(pVfs,pOut){
|
||||
// TODO: confirm that this calculation is correct
|
||||
wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
|
||||
'i64');
|
||||
return 0;
|
||||
});
|
||||
if(!oVfs.$xSleep){
|
||||
inst('xSleep', function(pVfs,ms){
|
||||
error("sqlite3_vfs::xSleep(",ms,") cannot be implemented from "+
|
||||
"JS and we have no default VFS to copy the impl from.");
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
if(!oVfs.$xRandomness){
|
||||
inst('xRandomness', function(pVfs, nOut, pOut){
|
||||
const heap = wasm.heap8u();
|
||||
let i = 0;
|
||||
for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
|
||||
return i;
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Set up OPFS sqlite3_io_methods...
|
||||
inst = installMethod(oIom);
|
||||
inst('xClose', async function(pFile){
|
||||
warn("xClose(",arguments,") uses await");
|
||||
const f = __opfsHandles[pFile];
|
||||
delete __opfsHandles[pFile];
|
||||
if(f.opfsHandle){
|
||||
await f.opfsHandle.close();
|
||||
if(f.deleteOnClose){
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
f.dispose();
|
||||
return 0;
|
||||
})
|
||||
('xRead', /*i(ppij)*/function(pFile,pDest,n,offset){
|
||||
/* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
|
||||
try {
|
||||
const f = __opfsHandles[pFile];
|
||||
const heap = wasm.heap8u();
|
||||
const b = new Uint8Array(heap.buffer, pDest, n);
|
||||
const nRead = f.opfsHandle.read(b, {at: offset});
|
||||
if(nRead<n){
|
||||
// MUST zero-fill short reads (per the docs)
|
||||
heap.fill(0, dest + nRead, n - nRead);
|
||||
}
|
||||
return 0;
|
||||
}catch(e){
|
||||
error("xRead(",arguments,") failed:",e);
|
||||
return capi.SQLITE_IOERR_READ;
|
||||
}
|
||||
})
|
||||
('xWrite', /*i(ppij)*/function(pFile,pSrc,n,offset){
|
||||
/* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
|
||||
try {
|
||||
const f = __opfsHandles[pFile];
|
||||
const b = new Uint8Array(wasm.heap8u().buffer, pSrc, n);
|
||||
const nOut = f.opfsHandle.write(b, {at: offset});
|
||||
if(nOut<n){
|
||||
error("xWrite(",arguments,") short write!");
|
||||
return capi.SQLITE_IOERR_WRITE;
|
||||
}
|
||||
return 0;
|
||||
}catch(e){
|
||||
error("xWrite(",arguments,") failed:",e);
|
||||
return capi.SQLITE_IOERR_WRITE;
|
||||
}
|
||||
})
|
||||
('xTruncate', /*i(pj)*/async function(pFile,sz){
|
||||
/* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
|
||||
try{
|
||||
warn("xTruncate(",arguments,") uses await");
|
||||
const f = __opfsHandles[pFile];
|
||||
await f.opfsHandle.truncate(sz);
|
||||
return 0;
|
||||
}
|
||||
catch(e){
|
||||
error("xTruncate(",arguments,") failed:",e);
|
||||
return capi.SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
})
|
||||
('xSync', /*i(pi)*/async function(pFile,flags){
|
||||
/* int (*xSync)(sqlite3_file*, int flags) */
|
||||
try {
|
||||
warn("xSync(",arguments,") uses await");
|
||||
const f = __opfsHandles[pFile];
|
||||
await f.opfsHandle.flush();
|
||||
return 0;
|
||||
}catch(e){
|
||||
error("xSync(",arguments,") failed:",e);
|
||||
return capi.SQLITE_IOERR_SYNC;
|
||||
}
|
||||
})
|
||||
('xFileSize', /*i(pp)*/async function(pFile,pSz){
|
||||
/* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
|
||||
try {
|
||||
warn("xFileSize(",arguments,") uses await");
|
||||
const f = __opfsHandles[pFile];
|
||||
const fsz = await f.opfsHandle.getSize();
|
||||
capi.wasm.setMemValue(pSz, fsz,'i64');
|
||||
return 0;
|
||||
}catch(e){
|
||||
error("xFileSize(",arguments,") failed:",e);
|
||||
return capi.SQLITE_IOERR_SEEK;
|
||||
}
|
||||
})
|
||||
('xLock', /*i(pi)*/function(pFile,lockType){
|
||||
/* int (*xLock)(sqlite3_file*, int) */
|
||||
// Opening a handle locks it automatically.
|
||||
warn("xLock(",arguments,") is a no-op");
|
||||
return 0;
|
||||
})
|
||||
('xUnlock', /*i(pi)*/function(pFile,lockType){
|
||||
/* int (*xUnlock)(sqlite3_file*, int) */
|
||||
// Opening a handle locks it automatically.
|
||||
warn("xUnlock(",arguments,") is a no-op");
|
||||
return 0;
|
||||
})
|
||||
('xCheckReservedLock', /*i(pp)*/function(pFile,pOut){
|
||||
/* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
|
||||
// Exclusive lock is automatically acquired when opened
|
||||
warn("xCheckReservedLock(",arguments,") is a no-op");
|
||||
wasm.setMemValue(pOut,1,'i32');
|
||||
return 0;
|
||||
})
|
||||
('xFileControl', /*i(pip)*/function(pFile,op,pArg){
|
||||
/* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
|
||||
debug("xFileControl(",arguments,") is a no-op");
|
||||
return capi.SQLITE_NOTFOUND;
|
||||
})
|
||||
('xDeviceCharacteristics',/*i(p)*/function(pFile){
|
||||
/* int (*xDeviceCharacteristics)(sqlite3_file*) */
|
||||
debug("xDeviceCharacteristics(",pFile,")");
|
||||
return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
|
||||
});
|
||||
// xSectorSize may be NULL
|
||||
//('xSectorSize', function(pFile){
|
||||
// /* int (*xSectorSize)(sqlite3_file*) */
|
||||
// log("xSectorSize(",pFile,")");
|
||||
// return 4096 /* ==> SQLITE_DEFAULT_SECTOR_SIZE */;
|
||||
//})
|
||||
|
||||
const rc = capi.sqlite3_vfs_register(oVfs.pointer, 0);
|
||||
if(rc){
|
||||
oVfs.dispose();
|
||||
toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
|
||||
}
|
||||
capi.sqlite3_vfs_register.addReference(oVfs, oIom);
|
||||
warn("End of (very incomplete) OPFS setup.", oVfs);
|
||||
//oVfs.dispose()/*only because we can't yet do anything with it*/;
|
||||
});
|
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
2022-05-22
|
||||
|
||||
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 is intended to be combined at build-time with other
|
||||
related code, most notably a header and footer which wraps this whole
|
||||
file into an Emscripten Module.postRun() handler which has a parameter
|
||||
named "Module" (the Emscripten Module object). The exact requirements,
|
||||
conventions, and build process are very much under construction and
|
||||
will be (re)documented once they've stopped fluctuating so much.
|
||||
|
||||
Specific goals of this project:
|
||||
|
||||
- Except where noted in the non-goals, provide a more-or-less
|
||||
feature-complete wrapper to the sqlite3 C API, insofar as WASM
|
||||
feature parity with C allows for. In fact, provide at least 3
|
||||
APIs...
|
||||
|
||||
1) Bind a low-level sqlite3 API which is as close to the native
|
||||
one as feasible in terms of usage.
|
||||
|
||||
2) A higher-level API, more akin to sql.js and node.js-style
|
||||
implementations. This one speaks directly to the low-level
|
||||
API. This API must be used from the same thread as the
|
||||
low-level API.
|
||||
|
||||
3) A second higher-level API which speaks to the previous APIs via
|
||||
worker messages. This one is intended for use in the main
|
||||
thread, with the lower-level APIs installed in a Worker thread,
|
||||
and talking to them via Worker messages. Because Workers are
|
||||
asynchronouns and have only a single message channel, some
|
||||
acrobatics are needed here to feed async work results back to
|
||||
the client (as we cannot simply pass around callbacks between
|
||||
the main and Worker threads).
|
||||
|
||||
- Insofar as possible, support client-side storage using JS
|
||||
filesystem APIs. As of this writing, such things are still very
|
||||
much TODO. Initial testing with using IndexedDB as backing storage
|
||||
showed it to work reasonably well, but it's also too easy to
|
||||
corrupt by using a web page in two browser tabs because IndexedDB
|
||||
lacks the locking features needed to support that.
|
||||
|
||||
Specific non-goals of this project:
|
||||
|
||||
- As WASM is a web-centric technology and UTF-8 is the King of
|
||||
Encodings in that realm, there are no currently plans to support
|
||||
the UTF16-related sqlite3 APIs. They would add a complication to
|
||||
the bindings for no appreciable benefit. Though web-related
|
||||
implementation details take priority, the lower-level WASM module
|
||||
"should" work in non-web WASM environments.
|
||||
|
||||
- Supporting old or niche-market platforms. WASM is built for a
|
||||
modern web and requires modern platforms.
|
||||
|
||||
- Though scalar User-Defined Functions (UDFs) may be created in
|
||||
JavaScript, there are currently no plans to add support for
|
||||
aggregate and window functions.
|
||||
|
||||
Attribution:
|
||||
|
||||
This project is endebted to the work of sql.js:
|
||||
|
||||
https://github.com/sql-js/sql.js
|
||||
|
||||
sql.js was an essential stepping stone in this code's development as
|
||||
it demonstrated how to handle some of the WASM-related voodoo (like
|
||||
handling pointers-to-pointers and adding JS implementations of
|
||||
C-bound callback functions). These APIs have a considerably
|
||||
different shape than sql.js's, however.
|
||||
*/
|
||||
|
||||
/**
|
||||
This global symbol is is only a temporary measure: the JS-side
|
||||
post-processing will remove that object from the global scope when
|
||||
setup is complete. We require it there temporarily in order to glue
|
||||
disparate parts together during the loading of the API (which spans
|
||||
several components).
|
||||
|
||||
This function requires a configuration object intended to abstract
|
||||
away details specific to any given WASM environment, primarily so
|
||||
that it can be used without any _direct_ dependency on Emscripten.
|
||||
(That said, OO API #1 requires, as of this writing, Emscripten's
|
||||
virtual filesystem API. Baby steps.)
|
||||
*/
|
||||
self.sqlite3ApiBootstrap = function(config){
|
||||
'use strict';
|
||||
|
||||
/** Throws a new Error, the message of which is the concatenation
|
||||
all args with a space between each. */
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
|
||||
/**
|
||||
Returns true if n is a 32-bit (signed) integer, else
|
||||
false. This is used for determining when we need to switch to
|
||||
double-type DB operations for integer values in order to keep
|
||||
more precision.
|
||||
*/
|
||||
const isInt32 = function(n){
|
||||
return ('bigint'!==typeof n /*TypeError: can't convert BigInt to number*/)
|
||||
&& !!(n===(n|0) && n<=2147483647 && n>=-2147483648);
|
||||
};
|
||||
|
||||
/** Returns v if v appears to be a TypedArray, else false. */
|
||||
const isTypedArray = (v)=>{
|
||||
return (v && v.constructor && isInt32(v.constructor.BYTES_PER_ELEMENT)) ? v : false;
|
||||
};
|
||||
|
||||
/**
|
||||
Returns true if v appears to be one of our bind()-able
|
||||
TypedArray types: Uint8Array or Int8Array. Support for
|
||||
TypedArrays with element sizes >1 is TODO.
|
||||
*/
|
||||
const isBindableTypedArray = (v)=>{
|
||||
return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT);
|
||||
};
|
||||
|
||||
/**
|
||||
Returns true if v appears to be one of the TypedArray types
|
||||
which is legal for holding SQL code (as opposed to binary blobs).
|
||||
|
||||
Currently this is the same as isBindableTypedArray() but it
|
||||
seems likely that we'll eventually want to add Uint32Array
|
||||
and friends to the isBindableTypedArray() list but not to the
|
||||
isSQLableTypedArray() list.
|
||||
*/
|
||||
const isSQLableTypedArray = (v)=>{
|
||||
return v && v.constructor && (1===v.constructor.BYTES_PER_ELEMENT);
|
||||
};
|
||||
|
||||
/** Returns true if isBindableTypedArray(v) does, else throws with a message
|
||||
that v is not a supported TypedArray value. */
|
||||
const affirmBindableTypedArray = (v)=>{
|
||||
return isBindableTypedArray(v)
|
||||
|| toss("Value is not of a supported TypedArray type.");
|
||||
};
|
||||
|
||||
const utf8Decoder = new TextDecoder('utf-8');
|
||||
const typedArrayToString = (str)=>utf8Decoder.decode(str);
|
||||
|
||||
/**
|
||||
An Error subclass specifically for reporting Wasm-level malloc()
|
||||
failure and enabling clients to unambiguously identify such
|
||||
exceptions.
|
||||
*/
|
||||
class WasmAllocError extends Error {
|
||||
constructor(...args){
|
||||
super(...args);
|
||||
this.name = 'WasmAllocError';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
The main sqlite3 binding API gets installed into this object,
|
||||
mimicking the C API as closely as we can. The numerous members
|
||||
names with prefixes 'sqlite3_' and 'SQLITE_' behave, insofar as
|
||||
possible, identically to the C-native counterparts, as documented at:
|
||||
|
||||
https://www.sqlite.org/c3ref/intro.html
|
||||
|
||||
A very few exceptions require an additional level of proxy
|
||||
function or may otherwise require special attention in the WASM
|
||||
environment, and all such cases are document here. Those not
|
||||
documented here are installed as 1-to-1 proxies for their C-side
|
||||
counterparts.
|
||||
*/
|
||||
const capi = {
|
||||
/**
|
||||
An Error subclass which is thrown by this object's alloc() method
|
||||
on OOM.
|
||||
*/
|
||||
WasmAllocError: WasmAllocError,
|
||||
/**
|
||||
The API's one single point of access to the WASM-side memory
|
||||
allocator. Works like malloc(3) (and is likely bound to
|
||||
malloc()) but throws an WasmAllocError if allocation fails. It is
|
||||
important that any code which might pass through the sqlite3 C
|
||||
API NOT throw and must instead return SQLITE_NOMEM (or
|
||||
equivalent, depending on the context).
|
||||
|
||||
That said, very few cases in the API can result in
|
||||
client-defined functions propagating exceptions via the C-style
|
||||
API. Most notably, this applies ot User-defined SQL Functions
|
||||
(UDFs) registered via sqlite3_create_function_v2(). For that
|
||||
specific case it is recommended that all UDF creation be
|
||||
funneled through a utility function and that a wrapper function
|
||||
be added around the UDF which catches any exception and sets
|
||||
the error state to OOM. (The overall complexity of registering
|
||||
UDFs essentially requires a helper for doing so!)
|
||||
*/
|
||||
alloc: undefined/*installed later*/,
|
||||
/**
|
||||
The API's one single point of access to the WASM-side memory
|
||||
deallocator. Works like free(3) (and is likely bound to
|
||||
free()).
|
||||
*/
|
||||
dealloc: undefined/*installed later*/,
|
||||
/**
|
||||
When using sqlite3_open_v2() it is important to keep the following
|
||||
in mind:
|
||||
|
||||
https://www.sqlite.org/c3ref/open.html
|
||||
|
||||
- The flags for use with its 3rd argument are installed in this
|
||||
object using the C-cide names, e.g. SQLITE_OPEN_CREATE.
|
||||
|
||||
- If the combination of flags passed to it are invalid,
|
||||
behavior is undefined. Thus is is never okay to call this
|
||||
with fewer than 3 arguments, as JS will default the
|
||||
missing arguments to `undefined`, which will result in a
|
||||
flag value of 0. Most of the available SQLITE_OPEN_xxx
|
||||
flags are meaningless in the WASM build, e.g. the mutext-
|
||||
and cache-related flags, but they are retained in this
|
||||
API for consistency's sake.
|
||||
|
||||
- The final argument to this function specifies the VFS to
|
||||
use, which is largely (but not entirely!) meaningless in
|
||||
the WASM environment. It should always be null or
|
||||
undefined, and it is safe to elide that argument when
|
||||
calling this function.
|
||||
*/
|
||||
sqlite3_open_v2: function(filename,dbPtrPtr,flags,vfsStr){}/*installed later*/,
|
||||
/**
|
||||
The sqlite3_prepare_v3() binding handles two different uses
|
||||
with differing JS/WASM semantics:
|
||||
|
||||
1) sqlite3_prepare_v3(pDb, sqlString, -1, prepFlags, ppStmt [, null])
|
||||
|
||||
2) sqlite3_prepare_v3(pDb, sqlPointer, sqlByteLen, prepFlags, ppStmt, sqlPointerToPointer)
|
||||
|
||||
Note that the SQL length argument (the 3rd argument) must, for
|
||||
usage (1), always be negative because it must be a byte length
|
||||
and that value is expensive to calculate from JS (where only
|
||||
the character length of strings is readily available). It is
|
||||
retained in this API's interface for code/documentation
|
||||
compatibility reasons but is currently _always_ ignored. With
|
||||
usage (2), the 3rd argument is used as-is but is is still
|
||||
critical that the C-style input string (2nd argument) be
|
||||
terminated with a 0 byte.
|
||||
|
||||
In usage (1), the 2nd argument must be of type string,
|
||||
Uint8Array, or Int8Array (either of which is assumed to
|
||||
hold SQL). If it is, this function assumes case (1) and
|
||||
calls the underyling C function with the equivalent of:
|
||||
|
||||
(pDb, sqlAsString, -1, prepFlags, ppStmt, null)
|
||||
|
||||
The pzTail argument is ignored in this case because its result
|
||||
is meaningless when a string-type value is passed through
|
||||
(because the string goes through another level of internal
|
||||
conversion for WASM's sake and the result pointer would refer
|
||||
to that transient conversion's memory, not the passed-in
|
||||
string).
|
||||
|
||||
If the sql argument is not a string, it must be a _pointer_ to
|
||||
a NUL-terminated string which was allocated in the WASM memory
|
||||
(e.g. using cwapi.wasm.alloc() or equivalent). In that case,
|
||||
the final argument may be 0/null/undefined or must be a pointer
|
||||
to which the "tail" of the compiled SQL is written, as
|
||||
documented for the C-side sqlite3_prepare_v3(). In case (2),
|
||||
the underlying C function is called with the equivalent of:
|
||||
|
||||
(pDb, sqlAsPointer, (sqlByteLen||-1), prepFlags, ppStmt, pzTail)
|
||||
|
||||
It returns its result and compiled statement as documented in
|
||||
the C API. Fetching the output pointers (5th and 6th
|
||||
parameters) requires using capi.wasm.getMemValue() (or
|
||||
equivalent) and the pzTail will point to an address relative to
|
||||
the sqlAsPointer value.
|
||||
|
||||
If passed an invalid 2nd argument type, this function will
|
||||
return SQLITE_MISUSE but will unfortunately be able to return
|
||||
any additional error information because we have no way to set
|
||||
the db's error state such that this function could return a
|
||||
non-0 integer and the client could call sqlite3_errcode() or
|
||||
sqlite3_errmsg() to fetch it. See the RFE at:
|
||||
|
||||
https://sqlite.org/forum/forumpost/f9eb79b11aefd4fc81d
|
||||
|
||||
The alternative would be to throw an exception for that case,
|
||||
but that would be in strong constrast to the rest of the
|
||||
C-level API and seems likely to cause more confusion.
|
||||
|
||||
Side-note: in the C API the function does not fail if provided
|
||||
an empty string but its result output pointer will be NULL.
|
||||
*/
|
||||
sqlite3_prepare_v3: function(dbPtr, sql, sqlByteLen, prepFlags,
|
||||
stmtPtrPtr, strPtrPtr){}/*installed later*/,
|
||||
|
||||
/**
|
||||
Equivalent to calling sqlite3_prapare_v3() with 0 as its 4th argument.
|
||||
*/
|
||||
sqlite3_prepare_v2: function(dbPtr, sql, sqlByteLen, stmtPtrPtr,
|
||||
strPtrPtr){}/*installed later*/,
|
||||
|
||||
/**
|
||||
Various internal-use utilities are added here as needed. They
|
||||
are bound to an object only so that we have access to them in
|
||||
the differently-scoped steps of the API bootstrapping
|
||||
process. At the end of the API setup process, this object gets
|
||||
removed.
|
||||
*/
|
||||
util:{
|
||||
isInt32, isTypedArray, isBindableTypedArray, isSQLableTypedArray,
|
||||
affirmBindableTypedArray, typedArrayToString
|
||||
},
|
||||
|
||||
/**
|
||||
Holds state which are specific to the WASM-related
|
||||
infrastructure and glue code. It is not expected that client
|
||||
code will normally need these, but they're exposed here in case
|
||||
it does. These APIs are _not_ to be considered an
|
||||
official/stable part of the sqlite3 WASM API. They may change
|
||||
as the developers' experience suggests appropriate changes.
|
||||
|
||||
Note that a number of members of this object are injected
|
||||
dynamically after the api object is fully constructed, so
|
||||
not all are documented inline here.
|
||||
*/
|
||||
wasm: {
|
||||
//^^^ TODO?: move wasm from sqlite3.capi.wasm to sqlite3.wasm
|
||||
/**
|
||||
Emscripten APIs have a deep-seated assumption that all pointers
|
||||
are 32 bits. We'll remain optimistic that that won't always be
|
||||
the case and will use this constant in places where we might
|
||||
otherwise use a hard-coded 4.
|
||||
*/
|
||||
ptrSizeof: config.wasmPtrSizeof || 4,
|
||||
/**
|
||||
The WASM IR (Intermediate Representation) value for
|
||||
pointer-type values. It MUST refer to a value type of the
|
||||
size described by this.ptrSizeof _or_ it may be any value
|
||||
which ends in '*', which Emscripten's glue code internally
|
||||
translates to i32.
|
||||
*/
|
||||
ptrIR: config.wasmPtrIR || "i32",
|
||||
/**
|
||||
True if BigInt support was enabled via (e.g.) the
|
||||
Emscripten -sWASM_BIGINT flag, else false. When
|
||||
enabled, certain 64-bit sqlite3 APIs are enabled which
|
||||
are not otherwise enabled due to JS/WASM int64
|
||||
impedence mismatches.
|
||||
*/
|
||||
bigIntEnabled: !!config.bigIntEnabled,
|
||||
/**
|
||||
The symbols exported by the WASM environment.
|
||||
*/
|
||||
exports: config.exports
|
||||
|| toss("Missing API config.exports (WASM module exports)."),
|
||||
|
||||
/**
|
||||
When Emscripten compiles with `-sIMPORT_MEMORY`, it
|
||||
initalizes the heap and imports it into wasm, as opposed to
|
||||
the other way around. In this case, the memory is not
|
||||
available via this.exports.memory.
|
||||
*/
|
||||
memory: config.memory || config.exports['memory']
|
||||
|| toss("API config object requires a WebAssembly.Memory object",
|
||||
"in either config.exports.memory (exported)",
|
||||
"or config.memory (imported)."),
|
||||
/* Many more wasm-related APIs get installed later on. */
|
||||
}/*wasm*/
|
||||
}/*capi*/;
|
||||
|
||||
/**
|
||||
capi.wasm.alloc()'s srcTypedArray.byteLength bytes,
|
||||
populates them with the values from the source
|
||||
TypedArray, and returns the pointer to that memory. The
|
||||
returned pointer must eventually be passed to
|
||||
capi.wasm.dealloc() to clean it up.
|
||||
|
||||
As a special case, to avoid further special cases where
|
||||
this is used, if srcTypedArray.byteLength is 0, it
|
||||
allocates a single byte and sets it to the value
|
||||
0. Even in such cases, calls must behave as if the
|
||||
allocated memory has exactly srcTypedArray.byteLength
|
||||
bytes.
|
||||
|
||||
ACHTUNG: this currently only works for Uint8Array and
|
||||
Int8Array types and will throw if srcTypedArray is of
|
||||
any other type.
|
||||
*/
|
||||
capi.wasm.mallocFromTypedArray = function(srcTypedArray){
|
||||
affirmBindableTypedArray(srcTypedArray);
|
||||
const pRet = this.alloc(srcTypedArray.byteLength || 1);
|
||||
this.heapForSize(srcTypedArray.constructor).set(srcTypedArray.byteLength ? srcTypedArray : [0], pRet);
|
||||
return pRet;
|
||||
}.bind(capi.wasm);
|
||||
|
||||
const keyAlloc = config.allocExportName || 'malloc',
|
||||
keyDealloc = config.deallocExportName || 'free';
|
||||
for(const key of [keyAlloc, keyDealloc]){
|
||||
const f = capi.wasm.exports[key];
|
||||
if(!(f instanceof Function)) toss("Missing required exports[",key,"] function.");
|
||||
}
|
||||
capi.wasm.alloc = function(n){
|
||||
const m = this.exports[keyAlloc](n);
|
||||
if(!m) throw new WasmAllocError("Failed to allocate "+n+" bytes.");
|
||||
return m;
|
||||
}.bind(capi.wasm)
|
||||
capi.wasm.dealloc = (m)=>capi.wasm.exports[keyDealloc](m);
|
||||
|
||||
/**
|
||||
Reports info about compile-time options using
|
||||
sqlite_compileoption_get() and sqlite3_compileoption_used(). It
|
||||
has several distinct uses:
|
||||
|
||||
If optName is an array then it is expected to be a list of
|
||||
compilation options and this function returns an object
|
||||
which maps each such option to true or false, indicating
|
||||
whether or not the given option was included in this
|
||||
build. That object is returned.
|
||||
|
||||
If optName is an object, its keys are expected to be compilation
|
||||
options and this function sets each entry to true or false,
|
||||
indicating whether the compilation option was used or not. That
|
||||
object is returned.
|
||||
|
||||
If passed no arguments then it returns an object mapping
|
||||
all known compilation options to their compile-time values,
|
||||
or boolean true if they are defined with no value. This
|
||||
result, which is relatively expensive to compute, is cached
|
||||
and returned for future no-argument calls.
|
||||
|
||||
In all other cases it returns true if the given option was
|
||||
active when when compiling the sqlite3 module, else false.
|
||||
|
||||
Compile-time option names may optionally include their
|
||||
"SQLITE_" prefix. When it returns an object of all options,
|
||||
the prefix is elided.
|
||||
*/
|
||||
capi.wasm.compileOptionUsed = function f(optName){
|
||||
if(!arguments.length){
|
||||
if(f._result) return f._result;
|
||||
else if(!f._opt){
|
||||
f._rx = /^([^=]+)=(.+)/;
|
||||
f._rxInt = /^-?\d+$/;
|
||||
f._opt = function(opt, rv){
|
||||
const m = f._rx.exec(opt);
|
||||
rv[0] = (m ? m[1] : opt);
|
||||
rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
|
||||
};
|
||||
}
|
||||
const rc = {}, ov = [0,0];
|
||||
let i = 0, k;
|
||||
while((k = capi.sqlite3_compileoption_get(i++))){
|
||||
f._opt(k,ov);
|
||||
rc[ov[0]] = ov[1];
|
||||
}
|
||||
return f._result = rc;
|
||||
}else if(Array.isArray(optName)){
|
||||
const rc = {};
|
||||
optName.forEach((v)=>{
|
||||
rc[v] = capi.sqlite3_compileoption_used(v);
|
||||
});
|
||||
return rc;
|
||||
}else if('object' === typeof optName){
|
||||
Object.keys(optName).forEach((k)=> {
|
||||
optName[k] = capi.sqlite3_compileoption_used(k);
|
||||
});
|
||||
return optName;
|
||||
}
|
||||
return (
|
||||
'string'===typeof optName
|
||||
) ? !!capi.sqlite3_compileoption_used(optName) : false;
|
||||
}/*compileOptionUsed()*/;
|
||||
|
||||
capi.wasm.bindingSignatures = [
|
||||
/**
|
||||
Signatures for the WASM-exported C-side functions. Each entry
|
||||
is an array with 2+ elements:
|
||||
|
||||
["c-side name",
|
||||
"result type" (capi.wasm.xWrap() syntax),
|
||||
[arg types in xWrap() syntax]
|
||||
// ^^^ this needn't strictly be an array: it can be subsequent
|
||||
// elements instead: [x,y,z] is equivalent to x,y,z
|
||||
]
|
||||
*/
|
||||
// Please keep these sorted by function name!
|
||||
["sqlite3_bind_blob","int", "sqlite3_stmt*", "int", "*", "int", "*"],
|
||||
["sqlite3_bind_double","int", "sqlite3_stmt*", "int", "f64"],
|
||||
["sqlite3_bind_int","int", "sqlite3_stmt*", "int", "int"],
|
||||
["sqlite3_bind_null",undefined, "sqlite3_stmt*", "int"],
|
||||
["sqlite3_bind_parameter_count", "int", "sqlite3_stmt*"],
|
||||
["sqlite3_bind_parameter_index","int", "sqlite3_stmt*", "string"],
|
||||
["sqlite3_bind_text","int", "sqlite3_stmt*", "int", "string", "int", "int"],
|
||||
["sqlite3_close_v2", "int", "sqlite3*"],
|
||||
["sqlite3_changes", "int", "sqlite3*"],
|
||||
["sqlite3_clear_bindings","int", "sqlite3_stmt*"],
|
||||
["sqlite3_column_blob","*", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_bytes","int", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_count", "int", "sqlite3_stmt*"],
|
||||
["sqlite3_column_double","f64", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_int","int", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_name","string", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_text","string", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_column_type","int", "sqlite3_stmt*", "int"],
|
||||
["sqlite3_compileoption_get", "string", "int"],
|
||||
["sqlite3_compileoption_used", "int", "string"],
|
||||
["sqlite3_create_function_v2", "int",
|
||||
"sqlite3*", "string", "int", "int", "*", "*", "*", "*", "*"],
|
||||
["sqlite3_data_count", "int", "sqlite3_stmt*"],
|
||||
["sqlite3_db_filename", "string", "sqlite3*", "string"],
|
||||
["sqlite3_db_name", "string", "sqlite3*", "int"],
|
||||
["sqlite3_errmsg", "string", "sqlite3*"],
|
||||
["sqlite3_error_offset", "int", "sqlite3*"],
|
||||
["sqlite3_errstr", "string", "int"],
|
||||
//["sqlite3_exec", "int", "sqlite3*", "string", "*", "*", "**"],
|
||||
// ^^^ TODO: we need a wrapper to support passing a function pointer or a function
|
||||
// for the callback.
|
||||
["sqlite3_expanded_sql", "string", "sqlite3_stmt*"],
|
||||
["sqlite3_extended_errcode", "int", "sqlite3*"],
|
||||
["sqlite3_extended_result_codes", "int", "sqlite3*", "int"],
|
||||
["sqlite3_finalize", "int", "sqlite3_stmt*"],
|
||||
["sqlite3_initialize", undefined],
|
||||
["sqlite3_interrupt", undefined, "sqlite3*"
|
||||
/* ^^^ we cannot actually currently support this because JS is
|
||||
single-threaded and we don't have a portable way to access a DB
|
||||
from 2 SharedWorkers concurrently. */],
|
||||
["sqlite3_libversion", "string"],
|
||||
["sqlite3_libversion_number", "int"],
|
||||
["sqlite3_open", "int", "string", "*"],
|
||||
["sqlite3_open_v2", "int", "string", "*", "int", "string"],
|
||||
/* sqlite3_prepare_v2() and sqlite3_prepare_v3() are handled
|
||||
separately due to us requiring two different sets of semantics
|
||||
for those, depending on how their SQL argument is provided. */
|
||||
["sqlite3_reset", "int", "sqlite3_stmt*"],
|
||||
["sqlite3_result_blob",undefined, "*", "*", "int", "*"],
|
||||
["sqlite3_result_double",undefined, "*", "f64"],
|
||||
["sqlite3_result_error",undefined, "*", "string", "int"],
|
||||
["sqlite3_result_error_code", undefined, "*", "int"],
|
||||
["sqlite3_result_error_nomem", undefined, "*"],
|
||||
["sqlite3_result_error_toobig", undefined, "*"],
|
||||
["sqlite3_result_int",undefined, "*", "int"],
|
||||
["sqlite3_result_null",undefined, "*"],
|
||||
["sqlite3_result_text",undefined, "*", "string", "int", "*"],
|
||||
["sqlite3_sourceid", "string"],
|
||||
["sqlite3_sql", "string", "sqlite3_stmt*"],
|
||||
["sqlite3_step", "int", "sqlite3_stmt*"],
|
||||
["sqlite3_strglob", "int", "string","string"],
|
||||
["sqlite3_strlike", "int", "string","string","int"],
|
||||
["sqlite3_total_changes", "int", "sqlite3*"],
|
||||
["sqlite3_value_blob", "*", "*"],
|
||||
["sqlite3_value_bytes","int", "*"],
|
||||
["sqlite3_value_double","f64", "*"],
|
||||
["sqlite3_value_text", "string", "*"],
|
||||
["sqlite3_value_type", "int", "*"],
|
||||
["sqlite3_vfs_find", "*", "string"],
|
||||
["sqlite3_vfs_register", "int", "*", "int"]
|
||||
]/*capi.wasm.bindingSignatures*/;
|
||||
|
||||
if(false && capi.wasm.compileOptionUsed('SQLITE_ENABLE_NORMALIZE')){
|
||||
/* ^^^ "the problem" is that this is an option feature and the
|
||||
build-time function-export list does not currently take
|
||||
optional features into account. */
|
||||
capi.wasm.bindingSignatures.push(["sqlite3_normalized_sql", "string", "sqlite3_stmt*"]);
|
||||
}
|
||||
|
||||
/**
|
||||
Functions which require BigInt (int64) support are separated from
|
||||
the others because we need to conditionally bind them or apply
|
||||
dummy impls, depending on the capabilities of the environment.
|
||||
*/
|
||||
capi.wasm.bindingSignatures.int64 = [
|
||||
["sqlite3_bind_int64","int", ["sqlite3_stmt*", "int", "i64"]],
|
||||
["sqlite3_changes64","i64", ["sqlite3*"]],
|
||||
["sqlite3_column_int64","i64", ["sqlite3_stmt*", "int"]],
|
||||
["sqlite3_total_changes64", "i64", ["sqlite3*"]]
|
||||
];
|
||||
|
||||
/* The remainder of the API will be set up in later steps. */
|
||||
return {
|
||||
capi,
|
||||
postInit: [
|
||||
/* some pieces of the API may install functions into this array,
|
||||
and each such function will be called, passed (self,sqlite3),
|
||||
at the very end of the API load/init process, where self is
|
||||
the current global object and sqlite3 is the object returned
|
||||
from sqlite3ApiBootstrap(). This array will be removed at the
|
||||
end of the API setup process. */],
|
||||
/** Config is needed downstream for gluing pieces together. It
|
||||
will be removed at the end of the API setup process. */
|
||||
config
|
||||
};
|
||||
}/*sqlite3ApiBootstrap()*/;
|
|
@ -0,0 +1,421 @@
|
|||
/*
|
||||
2022-07-22
|
||||
|
||||
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 implements a Worker-based wrapper around SQLite3 OO API
|
||||
#1.
|
||||
|
||||
In order to permit this API to be loaded in worker threads without
|
||||
automatically registering onmessage handlers, initializing the
|
||||
worker API requires calling initWorkerAPI(). If this function
|
||||
is called from a non-worker thread then it throws an exception.
|
||||
|
||||
When initialized, it installs message listeners to receive messages
|
||||
from the main thread and then it posts a message in the form:
|
||||
|
||||
```
|
||||
{type:'sqlite3-api',data:'worker-ready'}
|
||||
```
|
||||
|
||||
This file requires that the core C-style sqlite3 API and OO API #1
|
||||
have been loaded and that self.sqlite3 contains both,
|
||||
as documented for those APIs.
|
||||
*/
|
||||
self.sqlite3.initWorkerAPI = function(){
|
||||
'use strict';
|
||||
/**
|
||||
UNDER CONSTRUCTION
|
||||
|
||||
We need an API which can proxy the DB API via a Worker message
|
||||
interface. The primary quirky factor in such an API is that we
|
||||
cannot pass callback functions between the window thread and a
|
||||
worker thread, so we have to receive all db results via
|
||||
asynchronous message-passing. That requires an asychronous API
|
||||
with a distinctly different shape that the main OO API.
|
||||
|
||||
Certain important considerations here include:
|
||||
|
||||
- Support only one db connection or multiple? The former is far
|
||||
easier, but there's always going to be a user out there who wants
|
||||
to juggle six database handles at once. Do we add that complexity
|
||||
or tell such users to write their own code using the provided
|
||||
lower-level APIs?
|
||||
|
||||
- Fetching multiple results: do we pass them on as a series of
|
||||
messages, with start/end messages on either end, or do we collect
|
||||
all results and bundle them back in a single message? The former
|
||||
is, generically speaking, more memory-efficient but the latter
|
||||
far easier to implement in this environment. The latter is
|
||||
untennable for large data sets. Despite a web page hypothetically
|
||||
being a relatively limited environment, there will always be
|
||||
those users who feel that they should/need to be able to work
|
||||
with multi-hundred-meg (or larger) blobs, and passing around
|
||||
arrays of those may quickly exhaust the JS engine's memory.
|
||||
|
||||
TODOs include, but are not limited to:
|
||||
|
||||
- The ability to manage multiple DB handles. This can
|
||||
potentially be done via a simple mapping of DB.filename or
|
||||
DB.pointer (`sqlite3*` handle) to DB objects. The open()
|
||||
interface would need to provide an ID (probably DB.pointer) back
|
||||
to the user which can optionally be passed as an argument to
|
||||
the other APIs (they'd default to the first-opened DB, for
|
||||
ease of use). Client-side usability of this feature would
|
||||
benefit from making another wrapper class (or a singleton)
|
||||
available to the main thread, with that object proxying all(?)
|
||||
communication with the worker.
|
||||
|
||||
- Revisit how virtual files are managed. We currently delete DBs
|
||||
from the virtual filesystem when we close them, for the sake of
|
||||
saving memory (the VFS lives in RAM). Supporting multiple DBs may
|
||||
require that we give up that habit. Similarly, fully supporting
|
||||
ATTACH, where a user can upload multiple DBs and ATTACH them,
|
||||
also requires the that we manage the VFS entries better.
|
||||
*/
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
if('function' !== typeof importScripts){
|
||||
toss("Cannot initalize the sqlite3 worker API in the main thread.");
|
||||
}
|
||||
/* This is a web worker, so init the worker-based API. */
|
||||
const self = this.self;
|
||||
const sqlite3 = this.sqlite3 || toss("Missing self.sqlite3 object.");
|
||||
const SQLite3 = sqlite3.oo1 || toss("Missing self.sqlite3.oo1 OO API.");
|
||||
const DB = SQLite3.DB;
|
||||
|
||||
/**
|
||||
Returns the app-wide unique ID for the given db, creating one if
|
||||
needed.
|
||||
*/
|
||||
const getDbId = function(db){
|
||||
let id = wState.idMap.get(db);
|
||||
if(id) return id;
|
||||
id = 'db#'+(++wState.idSeq)+'@'+db.pointer;
|
||||
/** ^^^ can't simply use db.pointer b/c closing/opening may re-use
|
||||
the same address, which could map pending messages to a wrong
|
||||
instance. */
|
||||
wState.idMap.set(db, id);
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
Helper for managing Worker-level state.
|
||||
*/
|
||||
const wState = {
|
||||
defaultDb: undefined,
|
||||
idSeq: 0,
|
||||
idMap: new WeakMap,
|
||||
open: function(arg){
|
||||
// TODO: if arg is a filename, look for a db in this.dbs with the
|
||||
// same filename and close/reopen it (or just pass it back as is?).
|
||||
if(!arg && this.defaultDb) return this.defaultDb;
|
||||
//???if(this.defaultDb) this.defaultDb.close();
|
||||
let db;
|
||||
db = (Array.isArray(arg) ? new DB(...arg) : new DB(arg));
|
||||
this.dbs[getDbId(db)] = db;
|
||||
if(!this.defaultDb) this.defaultDb = db;
|
||||
return db;
|
||||
},
|
||||
close: function(db,alsoUnlink){
|
||||
if(db){
|
||||
delete this.dbs[getDbId(db)];
|
||||
db.close(alsoUnlink);
|
||||
if(db===this.defaultDb) this.defaultDb = undefined;
|
||||
}
|
||||
},
|
||||
post: function(type,data,xferList){
|
||||
if(xferList){
|
||||
self.postMessage({type, data},xferList);
|
||||
xferList.length = 0;
|
||||
}else{
|
||||
self.postMessage({type, data});
|
||||
}
|
||||
},
|
||||
/** Map of DB IDs to DBs. */
|
||||
dbs: Object.create(null),
|
||||
getDb: function(id,require=true){
|
||||
return this.dbs[id]
|
||||
|| (require ? toss("Unknown (or closed) DB ID:",id) : undefined);
|
||||
}
|
||||
};
|
||||
|
||||
/** Throws if the given db is falsy or not opened. */
|
||||
const affirmDbOpen = function(db = wState.defaultDb){
|
||||
return (db && db.pointer) ? db : toss("DB is not opened.");
|
||||
};
|
||||
|
||||
/** Extract dbId from the given message payload. */
|
||||
const getMsgDb = function(msgData,affirmExists=true){
|
||||
const db = wState.getDb(msgData.dbId,false) || wState.defaultDb;
|
||||
return affirmExists ? affirmDbOpen(db) : db;
|
||||
};
|
||||
|
||||
const getDefaultDbId = function(){
|
||||
return wState.defaultDb && getDbId(wState.defaultDb);
|
||||
};
|
||||
|
||||
/**
|
||||
A level of "organizational abstraction" for the Worker
|
||||
API. Each method in this object must map directly to a Worker
|
||||
message type key. The onmessage() dispatcher attempts to
|
||||
dispatch all inbound messages to a method of this object,
|
||||
passing it the event.data part of the inbound event object. All
|
||||
methods must return a plain Object containing any response
|
||||
state, which the dispatcher may amend. All methods must throw
|
||||
on error.
|
||||
*/
|
||||
const wMsgHandler = {
|
||||
xfer: [/*Temp holder for "transferable" postMessage() state.*/],
|
||||
/**
|
||||
Proxy for DB.exec() which expects a single argument of type
|
||||
string (SQL to execute) or an options object in the form
|
||||
expected by exec(). The notable differences from exec()
|
||||
include:
|
||||
|
||||
- The default value for options.rowMode is 'array' because
|
||||
the normal default cannot cross the window/Worker boundary.
|
||||
|
||||
- A function-type options.callback property cannot cross
|
||||
the window/Worker boundary, so is not useful here. If
|
||||
options.callback is a string then it is assumed to be a
|
||||
message type key, in which case a callback function will be
|
||||
applied which posts each row result via:
|
||||
|
||||
postMessage({type: thatKeyType, data: theRow})
|
||||
|
||||
And, at the end of the result set (whether or not any
|
||||
result rows were produced), it will post an identical
|
||||
message with data:null to alert the caller than the result
|
||||
set is completed.
|
||||
|
||||
The callback proxy must not recurse into this interface, or
|
||||
results are undefined. (It hypothetically cannot recurse
|
||||
because an exec() call will be tying up the Worker thread,
|
||||
causing any recursion attempt to wait until the first
|
||||
exec() is completed.)
|
||||
|
||||
The response is the input options object (or a synthesized
|
||||
one if passed only a string), noting that
|
||||
options.resultRows and options.columnNames may be populated
|
||||
by the call to exec().
|
||||
|
||||
This opens/creates the Worker's db if needed.
|
||||
*/
|
||||
exec: function(ev){
|
||||
const opt = (
|
||||
'string'===typeof ev.data
|
||||
) ? {sql: ev.data} : (ev.data || Object.create(null));
|
||||
if(undefined===opt.rowMode){
|
||||
/* Since the default rowMode of 'stmt' is not useful
|
||||
for the Worker interface, we'll default to
|
||||
something else. */
|
||||
opt.rowMode = 'array';
|
||||
}else if('stmt'===opt.rowMode){
|
||||
toss("Invalid rowMode for exec(): stmt mode",
|
||||
"does not work in the Worker API.");
|
||||
}
|
||||
const db = getMsgDb(ev);
|
||||
if(opt.callback || Array.isArray(opt.resultRows)){
|
||||
// Part of a copy-avoidance optimization for blobs
|
||||
db._blobXfer = this.xfer;
|
||||
}
|
||||
const callbackMsgType = opt.callback;
|
||||
if('string' === typeof callbackMsgType){
|
||||
/* Treat this as a worker message type and post each
|
||||
row as a message of that type. */
|
||||
const that = this;
|
||||
opt.callback =
|
||||
(row)=>wState.post(callbackMsgType,row,this.xfer);
|
||||
}
|
||||
try {
|
||||
db.exec(opt);
|
||||
if(opt.callback instanceof Function){
|
||||
opt.callback = callbackMsgType;
|
||||
wState.post(callbackMsgType, null);
|
||||
}
|
||||
}/*catch(e){
|
||||
console.warn("Worker is propagating:",e);throw e;
|
||||
}*/finally{
|
||||
delete db._blobXfer;
|
||||
if(opt.callback){
|
||||
opt.callback = callbackMsgType;
|
||||
}
|
||||
}
|
||||
return opt;
|
||||
}/*exec()*/,
|
||||
/**
|
||||
TO(re)DO, once we can abstract away access to the
|
||||
JS environment's virtual filesystem. Currently this
|
||||
always throws.
|
||||
|
||||
Response is (should be) an object:
|
||||
|
||||
{
|
||||
buffer: Uint8Array (db file contents),
|
||||
filename: the current db filename,
|
||||
mimetype: 'application/x-sqlite3'
|
||||
}
|
||||
|
||||
TODO is to determine how/whether this feature can support
|
||||
exports of ":memory:" and "" (temp file) DBs. The latter is
|
||||
ostensibly easy because the file is (potentially) on disk, but
|
||||
the former does not have a structure which maps directly to a
|
||||
db file image.
|
||||
*/
|
||||
export: function(ev){
|
||||
toss("export() requires reimplementing for portability reasons.");
|
||||
/**const db = getMsgDb(ev);
|
||||
const response = {
|
||||
buffer: db.exportBinaryImage(),
|
||||
filename: db.filename,
|
||||
mimetype: 'application/x-sqlite3'
|
||||
};
|
||||
this.xfer.push(response.buffer.buffer);
|
||||
return response;**/
|
||||
}/*export()*/,
|
||||
/**
|
||||
Proxy for the DB constructor. Expects to be passed a single
|
||||
object or a falsy value to use defaults. The object may
|
||||
have a filename property to name the db file (see the DB
|
||||
constructor for peculiarities and transformations) and/or a
|
||||
buffer property (a Uint8Array holding a complete database
|
||||
file's contents). The response is an object:
|
||||
|
||||
{
|
||||
filename: db filename (possibly differing from the input),
|
||||
|
||||
id: an opaque ID value intended for future distinction
|
||||
between multiple db handles. Messages including a specific
|
||||
ID will use the DB for that ID.
|
||||
|
||||
}
|
||||
|
||||
If the Worker's db is currently opened, this call closes it
|
||||
before proceeding.
|
||||
*/
|
||||
open: function(ev){
|
||||
wState.close(/*true???*/);
|
||||
const args = [], data = (ev.data || {});
|
||||
if(data.simulateError){
|
||||
toss("Throwing because of open.simulateError flag.");
|
||||
}
|
||||
if(data.filename) args.push(data.filename);
|
||||
if(data.buffer){
|
||||
args.push(data.buffer);
|
||||
this.xfer.push(data.buffer.buffer);
|
||||
}
|
||||
const db = wState.open(args);
|
||||
return {
|
||||
filename: db.filename,
|
||||
dbId: getDbId(db)
|
||||
};
|
||||
},
|
||||
/**
|
||||
Proxy for DB.close(). If ev.data may either be a boolean or
|
||||
an object with an `unlink` property. If that value is
|
||||
truthy then the db file (if the db is currently open) will
|
||||
be unlinked from the virtual filesystem, else it will be
|
||||
kept intact. The response object is:
|
||||
|
||||
{
|
||||
filename: db filename _if_ the db is opened when this
|
||||
is called, else the undefined value
|
||||
}
|
||||
*/
|
||||
close: function(ev){
|
||||
const db = getMsgDb(ev,false);
|
||||
const response = {
|
||||
filename: db && db.filename
|
||||
};
|
||||
if(db){
|
||||
wState.close(db, !!((ev.data && 'object'===typeof ev.data)
|
||||
? ev.data.unlink : ev.data));
|
||||
}
|
||||
return response;
|
||||
},
|
||||
toss: function(ev){
|
||||
toss("Testing worker exception");
|
||||
}
|
||||
}/*wMsgHandler*/;
|
||||
|
||||
/**
|
||||
UNDER CONSTRUCTION!
|
||||
|
||||
A subset of the DB API is accessible via Worker messages in the
|
||||
form:
|
||||
|
||||
{ type: apiCommand,
|
||||
dbId: optional DB ID value (not currently used!)
|
||||
data: apiArguments
|
||||
}
|
||||
|
||||
As a rule, these commands respond with a postMessage() of their
|
||||
own in the same form, but will, if needed, transform the `data`
|
||||
member to an object and may add state to it. The responses
|
||||
always have an object-format `data` part. If the inbound `data`
|
||||
is an object which has a `messageId` property, that property is
|
||||
always mirrored in the result object, for use in client-side
|
||||
dispatching of these asynchronous results. Exceptions thrown
|
||||
during processing result in an `error`-type event with a
|
||||
payload in the form:
|
||||
|
||||
{
|
||||
message: error string,
|
||||
errorClass: class name of the error type,
|
||||
dbId: DB handle ID,
|
||||
input: ev.data,
|
||||
[messageId: if set in the inbound message]
|
||||
}
|
||||
|
||||
The individual APIs are documented in the wMsgHandler object.
|
||||
*/
|
||||
self.onmessage = function(ev){
|
||||
ev = ev.data;
|
||||
let response, dbId = ev.dbId, evType = ev.type;
|
||||
const arrivalTime = performance.now();
|
||||
try {
|
||||
if(wMsgHandler.hasOwnProperty(evType) &&
|
||||
wMsgHandler[evType] instanceof Function){
|
||||
response = wMsgHandler[evType](ev);
|
||||
}else{
|
||||
toss("Unknown db worker message type:",ev.type);
|
||||
}
|
||||
}catch(err){
|
||||
evType = 'error';
|
||||
response = {
|
||||
message: err.message,
|
||||
errorClass: err.name,
|
||||
input: ev
|
||||
};
|
||||
if(err.stack){
|
||||
response.stack = ('string'===typeof err.stack)
|
||||
? err.stack.split('\n') : err.stack;
|
||||
}
|
||||
if(0) console.warn("Worker is propagating an exception to main thread.",
|
||||
"Reporting it _here_ for the stack trace:",err,response);
|
||||
}
|
||||
if(!response.messageId && ev.data
|
||||
&& 'object'===typeof ev.data && ev.data.messageId){
|
||||
response.messageId = ev.data.messageId;
|
||||
}
|
||||
if(!dbId){
|
||||
dbId = response.dbId/*from 'open' cmd*/
|
||||
|| getDefaultDbId();
|
||||
}
|
||||
if(!response.dbId) response.dbId = dbId;
|
||||
// Timing info is primarily for use in testing this API. It's not part of
|
||||
// the public API. arrivalTime = when the worker got the message.
|
||||
response.workerReceivedTime = arrivalTime;
|
||||
response.workerRespondTime = performance.now();
|
||||
response.departureTime = ev.departureTime;
|
||||
wState.post(evType, response, wMsgHandler.xfer);
|
||||
};
|
||||
setTimeout(()=>self.postMessage({type:'sqlite3-api',data:'worker-ready'}), 0);
|
||||
}.bind({self, sqlite3: self.sqlite3});
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
Dummy function stubs to get sqlite3.c compiling with
|
||||
wasi-sdk. This requires, in addition:
|
||||
|
||||
-D_WASI_EMULATED_MMAN -D_WASI_EMULATED_GETPID
|
||||
|
||||
-lwasi-emulated-getpid
|
||||
*/
|
||||
typedef unsigned mode_t;
|
||||
int fchmod(int fd, mode_t mode);
|
||||
int fchmod(int fd, mode_t mode){
|
||||
return (fd && mode) ? 0 : 0;
|
||||
}
|
||||
typedef unsigned uid_t;
|
||||
typedef uid_t gid_t;
|
||||
int fchown(int fd, uid_t owner, gid_t group);
|
||||
int fchown(int fd, uid_t owner, gid_t group){
|
||||
return (fd && owner && group) ? 0 : 0;
|
||||
}
|
||||
uid_t geteuid(void);
|
||||
uid_t geteuid(void){return 0;}
|
||||
#if !defined(F_WRLCK)
|
||||
enum {
|
||||
F_WRLCK,
|
||||
F_RDLCK,
|
||||
F_GETLK,
|
||||
F_SETLK,
|
||||
F_UNLCK
|
||||
};
|
||||
#endif
|
||||
|
||||
#undef HAVE_PREAD
|
||||
|
||||
#include <wasi/api.h>
|
||||
#define WASM__KEEP __attribute__((used))
|
||||
|
||||
#if 0
|
||||
/**
|
||||
wasi-sdk cannot build sqlite3's default VFS without at least the following
|
||||
functions. They are apparently syscalls which clients have to implement or
|
||||
otherwise obtain.
|
||||
|
||||
https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md
|
||||
*/
|
||||
environ_get
|
||||
environ_sizes_get
|
||||
clock_time_get
|
||||
fd_close
|
||||
fd_fdstat_get
|
||||
fd_fdstat_set_flags
|
||||
fd_filestat_get
|
||||
fd_filestat_set_size
|
||||
fd_pread
|
||||
fd_prestat_get
|
||||
fd_prestat_dir_name
|
||||
fd_read
|
||||
fd_seek
|
||||
fd_sync
|
||||
fd_write
|
||||
path_create_directory
|
||||
path_filestat_get
|
||||
path_filestat_set_times
|
||||
path_open
|
||||
path_readlink
|
||||
path_remove_directory
|
||||
path_unlink_file
|
||||
poll_oneoff
|
||||
proc_exit
|
||||
#endif
|
|
@ -0,0 +1,412 @@
|
|||
#include "sqlite3.c"
|
||||
|
||||
/*
|
||||
** This function is NOT part of the sqlite3 public API. It is strictly
|
||||
** for use by the sqlite project's own JS/WASM bindings.
|
||||
**
|
||||
** For purposes of certain hand-crafted C/Wasm function bindings, we
|
||||
** need a way of reporting errors which is consistent with the rest of
|
||||
** the C API. To that end, this internal-use-only function is a thin
|
||||
** proxy around sqlite3ErrorWithMessage(). The intent is that it only
|
||||
** be used from Wasm bindings such as sqlite3_prepare_v2/v3(), and
|
||||
** definitely not from client code.
|
||||
**
|
||||
** Returns err_code.
|
||||
*/
|
||||
int sqlite3_wasm_db_error(sqlite3*db, int err_code,
|
||||
const char *zMsg){
|
||||
if(0!=zMsg){
|
||||
const int nMsg = sqlite3Strlen30(zMsg);
|
||||
sqlite3ErrorWithMsg(db, err_code, "%.*s", nMsg, zMsg);
|
||||
}else{
|
||||
sqlite3ErrorWithMsg(db, err_code, NULL);
|
||||
}
|
||||
return err_code;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is NOT part of the sqlite3 public API. It is strictly
|
||||
** for use by the sqlite project's own JS/WASM bindings. Unlike the
|
||||
** rest of the sqlite3 API, this part requires C99 for snprintf() and
|
||||
** variadic macros.
|
||||
**
|
||||
** Returns a string containing a JSON-format "enum" of C-level
|
||||
** constants intended to be imported into the JS environment. The JSON
|
||||
** is initialized the first time this function is called and that
|
||||
** result is reused for all future calls.
|
||||
**
|
||||
** If this function returns NULL then it means that the internal
|
||||
** buffer is not large enough for the generated JSON. In debug builds
|
||||
** that will trigger an assert().
|
||||
*/
|
||||
const char * sqlite3_wasm_enum_json(void){
|
||||
static char strBuf[1024 * 8] = {0} /* where the JSON goes */;
|
||||
int n = 0, childCount = 0, structCount = 0
|
||||
/* output counters for figuring out where commas go */;
|
||||
char * pos = &strBuf[1] /* skip first byte for now to help protect
|
||||
** against a small race condition */;
|
||||
char const * const zEnd = pos + sizeof(strBuf) /* one-past-the-end */;
|
||||
if(strBuf[0]) return strBuf;
|
||||
/* Leave strBuf[0] at 0 until the end to help guard against a tiny
|
||||
** race condition. If this is called twice concurrently, they might
|
||||
** end up both writing to strBuf, but they'll both write the same
|
||||
** thing, so that's okay. If we set byte 0 up front then the 2nd
|
||||
** instance might return and use the string before the 1st instance
|
||||
** is done filling it. */
|
||||
|
||||
/* Core output macros... */
|
||||
#define lenCheck assert(pos < zEnd - 128 \
|
||||
&& "sqlite3_wasm_enum_json() buffer is too small."); \
|
||||
if(pos >= zEnd - 128) return 0
|
||||
#define outf(format,...) \
|
||||
pos += snprintf(pos, ((size_t)(zEnd - pos)), format, __VA_ARGS__); \
|
||||
lenCheck
|
||||
#define out(TXT) outf("%s",TXT)
|
||||
#define CloseBrace(LEVEL) \
|
||||
assert(LEVEL<5); memset(pos, '}', LEVEL); pos+=LEVEL; lenCheck
|
||||
|
||||
/* Macros for emitting maps of integer- and string-type macros to
|
||||
** their values. */
|
||||
#define DefGroup(KEY) n = 0; \
|
||||
outf("%s\"" #KEY "\": {",(childCount++ ? "," : ""));
|
||||
#define DefInt(KEY) \
|
||||
outf("%s\"%s\": %d", (n++ ? ", " : ""), #KEY, (int)KEY)
|
||||
#define DefStr(KEY) \
|
||||
outf("%s\"%s\": \"%s\"", (n++ ? ", " : ""), #KEY, KEY)
|
||||
#define _DefGroup CloseBrace(1)
|
||||
|
||||
DefGroup(version) {
|
||||
DefInt(SQLITE_VERSION_NUMBER);
|
||||
DefStr(SQLITE_VERSION);
|
||||
DefStr(SQLITE_SOURCE_ID);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(resultCodes) {
|
||||
DefInt(SQLITE_OK);
|
||||
DefInt(SQLITE_ERROR);
|
||||
DefInt(SQLITE_INTERNAL);
|
||||
DefInt(SQLITE_PERM);
|
||||
DefInt(SQLITE_ABORT);
|
||||
DefInt(SQLITE_BUSY);
|
||||
DefInt(SQLITE_LOCKED);
|
||||
DefInt(SQLITE_NOMEM);
|
||||
DefInt(SQLITE_READONLY);
|
||||
DefInt(SQLITE_INTERRUPT);
|
||||
DefInt(SQLITE_IOERR);
|
||||
DefInt(SQLITE_CORRUPT);
|
||||
DefInt(SQLITE_NOTFOUND);
|
||||
DefInt(SQLITE_FULL);
|
||||
DefInt(SQLITE_CANTOPEN);
|
||||
DefInt(SQLITE_PROTOCOL);
|
||||
DefInt(SQLITE_EMPTY);
|
||||
DefInt(SQLITE_SCHEMA);
|
||||
DefInt(SQLITE_TOOBIG);
|
||||
DefInt(SQLITE_CONSTRAINT);
|
||||
DefInt(SQLITE_MISMATCH);
|
||||
DefInt(SQLITE_MISUSE);
|
||||
DefInt(SQLITE_NOLFS);
|
||||
DefInt(SQLITE_AUTH);
|
||||
DefInt(SQLITE_FORMAT);
|
||||
DefInt(SQLITE_RANGE);
|
||||
DefInt(SQLITE_NOTADB);
|
||||
DefInt(SQLITE_NOTICE);
|
||||
DefInt(SQLITE_WARNING);
|
||||
DefInt(SQLITE_ROW);
|
||||
DefInt(SQLITE_DONE);
|
||||
|
||||
// Extended Result Codes
|
||||
DefInt(SQLITE_ERROR_MISSING_COLLSEQ);
|
||||
DefInt(SQLITE_ERROR_RETRY);
|
||||
DefInt(SQLITE_ERROR_SNAPSHOT);
|
||||
DefInt(SQLITE_IOERR_READ);
|
||||
DefInt(SQLITE_IOERR_SHORT_READ);
|
||||
DefInt(SQLITE_IOERR_WRITE);
|
||||
DefInt(SQLITE_IOERR_FSYNC);
|
||||
DefInt(SQLITE_IOERR_DIR_FSYNC);
|
||||
DefInt(SQLITE_IOERR_TRUNCATE);
|
||||
DefInt(SQLITE_IOERR_FSTAT);
|
||||
DefInt(SQLITE_IOERR_UNLOCK);
|
||||
DefInt(SQLITE_IOERR_RDLOCK);
|
||||
DefInt(SQLITE_IOERR_DELETE);
|
||||
DefInt(SQLITE_IOERR_BLOCKED);
|
||||
DefInt(SQLITE_IOERR_NOMEM);
|
||||
DefInt(SQLITE_IOERR_ACCESS);
|
||||
DefInt(SQLITE_IOERR_CHECKRESERVEDLOCK);
|
||||
DefInt(SQLITE_IOERR_LOCK);
|
||||
DefInt(SQLITE_IOERR_CLOSE);
|
||||
DefInt(SQLITE_IOERR_DIR_CLOSE);
|
||||
DefInt(SQLITE_IOERR_SHMOPEN);
|
||||
DefInt(SQLITE_IOERR_SHMSIZE);
|
||||
DefInt(SQLITE_IOERR_SHMLOCK);
|
||||
DefInt(SQLITE_IOERR_SHMMAP);
|
||||
DefInt(SQLITE_IOERR_SEEK);
|
||||
DefInt(SQLITE_IOERR_DELETE_NOENT);
|
||||
DefInt(SQLITE_IOERR_MMAP);
|
||||
DefInt(SQLITE_IOERR_GETTEMPPATH);
|
||||
DefInt(SQLITE_IOERR_CONVPATH);
|
||||
DefInt(SQLITE_IOERR_VNODE);
|
||||
DefInt(SQLITE_IOERR_AUTH);
|
||||
DefInt(SQLITE_IOERR_BEGIN_ATOMIC);
|
||||
DefInt(SQLITE_IOERR_COMMIT_ATOMIC);
|
||||
DefInt(SQLITE_IOERR_ROLLBACK_ATOMIC);
|
||||
DefInt(SQLITE_IOERR_DATA);
|
||||
DefInt(SQLITE_IOERR_CORRUPTFS);
|
||||
DefInt(SQLITE_LOCKED_SHAREDCACHE);
|
||||
DefInt(SQLITE_LOCKED_VTAB);
|
||||
DefInt(SQLITE_BUSY_RECOVERY);
|
||||
DefInt(SQLITE_BUSY_SNAPSHOT);
|
||||
DefInt(SQLITE_BUSY_TIMEOUT);
|
||||
DefInt(SQLITE_CANTOPEN_NOTEMPDIR);
|
||||
DefInt(SQLITE_CANTOPEN_ISDIR);
|
||||
DefInt(SQLITE_CANTOPEN_FULLPATH);
|
||||
DefInt(SQLITE_CANTOPEN_CONVPATH);
|
||||
//DefInt(SQLITE_CANTOPEN_DIRTYWAL)/*docs say not used*/;
|
||||
DefInt(SQLITE_CANTOPEN_SYMLINK);
|
||||
DefInt(SQLITE_CORRUPT_VTAB);
|
||||
DefInt(SQLITE_CORRUPT_SEQUENCE);
|
||||
DefInt(SQLITE_CORRUPT_INDEX);
|
||||
DefInt(SQLITE_READONLY_RECOVERY);
|
||||
DefInt(SQLITE_READONLY_CANTLOCK);
|
||||
DefInt(SQLITE_READONLY_ROLLBACK);
|
||||
DefInt(SQLITE_READONLY_DBMOVED);
|
||||
DefInt(SQLITE_READONLY_CANTINIT);
|
||||
DefInt(SQLITE_READONLY_DIRECTORY);
|
||||
DefInt(SQLITE_ABORT_ROLLBACK);
|
||||
DefInt(SQLITE_CONSTRAINT_CHECK);
|
||||
DefInt(SQLITE_CONSTRAINT_COMMITHOOK);
|
||||
DefInt(SQLITE_CONSTRAINT_FOREIGNKEY);
|
||||
DefInt(SQLITE_CONSTRAINT_FUNCTION);
|
||||
DefInt(SQLITE_CONSTRAINT_NOTNULL);
|
||||
DefInt(SQLITE_CONSTRAINT_PRIMARYKEY);
|
||||
DefInt(SQLITE_CONSTRAINT_TRIGGER);
|
||||
DefInt(SQLITE_CONSTRAINT_UNIQUE);
|
||||
DefInt(SQLITE_CONSTRAINT_VTAB);
|
||||
DefInt(SQLITE_CONSTRAINT_ROWID);
|
||||
DefInt(SQLITE_CONSTRAINT_PINNED);
|
||||
DefInt(SQLITE_CONSTRAINT_DATATYPE);
|
||||
DefInt(SQLITE_NOTICE_RECOVER_WAL);
|
||||
DefInt(SQLITE_NOTICE_RECOVER_ROLLBACK);
|
||||
DefInt(SQLITE_WARNING_AUTOINDEX);
|
||||
DefInt(SQLITE_AUTH_USER);
|
||||
DefInt(SQLITE_OK_LOAD_PERMANENTLY);
|
||||
//DefInt(SQLITE_OK_SYMLINK) /* internal use only */;
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(dataTypes) {
|
||||
DefInt(SQLITE_INTEGER);
|
||||
DefInt(SQLITE_FLOAT);
|
||||
DefInt(SQLITE_TEXT);
|
||||
DefInt(SQLITE_BLOB);
|
||||
DefInt(SQLITE_NULL);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(encodings) {
|
||||
/* Noting that the wasm binding only aims to support UTF-8. */
|
||||
DefInt(SQLITE_UTF8);
|
||||
DefInt(SQLITE_UTF16LE);
|
||||
DefInt(SQLITE_UTF16BE);
|
||||
DefInt(SQLITE_UTF16);
|
||||
/*deprecated DefInt(SQLITE_ANY); */
|
||||
DefInt(SQLITE_UTF16_ALIGNED);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(blobFinalizers) {
|
||||
/* SQLITE_STATIC/TRANSIENT need to be handled explicitly as
|
||||
** integers to avoid casting-related warnings. */
|
||||
out("\"SQLITE_STATIC\":0, \"SQLITE_TRANSIENT\":-1");
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(udfFlags) {
|
||||
DefInt(SQLITE_DETERMINISTIC);
|
||||
DefInt(SQLITE_DIRECTONLY);
|
||||
DefInt(SQLITE_INNOCUOUS);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(openFlags) {
|
||||
/* Noting that not all of these will have any effect in WASM-space. */
|
||||
DefInt(SQLITE_OPEN_READONLY);
|
||||
DefInt(SQLITE_OPEN_READWRITE);
|
||||
DefInt(SQLITE_OPEN_CREATE);
|
||||
DefInt(SQLITE_OPEN_URI);
|
||||
DefInt(SQLITE_OPEN_MEMORY);
|
||||
DefInt(SQLITE_OPEN_NOMUTEX);
|
||||
DefInt(SQLITE_OPEN_FULLMUTEX);
|
||||
DefInt(SQLITE_OPEN_SHAREDCACHE);
|
||||
DefInt(SQLITE_OPEN_PRIVATECACHE);
|
||||
DefInt(SQLITE_OPEN_EXRESCODE);
|
||||
DefInt(SQLITE_OPEN_NOFOLLOW);
|
||||
/* OPEN flags for use with VFSes... */
|
||||
DefInt(SQLITE_OPEN_MAIN_DB);
|
||||
DefInt(SQLITE_OPEN_MAIN_JOURNAL);
|
||||
DefInt(SQLITE_OPEN_TEMP_DB);
|
||||
DefInt(SQLITE_OPEN_TEMP_JOURNAL);
|
||||
DefInt(SQLITE_OPEN_TRANSIENT_DB);
|
||||
DefInt(SQLITE_OPEN_SUBJOURNAL);
|
||||
DefInt(SQLITE_OPEN_SUPER_JOURNAL);
|
||||
DefInt(SQLITE_OPEN_WAL);
|
||||
DefInt(SQLITE_OPEN_DELETEONCLOSE);
|
||||
DefInt(SQLITE_OPEN_EXCLUSIVE);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(syncFlags) {
|
||||
DefInt(SQLITE_SYNC_NORMAL);
|
||||
DefInt(SQLITE_SYNC_FULL);
|
||||
DefInt(SQLITE_SYNC_DATAONLY);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(prepareFlags) {
|
||||
DefInt(SQLITE_PREPARE_PERSISTENT);
|
||||
DefInt(SQLITE_PREPARE_NORMALIZE);
|
||||
DefInt(SQLITE_PREPARE_NO_VTAB);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(flock) {
|
||||
DefInt(SQLITE_LOCK_NONE);
|
||||
DefInt(SQLITE_LOCK_SHARED);
|
||||
DefInt(SQLITE_LOCK_RESERVED);
|
||||
DefInt(SQLITE_LOCK_PENDING);
|
||||
DefInt(SQLITE_LOCK_EXCLUSIVE);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(ioCap) {
|
||||
DefInt(SQLITE_IOCAP_ATOMIC);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC512);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC1K);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC2K);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC4K);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC8K);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC16K);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC32K);
|
||||
DefInt(SQLITE_IOCAP_ATOMIC64K);
|
||||
DefInt(SQLITE_IOCAP_SAFE_APPEND);
|
||||
DefInt(SQLITE_IOCAP_SEQUENTIAL);
|
||||
DefInt(SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN);
|
||||
DefInt(SQLITE_IOCAP_POWERSAFE_OVERWRITE);
|
||||
DefInt(SQLITE_IOCAP_IMMUTABLE);
|
||||
DefInt(SQLITE_IOCAP_BATCH_ATOMIC);
|
||||
} _DefGroup;
|
||||
|
||||
DefGroup(access){
|
||||
DefInt(SQLITE_ACCESS_EXISTS);
|
||||
DefInt(SQLITE_ACCESS_READWRITE);
|
||||
DefInt(SQLITE_ACCESS_READ)/*docs say this is unused*/;
|
||||
} _DefGroup;
|
||||
|
||||
#undef DefGroup
|
||||
#undef DefStr
|
||||
#undef DefInt
|
||||
#undef _DefGroup
|
||||
|
||||
/*
|
||||
** Emit an array of "StructBinder" struct descripions, which look
|
||||
** like:
|
||||
**
|
||||
** {
|
||||
** "name": "MyStruct",
|
||||
** "sizeof": 16,
|
||||
** "members": {
|
||||
** "member1": {"offset": 0,"sizeof": 4,"signature": "i"},
|
||||
** "member2": {"offset": 4,"sizeof": 4,"signature": "p"},
|
||||
** "member3": {"offset": 8,"sizeof": 8,"signature": "j"}
|
||||
** }
|
||||
** }
|
||||
**
|
||||
** Detailed documentation for those bits are in the docs for the
|
||||
** Jaccwabyt JS-side component.
|
||||
*/
|
||||
|
||||
/** Macros for emitting StructBinder description. */
|
||||
#define StructBinder__(TYPE) \
|
||||
n = 0; \
|
||||
outf("%s{", (structCount++ ? ", " : "")); \
|
||||
out("\"name\": \"" # TYPE "\","); \
|
||||
outf("\"sizeof\": %d", (int)sizeof(TYPE)); \
|
||||
out(",\"members\": {");
|
||||
#define StructBinder_(T) StructBinder__(T)
|
||||
/** ^^^ indirection needed to expand CurrentStruct */
|
||||
#define StructBinder StructBinder_(CurrentStruct)
|
||||
#define _StructBinder CloseBrace(2)
|
||||
#define M(MEMBER,SIG) \
|
||||
outf("%s\"%s\": " \
|
||||
"{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
|
||||
(n++ ? ", " : ""), #MEMBER, \
|
||||
(int)offsetof(CurrentStruct,MEMBER), \
|
||||
(int)sizeof(((CurrentStruct*)0)->MEMBER), \
|
||||
SIG)
|
||||
|
||||
structCount = 0;
|
||||
out(", \"structs\": ["); {
|
||||
|
||||
#define CurrentStruct sqlite3_vfs
|
||||
StructBinder {
|
||||
M(iVersion,"i");
|
||||
M(szOsFile,"i");
|
||||
M(mxPathname,"i");
|
||||
M(pNext,"p");
|
||||
M(zName,"s");
|
||||
M(pAppData,"p");
|
||||
M(xOpen,"i(pppip)");
|
||||
M(xDelete,"i(ppi)");
|
||||
M(xAccess,"i(ppip)");
|
||||
M(xFullPathname,"i(ppip)");
|
||||
M(xDlOpen,"p(pp)");
|
||||
M(xDlError,"p(pip)");
|
||||
M(xDlSym,"p()");
|
||||
M(xDlClose,"v(pp)");
|
||||
M(xRandomness,"i(pip)");
|
||||
M(xSleep,"i(pi)");
|
||||
M(xCurrentTime,"i(pp)");
|
||||
M(xGetLastError,"i(pip)");
|
||||
M(xCurrentTimeInt64,"i(pp)");
|
||||
M(xSetSystemCall,"i(ppp)");
|
||||
M(xGetSystemCall,"p(pp)");
|
||||
M(xNextSystemCall,"p(pp)");
|
||||
} _StructBinder;
|
||||
#undef CurrentStruct
|
||||
|
||||
#define CurrentStruct sqlite3_io_methods
|
||||
StructBinder {
|
||||
M(iVersion,"i");
|
||||
M(xClose,"i(p)");
|
||||
M(xRead,"i(ppij)");
|
||||
M(xWrite,"i(ppij)");
|
||||
M(xTruncate,"i(pj)");
|
||||
M(xSync,"i(pi)");
|
||||
M(xFileSize,"i(pp)");
|
||||
M(xLock,"i(pi)");
|
||||
M(xUnlock,"i(pi)");
|
||||
M(xCheckReservedLock,"i(pp)");
|
||||
M(xFileControl,"i(pip)");
|
||||
M(xSectorSize,"i(p)");
|
||||
M(xDeviceCharacteristics,"i(p)");
|
||||
M(xShmMap,"i(piiip)");
|
||||
M(xShmLock,"i(piii)");
|
||||
M(xShmBarrier,"v(p)");
|
||||
M(xShmUnmap,"i(pi)");
|
||||
M(xFetch,"i(pjip)");
|
||||
M(xUnfetch,"i(pjp)");
|
||||
} _StructBinder;
|
||||
#undef CurrentStruct
|
||||
|
||||
#define CurrentStruct sqlite3_file
|
||||
StructBinder {
|
||||
M(pMethods,"P");
|
||||
} _StructBinder;
|
||||
#undef CurrentStruct
|
||||
|
||||
} out( "]"/*structs*/);
|
||||
|
||||
out("}"/*top-level object*/);
|
||||
*pos = 0;
|
||||
strBuf[0] = '{'/*end of the race-condition workaround*/;
|
||||
return strBuf;
|
||||
#undef StructBinder
|
||||
#undef StructBinder_
|
||||
#undef StructBinder__
|
||||
#undef M
|
||||
#undef _StructBinder
|
||||
#undef CloseBrace
|
||||
#undef out
|
||||
#undef outf
|
||||
#undef lenCheck
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
2022-05-23
|
||||
|
||||
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 is a JS Worker file for the main sqlite3 api. It loads
|
||||
sqlite3.js, initializes the module, and postMessage()'s a message
|
||||
after the module is initialized:
|
||||
|
||||
{type: 'sqlite3-api', data: 'worker-ready'}
|
||||
|
||||
This seemingly superfluous level of indirection is necessary when
|
||||
loading sqlite3.js via a Worker. Instantiating a worker with new
|
||||
Worker("sqlite.js") will not (cannot) call sqlite3InitModule() to
|
||||
initialize the module due to a timing/order-of-operations conflict
|
||||
(and that symbol is not exported in a way that a Worker loading it
|
||||
that way can see it). Thus JS code wanting to load the sqlite3
|
||||
Worker-specific API needs to pass _this_ file (or equivalent) to the
|
||||
Worker constructor and then listen for an event in the form shown
|
||||
above in order to know when the module has completed initialization.
|
||||
*/
|
||||
"use strict";
|
||||
importScripts('sqlite3.js');
|
||||
sqlite3InitModule().then((EmscriptenModule)=>EmscriptenModule.sqlite3.initWorkerAPI());
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
2022-05-22
|
||||
|
||||
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 contains bootstrapping code used by various test scripts
|
||||
which live in this file's directory.
|
||||
*/
|
||||
'use strict';
|
||||
(function(self){
|
||||
/* querySelectorAll() proxy */
|
||||
const EAll = function(/*[element=document,] cssSelector*/){
|
||||
return (arguments.length>1 ? arguments[0] : document)
|
||||
.querySelectorAll(arguments[arguments.length-1]);
|
||||
};
|
||||
/* querySelector() proxy */
|
||||
const E = function(/*[element=document,] cssSelector*/){
|
||||
return (arguments.length>1 ? arguments[0] : document)
|
||||
.querySelector(arguments[arguments.length-1]);
|
||||
};
|
||||
|
||||
/**
|
||||
Helpers for writing sqlite3-specific tests.
|
||||
*/
|
||||
self.SqliteTestUtil = {
|
||||
/** Running total of the number of tests run via
|
||||
this API. */
|
||||
counter: 0,
|
||||
/**
|
||||
If expr is a function, it is called and its result
|
||||
is returned, coerced to a bool, else expr, coerced to
|
||||
a bool, is returned.
|
||||
*/
|
||||
toBool: function(expr){
|
||||
return (expr instanceof Function) ? !!expr() : !!expr;
|
||||
},
|
||||
/** abort() if expr is false. If expr is a function, it
|
||||
is called and its result is evaluated.
|
||||
*/
|
||||
assert: function f(expr, msg){
|
||||
if(!f._){
|
||||
f._ = ('undefined'===typeof abort
|
||||
? (msg)=>{throw new Error(msg)}
|
||||
: abort);
|
||||
}
|
||||
++this.counter;
|
||||
if(!this.toBool(expr)){
|
||||
f._(msg || "Assertion failed.");
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/** Identical to assert() but throws instead of calling
|
||||
abort(). */
|
||||
affirm: function(expr, msg){
|
||||
++this.counter;
|
||||
if(!this.toBool(expr)) throw new Error(msg || "Affirmation failed.");
|
||||
return this;
|
||||
},
|
||||
/** Calls f() and squelches any exception it throws. If it
|
||||
does not throw, this function throws. */
|
||||
mustThrow: function(f, msg){
|
||||
++this.counter;
|
||||
let err;
|
||||
try{ f(); } catch(e){err=e;}
|
||||
if(!err) throw new Error(msg || "Expected exception.");
|
||||
return this;
|
||||
},
|
||||
/**
|
||||
Works like mustThrow() but expects filter to be a regex,
|
||||
function, or string to match/filter the resulting exception
|
||||
against. If f() does not throw, this test fails and an Error is
|
||||
thrown. If filter is a regex, the test passes if
|
||||
filter.test(error.message) passes. If it's a function, the test
|
||||
passes if filter(error) returns truthy. If it's a string, the
|
||||
test passes if the filter matches the exception message
|
||||
precisely. In all other cases the test fails, throwing an
|
||||
Error.
|
||||
|
||||
If it throws, msg is used as the error report unless it's falsy,
|
||||
in which case a default is used.
|
||||
*/
|
||||
mustThrowMatching: function(f, filter, msg){
|
||||
++this.counter;
|
||||
let err;
|
||||
try{ f(); } catch(e){err=e;}
|
||||
if(!err) throw new Error(msg || "Expected exception.");
|
||||
let pass = false;
|
||||
if(filter instanceof RegExp) pass = filter.test(err.message);
|
||||
else if(filter instanceof Function) pass = filter(err);
|
||||
else if('string' === typeof filter) pass = (err.message === filter);
|
||||
if(!pass){
|
||||
throw new Error(msg || ("Filter rejected this exception: "+err.message));
|
||||
}
|
||||
return this;
|
||||
},
|
||||
/** Throws if expr is truthy or expr is a function and expr()
|
||||
returns truthy. */
|
||||
throwIf: function(expr, msg){
|
||||
++this.counter;
|
||||
if(this.toBool(expr)) throw new Error(msg || "throwIf() failed");
|
||||
return this;
|
||||
},
|
||||
/** Throws if expr is falsy or expr is a function and expr()
|
||||
returns falsy. */
|
||||
throwUnless: function(expr, msg){
|
||||
++this.counter;
|
||||
if(!this.toBool(expr)) throw new Error(msg || "throwUnless() failed");
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
This is a module object for use with the emscripten-installed
|
||||
sqlite3InitModule() factory function.
|
||||
*/
|
||||
self.sqlite3TestModule = {
|
||||
postRun: [
|
||||
/* function(theModule){...} */
|
||||
],
|
||||
//onRuntimeInitialized: function(){},
|
||||
/* Proxy for C-side stdout output. */
|
||||
print: function(){
|
||||
console.log.apply(console, Array.prototype.slice.call(arguments));
|
||||
},
|
||||
/* Proxy for C-side stderr output. */
|
||||
printErr: function(){
|
||||
console.error.apply(console, Array.prototype.slice.call(arguments));
|
||||
},
|
||||
/**
|
||||
Called by the module init bits to report loading
|
||||
progress. It gets passed an empty argument when loading is
|
||||
done (after onRuntimeInitialized() and any this.postRun
|
||||
callbacks have been run).
|
||||
*/
|
||||
setStatus: function f(text){
|
||||
if(!f.last){
|
||||
f.last = { text: '', step: 0 };
|
||||
f.ui = {
|
||||
status: E('#module-status'),
|
||||
progress: E('#module-progress'),
|
||||
spinner: E('#module-spinner')
|
||||
};
|
||||
}
|
||||
if(text === f.last.text) return;
|
||||
f.last.text = text;
|
||||
if(f.ui.progress){
|
||||
f.ui.progress.value = f.last.step;
|
||||
f.ui.progress.max = f.last.step + 1;
|
||||
}
|
||||
++f.last.step;
|
||||
if(text) {
|
||||
f.ui.status.classList.remove('hidden');
|
||||
f.ui.status.innerText = text;
|
||||
}else{
|
||||
if(f.ui.progress){
|
||||
f.ui.progress.remove();
|
||||
f.ui.spinner.remove();
|
||||
delete f.ui.progress;
|
||||
delete f.ui.spinner;
|
||||
}
|
||||
f.ui.status.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
})(self/*window or worker*/);
|
|
@ -29,3 +29,4 @@ span.labeled-input {
|
|||
color: red;
|
||||
background-color: yellow;
|
||||
}
|
||||
#test-output { font-family: monospace }
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,24 @@
|
|||
/* emcscript-related styling, used during the module load/intialization processes... */
|
||||
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
|
||||
div.emscripten { text-align: center; }
|
||||
div.emscripten_border { border: 1px solid black; }
|
||||
#module-spinner { overflow: visible; }
|
||||
#module-spinner > * {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.spinner {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin: 0px auto;
|
||||
animation: rotation 0.8s linear infinite;
|
||||
border-left: 10px solid rgb(0,150,240);
|
||||
border-right: 10px solid rgb(0,150,240);
|
||||
border-bottom: 10px solid rgb(0,150,240);
|
||||
border-top: 10px solid rgb(100,0,200);
|
||||
border-radius: 100%;
|
||||
background-color: rgb(200,100,250);
|
||||
}
|
||||
@keyframes rotation {
|
||||
from {transform: rotate(0deg);}
|
||||
to {transform: rotate(360deg);}
|
||||
}
|
|
@ -0,0 +1,737 @@
|
|||
/**
|
||||
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.
|
||||
|
||||
***********************************************************************
|
||||
|
||||
The Jaccwabyt API is documented in detail in an external file.
|
||||
|
||||
Project home: https://fossil.wanderinghorse.net/r/jaccwabyt
|
||||
|
||||
*/
|
||||
'use strict';
|
||||
self.Jaccwabyt = function StructBinderFactory(config){
|
||||
/* ^^^^ it is recommended that clients move that object into wherever
|
||||
they'd like to have it and delete the self-held copy ("self" being
|
||||
the global window or worker object). This API does not require the
|
||||
global reference - it is simply installed as a convenience for
|
||||
connecting these bits to other co-developed code before it gets
|
||||
removed from the global namespace.
|
||||
*/
|
||||
|
||||
/** Throws a new Error, the message of which is the concatenation
|
||||
all args with a space between each. */
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
|
||||
/**
|
||||
Implementing function bindings revealed significant
|
||||
shortcomings in Emscripten's addFunction()/removeFunction()
|
||||
interfaces:
|
||||
|
||||
https://github.com/emscripten-core/emscripten/issues/17323
|
||||
|
||||
Until those are resolved, or a suitable replacement can be
|
||||
implemented, our function-binding API will be more limited
|
||||
and/or clumsier to use than initially hoped.
|
||||
*/
|
||||
if(!(config.heap instanceof WebAssembly.Memory)
|
||||
&& !(config.heap instanceof Function)){
|
||||
toss("config.heap must be WebAssembly.Memory instance or a function.");
|
||||
}
|
||||
['alloc','dealloc'].forEach(function(k){
|
||||
(config[k] instanceof Function) ||
|
||||
toss("Config option '"+k+"' must be a function.");
|
||||
});
|
||||
const SBF = StructBinderFactory;
|
||||
const heap = (config.heap instanceof Function)
|
||||
? config.heap : (()=>new Uint8Array(config.heap.buffer)),
|
||||
alloc = config.alloc,
|
||||
dealloc = config.dealloc,
|
||||
log = config.log || console.log.bind(console),
|
||||
memberPrefix = (config.memberPrefix || ""),
|
||||
memberSuffix = (config.memberSuffix || ""),
|
||||
bigIntEnabled = (undefined===config.bigIntEnabled
|
||||
? !!self['BigInt64Array'] : !!config.bigIntEnabled),
|
||||
BigInt = self['BigInt'],
|
||||
BigInt64Array = self['BigInt64Array'],
|
||||
/* Undocumented (on purpose) config options: */
|
||||
functionTable = config.functionTable/*EXPERIMENTAL, undocumented*/,
|
||||
ptrSizeof = config.ptrSizeof || 4,
|
||||
ptrIR = config.ptrIR || 'i32'
|
||||
;
|
||||
|
||||
if(!SBF.debugFlags){
|
||||
SBF.__makeDebugFlags = function(deriveFrom=null){
|
||||
/* This is disgustingly overengineered. :/ */
|
||||
if(deriveFrom && deriveFrom.__flags) deriveFrom = deriveFrom.__flags;
|
||||
const f = function f(flags){
|
||||
if(0===arguments.length){
|
||||
return f.__flags;
|
||||
}
|
||||
if(flags<0){
|
||||
delete f.__flags.getter; delete f.__flags.setter;
|
||||
delete f.__flags.alloc; delete f.__flags.dealloc;
|
||||
}else{
|
||||
f.__flags.getter = 0!==(0x01 & flags);
|
||||
f.__flags.setter = 0!==(0x02 & flags);
|
||||
f.__flags.alloc = 0!==(0x04 & flags);
|
||||
f.__flags.dealloc = 0!==(0x08 & flags);
|
||||
}
|
||||
return f._flags;
|
||||
};
|
||||
Object.defineProperty(f,'__flags', {
|
||||
iterable: false, writable: false,
|
||||
value: Object.create(deriveFrom)
|
||||
});
|
||||
if(!deriveFrom) f(0);
|
||||
return f;
|
||||
};
|
||||
SBF.debugFlags = SBF.__makeDebugFlags();
|
||||
}/*static init*/
|
||||
|
||||
const isLittleEndian = (function() {
|
||||
const buffer = new ArrayBuffer(2);
|
||||
new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
|
||||
// Int16Array uses the platform's endianness.
|
||||
return new Int16Array(buffer)[0] === 256;
|
||||
})();
|
||||
/**
|
||||
Some terms used in the internal docs:
|
||||
|
||||
StructType: a struct-wrapping class generated by this
|
||||
framework.
|
||||
DEF: struct description object.
|
||||
SIG: struct member signature string.
|
||||
*/
|
||||
|
||||
/** True if SIG s looks like a function signature, else
|
||||
false. */
|
||||
const isFuncSig = (s)=>'('===s[1];
|
||||
/** True if SIG s is-a pointer signature. */
|
||||
const isPtrSig = (s)=>'p'===s || 'P'===s;
|
||||
const isAutoPtrSig = (s)=>'P'===s /*EXPERIMENTAL*/;
|
||||
const sigLetter = (s)=>isFuncSig(s) ? 'p' : s[0];
|
||||
/** Returns the WASM IR form of the Emscripten-conventional letter
|
||||
at SIG s[0]. Throws for an unknown SIG. */
|
||||
const sigIR = function(s){
|
||||
switch(sigLetter(s)){
|
||||
case 'i': return 'i32';
|
||||
case 'p': case 'P': case 's': return ptrIR;
|
||||
case 'j': return 'i64';
|
||||
case 'f': return 'float';
|
||||
case 'd': return 'double';
|
||||
}
|
||||
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
|
||||
? ()=>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
|
||||
to the given SIG. */
|
||||
const sigDVGetter = function(s){
|
||||
switch(sigLetter(s)) {
|
||||
case 'p': case 'P': case 's': {
|
||||
switch(ptrSizeof){
|
||||
case 4: return 'getInt32';
|
||||
case 8: return affirmBigIntArray() && 'getBigInt64';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'i': return 'getInt32';
|
||||
case 'j': return affirmBigIntArray() && 'getBigInt64';
|
||||
case 'f': return 'getFloat32';
|
||||
case 'd': return 'getFloat64';
|
||||
}
|
||||
toss("Unhandled DataView getter for signature:",s);
|
||||
};
|
||||
/** Returns the name of a DataView setter method corresponding
|
||||
to the given SIG. */
|
||||
const sigDVSetter = function(s){
|
||||
switch(sigLetter(s)){
|
||||
case 'p': case 'P': case 's': {
|
||||
switch(ptrSizeof){
|
||||
case 4: return 'setInt32';
|
||||
case 8: return affirmBigIntArray() && 'setBigInt64';
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'i': return 'setInt32';
|
||||
case 'j': return affirmBigIntArray() && 'setBigInt64';
|
||||
case 'f': return 'setFloat32';
|
||||
case 'd': return 'setFloat64';
|
||||
}
|
||||
toss("Unhandled DataView setter for signature:",s);
|
||||
};
|
||||
/**
|
||||
Returns either Number of BigInt, depending on the given
|
||||
SIG. This constructor is used in property setters to coerce
|
||||
the being-set value to the correct size.
|
||||
*/
|
||||
const sigDVSetWrapper = function(s){
|
||||
switch(sigLetter(s)) {
|
||||
case 'i': case 'f': case 'd': return Number;
|
||||
case 'j': return affirmBigIntArray() && BigInt;
|
||||
case 'p': case 'P': case 's':
|
||||
switch(ptrSizeof){
|
||||
case 4: return Number;
|
||||
case 8: return affirmBigIntArray() && BigInt;
|
||||
}
|
||||
break;
|
||||
}
|
||||
toss("Unhandled DataView set wrapper for signature:",s);
|
||||
};
|
||||
|
||||
const sPropName = (s,k)=>s+'::'+k;
|
||||
|
||||
const __propThrowOnSet = function(structName,propName){
|
||||
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
|
||||
pointers from JS code, we store them in a scope-local
|
||||
WeakMap which maps the struct-bound objects to their WASM
|
||||
pointers. The pointers are accessible via
|
||||
boundObject.pointer, which is gated behind an accessor
|
||||
function, but are not exposed anywhere else in the
|
||||
object. The main intention of that is to make it impossible
|
||||
for stale copies to be made.
|
||||
*/
|
||||
const __instancePointerMap = new WeakMap();
|
||||
|
||||
/** Property name for the pointer-is-external marker. */
|
||||
const xPtrPropName = '(pointer-is-external)';
|
||||
|
||||
/** Frees the obj.pointer memory and clears the pointer
|
||||
property. */
|
||||
const __freeStruct = function(ctor, obj, m){
|
||||
if(!m) m = __instancePointerMap.get(obj);
|
||||
if(m) {
|
||||
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);
|
||||
}
|
||||
}else if(Array.isArray(obj.ondispose)){
|
||||
obj.ondispose.forEach(function(x){
|
||||
try{
|
||||
if(x instanceof Function) x.call(obj);
|
||||
else if('number' === typeof x) dealloc(x);
|
||||
// else ignore. Strings are permitted to annotate entries
|
||||
// to assist in debugging.
|
||||
}catch(e){
|
||||
console.warn("ondispose() for",ctor.structName,'@',
|
||||
m,'threw. NOT propagating it.',e);
|
||||
}
|
||||
});
|
||||
}
|
||||
delete obj.ondispose;
|
||||
delete __ptrBacklinks.get(ctor)[m];
|
||||
delete __ptrBacklinksGlobal[m];
|
||||
__instancePointerMap.delete(obj);
|
||||
if(ctor.debugFlags.__flags.dealloc){
|
||||
log("debug.dealloc:",(obj[xPtrPropName]?"EXTERNAL":""),
|
||||
ctor.structName,"instance:",
|
||||
ctor.structInfo.sizeof,"bytes @"+m);
|
||||
}
|
||||
if(!obj[xPtrPropName]) dealloc(m);
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns a skeleton for a read-only property accessor wrapping
|
||||
value v. */
|
||||
const rop = (v)=>{return {configurable: false, writable: false,
|
||||
iterable: false, value: v}};
|
||||
|
||||
/** Allocates obj's memory buffer based on the size defined in
|
||||
DEF.sizeof. */
|
||||
const __allocStruct = function(ctor, obj, m){
|
||||
let fill = !m;
|
||||
if(m) Object.defineProperty(obj, xPtrPropName, rop(m));
|
||||
else{
|
||||
m = alloc(ctor.structInfo.sizeof);
|
||||
if(!m) toss("Allocation of",ctor.structName,"structure failed.");
|
||||
}
|
||||
try {
|
||||
if(ctor.debugFlags.__flags.alloc){
|
||||
log("debug.alloc:",(fill?"":"EXTERNAL"),
|
||||
ctor.structName,"instance:",
|
||||
ctor.structInfo.sizeof,"bytes @"+m);
|
||||
}
|
||||
if(fill) heap().fill(0, m, m + ctor.structInfo.sizeof);
|
||||
__instancePointerMap.set(obj, m);
|
||||
__ptrBacklinks.get(ctor)[m] = obj;
|
||||
__ptrBacklinksGlobal[m] = obj;
|
||||
}catch(e){
|
||||
__freeStruct(ctor, obj, m);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
/** Gets installed as the memoryDump() method of all structs. */
|
||||
const __memoryDump = function(){
|
||||
const p = this.pointer;
|
||||
return p
|
||||
? new Uint8Array(heap().slice(p, p+this.structInfo.sizeof))
|
||||
: null;
|
||||
};
|
||||
|
||||
const __memberKey = (k)=>memberPrefix + k + memberSuffix;
|
||||
const __memberKeyProp = rop(__memberKey);
|
||||
|
||||
/**
|
||||
Looks up a struct member in structInfo.members. Throws if found
|
||||
if tossIfNotFound is true, else returns undefined if not
|
||||
found. The given name may be either the name of the
|
||||
structInfo.members key (faster) or the key as modified by the
|
||||
memberPrefix/memberSuffix settings.
|
||||
*/
|
||||
const __lookupMember = function(structInfo, memberName, tossIfNotFound=true){
|
||||
let m = structInfo.members[memberName];
|
||||
if(!m && (memberPrefix || memberSuffix)){
|
||||
// Check for a match on members[X].key
|
||||
for(const v of Object.values(structInfo.members)){
|
||||
if(v.key===memberName){ m = v; break; }
|
||||
}
|
||||
if(!m && tossIfNotFound){
|
||||
toss(sPropName(structInfo.name,memberName),'is not a mapped struct member.');
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
Uses __lookupMember(obj.structInfo,memberName) to find a member,
|
||||
throwing if not found. Returns its signature, either in this
|
||||
framework's native format or in Emscripten format.
|
||||
*/
|
||||
const __memberSignature = function f(obj,memberName,emscriptenFormat=false){
|
||||
if(!f._) f._ = (x)=>x.replace(/[^vipPsjrd]/g,'').replace(/[pPs]/g,'i');
|
||||
const m = __lookupMember(obj.structInfo, memberName, true);
|
||||
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 = {
|
||||
configurable: false, enumerable: false,
|
||||
get: function(){return __instancePointerMap.get(this)},
|
||||
set: ()=>toss("Cannot assign the 'pointer' property of a struct.")
|
||||
// Reminder: leaving `set` undefined makes assignments
|
||||
// to the property _silently_ do nothing. Current unit tests
|
||||
// rely on it throwing, though.
|
||||
};
|
||||
|
||||
/** Impl of X.memberKeys() for StructType and struct ctors. */
|
||||
const __structMemberKeys = rop(function(){
|
||||
const a = [];
|
||||
Object.keys(this.structInfo.members).forEach((k)=>a.push(this.memberKey(k)));
|
||||
return a;
|
||||
});
|
||||
|
||||
const __utf8Decoder = new TextDecoder('utf-8');
|
||||
const __utf8Encoder = new TextEncoder();
|
||||
|
||||
/**
|
||||
Uses __lookupMember() to find the given obj.structInfo key.
|
||||
Returns that member if it is a string, else returns false. If the
|
||||
member is not found, throws if tossIfNotFound is true, else
|
||||
returns false.
|
||||
*/
|
||||
const __memberIsString = function(obj,memberName, tossIfNotFound=false){
|
||||
const m = __lookupMember(obj.structInfo, memberName, tossIfNotFound);
|
||||
return (m && 1===m.signature.length && 's'===m.signature[0]) ? m : false;
|
||||
};
|
||||
|
||||
/**
|
||||
Given a member description object, throws if member.signature is
|
||||
not valid for assigning to or interpretation as a C-style string.
|
||||
It optimistically assumes that any signature of (i,p,s) is
|
||||
C-string compatible.
|
||||
*/
|
||||
const __affirmCStringSignature = function(member){
|
||||
if('s'===member.signature) return;
|
||||
toss("Invalid member type signature for C-string value:",
|
||||
JSON.stringify(member));
|
||||
};
|
||||
|
||||
/**
|
||||
Looks up the given member in obj.structInfo. If it has a
|
||||
signature of 's' then it is assumed to be a C-style UTF-8 string
|
||||
and a decoded copy of the string at its address is returned. If
|
||||
the signature is of any other type, it throws. If an s-type
|
||||
member's address is 0, `null` is returned.
|
||||
*/
|
||||
const __memberToJsString = function f(obj,memberName){
|
||||
const m = __lookupMember(obj.structInfo, memberName, true);
|
||||
__affirmCStringSignature(m);
|
||||
const addr = obj[m.key];
|
||||
//log("addr =",addr,memberName,"m =",m);
|
||||
if(!addr) return null;
|
||||
let pos = addr;
|
||||
const mem = heap();
|
||||
for( ; mem[pos]!==0; ++pos ) {
|
||||
//log("mem[",pos,"]",mem[pos]);
|
||||
};
|
||||
//log("addr =",addr,"pos =",pos);
|
||||
if(addr===pos) return "";
|
||||
return __utf8Decoder.decode(new Uint8Array(mem.buffer, addr, pos-addr));
|
||||
};
|
||||
|
||||
/**
|
||||
Adds value v to obj.ondispose, creating ondispose,
|
||||
or converting it to an array, if needed.
|
||||
*/
|
||||
const __addOnDispose = function(obj, v){
|
||||
if(obj.ondispose){
|
||||
if(obj.ondispose instanceof Function){
|
||||
obj.ondispose = [obj.ondispose];
|
||||
}/*else assume it's an array*/
|
||||
}else{
|
||||
obj.ondispose = [];
|
||||
}
|
||||
obj.ondispose.push(v);
|
||||
};
|
||||
|
||||
/**
|
||||
Allocates a new UTF-8-encoded, NUL-terminated copy of the given
|
||||
JS string and returns its address relative to heap(). If
|
||||
allocation returns 0 this function throws. Ownership of the
|
||||
memory is transfered to the caller, who must eventually pass it
|
||||
to the configured dealloc() function.
|
||||
*/
|
||||
const __allocCString = function(str){
|
||||
const u = __utf8Encoder.encode(str);
|
||||
const mem = alloc(u.length+1);
|
||||
if(!mem) toss("Allocation error while duplicating string:",str);
|
||||
const h = heap();
|
||||
let i = 0;
|
||||
for( ; i < u.length; ++i ) h[mem + i] = u[i];
|
||||
h[mem + u.length] = 0;
|
||||
//log("allocCString @",mem," =",u);
|
||||
return mem;
|
||||
};
|
||||
|
||||
/**
|
||||
Sets the given struct member of obj to a dynamically-allocated,
|
||||
UTF-8-encoded, NUL-terminated copy of str. It is up to the caller
|
||||
to free any prior memory, if appropriate. The newly-allocated
|
||||
string is added to obj.ondispose so will be freed when the object
|
||||
is disposed.
|
||||
*/
|
||||
const __setMemberCString = function(obj, memberName, str){
|
||||
const m = __lookupMember(obj.structInfo, memberName, true);
|
||||
__affirmCStringSignature(m);
|
||||
/* Potential TODO: if obj.ondispose contains obj[m.key] then
|
||||
dealloc that value and clear that ondispose entry */
|
||||
const mem = __allocCString(str);
|
||||
obj[m.key] = mem;
|
||||
__addOnDispose(obj, mem);
|
||||
return obj;
|
||||
};
|
||||
|
||||
/**
|
||||
Prototype for all StructFactory instances (the constructors
|
||||
returned from StructBinder).
|
||||
*/
|
||||
const StructType = function ctor(structName, structInfo){
|
||||
if(arguments[2]!==rop){
|
||||
toss("Do not call the StructType constructor",
|
||||
"from client-level code.");
|
||||
}
|
||||
Object.defineProperties(this,{
|
||||
//isA: rop((v)=>v instanceof ctor),
|
||||
structName: rop(structName),
|
||||
structInfo: rop(structInfo)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
Properties inherited by struct-type-specific StructType instances
|
||||
and (indirectly) concrete struct-type instances.
|
||||
*/
|
||||
StructType.prototype = Object.create(null, {
|
||||
dispose: rop(function(){__freeStruct(this.constructor, this)}),
|
||||
lookupMember: rop(function(memberName, tossIfNotFound=true){
|
||||
return __lookupMember(this.structInfo, memberName, tossIfNotFound);
|
||||
}),
|
||||
memberToJsString: rop(function(memberName){
|
||||
return __memberToJsString(this, memberName);
|
||||
}),
|
||||
memberIsString: rop(function(memberName, tossIfNotFound=true){
|
||||
return __memberIsString(this, memberName, tossIfNotFound);
|
||||
}),
|
||||
memberKey: __memberKeyProp,
|
||||
memberKeys: __structMemberKeys,
|
||||
memberSignature: rop(function(memberName, emscriptenFormat=false){
|
||||
return __memberSignature(this, memberName, emscriptenFormat);
|
||||
}),
|
||||
memoryDump: rop(__memoryDump),
|
||||
pointer: __ptrPropDescriptor,
|
||||
setMemberCString: rop(function(memberName, str){
|
||||
return __setMemberCString(this, memberName, str);
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
"Static" properties for StructType.
|
||||
*/
|
||||
Object.defineProperties(StructType, {
|
||||
allocCString: rop(__allocCString),
|
||||
instanceForPointer: rop((ptr)=>__ptrBacklinksGlobal[ptr]),
|
||||
isA: rop((v)=>v instanceof StructType),
|
||||
hasExternalPointer: rop((v)=>(v instanceof StructType) && !!v[xPtrPropName]),
|
||||
memberKey: __memberKeyProp
|
||||
});
|
||||
|
||||
const isNumericValue = (v)=>Number.isFinite(v) || (v instanceof (BigInt || Number));
|
||||
|
||||
/**
|
||||
Pass this a StructBinder-generated prototype, and the struct
|
||||
member description object. It will define property accessors for
|
||||
proto[memberKey] which read from/write to memory in
|
||||
this.pointer. It modifies descr to make certain downstream
|
||||
operations much simpler.
|
||||
*/
|
||||
const makeMemberWrapper = function f(ctor,name, descr){
|
||||
if(!f._){
|
||||
/*cache all available getters/setters/set-wrappers for
|
||||
direct reuse in each accessor function. */
|
||||
f._ = {getters: {}, setters: {}, sw:{}};
|
||||
const a = ['i','p','P','s','f','d','v()'];
|
||||
if(bigIntEnabled) a.push('j');
|
||||
a.forEach(function(v){
|
||||
//const ir = sigIR(v);
|
||||
f._.getters[v] = sigDVGetter(v) /* DataView[MethodName] values for GETTERS */;
|
||||
f._.setters[v] = sigDVSetter(v) /* DataView[MethodName] values for SETTERS */;
|
||||
f._.sw[v] = sigDVSetWrapper(v) /* BigInt or Number ctor to wrap around values
|
||||
for conversion */;
|
||||
});
|
||||
const rxSig1 = /^[ipPsjfd]$/,
|
||||
rxSig2 = /^[vipPsjfd]\([ipPsjfd]*\)$/;
|
||||
f.sigCheck = function(obj, name, key,sig){
|
||||
if(Object.prototype.hasOwnProperty.call(obj, key)){
|
||||
toss(obj.structName,'already has a property named',key+'.');
|
||||
}
|
||||
rxSig1.test(sig) || rxSig2.test(sig)
|
||||
|| toss("Malformed signature for",
|
||||
sPropName(obj.structName,name)+":",sig);
|
||||
};
|
||||
}
|
||||
const key = ctor.memberKey(name);
|
||||
f.sigCheck(ctor.prototype, name, key, descr.signature);
|
||||
descr.key = key;
|
||||
descr.name = name;
|
||||
const sizeOf = sigSizeof(descr.signature);
|
||||
const sigGlyph = sigLetter(descr.signature);
|
||||
const xPropName = sPropName(ctor.prototype.structName,key);
|
||||
const dbg = ctor.prototype.debugFlags.__flags;
|
||||
/*
|
||||
TODO?: set prototype of descr to an object which can set/fetch
|
||||
its prefered representation, e.g. conversion to string or mapped
|
||||
function. Advantage: we can avoid doing that via if/else if/else
|
||||
in the get/set methods.
|
||||
*/
|
||||
const prop = Object.create(null);
|
||||
prop.configurable = false;
|
||||
prop.enumerable = false;
|
||||
prop.get = function(){
|
||||
if(dbg.getter){
|
||||
log("debug.getter:",f._.getters[sigGlyph],"for", sigIR(sigGlyph),
|
||||
xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf);
|
||||
}
|
||||
let rc = (
|
||||
new DataView(heap().buffer, this.pointer + descr.offset, sizeOf)
|
||||
)[f._.getters[sigGlyph]](0, isLittleEndian);
|
||||
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;
|
||||
};
|
||||
if(descr.readOnly){
|
||||
prop.set = __propThrowOnSet(ctor.prototype.structName,key);
|
||||
}else{
|
||||
prop.set = function(v){
|
||||
if(dbg.setter){
|
||||
log("debug.setter:",f._.setters[sigGlyph],"for", sigIR(sigGlyph),
|
||||
xPropName,'@', this.pointer,'+',descr.offset,'sz',sizeOf, v);
|
||||
}
|
||||
if(!this.pointer){
|
||||
toss("Cannot set struct property on disposed instance.");
|
||||
}
|
||||
if(null===v) v = 0;
|
||||
else while(!isNumericValue(v)){
|
||||
if(isAutoPtrSig(descr.signature) && (v instanceof StructType)){
|
||||
// It's a struct instance: let's store its pointer value!
|
||||
v = v.pointer || 0;
|
||||
if(dbg.setter) log("debug.setter:",xPropName,"resolved to",v);
|
||||
break;
|
||||
}
|
||||
toss("Invalid value for pointer-type",xPropName+'.');
|
||||
}
|
||||
(
|
||||
new DataView(heap().buffer, this.pointer + descr.offset, sizeOf)
|
||||
)[f._.setters[sigGlyph]](0, f._.sw[sigGlyph](v), isLittleEndian);
|
||||
};
|
||||
}
|
||||
Object.defineProperty(ctor.prototype, key, prop);
|
||||
}/*makeMemberWrapper*/;
|
||||
|
||||
/**
|
||||
The main factory function which will be returned to the
|
||||
caller.
|
||||
*/
|
||||
const StructBinder = function StructBinder(structName, structInfo){
|
||||
if(1===arguments.length){
|
||||
structInfo = structName;
|
||||
structName = structInfo.name;
|
||||
}else if(!structInfo.name){
|
||||
structInfo.name = structName;
|
||||
}
|
||||
if(!structName) toss("Struct name is required.");
|
||||
let lastMember = false;
|
||||
Object.keys(structInfo.members).forEach((k)=>{
|
||||
const m = structInfo.members[k];
|
||||
if(!m.sizeof) toss(structName,"member",k,"is missing sizeof.");
|
||||
else if(0!==(m.sizeof%4)){
|
||||
toss(structName,"member",k,"sizeof is not aligned.");
|
||||
}
|
||||
else if(0!==(m.offset%4)){
|
||||
toss(structName,"member",k,"offset is not aligned.");
|
||||
}
|
||||
if(!lastMember || lastMember.offset < m.offset) lastMember = m;
|
||||
});
|
||||
if(!lastMember) toss("No member property descriptions found.");
|
||||
else if(structInfo.sizeof < lastMember.offset+lastMember.sizeof){
|
||||
toss("Invalid struct config:",structName,
|
||||
"max member offset ("+lastMember.offset+") ",
|
||||
"extends past end of struct (sizeof="+structInfo.sizeof+").");
|
||||
}
|
||||
const debugFlags = rop(SBF.__makeDebugFlags(StructBinder.debugFlags));
|
||||
/** Constructor for the StructCtor. */
|
||||
const StructCtor = function StructCtor(externalMemory){
|
||||
if(!(this instanceof StructCtor)){
|
||||
toss("The",structName,"constructor may only be called via 'new'.");
|
||||
}else if(arguments.length){
|
||||
if(externalMemory!==(externalMemory|0) || externalMemory<=0){
|
||||
toss("Invalid pointer value for",structName,"constructor.");
|
||||
}
|
||||
__allocStruct(StructCtor, this, externalMemory);
|
||||
}else{
|
||||
__allocStruct(StructCtor, this);
|
||||
}
|
||||
};
|
||||
Object.defineProperties(StructCtor,{
|
||||
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),
|
||||
memberKey: __memberKeyProp,
|
||||
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){
|
||||
}),
|
||||
structInfo: rop(structInfo),
|
||||
structName: rop(structName)
|
||||
});
|
||||
StructCtor.prototype = new StructType(structName, structInfo, rop);
|
||||
Object.defineProperties(StructCtor.prototype,{
|
||||
debugFlags: debugFlags,
|
||||
constructor: rop(StructCtor)
|
||||
/*if we assign StructCtor.prototype and don't do
|
||||
this then StructCtor!==instance.constructor!*/
|
||||
});
|
||||
Object.keys(structInfo.members).forEach(
|
||||
(name)=>makeMemberWrapper(StructCtor, name, structInfo.members[name])
|
||||
);
|
||||
return StructCtor;
|
||||
};
|
||||
StructBinder.instanceForPointer = StructType.instanceForPointer;
|
||||
StructBinder.StructType = StructType;
|
||||
StructBinder.config = config;
|
||||
StructBinder.allocCString = __allocCString;
|
||||
if(!StructBinder.debugFlags){
|
||||
StructBinder.debugFlags = SBF.__makeDebugFlags(SBF.debugFlags);
|
||||
}
|
||||
return StructBinder;
|
||||
}/*StructBinderFactory*/;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,178 @@
|
|||
#include <assert.h>
|
||||
#include <string.h> /* memset() */
|
||||
#include <stddef.h> /* offsetof() */
|
||||
#include <stdio.h> /* snprintf() */
|
||||
#include <stdint.h> /* int64_t */
|
||||
/*#include <stdlib.h>*/ /* malloc/free(), needed for emscripten exports. */
|
||||
extern void * malloc(size_t);
|
||||
extern void free(void *);
|
||||
|
||||
/*
|
||||
** 2022-06-25
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
***********************************************************************
|
||||
**
|
||||
** Utility functions for use with the emscripten/WASM bits. These
|
||||
** functions ARE NOT part of the sqlite3 public API. They are strictly
|
||||
** for internal use by the JS/WASM bindings.
|
||||
**
|
||||
** This file is intended to be WASM-compiled together with sqlite3.c,
|
||||
** e.g.:
|
||||
**
|
||||
** emcc ... sqlite3.c wasm_util.c
|
||||
*/
|
||||
|
||||
/*
|
||||
** Experimenting with output parameters.
|
||||
*/
|
||||
int jaccwabyt_test_intptr(int * p){
|
||||
if(1==((int)p)%3){
|
||||
/* kludge to get emscripten to export malloc() and free() */;
|
||||
free(malloc(0));
|
||||
}
|
||||
return *p = *p * 2;
|
||||
}
|
||||
int64_t jaccwabyt_test_int64_max(void){
|
||||
return (int64_t)0x7fffffffffffffff;
|
||||
}
|
||||
int64_t jaccwabyt_test_int64_min(void){
|
||||
return ~jaccwabyt_test_int64_max();
|
||||
}
|
||||
int64_t jaccwabyt_test_int64_times2(int64_t x){
|
||||
return x * 2;
|
||||
}
|
||||
|
||||
void jaccwabyt_test_int64_minmax(int64_t * min, int64_t *max){
|
||||
*max = jaccwabyt_test_int64_max();
|
||||
*min = jaccwabyt_test_int64_min();
|
||||
/*printf("minmax: min=%lld, max=%lld\n", *min, *max);*/
|
||||
}
|
||||
int64_t jaccwabyt_test_int64ptr(int64_t * p){
|
||||
/*printf("jaccwabyt_test_int64ptr( @%lld = 0x%llx )\n", (int64_t)p, *p);*/
|
||||
return *p = *p * 2;
|
||||
}
|
||||
|
||||
void jaccwabyt_test_stack_overflow(int recurse){
|
||||
if(recurse) jaccwabyt_test_stack_overflow(recurse);
|
||||
}
|
||||
|
||||
struct WasmTestStruct {
|
||||
int v4;
|
||||
void * ppV;
|
||||
const char * cstr;
|
||||
int64_t v8;
|
||||
void (*xFunc)(void*);
|
||||
};
|
||||
typedef struct WasmTestStruct WasmTestStruct;
|
||||
void jaccwabyt_test_struct(WasmTestStruct * s){
|
||||
if(s){
|
||||
s->v4 *= 2;
|
||||
s->v8 = s->v4 * 2;
|
||||
s->ppV = s;
|
||||
s->cstr = __FILE__;
|
||||
if(s->xFunc) s->xFunc(s);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/** For testing the 'string-free' whwasmutil.xWrap() conversion. */
|
||||
char * jaccwabyt_test_str_hello(int fail){
|
||||
char * s = fail ? 0 : (char *)malloc(6);
|
||||
if(s){
|
||||
memcpy(s, "hello", 5);
|
||||
s[5] = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/*
|
||||
** Returns a NUL-terminated string containing a JSON-format metadata
|
||||
** regarding C structs, for use with the StructBinder API. The
|
||||
** returned memory is static and is only written to the first time
|
||||
** this is called.
|
||||
*/
|
||||
const char * jaccwabyt_test_ctype_json(void){
|
||||
static char strBuf[1024 * 8] = {0};
|
||||
int n = 0, structCount = 0, groupCount = 0;
|
||||
char * pos = &strBuf[1] /* skip first byte for now to help protect
|
||||
against a small race condition */;
|
||||
char const * const zEnd = pos + sizeof(strBuf);
|
||||
if(strBuf[0]) return strBuf;
|
||||
/* Leave first strBuf[0] at 0 until the end to help guard against a
|
||||
tiny race condition. If this is called twice concurrently, they
|
||||
might end up both writing to strBuf, but they'll both write the
|
||||
same thing, so that's okay. If we set byte 0 up front then the
|
||||
2nd instance might return a partially-populated string. */
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// First we need to build up our macro framework...
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Core output macros...
|
||||
#define lenCheck assert(pos < zEnd - 100)
|
||||
#define outf(format,...) \
|
||||
pos += snprintf(pos, ((size_t)(zEnd - pos)), format, __VA_ARGS__); \
|
||||
lenCheck
|
||||
#define out(TXT) outf("%s",TXT)
|
||||
#define CloseBrace(LEVEL) \
|
||||
assert(LEVEL<5); memset(pos, '}', LEVEL); pos+=LEVEL; lenCheck
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Macros for emitting StructBinder descriptions...
|
||||
#define StructBinder__(TYPE) \
|
||||
n = 0; \
|
||||
outf("%s{", (structCount++ ? ", " : "")); \
|
||||
out("\"name\": \"" # TYPE "\","); \
|
||||
outf("\"sizeof\": %d", (int)sizeof(TYPE)); \
|
||||
out(",\"members\": {");
|
||||
#define StructBinder_(T) StructBinder__(T)
|
||||
// ^^^ indirection needed to expand CurrentStruct
|
||||
#define StructBinder StructBinder_(CurrentStruct)
|
||||
#define _StructBinder CloseBrace(2)
|
||||
#define M(MEMBER,SIG) \
|
||||
outf("%s\"%s\": " \
|
||||
"{\"offset\":%d,\"sizeof\": %d,\"signature\":\"%s\"}", \
|
||||
(n++ ? ", " : ""), #MEMBER, \
|
||||
(int)offsetof(CurrentStruct,MEMBER), \
|
||||
(int)sizeof(((CurrentStruct*)0)->MEMBER), \
|
||||
SIG)
|
||||
// End of macros
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
out("\"structs\": ["); {
|
||||
|
||||
#define CurrentStruct WasmTestStruct
|
||||
StructBinder {
|
||||
M(v4,"i");
|
||||
M(cstr,"s");
|
||||
M(ppV,"p");
|
||||
M(v8,"j");
|
||||
M(xFunc,"v(p)");
|
||||
} _StructBinder;
|
||||
#undef CurrentStruct
|
||||
|
||||
} out( "]"/*structs*/);
|
||||
out("}"/*top-level object*/);
|
||||
*pos = 0;
|
||||
strBuf[0] = '{'/*end of the race-condition workaround*/;
|
||||
return strBuf;
|
||||
#undef DefGroup
|
||||
#undef Def
|
||||
#undef _DefGroup
|
||||
#undef StructBinder
|
||||
#undef StructBinder_
|
||||
#undef StructBinder__
|
||||
#undef M
|
||||
#undef _StructBinder
|
||||
#undef CurrentStruct
|
||||
#undef CloseBrace
|
||||
#undef out
|
||||
#undef outf
|
||||
#undef lenCheck
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
_jaccwabyt_test_intptr
|
||||
_jaccwabyt_test_int64ptr
|
||||
_jaccwabyt_test_int64_max
|
||||
_jaccwabyt_test_int64_min
|
||||
_jaccwabyt_test_int64_minmax
|
||||
_jaccwabyt_test_int64_times2
|
||||
_jaccwabyt_test_struct
|
||||
_jaccwabyt_test_ctype_json
|
||||
_jaccwabyt_test_stack_overflow
|
||||
_jaccwabyt_test_str_hello
|
|
@ -4,10 +4,9 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||
<link rel="stylesheet" href="emscripten.css"/>
|
||||
<link rel="stylesheet" href="testing.css"/>
|
||||
<link rel="stylesheet" href="common/emscripten.css"/>
|
||||
<link rel="stylesheet" href="common/testing.css"/>
|
||||
<title>sqlite3-api.js tests</title>
|
||||
<style></style>
|
||||
</head>
|
||||
<body>
|
||||
<header id='titlebar'><span>sqlite3-api.js tests</span></header>
|
||||
|
@ -25,9 +24,11 @@
|
|||
<div class="emscripten">
|
||||
<progress value="0" max="100" id="module-progress" hidden='1'></progress>
|
||||
</div><!-- /emscripten bits -->
|
||||
<div>Everything on this page happens in the dev console.</div>
|
||||
<script src="sqlite3.js"></script>
|
||||
<script src="SqliteTestUtil.js"></script>
|
||||
<div>Most stuff on this page happens in the dev console.</div>
|
||||
<hr>
|
||||
<div id='test-output'></div>
|
||||
<script src="api/sqlite3.js"></script>
|
||||
<script src="common/SqliteTestUtil.js"></script>
|
||||
<script src="testing1.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -4,10 +4,9 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
|
||||
<link rel="stylesheet" href="emscripten.css"/>
|
||||
<link rel="stylesheet" href="testing.css"/>
|
||||
<link rel="stylesheet" href="common/emscripten.css"/>
|
||||
<link rel="stylesheet" href="common/testing.css"/>
|
||||
<title>sqlite3-worker.js tests</title>
|
||||
<style></style>
|
||||
</head>
|
||||
<body>
|
||||
<header id='titlebar'><span>sqlite3-worker.js tests</span></header>
|
||||
|
@ -25,8 +24,10 @@
|
|||
<div class="emscripten">
|
||||
<progress value="0" max="100" id="module-progress" hidden='1'></progress>
|
||||
</div><!-- /emscripten bits -->
|
||||
<div>Everything on this page happens in the dev console.</div>
|
||||
<script src="testing-common.js"></script>
|
||||
<div>Most stuff on this page happens in the dev console.</div>
|
||||
<hr>
|
||||
<div id='test-output'></div>
|
||||
<script src="common/SqliteTestUtil.js"></script>
|
||||
<script src="testing2.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
2022-05-22
|
||||
|
||||
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 basic test script for sqlite3-worker.js.
|
||||
*/
|
||||
'use strict';
|
||||
(function(){
|
||||
const T = self.SqliteTestUtil;
|
||||
const SW = new Worker("api/sqlite3-worker.js");
|
||||
const DbState = {
|
||||
id: undefined
|
||||
};
|
||||
const eOutput = document.querySelector('#test-output');
|
||||
const log = console.log.bind(console)
|
||||
const logHtml = function(cssClass,...args){
|
||||
log.apply(this, args);
|
||||
const ln = document.createElement('div');
|
||||
if(cssClass) ln.classList.add(cssClass);
|
||||
ln.append(document.createTextNode(args.join(' ')));
|
||||
eOutput.append(ln);
|
||||
};
|
||||
const warn = console.warn.bind(console);
|
||||
const error = console.error.bind(console);
|
||||
const toss = (...args)=>{throw new Error(args.join(' '))};
|
||||
/** Posts a worker message as {type:type, data:data}. */
|
||||
const wMsg = function(type,data){
|
||||
log("Posting message to worker dbId="+(DbState.id||'default')+':',data);
|
||||
SW.postMessage({
|
||||
type,
|
||||
dbId: DbState.id,
|
||||
data,
|
||||
departureTime: performance.now()
|
||||
});
|
||||
return SW;
|
||||
};
|
||||
|
||||
SW.onerror = function(event){
|
||||
error("onerror",event);
|
||||
};
|
||||
|
||||
let startTime;
|
||||
|
||||
/**
|
||||
A queue for callbacks which are to be run in response to async
|
||||
DB commands. See the notes in runTests() for why we need
|
||||
this. The event-handling plumbing of this file requires that
|
||||
any DB command which includes a `messageId` property also have
|
||||
a queued callback entry, as the existence of that property in
|
||||
response payloads is how it knows whether or not to shift an
|
||||
entry off of the queue.
|
||||
*/
|
||||
const MsgHandlerQueue = {
|
||||
queue: [],
|
||||
id: 0,
|
||||
push: function(type,callback){
|
||||
this.queue.push(callback);
|
||||
return type + '-' + (++this.id);
|
||||
},
|
||||
shift: function(){
|
||||
return this.queue.shift();
|
||||
}
|
||||
};
|
||||
|
||||
const testCount = ()=>{
|
||||
logHtml("","Total test count:",T.counter+". Total time =",(performance.now() - startTime),"ms");
|
||||
};
|
||||
|
||||
const logEventResult = function(evd){
|
||||
logHtml(evd.errorClass ? 'error' : '',
|
||||
"runOneTest",evd.messageId,"Worker time =",
|
||||
(evd.workerRespondTime - evd.workerReceivedTime),"ms.",
|
||||
"Round-trip event time =",
|
||||
(performance.now() - evd.departureTime),"ms.",
|
||||
(evd.errorClass ? evd.message : "")
|
||||
);
|
||||
};
|
||||
|
||||
const runOneTest = function(eventType, eventData, callback){
|
||||
T.assert(eventData && 'object'===typeof eventData);
|
||||
/* ^^^ that is for the testing and messageId-related code, not
|
||||
a hard requirement of all of the Worker-exposed APIs. */
|
||||
eventData.messageId = MsgHandlerQueue.push(eventType,function(ev){
|
||||
logEventResult(ev.data);
|
||||
if(callback instanceof Function){
|
||||
callback(ev);
|
||||
testCount();
|
||||
}
|
||||
});
|
||||
wMsg(eventType, eventData);
|
||||
};
|
||||
|
||||
/** Methods which map directly to onmessage() event.type keys.
|
||||
They get passed the inbound event.data. */
|
||||
const dbMsgHandler = {
|
||||
open: function(ev){
|
||||
DbState.id = ev.dbId;
|
||||
log("open result",ev.data);
|
||||
},
|
||||
exec: function(ev){
|
||||
log("exec result",ev.data);
|
||||
},
|
||||
export: function(ev){
|
||||
log("export result",ev.data);
|
||||
},
|
||||
error: function(ev){
|
||||
error("ERROR from the worker:",ev.data);
|
||||
logEventResult(ev.data);
|
||||
},
|
||||
resultRowTest1: function f(ev){
|
||||
if(undefined === f.counter) f.counter = 0;
|
||||
if(ev.data) ++f.counter;
|
||||
//log("exec() result row:",ev.data);
|
||||
T.assert(null===ev.data || 'number' === typeof ev.data.b);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
"The problem" now is that the test results are async. We
|
||||
know, however, that the messages posted to the worker will
|
||||
be processed in the order they are passed to it, so we can
|
||||
create a queue of callbacks to handle them. The problem
|
||||
with that approach is that it's not error-handling
|
||||
friendly, in that an error can cause us to bypass a result
|
||||
handler queue entry. We have to perform some extra
|
||||
acrobatics to account for that.
|
||||
|
||||
Problem #2 is that we cannot simply start posting events: we
|
||||
first have to post an 'open' event, wait for it to respond, and
|
||||
collect its db ID before continuing. If we don't wait, we may
|
||||
well fire off 10+ messages before the open actually responds.
|
||||
*/
|
||||
const runTests2 = function(){
|
||||
const mustNotReach = ()=>{
|
||||
throw new Error("This is not supposed to be reached.");
|
||||
};
|
||||
runOneTest('exec',{
|
||||
sql: ["create table t(a,b)",
|
||||
"insert into t(a,b) values(1,2),(3,4),(5,6)"
|
||||
].join(';'),
|
||||
multi: true,
|
||||
resultRows: [], columnNames: []
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(0===ev.resultRows.length)
|
||||
.assert(0===ev.columnNames.length);
|
||||
});
|
||||
runOneTest('exec',{
|
||||
sql: 'select a a, b b from t order by a',
|
||||
resultRows: [], columnNames: [],
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(3===ev.resultRows.length)
|
||||
.assert(1===ev.resultRows[0][0])
|
||||
.assert(6===ev.resultRows[2][1])
|
||||
.assert(2===ev.columnNames.length)
|
||||
.assert('b'===ev.columnNames[1]);
|
||||
});
|
||||
runOneTest('exec',{
|
||||
sql: 'select a a, b b from t order by a',
|
||||
resultRows: [], columnNames: [],
|
||||
rowMode: 'object'
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(3===ev.resultRows.length)
|
||||
.assert(1===ev.resultRows[0].a)
|
||||
.assert(6===ev.resultRows[2].b)
|
||||
});
|
||||
runOneTest('exec',{sql:'intentional_error'}, mustNotReach);
|
||||
// Ensure that the message-handler queue survives ^^^ that error...
|
||||
runOneTest('exec',{
|
||||
sql:'select 1',
|
||||
resultRows: [],
|
||||
//rowMode: 'array', // array is the default in the Worker interface
|
||||
}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(1 === ev.resultRows.length)
|
||||
.assert(1 === ev.resultRows[0][0]);
|
||||
});
|
||||
runOneTest('exec',{
|
||||
sql: 'select a a, b b from t order by a',
|
||||
callback: 'resultRowTest1',
|
||||
rowMode: 'object'
|
||||
}, function(ev){
|
||||
T.assert(3===dbMsgHandler.resultRowTest1.counter);
|
||||
dbMsgHandler.resultRowTest1.counter = 0;
|
||||
});
|
||||
runOneTest('exec',{
|
||||
multi: true,
|
||||
sql:[
|
||||
'pragma foreign_keys=0;',
|
||||
// ^^^ arbitrary query with no result columns
|
||||
'select a, b from t order by a desc; select a from t;'
|
||||
// multi-exec only honors results from the first
|
||||
// statement with result columns (regardless of whether)
|
||||
// it has any rows).
|
||||
],
|
||||
rowMode: 1,
|
||||
resultRows: []
|
||||
},function(ev){
|
||||
const rows = ev.data.resultRows;
|
||||
T.assert(3===rows.length).
|
||||
assert(6===rows[0]);
|
||||
});
|
||||
runOneTest('exec',{sql: 'delete from t where a>3'});
|
||||
runOneTest('exec',{
|
||||
sql: 'select count(a) from t',
|
||||
resultRows: []
|
||||
},function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(1===ev.resultRows.length)
|
||||
.assert(2===ev.resultRows[0][0]);
|
||||
});
|
||||
if(0){
|
||||
// export requires reimpl. for portability reasons.
|
||||
runOneTest('export',{}, function(ev){
|
||||
ev = ev.data;
|
||||
T.assert('string' === typeof ev.filename)
|
||||
.assert(ev.buffer instanceof Uint8Array)
|
||||
.assert(ev.buffer.length > 1024)
|
||||
.assert('application/x-sqlite3' === ev.mimetype);
|
||||
});
|
||||
}
|
||||
/***** close() tests must come last. *****/
|
||||
runOneTest('close',{unlink:true},function(ev){
|
||||
ev = ev.data;
|
||||
T.assert('string' === typeof ev.filename);
|
||||
});
|
||||
runOneTest('close',{unlink:true},function(ev){
|
||||
ev = ev.data;
|
||||
T.assert(undefined === ev.filename);
|
||||
});
|
||||
};
|
||||
|
||||
const runTests = function(){
|
||||
/**
|
||||
Design decision time: all remaining tests depend on the 'open'
|
||||
command having succeeded. In order to support multiple DBs, the
|
||||
upcoming commands ostensibly have to know the ID of the DB they
|
||||
want to talk to. We have two choices:
|
||||
|
||||
1) We run 'open' and wait for its response, which contains the
|
||||
db id.
|
||||
|
||||
2) We have the Worker automatically use the current "default
|
||||
db" (the one which was most recently opened) if no db id is
|
||||
provided in the message. When we do this, the main thread may
|
||||
well fire off _all_ of the test messages before the 'open'
|
||||
actually responds, but because the messages are handled on a
|
||||
FIFO basis, those after the initial 'open' will pick up the
|
||||
"default" db. However, if the open fails, then all pending
|
||||
messages (until next next 'open', at least) except for 'close'
|
||||
will fail and we have no way of cancelling them once they've
|
||||
been posted to the worker.
|
||||
|
||||
We currently do (2) because (A) it's certainly the most
|
||||
client-friendly thing to do and (B) it seems likely that most
|
||||
apps using this API will only have a single db to work with so
|
||||
won't need to juggle multiple DB ids. If we revert to (1) then
|
||||
the following call to runTests2() needs to be moved into the
|
||||
callback function of the runOneTest() check for the 'open'
|
||||
command. Note, also, that using approach (2) does not keep the
|
||||
user from instead using approach (1), noting that doing so
|
||||
requires explicit handling of the 'open' message to account for
|
||||
it.
|
||||
*/
|
||||
const waitForOpen = 1,
|
||||
simulateOpenError = 0 /* if true, the remaining tests will
|
||||
all barf if waitForOpen is
|
||||
false. */;
|
||||
logHtml('',
|
||||
"Sending 'open' message and",(waitForOpen ? "" : "NOT ")+
|
||||
"waiting for its response before continuing.");
|
||||
startTime = performance.now();
|
||||
runOneTest('open', {
|
||||
filename:'testing2.sqlite3',
|
||||
simulateError: simulateOpenError
|
||||
}, function(ev){
|
||||
//log("open result",ev);
|
||||
T.assert('testing2.sqlite3'===ev.data.filename)
|
||||
.assert(ev.data.dbId)
|
||||
.assert(ev.data.messageId);
|
||||
DbState.id = ev.data.dbId;
|
||||
if(waitForOpen) setTimeout(runTests2, 0);
|
||||
});
|
||||
if(!waitForOpen) runTests2();
|
||||
};
|
||||
|
||||
SW.onmessage = function(ev){
|
||||
if(!ev.data || 'object'!==typeof ev.data){
|
||||
warn("Unknown sqlite3-worker message type:",ev);
|
||||
return;
|
||||
}
|
||||
ev = ev.data/*expecting a nested object*/;
|
||||
//log("main window onmessage:",ev);
|
||||
if(ev.data && ev.data.messageId){
|
||||
/* We're expecting a queued-up callback handler. */
|
||||
const f = MsgHandlerQueue.shift();
|
||||
if('error'===ev.type){
|
||||
dbMsgHandler.error(ev);
|
||||
return;
|
||||
}
|
||||
T.assert(f instanceof Function);
|
||||
f(ev);
|
||||
return;
|
||||
}
|
||||
switch(ev.type){
|
||||
case 'sqlite3-api':
|
||||
switch(ev.data){
|
||||
case 'worker-ready':
|
||||
log("Message:",ev);
|
||||
self.sqlite3TestModule.setStatus(null);
|
||||
runTests();
|
||||
return;
|
||||
default:
|
||||
warn("Unknown sqlite3-api message type:",ev);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
if(dbMsgHandler.hasOwnProperty(ev.type)){
|
||||
try{dbMsgHandler[ev.type](ev);}
|
||||
catch(err){
|
||||
error("Exception while handling db result message",
|
||||
ev,":",err);
|
||||
}
|
||||
return;
|
||||
}
|
||||
warn("Unknown sqlite3-api message type:",ev);
|
||||
}
|
||||
};
|
||||
log("Init complete, but async init bits may still be running.");
|
||||
})();
|
65
manifest
65
manifest
|
@ -1,9 +1,9 @@
|
|||
C Fix\sanother\sharmless\scomment\stypo\sthat\scauses\sa\stypo\sin\sthe\sdocumentation.
|
||||
D 2022-08-10T18:40:43.400
|
||||
C Merge\sin\swasm-cleanups\sbranch,\sreorganizing\sand\supdating\sthe\swasm-related\scomponents.
|
||||
D 2022-08-11T09:18:09.465
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
F Makefile.in 24d67c669635fba48c37fa5eacb01fcfde9fc522f152f60d3058c2dcf34da8da
|
||||
F Makefile.in 31f4141e5adc6f002296fc2c500803f460ca3e8b2cae53effeb021e74d7b29d7
|
||||
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
|
||||
F Makefile.msc d547a2fdba38a1c6cd1954977d0b0cc017f5f8fbfbc65287bf8d335808938016
|
||||
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
|
||||
|
@ -55,23 +55,6 @@ F ext/expert/expert1.test 95b00567ce0775126a1b788af2d055255014714ecfddc97913864d
|
|||
F ext/expert/sqlite3expert.c a912efbad597eafdb0ce934ebc11039f3190b2d479685d89184e107f65d856e1
|
||||
F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
|
||||
F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
|
||||
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3
|
||||
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api 540b9dec63a3a62a256e2f030827848a92e9b9d9b6fa5c0188295a4a1c5382cd
|
||||
F ext/fiddle/EXPORTED_RUNTIME_METHODS b831017ba67ba993b34a27400cef2f6095bd6789c0fc4eba7e7a251c207be31c
|
||||
F ext/fiddle/Makefile e25d34a0e1324f771d64c09c592601b97219282011587e6ce410fa8acdedb913
|
||||
F ext/fiddle/SqliteTestUtil.js 559731c3e8e0de330ec7d292e6c1846566408caee6637acc8a119ac338a8781c
|
||||
F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
|
||||
F ext/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba833865ab64a1b4ae
|
||||
F ext/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
|
||||
F ext/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8
|
||||
F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
|
||||
F ext/fiddle/sqlite3-api.js ccf4bd0c1c5bbb3be3469573423d6c53991941bec497eac63e9f17ea13bf8952
|
||||
F ext/fiddle/sqlite3-worker.js a9c2b614beca187dbdd8c053ec2770cc61ec1ac9c0ec6398ceb49a79f705a421
|
||||
F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a
|
||||
F ext/fiddle/testing1.html ea1f3be727f78e420007f823912c1a03b337ecbb8e79449abc2244ad4fe15d9a
|
||||
F ext/fiddle/testing1.js e2fa02ac8adbd21c69bc50cfcb79bfc26af0d30a8d6b95ac473a17e0dc9de733
|
||||
F ext/fiddle/testing2.html 9063b2430ade2fe9da4e711addd1b51a2741cf0c7ebf6926472a5e5dd63c0bc4
|
||||
F ext/fiddle/testing2.js 7b45b4e7fddbd51dbaf89b6722c02758051b34bac5a98c11b569a7e7572f88ee
|
||||
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
|
||||
F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
|
||||
F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
|
||||
|
@ -489,6 +472,39 @@ F ext/session/test_session.c f433f68a8a8c64b0f5bc74dc725078f12483301ad4ae8375205
|
|||
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
|
||||
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
|
||||
F ext/userauth/userauth.c 7f00cded7dcaa5d47f54539b290a43d2e59f4b1eb5f447545fa865f002fc80cb
|
||||
F ext/wasm/EXPORTED_FUNCTIONS.fiddle 7fb73f7150ab79d83bb45a67d257553c905c78cd3d693101699243f36c5ae6c3 w ext/fiddle/EXPORTED_FUNCTIONS.fiddle
|
||||
F ext/wasm/EXPORTED_RUNTIME_METHODS.fiddle a004bd5eeeda6d3b28d16779b7f1a80305bfe009dfc7f0721b042967f0d39d02 w ext/fiddle/EXPORTED_RUNTIME_METHODS
|
||||
F ext/wasm/GNUmakefile 5359a37fc13b68fad2259228590450339a0c59687744edd0db7bb93d3b1ae2b1 w ext/fiddle/Makefile
|
||||
F ext/wasm/README.md 4b00ae7c7d93c4591251245f0996a319e2651361013c98d2efb0b026771b7331 w ext/fiddle/index.md
|
||||
F ext/wasm/api/EXPORTED_FUNCTIONS.sqlite3-api c5eaceabb9e759aaae7d3101a4a3e542f96ab2c99d89a80ce20ec18c23115f33 w ext/fiddle/EXPORTED_FUNCTIONS.sqlite3-api
|
||||
F ext/wasm/api/EXPORTED_RUNTIME_METHODS.sqlite3-api 1ec3c73e7d66e95529c3c64ac3de2470b0e9e7fbf7a5b41261c367cf4f1b7287
|
||||
F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba81456260a713ed04900c
|
||||
F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b
|
||||
F ext/wasm/api/sqlite3-api-cleanup.js 149fd63a0400cd1d69548887ffde2ed89c13283384a63c2e9fcfc695e38a9e11
|
||||
F ext/wasm/api/sqlite3-api-glue.js 82c09f49c69984009ba5af2b628e67cc26c5dd203d383cd3091d40dab4e6514b
|
||||
F ext/wasm/api/sqlite3-api-oo1.js e9612cb704c0563c5d71ed2a8dccd95bf6394fa4de3115d1b978dc269c49ab02
|
||||
F ext/wasm/api/sqlite3-api-opfs.js a899a10b83f15cace5a449dd12d957b429d1d4eb31cdb10e6ee4c22c569ece77
|
||||
F ext/wasm/api/sqlite3-api-prologue.js 0fb0703d2d8ac89fa2d4dd8f9726b0ea226b8708ac34e5b482df046e147de0eb w ext/fiddle/sqlite3-api.js
|
||||
F ext/wasm/api/sqlite3-api-worker.js cae932a89e48730cd850ab280963a65a96cb8b4c58bacd54ba961991a3c32f51 w ext/fiddle/sqlite3-worker.js
|
||||
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
|
||||
F ext/wasm/api/sqlite3-wasm.c 827357635c356ca178ebfa1be5915afacb682043d27c4a230c194c1ace787be4
|
||||
F ext/wasm/api/sqlite3-worker.js 1325ca8d40129a82531902a3a077b795db2eeaee81746e5a0c811a04b415fa7f
|
||||
F ext/wasm/common/SqliteTestUtil.js e41a1406f18da9224523fad0c48885caf995b56956a5b9852909c0989e687e90 w ext/fiddle/SqliteTestUtil.js
|
||||
F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
|
||||
F ext/wasm/common/testing.css 572cf1ffae0b6eb7ca63684d3392bf350217a07b90e7a896e4fa850700c989b0 w ext/fiddle/testing.css
|
||||
F ext/wasm/common/whwasmutil.js 3d9deda1be718e2b10e2b6b474ba6ba857d905be314201ae5b3df5eef79f66aa
|
||||
F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f w ext/fiddle/emscripten.css
|
||||
F ext/wasm/fiddle/fiddle-worker.js 88bc2193a6cb6a3f04d8911bed50a4401fe6f277de7a71ba833865ab64a1b4ae w ext/fiddle/fiddle-worker.js
|
||||
F ext/wasm/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08 w ext/fiddle/fiddle.html
|
||||
F ext/wasm/fiddle/fiddle.js 812f9954cc7c4b191884ad171f36fcf2d0112d0a7ecfdf6087896833a0c079a8 w ext/fiddle/fiddle.js
|
||||
F ext/wasm/jaccwabyt/jaccwabyt.js 99b424b4d467d4544e82615b58e2fe07532a898540bf9de2a985f3c21e7082b2
|
||||
F ext/wasm/jaccwabyt/jaccwabyt.md 447cc02b598f7792edaa8ae6853a7847b8178a18ed356afacbdbf312b2588106
|
||||
F ext/wasm/jaccwabyt/jaccwabyt_test.c 39e4b865a33548f943e2eb9dd0dc8d619a80de05d5300668e9960fff30d0d36f
|
||||
F ext/wasm/jaccwabyt/jaccwabyt_test.exports 5ff001ef975c426ffe88d7d8a6e96ec725e568d2c2307c416902059339c06f19
|
||||
F ext/wasm/testing1.html 0bf3ff224628c1f1e3ed22a2dc1837c6c73722ad8c0ad9c8e6fb9e6047667231 w ext/fiddle/testing1.html
|
||||
F ext/wasm/testing1.js aef553114aada187eef125f5361fd1e58bf5e8e97acfa65c10cb41dd60295daa w ext/fiddle/testing1.js
|
||||
F ext/wasm/testing2.html 73e5048e666fd6fb28b6e635677a9810e1e139c599ddcf28d687c982134b92b8 w ext/fiddle/testing2.html
|
||||
F ext/wasm/testing2.js d37433c601f88ed275712c1cfc92d3fb36c7c22e1ed8c7396fb2359e42238ebc w ext/fiddle/testing2.js
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
|
@ -1982,8 +1998,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
|||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P b5e4f0db09ff7790862357df3c6fd38a2dcdfc82ff51b9f0d9470517648c844d
|
||||
R eaac50c574dee6cdda4672150249af55
|
||||
U drh
|
||||
Z 30c21cc0e98d29fef6fd9de3bc3b538c
|
||||
P bb084adb53386d8e52ca1e818a8322d2ec641b73fd8568cee01cc74c0ee9f265 683a3b937e608a5ecaf7f63f054e8a63179d67c8b2348bf843e5e68f27a369f5
|
||||
R 4ba76f10a568f2d09e4030f5a10b07d7
|
||||
T +closed 683a3b937e608a5ecaf7f63f054e8a63179d67c8b2348bf843e5e68f27a369f5 Closed\sby\sintegrate-merge.
|
||||
U stephan
|
||||
Z 747d196ca566e8555d460e3b390787ac
|
||||
# Remove this line to create a well-formed Fossil manifest.
|
||||
|
|
|
@ -1 +1 @@
|
|||
bb084adb53386d8e52ca1e818a8322d2ec641b73fd8568cee01cc74c0ee9f265
|
||||
c072594d3de3d6893c5d4a9d68439b84d043325f105b0d065575765a6e66c196
|
Loading…
Reference in New Issue