Note that this file is not named sqlite3.js because that file gets
generated by emscripten as the JS-glue counterpart of sqlite3.wasm.
- The API gets installed as self.sqlite3, where self is expected to be
- either the global window or Worker object. In addition, a higher-level
- OO API is installed as self.SQLite3.
-
- Potential TODO: instead of exporting 2 symbols, export only SQLite3
- as {api: sqlite3, oo1: SQLite3}. The way we export this module is
- not _really_ modern-JS-friendly because it exports global symbols
- (which is admittedly poor form). Exporting it "cleanly" 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.
+ This code installs an object named self.sqlite3, where self is
+ expected to be either the global window or Worker object:
+
+ self.sqlite3 = {
+ api: core WASM bindings of 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 such a wrapper on top of 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`,
["sqlite3_bind_blob","number",["number", "number", "number", "number", "number"]],
["sqlite3_bind_double","number",["number", "number", "number"]],
["sqlite3_bind_int","number",["number", "number", "number"]],
- ["sqlite3_bind_int64","number",["number", "number", "number"]],
+ [/*Noting that wasm does not currently support 64-bit integers:*/
+ "sqlite3_bind_int64","number",["number", "number", "number"]],
["sqlite3_bind_null","void",["number"]],
["sqlite3_bind_parameter_count", "number", ["number"]],
["sqlite3_bind_parameter_index","number",["number", "string"]],
["sqlite3_column_count", "number", ["number"]],
["sqlite3_column_count","number",["number"]],
["sqlite3_column_double","number",["number", "number"]],
+ ["sqlite3_column_int","number",["number", "number"]],
+ [/*Noting that wasm does not currently support 64-bit integers:*/
+ "sqlite3_column_int64","number",["number", "number"]],
["sqlite3_column_name","string",["number", "number"]],
["sqlite3_column_text","string",["number", "number"]],
["sqlite3_column_type","number",["number", "number"]],
and the sqlite3 API is in a worker. */
/* memory for use in some pointer-passing routines */
const pPtrArg = stackAlloc(4);
+ /** Throws a new error, concatenating all args with a space between
+ each. */
const toss = function(){
throw new Error(Array.prototype.join.call(arguments, ' '));
};
toss("TODO: support blob image of db here.");
}
this.checkRc(S.sqlite3_open(name, pPtrArg));
- this.pDb = getValue(pPtrArg, "i32");
+ this._pDb = getValue(pPtrArg, "i32");
this.filename = name;
this._statements = {/*map of open Stmt _pointers_*/};
};
toss("Do not call the Stmt constructor directly. Use DB.prepare().");
}
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._pStmt = arguments[1];
+ this.columnCount = S.sqlite3_column_count(this._pStmt);
+ this.parameterCount = S.sqlite3_bind_parameter_count(this._pStmt);
this._allocs = [/*list of alloc'd memory blocks for bind() values*/]
};
/** Throws if the given DB has been closed, else it is returned. */
const affirmDbOpen = function(db){
- if(!db.pDb) toss("DB has been closed.");
+ if(!db._pDb) toss("DB has been closed.");
return db;
};
*/
checkRc: function(sqliteResultCode){
if(!sqliteResultCode) return this;
- toss(S.sqlite3_errmsg(this.pDb) || "Unknown db error.");
+ toss(S.sqlite3_errmsg(this._pDb) || "Unknown db error.");
},
/**
Finalizes all open statements and closes this database
closed.
*/
close: function(){
- if(this.pDb){
+ if(this._pDb){
let s;
const that = this;
Object.keys(this._statements).forEach(function(k,s){
delete that._statements[k];
- if(s && s.pStmt) s.finalize();
+ if(s && s._pStmt) s.finalize();
});
- S.sqlite3_close_v2(this.pDb);
- delete this.pDb;
+ S.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 S.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(S.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);
returns a falsy value. */
const isSupportedBindType = function(v){
let t = BindTypes[null===v ? 'null' : typeof v];
- if(t) return t;
- // TODO: handle buffer/blob types.
- return undefined;
+ switch(t){
+ case BindTypes.boolean:
+ case BindTypes.null:
+ case BindTypes.number:
+ case BindTypes.string:
+ return t;
+ default:
+ if(v instanceof Uint8Array) return BindTypes.blob;
+ toss("Unhandled isSupportedBindType()",t);
+ }
}
/**
function returns that value, else it throws.
*/
const affirmSupportedBindType = function(v){
- const t = isSupportedBindType(v);
- if(t) return t;
- toss("Unsupport bind() argument type.");
+ return isSupportedBindType(v) || toss("Unsupport bind() argument type.");
};
/**
Else it throws.
*/
- const indexOfParam = function(stmt,key){
+ const affirmParamIndex = function(stmt,key){
const n = ('number'===typeof key)
- ? key : S.sqlite3_bind_parameter_index(stmt.pStmt, key);
+ ? key : S.sqlite3_bind_parameter_index(stmt._pStmt, key);
if(0===n || (n===key && (n!==(n|0)/*floating point*/))){
toss("Invalid bind() parameter name: "+key);
}
return n;
};
+ /** Throws if ndx is not an integer or if it is out of range
+ for stmt.columnCount, else returns stmt. */
+ const affirmColIndex = function(stmt,ndx){
+ if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){
+ toss("Column index",ndx,"is out of range.");
+ }
+ return stmt;
+ };
+
+ /** If stmt._mayGet, returns stmt, else throws. */
+ const affirmMayGet = function(stmt){
+ if(!affirmStmtOpen(stmt)._mayGet){
+ toss("Statement.step() has not (recently) returned true.");
+ }
+ return stmt;
+ };
+
/**
Binds a single bound parameter value on the given stmt at the
given index (numeric or named) using the given bindType (see
the BindTypes enum) and value. Throws on error. Returns stmt on
success.
*/
- const bindOne = function(stmt,ndx,bindType,val){
+ const bindOne = function f(stmt,ndx,bindType,val){
+ if(!f._){
+ f._ = {
+ string: function(stmt, ndx, val, asBlob){
+ const bytes = intArrayFromString(val,true);
+ const pStr = allocate(bytes, ALLOC_NORMAL);
+ stmt._allocs.push(pStr);
+ const func = asBlob ? S.sqlite3_bind_blob : S.sqlite3_bind_text;
+ return func(stmt._pStmt, ndx, pStr, bytes.length, 0);
+ }
+ };
+ }
affirmSupportedBindType(val);
- ndx = indexOfParam(stmt,ndx);
+ ndx = affirmParamIndex(stmt,ndx);
let rc = 0;
- switch(bindType){
+ switch((null===val || undefined===val) ? BindTypes.null : bindType){
case BindType.null:
- rc = S.sqlite3_bind_null(stmt.pStmt, ndx);
+ rc = S.sqlite3_bind_null(stmt._pStmt, ndx);
break;
case BindType.string:{
- const bytes = intArrayFromString(string,false);
- const pStr = allocate(bytes, ALLOC_NORMAL);
- stmt._allocs.push(pStr);
- rc = S.sqlite3_bind_text(stmt.pStmt, ndx, pStr,
- bytes.length, 0);
+ rc = f._.string(stmt, ndx, val, false);
break;
}
case BindType.number: {
const m = ((val === (val|0))
- ? (val>0xefffffff
+ ? ((val & 0x00000000/*>32 bits*/)
? S.sqlite3_bind_int64
: S.sqlite3_bind_int)
: S.sqlite3_bind_double);
- rc = m(stmt.pStmt, ndx, val);
+ rc = m(stmt._pStmt, ndx, val);
break;
}
case BindType.boolean:
- rc = S.sqlite3_bind_int(stmt.pStmt, ndx, val ? 1 : 0);
+ rc = S.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0);
break;
- case BindType.blob:
+ case BindType.blob: {
+ if('string'===typeof val){
+ rc = f._.string(stmt, ndx, val, true);
+ }else{
+ const len = val.length;
+ if(undefined===len){
+ toss("Binding a value as a blob requires",
+ "that it have a length member.");
+ }
+ const pBlob = allocate(val, ALLOC_NORMAL);
+ stmt._allocs.push(pBlob);
+ rc = S.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0);
+ }
+ }
default: toss("Unsupported bind() argument type.");
}
if(rc) stmt.db.checkRc(rc);
/** Throws if the given Stmt has been finalized, else
it is returned. */
const affirmStmtOpen = function(stmt){
- if(!stmt.pStmt) toss("Stmt has been closed.");
+ if(!stmt._pStmt) toss("Stmt has been closed.");
return stmt;
};
after this is.
*/
finalize: function(){
- if(this.pStmt){
+ if(this._pStmt){
freeBindMemory(this);
- delete this.db._statements[this.pStmt];
- S.sqlite3_finalize(this.pStmt);
- delete this.pStmt;
+ delete this.db._statements[this._pStmt];
+ S.sqlite3_finalize(this._pStmt);
+ delete this.columnCount;
+ delete this.parameterCount;
+ delete this._pStmt;
delete this.db;
}
},
Throws if this statement has been finalized. */
clearBindings: function(){
freeBindMemory(affirmStmtOpen(this));
- S.sqlite3_clear_bindings(this.pStmt);
+ S.sqlite3_clear_bindings(this._pStmt);
+ this._mayGet = false;
return this;
},
/**
*/
reset: function(alsoClearBinds){
if(alsoClearBinds) this.clearBindings();
- S.sqlite3_reset(affirmStmtOpen(this).pStmt);
+ S.sqlite3_reset(affirmStmtOpen(this)._pStmt);
+ this._mayGet = false;
return this;
},
/**
- Strings are bound as strings (use bindAsBlob() to force
blob binding).
- - buffers (blobs) are currently TODO but will be bound as
- blobs.
+ - Uint8Array instances are bound as blobs.
If passed an array, each element of the array is bound at
the parameter index equal to the array index plus 1
- The statement has been finalized.
*/
bind: function(/*[ndx,] value*/){
- if(!this.parameterCount){
+ if(!affirmStmtOpen(this).parameterCount){
toss("This statement has no bindable parameters.");
}
+ this._mayGet = false;
let ndx, arg;
switch(arguments.length){
case 1: ndx = 1; arg = arguments[0]; break;
case 2: ndx = arguments[0]; arg = arguments[1]; break;
default: toss("Invalid bind() arguments.");
}
- affirmStmtOpen(this);
if(null===arg || undefined===arg){
/* bind NULL */
return bindOne(this, ndx, BindType.null, arg);
using the BLOB binding mechanism instead of the default
selected one for the value. The ndx may be a numbered
or named bind index. The value must be of type string,
- buffer, or null/undefined (both treated as null).
+ Uint8Array, or null/undefined (both treated as null).
- If passed a single argument, a bind index of 1 is assumed.
+ If passed a single argument, a bind index of 1 is assumed.
*/
bindAsBlob: function(ndx,arg){
- affirmStmtOpen(this);
+ affirmStmtOpen(this)._mayGet = false;
if(1===arguments.length){
ndx = 1;
arg = arguments[0];
toss("Invalid value type for bindAsBlob()");
}
return bindOne(this, ndx, BindType.blob, arg);
+ },
+ /**
+ Steps the statement one time. If the result indicates that
+ a row of data is available, true is returned. If no row of
+ data is available, false is returned. Throws on error.
+ */
+ step: function(){
+ const rc = S.sqlite3_step(affirmStmtOpen(this)._pStmt);
+ this._mayGet = false;
+ switch(rc){
+ case S.SQLITE_DONE: return false;
+ case S.SQLITE_ROW: return this._mayGet = true;
+ default: this.db.checkRc(rc);
+ };
+ },
+ /**
+ Fetches the value from the given 0-based column index of
+ the current data row, throwing if index is out of range.
+
+ Requires that step() has just returned a truthy value, else
+ an exception is thrown.
+
+ By default it will determine the data type of the result
+ automatically. If passed a second arugment, it must be one
+ of the enumeration values for sqlite3 types, which are
+ defined as members of the sqlite3 module: SQLITE_INTEGER,
+ SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value,
+ except for undefined, will trigger an exception. Passing
+ undefined is the same as not passing a value. It is legal
+ to, e.g., fetch an integer value as a string, in which case
+ sqlite3 will convert the value to a string.
+
+ If ndx is an array, this function behaves a differently: it
+ assigns the indexes of the array, from 0 to the number of
+ result columns, to the values of the corresponding column,
+ and returns that array.
+
+ If ndx is a plain object, this function behaves even
+ differentlier: it assigns the properties of the object to
+ the values of their corresponding result columns.
+
+ Blobs are returned as Uint8Array instances.
+
+ Potential TODO: add type ID SQLITE_JSON, which fetches the
+ result as a string and passes it (if it's not null) to
+ JSON.parse(), returning the result of that. Until then,
+ getJSON() can be used for that.
+ */
+ get: function(ndx,asType){
+ affirmMayGet(this);
+ if(Array.isArray(ndx)){
+ let i = 0;
+ while(i<this.columnCount){
+ ndx[i] = this.get(i++);
+ }
+ return ndx;
+ }else if(ndx && 'object'===typeof ndx){
+ let i = 0;
+ while(i<this.columnCount){
+ ndx[S.sqlite3_column_name(this._pStmt,i)] = this.get(i++);
+ }
+ return ndx;
+ }
+ affirmColIndex(this, ndx);
+ switch(undefined===asType
+ ? S.sqlite3_column_type(this._pStmt, ndx)
+ : asType){
+ case S.SQLITE_INTEGER:
+ return S.sqlite3_column_int64(this._pStmt, ndx);
+ 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);
+ const rc = new Uint8Array(n);
+ for(let i = 0; i < n; ++i) rc[i] = HEAP8[ptr + i];
+ return rc;
+ }
+ default: toss("Don't know how to translate",
+ "type of result column #"+ndx+".");
+ }
+ abort("Not reached.");
+ },
+ /** Equivalent to get(ndx) but coerces the result to an
+ integer. */
+ getInt: function(ndx){return this.get(ndx,S.SQLITE_INTEGER)},
+ /** Equivalent to get(ndx) but coerces the result to a
+ float. */
+ getFloat: function(ndx){return this.get(ndx,S.SQLITE_FLOAT)},
+ /** Equivalent to get(ndx) but coerces the result to a
+ string. */
+ getString: function(ndx){return this.get(ndx,S.SQLITE_TEXT)},
+ /** Equivalent to get(ndx) but coerces the result to a
+ Uint8Array. */
+ getBlob: function(ndx){return this.get(ndx,S.SQLITE_BLOB)},
+ /**
+ A convenience wrapper around get() which fetches the value
+ as a string and then, if it is not null, passes it to
+ JSON.parse(), returning that result. Throws if parsing
+ fails.
+ */
+ getJSON: function(ndx){
+ const s = this.get(ndx, S.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.
+ */
+ getColumnName: function(ndx){
+ return S.sqlite3_column_name(affirmColIndex(this,ndx)._pStmt, ndx);
}
};
if(!arguments.length){
if(!f._opt){
f._rx = /^([^=]+)=(.+)/;
- f._rxInt = /^-?\d+/;
+ f._rxInt = /^-?\d+$/;
f._opt = function(opt, rv){
const m = f._rx.exec(opt);
rv[0] = (m ? m[1] : opt);
}
};
- namespace.sqlite3 = sqlite3;
- namespace.SQLite3 = SQLite3;
+ namespace.sqlite3 = {
+ api:sqlite3,
+ SQLite3
+ };
})(self/*worker or window*/);