+ On a slow internet connection this may take a moment. If this
+ message displays for "a long time", intialization may have
+ failed and the JavaScript console may contain clues as to why.
+
+
+
Downloading...
+
+
+
+
+
+ This is a placeholder for a terminal-like view.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{{ SCRIPT }}}
+
+
diff --git a/ext/fiddle/index.md b/ext/fiddle/index.md
new file mode 100644
index 0000000000..9d1f8d83ea
--- /dev/null
+++ b/ext/fiddle/index.md
@@ -0,0 +1,94 @@
+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.
+
+# Known Quirks and Limitations
+
+Some "impedence mismatch" between C and WASM/JavaScript is to be
+expected.
+
+## No I/O
+
+sqlite3 shell commands which require file I/O or pipes are disabled in
+the WASM build.
+
+## `exit()` Triggered from C
+
+When C code calls `exit()`, as happens (for example) when running an
+"unsafe" command when safe mode is active, WASM's connection to the
+sqlite3 shell environment has no sensible choice but to shut down
+because `exit()` leaves it in a state we can no longer recover
+from. The JavaScript-side application attempts to recognize this and
+warn the user that restarting the application is necessary. Currently
+the only way to restart it is to reload the page. Restructuring the
+shell code such that it could be "rebooted" without restarting the
+JS app would require some invasive changes which are not currently
+on any TODO list but have not been entirely ruled out long-term.
+
+
+[emscripten]: https://emscripten.org
+[althttpd]: https://sqlite.org/althttpd
diff --git a/ext/fiddle/module-post.js b/ext/fiddle/module-post.js
new file mode 100644
index 0000000000..8e13ab1d3c
--- /dev/null
+++ b/ext/fiddle/module-post.js
@@ -0,0 +1,233 @@
+/* This is the --post-js file for emcc. It gets appended to the
+ generated fiddle.js. It should contain all app-level code.
+
+ Maintenance achtung: do not call any wasm-bound functions from
+ outside of the onRuntimeInitialized() function. They are not
+ permitted to be called until after the module init is complete,
+ which does not happen until after this file is processed. Once that
+ init is finished, Module.onRuntimeInitialized() will be
+ triggered. All app-level init code should go into that callback or
+ be triggered via it. Calling wasm-bound functions before that
+ callback is run will trigger an assertion in the wasm environment.
+*/
+window.Module.onRuntimeInitialized = function(){
+ 'use strict';
+ const Module = window.Module /* wasm module as set up by emscripten */;
+ delete Module.onRuntimeInitialized;
+
+ /* querySelectorAll() proxy */
+ const EAll = function(/*[element=document,] cssSelector*/){
+ return (arguments.length>1 ? arguments[0] : document)
+ .querySelectorAll(arguments[arguments.length-1]);
+ };
+ /* querySelector() proxy */
+ const E = function(/*[element=document,] cssSelector*/){
+ return (arguments.length>1 ? arguments[0] : document)
+ .querySelector(arguments[arguments.length-1]);
+ };
+
+ // Unhide all elements which start out hidden
+ EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
+
+ const taInput = E('#input');
+ const btnClearIn = E('#btn-clear');
+ 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 = E('#output');
+ const btnClearOut = E('#btn-clear-output');
+ btnClearOut.addEventListener('click',function(){
+ taOutput.value = '';
+ if(Module.jqTerm) Module.jqTerm.clear();
+ },false);
+ /* Sends the given text to the shell. If it's null or empty, this
+ is a no-op except that the very first call will initialize the
+ db and output an informational header. */
+ const doExec = function f(sql){
+ if(!f._) f._ = Module.cwrap('fiddle_exec', null, ['string']);
+ if(Module._isDead){
+ Module.printErr("shell module has exit()ed. Cannot run SQL.");
+ return;
+ }
+ if(Module.config.autoClearOutput) taOutput.value='';
+ f._(sql);
+ };
+ const btnRun = E('#btn-run');
+ btnRun.addEventListener('click',function(){
+ const sql = taInput.value.trim();
+ if(sql){
+ doExec(sql);
+ }
+ },false);
+
+ const mainWrapper = E('#main-wrapper');
+ /* For each checkboxes with data-csstgt, set up a handler which
+ toggles the given CSS class on the element matching
+ E(data-csstgt). */
+ EAll('input[type=checkbox][data-csstgt]')
+ .forEach(function(e){
+ const tgt = E(e.dataset.csstgt);
+ const cssClass = e.dataset.cssclass || 'error';
+ e.checked = tgt.classList.contains(cssClass);
+ e.addEventListener('change', function(){
+ tgt.classList[
+ this.checked ? 'add' : 'remove'
+ ](cssClass)
+ }, false);
+ });
+ /* For each checkbox with data-config=X, set up a binding to
+ Module.config[X]. These must be set up AFTER data-csstgt
+ checkboxes so that those two states can be synced properly. */
+ EAll('input[type=checkbox][data-config]')
+ .forEach(function(e){
+ const confVal = !!Module.config[e.dataset.config];
+ if(e.checked !== confVal){
+ /* Ensure that data-csstgt mappings (if any) get
+ synced properly. */
+ e.checked = confVal;
+ e.dispatchEvent(new Event('change'));
+ }
+ e.addEventListener('change', function(){
+ Module.config[this.dataset.config] = this.checked;
+ }, false);
+ });
+ /* For each button with data-cmd=X, map a click handler which
+ calls doExec(X). */
+ const cmdClick = function(){doExec(this.dataset.cmd);};
+ EAll('button[data-cmd]').forEach(
+ e => e.addEventListener('click', cmdClick, false)
+ );
+
+
+ /**
+ Given a DOM element, this routine measures its "effective
+ height", which is the bounding top/bottom range of this element
+ and all of its children, recursively. For some DOM structure
+ cases, a parent may have a reported height of 0 even though
+ children have non-0 sizes.
+
+ Returns 0 if !e or if the element really has no height.
+ */
+ const effectiveHeight = function f(e){
+ if(!e) return 0;
+ if(!f.measure){
+ f.measure = function callee(e, depth){
+ if(!e) return;
+ const m = e.getBoundingClientRect();
+ if(0===depth){
+ callee.top = m.top;
+ callee.bottom = m.bottom;
+ }else{
+ callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
+ callee.bottom = Math.max(callee.bottom, m.bottom);
+ }
+ Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
+ if(0===depth){
+ //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
+ f.extra += callee.bottom - callee.top;
+ }
+ return f.extra;
+ };
+ }
+ f.extra = 0;
+ f.measure(e,0);
+ return f.extra;
+ };
+
+ /**
+ Returns a function, that, as long as it continues to be invoked,
+ will not be triggered. The function will be called after it stops
+ being called for N milliseconds. If `immediate` is passed, call
+ the callback immediately and hinder future invocations until at
+ least the given time has passed.
+
+ If passed only 1 argument, or passed a falsy 2nd argument,
+ the default wait time set in this function's $defaultDelay
+ property is used.
+
+ Source: underscore.js, by way of https://davidwalsh.name/javascript-debounce-function
+ */
+ const debounce = function f(func, wait, immediate) {
+ var timeout;
+ if(!wait) wait = f.$defaultDelay;
+ return function() {
+ const context = this, args = Array.prototype.slice.call(arguments);
+ const later = function() {
+ timeout = undefined;
+ if(!immediate) func.apply(context, args);
+ };
+ const callNow = immediate && !timeout;
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ if(callNow) func.apply(context, args);
+ };
+ };
+ debounce.$defaultDelay = 500 /*arbitrary*/;
+
+ const ForceResizeKludge = (function(){
+ /* Workaround for Safari mayhem regarding use of vh CSS units....
+ We cannot use vh units to set the terminal area size because
+ Safari chokes on that, so we calculate that height here. Larger
+ than ~95% is too big for Firefox on Android, causing the input
+ area to move off-screen. */
+ const bcl = document.body.classList;
+ const appViews = EAll('.app-view');
+ const resized = function f(){
+ if(f.$disabled) return;
+ const wh = window.innerHeight;
+ var ht;
+ var extra = 0;
+ const elemsToCount = [
+ E('body > header')
+ ];
+ elemsToCount.forEach((e)=>e ? extra += effectiveHeight(e) : false);
+ ht = wh - extra;
+ appViews.forEach(function(e){
+ e.style.height =
+ e.style.maxHeight = [
+ "calc(", (ht>=100 ? ht : 100), "px",
+ " - 3em"/*fudge value*/,")"
+ /* ^^^^ hypothetically not needed, but both Chrome/FF on
+ Linux will force scrollbars on the body if this value is
+ too small (<0.75em in my tests). */
+ ].join('');
+ });
+ };
+ resized.$disabled = true/*gets deleted when setup is finished*/;
+ window.addEventListener('resize', debounce(resized, 250), false);
+ return resized;
+ })();
+
+ Module.print(null/*clear any output generated by the init process*/);
+ if(window.jQuery && window.jQuery.terminal){
+ /* Set up the terminal-style view... */
+ const eTerm = window.jQuery('#jqterminal').empty();
+ Module.jqTerm = eTerm.terminal(doExec,{
+ prompt: 'sqlite> ',
+ greetings: false /* note that the docs incorrectly call this 'greeting' */
+ });
+ //Module.jqTerm.clear(/*remove the "greeting"*/);
+ /* Set up a button to toggle the views... */
+ const head = E('header#titlebar');
+ const btnToggleView = jQuery("")[0];
+ head.appendChild(btnToggleView);
+ btnToggleView.addEventListener('click',function f(){
+ EAll('.app-view').forEach(e=>e.classList.toggle('hidden'));
+ if(document.body.classList.toggle('terminal-mode')){
+ ForceResizeKludge();
+ }
+ }, false);
+ btnToggleView.click();
+ }
+ doExec(null/*init the db and output the header*/);
+ delete ForceResizeKludge.$disabled;
+ ForceResizeKludge();
+};
diff --git a/ext/fiddle/module-pre.js b/ext/fiddle/module-pre.js
new file mode 100644
index 0000000000..ebd812ef99
--- /dev/null
+++ b/ext/fiddle/module-pre.js
@@ -0,0 +1,110 @@
+/* This is the --pre-js file for emcc. It gets prepended to the
+ generated fiddle.js. It should contain only code which is relevant
+ to the setup and initialization of the wasm module. */
+(function(){
+ 'use strict';
+
+ /**
+ What follows is part of the emscripten core setup. Do not
+ modify it without understanding what it's doing.
+ */
+ const statusElement = document.getElementById('status');
+ const progressElement = document.getElementById('progress');
+ const spinnerElement = document.getElementById('spinner');
+ const Module = window.Module = {
+ /* Config object. Referenced by certain Module methods and
+ app-level code. */
+ config: {
+ /* If true, the Module.print() impl will auto-scroll
+ the output widget to the bottom when it receives output,
+ else it won't. */
+ autoScrollOutput: true,
+ /* If true, the output area will be cleared before each
+ command is run, else it will not. */
+ autoClearOutput: false,
+ /* If true, Module.print() will echo its output to
+ the console, in addition to its normal output widget. */
+ printToConsole: true,
+ /* If true, display input/output areas side-by-side. */
+ sideBySide: false,
+ /* If true, swap positions of the input/output areas. */
+ swapInOut: false
+ },
+ preRun: [],
+ postRun: [],
+ //onRuntimeInitialized: function(){},
+ print: (function f() {
+ /* Maintenance reminder: we currently require/expect a textarea
+ output element. It might be nice to extend this to behave
+ differently if the output element is a non-textarea element,
+ in which case it would need to append the given text as a TEXT
+ node and add a line break. */
+ const outputElem = document.getElementById('output');
+ outputElem.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, "&");
+ //text = text.replace(//g, ">");
+ //text = text.replace('\n', ' ', 'g');
+ if(null===text){/*special case: clear output*/
+ outputElem.value = '';
+ return;
+ }
+ if(window.Module.config.printToConsole) console.log(text);
+ if(window.Module.jqTerm) window.Module.jqTerm.echo(text);
+ outputElem.value += text + "\n";
+ if(window.Module.config.autoScrollOutput){
+ outputElem.scrollTop = outputElem.scrollHeight;
+ }
+ };
+ })(),
+ setStatus: function f(text) {
+ if(!f.last) f.last = { time: Date.now(), text: '' };
+ if(text === f.last.text) return;
+ const m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
+ const now = Date.now();
+ if(m && now - f.last.time < 30) return; // if this is a progress update, skip it if too soon
+ f.last.time = now;
+ f.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.remove();
+ if(!text) spinnerElement.remove();
+ }
+ if(text) statusElement.innerText = text;
+ else statusElement.remove();
+ },
+ totalDependencies: 0,
+ monitorRunDependencies: function(left) {
+ this.totalDependencies = Math.max(this.totalDependencies, left);
+ this.setStatus(left
+ ? ('Preparing... (' + (this.totalDependencies-left)
+ + '/' + this.totalDependencies + ')')
+ : 'All downloads complete.');
+ }
+ };
+ Module.printErr = Module.print/*capture stderr output*/;
+ Module.setStatus('Downloading...');
+ window.onerror = function(/*message, source, lineno, colno, error*/) {
+ const err = arguments[4];
+ if(err && 'ExitStatus'==err.name){
+ Module._isDead = true;
+ Module.printErr("FATAL ERROR:", err.message);
+ Module.printErr("Restarting the app requires reloading the page.");
+ const taOutput = document.querySelector('#output');
+ if(taOutput) taOutput.classList.add('error');
+ }
+ Module.setStatus('Exception thrown, see JavaScript console');
+ spinnerElement.style.display = 'none';
+ Module.setStatus = function(text) {
+ if(text) console.error('[post-exception status] ' + text);
+ };
+ };
+})();
diff --git a/manifest b/manifest
index 7f1d327e7e..aa0145d73b 100644
--- 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 Merge\sthe\schanges\sto\ssupport\sthe\s"fiddle"\sextension.
+D 2022-05-19T16:59:46.843
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
-F Makefile.in b210ad2733317f1a4353085dfb9d385ceec30b0e6a61d20a5accabecac6b1949
+F Makefile.in 5dbc61c076215a580d59d1f21b5e62955d2e570321b63f00a31b188f1f5089a6
F Makefile.linux-gcc f609543700659711fbd230eced1f01353117621dccae7b9fb70daa64236c5241
F Makefile.msc b28a8a7a977e7312f6859f560348e1eb110c21bd6cf9fab0d16537c0a514eef3
F README.md 8b8df9ca852aeac4864eb1e400002633ee6db84065bd01b78c33817f97d31f5e
@@ -55,6 +55,11 @@ 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 b2904d52c10a7c984cfab95c54fb85f33aa8a6b2653faf1527d08ce57114be46
+F ext/fiddle/fiddle.in.html ca27f4b0f0477096e78d8b9b44109c234d9305531ab63ecd559a739bdea0b11c
+F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf
+F ext/fiddle/module-post.js 3d1a368312c598f73eb5d1d715c464ca473d491ad5df4d0636fbcf91a74817a9
+F ext/fiddle/module-pre.js a7b046c0f764b100a5bedd3880bece8e6fb5908fb73cb91fb7a9b692bc938862
F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e
F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b
F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea
@@ -554,7 +559,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 cc3e19b2d2eefbadc4139b016c097d6478eae01d14eca993368ee5cff8820fff
F src/sqlite.h.in d15c307939039086adca159dd340a94b79b69827e74c6d661f343eeeaefba896
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h a988810c9b21c0dc36dc7a62735012339dc76fc7ab448fb0792721d30eacb69d
@@ -1954,8 +1959,9 @@ 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
+P f7e1ceb5b59a876cfd04a8aac0ee2b322c970555b9c361b4953d711ef6596e37 56b82ae806c61b95e62042ca70ed952ce01832b02da55c2b315f9201989514ab
+R f9801562283af03ef889fbffa87ba13d
+T +closed 56b82ae806c61b95e62042ca70ed952ce01832b02da55c2b315f9201989514ab
U drh
-Z 99cc78493b68b2d09cacc03a9e60389c
+Z 5ac79eb27823f3c1a240f3c2dd67e98a
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index e1be3622c7..4e55b2ea8e 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-f7e1ceb5b59a876cfd04a8aac0ee2b322c970555b9c361b4953d711ef6596e37
\ No newline at end of file
+58585f01aa4747d3a09771fb462066bd037914f435ff04fa16ed9b0571e7912a
\ No newline at end of file
diff --git a/src/shell.c.in b/src/shell.c.in
index cace8bf2f4..9a0859293b 100644
--- a/src/shell.c.in
+++ b/src/shell.c.in
@@ -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
@@ -798,7 +809,7 @@ static void freeText(ShellText *p){
** If the third argument, quote, is not '\0', then it is used as a
** quote character for zAppend.
*/
-static void appendText(ShellText *p, char const *zAppend, char quote){
+static void appendText(ShellText *p, const char *zAppend, char quote){
int len;
int i;
int nAppend = strlen30(zAppend);
@@ -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 */
+ const char * zPos; /* Cursor pos into zInput */
+ } wasm;
+#endif
};
+#ifdef SQLITE_SHELL_WASM_MODE
+static ShellState shellState;
+#endif
+
/* Allowed values for ShellState.autoEQP
*/
@@ -4218,13 +4241,14 @@ static int run_schema_dump_query(
** Text of help messages.
**
** The help text for each individual command begins with a line that starts
-** with ".". Subsequent lines are supplimental information.
+** with ".". Subsequent lines are supplemental information.
**
** There must be two or more spaces between the end of the command and the
** start of the description of what that command does.
*/
static const char *(azHelp[]) = {
-#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE)
+#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) \
+ && !defined(SQLITE_SHELL_WASM_MODE)
".archive ... Manage SQL archives",
" Each command must have exactly one of the following options:",
" -c, --create Create a new archive",
@@ -4250,10 +4274,12 @@ static const char *(azHelp[]) = {
#ifndef SQLITE_OMIT_AUTHORIZATION
".auth ON|OFF Show authorizer callbacks",
#endif
+#ifndef SQLITE_SHELL_WASM_MODE
".backup ?DB? FILE Backup DB (default \"main\") to FILE",
" Options:",
" --append Use the appendvfs",
" --async Write to FILE without journal and fsync()",
+#endif
".bail on|off Stop after hitting an error. Default OFF",
".binary on|off Turn binary output on or off. Default OFF",
".cd DIRECTORY Change the working directory to DIRECTORY",
@@ -4280,9 +4306,13 @@ static const char *(azHelp[]) = {
" trace Like \"full\" but enable \"PRAGMA vdbe_trace\"",
#endif
" trigger Like \"full\" but also show trigger bytecode",
+#ifndef SQLITE_SHELL_WASM_MODE
".excel Display the output of next command in spreadsheet",
" --bom Put a UTF8 byte-order mark on intermediate file",
+#endif
+#ifndef SQLITE_SHELL_WASM_MODE
".exit ?CODE? Exit this program with return-code CODE",
+#endif
".expert EXPERIMENTAL. Suggest indexes for queries",
".explain ?on|off|auto? Change the EXPLAIN formatting mode. Default: auto",
".filectrl CMD ... Run various sqlite3_file_control() operations",
@@ -4291,6 +4321,7 @@ static const char *(azHelp[]) = {
".fullschema ?--indent? Show schema and the content of sqlite_stat tables",
".headers on|off Turn display of headers on or off",
".help ?-all? ?PATTERN? Show help text for PATTERN",
+#ifndef SQLITE_SHELL_WASM_MODE
".import FILE TABLE Import data from FILE into TABLE",
" Options:",
" --ascii Use \\037 and \\036 as column and row separators",
@@ -4305,6 +4336,7 @@ static const char *(azHelp[]) = {
" from the \".mode\" output mode",
" * If FILE begins with \"|\" then it is a command that generates the",
" input text.",
+#endif
#ifndef SQLITE_OMIT_TEST_CONTROL
".imposter INDEX TABLE Create imposter table TABLE on index INDEX",
#endif
@@ -4318,10 +4350,12 @@ static const char *(azHelp[]) = {
".lint OPTIONS Report potential schema issues.",
" Options:",
" fkey-indexes Find missing foreign key indexes",
-#ifndef SQLITE_OMIT_LOAD_EXTENSION
+#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_WASM_MODE)
".load FILE ?ENTRY? Load an extension library",
#endif
+#ifndef SQLITE_SHELL_WASM_MODE
".log FILE|off Turn logging on or off. FILE can be stderr/stdout",
+#endif
".mode MODE ?OPTIONS? Set output mode",
" MODE is one of:",
" ascii Columns/rows delimited by 0x1F and 0x1E",
@@ -4348,6 +4382,7 @@ static const char *(azHelp[]) = {
" TABLE The name of SQL table used for \"insert\" mode",
".nonce STRING Suspend safe mode for one command if nonce matches",
".nullvalue STRING Use STRING in place of NULL values",
+#ifndef SQLITE_SHELL_WASM_MODE
".once ?OPTIONS? ?FILE? Output for the next SQL command only to FILE",
" If FILE begins with '|' then open as a pipe",
" --bom Put a UTF8 byte-order mark at the beginning",
@@ -4356,6 +4391,7 @@ static const char *(azHelp[]) = {
".open ?OPTIONS? ?FILE? Close existing database and reopen FILE",
" Options:",
" --append Use appendvfs to append database to the end of FILE",
+#endif
#ifndef SQLITE_OMIT_DESERIALIZE
" --deserialize Load into memory using sqlite3_deserialize()",
" --hexdb Load the output of \"dbtotxt\" as an in-memory db",
@@ -4387,9 +4423,11 @@ static const char *(azHelp[]) = {
" --reset Reset the count for each input and interrupt",
#endif
".prompt MAIN CONTINUE Replace the standard prompts",
+#ifndef SQLITE_SHELL_WASM_MODE
".quit Exit this program",
".read FILE Read input from FILE or command output",
" If FILE begins with \"|\", it is a command that generates the input.",
+#endif
#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB)
".recover Recover as much data as possible from corrupt db.",
" --freelist-corrupt Assume the freelist is corrupt",
@@ -4398,8 +4436,10 @@ static const char *(azHelp[]) = {
" --no-rowids Do not attempt to recover rowid values",
" that are not also INTEGER PRIMARY KEYs",
#endif
+#ifndef SQLITE_SHELL_WASM_MODE
".restore ?DB? FILE Restore content of DB (default \"main\") from FILE",
".save ?OPTIONS? FILE Write database to FILE (an alias for .backup ...)",
+#endif
".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off",
".schema ?PATTERN? Show the CREATE statements matching PATTERN",
" Options:",
@@ -4433,7 +4473,7 @@ static const char *(azHelp[]) = {
" --sha3-384 Use the sha3-384 algorithm",
" --sha3-512 Use the sha3-512 algorithm",
" Any other argument is a LIKE pattern for tables to hash",
-#ifndef SQLITE_NOHAVE_SYSTEM
+#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE)
".shell CMD ARGS... Run CMD ARGS... in a system shell",
#endif
".show Show the current values for various settings",
@@ -4442,11 +4482,13 @@ static const char *(azHelp[]) = {
" on Turn on automatic stat display",
" stmt Show statement stats",
" vmstep Show the virtual machine step count only",
-#ifndef SQLITE_NOHAVE_SYSTEM
+#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE)
".system CMD ARGS... Run CMD ARGS... in a system shell",
#endif
".tables ?TABLE? List names of tables matching LIKE pattern TABLE",
+#ifndef SQLITE_SHELL_WASM_MODE
".testcase NAME Begin redirecting output to 'testcase-out.txt'",
+#endif
".testctrl CMD ... Run various sqlite3_test_control() operations",
" Run \".testctrl\" with no arguments for details",
".timeout MS Try opening locked tables for MS milliseconds",
@@ -4991,14 +5033,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
@@ -8121,7 +8165,8 @@ static int do_meta_command(char *zLine, ShellState *p){
}else
#endif
-#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)
+#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) \
+ && !defined(SQLITE_SHELL_WASM_MODE)
if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){
open_db(p, 0);
failIfSafeMode(p, "cannot run .archive in safe mode");
@@ -8129,6 +8174,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}else
#endif
+#ifndef SQLITE_SHELL_WASM_MODE
if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0)
|| (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0)
){
@@ -8197,6 +8243,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
close_db(pDest);
}else
+#endif /* !defined(SQLITE_SHELL_WASM_MODE) */
if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){
if( nArg==2 ){
@@ -8578,10 +8625,12 @@ static int do_meta_command(char *zLine, ShellState *p){
}
}else
+#ifndef SQLITE_SHELL_WASM_MODE
if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){
if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc);
rc = 2;
}else
+#endif
/* The ".explain" command is automatic now. It is largely pointless. It
** retained purely for backwards compatibility */
@@ -8836,6 +8885,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
}else
+#ifndef SQLITE_SHELL_WASM_MODE
if( c=='i' && strncmp(azArg[0], "import", n)==0 ){
char *zTable = 0; /* Insert data into this table */
char *zSchema = 0; /* within this schema (may default to "main") */
@@ -9126,6 +9176,7 @@ static int do_meta_command(char *zLine, ShellState *p){
sCtx.nRow, sCtx.nErr, sCtx.nLine-1);
}
}else
+#endif /* !defined(SQLITE_SHELL_WASM_MODE) */
#ifndef SQLITE_UNTESTABLE
if( c=='i' && strncmp(azArg[0], "imposter", n)==0 ){
@@ -9315,7 +9366,7 @@ static int do_meta_command(char *zLine, ShellState *p){
lintDotCommand(p, azArg, nArg);
}else
-#ifndef SQLITE_OMIT_LOAD_EXTENSION
+#if !defined(SQLITE_OMIT_LOAD_EXTENSION) && !defined(SQLITE_SHELL_WASM_MODE)
if( c=='l' && strncmp(azArg[0], "load", n)==0 ){
const char *zFile, *zProc;
char *zErrMsg = 0;
@@ -9337,6 +9388,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}else
#endif
+#ifndef SQLITE_SHELL_WASM_MODE
if( c=='l' && strncmp(azArg[0], "log", n)==0 ){
failIfSafeMode(p, "cannot run .log in safe mode");
if( nArg!=2 ){
@@ -9348,6 +9400,7 @@ static int do_meta_command(char *zLine, ShellState *p){
p->pLog = output_file_open(zFile, 0);
}
}else
+#endif
if( c=='m' && strncmp(azArg[0], "mode", n)==0 ){
const char *zMode = 0;
@@ -9583,6 +9636,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
}else
+#ifndef SQLITE_SHELL_WASM_MODE
if( (c=='o'
&& (strncmp(azArg[0], "output", n)==0||strncmp(azArg[0], "once", n)==0))
|| (c=='e' && n==5 && strcmp(azArg[0],"excel")==0)
@@ -9698,6 +9752,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
sqlite3_free(zFile);
}else
+#endif /* !defined(SQLITE_SHELL_WASM_MODE) */
if( c=='p' && n>=3 && strncmp(azArg[0], "parameter", n)==0 ){
open_db(p,0);
@@ -9867,10 +9922,13 @@ static int do_meta_command(char *zLine, ShellState *p){
}
}else
+#ifndef SQLITE_SHELL_WASM_MODE
if( c=='q' && strncmp(azArg[0], "quit", n)==0 ){
rc = 2;
}else
+#endif
+#ifndef SQLITE_SHELL_WASM_MODE
if( c=='r' && n>=3 && strncmp(azArg[0], "read", n)==0 ){
FILE *inSaved = p->in;
int savedLineno = p->lineno;
@@ -9905,7 +9963,9 @@ static int do_meta_command(char *zLine, ShellState *p){
p->in = inSaved;
p->lineno = savedLineno;
}else
+#endif /* !defined(SQLITE_SHELL_WASM_MODE) */
+#ifndef SQLITE_SHELL_WASM_MODE
if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){
const char *zSrcFile;
const char *zDb;
@@ -9957,6 +10017,7 @@ static int do_meta_command(char *zLine, ShellState *p){
}
close_db(pSrc);
}else
+#endif /* !defined(SQLITE_SHELL_WASM_MODE) */
if( c=='s' && strncmp(azArg[0], "scanstats", n)==0 ){
if( nArg==2 ){
@@ -10582,7 +10643,7 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_free(zSql);
}else
-#ifndef SQLITE_NOHAVE_SYSTEM
+#if !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE)
if( c=='s'
&& (strncmp(azArg[0], "shell", n)==0 || strncmp(azArg[0],"system",n)==0)
){
@@ -10603,7 +10664,7 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_free(zCmd);
if( x ) raw_printf(stderr, "System command returns %d\n", x);
}else
-#endif /* !defined(SQLITE_NOHAVE_SYSTEM) */
+#endif /* !defined(SQLITE_NOHAVE_SYSTEM) && !defined(SQLITE_SHELL_WASM_MODE) */
if( c=='s' && strncmp(azArg[0], "show", n)==0 ){
static const char *azBool[] = { "off", "on", "trigger", "full"};
@@ -10783,6 +10844,7 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_free(azResult);
}else
+#ifndef SQLITE_SHELL_WASM_MODE
/* Begin redirecting output to the file "testcase-out.txt" */
if( c=='t' && strcmp(azArg[0],"testcase")==0 ){
output_reset(p);
@@ -10796,6 +10858,7 @@ static int do_meta_command(char *zLine, ShellState *p){
sqlite3_snprintf(sizeof(p->zTestcase), p->zTestcase, "?");
}
}else
+#endif /* !defined(SQLITE_SHELL_WASM_MODE) */
#ifndef SQLITE_UNTESTABLE
if( c=='t' && n>=8 && strncmp(azArg[0], "testctrl", n)==0 ){
@@ -11467,6 +11530,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 +11944,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 +11958,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 +11978,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 +12240,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 +12508,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 +12543,47 @@ 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(const char * 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 */