mirror of
				https://github.com/sqlite/sqlite.git
				synced 2025-11-03 16:53:36 +03:00 
			
		
		
		
	
		
			
				
	
	
		
			589 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			589 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/*
 | 
						|
  2022-08-29
 | 
						|
 | 
						|
  The author disclaims copyright to this source code.  In place of a
 | 
						|
  legal notice, here is a blessing:
 | 
						|
 | 
						|
  *   May you do good and not evil.
 | 
						|
  *   May you find forgiveness for yourself and forgive others.
 | 
						|
  *   May you share freely, never taking more than you give.
 | 
						|
 | 
						|
  ***********************************************************************
 | 
						|
 | 
						|
  A basic batch SQL runner for sqlite3-api.js. This file must be run in
 | 
						|
  main JS thread and sqlite3.js must have been loaded before it.
 | 
						|
*/
 | 
						|
'use strict';
 | 
						|
(function(){
 | 
						|
  const toss = function(...args){throw new Error(args.join(' '))};
 | 
						|
  const warn = console.warn.bind(console);
 | 
						|
  let sqlite3;
 | 
						|
  const urlParams = new URL(self.location.href).searchParams;
 | 
						|
  const cacheSize = (()=>{
 | 
						|
    if(urlParams.has('cachesize')) return +urlParams.get('cachesize');
 | 
						|
    return 200;
 | 
						|
  })();
 | 
						|
 | 
						|
  /** Throws if the given sqlite3 result code is not 0. */
 | 
						|
  const checkSqliteRc = (dbh,rc)=>{
 | 
						|
    if(rc) toss("Prepare failed:",sqlite3.capi.sqlite3_errmsg(dbh));
 | 
						|
  };
 | 
						|
 | 
						|
  const sqlToDrop = [
 | 
						|
    "SELECT type,name FROM sqlite_schema ",
 | 
						|
    "WHERE name NOT LIKE 'sqlite\\_%' escape '\\' ",
 | 
						|
    "AND name NOT LIKE '\\_%' escape '\\'"
 | 
						|
  ].join('');
 | 
						|
  
 | 
						|
  const clearDbWebSQL = function(db){
 | 
						|
    db.handle.transaction(function(tx){
 | 
						|
      const onErr = (e)=>console.error(e);
 | 
						|
      const callback = function(tx, result){
 | 
						|
        const rows = result.rows;
 | 
						|
        let i, n;
 | 
						|
        i = n = rows.length;
 | 
						|
        while(i--){
 | 
						|
          const row = rows.item(i);
 | 
						|
          const name = JSON.stringify(row.name);
 | 
						|
          const type = row.type;
 | 
						|
          switch(type){
 | 
						|
              case 'index': case 'table':
 | 
						|
              case 'trigger': case 'view': {
 | 
						|
                const sql2 = 'DROP '+type+' '+name;
 | 
						|
                tx.executeSql(sql2, [], ()=>{}, onErr);
 | 
						|
                break;
 | 
						|
              }
 | 
						|
              default:
 | 
						|
                warn("Unhandled db entry type:",type,'name =',name);
 | 
						|
                break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      };
 | 
						|
      tx.executeSql(sqlToDrop, [], callback, onErr);
 | 
						|
      db.handle.changeVersion(db.handle.version, "", ()=>{}, onErr, ()=>{});
 | 
						|
    });
 | 
						|
  };
 | 
						|
 | 
						|
  const clearDbSqlite = function(db){
 | 
						|
    // This would be SO much easier with the oo1 API, but we specifically want to
 | 
						|
    // inject metrics we can't get via that API, and we cannot reliably (OPFS)
 | 
						|
    // open the same DB twice to clear it using that API, so...
 | 
						|
    const rc = sqlite3.wasm.exports.sqlite3_wasm_db_reset(db.handle);
 | 
						|
    App.logHtml("reset db rc =",rc,db.id, db.filename);
 | 
						|
  };
 | 
						|
 | 
						|
  
 | 
						|
  const E = (s)=>document.querySelector(s);
 | 
						|
  const App = {
 | 
						|
    e: {
 | 
						|
      output: E('#test-output'),
 | 
						|
      selSql: E('#sql-select'),
 | 
						|
      btnRun: E('#sql-run'),
 | 
						|
      btnRunNext: E('#sql-run-next'),
 | 
						|
      btnRunRemaining: E('#sql-run-remaining'),
 | 
						|
      btnExportMetrics: E('#export-metrics'),
 | 
						|
      btnClear: E('#output-clear'),
 | 
						|
      btnReset: E('#db-reset'),
 | 
						|
      cbReverseLog: E('#cb-reverse-log-order'),
 | 
						|
      selImpl: E('#select-impl'),
 | 
						|
      fsToolbar: E('#toolbar')
 | 
						|
    },
 | 
						|
    db: Object.create(null),
 | 
						|
    dbs: Object.create(null),
 | 
						|
    cache:{},
 | 
						|
    log: console.log.bind(console),
 | 
						|
    warn: console.warn.bind(console),
 | 
						|
    cls: function(){this.e.output.innerHTML = ''},
 | 
						|
    logHtml2: function(cssClass,...args){
 | 
						|
      const ln = document.createElement('div');
 | 
						|
      if(cssClass) ln.classList.add(cssClass);
 | 
						|
      ln.append(document.createTextNode(args.join(' ')));
 | 
						|
      this.e.output.append(ln);
 | 
						|
      //this.e.output.lastElementChild.scrollIntoViewIfNeeded();
 | 
						|
    },
 | 
						|
    logHtml: function(...args){
 | 
						|
      console.log(...args);
 | 
						|
      if(1) this.logHtml2('', ...args);
 | 
						|
    },
 | 
						|
    logErr: function(...args){
 | 
						|
      console.error(...args);
 | 
						|
      if(1) this.logHtml2('error', ...args);
 | 
						|
    },
 | 
						|
 | 
						|
    execSql: async function(name,sql){
 | 
						|
      const db = this.getSelectedDb();
 | 
						|
      const banner = "========================================";
 | 
						|
      this.logHtml(banner,
 | 
						|
                   "Running",name,'('+sql.length,'bytes) using',db.id);
 | 
						|
      const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm;
 | 
						|
      let pStmt = 0, pSqlBegin;
 | 
						|
      const stack = wasm.scopedAllocPush();
 | 
						|
      const metrics = db.metrics = Object.create(null);
 | 
						|
      metrics.prepTotal = metrics.stepTotal = 0;
 | 
						|
      metrics.stmtCount = 0;
 | 
						|
      metrics.malloc = 0;
 | 
						|
      metrics.strcpy = 0;
 | 
						|
      this.blockControls(true);
 | 
						|
      if(this.gotErr){
 | 
						|
        this.logErr("Cannot run SQL: error cleanup is pending.");
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      // Run this async so that the UI can be updated for the above header...
 | 
						|
      const endRun = ()=>{
 | 
						|
        metrics.evalSqlEnd = performance.now();
 | 
						|
        metrics.evalTimeTotal = (metrics.evalSqlEnd - metrics.evalSqlStart);
 | 
						|
        this.logHtml(db.id,"metrics:",JSON.stringify(metrics, undefined, ' '));
 | 
						|
        this.logHtml("prepare() count:",metrics.stmtCount);
 | 
						|
        this.logHtml("Time in prepare_v2():",metrics.prepTotal,"ms",
 | 
						|
                     "("+(metrics.prepTotal / metrics.stmtCount),"ms per prepare())");
 | 
						|
        this.logHtml("Time in step():",metrics.stepTotal,"ms",
 | 
						|
                     "("+(metrics.stepTotal / metrics.stmtCount),"ms per step())");
 | 
						|
        this.logHtml("Total runtime:",metrics.evalTimeTotal,"ms");
 | 
						|
        this.logHtml("Overhead (time - prep - step):",
 | 
						|
                     (metrics.evalTimeTotal - metrics.prepTotal - metrics.stepTotal)+"ms");
 | 
						|
        this.logHtml(banner,"End of",name);
 | 
						|
      };
 | 
						|
 | 
						|
      let runner;
 | 
						|
      if('websql'===db.id){
 | 
						|
        const who = this;
 | 
						|
        runner = function(resolve, reject){
 | 
						|
          /* WebSQL cannot execute multiple statements, nor can it execute SQL without
 | 
						|
             an explicit transaction. Thus we have to do some fragile surgery on the
 | 
						|
             input SQL. Since we're only expecting carefully curated inputs, the hope is
 | 
						|
             that this will suffice. PS: it also can't run most SQL functions, e.g. even
 | 
						|
             instr() results in "not authorized". */
 | 
						|
          if('string'!==typeof sql){ // assume TypedArray
 | 
						|
            sql = new TextDecoder().decode(sql);
 | 
						|
          }
 | 
						|
          sql = sql.replace(/-- [^\n]+\n/g,''); // comment lines interfere with our split()
 | 
						|
          const sqls = sql.split(/;+\n/);
 | 
						|
          const rxBegin = /^BEGIN/i, rxCommit = /^COMMIT/i;
 | 
						|
          try {
 | 
						|
            const nextSql = ()=>{
 | 
						|
              let x = sqls.shift();
 | 
						|
              while(sqls.length && !x) x = sqls.shift();
 | 
						|
              return x && x.trim();
 | 
						|
            };
 | 
						|
            const who = this;
 | 
						|
            const transaction = function(tx){
 | 
						|
              try {
 | 
						|
                let s;
 | 
						|
                /* Try to approximate the spirit of the input scripts
 | 
						|
                   by running batches bound by BEGIN/COMMIT statements. */
 | 
						|
                for(s = nextSql(); !!s; s = nextSql()){
 | 
						|
                  if(rxBegin.test(s)) continue;
 | 
						|
                  else if(rxCommit.test(s)) break;
 | 
						|
                  //console.log("websql sql again",sqls.length, s);
 | 
						|
                  ++metrics.stmtCount;
 | 
						|
                  const t = performance.now();
 | 
						|
                  tx.executeSql(s,[], ()=>{}, (t,e)=>{
 | 
						|
                    console.error("WebSQL error",e,"SQL =",s);
 | 
						|
                    who.logErr(e.message);
 | 
						|
                    //throw e;
 | 
						|
                    return false;
 | 
						|
                  });
 | 
						|
                  metrics.stepTotal += performance.now() - t;
 | 
						|
                }
 | 
						|
              }catch(e){
 | 
						|
                who.logErr("transaction():",e.message);
 | 
						|
                throw e;
 | 
						|
              }
 | 
						|
            };
 | 
						|
            const n = sqls.length;
 | 
						|
            const nextBatch = function(){
 | 
						|
              if(sqls.length){
 | 
						|
                console.log("websql sqls.length",sqls.length,'of',n);
 | 
						|
                db.handle.transaction(transaction, (e)=>{
 | 
						|
                  who.logErr("Ignoring and contiuing:",e.message)
 | 
						|
                  //reject(e);
 | 
						|
                  return false;
 | 
						|
                }, nextBatch);
 | 
						|
              }else{
 | 
						|
                resolve(who);
 | 
						|
              }
 | 
						|
            };
 | 
						|
            metrics.evalSqlStart = performance.now();
 | 
						|
            nextBatch();
 | 
						|
          }catch(e){
 | 
						|
            //this.gotErr = e;
 | 
						|
            console.error("websql error:",e);
 | 
						|
            who.logErr(e.message);
 | 
						|
            //reject(e);
 | 
						|
          }
 | 
						|
        }.bind(this);
 | 
						|
      }else{/*sqlite3 db...*/
 | 
						|
        runner = function(resolve, reject){
 | 
						|
          metrics.evalSqlStart = performance.now();
 | 
						|
          try {
 | 
						|
            let t;
 | 
						|
            let sqlByteLen = sql.byteLength;
 | 
						|
            const [ppStmt, pzTail] = wasm.scopedAllocPtr(2);
 | 
						|
            t = performance.now();
 | 
						|
            pSqlBegin = wasm.scopedAlloc( sqlByteLen + 1/*SQL + NUL*/) || toss("alloc(",sqlByteLen,") failed");
 | 
						|
            metrics.malloc = performance.now() - t;
 | 
						|
            metrics.byteLength = sqlByteLen;
 | 
						|
            let pSql = pSqlBegin;
 | 
						|
            const pSqlEnd = pSqlBegin + sqlByteLen;
 | 
						|
            t = performance.now();
 | 
						|
            wasm.heap8().set(sql, pSql);
 | 
						|
            wasm.poke(pSql + sqlByteLen, 0);
 | 
						|
            metrics.strcpy = performance.now() - t;
 | 
						|
            let breaker = 0;
 | 
						|
            while(pSql && wasm.peek(pSql,'i8')){
 | 
						|
              wasm.pokePtr(ppStmt, 0);
 | 
						|
              wasm.pokePtr(pzTail, 0);
 | 
						|
              t = performance.now();
 | 
						|
              let rc = capi.sqlite3_prepare_v3(
 | 
						|
                db.handle, pSql, sqlByteLen, 0, ppStmt, pzTail
 | 
						|
              );
 | 
						|
              metrics.prepTotal += performance.now() - t;
 | 
						|
              checkSqliteRc(db.handle, rc);
 | 
						|
              pStmt = wasm.peekPtr(ppStmt);
 | 
						|
              pSql = wasm.peekPtr(pzTail);
 | 
						|
              sqlByteLen = pSqlEnd - pSql;
 | 
						|
              if(!pStmt) continue/*empty statement*/;
 | 
						|
              ++metrics.stmtCount;
 | 
						|
              t = performance.now();
 | 
						|
              rc = capi.sqlite3_step(pStmt);
 | 
						|
              capi.sqlite3_finalize(pStmt);
 | 
						|
              pStmt = 0;
 | 
						|
              metrics.stepTotal += performance.now() - t;
 | 
						|
              switch(rc){
 | 
						|
                  case capi.SQLITE_ROW:
 | 
						|
                  case capi.SQLITE_DONE: break;
 | 
						|
                  default: checkSqliteRc(db.handle, rc); toss("Not reached.");
 | 
						|
              }
 | 
						|
            }
 | 
						|
            resolve(this);
 | 
						|
          }catch(e){
 | 
						|
            if(pStmt) capi.sqlite3_finalize(pStmt);
 | 
						|
            //this.gotErr = e;
 | 
						|
            reject(e);
 | 
						|
          }finally{
 | 
						|
            capi.sqlite3_exec(db.handle,"rollback;",0,0,0);
 | 
						|
            wasm.scopedAllocPop(stack);
 | 
						|
          }
 | 
						|
        }.bind(this);
 | 
						|
      }
 | 
						|
      let p;
 | 
						|
      if(1){
 | 
						|
        p = new Promise(function(res,rej){
 | 
						|
          setTimeout(()=>runner(res, rej), 50)/*give UI a chance to output the "running" banner*/;
 | 
						|
        });
 | 
						|
      }else{
 | 
						|
        p = new Promise(runner);
 | 
						|
      }
 | 
						|
      return p.catch(
 | 
						|
        (e)=>this.logErr("Error via execSql("+name+",...):",e.message)
 | 
						|
      ).finally(()=>{
 | 
						|
        endRun();
 | 
						|
        this.blockControls(false);
 | 
						|
      });
 | 
						|
    },
 | 
						|
    
 | 
						|
    clearDb: function(){
 | 
						|
      const db = this.getSelectedDb();
 | 
						|
      if('websql'===db.id){
 | 
						|
        this.logErr("TODO: clear websql db.");
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      if(!db.handle) return;
 | 
						|
      const capi = this.sqlite3, wasm = this.sqlite3.wasm;
 | 
						|
      //const scope = wasm.scopedAllocPush(
 | 
						|
      this.logErr("TODO: clear db");
 | 
						|
    },
 | 
						|
    
 | 
						|
    /**
 | 
						|
       Loads batch-runner.list and populates the selection list from
 | 
						|
       it. Returns a promise which resolves to nothing in particular
 | 
						|
       when it completes. Only intended to be run once at the start
 | 
						|
       of the app.
 | 
						|
     */
 | 
						|
    loadSqlList: async function(){
 | 
						|
      const sel = this.e.selSql;
 | 
						|
      sel.innerHTML = '';
 | 
						|
      this.blockControls(true);
 | 
						|
      const infile = 'batch-runner.list';
 | 
						|
      this.logHtml("Loading list of SQL files:", infile);
 | 
						|
      let txt;
 | 
						|
      try{
 | 
						|
        const r = await fetch(infile);
 | 
						|
        if(404 === r.status){
 | 
						|
          toss("Missing file '"+infile+"'.");
 | 
						|
        }
 | 
						|
        if(!r.ok) toss("Loading",infile,"failed:",r.statusText);
 | 
						|
        txt = await r.text();
 | 
						|
        const warning = E('#warn-list');
 | 
						|
        if(warning) warning.remove();
 | 
						|
      }catch(e){
 | 
						|
        this.logErr(e.message);
 | 
						|
        throw e;
 | 
						|
      }finally{
 | 
						|
        this.blockControls(false);
 | 
						|
      }
 | 
						|
      const list = txt.split(/\n+/);
 | 
						|
      let opt;
 | 
						|
      if(0){
 | 
						|
        opt = document.createElement('option');
 | 
						|
        opt.innerText = "Select file to evaluate...";
 | 
						|
        opt.value = '';
 | 
						|
        opt.disabled = true;
 | 
						|
        opt.selected = true;
 | 
						|
        sel.appendChild(opt);
 | 
						|
      }
 | 
						|
      list.forEach(function(fn){
 | 
						|
        if(!fn) return;
 | 
						|
        opt = document.createElement('option');
 | 
						|
        opt.value = fn;
 | 
						|
        opt.innerText = fn.split('/').pop();
 | 
						|
        sel.appendChild(opt);
 | 
						|
      });
 | 
						|
      this.logHtml("Loaded",infile);
 | 
						|
    },
 | 
						|
 | 
						|
    /** Fetch ./fn and return its contents as a Uint8Array. */
 | 
						|
    fetchFile: async function(fn, cacheIt=false){
 | 
						|
      if(cacheIt && this.cache[fn]) return this.cache[fn];
 | 
						|
      this.logHtml("Fetching",fn,"...");
 | 
						|
      let sql;
 | 
						|
      try {
 | 
						|
        const r = await fetch(fn);
 | 
						|
        if(!r.ok) toss("Fetch failed:",r.statusText);
 | 
						|
        sql = new Uint8Array(await r.arrayBuffer());
 | 
						|
      }catch(e){
 | 
						|
        this.logErr(e.message);
 | 
						|
        throw e;
 | 
						|
      }
 | 
						|
      this.logHtml("Fetched",sql.length,"bytes from",fn);
 | 
						|
      if(cacheIt) this.cache[fn] = sql;
 | 
						|
      return sql;
 | 
						|
    }/*fetchFile()*/,
 | 
						|
 | 
						|
    /** Disable or enable certain UI controls. */
 | 
						|
    blockControls: function(disable){
 | 
						|
      //document.querySelectorAll('.disable-during-eval').forEach((e)=>e.disabled = disable);
 | 
						|
      this.e.fsToolbar.disabled = disable;
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
       Converts this.metrics() to a form which is suitable for easy conversion to
 | 
						|
       CSV. It returns an array of arrays. The first sub-array is the column names.
 | 
						|
       The 2nd and subsequent are the values, one per test file (only the most recent
 | 
						|
       metrics are kept for any given file).
 | 
						|
    */
 | 
						|
    metricsToArrays: function(){
 | 
						|
      const rc = [];
 | 
						|
      Object.keys(this.dbs).sort().forEach((k)=>{
 | 
						|
        const d = this.dbs[k];
 | 
						|
        const m = d.metrics;
 | 
						|
        delete m.evalSqlStart;
 | 
						|
        delete m.evalSqlEnd;
 | 
						|
        const mk = Object.keys(m).sort();
 | 
						|
        if(!rc.length){
 | 
						|
          rc.push(['db', ...mk]);
 | 
						|
        }
 | 
						|
        const row = [k.split('/').pop()/*remove dir prefix from filename*/];
 | 
						|
        rc.push(row);
 | 
						|
        row.push(...mk.map((kk)=>m[kk]));
 | 
						|
      });
 | 
						|
      return rc;
 | 
						|
    },
 | 
						|
 | 
						|
    metricsToBlob: function(colSeparator='\t'){
 | 
						|
      const ar = [], ma = this.metricsToArrays();
 | 
						|
      if(!ma.length){
 | 
						|
        this.logErr("Metrics are empty. Run something.");
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      ma.forEach(function(row){
 | 
						|
        ar.push(row.join(colSeparator),'\n');
 | 
						|
      });
 | 
						|
      return new Blob(ar);
 | 
						|
    },
 | 
						|
    
 | 
						|
    downloadMetrics: function(){
 | 
						|
      const b = this.metricsToBlob();
 | 
						|
      if(!b) return;
 | 
						|
      const url = URL.createObjectURL(b);
 | 
						|
      const a = document.createElement('a');
 | 
						|
      a.href = url;
 | 
						|
      a.download = 'batch-runner-js-'+((new Date().getTime()/1000) | 0)+'.csv';
 | 
						|
      this.logHtml("Triggering download of",a.download);
 | 
						|
      document.body.appendChild(a);
 | 
						|
      a.click();
 | 
						|
      setTimeout(()=>{
 | 
						|
        document.body.removeChild(a);
 | 
						|
        URL.revokeObjectURL(url);
 | 
						|
      }, 500);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
       Fetch file fn and eval it as an SQL blob. This is an async
 | 
						|
       operation and returns a Promise which resolves to this
 | 
						|
       object on success.
 | 
						|
    */
 | 
						|
    evalFile: async function(fn){
 | 
						|
      const sql = await this.fetchFile(fn);
 | 
						|
      return this.execSql(fn,sql);
 | 
						|
    }/*evalFile()*/,
 | 
						|
 | 
						|
    /**
 | 
						|
       Clears all DB tables in all _opened_ databases. Because of
 | 
						|
       disparities between backends, we cannot simply "unlink" the
 | 
						|
       databases to clean them up.
 | 
						|
    */
 | 
						|
    clearStorage: function(onlySelectedDb=false){
 | 
						|
      const list = onlySelectedDb
 | 
						|
            ? [('boolean'===typeof onlySelectedDb)
 | 
						|
                ? this.dbs[this.e.selImpl.value]
 | 
						|
                : onlySelectedDb]
 | 
						|
            : Object.values(this.dbs);
 | 
						|
      for(let db of list){
 | 
						|
        if(db && db.handle){
 | 
						|
          this.logHtml("Clearing db",db.id);
 | 
						|
          db.clear();
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
       Fetches the handle of the db associated with
 | 
						|
       this.e.selImpl.value, opening it if needed.
 | 
						|
    */
 | 
						|
    getSelectedDb: function(){
 | 
						|
      if(!this.dbs.memdb){
 | 
						|
        for(let opt of this.e.selImpl.options){
 | 
						|
          const d = this.dbs[opt.value] = Object.create(null);
 | 
						|
          d.id = opt.value;
 | 
						|
          switch(d.id){
 | 
						|
              case 'virtualfs':
 | 
						|
                d.filename = 'file:/virtualfs.sqlite3?vfs=unix-none';
 | 
						|
                break;
 | 
						|
              case 'memdb':
 | 
						|
                d.filename = ':memory:';
 | 
						|
                break;
 | 
						|
              case 'wasmfs-opfs':
 | 
						|
                d.filename = 'file:'+(
 | 
						|
                  this.sqlite3.capi.sqlite3_wasmfs_opfs_dir()
 | 
						|
                )+'/wasmfs-opfs.sqlite3b';
 | 
						|
                break;
 | 
						|
              case 'websql':
 | 
						|
                d.filename = 'websql.db';
 | 
						|
                break;
 | 
						|
              default:
 | 
						|
                this.logErr("Unhandled db selection option (see details in the console).",opt);
 | 
						|
                toss("Unhandled db init option");
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }/*first-time init*/
 | 
						|
      const dbId = this.e.selImpl.value;
 | 
						|
      const d = this.dbs[dbId];
 | 
						|
      if(d.handle) return d;
 | 
						|
      if('websql' === dbId){
 | 
						|
        d.handle = self.openDatabase('batch-runner', '0.1', 'foo', 1024 * 1024 * 50);
 | 
						|
        d.clear = ()=>clearDbWebSQL(d);
 | 
						|
        d.handle.transaction(function(tx){
 | 
						|
          tx.executeSql("PRAGMA cache_size="+cacheSize);
 | 
						|
          App.logHtml(dbId,"cache_size =",cacheSize);
 | 
						|
        });
 | 
						|
      }else{
 | 
						|
        const capi = this.sqlite3.capi, wasm = this.sqlite3.wasm;
 | 
						|
        const stack = wasm.scopedAllocPush();
 | 
						|
        let pDb = 0;
 | 
						|
        try{
 | 
						|
          const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
 | 
						|
          const ppDb = wasm.scopedAllocPtr();
 | 
						|
          const rc = capi.sqlite3_open_v2(d.filename, ppDb, oFlags, null);
 | 
						|
          pDb = wasm.peekPtr(ppDb)
 | 
						|
          if(rc) toss("sqlite3_open_v2() failed with code",rc);
 | 
						|
          capi.sqlite3_exec(pDb, "PRAGMA cache_size="+cacheSize, 0, 0, 0);
 | 
						|
          this.logHtml(dbId,"cache_size =",cacheSize);
 | 
						|
        }catch(e){
 | 
						|
          if(pDb) capi.sqlite3_close_v2(pDb);
 | 
						|
        }finally{
 | 
						|
          wasm.scopedAllocPop(stack);
 | 
						|
        }
 | 
						|
        d.handle = pDb;
 | 
						|
        d.clear = ()=>clearDbSqlite(d);
 | 
						|
      }
 | 
						|
      d.clear();
 | 
						|
      this.logHtml("Opened db:",dbId,d.filename);
 | 
						|
      console.log("db =",d);
 | 
						|
      return d;
 | 
						|
    },
 | 
						|
 | 
						|
    run: function(sqlite3){
 | 
						|
      delete this.run;
 | 
						|
      this.sqlite3 = sqlite3;
 | 
						|
      const capi = sqlite3.capi, wasm = sqlite3.wasm;
 | 
						|
      this.logHtml("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
 | 
						|
      this.logHtml("WASM heap size =",wasm.heap8().length);
 | 
						|
      this.loadSqlList();
 | 
						|
      if(capi.sqlite3_wasmfs_opfs_dir()){
 | 
						|
        E('#warn-opfs').classList.remove('hidden');
 | 
						|
      }else{
 | 
						|
        E('#warn-opfs').remove();
 | 
						|
        E('option[value=wasmfs-opfs]').disabled = true;
 | 
						|
      }
 | 
						|
      if('function' === typeof self.openDatabase){
 | 
						|
        E('#warn-websql').classList.remove('hidden');
 | 
						|
      }else{
 | 
						|
        E('option[value=websql]').disabled = true;
 | 
						|
        E('#warn-websql').remove();
 | 
						|
      }
 | 
						|
      const who = this;
 | 
						|
      if(this.e.cbReverseLog.checked){
 | 
						|
        this.e.output.classList.add('reverse');
 | 
						|
      }
 | 
						|
      this.e.cbReverseLog.addEventListener('change', function(){
 | 
						|
        who.e.output.classList[this.checked ? 'add' : 'remove']('reverse');
 | 
						|
      }, false);
 | 
						|
      this.e.btnClear.addEventListener('click', ()=>this.cls(), false);
 | 
						|
      this.e.btnRun.addEventListener('click', function(){
 | 
						|
        if(!who.e.selSql.value) return;
 | 
						|
        who.evalFile(who.e.selSql.value);
 | 
						|
      }, false);
 | 
						|
      this.e.btnRunNext.addEventListener('click', function(){
 | 
						|
        ++who.e.selSql.selectedIndex;
 | 
						|
        if(!who.e.selSql.value) return;
 | 
						|
        who.evalFile(who.e.selSql.value);
 | 
						|
      }, false);
 | 
						|
      this.e.btnReset.addEventListener('click', function(){
 | 
						|
        who.clearStorage(true);
 | 
						|
      }, false);
 | 
						|
      this.e.btnExportMetrics.addEventListener('click', function(){
 | 
						|
        who.logHtml2('warning',"Triggering download of metrics CSV. Check your downloads folder.");
 | 
						|
        who.downloadMetrics();
 | 
						|
        //const m = who.metricsToArrays();
 | 
						|
        //console.log("Metrics:",who.metrics, m);
 | 
						|
      });
 | 
						|
      this.e.selImpl.addEventListener('change', function(){
 | 
						|
        who.getSelectedDb();
 | 
						|
      });
 | 
						|
      this.e.btnRunRemaining.addEventListener('click', async function(){
 | 
						|
        let v = who.e.selSql.value;
 | 
						|
        const timeStart = performance.now();
 | 
						|
        while(v){
 | 
						|
          await who.evalFile(v);
 | 
						|
          if(who.gotError){
 | 
						|
            who.logErr("Error handling script",v,":",who.gotError.message);
 | 
						|
            break;
 | 
						|
          }
 | 
						|
          ++who.e.selSql.selectedIndex;
 | 
						|
          v = who.e.selSql.value;
 | 
						|
        }
 | 
						|
        const timeTotal = performance.now() - timeStart;
 | 
						|
        who.logHtml("Run-remaining time:",timeTotal,"ms ("+(timeTotal/1000/60)+" minute(s))");
 | 
						|
        who.clearStorage();
 | 
						|
      }, false);
 | 
						|
    }/*run()*/
 | 
						|
  }/*App*/;
 | 
						|
 | 
						|
  self.sqlite3TestModule.initSqlite3().then(function(sqlite3_){
 | 
						|
    sqlite3 = sqlite3_;
 | 
						|
    self.App = App /* only to facilitate dev console access */;
 | 
						|
    App.run(sqlite3);
 | 
						|
  });
 | 
						|
})();
 |