1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

WASM: added bindings for sqlite3_compileoption_get/used(), moved OO #1 into sqlite3-api.js since it can only be used from the same thread as that API and separating them complicates client-side use. Started adding test utilities and tests for the OO1 API.

FossilOrigin-Name: f3bc0328c87cac7d50513b0f13576d8fe7b411396f19c08fbe7e7c657b33cfbf
This commit is contained in:
stephan
2022-05-22 16:25:43 +00:00
parent d60b7275c3
commit 40b5b19a0f
6 changed files with 575 additions and 399 deletions

View File

@ -3,6 +3,7 @@ _sqlite3_bind_double
_sqlite3_bind_int
_sqlite3_bind_int64
_sqlite3_bind_null
_sqlite3_bind_parameter_count
_sqlite3_bind_parameter_index
_sqlite3_bind_text
_sqlite3_changes
@ -16,6 +17,8 @@ _sqlite3_column_double
_sqlite3_column_name
_sqlite3_column_text
_sqlite3_column_type
_sqlite3_compileoption_get
_sqlite3_compileoption_used
_sqlite3_create_function_v2
_sqlite3_data_count
_sqlite3_db_filename

View File

@ -10,21 +10,31 @@
***********************************************************************
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
@ -74,8 +84,23 @@
/* 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,
@ -108,6 +133,7 @@
["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"]],
@ -121,6 +147,8 @@
["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"]],
@ -162,5 +190,447 @@
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*/);

View File

@ -28,7 +28,6 @@
const statusElement = E('#module-status');
const progressElement = E('#module-progress');
const spinnerElement = E('#module-spinner');
self.Module = {
/* ^^^ cannot declare that const because fiddle-module.js
(auto-generated) includes a decl for it and runs in this scope. */
@ -71,9 +70,10 @@
: 'All downloads complete.');
},
/* Loads sqlite3-api.js and calls the given callback (if
provided), passing it the sqlite3 module. Whether this is
synchronous or async depends on whether it's run in the
main thread or a worker.*/
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 or a
worker.*/
loadSqliteAPI: function(callback){
const theScript = 'sqlite3-api.js';
if(self.importScripts){/*worker*/
@ -88,9 +88,62 @@
script.async = true;
script.src = theScript;
}).then(() => {
if(callback) callback(self.sqlite3);
if(callback) callback({sqlite3:self.sqlite3,
SQLite3:self.SQLite3});
});
}
}
};
const _expr = function(expr){
return (expr instanceof Function) ? expr() : !!expr;
};
/**
Helpers for writing sqlite3-specific tests.
*/
self.SqliteTester = {
/** Running total of the number of tests run via
this API. */
counter: 0,
/** abort() if expr is false. If expr is a function, it
is called and its result is evaluated.
*/
assert: function(expr, msg){
++this.counter;
if(!_expr(expr)) abort(msg || "Assertion failed.");
return this;
},
/** Identical to assert() but throws instead of calling
abort(). */
affirm: function(expr, msg){
++this.counter;
if(!_expr(expr)) throw new Error(msg || "Affirmation failed.");
return this;
},
/** Calls f() and squelches any exception it throws. If it
does not throw, this function throws. */
mustThrow: function(f, msg){
++this.counter;
let err;
try{ f(); } catch(e){err=e;}
if(!err) throw new Error(msg || "Expected exception.");
return this;
},
/** Throws if expr is truthy or expr is a function and expr()
returns truthy. */
throwIf: function(expr, msg){
++this.counter;
if(_expr(expr)) throw new Error(msg || "throwIf() failed");
return this;
},
/** Throws if expr is falsy or expr is a function and expr()
returns falsy. */
throwUnless: function(expr, msg){
++this.counter;
if(!_expr(expr)) throw new Error(msg || "throwUnless() failed");
return this;
}
};
})(self/*window or worker*/);

View File

@ -12,382 +12,32 @@
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(){

View File

@ -1,5 +1,5 @@
C WASM\sOO\swrapper\s#1:\sprepare()\sand\sbind()\sAPIs\sare\sin\splace\sbut\sare\suntested,\spending\sfetch/get\sAPIs.
D 2022-05-22T14:07:44.577
C WASM:\sadded\sbindings\sfor\ssqlite3_compileoption_get/used(),\smoved\sOO\s#1\sinto\ssqlite3-api.js\ssince\sit\scan\sonly\sbe\sused\sfrom\sthe\ssame\sthread\sas\sthat\sAPI\sand\sseparating\sthem\scomplicates\sclient-side\suse.\sStarted\sadding\stest\sutilities\sand\stests\sfor\sthe\sOO1\sAPI.
D 2022-05-22T16:25:43.076
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -56,7 +56,7 @@ F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0
F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 a3a2862941270ae5e2633d21cbf44979901c4b75efa42a452c15ef879b47ad2b
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 00553766051a038b1acd3992d04e50540d1284c3ea78bd11daa521383e57d653
F ext/fiddle/EXPORTED_RUNTIME_METHODS 91d5dcb0168ee056fa1a340cb8ab3c23d922622f8dad39d28919dd8af2b3ade0
F ext/fiddle/Makefile 9277c73e208b9c8093659256c9f07409c877e366480c7c22ec545ee345451d95
F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
@ -64,10 +64,10 @@ F ext/fiddle/fiddle-worker.js c22557b641b47fa1473d3465a4e69fe06b8b09b924955805a4
F ext/fiddle/fiddle.html 657c6c3f860c322fba3c69fa4f7a1209e2d2ce44b4bc65a3e154e3a97c047a7c
F ext/fiddle/fiddle.js f9c79164428e96a5909532f18a8bc8f8c8ec4f738bfc09ad3d2a532c2400f9f0
F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
F ext/fiddle/sqlite3-api.js 5f256e3dc78ed0ac4f8556c0c77860812f9baf542b7a73b19b2abb72a6e13146
F ext/fiddle/testing-common.js 37b014758db7e5e74278e37dc712ced2fc9b40d0617f5ed0b8b64a6bd9c0a45d
F ext/fiddle/sqlite3-api.js ab7e7ded7b3079ee7de43e8290f1942e757d90ebb47ae4654cfe03c980cd0cad
F ext/fiddle/testing-common.js 2b2826a1e7c8ca3e610dfa4255ff1077438b6570e08096cc139c226811e60dbb
F ext/fiddle/testing1.html 68cec1b1c8646a071717e5979f22e4268e6d36d96ba13ad68333351acdbcf1d1
F ext/fiddle/testing1.js 2e9aa40a17c97ab8e90a8ba942725ebf590ae5db3f0329583d7431e4524f5b11
F ext/fiddle/testing1.js 6a314a10efc954bcd854af89d53ab768f48a42d3dcb80773b297f4ba0ac0236d
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@ -1967,8 +1967,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P dea098b64eb95c395b346ebcae687afe42b7d21df48833527808c02226300a66
R d6777acddc9dd48a02f29eb36eccd673
P 84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39
R d3343607f5b0d8f2cee2f7485f266550
U stephan
Z ec1032561dde0c5db030c66f88c029fe
Z 4ea03d4418e5522f96fc7a3cf30bf26a
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39
f3bc0328c87cac7d50513b0f13576d8fe7b411396f19c08fbe7e7c657b33cfbf