wasm: add a small demo/presentation app for JS OO API #1 and make a few minor additions to that API.

FossilOrigin-Name: d6d79b661a1c6137d4693393e02416da4858d58dc84d144081a48d523655b483
This commit is contained in:
stephan 2022-08-16 16:36:19 +00:00
parent ba851ae72e
commit e0c582850e
6 changed files with 345 additions and 55 deletions

View File

@ -80,35 +80,57 @@
not resolve to real filenames, but "" uses an on-storage
temporary database and requires that the VFS support that.
The db is currently opened with a fixed set of flags:
(SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE |
SQLITE_OPEN_EXRESCODE). This API will change in the future
permit the caller to provide those flags via an additional
argument.
The second argument specifies the open/create mode for the
database. It must be string containing a sequence of letters (in
any order, but case sensitive) specifying the mode:
- "c" => create if it does not exist, else fail if it does not
exist. Implies the "w" flag.
- "w" => write. Implies "r": a db cannot be write-only.
- "r" => read-only if neither "w" nor "c" are provided, else it
is ignored.
If "w" is not provided, the db is implicitly read-only, noting that
"rc" is meaningless
Any other letters are currently ignored. The default is
"c". These modes are ignored for the special ":memory:" and ""
names.
The final argument is currently unimplemented but will eventually
be used to specify an optional sqlite3 VFS implementation name,
as for the final argument to sqlite3_open_v2().
For purposes of passing a DB instance to C-style sqlite3
functions, its read-only `pointer` property holds its `sqlite3*`
pointer value. That property can also be used to check whether
this DB instance is still open.
functions, the DB object's read-only `pointer` property holds its
`sqlite3*` pointer value. That property can also be used to check
whether this DB instance is still open.
*/
const DB = function ctor(fn=':memory:'){
const DB = function ctor(fn=':memory:', flags='c', vtab="not yet implemented"){
if('string'!==typeof fn){
toss3("Invalid filename for DB constructor.");
}
let ptr, oflags = 0;
if( flags.indexOf('c')>=0 ){
oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
}
if( flags.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
oflags |= capi.SQLITE_OPEN_EXRESCODE;
const stack = capi.wasm.scopedAllocPush();
let ptr;
try {
const ppDb = capi.wasm.scopedAllocPtr() /* output (sqlite3**) arg */;
const rc = capi.sqlite3_open_v2(fn, ppDb, capi.SQLITE_OPEN_READWRITE
| capi.SQLITE_OPEN_CREATE
| capi.SQLITE_OPEN_EXRESCODE, null);
const rc = capi.sqlite3_open_v2(fn, ppDb, oflags, null);
ptr = capi.wasm.getMemValue(ppDb, '*');
ctor.checkRc(ptr, rc);
}catch( e ){
if( ptr ) capi.sqlite3_close_v2(ptr);
throw e;
}finally{
capi.wasm.scopedAllocPop(stack);
}
finally{capi.wasm.scopedAllocPop(stack);}
this.filename = fn;
__ptrMap.set(this, ptr);
__stmtMap.set(this, Object.create(null));
@ -315,11 +337,10 @@
file. If the name is "" or ":memory:", it resolves to false.
Note that it is not aware of the peculiarities of URI-style
names and a URI-style name for a ":memory:" db will fool it.
Returns false if this db is closed.
*/
hasFilename: function(){
const fn = this.filename;
if(!fn || ':memory'===fn) return false;
return true;
return this.filename && ':memory'!==this.filename;
},
/**
Returns the name of the given 0-based db number, as documented
@ -451,14 +472,14 @@
statement in the SQL which has any bindable
parameters. (Empty statements are skipped entirely.)
- .callback = a function which gets called for each row of
the FIRST statement in the SQL which has result
_columns_, but only if that statement has any result
_rows_. The second argument passed to the callback is
always the current Stmt object (so that the caller may
collect column names, or similar). The first argument
passed to the callback defaults to the current Stmt
object but may be changed with ...
- .callback = a function which gets called for each row of the
FIRST statement in the SQL which has result _columns_, but only
if that statement has any result _rows_. The callback's "this"
is the options object. The second argument passed to the
callback is always the current Stmt object (so that the caller
may collect column names, or similar). The first argument
passed to the callback defaults to the current Stmt object but
may be changed with ...
- .rowMode = either a string describing what type of argument
should be passed as the first argument to the callback or an
@ -479,12 +500,13 @@
the FIRST first statement which has result _columns_ is
appended to the array in the format specified for the `rowMode`
option, with the exception that the only legal values for
`rowMode` in this case are 'array' or 'object', neither of
which is the default. It is legal to use both `resultRows` and
`callback`, but `resultRows` is likely much simpler to use for
small data sets and can be used over a WebWorker-style message
interface. execMulti() throws if `resultRows` is set and
`rowMode` is 'stmt' (which is the default!).
`rowMode` in this case are 'array', 'object', or an integer,
none of which are the default for `rowMode`. It is legal to use
both `resultRows` and `callback`, but `resultRows` is likely
much simpler to use for small data sets and can be used over a
WebWorker-style message interface. execMulti() throws if
`resultRows` is set and `rowMode` is 'stmt' (which is the
default!).
- saveSql = an optional array. If set, the SQL of each
executed statement is appended to this array before the
@ -1081,6 +1103,7 @@
delete __stmtMap.get(this.db)[this.pointer];
capi.sqlite3_finalize(this.pointer);
__ptrMap.delete(this);
delete this._mayGet;
delete this.columnCount;
delete this.parameterCount;
delete this.db;
@ -1268,6 +1291,27 @@
DB.checkRc(this.db.pointer, rc);
}
},
/**
Functions exactly like step() except that...
1) On success, it calls this.reset() and returns this object.
2) On error, it throws and does not call reset().
This is intended to simplify constructs like:
```
for(...) {
stmt.bind(...).stepReset();
}
```
Note that the reset() call makes it illegal to call this.get()
after the step.
*/
stepReset: function(){
this.step();
return this.reset();
},
/**
Functions like step() except that
it finalizes this statement immediately after stepping unless
@ -1286,17 +1330,9 @@
```
*/
stepFinalize: function(){
affirmUnlocked(this, 'step()');
const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer);
switch(rc){
case capi.SQLITE_DONE: this.finalize(); return false;
case capi.SQLITE_ROW: this.finalize(); return true;
default:
const rc = this.step();
this.finalize();
console.warn("sqlite3_step() rc=",rc,"SQL =",
capi.sqlite3_sql(this.pointer));
DB.checkRc(this.db.pointer, rc);
}
return rc;
},
/**
Fetches the value from the given 0-based column index of
@ -1400,7 +1436,7 @@
default: toss3("Don't know how to translate",
"type of result column #"+ndx+".");
}
abort("Not reached.");
toss3("Not reached.");
},
/** Equivalent to get(ndx) but coerces the result to an
integer. */

34
ext/wasm/demo-oo1.html Normal file
View File

@ -0,0 +1,34 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
<link rel="stylesheet" href="common/emscripten.css"/>
<link rel="stylesheet" href="common/testing.css"/>
<title>sqlite3-api OO #1 Demo</title>
</head>
<body>
<header id='titlebar'><span>sqlite3-api OO #1 Demo</span></header>
<!-- emscripten bits -->
<figure id="module-spinner">
<div class="spinner"></div>
<div class='center'><strong>Initializing app...</strong></div>
<div class='center'>
On a slow internet connection this may take a moment. If this
message displays for "a long time", intialization may have
failed and the JavaScript console may contain clues as to why.
</div>
</figure>
<div class="emscripten" id="module-status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="module-progress" hidden='1'></progress>
</div><!-- /emscripten bits -->
<div>Most stuff on this page happens in the dev console.</div>
<hr>
<div id='test-output'></div>
<script src="sqlite3.js"></script>
<script src="common/SqliteTestUtil.js"></script>
<script src="demo-oo1.js"></script>
</body>
</html>

218
ext/wasm/demo-oo1.js Normal file
View File

@ -0,0 +1,218 @@
/*
2022-08-16
The author disclaims copyright to this source code. In place of a
legal notice, here is a blessing:
* May you do good and not evil.
* May you find forgiveness for yourself and forgive others.
* May you share freely, never taking more than you give.
***********************************************************************
A basic demonstration of the SQLite3 OO API #1, shorn of assertions
and the like to improve readability.
*/
'use strict';
(function(){
const T = self.SqliteTestUtil;
const toss = function(...args){throw new Error(args.join(' '))};
const debug = console.debug.bind(console),
log = console.log.bind(console),
warn = console.warn.bind(console),
error = console.error.bind(console);
const demo1 = function(sqlite3,EmModule){
const capi = sqlite3.capi,
oo = sqlite3.oo1,
wasm = capi.wasm;
// If we have persistent storage, maybe init and mount it:
const dbDir = true
? "" // this demo works better without persistent storage.
: capi.sqlite3_web_persistent_dir();
// ^^^ returns name of persistent mount point or "" if we have none
const db = new oo.DB(dbDir+"/mydb.sqlite3");
/**
Never(!) rely on garbage collection to clean up DBs and
(especially) statements. Always wrap their lifetimes in
try/finally construct...
*/
try {
log("Create a table...");
db.exec("CREATE TABLE IF NOT EXISTS t(a,b)");
//Equivalent:
db.exec({
sql:"CREATE TABLE IF NOT EXISTS t(a,b)"
// ... numerous other options ...
});
// SQL can be either a string or a byte array
log("Insert some data using exec()...");
let i;
for( i = 1; i <= 5; ++i ){
db.exec({
sql: "insert into t(a,b) values (?,?)",
// bind by parameter index...
bind: [i, i*2]
});
db.exec({
sql: "insert into t(a,b) values ($a,$b)",
// bind by parameter name...
bind: {$a: i * 3, $b: i * 4}
});
}
log("Insert using a prepared statement...");
let q = db.prepare("insert into t(a,b) values(?,?)");
try {
for( i = 100; i < 103; ++i ){
q.bind( [i, i*2] ).step();
q.reset();
}
// Equivalent...
for( i = 103; i <= 105; ++i ){
q.bind(1, i).bind(2, i*2).stepReset();
}
}finally{
q.finalize();
}
log("Query data with exec() using rowMode 'array'...");
db.exec({
sql: "select a from t order by a limit 3",
rowMode: 'array', // 'array', 'object', or 'stmt' (default)
callback: function(row){
log("row ",++this.counter,"=",row);
}.bind({counter: 0})
});
log("Query data with exec() using rowMode 'object'...");
db.exec({
sql: "select a as aa, b as bb from t order by aa limit 3",
rowMode: 'object',
callback: function(row){
log("row ",++this.counter,"=",row);
}.bind({counter: 0})
});
log("Query data with exec() using rowMode 'stmt'...");
db.exec({
sql: "select a from t order by a limit 3",
rowMode: 'stmt', // stmt === the default
callback: function(row){
log("row ",++this.counter,"get(0) =",row.get(0));
}.bind({counter: 0})
});
log("Query data with exec() using rowMode INTEGER (result column index)...");
db.exec({
sql: "select a, b from t order by a limit 3",
rowMode: 1, // === result column 1
callback: function(row){
log("row ",++this.counter,"b =",row);
}.bind({counter: 0})
});
log("Query data with exec() without a callback...");
let resultRows = [];
db.exec({
sql: "select a, b from t order by a limit 3",
rowMode: 'object',
resultRows: resultRows
});
log("Result rows:",resultRows);
log("Create a scalar UDF...");
db.createFunction({
name: 'twice',
callback: function(arg){ // note the call arg count
return arg + arg;
}
});
log("Run scalar UDF and collect result column names...");
let columnNames = [];
db.exec({
sql: "select a, twice(a), twice(''||a) from t order by a desc limit 3",
columnNames: columnNames,
callback: function(row){
log("a =",row.get(0), "twice(a) =", row.get(1), "twice(''||a) =",row.get(2));
}
});
log("Result column names:",columnNames);
/**
Main differences between exec() and execMulti():
- execMulti() traverses all statements in the input SQL
- exec() supports a couple options not supported by execMulti(),
and vice versa.
- execMulti() result callback/array only activates for the
first statement which has result columns. It is arguable
whether it should support a callback at all, and that
feature may be removed.
- execMulti() column-bind data only activates for the first statement
with bindable columns. This feature is arguable and may be removed.
*/
if(0){
warn("UDF will throw because of incorrect arg count...");
db.exec("select twice(1,2,3)");
}
try {
db.callInTransaction( function(D) {
D.exec("delete from t");
log("In transaction: count(*) from t =",db.selectValue("select count(*) from t"));
throw new sqlite3.SQLite3Error("Demonstrating callInTransaction() rollback");
});
}catch(e){
log("Got expected exception:",e.message);
log("count(*) from t =",db.selectValue("select count(*) from t"));
}
}finally{
db.close();
}
/**
Misc. DB features:
- get change count (total or statement-local, 32- or 64-bit)
- get its file name
- selectValue() takes SQL and returns first column of first row.
Misc. Stmt features:
- Various forms of bind()
- clearBindings()
- reset()
- Various forms of step()
- Variants of get() for explicit type treatment/conversion,
e.g. getInt(), getFloat(), getBlob(), getJSON()
- getColumnName(ndx), getColumnNames()
- getParamIndex(name)
*/
}/*demo1()*/;
const runDemos = function(Module){
//log("Module",Module);
const sqlite3 = Module.sqlite3,
capi = sqlite3.capi,
oo = sqlite3.oo1,
wasm = capi.wasm;
log("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
try {
[ demo1 ].forEach((f)=>f(sqlite3, Module))
}catch(e){
error("Exception:",e.message);
throw e;
}
};
sqlite3InitModule(self.sqlite3TestModule).then(runDemos);
})();

View File

@ -27,7 +27,7 @@
<div>Most stuff on this page happens in the dev console.</div>
<hr>
<div id='test-output'></div>
<script src="api/sqlite3.js"></script>
<script src="sqlite3.js"></script>
<script src="common/SqliteTestUtil.js"></script>
<script src="testing1.js"></script>
</body>

View File

@ -1,5 +1,5 @@
C wasm:\smove\sanother\sfile\sand\supdate\stesting1/testing2\sto\saccount\sfor\s[e38d00c2b82d].\sDisable\swasmfs\sby\sdefault\sas\sit\sbreaks\sthe\sworker-based\smodule\sloader\s(reason\sas\syet\sunknown).
D 2022-08-16T16:16:25.326
C wasm:\sadd\sa\ssmall\sdemo/presentation\sapp\sfor\sJS\sOO\sAPI\s#1\sand\smake\sa\sfew\sminor\sadditions\sto\sthat\sAPI.
D 2022-08-16T16:36:19.533
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -483,7 +483,7 @@ F ext/wasm/api/post-js-footer.js b64319261d920211b8700004d08b956a6c285f3b0bba814
F ext/wasm/api/post-js-header.js 0e853b78db83cb1c06b01663549e0e8b4f377f12f5a2d9a4a06cb776c003880b
F ext/wasm/api/sqlite3-api-cleanup.js 149fd63a0400cd1d69548887ffde2ed89c13283384a63c2e9fcfc695e38a9e11
F ext/wasm/api/sqlite3-api-glue.js 82c09f49c69984009ba5af2b628e67cc26c5dd203d383cd3091d40dab4e6514b
F ext/wasm/api/sqlite3-api-oo1.js a3469bbb217b9787ba9aa6216423ec55cf9457fecefb9698e433d0e1cc4cc918
F ext/wasm/api/sqlite3-api-oo1.js 1d63e7e453e38ff2ad0c5e8bf68345f6fc5fe99fbc4a893cc982b4c50d904ca0
F ext/wasm/api/sqlite3-api-opfs.js c93cdd14f81a26b3a64990515ee05c7e29827fbc8fba4e4c2fef3a37a984db89
F ext/wasm/api/sqlite3-api-prologue.js c0f335bf8b44071da0204b8fa95ce78fd737033b155e7bcfdaee6ae64600802f
F ext/wasm/api/sqlite3-api-worker.js 1124f404ecdf3c14d9f829425cef778cd683911a9883f0809a463c3c7773c9fd
@ -493,6 +493,8 @@ F ext/wasm/common/SqliteTestUtil.js e41a1406f18da9224523fad0c48885caf995b56956a5
F ext/wasm/common/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/wasm/common/testing.css 572cf1ffae0b6eb7ca63684d3392bf350217a07b90e7a896e4fa850700c989b0
F ext/wasm/common/whwasmutil.js 41b8e097e0a9cb07c24c0ede3c81b72470a63f4a4efb07f75586dc131569f5ae
F ext/wasm/demo-oo1.html 75646855b38405d82781246fd08c852a2b3bee05dd9f0fe10ab655a8cffb79aa
F ext/wasm/demo-oo1.js 0b1f85ee622b8f0ffe133ed88584bfc6b1ef1dcbe5b605278073e4694ebd0a2f
F ext/wasm/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/wasm/fiddle/fiddle-worker.js bccf46045be8824752876f3eec01c223be0616ccac184bffd0024cfe7a3262b8
F ext/wasm/fiddle/fiddle.html 550c5aafce40bd218de9bf26192749f69f9b10bc379423ecd2e162bcef885c08
@ -506,8 +508,8 @@ F ext/wasm/scratchpad-opfs-main.js a819ed26047c5539630cea59add6a5082ba04cdf82da2
F ext/wasm/scratchpad-opfs-worker.html 66c1d15d678f3bd306373d76b61c6c8aef988f61f4a8dd40185d452f9c6d2bf5
F ext/wasm/scratchpad-opfs-worker.js 3ec2868c669713145c76eb5877c64a1b20741f741817b87c907a154b676283a9
F ext/wasm/scratchpad-opfs-worker2.js de3ea77548243a638c8426b7e43cc1dbfc511013228ab98436eb102923ed6689
F ext/wasm/sqlite3-worker.js 1325ca8d40129a82531902a3a077b795db2eeaee81746e5a0c811a04b415fa7f w ext/wasm/api/sqlite3-worker.js
F ext/wasm/testing1.html 0bf3ff224628c1f1e3ed22a2dc1837c6c73722ad8c0ad9c8e6fb9e6047667231
F ext/wasm/sqlite3-worker.js 1325ca8d40129a82531902a3a077b795db2eeaee81746e5a0c811a04b415fa7f
F ext/wasm/testing1.html 528001c7e32ee567abc195aa071fd9820cc3c8ffc9c8a39a75e680db05f0c409
F ext/wasm/testing1.js a25069e20d5f8dc548cc98bcf7002cec812084421a1f7f70ffae2c706d1167b2
F ext/wasm/testing2.html 73e5048e666fd6fb28b6e635677a9810e1e139c599ddcf28d687c982134b92b8
F ext/wasm/testing2.js dbb825174878716fab42c340962c0c1b32bfbe26dd16c7b082d30d35f510466c
@ -2004,8 +2006,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 41762f9518bb51b8b23ae6507628d6d3256044e1f2aca6e7251dc57722062c42
R 8ceb5e9180bc147ed0af56485cecdca6
P 6dad5e0573ee866657ee10b43e55b86fc9caac7a66c13bdbd35c3625a4783f14
R 21ffe0bceeaf14b3e3d3b64b375f9eb8
U stephan
Z 7a0a645037fbcb33d5a75b781a0d284d
Z cfe198e466da02226e0ffe831d5c4056
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
6dad5e0573ee866657ee10b43e55b86fc9caac7a66c13bdbd35c3625a4783f14
d6d79b661a1c6137d4693393e02416da4858d58dc84d144081a48d523655b483