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:
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
31
ext/fiddle/testing.css
Normal 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;
|
||||
}
|
@ -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>
|
||||
|
@ -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*/);
|
||||
|
Reference in New Issue
Block a user