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)
--- /dev/null
+default:
+ make -C ../.. fiddle
+
+clean:
+ make -C ../../ clean-fiddle
--- /dev/null
+<!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, "&");
+ //text = text.replace(/</g, "<");
+ //text = text.replace(/>/g, ">");
+ //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>
--- /dev/null
+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
-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
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
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
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.
-f7e1ceb5b59a876cfd04a8aac0ee2b322c970555b9c361b4953d711ef6596e37
\ No newline at end of file
+af9c21c9e0caf05adac7a9fcde39a9164c89f1c78b767b6fdd74a1405a3d373f
\ No newline at end of file
# 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;
** 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;
}
return zResult;
}
-
+#endif /* !SQLITE_SHELL_WASM_MODE */
/*
** Return the value of a hexadecimal digit. Return -1 if the input
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
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
*/
#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
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
# 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
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;
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") ){
#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
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 ){
(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 */