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:
@ -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);
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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 */;
|
||||
|
||||
|
@ -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_*/};
|
||||
};
|
||||
|
Reference in New Issue
Block a user