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

fiddle: lots of generic refactoring, restructuring, and cleanup in the higher-level code. Added push-fiddle ext/fiddle/Makefile target to push the fiddle app to a remote server via rsync.

FossilOrigin-Name: ed19fef3459499abb0a4a010f368b4576d6e068d930c8480446ea677ac87c1c1
This commit is contained in:
stephan
2022-05-24 14:36:45 +00:00
parent 2f6a729d55
commit 400ee2ecef
9 changed files with 298 additions and 199 deletions

View File

@ -1,10 +1,31 @@
# This makefile exists primarily to simplify/speed up development from
# emacs. It is not part of the canonical build process.
# 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
push-demo:
rsync -va fiddle*.js fiddle*.wasm fiddle.html *.css wh2:www/wh/sqlite3/.
demo_files = emscripten.css fiddle.html \
fiddle.js fiddle-module.js \
fiddle-module.wasm fiddle-worker.js
# demo_target 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.
demo_target ?=
ifeq (,$(demo_target))
ifneq (,$(wildcard /home/stephan))
demo_target = wh2:www/wh/sqlite3/.
else ifneq (,$(wildcard /home/drh))
#demo_target = if appropriate, add that user@host:/path here
endif
endif
push-fiddle: $(demo_files)
@if [ x = "x$(demo_target)" ]; then \
echo "demo_target must be a [user@]HOST:/path for rsync"; \
exit 1; \
fi
rsync -va $(demo_files) $(demo_target)

View File

@ -132,35 +132,45 @@
return (arguments.length>1 ? arguments[0] : document)
.querySelector(arguments[arguments.length-1]);
};
const statusElement = E('#module-status');
const progressElement = E('#module-progress');
const spinnerElement = E('#module-spinner');
/** Handles status updates from the Module object. */
SF.addMsgHandler('module', function f(ev){
ev = ev.data;
if('status'!==ev.type){
console.warn("Unexpected module-type message:",ev);
return;
}
if(!f.ui){
f.ui = {
status: E('#module-status'),
progress: E('#module-progress'),
spinner: E('#module-spinner')
};
}
const msg = ev.data;
progressElement.value = msg.step;
progressElement.max = msg.step + 1/*we don't know how many steps to expect*/;
if(f.ui.progres){
progress.value = msg.step;
progress.max = msg.step + 1/*we don't know how many steps to expect*/;
}
if(1==msg.step){
progressElement.hidden = false;
spinnerElement.hidden = false;
f.ui.progress.classList.remove('hidden');
f.ui.spinner.classList.remove('hidden');
}
if(msg.text){
statusElement.classList.remove('hidden');
statusElement.innerText = msg.text;
f.ui.status.classList.remove('hidden');
f.ui.status.innerText = msg.text;
}else{
progressElement.remove();
spinnerElement.remove();
statusElement.classList.add('hidden');
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');
/* The module can post messages about fatal problems,
e.g. an exit() being triggered or assertion failure,
after the last "load" message has arrived, so
leave the statusElement and message listener intact. */
leave f.ui.status and message listener intact. */
}
});
@ -209,9 +219,9 @@
},false);
/** To be called immediately before work is sent to the
worker. Updates some UI elements. The 'working'/'end'
worker. Updates some UI elements. The 'working'/'end'
event will apply the inverse, undoing the bits this
function does. This impl is not in the 'working'/'start'
function does. This impl is not in the 'working'/'start'
event handler because that event is given to us
asynchronously _after_ we need to have performed this
work.
@ -242,13 +252,15 @@
};
SF.addMsgHandler('working',function f(ev){
if('start' === ev.data){
/* See notes in preStartWork(). */
}else if('end' === ev.data){
preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
btnShellExec.innerText = preStartWork._.btnLabel;
btnShellExec.removeAttribute('disabled');
switch(ev.data){
case 'start': /* See notes in preStartWork(). */; return;
case 'end':
preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
btnShellExec.innerText = preStartWork._.btnLabel;
btnShellExec.removeAttribute('disabled');
return;
}
console.warn("Unhandled 'working' event:",ev.data);
});
/* For each checkbox with data-csstgt, set up a handler which

View File

@ -17,31 +17,33 @@
Note that this file is not named sqlite3.js because that file gets
generated by emscripten as the JS-glue counterpart of sqlite3.wasm.
This code installs an object named self.sqlite3, where self is
expected to be either the global window or Worker object:
This code installs an object named self.Module.sqlite3, where self
is expected to be either the global window or Worker object and
Module is the object set up by the emscripten infrastructure. The
sqlite3 object looks like:
self.sqlite3 = {
api: core WASM bindings of sqlite3 APIs,
{
api: bindings for much of the core sqlite3 APIs,
SQLite3: high-level OO API wrapper
};
}
The way we export this module is not _really_ modern-JS-friendly
because it exports a global symbol (which is admittedly not
ideal). Exporting it "cleanly," without introducing any global-scope
symbols, requires using a module loader in all client code. As there
are several different approaches, none of which this developer is
currently truly familiar with, the current approach will have to do
for the time being.
because it exports/relies on a global symbol (which is admittedly
not ideal). Exporting it "cleanly," without introducing any
global-scope symbols, requires using a module loader in all client
code. As there are several different approaches, none of which this
developer is currently truly familiar with, the current approach
will have to do for the time being.
Because using the low-level API properly requires some degree of
WASM-related magic, it is not recommended that that API be used
as-is in client-level code. Rather, client code should use the
higher-level OO API or write a custom wrapper on top of the
lower-level API.
This file installs namespace.sqlite3, where namespace is `self`,
meaning either the global window or worker, depending on where this
is loaded from.
Because using certain parts of the low-level API properly requires
some degree of WASM-related magic, it is not recommended that that
API be used as-is in client-level code. Rather, client code should
use the higher-level OO API or write a custom wrapper on top of the
lower-level API. In short, using any C-style APIs which take
pointers-to-pointer arguments require WASM-specific interfaces
installed by emcscripten-generated code. Those which take or return
only integers, doubles, strings, or "plain" pointers to db or
statement objects can be used in a straightforward manner.
# Goals and Non-goals of this API
@ -60,13 +62,16 @@
can be interacted with, but keeping the DB operations out of the
UI thread is generally desirable.
- Insofar as possible, support client-side storage using JS
filesystem APIs. As of this writing, such things are still very
much TODO.
Non-goals:
- As WASM is a web-based technology and UTF-8 is the King of
Encodings in that realm, there are no plans to support the
UTF16-related APIs will not be. They would add a complication to
the bindings for no appreciable benefit.
- As WASM is a web-centric technology and UTF-8 is the King of
Encodings in that realm, there are no current plans to support the
UTF16-related APIs. They would add a complication to the bindings
for no appreciable benefit.
- Supporting old or niche-market platforms. WASM is built for a
modern web and requires modern platforms.
@ -247,8 +252,6 @@
throw new Error(Array.prototype.join.call(arguments, ' '));
};
const S/*convenience alias*/ = api;
/**
The DB class wraps a sqlite3 db handle.
*/
@ -258,7 +261,7 @@
toss("TODO: support blob image of db here.");
}
setValue(pPtrArg, 0, "i32");
this.checkRc(S.sqlite3_open(name, pPtrArg));
this.checkRc(api.sqlite3_open(name, pPtrArg));
this._pDb = getValue(pPtrArg, "i32");
this.filename = name;
this._statements = {/*map of open Stmt _pointers_ to Stmt*/};
@ -291,8 +294,8 @@
}
this.db = arguments[0];
this._pStmt = arguments[1];
this.columnCount = S.sqlite3_column_count(this._pStmt);
this.parameterCount = S.sqlite3_bind_parameter_count(this._pStmt);
this.columnCount = api.sqlite3_column_count(this._pStmt);
this.parameterCount = api.sqlite3_bind_parameter_count(this._pStmt);
this._allocs = [/*list of alloc'd memory blocks for bind() values*/]
};
@ -370,7 +373,7 @@
checkRc: function(sqliteResultCode){
if(!sqliteResultCode) return this;
toss("sqlite result code",sqliteResultCode+":",
S.sqlite3_errmsg(this._pDb) || "Unknown db error.");
api.sqlite3_errmsg(this._pDb) || "Unknown db error.");
},
/**
Finalizes all open statements and closes this database
@ -388,7 +391,7 @@
Object.values(this._udfs).forEach(Module.removeFunction);
delete this._udfs;
delete this._statements;
S.sqlite3_close_v2(this._pDb);
api.sqlite3_close_v2(this._pDb);
delete this._pDb;
}
},
@ -401,7 +404,7 @@
a name of `main`.
*/
fileName: function(dbName){
return S.sqlite3_db_filename(affirmDbOpen(this)._pDb, dbName||"main");
return api.sqlite3_db_filename(affirmDbOpen(this)._pDb, dbName||"main");
},
/**
Compiles the given SQL and returns a prepared Stmt. This is
@ -410,7 +413,7 @@
prepare: function(sql){
affirmDbOpen(this);
setValue(pPtrArg,0,"i32");
this.checkRc(S.sqlite3_prepare_v2(this._pDb, sql, -1, pPtrArg, null));
this.checkRc(api.sqlite3_prepare_v2(this._pDb, sql, -1, pPtrArg, null));
const pStmt = getValue(pPtrArg, "i32");
if(!pStmt) toss("Empty SQL is not permitted.");
const stmt = new Stmt(this, pStmt, BindTypes);
@ -533,14 +536,14 @@
while(getValue(pSql, "i8")){
setValue(pPtrArg, 0, "i32");
setValue(pzTail, 0, "i32");
this.checkRc(S.sqlite3_prepare_v2_sqlptr(
this.checkRc(api.sqlite3_prepare_v2_sqlptr(
this._pDb, pSql, -1, pPtrArg, pzTail
));
const pStmt = getValue(pPtrArg, "i32");
pSql = getValue(pzTail, "i32");
if(!pStmt) continue;
if(opt.saveSql){
opt.saveSql.push(S.sqlite3_sql(pStmt).trim());
opt.saveSql.push(api.sqlite3_sql(pStmt).trim());
}
stmt = new Stmt(this, pStmt, BindTypes);
if(bind && stmt.parameterCount){
@ -653,18 +656,18 @@
const tgt = [];
for(i = 0; i < argc; ++i){
pVal = getValue(pArgv + (4 * i), "i32");
valType = S.sqlite3_value_type(pVal);
valType = api.sqlite3_value_type(pVal);
switch(valType){
case S.SQLITE_INTEGER:
case S.SQLITE_FLOAT:
arg = S.sqlite3_value_double(pVal);
case api.SQLITE_INTEGER:
case api.SQLITE_FLOAT:
arg = api.sqlite3_value_double(pVal);
break;
case SQLITE_TEXT:
arg = S.sqlite3_value_text(pVal);
arg = api.sqlite3_value_text(pVal);
break;
case SQLITE_BLOB:{
const n = S.sqlite3_value_bytes(ptr);
const pBlob = S.sqlite3_value_blob(ptr);
const n = api.sqlite3_value_bytes(ptr);
const pBlob = api.sqlite3_value_blob(ptr);
arg = new Uint8Array(n);
let i;
for(i = 0; i < n; ++i) arg[i] = HEAP8[pBlob+i];
@ -680,25 +683,25 @@
f._setResult = function(pCx, val){
switch(typeof val) {
case 'boolean':
S.sqlite3_result_int(pCx, val ? 1 : 0);
api.sqlite3_result_int(pCx, val ? 1 : 0);
break;
case 'number': {
(isInt32(val)
? S.sqlite3_result_int
: S.sqlite3_result_double)(pCx, val);
? api.sqlite3_result_int
: api.sqlite3_result_double)(pCx, val);
break;
}
case 'string':
S.sqlite3_result_text(pCx, val, -1,
api.sqlite3_result_text(pCx, val, -1,
-1/*==SQLITE_TRANSIENT*/);
break;
case 'object':
if(null===val) {
S.sqlite3_result_null(pCx);
api.sqlite3_result_null(pCx);
break;
}else if(undefined!==val.length){
const pBlob = Module.allocate(val, ALLOC_NORMAL);
S.sqlite3_result_blob(pCx, pBlob, val.length, -1/*==SQLITE_TRANSIENT*/);
api.sqlite3_result_blob(pCx, pBlob, val.length, -1/*==SQLITE_TRANSIENT*/);
Module._free(blobptr);
break;
}
@ -712,20 +715,20 @@
try{
f._setResult(pCx, callback.apply(null, f._extractArgs(argc, pArgv)));
}catch(e){
S.sqlite3_result_error(pCx, e.message, -1);
api.sqlite3_result_error(pCx, e.message, -1);
}
};
const pUdf = Module.addFunction(wrapper, "viii");
let fFlags = 0;
if(getOwnOption(opt, 'deterministic')) fFlags |= S.SQLITE_DETERMINISTIC;
if(getOwnOption(opt, 'directOnly')) fFlags |= S.SQLITE_DIRECTONLY;
if(getOwnOption(opt, 'innocuous')) fFlags |= S.SQLITE_INNOCUOUS;
if(getOwnOption(opt, 'deterministic')) fFlags |= api.SQLITE_DETERMINISTIC;
if(getOwnOption(opt, 'directOnly')) fFlags |= api.SQLITE_DIRECTONLY;
if(getOwnOption(opt, 'innocuous')) fFlags |= api.SQLITE_INNOCUOUS;
name = name.toLowerCase();
try {
this.checkRc(S.sqlite3_create_function_v2(
this.checkRc(api.sqlite3_create_function_v2(
this._pDb, name,
(opt.hasOwnProperty('arity') ? +opt.arity : callback.length),
S.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf,
api.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf,
null/*xStep*/, null/*xFinal*/, null/*xDestroy*/));
}catch(e){
Module.removeFunction(pUdf);
@ -802,7 +805,7 @@
*/
const affirmParamIndex = function(stmt,key){
const n = ('number'===typeof key)
? key : S.sqlite3_bind_parameter_index(stmt._pStmt, key);
? key : api.sqlite3_bind_parameter_index(stmt._pStmt, key);
if(0===n || (n===key && (n!==(n|0)/*floating point*/))){
toss("Invalid bind() parameter name: "+key);
}
@ -853,7 +856,7 @@
const bytes = intArrayFromString(val,true);
const pStr = Module.allocate(bytes, ALLOC_NORMAL);
stmt._allocs.push(pStr);
const func = asBlob ? S.sqlite3_bind_blob : S.sqlite3_bind_text;
const func = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text;
return func(stmt._pStmt, ndx, pStr, bytes.length, 0);
}
};
@ -863,7 +866,7 @@
let rc = 0;
switch((null===val || undefined===val) ? BindTypes.null : bindType){
case BindTypes.null:
rc = S.sqlite3_bind_null(stmt._pStmt, ndx);
rc = api.sqlite3_bind_null(stmt._pStmt, ndx);
break;
case BindTypes.string:{
rc = f._.string(stmt, ndx, val, false);
@ -871,15 +874,15 @@
}
case BindTypes.number: {
const m = (isInt32(val)
? S.sqlite3_bind_int
? api.sqlite3_bind_int
/*It's illegal to bind a 64-bit int
from here*/
: S.sqlite3_bind_double);
: api.sqlite3_bind_double);
rc = m(stmt._pStmt, ndx, val);
break;
}
case BindTypes.boolean:
rc = S.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0);
rc = api.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0);
break;
case BindTypes.blob: {
if('string'===typeof val){
@ -892,7 +895,7 @@
}
const pBlob = Module.allocate(val, ALLOC_NORMAL);
stmt._allocs.push(pBlob);
rc = S.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0);
rc = api.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0);
}
}
default: toss("Unsupported bind() argument type.");
@ -923,7 +926,7 @@
affirmUnlocked(this,'finalize()');
freeBindMemory(this);
delete this.db._statements[this._pStmt];
S.sqlite3_finalize(this._pStmt);
api.sqlite3_finalize(this._pStmt);
delete this.columnCount;
delete this.parameterCount;
delete this._pStmt;
@ -937,7 +940,7 @@
freeBindMemory(
affirmUnlocked(affirmStmtOpen(this), 'clearBindings()')
);
S.sqlite3_clear_bindings(this._pStmt);
api.sqlite3_clear_bindings(this._pStmt);
this._mayGet = false;
return this;
},
@ -953,7 +956,7 @@
reset: function(alsoClearBinds){
affirmUnlocked(this,'reset()');
if(alsoClearBinds) this.clearBindings();
S.sqlite3_reset(affirmStmtOpen(this)._pStmt);
api.sqlite3_reset(affirmStmtOpen(this)._pStmt);
this._mayGet = false;
return this;
},
@ -1098,14 +1101,14 @@
*/
step: function(){
affirmUnlocked(this, 'step()');
const rc = S.sqlite3_step(affirmStmtOpen(this)._pStmt);
const rc = api.sqlite3_step(affirmStmtOpen(this)._pStmt);
switch(rc){
case S.SQLITE_DONE: return this._mayGet = false;
case S.SQLITE_ROW: return this._mayGet = true;
case api.SQLITE_DONE: return this._mayGet = false;
case api.SQLITE_ROW: return this._mayGet = true;
default:
this._mayGet = false;
console.warn("sqlite3_step() rc=",rc,"SQL =",
S.sqlite3_sql(this._pStmt));
api.sqlite3_sql(this._pStmt));
this.db.checkRc(rc);
};
},
@ -1155,27 +1158,27 @@
}else if(ndx && 'object'===typeof ndx){
let i = 0;
while(i<this.columnCount){
ndx[S.sqlite3_column_name(this._pStmt,i)] = this.get(i++);
ndx[api.sqlite3_column_name(this._pStmt,i)] = this.get(i++);
}
return ndx;
}
affirmColIndex(this, ndx);
switch(undefined===asType
? S.sqlite3_column_type(this._pStmt, ndx)
? api.sqlite3_column_type(this._pStmt, ndx)
: asType){
case S.SQLITE_NULL: return null;
case S.SQLITE_INTEGER:{
return 0 | S.sqlite3_column_double(this._pStmt, ndx);
case api.SQLITE_NULL: return null;
case api.SQLITE_INTEGER:{
return 0 | api.sqlite3_column_double(this._pStmt, ndx);
/* ^^^^^^^^ strips any fractional part and handles
handles >32bits */
}
case S.SQLITE_FLOAT:
return S.sqlite3_column_double(this._pStmt, ndx);
case S.SQLITE_TEXT:
return S.sqlite3_column_text(this._pStmt, ndx);
case S.SQLITE_BLOB: {
const n = S.sqlite3_column_bytes(this._pStmt, ndx);
const ptr = S.sqlite3_column_blob(this._pStmt, ndx);
case api.SQLITE_FLOAT:
return api.sqlite3_column_double(this._pStmt, ndx);
case api.SQLITE_TEXT:
return api.sqlite3_column_text(this._pStmt, ndx);
case api.SQLITE_BLOB: {
const n = api.sqlite3_column_bytes(this._pStmt, ndx);
const ptr = api.sqlite3_column_blob(this._pStmt, ndx);
const rc = new Uint8Array(n);
for(let i = 0; i < n; ++i) rc[i] = HEAP8[ptr + i];
return rc;
@ -1187,16 +1190,16 @@
},
/** Equivalent to get(ndx) but coerces the result to an
integer. */
getInt: function(ndx){return this.get(ndx,S.SQLITE_INTEGER)},
getInt: function(ndx){return this.get(ndx,api.SQLITE_INTEGER)},
/** Equivalent to get(ndx) but coerces the result to a
float. */
getFloat: function(ndx){return this.get(ndx,S.SQLITE_FLOAT)},
getFloat: function(ndx){return this.get(ndx,api.SQLITE_FLOAT)},
/** Equivalent to get(ndx) but coerces the result to a
string. */
getString: function(ndx){return this.get(ndx,S.SQLITE_TEXT)},
getString: function(ndx){return this.get(ndx,api.SQLITE_TEXT)},
/** Equivalent to get(ndx) but coerces the result to a
Uint8Array. */
getBlob: function(ndx){return this.get(ndx,S.SQLITE_BLOB)},
getBlob: function(ndx){return this.get(ndx,api.SQLITE_BLOB)},
/**
A convenience wrapper around get() which fetches the value
as a string and then, if it is not null, passes it to
@ -1205,16 +1208,17 @@
string, on the other hand, will trigger an exception.
*/
getJSON: function(ndx){
const s = this.get(ndx, S.SQLITE_STRING);
const s = this.get(ndx, api.SQLITE_STRING);
return null===s ? s : JSON.parse(s);
},
/**
Returns the result column name of the given index, or
throws if index is out of bounds or this statement has been
finalized.
finalized. This can be used without having run step()
first.
*/
getColumnName: function(ndx){
return S.sqlite3_column_name(
return api.sqlite3_column_name(
affirmColIndex(affirmStmtOpen(this),ndx)._pStmt, ndx
);
},
@ -1230,7 +1234,7 @@
affirmColIndex(affirmStmtOpen(this),0);
if(!tgt) tgt = [];
for(let i = 0; i < this.columnCount; ++i){
tgt.push(S.sqlite3_column_name(this._pStmt, i));
tgt.push(api.sqlite3_column_name(this._pStmt, i));
}
return tgt;
},
@ -1242,7 +1246,7 @@
*/
getParamIndex: function(name){
return (affirmStmtOpen(this).parameterCount
? S.sqlite3_bind_parameter_index(this._pStmt, name)
? api.sqlite3_bind_parameter_index(this._pStmt, name)
: undefined);
}
}/*Stmt.prototype*/;
@ -1250,7 +1254,7 @@
/** OO binding's namespace. */
const SQLite3 = {
version: {
lib: S.sqlite3_libversion(),
lib: api.sqlite3_libversion(),
ooApi: "0.0.1"
},
DB,
@ -1293,7 +1297,7 @@
}
const rc = {}, ov = [0,0];
let i = 0, k;
while((k = S.sqlite3_compileoption_get(i++))){
while((k = api.sqlite3_compileoption_get(i++))){
f._opt(k,ov);
rc[ov[0]] = ov[1];
}
@ -1302,19 +1306,19 @@
else if(Array.isArray(optName)){
const rc = {};
optName.forEach((v)=>{
rc[v] = S.sqlite3_compileoption_used(v);
rc[v] = api.sqlite3_compileoption_used(v);
});
return rc;
}
else if('object' === typeof optName){
Object.keys(optName).forEach((k)=> {
optName[k] = S.sqlite3_compileoption_used(k);
optName[k] = api.sqlite3_compileoption_used(k);
});
return optName;
}
return (
'string'===typeof optName
) ? !!S.sqlite3_compileoption_used(optName) : false;
) ? !!api.sqlite3_compileoption_used(optName) : false;
}
};
@ -1322,4 +1326,4 @@
api: api,
SQLite3
};
})(self/*worker or window*/);
})(self/*worker or window*/.Module);

View File

@ -26,9 +26,6 @@
};
/* emscripten-related bits... */
const statusElement = E('#module-status');
const progressElement = E('#module-progress');
const spinnerElement = E('#module-spinner');
self.Module = {
/* ^^^ cannot declare that const because sqlite3.js
(auto-generated) includes a decl for it and runs in this
@ -43,25 +40,33 @@
console.error.apply(console, Array.prototype.slice.call(arguments));
},
setStatus: function f(text){
if(!f.last) f.last = { time: Date.now(), text: '' };
if(text === f.last.text) return;
const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
const now = Date.now();
if(m && now - f.last.time < 30) return; // if this is a progress update, skip it if too soon
f.last.time = now;
f.last.text = text;
if(m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.remove();
if(!text) spinnerElement.remove();
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');
}
if(text) statusElement.innerText = text;
else statusElement.remove();
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
@ -71,16 +76,33 @@
+ '/' + this.totalDependencies + ')')
: 'All downloads complete.');
},
/* Loads sqlite3-api.js and calls the given callback (if
provided), passing it an object which contains the sqlite3
and SQLite3 modules. Whether this is synchronous or async
depends on whether it's run in the main thread (async) or a
worker (synchronous). */
loadSqliteAPI: function(callback){
/**
Loads sqlite3-api.js and calls the given callback (if
provided), passing it an object:
{
api:sqlite3_c-like API wrapper,
SQLite3: OO wrapper
}
Whether this is synchronous or async depends on whether
it's run in the main thread (async) or a worker
(synchronous).
If called after the module has been loaded, it uses a
cached reference, noting that multiple async calls may end
up loading it multiple times.
*/
loadSqliteAPI: function f(callback){
const namespace = self.Module;
if(namespace.sqlite3){
if(callback) callback(namespace.sqlite3);
return;
}
const theScript = 'sqlite3-api.js';
if(self.importScripts){/*worker*/
importScripts(theScript);
if(callback) callback(self.sqlite3);
if(callback) callback(namespace.sqlite3);
}else{/*main thread*/
new Promise((resolve, reject) => {
const script = document.createElement('script');
@ -90,8 +112,7 @@
script.async = true;
script.src = theScript;
}).then(() => {
if(callback) callback({sqlite3:self.sqlite3,
SQLite3:self.SQLite3});
if(callback) callback(namespace.sqlite3);
});
}
}

31
ext/fiddle/testing.css Normal file
View File

@ -0,0 +1,31 @@
textarea {
font-family: monospace;
}
header {
font-size: 130%;
font-weight: bold;
}
.hidden, .initially-hidden {
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
display: none !important;
}
fieldset.options {
font-size: 75%;
}
fieldset > legend {
padding: 0 0.5em;
}
span.labeled-input {
padding: 0.25em;
margin: 0.25em 0.5em;
border-radius: 0.25em;
white-space: nowrap;
background: #0002;
}
.center { text-align: center; }
.error {
color: red;
background-color: yellow;
}

View File

@ -4,6 +4,7 @@
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="emscripten.css"/>
<link rel="stylesheet" href="testing.css"/>
<title>sqlite3-api.js tests</title>
<style></style>
</head>

View File

@ -12,27 +12,15 @@
A basic test script for sqlite3-api.js.
*/
const mainTest1 = function(namespace){
(function(){
const T = self.SqliteTestUtil;
T.assert(Module._free instanceof Function).
assert(Module.allocate instanceof Function).
assert(Module.addFunction instanceof Function).
assert(Module.removeFunction instanceof Function);
const S = namespace.sqlite3.api;
const oo = namespace.sqlite3.SQLite3;
console.log("Loaded module:",S.sqlite3_libversion(),
S.sqlite3_sourceid());
const db = new oo.DB();
const log = console.log.bind(console);
try {
const test1 = function(db,api){
log("Basic sanity tests...");
T.assert(db._pDb);
log("DB:",db.filename);
log("Build options:",oo.compileOptionUsed());
let st = db.prepare("select 3 as a");
log("statement =",st);
//log("statement =",st);
T.assert(st._pStmt)
.assert(!st._mayGet)
.assert('a' === st.getColumnName(0))
@ -43,14 +31,14 @@ const mainTest1 = function(namespace){
.assert(true===st.step())
.assert(3 === st.get(0))
.mustThrow(()=>st.get(1))
.mustThrow(()=>st.get(0,~S.SQLITE_INTEGER))
.assert(3 === st.get(0,S.SQLITE_INTEGER))
.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,S.SQLITE_TEXT))
.assert('3' === st.get(0,api.SQLITE_TEXT))
.assert('3' === st.getString(0))
.assert(3.0 === st.get(0,S.SQLITE_FLOAT))
.assert(3.0 === st.get(0,api.SQLITE_FLOAT))
.assert(3.0 === st.getFloat(0))
.assert(st.get(0,S.SQLITE_BLOB) instanceof Uint8Array)
.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)
@ -73,7 +61,7 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
bind: [5,6]
});
T.assert(2 === list.length);
log("Exec'd SQL:", list);
//log("Exec'd SQL:", list);
let counter = 0, colNames = [];
db.exec("SELECT a a, b b FROM t",{
rowMode: 'object',
@ -95,7 +83,9 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
}
});
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)")).
@ -110,6 +100,8 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
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)")).
@ -120,18 +112,34 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`,
assert(null === db.selectValue("select ?",null)).
assert(null === db.selectValue("select ?",[null])).
assert(null === db.selectValue("select $a",{$a:null}));
}finally{
db.close();
}
log("Total Test count:",T.counter);
};
};
self/*window or worker*/.Module.postRun.push(function(theModule){
/** Use a timeout so that we are (hopefully) out from under the
module init stack when our setup gets run. */
setTimeout(function(){
theModule.loadSqliteAPI(mainTest1);
},0);
});
const runTests = function(namespace){
T.assert(Module._free instanceof Function).
assert(Module.allocate instanceof Function).
assert(Module.addFunction instanceof Function).
assert(Module.removeFunction instanceof Function);
const api = namespace.api;
const oo = namespace.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
].forEach((f)=>f(db, api));
}finally{
db.close();
}
log("Total Test count:",T.counter);
};
self.Module.postRun.push(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. */
setTimeout(()=>theModule.loadSqliteAPI(runTests), 0);
});
})(self/*window or worker*/);