WASM OO wrapper #1: prepare() and bind() APIs are in place but are untested, pending fetch/get APIs.

FossilOrigin-Name: 84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39
This commit is contained in:
stephan
2022-05-22 14:07:44 +00:00
parent 166542cc98
commit d60b7275c3
5 changed files with 436 additions and 58 deletions
+4
View File
@@ -1,6 +1,8 @@
_sqlite3_bind_blob
_sqlite3_bind_double
_sqlite3_bind_int
_sqlite3_bind_int64
_sqlite3_bind_null
_sqlite3_bind_parameter_index
_sqlite3_bind_text
_sqlite3_changes
@@ -16,9 +18,11 @@ _sqlite3_column_text
_sqlite3_column_type
_sqlite3_create_function_v2
_sqlite3_data_count
_sqlite3_db_filename
_sqlite3_errmsg
_sqlite3_exec
_sqlite3_finalize
_sqlite3_interrupt
_sqlite3_libversion
_sqlite3_open
_sqlite3_prepare_v2
+41 -40
View File
@@ -85,6 +85,7 @@
SQLITE_FLOAT: 2,
SQLITE_TEXT: 3,
SQLITE_BLOB: 4,
SQLITE_NULL: 5,
/* sqlite encodings, used for creating UDFs, noting that we
will only support UTF8. */
SQLITE_UTF8: 1
@@ -102,61 +103,61 @@
use for the JS-side binding. That's required when overloading
a binding for two different uses.
*/
["sqlite3_open", "number", ["string", "number"]],
["sqlite3_close_v2", "number", ["number"]],
["sqlite3_exec", "number",
["number", "string", "number", "number", "number"]],
["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"]],
["sqlite3_bind_null","void",["number"]],
["sqlite3_bind_parameter_index","number",["number", "string"]],
["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]],
["sqlite3_changes", "number", ["number"]],
["sqlite3_clear_bindings","number",["number"]],
["sqlite3_close_v2", "number", ["number"]],
["sqlite3_column_blob","number", ["number", "number"]],
["sqlite3_column_bytes","number",["number", "number"]],
["sqlite3_column_count", "number", ["number"]],
["sqlite3_column_count","number",["number"]],
["sqlite3_column_double","number",["number", "number"]],
["sqlite3_column_name","string",["number", "number"]],
["sqlite3_column_text","string",["number", "number"]],
["sqlite3_column_type","number",["number", "number"]],
["sqlite3_create_function_v2", "number",
["number", "string", "number", "number","number",
"number", "number", "number", "number"]],
["sqlite3_data_count", "number", ["number"]],
["sqlite3_db_filename", "string", ["number", "string"]],
["sqlite3_errmsg", "string", ["number"]],
["sqlite3_exec", "number", ["number", "string", "number", "number", "number"]],
["sqlite3_finalize", "number", ["number"]],
["sqlite3_interrupt", "void", ["number"]],
["sqlite3_libversion", "string", []],
["sqlite3_open", "number", ["string", "number"]],
["sqlite3_prepare_v2", "number", ["number", "string", "number", "number", "number"]],
["sqlite3_prepare_v2_sqlptr",
["sqlite3_prepare_v2_sqlptr", "sqlite3_prepare_v2",
/* Impl which requires that the 2nd argument be a pointer to
the SQL, instead of a string. This is used for cases where
we require a non-NULL value for the final argument. We may
or may not need this, depending on how our higher-level
API shapes up, but this code's spiritual guide (sql.js)
uses it we we'll include it. */
"sqlite3_prepare_v2",
"number", ["number", "number", "number", "number", "number"]],
["sqlite3_bind_text","number",["number", "number", "number", "number", "number"]],
["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_parameter_index","number",["number", "string"]],
["sqlite3_step", "number", ["number"]],
["sqlite3_errmsg", "string", ["number"]],
["sqlite3_column_count","number",["number"]],
["sqlite3_data_count", "number", ["number"]],
["sqlite3_column_count", "number", ["number"]],
["sqlite3_column_double","number",["number", "number"]],
["sqlite3_column_text","string",["number", "number"]],
["sqlite3_column_blob","number", ["number", "number"]],
["sqlite3_column_bytes","number",["number", "number"]],
["sqlite3_column_type","number",["number", "number"]],
["sqlite3_column_name","string",["number", "number"]],
["sqlite3_reset", "number", ["number"]],
["sqlite3_clear_bindings","number",["number"]],
["sqlite3_finalize", "number", ["number"]],
["sqlite3_create_function_v2", "number",
["number", "string", "number", "number",
"number", "number", "number", "number",
"number"]],
["sqlite3_value_type", "number", ["number"]],
["sqlite3_value_bytes","number",["number"]],
["sqlite3_value_text", "string", ["number"]],
["sqlite3_value_blob", "number", ["number"]],
["sqlite3_value_double","number",["number"]],
["sqlite3_result_blob",null,["number", "number", "number", "number"]],
["sqlite3_result_double",null,["number", "number"]],
["sqlite3_result_error",null,["number", "string", "number"]],
["sqlite3_result_int",null,["number", "number"]],
["sqlite3_result_null",null,["number"]],
["sqlite3_result_text",null,["number", "string", "number", "number"]],
["sqlite3_result_blob",null,["number", "number", "number", "number"]],
["sqlite3_result_int",null,["number", "number"]],
["sqlite3_result_error",null,["number", "string", "number"]],
["sqlite3_libversion", "string", []],
["sqlite3_sourceid", "string", []]
["sqlite3_sourceid", "string", []],
["sqlite3_step", "number", ["number"]],
["sqlite3_value_blob", "number", ["number"]],
["sqlite3_value_bytes","number",["number"]],
["sqlite3_value_double","number",["number"]],
["sqlite3_value_text", "string", ["number"]],
["sqlite3_value_type", "number", ["number"]]
//["sqlite3_sql", "string", ["number"]],
//["sqlite3_normalized_sql", "string", ["number"]]
].forEach(function(e){
const a = Array.prototype.slice.call(e);
].forEach(function(a){
const k = (4==a.length) ? a.shift() : a[0];
api[k] = cwrap.apply(this, a);
});
+381 -8
View File
@@ -12,12 +12,385 @@
A basic test script for sqlite3-api.js.
*/
(function(){
self.Module.onRuntimeInitialized = function(){
console.log("Loading sqlite3-api.js...");
self.Module.loadSqliteAPI(function(S){
console.log("Loaded module:",S.sqlite3_libversion(),
S.sqlite3_sourceid());
});
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, ' '));
};
})(self/*window or worker*/);
/**
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*/){
console.log("Loaded module:",S.sqlite3_libversion(),
S.sqlite3_sourceid());
const oo = setupAPI(S);
const db = new oo.DB();
console.log("DB:",db.filename);
};
self/*window or worker*/.Module.onRuntimeInitialized = function(){
console.log("Loading sqlite3-api.js...");
self.Module.loadSqliteAPI(mainTest1);
};
+9 -9
View File
@@ -1,5 +1,5 @@
C Build\srefactoring\sfor\sthe\sfiddle/wasm\sbits.\sSet\sup\swasm\sbinding\sof\sa\schunk\sof\sthe\score\sC\sAPI\sand\sadded\ssome\sinfastructure\sfor\screating\stest\spages\sfor\sit.
D 2022-05-22T00:27:19.296
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
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -55,8 +55,8 @@ F ext/expert/expert1.test 3c642a4e7bbb14f21ddab595436fb465a4733f47a0fe5b2855e1d5
F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0ff5d9cdfac204
F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f w ext/fiddle/EXPORTED_FUNCTIONS
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 4b06e6c3ce8c8389274079ffb6b441ffff1a55e32a448cf21ce1da45a16c8a01
F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f
F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 a3a2862941270ae5e2633d21cbf44979901c4b75efa42a452c15ef879b47ad2b
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 d3c6da99850e146e50dc42039ac027e5d9b08b9f24eb22b31d1982c49930ee7c
F ext/fiddle/sqlite3-api.js 5f256e3dc78ed0ac4f8556c0c77860812f9baf542b7a73b19b2abb72a6e13146
F ext/fiddle/testing-common.js 37b014758db7e5e74278e37dc712ced2fc9b40d0617f5ed0b8b64a6bd9c0a45d
F ext/fiddle/testing1.html 68cec1b1c8646a071717e5979f22e4268e6d36d96ba13ad68333351acdbcf1d1
F ext/fiddle/testing1.js 0fb900c768b06c2ec3922ab522f721a68b0756d200e3c66602461f45910bcd39
F ext/fiddle/testing1.js 2e9aa40a17c97ab8e90a8ba942725ebf590ae5db3f0329583d7431e4524f5b11
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 c7cfdd4c3682659352642461d3307bf8180703b121ec1802ba5881f8e1ef9809
R d89e623e1614875df0e3a579152a2be9
P dea098b64eb95c395b346ebcae687afe42b7d21df48833527808c02226300a66
R d6777acddc9dd48a02f29eb36eccd673
U stephan
Z fd89698a5fe01334529b2fb407d595a1
Z ec1032561dde0c5db030c66f88c029fe
# Remove this line to create a well-formed Fossil manifest.
+1 -1
View File
@@ -1 +1 @@
dea098b64eb95c395b346ebcae687afe42b7d21df48833527808c02226300a66
84c8f63a1c446331a3afe52b0c8bdfa6980f24aa4cf600f576877fef5e650c39