Merge in wasm-cleanups branch, reorganizing and updating the wasm-related components.

FossilOrigin-Name: c072594d3de3d6893c5d4a9d68439b84d043325f105b0d065575765a6e66c196
This commit is contained in:
stephan 2022-08-11 09:18:09 +00:00
commit 9289c47df7
42 changed files with 9253 additions and 2515 deletions

View File

@ -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.
#
########################################################################

View File

@ -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)

View File

@ -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

View File

@ -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);
});

View File

@ -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);
});
})();

View File

@ -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.");
})();

View File

@ -1,14 +1,14 @@
ALLOC_NORMAL
FS
UTF8ToString
addFunction
allocate
allocateUTF8OnStack
ccall
cwrap
getValue
intArrayFromString
lengthBytesUTF8
removeFunction
setValue
stackAlloc
stackRestore
stackSave
stringToUTF8Array

287
ext/wasm/GNUmakefile Normal file
View File

@ -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
########################################################################

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
FS
wasmMemory

View File

@ -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(...)*/;

View File

@ -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!
*/

View File

@ -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);

View File

@ -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

View File

@ -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*/;
});

View File

@ -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()*/;

View File

@ -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});

View File

@ -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

412
ext/wasm/api/sqlite3-wasm.c Normal file
View File

@ -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
}

View File

@ -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());

View File

@ -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*/);

View File

@ -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

View File

@ -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);}
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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>

1088
ext/wasm/testing1.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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>

340
ext/wasm/testing2.js Normal file
View File

@ -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.");
})();

View File

@ -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.

View File

@ -1 +1 @@
bb084adb53386d8e52ca1e818a8322d2ec641b73fd8568cee01cc74c0ee9f265
c072594d3de3d6893c5d4a9d68439b84d043325f105b0d065575765a6e66c196