fiddle: initial work on loading a client-side db file. Works but requires some cleanup. Export is not yet implemented.

FossilOrigin-Name: 0fa8378c006fcf2311772d36cf2e3c2cd8e8648f671de89ee9832e2e1a06ef49
This commit is contained in:
stephan 2022-05-24 19:01:21 +00:00
parent 6af03b469e
commit de1e02ee52
7 changed files with 173 additions and 39 deletions

View File

@ -159,30 +159,73 @@ self.Module = {
}
};
const shellExec = function f(sql){
if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']);
if(Module._isDead){
wMsg('stderr', "shell module has exit()ed. Cannot run SQL.");
return;
}
wMsg('working','start');
try {
if(f._running) wMsg('stderr','Cannot run multiple commands concurrently.');
else{
f._running = true;
f._(sql);
const Sqlite3Shell = {
exec: function f(sql){
if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']);
if(Module._isDead){
wMsg('stderr', "shell module has exit()ed. Cannot run SQL.");
return;
}
} finally {
wMsg('working','end');
delete f._running;
wMsg('working','start');
try {
if(f._running) wMsg('stderr','Cannot run multiple commands concurrently.');
else{
f._running = true;
f._(sql);
}
} finally {
wMsg('working','end');
delete f._running;
}
},
/* Interrupt can't work: this Worker is tied up working, so won't get the
interrupt event which would be needed to perform the interrupt. */
interrupt: function f(){
if(!f._) f._ = Module.cwrap('fiddle_interrupt', null);
wMsg('stdout',"Requesting interrupt.");
f._();
}
};
self.onmessage = function(ev){
self.onmessage = function f(ev){
ev = ev.data;
if(!f.cache){
f.cache = {
prevFilename: null
};
}
//console.debug("worker: onmessage.data",ev);
switch(ev.type){
case 'shellExec': shellExec(ev.data); return;
case 'shellExec': Sqlite3Shell.exec(ev.data); return;
case 'interrupt': Sqlite3Shell.interrupt(); return;
case 'open': {
/* Expects: {
buffer: ArrayBuffer | Uint8Array,
filename: for logging/informational purposes only
} */
const opt = ev.data;
let buffer = opt.buffer;
if(buffer instanceof Uint8Array){
}else if(buffer instanceof ArrayBuffer){
buffer = new Uint8Array(buffer);
}else{
wMsg('stderr',"'open' expects {buffer:Uint8Array} containing an uploaded db.");
return;
}
if(f.cache.prevFilename){
FS.unlink(f.cache.prevFilename);
/* Noting that it might not actually be removed until
the current db handle closes it. */
f.cache.prevFilename = null;
}
const fn = "db-"+((Math.random() * 10000000) | 0)+
"-"+((Math.random() * 10000000) | 0)+".sqlite3";
FS.createDataFile("/", fn, buffer, true, true);
f.cache.prevFilename = fn;
Sqlite3Shell.exec(".open /"+fn);
wMsg('stdout',"Replaced DB with "+(opt.filename || fn)+".");
return;
}
};
console.warn("Unknown fiddle-worker message type:",ev);
};

View File

@ -76,6 +76,10 @@
fieldset > legend {
padding: 0 0.5em;
}
fieldset.options > div {
display: flex;
flex-wrap: wrap;
}
span.labeled-input {
padding: 0.25em;
margin: 0.25em 0.5em;
@ -108,7 +112,7 @@
}
#view-split {
display: flex;
flex-direction: column;
flex-direction: column-reverse;
}
</style>
</head>
@ -161,6 +165,10 @@
data-config='autoClearOutput'>
<label for='opt-cb-autoclear'>Auto-clear output</label>
</span>
<span class='labeled-input'>
<input type='file' id='load-db'/>
<label>Load DB</label>
</span>
</div>
</fieldset>
<div id='main-wrapper' class=''>
@ -185,6 +193,11 @@ SELECT * FROM t;</textarea>
placeholder="Shell output."></textarea>
<div class='button-bar'>
<button id='btn-clear-output'>Clear Output</button>
<button id='btn-interrupt' class='hidden' disabled>Interrupt</button>
<!-- interruption cannot work in the current configuration
because we cannot send an interrupt message when work
is currently underway. At that point the Worker is
tied up and will not receive the message. */
</div>
</div>
</div>

View File

@ -218,6 +218,8 @@
if(sql) SF.dbExec(sql);
},false);
const btnInterrupt = E("#btn-interrupt");
btnInterrupt.classList.add('hidden');
/** To be called immediately before work is sent to the
worker. Updates some UI elements. The 'working'/'end'
event will apply the inverse, undoing the bits this
@ -237,6 +239,7 @@
}
f._.pageTitle.innerText = "[working...] "+f._.pageTitleOrig;
btnShellExec.setAttribute('disabled','disabled');
btnInterrupt.removeAttribute('disabled','disabled');
};
/* Sends the given text to the db module to evaluate as if it
@ -258,6 +261,7 @@
preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
btnShellExec.innerText = preStartWork._.btnLabel;
btnShellExec.removeAttribute('disabled');
btnInterrupt.setAttribute('disabled','disabled');
return;
}
console.warn("Unhandled 'working' event:",ev.data);
@ -294,12 +298,47 @@
}, false);
});
/* For each button with data-cmd=X, map a click handler which
calls dbExec(X). */
calls SF.dbExec(X). */
const cmdClick = function(){SF.dbExec(this.dataset.cmd);};
EAll('button[data-cmd]').forEach(
e => e.addEventListener('click', cmdClick, false)
);
btnInterrupt.addEventListener('click',function(){
SF.wMsg('interrupt');
});
const fileSelector = E('#load-db');
fileSelector.addEventListener('change',function(){
const f = this.files[0];
const r = new FileReader();
const status = {loaded: 0, total: 0};
fileSelector.setAttribute('disabled','disabled');
r.addEventListener('loadstart', function(){
SF.echo("Loading",f.name,"...");
});
r.addEventListener('progress', function(ev){
SF.echo("Loading progress:",ev.loaded,"of",ev.total,"bytes.");
});
r.addEventListener('load', function(){
fileSelector.removeAttribute('disabled');
SF.echo("Loaded",f.name+". Opening db...");
SF.wMsg('open',{
filename: f.name,
buffer: this.result
});
});
r.addEventListener('error',function(){
fileSelector.removeAttribute('disabled');
SF.echo("Loading",f.name,"failed for unknown reason.");
});
r.addEventListener('abort',function(){
fileSelector.removeAttribute('disabled');
SF.echo("Cancelled loading of",f.name+".");
});
r.readAsArrayBuffer(f);
});
/**
Given a DOM element, this routine measures its "effective
height", which is the bounding top/bottom range of this element
@ -445,7 +484,7 @@ SELECT group_concat(rtrim(t),x'0a') as Mandelbrot FROM a;`}
taInput.value = '-- ' +
this.selectedOptions[0].innerText +
'\n' + this.value;
//dbExec(this.value);
SF.dbExec(this.value);
});
})()/* example queries */;

View File

@ -253,17 +253,39 @@
};
/**
The DB class wraps a sqlite3 db handle.
The DB class wraps a sqlite3 db handle. Its argument may either
be a db name or a Uint8Array containing a binary image of a
database. If the name is not provided or is an empty string,
":memory:" is used. A string name other than ":memory:" or ""
will currently fail to open, for lack of a filesystem to
load it from. If given a blob, a random name is generated.
Achtung: all arguments other than those specifying an
in-memory db are currently untested for lack of an app
to test them in.
*/
const DB = function(name/*TODO: openMode flags*/){
if(!name) name = ':memory:';
else if('string'!==typeof name){
const DB = function(name/*TODO? openMode flags*/){
let fn, buff;
if(name instanceof Uint8Array){
buff = name;
name = undefined;
fn = "db-"+((Math.random() * 10000000) | 0)+
"-"+((Math.random() * 10000000) | 0)+".sqlite3";
}else if(":memory:" === name || "" === name){
fn = name || ":memory:";
name = undefined;
}else if('string'!==typeof name){
toss("TODO: support blob image of db here.");
}else{
fn = name;
}
if(buff){
FS.createDataFile("/", fn, buff, true, true);
}
setValue(pPtrArg, 0, "i32");
this.checkRc(api.sqlite3_open(name, pPtrArg));
this.checkRc(api.sqlite3_open(fn, pPtrArg));
this._pDb = getValue(pPtrArg, "i32");
this.filename = name;
this.filename = fn;
this._statements = {/*map of open Stmt _pointers_ to Stmt*/};
this._udfs = {/*map of UDF names to wasm function _pointers_*/};
};

View File

@ -1,5 +1,5 @@
C When\san\sON\sclause\son\san\sINNER\sJOIN\sreferences\sa\stable\sto\sthe\sright\sof\nof\sthe\sjoin,\sjust\sconvert\sthe\sON\sclause\sto\san\sordinary\sWHERE\sclause\sterm,\nin\sorder\sto\sbe\scompatible\swith\solder\sversions\sof\sSQLite.\s\sSee\n[forum:/forumpost/687b0bf563a1d4f1|forum\sthread\s687b0bf563a1d4f1]\sfor\sdetails.
D 2022-05-24T16:05:41.137
C fiddle:\sinitial\swork\son\sloading\sa\sclient-side\sdb\sfile.\sWorks\sbut\srequires\ssome\scleanup.\sExport\sis\snot\syet\simplemented.
D 2022-05-24T19:01:21.099
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -61,11 +61,11 @@ F ext/fiddle/EXPORTED_RUNTIME_METHODS ff64aea52779b0d4a838268275fe02adf6f2fdf4d9
F ext/fiddle/Makefile 2608fe0c56fa8f9cdf17e28d2be6def550a2fe987db5f7fc06d0210bfc868258
F ext/fiddle/SqliteTestUtil.js e3094833660a6ddd40766b802901b5861b37f0b89c6c577ee0ce4c9d36399e61
F ext/fiddle/emscripten.css 3d253a6fdb8983a2ac983855bfbdd4b6fa1ff267c28d69513dd6ef1f289ada3f
F ext/fiddle/fiddle-worker.js e87c17070b979bd057a6849332f2a86660a4255ff7f1b6671e3e6026182ffd5a
F ext/fiddle/fiddle.html 657c6c3f860c322fba3c69fa4f7a1209e2d2ce44b4bc65a3e154e3a97c047a7c
F ext/fiddle/fiddle.js 0263a1ebf7e09ecd8b37ff8e00b9ba27c543b65b6c3dbf2f9def90e6c71c4580
F ext/fiddle/fiddle-worker.js 12b51133e47d1bfaf3e09bb0fa956c0f24bada42d25d4d67322ed86ca94ff634
F ext/fiddle/fiddle.html caee127caba2e2a797954ae911f071a6fd224c07e108171d24b01940058b4564
F ext/fiddle/fiddle.js e4b10f2b7c325060d0c2ff49408cf5ca1439f8bac2df97781fe7213eaac1d6a3
F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
F ext/fiddle/sqlite3-api.js 5492d48b4167179fd979fae99f0c21dc2d0f03460be9ff2d35e62225c58c4c9c
F ext/fiddle/sqlite3-api.js 5b47e19d2e34cf0d6f09e1ea845f4e151879aaa37dc06eb88b21865efb94bd8a
F ext/fiddle/testing-common.js a2527fd8dfb500bad9b434ae2645bb91489792115ee1e1b4b53cac4e9198992a
F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a
F ext/fiddle/testing1.html c00236d71b7f7523b722ae2f79cb2b734e6ed4ff16102fa69974145f6e2bfc95
@ -569,7 +569,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
F src/resolve.c a4eb3c617027fd049b07432f3b942ea7151fa793a332a11a7d0f58c9539e104f
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
F src/select.c 4f13d01141caae4f982125fecf50703c746ab621c57ca0aa747fab2b28e88c1e
F src/shell.c.in 55d71bf8c7a8f2a66bc5f99cd76f226790b291599b83415533dad84a553ed806
F src/shell.c.in b0adbcaaa7941284194b1e3ca2b89fb8a3cb96d395ea2f3be46b85f166716b09
F src/sqlite.h.in d15c307939039086adca159dd340a94b79b69827e74c6d661f343eeeaefba896
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d
@ -1969,8 +1969,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 2f9a42fb141d386f6edd03a37da3b0cef63dcc9fbfd076076b5330a8aa7d45a8
R b23285aa9cad2fbb603a6d0b592197f4
U drh
Z a3136607e1e2d962d7405d0d9fcd3d10
P 2b6ebba26d936ae7b9acf7d4bd15e82cbfabda22e1044b3dd838c7b07095100e
R 62090175f25fd90e2c5311e4c96dd351
T *branch * fiddle-local-db
T *sym-fiddle-local-db *
T -sym-trunk * Cancelled\sby\sbranch.
U stephan
Z 3bd69b4520f16e04c7fb41fc28b99798
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
2b6ebba26d936ae7b9acf7d4bd15e82cbfabda22e1044b3dd838c7b07095100e
0fa8378c006fcf2311772d36cf2e3c2cd8e8648f671de89ee9832e2e1a06ef49

View File

@ -4394,6 +4394,11 @@ static const char *(azHelp[]) = {
" --bom Put a UTF8 byte-order mark at the beginning",
" -e Send output to the system text editor",
" -x Send output as CSV to a spreadsheet (same as \".excel\")",
/* Note that .open is (partially) available in WASM builds but is
** currently only intended to be fed the names of already-existing
** binary db imports, so is "undocumented." Passing it an unknown
** filename will lead to an exit(), so we don't want WASM-side
** callers using it. */
".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
" Options:",
" --append Use appendvfs to append database to the end of FILE",
@ -4407,12 +4412,14 @@ static const char *(azHelp[]) = {
" --nofollow Do not follow symbolic links",
" --readonly Open FILE readonly",
" --zip FILE is a ZIP archive",
#ifndef SQLITE_SHELL_WASM_MODE
".output ?FILE? Send output to FILE or stdout if FILE is omitted",
" If FILE begins with '|' then open it as a pipe.",
" Options:",
" --bom Prefix output with a UTF8 byte-order mark",
" -e Send output to the system text editor",
" -x Send output as CSV to a spreadsheet",
#endif
".parameter CMD ... Manage SQL parameter bindings",
" clear Erase all bindings",
" init Initialize the TEMP table that holds bindings",
@ -9574,6 +9581,7 @@ static int do_meta_command(char *zLine, ShellState *p){
/* Check for command-line arguments */
for(iName=1; iName<nArg; iName++){
const char *z = azArg[iName];
#ifndef SQLITE_SHELL_WASM_MODE
if( optionMatch(z,"new") ){
newFlag = 1;
#ifdef SQLITE_HAVE_ZLIB
@ -9594,7 +9602,9 @@ static int do_meta_command(char *zLine, ShellState *p){
}else if( optionMatch(z, "maxsize") && iName+1<nArg ){
p->szMax = integerValue(azArg[++iName]);
#endif /* SQLITE_OMIT_DESERIALIZE */
}else if( z[0]=='-' ){
}else
#endif /* !SQLITE_SHELL_WASM_MODE */
if( z[0]=='-' ){
utf8_printf(stderr, "unknown option: %s\n", z);
rc = 1;
goto meta_command_exit;
@ -9621,6 +9631,7 @@ static int do_meta_command(char *zLine, ShellState *p){
/* If a filename is specified, try to open it first */
if( zFN || p->openMode==SHELL_OPEN_HEXDB ){
if( newFlag && zFN && !p->bSafeMode ) shellDeleteFile(zFN);
#ifndef SQLITE_SHELL_WASM_MODE
if( p->bSafeMode
&& p->openMode!=SHELL_OPEN_HEXDB
&& zFN
@ -9628,6 +9639,9 @@ static int do_meta_command(char *zLine, ShellState *p){
){
failIfSafeMode(p, "cannot open disk-based database files in safe mode");
}
#else
/* WASM mode has its own sandboxed pseudo-filesystem. */
#endif
if( zFN ){
zNewFilename = sqlite3_mprintf("%s", zFN);
shell_check_oom(zNewFilename);