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
+99 -95
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);