]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Initial version of an sqlfiddle-style application using a WASM build of the sqlite3...
authorstephan <stephan@noemail.net>
Wed, 18 May 2022 17:14:24 +0000 (17:14 +0000)
committerstephan <stephan@noemail.net>
Wed, 18 May 2022 17:14:24 +0000 (17:14 +0000)
FossilOrigin-Name: af9c21c9e0caf05adac7a9fcde39a9164c89f1c78b767b6fdd74a1405a3d373f

Makefile.in
ext/fiddle/Makefile [new file with mode: 0644]
ext/fiddle/fiddle.in.html [new file with mode: 0644]
ext/fiddle/index.md [new file with mode: 0644]
manifest
manifest.uuid
src/shell.c.in

index e5d30d030b4af3a29c9432bdf2104e015f2fb88b..92b2525d589efd7108b275f23471ffb2f7aa67cd 100644 (file)
@@ -1512,3 +1512,24 @@ sqlite3.def: $(REAL_LIBOBJ)
 sqlite3.dll: $(REAL_LIBOBJ) sqlite3.def
        $(TCC) -shared -o $@ sqlite3.def \
                -Wl,"--strip-all" $(REAL_LIBOBJ)
+
+
+#
+# fiddle section
+#
+fiddle_dir = ext/fiddle
+fiddle_tmpl = $(fiddle_dir)/fiddle.in.html
+fiddle_html = $(fiddle_dir)/fiddle.html
+fiddle_generated = $(fiddle_html) \
+                   $(fiddle_dir)/fiddle.js \
+                   $(fiddle_dir)/fiddle.wasm
+clean-fiddle:
+       rm -f $(fiddle_generated)
+clean: clean-fiddle
+emcc_flags = -sEXPORTED_RUNTIME_METHODS=ccall,cwrap \
+             -sEXPORTED_FUNCTIONS=_fiddle_exec \
+             --shell-file $(fiddle_tmpl)
+$(fiddle_html): Makefile sqlite3.c shell.c $(fiddle_tmpl)
+       emcc -o $@ $(emcc_flags) sqlite3.c shell.c
+
+fiddle: $(fiddle_html)
diff --git a/ext/fiddle/Makefile b/ext/fiddle/Makefile
new file mode 100644 (file)
index 0000000..a3bc352
--- /dev/null
@@ -0,0 +1,5 @@
+default:
+       make -C ../.. fiddle
+
+clean:
+       make -C ../../ clean-fiddle
diff --git a/ext/fiddle/fiddle.in.html b/ext/fiddle/fiddle.in.html
new file mode 100644 (file)
index 0000000..ddd7d69
--- /dev/null
@@ -0,0 +1,208 @@
+<!doctype html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <title>sqlite3 fiddle</title>
+    <style>
+      .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
+      textarea { font-family: monospace; }
+      div.emscripten { text-align: center; }
+      div.emscripten_border { border: 1px solid black; }
+      .spinner {
+        height: 50px;
+        width: 50px;
+        margin: 0px auto;
+        animation: rotation 0.8s linear infinite;
+        border-left: 10px solid rgb(0,150,240);
+        border-right: 10px solid rgb(0,150,240);
+        border-bottom: 10px solid rgb(0,150,240);
+        border-top: 10px solid rgb(100,0,200);
+        border-radius: 100%;
+        background-color: rgb(200,100,250);
+      }
+      @keyframes rotation {
+        from {transform: rotate(0deg);}
+        to {transform: rotate(360deg);}
+      }
+      body > header {
+          font-size: 130%;
+          font-weight: bold;
+      }
+      
+      #main-wrapper {
+          display: flex;
+          flex-direction: column;
+      }
+      #main-wrapper.side-by-side {
+          flex-direction: row;
+      }
+      .ta-wrapper{
+          display: flex;
+          flex-direction: column;
+          align-items: stretch;
+          flex: 1 1 auto;
+      }
+      .button-bar {
+          display: flex;
+          justify-content: center;
+      }
+      .button-bar button {
+          margin: 0.25em 1em;
+      }
+    </style>
+  </head>
+  <body>
+    <header>sqlite3 fiddle</header>
+    <figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
+    <div class="emscripten" id="status">Downloading...</div>
+    <div class="emscripten">
+      <progress value="0" max="100" id="progress" hidden='1'></progress>  
+    </div>
+    <div id='main-wrapper'>
+      <div class='ta-wrapper'>
+        <textarea id="input" rows="8">-- Use ctrl-enter or shift-enter to execute SQL
+.nullvalue THIS_IS_NULL
+.mode box
+create table t(a,b);
+insert into t(a,b) values('abc',123),('def',456),(NULL,789),('ghi',012);
+select * from t;</textarea>        
+        <div class='button-bar'>
+          <button id='btn-run' disabled>Run</button>
+          <button id='btn-clear' disabled>Clear</button>
+        </div>
+      </div>
+      <div class='ta-wrapper'>
+        <textarea id="output" readonly rows="8"></textarea>
+        <div class='button-bar'>
+          <button id='btn-clear-output' disabled>Clear</button>
+        </div>
+      </div>
+    </div>
+    <fieldset>
+      <legend>Options</legend>
+      <div class=''>
+        <input type='checkbox' id='opt-cb-sbs'>
+        <label for='opt-cb-sbs'>Side-by-side</label>
+      </div>
+    </fieldset>
+    <hr>
+    <script type='text/javascript'>
+      (function(){
+          /**
+             Callback for the emscripten module init process, gets
+             passed the module object after all parts of the module
+             have been loaded and initialized.
+          */
+          const doAppSetup = function(Module) {
+              const taInput = document.querySelector('#input');
+              const btnClearIn = document.querySelector('#btn-clear');
+              document.querySelectorAll('button').forEach(function(e){
+                  e.removeAttribute('disabled');
+              });
+              btnClearIn.addEventListener('click',function(){
+                  taInput.value = '';
+              },false);
+              // Ctrl-enter and shift-enter both run the current SQL.
+              taInput.addEventListener('keydown',function(ev){
+                  if((ev.ctrlKey || ev.shiftKey) && 13 === ev.keyCode){
+                      ev.preventDefault();
+                      ev.stopPropagation();
+                      btnRun.click();
+                  }
+              }, false);
+              const taOutput = document.querySelector('#output');
+              const btnClearOut = document.querySelector('#btn-clear-output');
+              btnClearOut.addEventListener('click',function(){
+                  taOutput.value = '';
+              },false);
+              const doExec = Module.cwrap('fiddle_exec', null, ['string']);
+              const btnRun = document.querySelector('#btn-run');
+              btnRun.addEventListener('click',function(){
+                  const sql = taInput.value.trim();
+                  if(sql){
+                      doExec(sql);
+                  }
+              },false);
+              doExec()/*sets up the db and outputs the header*/;
+
+              let e = document.querySelector('#opt-cb-sbs');
+              const mainWrapper = document.querySelector('#main-wrapper');
+              e.addEventListener('change', function(){
+                  mainWrapper.classList[this.checked ? 'add' : 'remove']('side-by-side');
+              }, false);
+          };
+
+          /**
+             What follow is part of the emscripten core setup. Do not modify without
+             understanding what it's doing...
+          */
+          var statusElement = document.getElementById('status');
+          var progressElement = document.getElementById('progress');
+          var spinnerElement = document.getElementById('spinner');
+          window.Module = {
+              preRun: [],
+              postRun: [],
+              onRuntimeInitialized: function(){
+                  doAppSetup(this);
+              },
+              print: (function f() {
+                  if(!f._){
+                      f._ = document.getElementById('output');
+                  }
+                  f._.value = ''; // clear browser cache
+                  return function(text) {
+                      if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
+                      // These replacements are necessary if you render to raw HTML
+                      //text = text.replace(/&/g, "&amp;");
+                      //text = text.replace(/</g, "&lt;");
+                      //text = text.replace(/>/g, "&gt;");
+                      //text = text.replace('\n', '<br>', 'g');
+                      //console.log("arguments",arguments);
+                      console.log(text);
+                      f._.value += text + "\n";
+                      f._.scrollTop = f._.scrollHeight; // focus on bottom
+                  };
+              })(),
+              setStatus: function(text) {
+                  if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
+                  if (text === Module.setStatus.last.text) return;
+                  var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
+                  var now = Date.now();
+                  if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
+                  Module.setStatus.last.time = now;
+                  Module.setStatus.last.text = text;
+                  if (m) {
+                      text = m[1];
+                      progressElement.value = parseInt(m[2])*100;
+                      progressElement.max = parseInt(m[4])*100;
+                      progressElement.hidden = false;
+                      spinnerElement.hidden = false;
+                  } else {
+                      progressElement.value = null;
+                      progressElement.max = null;
+                      progressElement.hidden = true;
+                      if (!text) spinnerElement.hidden = true;
+                  }
+                  statusElement.innerHTML = text;
+              },
+              totalDependencies: 0,
+              monitorRunDependencies: function(left) {
+                  this.totalDependencies = Math.max(this.totalDependencies, left);
+                  Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
+              }
+          };
+          Module.printErr = Module.print/*redirect stderr*/;
+          Module.setStatus('Downloading...');
+          window.onerror = function() {
+              Module.setStatus('Exception thrown, see JavaScript console');
+              spinnerElement.style.display = 'none';
+              Module.setStatus = function(text) {
+                  if (text) console.error('[post-exception status] ' + text);
+              };
+          };
+      })();
+    </script>
+    {{{ SCRIPT }}}
+  </body>
+</html>
diff --git a/ext/fiddle/index.md b/ext/fiddle/index.md
new file mode 100644 (file)
index 0000000..9f5ab41
--- /dev/null
@@ -0,0 +1,71 @@
+This directory houses a "fiddle"-style application which embeds a
+[Web Assembly (WASM)](https://en.wikipedia.org/wiki/WebAssembly)
+build of the sqlite3 shell app into an HTML page, effectively running
+the shell in a client-side browser.
+
+It requires [emscripten][] and that the build environment be set up for
+emscripten. A mini-HOWTO for setting that up follows...
+
+First, install the Emscripten SDK, as documented
+[here](https://emscripten.org/docs/getting_started/downloads.html) and summarized
+below for Linux environments:
+
+```
+# Clone the emscripten repository:
+$ git clone https://github.com/emscripten-core/emsdk.git
+$ cd emsdk
+
+# Download and install the latest SDK tools:
+$ ./emsdk install latest
+
+# Make the "latest" SDK "active" for the current user:
+$ ./emsdk activate latest
+```
+
+Those parts only need to be run once. The following needs to be run for each
+shell instance which needs the `emcc` compiler:
+
+```
+# Activate PATH and other environment variables in the current terminal:
+$ source ./emsdk_env.sh
+
+$ which emcc
+/path/to/emsdk/upstream/emscripten/emcc
+```
+
+That `env` script needs to be sourced for building this application from the
+top of the sqlite3 build tree:
+
+```
+$ make fiddle
+```
+
+Or:
+
+```
+$ cd ext/fiddle
+$ make
+```
+
+That will generate the fiddle application under
+[ext/fiddle](/dir/ext/fiddle), as `fiddle.html`. That application
+cannot, due to XMLHttpRequest security limitations, run if the HTML
+file is opened directly in the browser (i.e. if it is opened using a
+`file://` URL), so it needs to be served via an HTTP server.  For
+example, using [althttpd][]:
+
+```
+$ cd ext/fiddle
+$ althttpd -debug 1 -jail 0 -port 9090 -root .
+```
+
+Then browse to `http://localhost:9090/fiddle.html`.
+
+Note that when serving this app via [althttpd][], it must be a version
+from 2022-05-17 or newer so that it recognizes the `.wasm` file
+extension and responds with the mimetype `application/wasm`, as the
+wasm loader is pedantic about that detail.
+
+
+[emscripten]: https://emscripten.org
+[althttpd]: https://sqlite.org/althttpd
index 7f1d327e7e10c97dc8de777ad79159ddfd6d92c5..2cc14ec5e249d370abad427365bca8d75d47e222 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
-C Fix\sharmless\scompiler\swarnings\sin\sthe\snew\sunixFullPathname\simplementation.
-D 2022-05-17T15:11:57.632
+C Initial\sversion\sof\san\ssqlfiddle-style\sapplication\susing\sa\sWASM\sbuild\sof\sthe\ssqlite3\sshell.
+D 2022-05-18T17:14:24.622
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in b210ad2733317f1a4353085dfb9d385ceec30b0e6a61d20a5accabecac6b1949
+F Makefile.in ff32504cde350caaf4d52abfdca5dd1fa88b21e53071a0f9cc3539b6789c3606
 F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
 F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3
 F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
@@ -55,6 +55,9 @@ F ext/expert/expert1.test 3c642a4e7bbb14f21ddab595436fb465a4733f47a0fe5b2855e1d5
 F ext/expert/sqlite3expert.c 6ca30d73b9ed75bd56d6e0d7f2c962d2affaa72c505458619d0ff5d9cdfac204
 F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
 F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be14095c32a72
+F ext/fiddle/Makefile ea647919e6ac4b50edde1490f60ee87e8ccd75141e4aa650718c6f28eb323bbc
+F ext/fiddle/fiddle.in.html 85db5e736f82fd2cae1c4f61b5af62239cc5db363b9eb6f4e383e0d97c59b322
+F ext/fiddle/index.md 08d25ec6fe2a56923e8ea6e5d6c80907bf3a60f9c40a6841a8f402e402dd5f22
 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
 F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
 F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@@ -554,7 +557,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c
 F src/resolve.c a4eb3c617027fd049b07432f3b942ea7151fa793a332a11a7d0f58c9539e104f
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 74060a09f66c0c056f3c61627e22cb484af0bbfa29d7d14dcf17c684742c15de
-F src/shell.c.in 176cad562152cbbafe7ecc9c83c82850e2c3d0cf33ec0a52d67341d35c842f22
+F src/shell.c.in 1892f21aafee8eca543881ef429f6166386ca1ae0051252e91d1235bd6f4217b
 F src/sqlite.h.in d15c307939039086adca159dd340a94b79b69827e74c6d661f343eeeaefba896
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d
@@ -1954,8 +1957,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 d8b249e8cdf0babe1427d0587dbdc27a52ec06a5ef3a20dfb05a0ea4adb85858
-R 56c8d067b3c475439a951ae630953e32
-U drh
-Z 99cc78493b68b2d09cacc03a9e60389c
+P f7e1ceb5b59a876cfd04a8aac0ee2b322c970555b9c361b4953d711ef6596e37
+R f9ce230cf39758aaeb408ee00f09f455
+T *branch * fiddle
+T *sym-fiddle *
+T -sym-trunk *
+U stephan
+Z 33db0432b22b9541a19d442c2dec2c8e
 # Remove this line to create a well-formed Fossil manifest.
index e1be3622c75c432b6a28eac8539d88a314e31ea6..9427d521e0857e749571aebe963a45b69d42f8ec 100644 (file)
@@ -1 +1 @@
-f7e1ceb5b59a876cfd04a8aac0ee2b322c970555b9c361b4953d711ef6596e37
\ No newline at end of file
+af9c21c9e0caf05adac7a9fcde39a9164c89f1c78b767b6fdd74a1405a3d373f
\ No newline at end of file
index cace8bf2f4ad307ba266a541316bbbdd467484f2..b46a00de66da4b68b6ab90e2f865c95fa557695b 100644 (file)
@@ -229,6 +229,16 @@ static void setTextMode(FILE *file, int isOutput){
 # define setTextMode(X,Y)
 #endif
 
+/*
+** When compiling with emcc (a.k.a. emscripten), we're building a
+** WebAssembly (WASM) bundle and need to disable and rewire a few
+** things.
+*/
+#ifdef __EMSCRIPTEN__
+#define SQLITE_SHELL_WASM_MODE
+#else
+#undef SQLITE_SHELL_WASM_MODE
+#endif
 
 /* True if the timer is enabled */
 static int enableTimer = 0;
@@ -691,6 +701,7 @@ static char *local_getline(char *zLine, FILE *in){
 ** be freed by the caller or else passed back into this routine via the
 ** zPrior argument for reuse.
 */
+#ifndef SQLITE_SHELL_WASM_MODE
 static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
   char *zPrompt;
   char *zResult;
@@ -710,7 +721,7 @@ static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
   }
   return zResult;
 }
-
+#endif /* !SQLITE_SHELL_WASM_MODE */
 
 /*
 ** Return the value of a hexadecimal digit.  Return -1 if the input
@@ -1009,16 +1020,18 @@ INCLUDE test_windirent.h
 INCLUDE test_windirent.c
 #define dirent DIRENT
 #endif
-INCLUDE ../ext/misc/shathree.c
-INCLUDE ../ext/misc/fileio.c
-INCLUDE ../ext/misc/completion.c
-INCLUDE ../ext/misc/appendvfs.c
 INCLUDE ../ext/misc/memtrace.c
+INCLUDE ../ext/misc/shathree.c
 INCLUDE ../ext/misc/uint.c
 INCLUDE ../ext/misc/decimal.c
 INCLUDE ../ext/misc/ieee754.c
 INCLUDE ../ext/misc/series.c
 INCLUDE ../ext/misc/regexp.c
+#ifndef SQLITE_SHELL_WASM_MODE
+INCLUDE ../ext/misc/fileio.c
+INCLUDE ../ext/misc/completion.c
+INCLUDE ../ext/misc/appendvfs.c
+#endif
 #ifdef SQLITE_HAVE_ZLIB
 INCLUDE ../ext/misc/zipfile.c
 INCLUDE ../ext/misc/sqlar.c
@@ -1149,8 +1162,18 @@ struct ShellState {
   char *zNonce;          /* Nonce for temporary safe-mode excapes */
   EQPGraph sGraph;       /* Information for the graphical EXPLAIN QUERY PLAN */
   ExpertInfo expert;     /* Valid if previous command was ".expert OPT..." */
+#ifdef SQLITE_SHELL_WASM_MODE
+  struct {
+    const char * zInput; /* Input string from wasm/JS proxy */
+    char const * zPos;   /* Cursor pos into zInput */
+  } wasm;
+#endif
 };
 
+#ifdef SQLITE_SHELL_WASM_MODE
+static ShellState shellState;
+#endif
+
 
 /* Allowed values for ShellState.autoEQP
 */
@@ -4991,14 +5014,16 @@ static void open_db(ShellState *p, int openFlags){
 #ifndef SQLITE_OMIT_LOAD_EXTENSION
     sqlite3_enable_load_extension(p->db, 1);
 #endif
-    sqlite3_fileio_init(p->db, 0, 0);
     sqlite3_shathree_init(p->db, 0, 0);
-    sqlite3_completion_init(p->db, 0, 0);
     sqlite3_uint_init(p->db, 0, 0);
     sqlite3_decimal_init(p->db, 0, 0);
     sqlite3_regexp_init(p->db, 0, 0);
     sqlite3_ieee_init(p->db, 0, 0);
     sqlite3_series_init(p->db, 0, 0);
+#ifndef SQLITE_SHELL_WASM_MODE
+    sqlite3_fileio_init(p->db, 0, 0);
+    sqlite3_completion_init(p->db, 0, 0);
+#endif
 #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
     sqlite3_dbdata_init(p->db, 0, 0);
 #endif
@@ -11467,6 +11492,39 @@ static void echo_group_input(ShellState *p, const char *zDo){
   if( ShellHasFlag(p, SHFLG_Echo) ) utf8_printf(p->out, "%s\n", zDo);
 }
 
+#ifdef SQLITE_SHELL_WASM_MODE
+/*
+** Alternate one_input_line() impl for wasm mode. This is not in the primary impl
+** because we need the global shellState and cannot access it from that function
+** without moving lots of code around (creating a larger/messier diff).
+*/
+static char *one_input_line(FILE *in, char *zPrior, int isContinuation){
+  /* Parse the next line from shellState.wasm.zInput. */
+  const char *zBegin = shellState.wasm.zPos;
+  const char *z = zBegin;
+  char *zLine = 0;
+  int nZ = 0;
+
+  UNUSED_PARAMETER(in);
+  UNUSED_PARAMETER(isContinuation);
+  if(!z || !*z){
+    return 0;
+  }
+  while(*z && isspace(*z)) ++z;
+  zBegin = z;
+  for(; *z && '\n'!=*z; ++nZ, ++z){}
+  if(nZ>0 && '\r'==zBegin[nZ-1]){
+    --nZ;
+  }
+  shellState.wasm.zPos = z;
+  zLine = realloc(zPrior, nZ+1);
+  shell_check_oom(zLine);
+  memcpy(zLine, zBegin, (size_t)nZ);
+  zLine[nZ] = 0;
+  return zLine;
+}
+#endif /* SQLITE_SHELL_WASM_MODE */
+
 /*
 ** Read input from *in and process it.  If *in==0 then input
 ** is interactive - the user is typing it it.  Otherwise, input
@@ -11848,6 +11906,10 @@ static char *cmdline_option_value(int argc, char **argv, int i){
 #  endif
 #endif
 
+#ifdef SQLITE_SHELL_WASM_MODE
+#  define main fiddle_main
+#endif
+
 #if SQLITE_SHELL_IS_UTF8
 int SQLITE_CDECL main(int argc, char **argv){
 #else
@@ -11858,7 +11920,11 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
   sqlite3_uint64 mem_main_enter = sqlite3_memory_used();
 #endif
   char *zErrMsg = 0;
+#ifdef SQLITE_SHELL_WASM_MODE
+#  define data shellState
+#else
   ShellState data;
+#endif
   const char *zInitFile = 0;
   int i;
   int rc = 0;
@@ -11874,8 +11940,13 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 
   setBinaryMode(stdin, 0);
   setvbuf(stderr, 0, _IONBF, 0); /* Make sure stderr is unbuffered */
+#ifdef SQLITE_SHELL_WASM_MODE
+  stdin_is_interactive = 0;
+  stdout_is_console = 1;
+#else
   stdin_is_interactive = isatty(0);
   stdout_is_console = isatty(1);
+#endif
 
 #if !defined(_WIN32_WCE)
   if( getenv("SQLITE_DEBUG_BREAK") ){
@@ -12131,7 +12202,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
 #endif
   }
   data.out = stdout;
+#ifndef SQLITE_SHELL_WASM_MODE
   sqlite3_appendvfs_init(0,0,0);
+#endif
 
   /* Go ahead and open the database file if it already exists.  If the
   ** file does not exist, delay opening it.  This prevents empty database
@@ -12397,6 +12470,9 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       rc = process_input(&data);
     }
   }
+#ifndef SQLITE_SHELL_WASM_MODE
+  /* In WASM mode we have to leave the db state in place so that
+  ** client code can "push" SQL into it after this call returns. */
   free(azCmd);
   set_table_name(&data, 0);
   if( data.db ){
@@ -12429,5 +12505,45 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
                 (unsigned int)(sqlite3_memory_used()-mem_main_enter));
   }
 #endif
+#endif /* !SQLITE_SHELL_WASM_MODE */
   return rc;
 }
+
+
+#ifdef SQLITE_SHELL_WASM_MODE
+/*
+** Trivial exportable function for emscripten. Needs to be exported using:
+**
+** emcc ..flags... -sEXPORTED_FUNCTIONS=_fiddle_exec -sEXPORTED_RUNTIME_METHODS=ccall,cwrap
+**
+** (Note the underscore before the function name.) It processes zSql
+** as if it were input to the sqlite3 shell and redirects all output
+** to the wasm binding.
+*/
+void fiddle_exec(char const * zSql){
+  static int once = 0;
+  int rc = 0;
+  if(!once){
+    /* Simulate an argv array for main() */
+    static char * argv[] = {"fiddle", "-bail", "-safe"};
+    rc = fiddle_main((int)(sizeof(argv)/sizeof(argv[0])), argv);
+    once = rc ? -1 : 1;
+    memset(&shellState.wasm, 0, sizeof(shellState.wasm));
+    printf(
+        "SQLite version %s %.19s\n" /*extra-version-info*/,
+        sqlite3_libversion(), sqlite3_sourceid()
+    );
+    puts("WASM shell");
+    puts("Enter \".help\" for usage hints.");
+    puts("Connected to a transient in-memory database.");
+  }
+  if(once<0){
+    puts("DB init failed. Not executing SQL.");
+  }else if(zSql && *zSql){
+    shellState.wasm.zInput = zSql;
+    shellState.wasm.zPos = zSql;
+    process_input(&shellState);
+    memset(&shellState.wasm, 0, sizeof(shellState.wasm));
+  }
+}
+#endif /* SQLITE_SHELL_WASM_MODE */