***********************************************************************
- This file is intended to be loaded after loading
- sqlite3-module.wasm. It sets one of any number of potential
- bindings using that API, this one as closely matching the C-native
- API as is feasible.
+ This file is intended to be loaded after loading sqlite3.wasm. It
+ sets one of any number of potential bindings using that API, this
+ one as closely matching the C-native API as is feasible.
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.
+ either the global window or Worker object. In addition, a higher-level
+ OO API is installed as self.SQLite3.
- Because using this API properly requires some degree of WASM-related
- magic, it is not recommended that this API be used as-is in
- client-level code, but instead is intended to be used as a basis for
- APIs more appropriate for high-level client code.
+ 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.
+
+ 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
+ lower-level API.
This file installs namespace.sqlite3, where namespace is `self`,
meaning either the global window or worker, depending on where this
/* It is important that the following integer values match
those from the C code. Ideally we could fetch them from the
C API, e.g., in the form of a JSON object, but getting that
- JSON string constructed within our current confised is
- currently not worth the effort. */
+ JSON string constructed within our current confines is
+ currently not worth the effort.
+
+ Reminder to self: we could probably do so by adding the
+ proverbial level of indirection, calling in to C to get it,
+ and having that C func call an
+ emscripten-installed/JS-implemented library function which
+ builds the result object:
+
+ const obj = {};
+ sqlite3__get_enum(function(key,val){
+ obj[key] = val;
+ });
+
+ but whether or not we can pass a function that way, via a
+ (void*) is as yet unknown.
+ */
/* Minimum subset of sqlite result codes we'll need. */
SQLITE_OK: 0,
SQLITE_ROW: 100,
["sqlite3_bind_int","number",["number", "number", "number"]],
["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_bind_text","number",["number", "number", "number", "number", "number"]],
["sqlite3_changes", "number", ["number"]],
["sqlite3_column_name","string",["number", "number"]],
["sqlite3_column_text","string",["number", "number"]],
["sqlite3_column_type","number",["number", "number"]],
+ ["sqlite3_compileoption_get", "string", ["number"]],
+ ["sqlite3_compileoption_used", "number", ["string"]],
["sqlite3_create_function_v2", "number",
["number", "string", "number", "number","number",
"number", "number", "number", "number"]],
api[k] = cwrap.apply(this, a);
});
//console.debug("libversion =",api.sqlite3_libversion());
- namespace.sqlite3 = api;
+
+ /* What follows is colloquially known as "OO API #1". It is a
+ binding of the sqlite3 API which is designed to be run within
+ the same thread (main or worker) as the one in which the
+ sqlite3 WASM binding was initialized. This wrapper cannot use
+ the sqlite3 binding if, e.g., the wrapper is in the main thread
+ and the sqlite3 API is in a worker. */
+ /* memory for use in some pointer-passing routines */
+ const pPtrArg = stackAlloc(4);
+ const toss = function(){
+ throw new Error(Array.prototype.join.call(arguments, ' '));
+ };
+
+ const sqlite3/*canonical name*/ = S/*convenience alias*/ = api;
+
+ /**
+ The DB class wraps a sqlite3 db handle.
+ */
+ const DB = function(name/*TODO: openMode flags*/){
+ if(!name) name = ':memory:';
+ else if('string'!==typeof name){
+ toss("TODO: support blob image of db here.");
+ }
+ this.checkRc(S.sqlite3_open(name, pPtrArg));
+ this.pDb = getValue(pPtrArg, "i32");
+ this.filename = name;
+ this._statements = {/*map of open Stmt _pointers_*/};
+ };
+
+ /**
+ Internal-use enum for mapping JS types to DB-bindable types.
+ These do not (and need not) line up with the SQLITE_type
+ values. All values in this enum must be truthy and distinct
+ but they need not be numbers.
+ */
+ const BindTypes = {
+ null: 1,
+ number: 2,
+ string: 3,
+ boolean: 4,
+ blob: 5
+ };
+ BindTypes['undefined'] == BindTypes.null;
+
+ /**
+ This class wraps sqlite3_stmt. Calling this constructor
+ directly will trigger an exception. Use DB.prepare() to create
+ new instances.
+ */
+ const Stmt = function(){
+ if(BindTypes!=arguments[2]){
+ 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._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.");
+ return db;
+ };
+
+ DB.prototype = {
+ /**
+ Expects to be given an sqlite3 API result code. If it is
+ falsy, this function returns this object, else it throws an
+ exception with an error message from sqlite3_errmsg(),
+ using this object's db handle.
+ */
+ checkRc: function(sqliteResultCode){
+ if(!sqliteResultCode) return this;
+ toss(S.sqlite3_errmsg(this.pDb) || "Unknown db error.");
+ },
+ /**
+ Finalizes all open statements and closes this database
+ connection. This is a no-op if the db has already been
+ closed.
+ */
+ close: function(){
+ 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();
+ });
+ S.sqlite3_close_v2(this.pDb);
+ delete this.pDb;
+ }
+ },
+ /**
+ Similar to this.filename but will return NULL for
+ special names like ":memory:". Not of much use until
+ we have filesystem support. Throws if the DB has
+ been closed. If passed an argument it then it will return
+ the filename of the ATTACHEd db with that name, else it assumes
+ a name of `main`.
+ */
+ fileName: function(dbName){
+ return S.sqlite3_db_filename(affirmDbOpen(this).pDb, dbName||"main");
+ },
+ /**
+ Compiles the given SQL and returns a prepared Stmt. This is
+ the only way to create new Stmt objects. Throws on error.
+ */
+ prepare: function(sql){
+ affirmDbOpen(this);
+ setValue(pPtrArg,0,"i32");
+ 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);
+ this._statements[pStmt] = stmt;
+ return stmt;
+ }
+ };
+
+ /** Returns an opaque truthy value from the BindTypes
+ enum if v's type is a valid bindable type, else
+ 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;
+ }
+
+ /**
+ If isSupportedBindType(v) returns a truthy value, this
+ function returns that value, else it throws.
+ */
+ const affirmSupportedBindType = function(v){
+ const t = isSupportedBindType(v);
+ if(t) return t;
+ toss("Unsupport bind() argument type.");
+ };
+
+ /**
+ If key is a number and within range of stmt's bound parameter
+ count, key is returned.
+
+ If key is not a number then it is checked against named
+ parameters. If a match is found, its index is returned.
+
+ Else it throws.
+ */
+ const indexOfParam = function(stmt,key){
+ const n = ('number'===typeof 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);
+ }
+ else if(n<1 || n>=stmt.parameterCount) toss("Bind index",key,"is out of range.");
+ return n;
+ };
+
+ /**
+ 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){
+ affirmSupportedBindType(val);
+ ndx = indexOfParam(stmt,ndx);
+ let rc = 0;
+ switch(bindType){
+ case BindType.null:
+ 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);
+ break;
+ }
+ case BindType.number: {
+ const m = ((val === (val|0))
+ ? (val>0xefffffff
+ ? S.sqlite3_bind_int64
+ : S.sqlite3_bind_int)
+ : S.sqlite3_bind_double);
+ rc = m(stmt.pStmt, ndx, val);
+ break;
+ }
+ case BindType.boolean:
+ rc = S.sqlite3_bind_int(stmt.pStmt, ndx, val ? 1 : 0);
+ break;
+ case BindType.blob:
+ default: toss("Unsupported bind() argument type.");
+ }
+ if(rc) stmt.db.checkRc(rc);
+ return stmt;
+ };
+
+ /** Throws if the given Stmt has been finalized, else
+ it is returned. */
+ const affirmStmtOpen = function(stmt){
+ if(!stmt.pStmt) toss("Stmt has been closed.");
+ return stmt;
+ };
+
+ /** Frees any memory explicitly allocated for the given
+ Stmt object. Returns stmt. */
+ const freeBindMemory = function(stmt){
+ let m;
+ while(undefined !== (m = stmt._allocs.pop())){
+ _free(m);
+ }
+ return stmt;
+ };
+
+ Stmt.prototype = {
+ /**
+ "Finalizes" this statement. This is a no-op if the
+ statement has already been finalizes. Returns
+ undefined. Most methods in this class will throw if called
+ after this is.
+ */
+ finalize: function(){
+ if(this.pStmt){
+ freeBindMemory(this);
+ delete this.db._statements[this.pStmt];
+ S.sqlite3_finalize(this.pStmt);
+ delete this.pStmt;
+ delete this.db;
+ }
+ },
+ /** Clears all bound values. Returns this object.
+ Throws if this statement has been finalized. */
+ clearBindings: function(){
+ freeBindMemory(affirmStmtOpen(this));
+ S.sqlite3_clear_bindings(this.pStmt);
+ return this;
+ },
+ /**
+ Resets this statement so that it may be step()ed again
+ from the beginning. Returns this object. Throws if this
+ statement has been finalized.
+
+ If passed a truthy argument then this.clearBindings() is
+ also called, otherwise any existing bindings, along with
+ any memory allocated for them, are retained.
+ */
+ reset: function(alsoClearBinds){
+ if(alsoClearBinds) this.clearBindings();
+ S.sqlite3_reset(affirmStmtOpen(this).pStmt);
+ return this;
+ },
+ /**
+ Binds one or more values to its bindable parameters. It
+ accepts 1 or 2 arguments:
+
+ If passed a single argument, it must be either an array, an
+ object, or a value of a bindable type (see below).
+
+ If passed 2 arguments, the first one is the 1-based bind
+ index or bindable parameter name and the second one must be
+ a value of a bindable type.
+
+ Bindable value types:
+
+ - null or undefined is bound as NULL.
+
+ - Numbers are bound as either doubles or integers: int64 if
+ they are larger than 0xEFFFFFFF, else int32. Booleans are
+ bound as integer 0 or 1. Note that doubles with no
+ fractional part are bound as integers. It is not expected
+ that that distinction is significant for the majority of
+ clients due to sqlite3's data typing model. This API does
+ not currently support the BigInt type.
+
+ - Strings are bound as strings (use bindAsBlob() to force
+ blob binding).
+
+ - buffers (blobs) are currently TODO but will be 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
+ (because arrays are 0-based but binding is 1-based).
+
+ If passed an object, each object key is treated as a
+ bindable parameter name. The object keys _must_ match any
+ bindable parameter names, including any `$`, `@`, or `:`
+ prefix. Because `$` is a legal identifier chararacter in
+ JavaScript, that is the suggested prefix for bindable
+ parameters.
+
+ It returns this object on success and throws on
+ error. Errors include:
+
+ - Any bind index is out of range, a named bind parameter
+ does not match, or this statement has no bindable
+ parameters.
+
+ - Any value to bind is of an unsupported type.
+
+ - Passed no arguments or more than two.
+
+ - The statement has been finalized.
+ */
+ bind: function(/*[ndx,] value*/){
+ if(!this.parameterCount){
+ toss("This statement has no bindable parameters.");
+ }
+ 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);
+ }
+ else if(Array.isArray(arg)){
+ /* bind each entry by index */
+ if(1!==arguments.length){
+ toss("When binding an array, an index argument is not permitted.");
+ }
+ arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
+ return this;
+ }
+ else if('object'===typeof arg/*null was checked above*/){
+ /* bind by name */
+ if(1!==arguments.length){
+ toss("When binding an object, an index argument is not permitted.");
+ }
+ Object.keys(arg)
+ .forEach(k=>bindOne(this, k,
+ affirmSupportedBindType(arg[k]),
+ arg[k]));
+ return this;
+ }else{
+ return bindOne(this, ndx,
+ affirmSupportedBindType(arg), arg);
+ }
+ toss("Should not reach this point.");
+ },
+ /**
+ Special case of bind() which binds the given value
+ 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).
+
+ If passed a single argument, a bind index of 1 is assumed.
+ */
+ bindAsBlob: function(ndx,arg){
+ affirmStmtOpen(this);
+ if(1===arguments.length){
+ ndx = 1;
+ arg = arguments[0];
+ }
+ const t = affirmSupportedBindType(arg);
+ if(BindTypes.string !== t && BindTypes.blob !== t
+ && BindTypes.null !== t){
+ toss("Invalid value type for bindAsBlob()");
+ }
+ return bindOne(this, ndx, BindType.blob, arg);
+ }
+ };
+
+ /** OO binding's namespace. */
+ const SQLite3 = {
+ version: {
+ lib: sqlite3.sqlite3_libversion(),
+ ooApi: "0.0.1"
+ },
+ DB,
+ Stmt,
+ /**
+ Reports whether a given compile-time option, named by the
+ given argument.
+
+ If optName is an array then it is expected to be a list of
+ compilation options and this function returns an object
+ which maps each such option to true or false. That object
+ is returned.
+
+ If optName is an object, its keys are expected to be
+ compilation options and this function sets each entry to
+ true or false. That object is returned.
+
+ If passed no arguments then it returns an object mapping
+ all known compilation options to their compile-time values,
+ or true if the are defined with no value.
+
+ In all other cases it returns true if the option was active
+ when when compiling the sqlite3 module, else false.
+
+ Compile-time option names may optionally include their
+ "SQLITE_" prefix. When it returns an object of all options,
+ the prefix is elided.
+ */
+ compileOptionUsed: function f(optName){
+ if(!arguments.length){
+ if(!f._opt){
+ f._rx = /^([^=]+)=(.+)/;
+ f._rxInt = /^-?\d+/;
+ f._opt = function(opt, rv){
+ const m = f._rx.exec(opt);
+ rv[0] = (m ? m[1] : opt);
+ rv[1] = m ? (f._rxInt.test(m[2]) ? +m[2] : m[2]) : true;
+ };
+ }
+ const rc = {}, ov = [0,0];
+ let i = 0;
+ while((k = S.sqlite3_compileoption_get(i++))){
+ f._opt(k,ov);
+ rc[ov[0]] = ov[1];
+ }
+ return rc;
+ }
+ else if(Array.isArray(optName)){
+ const rc = {};
+ optName.forEach((v)=>{
+ rc[v] = S.sqlite3_compileoption_used(v);
+ });
+ return rc;
+ }
+ else if('object' === typeof optName){
+ Object.keys(optName).forEach((k)=> {
+ optName[k] = S.sqlite3_compileoption_used(k);
+ });
+ return optName;
+ }
+ return (
+ 'string'===typeof optName
+ ) ? !!S.sqlite3_compileoption_used(optName) : false;
+ }
+ };
+
+ namespace.sqlite3 = sqlite3;
+ namespace.SQLite3 = SQLite3;
})(self/*worker or window*/);
A basic test script for sqlite3-api.js.
*/
-const setupAPI = function(S/*sqlite3 module*/){
- /* memory for use in some pointer-passing routines */
- const pPtrArg = stackAlloc(4);
- const dummyArg = {/*for restricting Stmt constructor to internal use*/};
- const toss = function(){
- throw new Error(Array.prototype.join.apply(arguments, ' '));
- };
-
- /**
- The DB class wraps a sqlite3 db handle.
- */
- const DB = function(name/*TODO: openMode flags*/){
- if(!name) name = ':memory:';
- else if('string'!==typeof name){
- toss("TODO: support blob image of db here.");
- }
- this.checkRc(S.sqlite3_open(name, pPtrArg));
- this.pDb = getValue(pPtrArg, "i32");
- this.filename = name;
- this._statements = {/*array of open Stmt _pointers_*/};
- };
-
- /**
- This class wraps sqlite3_stmt. Calling this constructor
- directly will trigger an exception. Use DB.prepare() to create
- new instances.
- */
- const Stmt = function(){
- if(dummyArg!=arguments[2]){
- 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._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.");
- return db;
- };
-
- DB.prototype = {
- /**
- Expects to be given an sqlite3 API result code. If it is
- falsy, this function returns this object, else it throws an
- exception with an error message from sqlite3_errmsg(),
- using this object's db handle.
- */
- checkRc: function(sqliteResultCode){
- if(!sqliteResultCode) return this;
- toss(S.sqlite3_errmsg(this.pDb) || "Unknown db error.");
- },
- /**
- Finalizes all open statements and closes this database
- connection. This is a no-op if the db has already been
- closed.
- */
- close: function(){
- if(this.pDb){
- let s;
- while(undefined!==(s = this._statements.pop())){
- if(s.pStmt) s.finalize();
- }
- S.sqlite3_close_v2(this.pDb);
- delete this.pDb;
- }
- },
- /**
- Similar to this.filename but will return NULL for
- special names like ":memory:". Not of much use until
- we have filesystem support. Throws if the DB has
- been closed. If passed an argument it then it will return
- the filename of the ATTACHEd db with that name, else it assumes
- a name of `main`.
- */
- fileName: function(dbName){
- return S.sqlite3_db_filename(affirmDbOpen(this).pDb, dbName||"main");
- },
-
- /**
- Compiles the given SQL and returns a prepared Stmt. This is
- the only way to create new Stmt objects. Throws on error.
- */
- prepare: function(sql){
- affirmDbOpen(this);
- setValue(pPtrArg,0,"i32");
- 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, dummyArg);
- this._statements[pStmt] = stmt;
- return stmt;
- }
- };
-
- /**
- Internal-use enum for mapping JS types to DB-bindable types.
- These do not (and need not) line up with the SQLITE_type
- values. All values in this enum must be truthy and distinct
- but they need not be numbers.
- */
- const BindTypes = {
- null: 1,
- number: 2,
- string: 3,
- boolean: 4,
- blob: 5
- };
- BindTypes['undefined'] == BindTypes.null;
-
- /** Returns an opaque truthy value from the BindTypes
- enum if v's type is a valid bindable type, else
- 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;
- }
-
- /**
- If isSupportedBindType(v) returns a truthy value, this
- function returns that value, else it throws.
- */
- const affirmSupportedBindType = function(v){
- const t = isSupportedBindType(v);
- if(t) return t;
- toss("Unsupport bind() argument type.");
- };
-
- /**
- If key is a number and within range of stmt's bound parameter
- count, key is returned.
-
- If key is not a number then it is checked against named
- parameters. If a match is found, its index is returned.
-
- Else it throws.
- */
- const indexOfParam = function(stmt,key){
- const n = ('number'===typeof 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);
- }
- else if(n>=stmt.columnCount) toss("Bind index",key,"is out of range.");
- return n;
- };
-
- /**
- 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){
- affirmSupportedBindType(val);
- ndx = indexOfParam(stmt,ndx);
- let rc = 0;
- switch(bindType){
- case BindType.null:
- 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);
- break;
- }
- case BindType.number: {
- const m = ((val === (val|0))
- ? (val>0xefffffff
- ? S.sqlite3_bind_int64
- : S.sqlite3_bind_int)
- : S.sqlite3_bind_double);
- rc = m(stmt.pStmt, ndx, val);
- break;
- }
- case BindType.boolean:
- rc = S.sqlite3_bind_int(stmt.pStmt, ndx, val ? 1 : 0);
- break;
- case BindType.blob:
- default: toss("Unsupported bind() argument type.");
- }
- if(rc) stmt.db.checkRc(rc);
- return stmt;
- };
-
- /** Throws if the given Stmt has been finalized, else
- it is returned. */
- const affirmStmtOpen = function(stmt){
- if(!stmt.pStmt) toss("Stmt has been closed.");
- return stmt;
- };
-
- /** Frees any memory explicitly allocated for the given
- Stmt object. Returns stmt. */
- const freeBindMemory = function(stmt){
- let m;
- while(undefined !== (m = stmt._allocs.pop())){
- _free(m);
- }
- return stmt;
- };
-
- Stmt.prototype = {
- /**
- "Finalizes" this statement. This is a no-op if the
- statement has already been finalizes. Returns
- undefined. Most methods in this class will throw if called
- after this is.
- */
- finalize: function(){
- if(this.pStmt){
- freeBindMemory(this);
- S.sqlite3_finalize(this.pStmt);
- delete this.pStmt;
- delete this.db;
- }
- },
- /** Clears all bound values. Returns this object.
- Throws if this statement has been finalized. */
- clearBindings: function(){
- freeBindMemory(affirmStmtOpen(this));
- S.sqlite3_clear_bindings(this.pStmt);
- return this;
- },
- /**
- Resets this statement so that it may be step()ed again
- from the beginning. Returns this object. Throws if this
- statement has been finalized.
-
- If passed a truthy argument then this.clearBindings() is
- also called, otherwise any existing bindings, along with
- any memory allocated for them, are retained.
- */
- reset: function(alsoClearBinds){
- if(alsoClearBinds) this.clearBindings();
- S.sqlite3_reset(affirmStmtOpen(this).pStmt);
- return this;
- },
- /**
- Binds one or more values to its bindable parameters. It
- accepts 1 or 2 arguments:
-
- If passed a single argument, it must be either an array, an
- object, or a value of a bindable type (see below).
-
- If passed 2 arguments, the first one is the 1-based bind
- index or bindable parameter name and the second one must be
- a value of a bindable type.
-
- Bindable value types:
-
- - null or undefined is bound as NULL.
-
- - Numbers are bound as either doubles or integers: int64 if
- they are larger than 0xEFFFFFFF, else int32. Booleans are
- bound as integer 0 or 1. Note that doubles with no
- fractional part are bound as integers. It is not expected
- that that distinction is significant for the majority of
- clients due to sqlite3's data typing model. This API does
- not currently support the BigInt type.
-
- - Strings are bound as strings (use bindAsBlob() to force
- blob binding).
-
- - buffers (blobs) are currently TODO but will be 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
- (because arrays are 0-based but binding is 1-based).
-
- If passed an object, each object key is treated as a
- bindable parameter name. The object keys _must_ match any
- bindable parameter names, including any `$`, `@`, or `:`
- prefix. Because `$` is a legal identifier chararacter in
- JavaScript, that is the suggested prefix for bindable
- parameters.
-
- It returns this object on success and throws on
- error. Errors include:
-
- - Any bind index is out of range or a named bind parameter
- does not match.
-
- - Any value to bind is of an unsupported type.
-
- - Passed no arguments or more than two.
-
- - The statement has been finalized.
- */
- bind: function(/*[ndx,] value*/){
- 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);
- }
- else if(Array.isArray(arg)){
- /* bind each entry by index */
- if(1!==arguments.length){
- toss("When binding an array, an index argument is not permitted.");
- }
- arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
- return this;
- }
- else if('object'===typeof arg/*null was checked above*/){
- /* bind by name */
- if(1!==arguments.length){
- toss("When binding an object, an index argument is not permitted.");
- }
- Object.keys(arg)
- .forEach(k=>bindOne(this, k,
- affirmSupportedBindType(arg[k]),
- arg[k]));
- return this;
- }else{
- return bindOne(this, ndx,
- affirmSupportedBindType(arg), arg);
- }
- toss("Should not reach this point.");
- },
- /**
- Special case of bind() which binds the given value
- 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).
-
- If passed a single argument, a bind index of 1 is assumed.
- */
- bindAsBlob: function(ndx,arg){
- affirmStmtOpen(this);
- if(1===arguments.length){
- ndx = 1;
- arg = arguments[0];
- }
- const t = affirmSupportedBindType(arg);
- if(BindTypes.string !== t && BindTypes.blob !== t
- && BindTypes.null !== t){
- toss("Invalid value type for bindAsBlob()");
- }
- return bindOne(this, ndx, BindType.blob, arg);
- }
- };
-
- const SQLite3 = {
- version: {
- lib: S.sqlite3_libversion(),
- ooApi: "0.0.1"
- },
- DB
- };
- return SQLite3;
-};
-
-const mainTest1 = function(S/*sqlite3 module*/){
+const mainTest1 = function(namespace){
+ const S = namespace.sqlite3;
+ const oo = namespace.SQLite3;
+ const T = self.SqliteTester;
console.log("Loaded module:",S.sqlite3_libversion(),
S.sqlite3_sourceid());
- const oo = setupAPI(S);
-
const db = new oo.DB();
- console.log("DB:",db.filename);
+ const log = console.log.bind(console);
+ T.assert(db.pDb);
+ log("DB:",db.filename);
+ log("Build options:",oo.compileOptionUsed());
+
+ let st = db.prepare("select 1");
+ T.assert(st.pStmt);
+ log("statement =",st);
+ T.assert(st === db._statements[st.pStmt])
+ .assert(1===st.columnCount)
+ .assert(0===st.parameterCount)
+ .mustThrow(()=>st.bind(1,null));
+
+ let pId = st.pStmt;
+ st.finalize();
+ T.assert(!st.pStmt)
+ .assert(!db._statements[pId]);
+ log("Test count:",T.counter);
};
self/*window or worker*/.Module.onRuntimeInitialized = function(){