1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

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_*/};
};