aboutsummaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
Diffstat (limited to 'ext')
-rw-r--r--ext/fiddle/fiddle-worker.js77
-rw-r--r--ext/fiddle/fiddle.html15
-rw-r--r--ext/fiddle/fiddle.js43
-rw-r--r--ext/fiddle/sqlite3-api.js34
4 files changed, 143 insertions, 26 deletions
diff --git a/ext/fiddle/fiddle-worker.js b/ext/fiddle/fiddle-worker.js
index 16da63606..6a687e42c 100644
--- a/ext/fiddle/fiddle-worker.js
+++ b/ext/fiddle/fiddle-worker.js
@@ -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;
+ }
+ 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;
}
- } 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);
};
diff --git a/ext/fiddle/fiddle.html b/ext/fiddle/fiddle.html
index 52ae2467e..8db780e4d 100644
--- a/ext/fiddle/fiddle.html
+++ b/ext/fiddle/fiddle.html
@@ -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>
diff --git a/ext/fiddle/fiddle.js b/ext/fiddle/fiddle.js
index 5590279d0..796c6bced 100644
--- a/ext/fiddle/fiddle.js
+++ b/ext/fiddle/fiddle.js
@@ -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 */;
diff --git a/ext/fiddle/sqlite3-api.js b/ext/fiddle/sqlite3-api.js
index e97e7c183..1bf162900 100644
--- a/ext/fiddle/sqlite3-api.js
+++ b/ext/fiddle/sqlite3-api.js
@@ -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_*/};
};