From: stephan Date: Tue, 24 May 2022 14:36:45 +0000 (+0000) Subject: fiddle: lots of generic refactoring, restructuring, and cleanup in the higher-level... X-Git-Tag: version-3.39.0~105 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=400ee2ecef4e56703bcbb0493947a7fa88b572fe;p=thirdparty%2Fsqlite.git fiddle: lots of generic refactoring, restructuring, and cleanup in the higher-level code. Added push-fiddle ext/fiddle/Makefile target to push the fiddle app to a remote server via rsync. FossilOrigin-Name: ed19fef3459499abb0a4a010f368b4576d6e068d930c8480446ea677ac87c1c1 --- diff --git a/ext/fiddle/Makefile b/ext/fiddle/Makefile index 73107d3bde..16f68132ad 100644 --- a/ext/fiddle/Makefile +++ b/ext/fiddle/Makefile @@ -1,10 +1,31 @@ -# This makefile exists primarily to simplify/speed up development from -# emacs. It is not part of the canonical build process. +# This GNU makefile exists primarily to simplify/speed up development +# from emacs. It is not part of the canonical build process. default: make -C ../.. wasm -e emcc_opt=-O0 clean: make -C ../../ clean-wasm -push-demo: - rsync -va fiddle*.js fiddle*.wasm fiddle.html *.css wh2:www/wh/sqlite3/. +demo_files = emscripten.css fiddle.html \ + fiddle.js fiddle-module.js \ + fiddle-module.wasm fiddle-worker.js + +# demo_target is the remote destination for the fiddle app. It +# must be a [user@]HOST:/path for rsync. +# Note that the target "should probably" contain a symlink of +# index.html -> fiddle.html. +demo_target ?= +ifeq (,$(demo_target)) +ifneq (,$(wildcard /home/stephan)) + demo_target = wh2:www/wh/sqlite3/. +else ifneq (,$(wildcard /home/drh)) + #demo_target = if appropriate, add that user@host:/path here +endif +endif + +push-fiddle: $(demo_files) + @if [ x = "x$(demo_target)" ]; then \ + echo "demo_target must be a [user@]HOST:/path for rsync"; \ + exit 1; \ + fi + rsync -va $(demo_files) $(demo_target) diff --git a/ext/fiddle/fiddle.js b/ext/fiddle/fiddle.js index 2353509da7..5590279d0c 100644 --- a/ext/fiddle/fiddle.js +++ b/ext/fiddle/fiddle.js @@ -132,35 +132,45 @@ return (arguments.length>1 ? arguments[0] : document) .querySelector(arguments[arguments.length-1]); }; - - const statusElement = E('#module-status'); - const progressElement = E('#module-progress'); - const spinnerElement = E('#module-spinner'); + /** Handles status updates from the Module object. */ SF.addMsgHandler('module', function f(ev){ ev = ev.data; if('status'!==ev.type){ console.warn("Unexpected module-type message:",ev); return; } + if(!f.ui){ + f.ui = { + status: E('#module-status'), + progress: E('#module-progress'), + spinner: E('#module-spinner') + }; + } const msg = ev.data; - progressElement.value = msg.step; - progressElement.max = msg.step + 1/*we don't know how many steps to expect*/; + if(f.ui.progres){ + progress.value = msg.step; + progress.max = msg.step + 1/*we don't know how many steps to expect*/; + } if(1==msg.step){ - progressElement.hidden = false; - spinnerElement.hidden = false; + f.ui.progress.classList.remove('hidden'); + f.ui.spinner.classList.remove('hidden'); } if(msg.text){ - statusElement.classList.remove('hidden'); - statusElement.innerText = msg.text; + f.ui.status.classList.remove('hidden'); + f.ui.status.innerText = msg.text; }else{ - progressElement.remove(); - spinnerElement.remove(); - statusElement.classList.add('hidden'); + if(f.ui.progress){ + f.ui.progress.remove(); + f.ui.spinner.remove(); + delete f.ui.progress; + delete f.ui.spinner; + } + f.ui.status.classList.add('hidden'); /* The module can post messages about fatal problems, e.g. an exit() being triggered or assertion failure, after the last "load" message has arrived, so - leave the statusElement and message listener intact. */ + leave f.ui.status and message listener intact. */ } }); @@ -209,9 +219,9 @@ },false); /** To be called immediately before work is sent to the - worker. Updates some UI elements. The 'working'/'end' + worker. Updates some UI elements. The 'working'/'end' event will apply the inverse, undoing the bits this - function does. This impl is not in the 'working'/'start' + function does. This impl is not in the 'working'/'start' event handler because that event is given to us asynchronously _after_ we need to have performed this work. @@ -242,13 +252,15 @@ }; SF.addMsgHandler('working',function f(ev){ - if('start' === ev.data){ - /* See notes in preStartWork(). */ - }else if('end' === ev.data){ - preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig; - btnShellExec.innerText = preStartWork._.btnLabel; - btnShellExec.removeAttribute('disabled'); + switch(ev.data){ + case 'start': /* See notes in preStartWork(). */; return; + case 'end': + preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig; + btnShellExec.innerText = preStartWork._.btnLabel; + btnShellExec.removeAttribute('disabled'); + return; } + console.warn("Unhandled 'working' event:",ev.data); }); /* For each checkbox with data-csstgt, set up a handler which diff --git a/ext/fiddle/sqlite3-api.js b/ext/fiddle/sqlite3-api.js index 90b8de798b..e97e7c1837 100644 --- a/ext/fiddle/sqlite3-api.js +++ b/ext/fiddle/sqlite3-api.js @@ -17,31 +17,33 @@ Note that this file is not named sqlite3.js because that file gets generated by emscripten as the JS-glue counterpart of sqlite3.wasm. - This code installs an object named self.sqlite3, where self is - expected to be either the global window or Worker object: + This code installs an object named self.Module.sqlite3, where self + is expected to be either the global window or Worker object and + Module is the object set up by the emscripten infrastructure. The + sqlite3 object looks like: - self.sqlite3 = { - api: core WASM bindings of sqlite3 APIs, + { + api: bindings for much of the core sqlite3 APIs, SQLite3: high-level OO API wrapper - }; + } The way we export this module is not _really_ modern-JS-friendly - because it exports a global symbol (which is admittedly not - ideal). Exporting it "cleanly," without introducing any global-scope - symbols, requires using a module loader in all client code. As there - are several different approaches, none of which this developer is - currently truly familiar with, the current approach will have to do - for the time being. - - Because using the low-level API properly requires some degree of - WASM-related magic, it is not recommended that that API be used - as-is in client-level code. Rather, client code should use the - higher-level OO API or write a custom wrapper on top of the - lower-level API. - - This file installs namespace.sqlite3, where namespace is `self`, - meaning either the global window or worker, depending on where this - is loaded from. + because it exports/relies on a global symbol (which is admittedly + not ideal). Exporting it "cleanly," without introducing any + global-scope symbols, requires using a module loader in all client + code. As there are several different approaches, none of which this + developer is currently truly familiar with, the current approach + will have to do for the time being. + + Because using certain parts of the low-level API properly requires + some degree of WASM-related magic, it is not recommended that that + API be used as-is in client-level code. Rather, client code should + use the higher-level OO API or write a custom wrapper on top of the + lower-level API. In short, using any C-style APIs which take + pointers-to-pointer arguments require WASM-specific interfaces + installed by emcscripten-generated code. Those which take or return + only integers, doubles, strings, or "plain" pointers to db or + statement objects can be used in a straightforward manner. # Goals and Non-goals of this API @@ -60,13 +62,16 @@ can be interacted with, but keeping the DB operations out of the UI thread is generally desirable. + - Insofar as possible, support client-side storage using JS + filesystem APIs. As of this writing, such things are still very + much TODO. Non-goals: - - As WASM is a web-based technology and UTF-8 is the King of - Encodings in that realm, there are no plans to support the - UTF16-related APIs will not be. They would add a complication to - the bindings for no appreciable benefit. + - As WASM is a web-centric technology and UTF-8 is the King of + Encodings in that realm, there are no current plans to support the + UTF16-related APIs. They would add a complication to the bindings + for no appreciable benefit. - Supporting old or niche-market platforms. WASM is built for a modern web and requires modern platforms. @@ -247,8 +252,6 @@ throw new Error(Array.prototype.join.call(arguments, ' ')); }; - const S/*convenience alias*/ = api; - /** The DB class wraps a sqlite3 db handle. */ @@ -258,7 +261,7 @@ toss("TODO: support blob image of db here."); } setValue(pPtrArg, 0, "i32"); - this.checkRc(S.sqlite3_open(name, pPtrArg)); + this.checkRc(api.sqlite3_open(name, pPtrArg)); this._pDb = getValue(pPtrArg, "i32"); this.filename = name; this._statements = {/*map of open Stmt _pointers_ to Stmt*/}; @@ -291,8 +294,8 @@ } this.db = arguments[0]; this._pStmt = arguments[1]; - this.columnCount = S.sqlite3_column_count(this._pStmt); - this.parameterCount = S.sqlite3_bind_parameter_count(this._pStmt); + this.columnCount = api.sqlite3_column_count(this._pStmt); + this.parameterCount = api.sqlite3_bind_parameter_count(this._pStmt); this._allocs = [/*list of alloc'd memory blocks for bind() values*/] }; @@ -370,7 +373,7 @@ checkRc: function(sqliteResultCode){ if(!sqliteResultCode) return this; toss("sqlite result code",sqliteResultCode+":", - S.sqlite3_errmsg(this._pDb) || "Unknown db error."); + api.sqlite3_errmsg(this._pDb) || "Unknown db error."); }, /** Finalizes all open statements and closes this database @@ -388,7 +391,7 @@ Object.values(this._udfs).forEach(Module.removeFunction); delete this._udfs; delete this._statements; - S.sqlite3_close_v2(this._pDb); + api.sqlite3_close_v2(this._pDb); delete this._pDb; } }, @@ -401,7 +404,7 @@ a name of `main`. */ fileName: function(dbName){ - return S.sqlite3_db_filename(affirmDbOpen(this)._pDb, dbName||"main"); + return api.sqlite3_db_filename(affirmDbOpen(this)._pDb, dbName||"main"); }, /** Compiles the given SQL and returns a prepared Stmt. This is @@ -410,7 +413,7 @@ prepare: function(sql){ affirmDbOpen(this); setValue(pPtrArg,0,"i32"); - this.checkRc(S.sqlite3_prepare_v2(this._pDb, sql, -1, pPtrArg, null)); + this.checkRc(api.sqlite3_prepare_v2(this._pDb, sql, -1, pPtrArg, null)); const pStmt = getValue(pPtrArg, "i32"); if(!pStmt) toss("Empty SQL is not permitted."); const stmt = new Stmt(this, pStmt, BindTypes); @@ -533,14 +536,14 @@ while(getValue(pSql, "i8")){ setValue(pPtrArg, 0, "i32"); setValue(pzTail, 0, "i32"); - this.checkRc(S.sqlite3_prepare_v2_sqlptr( + this.checkRc(api.sqlite3_prepare_v2_sqlptr( this._pDb, pSql, -1, pPtrArg, pzTail )); const pStmt = getValue(pPtrArg, "i32"); pSql = getValue(pzTail, "i32"); if(!pStmt) continue; if(opt.saveSql){ - opt.saveSql.push(S.sqlite3_sql(pStmt).trim()); + opt.saveSql.push(api.sqlite3_sql(pStmt).trim()); } stmt = new Stmt(this, pStmt, BindTypes); if(bind && stmt.parameterCount){ @@ -653,18 +656,18 @@ const tgt = []; for(i = 0; i < argc; ++i){ pVal = getValue(pArgv + (4 * i), "i32"); - valType = S.sqlite3_value_type(pVal); + valType = api.sqlite3_value_type(pVal); switch(valType){ - case S.SQLITE_INTEGER: - case S.SQLITE_FLOAT: - arg = S.sqlite3_value_double(pVal); + case api.SQLITE_INTEGER: + case api.SQLITE_FLOAT: + arg = api.sqlite3_value_double(pVal); break; case SQLITE_TEXT: - arg = S.sqlite3_value_text(pVal); + arg = api.sqlite3_value_text(pVal); break; case SQLITE_BLOB:{ - const n = S.sqlite3_value_bytes(ptr); - const pBlob = S.sqlite3_value_blob(ptr); + const n = api.sqlite3_value_bytes(ptr); + const pBlob = api.sqlite3_value_blob(ptr); arg = new Uint8Array(n); let i; for(i = 0; i < n; ++i) arg[i] = HEAP8[pBlob+i]; @@ -680,25 +683,25 @@ f._setResult = function(pCx, val){ switch(typeof val) { case 'boolean': - S.sqlite3_result_int(pCx, val ? 1 : 0); + api.sqlite3_result_int(pCx, val ? 1 : 0); break; case 'number': { (isInt32(val) - ? S.sqlite3_result_int - : S.sqlite3_result_double)(pCx, val); + ? api.sqlite3_result_int + : api.sqlite3_result_double)(pCx, val); break; } case 'string': - S.sqlite3_result_text(pCx, val, -1, + api.sqlite3_result_text(pCx, val, -1, -1/*==SQLITE_TRANSIENT*/); break; case 'object': if(null===val) { - S.sqlite3_result_null(pCx); + api.sqlite3_result_null(pCx); break; }else if(undefined!==val.length){ const pBlob = Module.allocate(val, ALLOC_NORMAL); - S.sqlite3_result_blob(pCx, pBlob, val.length, -1/*==SQLITE_TRANSIENT*/); + api.sqlite3_result_blob(pCx, pBlob, val.length, -1/*==SQLITE_TRANSIENT*/); Module._free(blobptr); break; } @@ -712,20 +715,20 @@ try{ f._setResult(pCx, callback.apply(null, f._extractArgs(argc, pArgv))); }catch(e){ - S.sqlite3_result_error(pCx, e.message, -1); + api.sqlite3_result_error(pCx, e.message, -1); } }; const pUdf = Module.addFunction(wrapper, "viii"); let fFlags = 0; - if(getOwnOption(opt, 'deterministic')) fFlags |= S.SQLITE_DETERMINISTIC; - if(getOwnOption(opt, 'directOnly')) fFlags |= S.SQLITE_DIRECTONLY; - if(getOwnOption(opt, 'innocuous')) fFlags |= S.SQLITE_INNOCUOUS; + if(getOwnOption(opt, 'deterministic')) fFlags |= api.SQLITE_DETERMINISTIC; + if(getOwnOption(opt, 'directOnly')) fFlags |= api.SQLITE_DIRECTONLY; + if(getOwnOption(opt, 'innocuous')) fFlags |= api.SQLITE_INNOCUOUS; name = name.toLowerCase(); try { - this.checkRc(S.sqlite3_create_function_v2( + this.checkRc(api.sqlite3_create_function_v2( this._pDb, name, (opt.hasOwnProperty('arity') ? +opt.arity : callback.length), - S.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf, + api.SQLITE_UTF8 | fFlags, null/*pApp*/, pUdf, null/*xStep*/, null/*xFinal*/, null/*xDestroy*/)); }catch(e){ Module.removeFunction(pUdf); @@ -802,7 +805,7 @@ */ const affirmParamIndex = function(stmt,key){ const n = ('number'===typeof key) - ? key : S.sqlite3_bind_parameter_index(stmt._pStmt, key); + ? key : api.sqlite3_bind_parameter_index(stmt._pStmt, key); if(0===n || (n===key && (n!==(n|0)/*floating point*/))){ toss("Invalid bind() parameter name: "+key); } @@ -853,7 +856,7 @@ const bytes = intArrayFromString(val,true); const pStr = Module.allocate(bytes, ALLOC_NORMAL); stmt._allocs.push(pStr); - const func = asBlob ? S.sqlite3_bind_blob : S.sqlite3_bind_text; + const func = asBlob ? api.sqlite3_bind_blob : api.sqlite3_bind_text; return func(stmt._pStmt, ndx, pStr, bytes.length, 0); } }; @@ -863,7 +866,7 @@ let rc = 0; switch((null===val || undefined===val) ? BindTypes.null : bindType){ case BindTypes.null: - rc = S.sqlite3_bind_null(stmt._pStmt, ndx); + rc = api.sqlite3_bind_null(stmt._pStmt, ndx); break; case BindTypes.string:{ rc = f._.string(stmt, ndx, val, false); @@ -871,15 +874,15 @@ } case BindTypes.number: { const m = (isInt32(val) - ? S.sqlite3_bind_int + ? api.sqlite3_bind_int /*It's illegal to bind a 64-bit int from here*/ - : S.sqlite3_bind_double); + : api.sqlite3_bind_double); rc = m(stmt._pStmt, ndx, val); break; } case BindTypes.boolean: - rc = S.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0); + rc = api.sqlite3_bind_int(stmt._pStmt, ndx, val ? 1 : 0); break; case BindTypes.blob: { if('string'===typeof val){ @@ -892,7 +895,7 @@ } const pBlob = Module.allocate(val, ALLOC_NORMAL); stmt._allocs.push(pBlob); - rc = S.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0); + rc = api.sqlite3_bind_blob(stmt._pStmt, ndx, pBlob, len, 0); } } default: toss("Unsupported bind() argument type."); @@ -923,7 +926,7 @@ affirmUnlocked(this,'finalize()'); freeBindMemory(this); delete this.db._statements[this._pStmt]; - S.sqlite3_finalize(this._pStmt); + api.sqlite3_finalize(this._pStmt); delete this.columnCount; delete this.parameterCount; delete this._pStmt; @@ -937,7 +940,7 @@ freeBindMemory( affirmUnlocked(affirmStmtOpen(this), 'clearBindings()') ); - S.sqlite3_clear_bindings(this._pStmt); + api.sqlite3_clear_bindings(this._pStmt); this._mayGet = false; return this; }, @@ -953,7 +956,7 @@ reset: function(alsoClearBinds){ affirmUnlocked(this,'reset()'); if(alsoClearBinds) this.clearBindings(); - S.sqlite3_reset(affirmStmtOpen(this)._pStmt); + api.sqlite3_reset(affirmStmtOpen(this)._pStmt); this._mayGet = false; return this; }, @@ -1098,14 +1101,14 @@ */ step: function(){ affirmUnlocked(this, 'step()'); - const rc = S.sqlite3_step(affirmStmtOpen(this)._pStmt); + const rc = api.sqlite3_step(affirmStmtOpen(this)._pStmt); switch(rc){ - case S.SQLITE_DONE: return this._mayGet = false; - case S.SQLITE_ROW: return this._mayGet = true; + case api.SQLITE_DONE: return this._mayGet = false; + case api.SQLITE_ROW: return this._mayGet = true; default: this._mayGet = false; console.warn("sqlite3_step() rc=",rc,"SQL =", - S.sqlite3_sql(this._pStmt)); + api.sqlite3_sql(this._pStmt)); this.db.checkRc(rc); }; }, @@ -1155,27 +1158,27 @@ }else if(ndx && 'object'===typeof ndx){ let i = 0; while(i32bits */ } - case S.SQLITE_FLOAT: - return S.sqlite3_column_double(this._pStmt, ndx); - case S.SQLITE_TEXT: - return S.sqlite3_column_text(this._pStmt, ndx); - case S.SQLITE_BLOB: { - const n = S.sqlite3_column_bytes(this._pStmt, ndx); - const ptr = S.sqlite3_column_blob(this._pStmt, ndx); + case api.SQLITE_FLOAT: + return api.sqlite3_column_double(this._pStmt, ndx); + case api.SQLITE_TEXT: + return api.sqlite3_column_text(this._pStmt, ndx); + case api.SQLITE_BLOB: { + const n = api.sqlite3_column_bytes(this._pStmt, ndx); + const ptr = api.sqlite3_column_blob(this._pStmt, ndx); const rc = new Uint8Array(n); for(let i = 0; i < n; ++i) rc[i] = HEAP8[ptr + i]; return rc; @@ -1187,16 +1190,16 @@ }, /** Equivalent to get(ndx) but coerces the result to an integer. */ - getInt: function(ndx){return this.get(ndx,S.SQLITE_INTEGER)}, + getInt: function(ndx){return this.get(ndx,api.SQLITE_INTEGER)}, /** Equivalent to get(ndx) but coerces the result to a float. */ - getFloat: function(ndx){return this.get(ndx,S.SQLITE_FLOAT)}, + getFloat: function(ndx){return this.get(ndx,api.SQLITE_FLOAT)}, /** Equivalent to get(ndx) but coerces the result to a string. */ - getString: function(ndx){return this.get(ndx,S.SQLITE_TEXT)}, + getString: function(ndx){return this.get(ndx,api.SQLITE_TEXT)}, /** Equivalent to get(ndx) but coerces the result to a Uint8Array. */ - getBlob: function(ndx){return this.get(ndx,S.SQLITE_BLOB)}, + getBlob: function(ndx){return this.get(ndx,api.SQLITE_BLOB)}, /** A convenience wrapper around get() which fetches the value as a string and then, if it is not null, passes it to @@ -1205,16 +1208,17 @@ string, on the other hand, will trigger an exception. */ getJSON: function(ndx){ - const s = this.get(ndx, S.SQLITE_STRING); + const s = this.get(ndx, api.SQLITE_STRING); return null===s ? s : JSON.parse(s); }, /** Returns the result column name of the given index, or throws if index is out of bounds or this statement has been - finalized. + finalized. This can be used without having run step() + first. */ getColumnName: function(ndx){ - return S.sqlite3_column_name( + return api.sqlite3_column_name( affirmColIndex(affirmStmtOpen(this),ndx)._pStmt, ndx ); }, @@ -1230,7 +1234,7 @@ affirmColIndex(affirmStmtOpen(this),0); if(!tgt) tgt = []; for(let i = 0; i < this.columnCount; ++i){ - tgt.push(S.sqlite3_column_name(this._pStmt, i)); + tgt.push(api.sqlite3_column_name(this._pStmt, i)); } return tgt; }, @@ -1242,7 +1246,7 @@ */ getParamIndex: function(name){ return (affirmStmtOpen(this).parameterCount - ? S.sqlite3_bind_parameter_index(this._pStmt, name) + ? api.sqlite3_bind_parameter_index(this._pStmt, name) : undefined); } }/*Stmt.prototype*/; @@ -1250,7 +1254,7 @@ /** OO binding's namespace. */ const SQLite3 = { version: { - lib: S.sqlite3_libversion(), + lib: api.sqlite3_libversion(), ooApi: "0.0.1" }, DB, @@ -1293,7 +1297,7 @@ } const rc = {}, ov = [0,0]; let i = 0, k; - while((k = S.sqlite3_compileoption_get(i++))){ + while((k = api.sqlite3_compileoption_get(i++))){ f._opt(k,ov); rc[ov[0]] = ov[1]; } @@ -1302,19 +1306,19 @@ else if(Array.isArray(optName)){ const rc = {}; optName.forEach((v)=>{ - rc[v] = S.sqlite3_compileoption_used(v); + rc[v] = api.sqlite3_compileoption_used(v); }); return rc; } else if('object' === typeof optName){ Object.keys(optName).forEach((k)=> { - optName[k] = S.sqlite3_compileoption_used(k); + optName[k] = api.sqlite3_compileoption_used(k); }); return optName; } return ( 'string'===typeof optName - ) ? !!S.sqlite3_compileoption_used(optName) : false; + ) ? !!api.sqlite3_compileoption_used(optName) : false; } }; @@ -1322,4 +1326,4 @@ api: api, SQLite3 }; -})(self/*worker or window*/); +})(self/*worker or window*/.Module); diff --git a/ext/fiddle/testing-common.js b/ext/fiddle/testing-common.js index 2701481289..9a51dfad41 100644 --- a/ext/fiddle/testing-common.js +++ b/ext/fiddle/testing-common.js @@ -26,9 +26,6 @@ }; /* emscripten-related bits... */ - const statusElement = E('#module-status'); - const progressElement = E('#module-progress'); - const spinnerElement = E('#module-spinner'); self.Module = { /* ^^^ cannot declare that const because sqlite3.js (auto-generated) includes a decl for it and runs in this @@ -43,25 +40,33 @@ console.error.apply(console, Array.prototype.slice.call(arguments)); }, setStatus: function f(text){ - if(!f.last) f.last = { time: Date.now(), text: '' }; + if(!f.last){ + f.last = { text: '', step: 0 }; + f.ui = { + status: E('#module-status'), + progress: E('#module-progress'), + spinner: E('#module-spinner') + }; + } 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(f.ui.progress){ + f.ui.progress.value = f.last.step; + f.ui.progress.max = f.last.step + 1; + } + ++f.last.step; + if(text) { + f.ui.status.classList.remove('hidden'); + f.ui.status.innerText = text; + }else{ + if(f.ui.progress){ + f.ui.progress.remove(); + f.ui.spinner.remove(); + delete f.ui.progress; + delete f.ui.spinner; + } + f.ui.status.classList.add('hidden'); } - if(text) statusElement.innerText = text; - else statusElement.remove(); }, totalDependencies: 0, monitorRunDependencies: function(left) { @@ -71,16 +76,33 @@ + '/' + this.totalDependencies + ')') : 'All downloads complete.'); }, - /* Loads sqlite3-api.js and calls the given callback (if - provided), passing it an object which contains the sqlite3 - and SQLite3 modules. Whether this is synchronous or async - depends on whether it's run in the main thread (async) or a - worker (synchronous). */ - loadSqliteAPI: function(callback){ + /** + Loads sqlite3-api.js and calls the given callback (if + provided), passing it an object: + + { + api:sqlite3_c-like API wrapper, + SQLite3: OO wrapper + } + + Whether this is synchronous or async depends on whether + it's run in the main thread (async) or a worker + (synchronous). + + If called after the module has been loaded, it uses a + cached reference, noting that multiple async calls may end + up loading it multiple times. + */ + loadSqliteAPI: function f(callback){ + const namespace = self.Module; + if(namespace.sqlite3){ + if(callback) callback(namespace.sqlite3); + return; + } const theScript = 'sqlite3-api.js'; if(self.importScripts){/*worker*/ importScripts(theScript); - if(callback) callback(self.sqlite3); + if(callback) callback(namespace.sqlite3); }else{/*main thread*/ new Promise((resolve, reject) => { const script = document.createElement('script'); @@ -90,8 +112,7 @@ script.async = true; script.src = theScript; }).then(() => { - if(callback) callback({sqlite3:self.sqlite3, - SQLite3:self.SQLite3}); + if(callback) callback(namespace.sqlite3); }); } } diff --git a/ext/fiddle/testing.css b/ext/fiddle/testing.css new file mode 100644 index 0000000000..f87dbd2cf1 --- /dev/null +++ b/ext/fiddle/testing.css @@ -0,0 +1,31 @@ +textarea { + font-family: monospace; +} +header { + font-size: 130%; + font-weight: bold; +} +.hidden, .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; +} +.center { text-align: center; } +.error { + color: red; + background-color: yellow; +} diff --git a/ext/fiddle/testing1.html b/ext/fiddle/testing1.html index d428f12f65..08a0009c60 100644 --- a/ext/fiddle/testing1.html +++ b/ext/fiddle/testing1.html @@ -4,6 +4,7 @@ + sqlite3-api.js tests diff --git a/ext/fiddle/testing1.js b/ext/fiddle/testing1.js index 5dfc9cd8e9..d35557865d 100644 --- a/ext/fiddle/testing1.js +++ b/ext/fiddle/testing1.js @@ -12,27 +12,15 @@ A basic test script for sqlite3-api.js. */ - -const mainTest1 = function(namespace){ +(function(){ const T = self.SqliteTestUtil; - T.assert(Module._free instanceof Function). - assert(Module.allocate instanceof Function). - assert(Module.addFunction instanceof Function). - assert(Module.removeFunction instanceof Function); - - const S = namespace.sqlite3.api; - const oo = namespace.sqlite3.SQLite3; - console.log("Loaded module:",S.sqlite3_libversion(), - S.sqlite3_sourceid()); - const db = new oo.DB(); const log = console.log.bind(console); - try { + const test1 = function(db,api){ + log("Basic sanity tests..."); T.assert(db._pDb); - log("DB:",db.filename); - log("Build options:",oo.compileOptionUsed()); let st = db.prepare("select 3 as a"); - log("statement =",st); + //log("statement =",st); T.assert(st._pStmt) .assert(!st._mayGet) .assert('a' === st.getColumnName(0)) @@ -43,14 +31,14 @@ const mainTest1 = function(namespace){ .assert(true===st.step()) .assert(3 === st.get(0)) .mustThrow(()=>st.get(1)) - .mustThrow(()=>st.get(0,~S.SQLITE_INTEGER)) - .assert(3 === st.get(0,S.SQLITE_INTEGER)) + .mustThrow(()=>st.get(0,~api.SQLITE_INTEGER)) + .assert(3 === st.get(0,api.SQLITE_INTEGER)) .assert(3 === st.getInt(0)) - .assert('3' === st.get(0,S.SQLITE_TEXT)) + .assert('3' === st.get(0,api.SQLITE_TEXT)) .assert('3' === st.getString(0)) - .assert(3.0 === st.get(0,S.SQLITE_FLOAT)) + .assert(3.0 === st.get(0,api.SQLITE_FLOAT)) .assert(3.0 === st.getFloat(0)) - .assert(st.get(0,S.SQLITE_BLOB) instanceof Uint8Array) + .assert(st.get(0,api.SQLITE_BLOB) instanceof Uint8Array) .assert(st.getBlob(0) instanceof Uint8Array) .assert(3 === st.get([])[0]) .assert(3 === st.get({}).a) @@ -73,7 +61,7 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`, bind: [5,6] }); T.assert(2 === list.length); - log("Exec'd SQL:", list); + //log("Exec'd SQL:", list); let counter = 0, colNames = []; db.exec("SELECT a a, b b FROM t",{ rowMode: 'object', @@ -95,7 +83,9 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`, } }); T.assert(6 === counter); + }; + const testUDF = function(db){ log("Testing UDF..."); db.createFunction("foo",function(a,b){return a+b}); T.assert(7===db.selectValue("select foo(3,4)")). @@ -110,6 +100,8 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`, return rc; } }); + + log("Testing DB::selectValue() w/ UDF..."); T.assert(0===db.selectValue("select bar()")). assert(1===db.selectValue("select bar(1)")). assert(3===db.selectValue("select bar(1,2)")). @@ -120,18 +112,34 @@ INSERT INTO t(a,b) VALUES(1,2),(3,4),(?,?);`, assert(null === db.selectValue("select ?",null)). assert(null === db.selectValue("select ?",[null])). assert(null === db.selectValue("select $a",{$a:null})); - - }finally{ - db.close(); - } - log("Total Test count:",T.counter); -}; + }; + + const runTests = function(namespace){ + T.assert(Module._free instanceof Function). + assert(Module.allocate instanceof Function). + assert(Module.addFunction instanceof Function). + assert(Module.removeFunction instanceof Function); + const api = namespace.api; + const oo = namespace.SQLite3; + console.log("Loaded module:",api.sqlite3_libversion(), + api.sqlite3_sourceid()); + log("Build options:",oo.compileOptionUsed()); + const db = new oo.DB(); + try { + log("DB:",db.filename); + [ + test1, testUDF + ].forEach((f)=>f(db, api)); + }finally{ + db.close(); + } + log("Total Test count:",T.counter); + }; -self/*window or worker*/.Module.postRun.push(function(theModule){ - /** Use a timeout so that we are (hopefully) out from under the - module init stack when our setup gets run. */ - - setTimeout(function(){ - theModule.loadSqliteAPI(mainTest1); - },0); -}); + self.Module.postRun.push(function(theModule){ + /** Use a timeout so that we are (hopefully) out from under the + module init stack when our setup gets run. Just on principle, + not because we _need_ to be. */ + setTimeout(()=>theModule.loadSqliteAPI(runTests), 0); + }); +})(self/*window or worker*/); diff --git a/manifest b/manifest index e9f9fc3988..04cee01095 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C wasm/JS:\sminor\sdoc\supdates,\scorrected\sbind()ing\sof\sthe\sundefined\svalue\sto\sbehave\sas\sdocumented,\sremoved\ssome\ssuperfluous\scode. -D 2022-05-24T01:15:21.052 +C fiddle:\slots\sof\sgeneric\srefactoring,\srestructuring,\sand\scleanup\sin\sthe\shigher-level\scode.\sAdded\spush-fiddle\sext/fiddle/Makefile\starget\sto\spush\sthe\sfiddle\sapp\sto\sa\sremote\sserver\svia\srsync. +D 2022-05-24T14:36:45.563 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -58,17 +58,18 @@ F ext/expert/test_expert.c d56c194b769bdc90cf829a14c9ecbc1edca9c850b837a4d0b13be F ext/fiddle/EXPORTED_FUNCTIONS.fiddle 487fc7c83d45c48326f731c89162ed17ab15767e5efede8999d7d6c6e2d04c0f F ext/fiddle/EXPORTED_FUNCTIONS.sqlite3 07b573a1830cb2d38ed347cf2a4139ec3b9c0f69748da6a2d8356b426c807694 F ext/fiddle/EXPORTED_RUNTIME_METHODS ff64aea52779b0d4a838268275fe02adf6f2fdf4d9ce21c22d104bf3d7597398 -F ext/fiddle/Makefile 9277c73e208b9c8093659256c9f07409c877e366480c7c22ec545ee345451d95 +F ext/fiddle/Makefile 8eb51a07b4ff7e5684ca829906233c07d0dccb5d14a9d8b4ec737a2a6f3f0d7e 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 68f5bb45fc1ae7f8ae3f6b85f465257db514d12bf50ec492259685178c452a88 +F ext/fiddle/fiddle.js 0263a1ebf7e09ecd8b37ff8e00b9ba27c543b65b6c3dbf2f9def90e6c71c4580 F ext/fiddle/index.md d9c1c308d8074341bc3b11d1d39073cd77754cb3ca9aeb949f23fdd8323d81cf -F ext/fiddle/sqlite3-api.js 3f41887a66d620ae506fea4a735d909c3dc0023045265736958de6d3016fbfc9 -F ext/fiddle/testing-common.js 723aada13d90a5ee3f0f8f5b5b88e46954becae5d2b04ded811d90106057f4ac -F ext/fiddle/testing1.html 026502e5d5e6a250e4101f8e8948708a1295ce831a094d741839ecaf788d8533 -F ext/fiddle/testing1.js b9dd06fd02fbcf947794ceb0bcca1a00e3440d80bf1d819a73bbcac25c87086e +F ext/fiddle/sqlite3-api.js 5492d48b4167179fd979fae99f0c21dc2d0f03460be9ff2d35e62225c58c4c9c +F ext/fiddle/testing-common.js a2527fd8dfb500bad9b434ae2645bb91489792115ee1e1b4b53cac4e9198992a +F ext/fiddle/testing.css 750572dded671d2cf142bbcb27af5542522ac08db128245d0b9fe410aa1d7f2a +F ext/fiddle/testing1.html c00236d71b7f7523b722ae2f79cb2b734e6ed4ff16102fa69974145f6e2bfc95 +F ext/fiddle/testing1.js a2cee7ee12c2e1756e775125b0f9950dc5e5faeeeb4979c6d9894626d90cb5d9 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b F ext/fts1/ft_hash.h 06df7bba40dadd19597aa400a875dbc2fed705ea @@ -1968,8 +1969,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 70f91fab825d365f505750acdb8d3ae532880c4cdb64d1e61bb21b24a115958b -R aa6cc8d5c8c48a5dcba3187d74399b46 +P 526c8c728019b317624a93f6f07840ca524bca84e7c03ce5e86e38953146236f +R 8d308c6cc994c31188b8c779dc67218e U stephan -Z 0033b446f56e6779d5a11407c4e1444a +Z 3a03c1dbb04138d0aaacfe24b6f85ef3 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 7ae63bdd66..4f241efce6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -526c8c728019b317624a93f6f07840ca524bca84e7c03ce5e86e38953146236f \ No newline at end of file +ed19fef3459499abb0a4a010f368b4576d6e068d930c8480446ea677ac87c1c1 \ No newline at end of file