12cae138fSstephan/* 22cae138fSstephan 2022-08-29 32cae138fSstephan 42cae138fSstephan The author disclaims copyright to this source code. In place of a 52cae138fSstephan legal notice, here is a blessing: 62cae138fSstephan 72cae138fSstephan * May you do good and not evil. 82cae138fSstephan * May you find forgiveness for yourself and forgive others. 92cae138fSstephan * May you share freely, never taking more than you give. 102cae138fSstephan 112cae138fSstephan *********************************************************************** 122cae138fSstephan 13cdefd5d0Sstephan A basic batch SQL runner for sqlite3-api.js. This file must be run in 142cae138fSstephan main JS thread and sqlite3.js must have been loaded before it. 152cae138fSstephan*/ 162cae138fSstephan'use strict'; 172cae138fSstephan(function(){ 182cae138fSstephan const toss = function(...args){throw new Error(args.join(' '))}; 192f06bf25Sstephan const warn = console.warn.bind(console); 205b915007Sstephan let sqlite3; 21d234902bSstephan const urlParams = new URL(self.location.href).searchParams; 22d234902bSstephan const cacheSize = (()=>{ 23d234902bSstephan if(urlParams.has('cachesize')) return +urlParams.get('cachesize'); 24d234902bSstephan return 200; 25d234902bSstephan })(); 262cae138fSstephan 275ad36319Sstephan /** Throws if the given sqlite3 result code is not 0. */ 285ad36319Sstephan const checkSqliteRc = (dbh,rc)=>{ 295ad36319Sstephan if(rc) toss("Prepare failed:",sqlite3.capi.sqlite3_errmsg(dbh)); 305ad36319Sstephan }; 315ad36319Sstephan 325ad36319Sstephan const sqlToDrop = [ 335ad36319Sstephan "SELECT type,name FROM sqlite_schema ", 345ad36319Sstephan "WHERE name NOT LIKE 'sqlite\\_%' escape '\\' ", 355ad36319Sstephan "AND name NOT LIKE '\\_%' escape '\\'" 365ad36319Sstephan ].join(''); 375ad36319Sstephan 385ad36319Sstephan const clearDbWebSQL = function(db){ 395ad36319Sstephan db.handle.transaction(function(tx){ 405ad36319Sstephan const onErr = (e)=>console.error(e); 415ad36319Sstephan const callback = function(tx, result){ 425ad36319Sstephan const rows = result.rows; 435ad36319Sstephan let i, n; 445ad36319Sstephan i = n = rows.length; 455ad36319Sstephan while(i--){ 465ad36319Sstephan const row = rows.item(i); 475ad36319Sstephan const name = JSON.stringify(row.name); 485ad36319Sstephan const type = row.type; 495ad36319Sstephan switch(type){ 50cb22bd80Sstephan case 'index': case 'table': 51cb22bd80Sstephan case 'trigger': case 'view': { 525ad36319Sstephan const sql2 = 'DROP '+type+' '+name; 53cb22bd80Sstephan tx.executeSql(sql2, [], ()=>{}, onErr); 545ad36319Sstephan break; 555ad36319Sstephan } 565ad36319Sstephan default: 57cb22bd80Sstephan warn("Unhandled db entry type:",type,'name =',name); 585ad36319Sstephan break; 595ad36319Sstephan } 605ad36319Sstephan } 615ad36319Sstephan }; 625ad36319Sstephan tx.executeSql(sqlToDrop, [], callback, onErr); 635ad36319Sstephan db.handle.changeVersion(db.handle.version, "", ()=>{}, onErr, ()=>{}); 645ad36319Sstephan }); 655ad36319Sstephan }; 665ad36319Sstephan 675ad36319Sstephan const clearDbSqlite = function(db){ 685ad36319Sstephan // This would be SO much easier with the oo1 API, but we specifically want to 695ad36319Sstephan // inject metrics we can't get via that API, and we cannot reliably (OPFS) 705ad36319Sstephan // open the same DB twice to clear it using that API, so... 71*8948fbeeSstephan const rc = sqlite3.wasm.exports.sqlite3_wasm_db_reset(db.handle); 72d234902bSstephan App.logHtml("reset db rc =",rc,db.id, db.filename); 735ad36319Sstephan }; 745ad36319Sstephan 755ad36319Sstephan 765ad36319Sstephan const E = (s)=>document.querySelector(s); 772cae138fSstephan const App = { 782cae138fSstephan e: { 795ad36319Sstephan output: E('#test-output'), 805ad36319Sstephan selSql: E('#sql-select'), 815ad36319Sstephan btnRun: E('#sql-run'), 825ad36319Sstephan btnRunNext: E('#sql-run-next'), 835ad36319Sstephan btnRunRemaining: E('#sql-run-remaining'), 845ad36319Sstephan btnExportMetrics: E('#export-metrics'), 855ad36319Sstephan btnClear: E('#output-clear'), 865ad36319Sstephan btnReset: E('#db-reset'), 875ad36319Sstephan cbReverseLog: E('#cb-reverse-log-order'), 88cb22bd80Sstephan selImpl: E('#select-impl'), 89cb22bd80Sstephan fsToolbar: E('#toolbar') 902cae138fSstephan }, 915b915007Sstephan db: Object.create(null), 925ad36319Sstephan dbs: Object.create(null), 932f06bf25Sstephan cache:{}, 942cae138fSstephan log: console.log.bind(console), 952cae138fSstephan warn: console.warn.bind(console), 962cae138fSstephan cls: function(){this.e.output.innerHTML = ''}, 972cae138fSstephan logHtml2: function(cssClass,...args){ 982cae138fSstephan const ln = document.createElement('div'); 992cae138fSstephan if(cssClass) ln.classList.add(cssClass); 1002cae138fSstephan ln.append(document.createTextNode(args.join(' '))); 1012cae138fSstephan this.e.output.append(ln); 1022f06bf25Sstephan //this.e.output.lastElementChild.scrollIntoViewIfNeeded(); 1032cae138fSstephan }, 1042cae138fSstephan logHtml: function(...args){ 1052cae138fSstephan console.log(...args); 1062cae138fSstephan if(1) this.logHtml2('', ...args); 1072cae138fSstephan }, 1082cae138fSstephan logErr: function(...args){ 1092cae138fSstephan console.error(...args); 1102cae138fSstephan if(1) this.logHtml2('error', ...args); 1112cae138fSstephan }, 1122cae138fSstephan 1135ad36319Sstephan execSql: async function(name,sql){ 1145ad36319Sstephan const db = this.getSelectedDb(); 1155ad36319Sstephan const banner = "========================================"; 1165ad36319Sstephan this.logHtml(banner, 1175ad36319Sstephan "Running",name,'('+sql.length,'bytes) using',db.id); 118*8948fbeeSstephan const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; 1195ad36319Sstephan let pStmt = 0, pSqlBegin; 1202cae138fSstephan const stack = wasm.scopedAllocPush(); 1215ad36319Sstephan const metrics = db.metrics = Object.create(null); 1225ad36319Sstephan metrics.prepTotal = metrics.stepTotal = 0; 1235ad36319Sstephan metrics.stmtCount = 0; 1245ad36319Sstephan metrics.malloc = 0; 1255ad36319Sstephan metrics.strcpy = 0; 1265ad36319Sstephan this.blockControls(true); 1275ad36319Sstephan if(this.gotErr){ 1285ad36319Sstephan this.logErr("Cannot run SQL: error cleanup is pending."); 1295ad36319Sstephan return; 1305ad36319Sstephan } 1315ad36319Sstephan // Run this async so that the UI can be updated for the above header... 132cb22bd80Sstephan const endRun = ()=>{ 1335ad36319Sstephan metrics.evalSqlEnd = performance.now(); 1345ad36319Sstephan metrics.evalTimeTotal = (metrics.evalSqlEnd - metrics.evalSqlStart); 135cb22bd80Sstephan this.logHtml(db.id,"metrics:",JSON.stringify(metrics, undefined, ' ')); 1365ad36319Sstephan this.logHtml("prepare() count:",metrics.stmtCount); 1375ad36319Sstephan this.logHtml("Time in prepare_v2():",metrics.prepTotal,"ms", 1385ad36319Sstephan "("+(metrics.prepTotal / metrics.stmtCount),"ms per prepare())"); 1395ad36319Sstephan this.logHtml("Time in step():",metrics.stepTotal,"ms", 1405ad36319Sstephan "("+(metrics.stepTotal / metrics.stmtCount),"ms per step())"); 1415ad36319Sstephan this.logHtml("Total runtime:",metrics.evalTimeTotal,"ms"); 1425ad36319Sstephan this.logHtml("Overhead (time - prep - step):", 1435ad36319Sstephan (metrics.evalTimeTotal - metrics.prepTotal - metrics.stepTotal)+"ms"); 1445ad36319Sstephan this.logHtml(banner,"End of",name); 1455ad36319Sstephan }; 1465ad36319Sstephan 1475ad36319Sstephan let runner; 1485ad36319Sstephan if('websql'===db.id){ 149359d6239Sstephan const who = this; 1505ad36319Sstephan runner = function(resolve, reject){ 1515ad36319Sstephan /* WebSQL cannot execute multiple statements, nor can it execute SQL without 1525ad36319Sstephan an explicit transaction. Thus we have to do some fragile surgery on the 1535ad36319Sstephan input SQL. Since we're only expecting carefully curated inputs, the hope is 154cb22bd80Sstephan that this will suffice. PS: it also can't run most SQL functions, e.g. even 155cb22bd80Sstephan instr() results in "not authorized". */ 156cb22bd80Sstephan if('string'!==typeof sql){ // assume TypedArray 157cb22bd80Sstephan sql = new TextDecoder().decode(sql); 158cb22bd80Sstephan } 159cb22bd80Sstephan sql = sql.replace(/-- [^\n]+\n/g,''); // comment lines interfere with our split() 1605ad36319Sstephan const sqls = sql.split(/;+\n/); 161cb22bd80Sstephan const rxBegin = /^BEGIN/i, rxCommit = /^COMMIT/i; 1622cae138fSstephan try { 1635ad36319Sstephan const nextSql = ()=>{ 1645ad36319Sstephan let x = sqls.shift(); 165cb22bd80Sstephan while(sqls.length && !x) x = sqls.shift(); 1665ad36319Sstephan return x && x.trim(); 1675ad36319Sstephan }; 168cb22bd80Sstephan const who = this; 1695ad36319Sstephan const transaction = function(tx){ 170cb22bd80Sstephan try { 1715ad36319Sstephan let s; 172cb22bd80Sstephan /* Try to approximate the spirit of the input scripts 173cb22bd80Sstephan by running batches bound by BEGIN/COMMIT statements. */ 174cb22bd80Sstephan for(s = nextSql(); !!s; s = nextSql()){ 1755ad36319Sstephan if(rxBegin.test(s)) continue; 1765ad36319Sstephan else if(rxCommit.test(s)) break; 177cb22bd80Sstephan //console.log("websql sql again",sqls.length, s); 1785ad36319Sstephan ++metrics.stmtCount; 1795ad36319Sstephan const t = performance.now(); 180cb22bd80Sstephan tx.executeSql(s,[], ()=>{}, (t,e)=>{ 181cb22bd80Sstephan console.error("WebSQL error",e,"SQL =",s); 182cb22bd80Sstephan who.logErr(e.message); 183359d6239Sstephan //throw e; 184359d6239Sstephan return false; 185cb22bd80Sstephan }); 1865ad36319Sstephan metrics.stepTotal += performance.now() - t; 187ffc0cbb0Sstephan } 1885ad36319Sstephan }catch(e){ 189cb22bd80Sstephan who.logErr("transaction():",e.message); 190cb22bd80Sstephan throw e; 191cb22bd80Sstephan } 192cb22bd80Sstephan }; 193cb22bd80Sstephan const n = sqls.length; 194cb22bd80Sstephan const nextBatch = function(){ 195cb22bd80Sstephan if(sqls.length){ 196cb22bd80Sstephan console.log("websql sqls.length",sqls.length,'of',n); 197359d6239Sstephan db.handle.transaction(transaction, (e)=>{ 198359d6239Sstephan who.logErr("Ignoring and contiuing:",e.message) 199359d6239Sstephan //reject(e); 200359d6239Sstephan return false; 201359d6239Sstephan }, nextBatch); 202cb22bd80Sstephan }else{ 203cb22bd80Sstephan resolve(who); 204cb22bd80Sstephan } 205cb22bd80Sstephan }; 206cb22bd80Sstephan metrics.evalSqlStart = performance.now(); 207cb22bd80Sstephan nextBatch(); 208cb22bd80Sstephan }catch(e){ 209cb22bd80Sstephan //this.gotErr = e; 210cb22bd80Sstephan console.error("websql error:",e); 211359d6239Sstephan who.logErr(e.message); 212359d6239Sstephan //reject(e); 2135ad36319Sstephan } 2145ad36319Sstephan }.bind(this); 2155ad36319Sstephan }else{/*sqlite3 db...*/ 2165ad36319Sstephan runner = function(resolve, reject){ 2175ad36319Sstephan metrics.evalSqlStart = performance.now(); 2185ad36319Sstephan try { 2195ad36319Sstephan let t; 2205ad36319Sstephan let sqlByteLen = sql.byteLength; 2215ad36319Sstephan const [ppStmt, pzTail] = wasm.scopedAllocPtr(2); 2225ad36319Sstephan t = performance.now(); 2235ad36319Sstephan pSqlBegin = wasm.scopedAlloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed"); 2245ad36319Sstephan metrics.malloc = performance.now() - t; 2255ad36319Sstephan metrics.byteLength = sqlByteLen; 2265ad36319Sstephan let pSql = pSqlBegin; 2275ad36319Sstephan const pSqlEnd = pSqlBegin + sqlByteLen; 2285ad36319Sstephan t = performance.now(); 2295ad36319Sstephan wasm.heap8().set(sql, pSql); 2305ad36319Sstephan wasm.setMemValue(pSql + sqlByteLen, 0); 2315ad36319Sstephan metrics.strcpy = performance.now() - t; 2325ad36319Sstephan let breaker = 0; 2335ad36319Sstephan while(pSql && wasm.getMemValue(pSql,'i8')){ 2345ad36319Sstephan wasm.setPtrValue(ppStmt, 0); 2355ad36319Sstephan wasm.setPtrValue(pzTail, 0); 2365ad36319Sstephan t = performance.now(); 2375ad36319Sstephan let rc = capi.sqlite3_prepare_v3( 2385ad36319Sstephan db.handle, pSql, sqlByteLen, 0, ppStmt, pzTail 2395ad36319Sstephan ); 2405ad36319Sstephan metrics.prepTotal += performance.now() - t; 2415ad36319Sstephan checkSqliteRc(db.handle, rc); 2425ad36319Sstephan pStmt = wasm.getPtrValue(ppStmt); 2435ad36319Sstephan pSql = wasm.getPtrValue(pzTail); 2445ad36319Sstephan sqlByteLen = pSqlEnd - pSql; 2455ad36319Sstephan if(!pStmt) continue/*empty statement*/; 2465ad36319Sstephan ++metrics.stmtCount; 2475ad36319Sstephan t = performance.now(); 2485ad36319Sstephan rc = capi.sqlite3_step(pStmt); 2495ad36319Sstephan capi.sqlite3_finalize(pStmt); 2505ad36319Sstephan pStmt = 0; 2515ad36319Sstephan metrics.stepTotal += performance.now() - t; 2525ad36319Sstephan switch(rc){ 2535ad36319Sstephan case capi.SQLITE_ROW: 2545ad36319Sstephan case capi.SQLITE_DONE: break; 2555ad36319Sstephan default: checkSqliteRc(db.handle, rc); toss("Not reached."); 2565ad36319Sstephan } 2575ad36319Sstephan } 2585ad36319Sstephan resolve(this); 2595ad36319Sstephan }catch(e){ 2605ad36319Sstephan if(pStmt) capi.sqlite3_finalize(pStmt); 261cb22bd80Sstephan //this.gotErr = e; 2625ad36319Sstephan reject(e); 2632cae138fSstephan }finally{ 264359d6239Sstephan capi.sqlite3_exec(db.handle,"rollback;",0,0,0); 2652cae138fSstephan wasm.scopedAllocPop(stack); 2662cae138fSstephan } 2675ad36319Sstephan }.bind(this); 2685ad36319Sstephan } 2695ad36319Sstephan let p; 2705ad36319Sstephan if(1){ 2715ad36319Sstephan p = new Promise(function(res,rej){ 2725ad36319Sstephan setTimeout(()=>runner(res, rej), 50)/*give UI a chance to output the "running" banner*/; 2735ad36319Sstephan }); 2745ad36319Sstephan }else{ 2755ad36319Sstephan p = new Promise(runner); 2765ad36319Sstephan } 2775ad36319Sstephan return p.catch( 2785ad36319Sstephan (e)=>this.logErr("Error via execSql("+name+",...):",e.message) 2795ad36319Sstephan ).finally(()=>{ 280cb22bd80Sstephan endRun(); 2815ad36319Sstephan this.blockControls(false); 2825ad36319Sstephan }); 2832cae138fSstephan }, 2842cae138fSstephan 2855ad36319Sstephan clearDb: function(){ 2865ad36319Sstephan const db = this.getSelectedDb(); 2875ad36319Sstephan if('websql'===db.id){ 2885ad36319Sstephan this.logErr("TODO: clear websql db."); 2895ad36319Sstephan return; 2905b915007Sstephan } 2915ad36319Sstephan if(!db.handle) return; 292*8948fbeeSstephan const capi = this.sqlite3, wasm = this.sqlite3.wasm; 2935ad36319Sstephan //const scope = wasm.scopedAllocPush( 2945ad36319Sstephan this.logErr("TODO: clear db"); 2952cae138fSstephan }, 2962cae138fSstephan 29753f635dfSstephan /** 29853f635dfSstephan Loads batch-runner.list and populates the selection list from 29953f635dfSstephan it. Returns a promise which resolves to nothing in particular 30053f635dfSstephan when it completes. Only intended to be run once at the start 30153f635dfSstephan of the app. 30253f635dfSstephan */ 3032cae138fSstephan loadSqlList: async function(){ 3042cae138fSstephan const sel = this.e.selSql; 3052cae138fSstephan sel.innerHTML = ''; 3062cae138fSstephan this.blockControls(true); 3072cae138fSstephan const infile = 'batch-runner.list'; 3082cae138fSstephan this.logHtml("Loading list of SQL files:", infile); 3092cae138fSstephan let txt; 3102cae138fSstephan try{ 3112cae138fSstephan const r = await fetch(infile); 3122cae138fSstephan if(404 === r.status){ 3132cae138fSstephan toss("Missing file '"+infile+"'."); 3142cae138fSstephan } 3152cae138fSstephan if(!r.ok) toss("Loading",infile,"failed:",r.statusText); 3162cae138fSstephan txt = await r.text(); 3175ad36319Sstephan const warning = E('#warn-list'); 3182f06bf25Sstephan if(warning) warning.remove(); 3192cae138fSstephan }catch(e){ 3202cae138fSstephan this.logErr(e.message); 3212cae138fSstephan throw e; 3222cae138fSstephan }finally{ 3232cae138fSstephan this.blockControls(false); 3242cae138fSstephan } 3252f06bf25Sstephan const list = txt.split(/\n+/); 3262cae138fSstephan let opt; 3272cae138fSstephan if(0){ 3282cae138fSstephan opt = document.createElement('option'); 3292cae138fSstephan opt.innerText = "Select file to evaluate..."; 3302cae138fSstephan opt.value = ''; 3312cae138fSstephan opt.disabled = true; 3322cae138fSstephan opt.selected = true; 3332cae138fSstephan sel.appendChild(opt); 3342cae138fSstephan } 3352cae138fSstephan list.forEach(function(fn){ 3362f06bf25Sstephan if(!fn) return; 3372cae138fSstephan opt = document.createElement('option'); 338fbf0f488Sstephan opt.value = fn; 339fbf0f488Sstephan opt.innerText = fn.split('/').pop(); 3402cae138fSstephan sel.appendChild(opt); 3412cae138fSstephan }); 3422cae138fSstephan this.logHtml("Loaded",infile); 3432cae138fSstephan }, 3442cae138fSstephan 3452cae138fSstephan /** Fetch ./fn and return its contents as a Uint8Array. */ 3462f06bf25Sstephan fetchFile: async function(fn, cacheIt=false){ 3472f06bf25Sstephan if(cacheIt && this.cache[fn]) return this.cache[fn]; 3482cae138fSstephan this.logHtml("Fetching",fn,"..."); 3492cae138fSstephan let sql; 3502cae138fSstephan try { 3512cae138fSstephan const r = await fetch(fn); 3522cae138fSstephan if(!r.ok) toss("Fetch failed:",r.statusText); 3532cae138fSstephan sql = new Uint8Array(await r.arrayBuffer()); 3542cae138fSstephan }catch(e){ 3552cae138fSstephan this.logErr(e.message); 3562cae138fSstephan throw e; 3572cae138fSstephan } 3582cae138fSstephan this.logHtml("Fetched",sql.length,"bytes from",fn); 3592f06bf25Sstephan if(cacheIt) this.cache[fn] = sql; 3602cae138fSstephan return sql; 3612f06bf25Sstephan }/*fetchFile()*/, 3622cae138fSstephan 3632f06bf25Sstephan /** Disable or enable certain UI controls. */ 3642f06bf25Sstephan blockControls: function(disable){ 365cb22bd80Sstephan //document.querySelectorAll('.disable-during-eval').forEach((e)=>e.disabled = disable); 366cb22bd80Sstephan this.e.fsToolbar.disabled = disable; 3672cae138fSstephan }, 3682cae138fSstephan 36953f635dfSstephan /** 37053f635dfSstephan Converts this.metrics() to a form which is suitable for easy conversion to 37153f635dfSstephan CSV. It returns an array of arrays. The first sub-array is the column names. 37253f635dfSstephan The 2nd and subsequent are the values, one per test file (only the most recent 37353f635dfSstephan metrics are kept for any given file). 37453f635dfSstephan */ 37553f635dfSstephan metricsToArrays: function(){ 37653f635dfSstephan const rc = []; 377cb22bd80Sstephan Object.keys(this.dbs).sort().forEach((k)=>{ 378cb22bd80Sstephan const d = this.dbs[k]; 379cb22bd80Sstephan const m = d.metrics; 3805ad36319Sstephan delete m.evalSqlStart; 3815ad36319Sstephan delete m.evalSqlEnd; 38253f635dfSstephan const mk = Object.keys(m).sort(); 38353f635dfSstephan if(!rc.length){ 3845ad36319Sstephan rc.push(['db', ...mk]); 38553f635dfSstephan } 38653f635dfSstephan const row = [k.split('/').pop()/*remove dir prefix from filename*/]; 38753f635dfSstephan rc.push(row); 388cb22bd80Sstephan row.push(...mk.map((kk)=>m[kk])); 38953f635dfSstephan }); 39053f635dfSstephan return rc; 39153f635dfSstephan }, 39253f635dfSstephan 39353f635dfSstephan metricsToBlob: function(colSeparator='\t'){ 39453f635dfSstephan const ar = [], ma = this.metricsToArrays(); 39553f635dfSstephan if(!ma.length){ 39653f635dfSstephan this.logErr("Metrics are empty. Run something."); 39753f635dfSstephan return; 39853f635dfSstephan } 39953f635dfSstephan ma.forEach(function(row){ 40053f635dfSstephan ar.push(row.join(colSeparator),'\n'); 40153f635dfSstephan }); 40253f635dfSstephan return new Blob(ar); 40353f635dfSstephan }, 40453f635dfSstephan 40553f635dfSstephan downloadMetrics: function(){ 40653f635dfSstephan const b = this.metricsToBlob(); 40753f635dfSstephan if(!b) return; 40853f635dfSstephan const url = URL.createObjectURL(b); 40953f635dfSstephan const a = document.createElement('a'); 41053f635dfSstephan a.href = url; 411cdefd5d0Sstephan a.download = 'batch-runner-js-'+((new Date().getTime()/1000) | 0)+'.csv'; 41253f635dfSstephan this.logHtml("Triggering download of",a.download); 41353f635dfSstephan document.body.appendChild(a); 41453f635dfSstephan a.click(); 41553f635dfSstephan setTimeout(()=>{ 41653f635dfSstephan document.body.removeChild(a); 41753f635dfSstephan URL.revokeObjectURL(url); 41853f635dfSstephan }, 500); 41953f635dfSstephan }, 42053f635dfSstephan 42153f635dfSstephan /** 42253f635dfSstephan Fetch file fn and eval it as an SQL blob. This is an async 42353f635dfSstephan operation and returns a Promise which resolves to this 42453f635dfSstephan object on success. 42553f635dfSstephan */ 4262cae138fSstephan evalFile: async function(fn){ 4272cae138fSstephan const sql = await this.fetchFile(fn); 4285ad36319Sstephan return this.execSql(fn,sql); 4292f06bf25Sstephan }/*evalFile()*/, 4302cae138fSstephan 4315ad36319Sstephan /** 4325ad36319Sstephan Clears all DB tables in all _opened_ databases. Because of 4335ad36319Sstephan disparities between backends, we cannot simply "unlink" the 4345ad36319Sstephan databases to clean them up. 4355ad36319Sstephan */ 4365ad36319Sstephan clearStorage: function(onlySelectedDb=false){ 437cb22bd80Sstephan const list = onlySelectedDb 438cb22bd80Sstephan ? [('boolean'===typeof onlySelectedDb) 4395ad36319Sstephan ? this.dbs[this.e.selImpl.value] 440cb22bd80Sstephan : onlySelectedDb] 4415ad36319Sstephan : Object.values(this.dbs); 4425ad36319Sstephan for(let db of list){ 4435ad36319Sstephan if(db && db.handle){ 4445ad36319Sstephan this.logHtml("Clearing db",db.id); 445cb22bd80Sstephan db.clear(); 4465ad36319Sstephan } 4475ad36319Sstephan } 4485b915007Sstephan }, 4495b915007Sstephan 4505ad36319Sstephan /** 4515ad36319Sstephan Fetches the handle of the db associated with 4525ad36319Sstephan this.e.selImpl.value, opening it if needed. 4535ad36319Sstephan */ 4545ad36319Sstephan getSelectedDb: function(){ 4555ad36319Sstephan if(!this.dbs.memdb){ 4565ad36319Sstephan for(let opt of this.e.selImpl.options){ 4575ad36319Sstephan const d = this.dbs[opt.value] = Object.create(null); 4585ad36319Sstephan d.id = opt.value; 4595ad36319Sstephan switch(d.id){ 4605ad36319Sstephan case 'virtualfs': 4615ad36319Sstephan d.filename = 'file:/virtualfs.sqlite3?vfs=unix-none'; 4625ad36319Sstephan break; 4635ad36319Sstephan case 'memdb': 4645ad36319Sstephan d.filename = ':memory:'; 4655ad36319Sstephan break; 4665ad36319Sstephan case 'wasmfs-opfs': 467cb22bd80Sstephan d.filename = 'file:'+( 468cb22bd80Sstephan this.sqlite3.capi.sqlite3_wasmfs_opfs_dir() 469cb22bd80Sstephan )+'/wasmfs-opfs.sqlite3b'; 4705ad36319Sstephan break; 4715ad36319Sstephan case 'websql': 4725ad36319Sstephan d.filename = 'websql.db'; 4735ad36319Sstephan break; 4745ad36319Sstephan default: 4755ad36319Sstephan this.logErr("Unhandled db selection option (see details in the console).",opt); 4765ad36319Sstephan toss("Unhandled db init option"); 4775b915007Sstephan } 4785ad36319Sstephan } 4795ad36319Sstephan }/*first-time init*/ 4805ad36319Sstephan const dbId = this.e.selImpl.value; 4815ad36319Sstephan const d = this.dbs[dbId]; 4825ad36319Sstephan if(d.handle) return d; 4835ad36319Sstephan if('websql' === dbId){ 4845ad36319Sstephan d.handle = self.openDatabase('batch-runner', '0.1', 'foo', 1024 * 1024 * 50); 4855ad36319Sstephan d.clear = ()=>clearDbWebSQL(d); 486d234902bSstephan d.handle.transaction(function(tx){ 487d234902bSstephan tx.executeSql("PRAGMA cache_size="+cacheSize); 488d234902bSstephan App.logHtml(dbId,"cache_size =",cacheSize); 489d234902bSstephan }); 4905ad36319Sstephan }else{ 491*8948fbeeSstephan const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm; 4925ad36319Sstephan const stack = wasm.scopedAllocPush(); 4935ad36319Sstephan let pDb = 0; 4945ad36319Sstephan try{ 4955ad36319Sstephan const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE; 4965ad36319Sstephan const ppDb = wasm.scopedAllocPtr(); 4975ad36319Sstephan const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null); 4985ad36319Sstephan pDb = wasm.getPtrValue(ppDb) 4995ad36319Sstephan if(rc) toss("sqlite3_open_v2() failed with code",rc); 500d234902bSstephan capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0); 501d234902bSstephan this.logHtml(dbId,"cache_size =",cacheSize); 5025ad36319Sstephan }catch(e){ 5035ad36319Sstephan if(pDb) capi.sqlite3_close_v2(pDb); 5045ad36319Sstephan }finally{ 5055ad36319Sstephan wasm.scopedAllocPop(stack); 5065ad36319Sstephan } 5075ad36319Sstephan d.handle = pDb; 5085ad36319Sstephan d.clear = ()=>clearDbSqlite(d); 5095ad36319Sstephan } 5105ad36319Sstephan d.clear(); 511d234902bSstephan this.logHtml("Opened db:",dbId,d.filename); 5125ad36319Sstephan console.log("db =",d); 5135ad36319Sstephan return d; 5145b915007Sstephan }, 5155b915007Sstephan 5162cae138fSstephan run: function(sqlite3){ 5172f06bf25Sstephan delete this.run; 5182cae138fSstephan this.sqlite3 = sqlite3; 519*8948fbeeSstephan const capi = sqlite3.capi, wasm = sqlite3.wasm; 5202cae138fSstephan this.logHtml("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); 5212cae138fSstephan this.logHtml("WASM heap size =",wasm.heap8().length); 5222f06bf25Sstephan this.loadSqlList(); 5235ad36319Sstephan if(capi.sqlite3_wasmfs_opfs_dir()){ 5245ad36319Sstephan E('#warn-opfs').classList.remove('hidden'); 5255b915007Sstephan }else{ 5265ad36319Sstephan E('#warn-opfs').remove(); 5275ad36319Sstephan E('option[value=wasmfs-opfs]').disabled = true; 5285ad36319Sstephan } 5295ad36319Sstephan if('function' === typeof self.openDatabase){ 5305ad36319Sstephan E('#warn-websql').classList.remove('hidden'); 53157db2174Sstephan }else{ 5325ad36319Sstephan E('option[value=websql]').disabled = true; 5335ad36319Sstephan E('#warn-websql').remove(); 53457db2174Sstephan } 5352cae138fSstephan const who = this; 53653f635dfSstephan if(this.e.cbReverseLog.checked){ 53753f635dfSstephan this.e.output.classList.add('reverse'); 53853f635dfSstephan } 53953f635dfSstephan this.e.cbReverseLog.addEventListener('change', function(){ 5405ad36319Sstephan who.e.output.classList[this.checked ? 'add' : 'remove']('reverse'); 54153f635dfSstephan }, false); 5422cae138fSstephan this.e.btnClear.addEventListener('click', ()=>this.cls(), false); 5432cae138fSstephan this.e.btnRun.addEventListener('click', function(){ 5442cae138fSstephan if(!who.e.selSql.value) return; 5452cae138fSstephan who.evalFile(who.e.selSql.value); 5462cae138fSstephan }, false); 5472f06bf25Sstephan this.e.btnRunNext.addEventListener('click', function(){ 5482f06bf25Sstephan ++who.e.selSql.selectedIndex; 5492f06bf25Sstephan if(!who.e.selSql.value) return; 5502f06bf25Sstephan who.evalFile(who.e.selSql.value); 5512f06bf25Sstephan }, false); 5522f06bf25Sstephan this.e.btnReset.addEventListener('click', function(){ 5535ad36319Sstephan who.clearStorage(true); 5542f06bf25Sstephan }, false); 55553f635dfSstephan this.e.btnExportMetrics.addEventListener('click', function(){ 556cdefd5d0Sstephan who.logHtml2('warning',"Triggering download of metrics CSV. Check your downloads folder."); 55753f635dfSstephan who.downloadMetrics(); 558cdefd5d0Sstephan //const m = who.metricsToArrays(); 559cdefd5d0Sstephan //console.log("Metrics:",who.metrics, m); 56053f635dfSstephan }); 5615ad36319Sstephan this.e.selImpl.addEventListener('change', function(){ 5625ad36319Sstephan who.getSelectedDb(); 5635ad36319Sstephan }); 5642f06bf25Sstephan this.e.btnRunRemaining.addEventListener('click', async function(){ 5652f06bf25Sstephan let v = who.e.selSql.value; 5662f06bf25Sstephan const timeStart = performance.now(); 5672f06bf25Sstephan while(v){ 5682f06bf25Sstephan await who.evalFile(v); 5692f06bf25Sstephan if(who.gotError){ 5702f06bf25Sstephan who.logErr("Error handling script",v,":",who.gotError.message); 5712f06bf25Sstephan break; 5722f06bf25Sstephan } 5732f06bf25Sstephan ++who.e.selSql.selectedIndex; 5742f06bf25Sstephan v = who.e.selSql.value; 5752f06bf25Sstephan } 5762f06bf25Sstephan const timeTotal = performance.now() - timeStart; 5772f06bf25Sstephan who.logHtml("Run-remaining time:",timeTotal,"ms ("+(timeTotal/1000/60)+" minute(s))"); 5785b915007Sstephan who.clearStorage(); 5792f06bf25Sstephan }, false); 5802f06bf25Sstephan }/*run()*/ 5812cae138fSstephan }/*App*/; 5822cae138fSstephan 583359d6239Sstephan self.sqlite3TestModule.initSqlite3().then(function(sqlite3_){ 584359d6239Sstephan sqlite3 = sqlite3_; 585b94a9860Sstephan self.App = App /* only to facilitate dev console access */; 586b94a9860Sstephan App.run(sqlite3); 5872cae138fSstephan }); 5882cae138fSstephan})(); 589