Skip to content

Commit

Permalink
Merge the changes to support the "fiddle" extension.
Browse files Browse the repository at this point in the history
FossilOrigin-Name: 58585f01aa4747d3a09771fb462066bd037914f435ff04fa16ed9b0571e7912a
  • Loading branch information
drh committed May 19, 2022
2 parents b8b2d9c + d59ac28 commit 9d023c2
Show file tree
Hide file tree
Showing 9 changed files with 903 additions and 25 deletions.
33 changes: 33 additions & 0 deletions Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -1512,3 +1512,36 @@ 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_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_opt = -O0
#emcc_opt = -O1
#emcc_opt = -O2
#emcc_opt = -O3
emcc_opt = -Oz
emcc_flags = $(emcc_opt) $(SHELL_OPT) \
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap \
-sEXPORTED_FUNCTIONS=_fiddle_exec \
-sEXIT_RUNTIME=1 \
--pre-js $(fiddle_dir)/module-pre.js \
--post-js $(fiddle_dir)/module-post.js \
--shell-file $(fiddle_dir)/fiddle.in.html \
$(fiddle_cflags)
# $(fiddle_cflags) is intended to be passed to make via the CLI in
# order to override, e.g., -Ox for one-off builds.
$(fiddle_html): Makefile sqlite3.c shell.c \
$(fiddle_dir)/fiddle.in.html \
$(fiddle_dir)/module-pre.js $(fiddle_dir)/module-post.js
emcc -o $@ $(emcc_flags) sqlite3.c shell.c
fiddle: $(fiddle_html)
7 changes: 7 additions & 0 deletions ext/fiddle/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This makefile exists primarily to simplify/speed up development from
# emacs. It is not part of the canonical build process.
default:
make -C ../.. fiddle -e emcc_opt=-O0

clean:
make -C ../../ clean-fiddle
239 changes: 239 additions & 0 deletions ext/fiddle/fiddle.in.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
<!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>
<!-- script src="jqterm/jqterm-bundle.min.js"></script>
<link rel="stylesheet" href="jqterm/jquery.terminal.min.css"/ -->
<style>
/* emcscript-related styling, used during the intialization phase... */
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
#spinner { overflow: visible; }
#spinner > * {
margin-top: 1em;
}
.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);}
}

/* The following styles are for app-level use. */

textarea {
font-family: monospace;
flex: 1 1 auto;
}
header {
font-size: 130%;
font-weight: bold;
}
#main-wrapper {
display: flex;
flex-direction: column-reverse;
flex: 20 1 auto;
}
#main-wrapper.side-by-side {
flex-direction: row-reverse;
}
#main-wrapper.swapio {
flex-direction: column;
}
#main-wrapper.side-by-side.swapio {
flex-direction: row;
}
.ta-wrapper{
display: flex;
flex-direction: column;
align-items: stretch;
margin: 0 0.25em;
flex: 1 1 auto;
}
.ta-wrapper.input { flex: 10 1 auto; }
.ta-wrapper.output { flex: 20 1 auto; }
.ta-wrapper textarea {
font-size: 110%;
filter: invert(100%);
flex: 10 1 auto;
}
/*#main-wrapper:not(.side-by-side) .ta-wrapper.input {
flex: 5 1 auto;
}*/
.button-bar {
display: flex;
justify-content: center;
flex: 0 1 auto;
}
.button-bar button {
margin: 0.25em 1em;
}
label[for] {
cursor: pointer;
}
fieldset {
border-radius: 0.5em;
}
.error {
color: red;
background-color: yellow;
}
.hidden {
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
display: none !important;
}
.initially-hidden {
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
display: none !important;
}
fieldset.options {
font-size: 75%;
}
fieldset > legend {
padding: 0 0.5em;
}
span.labeled-input {
padding: 0.25em;
margin: 0.25em 0.5em;
border-radius: 0.25em;
white-space: nowrap;
background: #0002;
}
#notes-caveats {
border-top: 1px dotted;
padding-top: 0.25em;
margin-top: 0.5em;
}
.center { text-align: center; }

body.terminal-mode {
max-height: calc(100% - 2em);
display: flex;
flex-direction: column;
align-items: stretch;
}
#jqterminal {
}
.app-view {
flex: 20 1 auto;
}
#titlebar {
display: flex;
justify-content: space-between;
margin-bottom: 0.5em;
}
#view-split {
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<header id='titlebar'><span>sqlite3 fiddle</span></header>
<figure id="spinner">
<div class="spinner"></div>
<div class='center'><strong>Initializing app...</strong></div>
<div class='center'>
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.
</div>
</figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden='1'></progress>
</div>

<div id='jqterminal' class='app-view hidden initially-hidden'>
This is a placeholder for a terminal-like view.
</div>

<div id='view-split' class='app-view initially-hidden'>
<fieldset class='options'>
<legend>Options</legend>
<div class=''>
<span class='labeled-input'>
<input type='checkbox' id='opt-cb-sbs'
data-csstgt='#main-wrapper'
data-cssclass='side-by-side'
data-config='sideBySide'>
<label for='opt-cb-sbs'>Side-by-side</label>
</span>
<span class='labeled-input'>
<input type='checkbox' id='opt-cb-swapio'
data-csstgt='#main-wrapper'
data-cssclass='swapio'
data-config='swapInOut'>
<label for='opt-cb-swapio'>Swap in/out</label>
</span>
<span class='labeled-input'>
<input type='checkbox' id='opt-cb-autoscroll'
data-config='autoScrollOutput'>
<label for='opt-cb-autoscroll'>Auto-scroll output</label>
</span>
<span class='labeled-input'>
<input type='checkbox' id='opt-cb-autoclear'
data-config='autoClearOutput'>
<label for='opt-cb-autoclear'>Auto-clear output</label>
</span>
</div>
</fieldset>
<div id='main-wrapper' class=''>
<div class='ta-wrapper input'>
<textarea id="input"
placeholder="Shell input. Ctrl-enter/shift-enter runs it.">
-- Use ctrl-enter or shift-enter to execute SQL
.nullvalue 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'>Run</button>
<button id='btn-clear'>Clear</button>
<button data-cmd='.help'>Help</button>
</div>
</div>
<div class='ta-wrapper output'>
<textarea id="output" readonly
placeholder="Shell output."></textarea>
<div class='button-bar'>
<button id='btn-clear-output'>Clear</button>
</div>
</div>
</div>
</div> <!-- .app-view -->
<!-- Maintenance notes:
- emscripten module init goes is in fiddle-pre.js and gets
prepended to the generated script code.
- App-specific code is in fiddle-post.js and gets appended to
the generated script code.
- The following placeholder (if you're reading this in the
input template file) gets replaced by a generated
amalgamation of: module-pre.js, emcc-generated bootstrapping
code, and module-post.js.
-->
{{{ SCRIPT }}}
</body>
</html>
94 changes: 94 additions & 0 deletions ext/fiddle/index.md
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 9d023c2

Please sign in to comment.