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 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 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 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
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.
throw new Error(Array.prototype.join.call(arguments, ' '));
};
- const S/*convenience alias*/ = api;
-
/**
The DB class wraps a sqlite3 db handle.
*/
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*/};
}
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*/]
};
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
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;
}
},
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
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);
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){
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];
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;
}
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);
*/
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);
}
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);
}
};
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);
}
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){
}
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.");
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;
freeBindMemory(
affirmUnlocked(affirmStmtOpen(this), 'clearBindings()')
);
- S.sqlite3_clear_bindings(this._pStmt);
+ api.sqlite3_clear_bindings(this._pStmt);
this._mayGet = false;
return this;
},
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;
},
*/
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);
};
},
}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;
},
/** 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
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
);
},
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;
},
*/
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*/;
/** OO binding's namespace. */
const SQLite3 = {
version: {
- lib: S.sqlite3_libversion(),
+ lib: api.sqlite3_libversion(),
ooApi: "0.0.1"
},
DB,
}
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];
}
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;
}
};
api: api,
SQLite3
};
-})(self/*worker or window*/);
+})(self/*worker or window*/.Module);
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))
.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)
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',
}
});
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)")).
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(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);
-};
+ };
+
+ 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/*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);
-});
+ 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*/);