]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
fiddle: initial work on loading a client-side db file. Works but requires some cleanu...
authorstephan <stephan@noemail.net>
Tue, 24 May 2022 19:01:21 +0000 (19:01 +0000)
committerstephan <stephan@noemail.net>
Tue, 24 May 2022 19:01:21 +0000 (19:01 +0000)
FossilOrigin-Name: 0fa8378c006fcf2311772d36cf2e3c2cd8e8648f671de89ee9832e2e1a06ef49

ext/fiddle/fiddle-worker.js
ext/fiddle/fiddle.html
ext/fiddle/fiddle.js
ext/fiddle/sqlite3-api.js
manifest
manifest.uuid
src/shell.c.in

index 16da63606efd8ec9304d8eda8b61aa6d9c54e0a9..6a687e42cb63c3343eb9ed4070034e20192f0c63 100644 (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;
+        }
+        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);
 };
index 52ae2467ec2efa412bc3bfac2c9000665460f191..8db780e4d75625d9e2f27943969f56cb021785cf 100644 (file)
       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;
       }
       #view-split {
           display: flex;
-          flex-direction: column;
+          flex-direction: column-reverse;
       }
     </style>
   </head>
                    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>
index 5590279d0c06b0df65417fc3571cdb19178be8af..796c6bcedb8c95ee731bcc2af69ab85565515dd4 100644 (file)
             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
             }
             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
                     preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
                     btnShellExec.innerText = preStartWork._.btnLabel;
                     btnShellExec.removeAttribute('disabled');
+                    btnInterrupt.setAttribute('disabled','disabled');
                     return;
             }
             console.warn("Unhandled 'working' event:",ev.data);
                 }, 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 */;
 
index e97e7c18374dc38d2ae8311f7a0151474e09f050..1bf16290034b56d67906314be93e5107b08d2baa 100644 (file)
     };
 
     /**
-       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_*/};
     };
index a6f75bdb8585eff39218e9cdab91b3d60906e8fe..85d3e696eda613e2a7ce5000ff0108fd92b0aa4c 100644 (file)
--- a/manifest
+++ b/manifest
@@ -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.
index 1cb0daf032406dbfd361a5a86e5f59495f115636..4689d8f38c959729d744c90d6d95dfda0632646a 100644 (file)
@@ -1 +1 @@
-2b6ebba26d936ae7b9acf7d4bd15e82cbfabda22e1044b3dd838c7b07095100e
\ No newline at end of file
+0fa8378c006fcf2311772d36cf2e3c2cd8e8648f671de89ee9832e2e1a06ef49
\ No newline at end of file
index 83d985abf9a257938508b0f7f3adf59e864aa5fa..06a4b8113727d8562d97711ec5d626fdb3c858dd 100644 (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);