xref: /sqlite-3.40.0/ext/wasm/batch-runner.js (revision 2cae138f)
1/*
2  2022-08-29
3
4  The author disclaims copyright to this source code.  In place of a
5  legal notice, here is a blessing:
6
7  *   May you do good and not evil.
8  *   May you find forgiveness for yourself and forgive others.
9  *   May you share freely, never taking more than you give.
10
11  ***********************************************************************
12
13  A basic batch SQL running for sqlite3-api.js. This file must be run in
14  main JS thread and sqlite3.js must have been loaded before it.
15*/
16'use strict';
17(function(){
18  const T = self.SqliteTestUtil;
19  const toss = function(...args){throw new Error(args.join(' '))};
20  const debug = console.debug.bind(console);
21
22  const App = {
23    e: {
24      output: document.querySelector('#test-output'),
25      selSql: document.querySelector('#sql-select'),
26      btnRun: document.querySelector('#sql-run'),
27      btnClear: document.querySelector('#output-clear')
28    },
29    log: console.log.bind(console),
30    warn: console.warn.bind(console),
31    cls: function(){this.e.output.innerHTML = ''},
32    logHtml2: function(cssClass,...args){
33      const ln = document.createElement('div');
34      if(cssClass) ln.classList.add(cssClass);
35      ln.append(document.createTextNode(args.join(' ')));
36      this.e.output.append(ln);
37    },
38    logHtml: function(...args){
39      console.log(...args);
40      if(1) this.logHtml2('', ...args);
41    },
42    logErr: function(...args){
43      console.error(...args);
44      if(1) this.logHtml2('error', ...args);
45    },
46
47    openDb: function(fn){
48      if(this.pDb){
49        toss("Already have an opened db.");
50      }
51      const capi = this.sqlite3.capi, wasm = capi.wasm;
52      const stack = wasm.scopedAllocPush();
53      let pDb = 0;
54      try{
55        const oFlags = capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
56        const ppDb = wasm.scopedAllocPtr();
57        const rc = capi.sqlite3_open_v2(fn, ppDb, oFlags, null);
58        pDb = wasm.getPtrValue(ppDb)
59      }finally{
60        wasm.scopedAllocPop(stack);
61      }
62      this.logHtml("Opened db:",capi.sqlite3_db_filename(pDb, 'main'));
63      return this.pDb = pDb;
64    },
65
66    closeDb: function(){
67      if(this.pDb){
68        this.sqlite3.capi.sqlite3_close_v2(this.pDb);
69        this.pDb = undefined;
70      }
71    },
72
73    loadSqlList: async function(){
74      const sel = this.e.selSql;
75      sel.innerHTML = '';
76      this.blockControls(true);
77      const infile = 'batch-runner.list';
78      this.logHtml("Loading list of SQL files:", infile);
79      let txt;
80      try{
81        const r = await fetch(infile);
82        if(404 === r.status){
83          toss("Missing file '"+infile+"'.");
84        }
85        if(!r.ok) toss("Loading",infile,"failed:",r.statusText);
86        txt = await r.text();
87      }catch(e){
88        this.logErr(e.message);
89        throw e;
90      }finally{
91        this.blockControls(false);
92      }
93      const list = txt.split('\n');
94      let opt;
95      if(0){
96        opt = document.createElement('option');
97        opt.innerText = "Select file to evaluate...";
98        opt.value = '';
99        opt.disabled = true;
100        opt.selected = true;
101        sel.appendChild(opt);
102      }
103      list.forEach(function(fn){
104        opt = document.createElement('option');
105        opt.value = opt.innerText = fn;
106        sel.appendChild(opt);
107      });
108      this.logHtml("Loaded",infile);
109    },
110
111    /** Fetch ./fn and return its contents as a Uint8Array. */
112    fetchFile: async function(fn){
113      this.logHtml("Fetching",fn,"...");
114      let sql;
115      try {
116        const r = await fetch(fn);
117        if(!r.ok) toss("Fetch failed:",r.statusText);
118        sql = new Uint8Array(await r.arrayBuffer());
119      }catch(e){
120        this.logErr(e.message);
121        throw e;
122      }
123      this.logHtml("Fetched",sql.length,"bytes from",fn);
124      return sql;
125    },
126
127    checkRc: function(rc){
128      if(rc){
129        toss("Prepare failed:",this.sqlite3.capi.sqlite3_errmsg(this.pDb));
130      }
131    },
132
133    blockControls: function(block){
134      [
135        this.e.selSql, this.e.btnRun, this.e.btnClear
136      ].forEach((e)=>e.disabled = block);
137    },
138
139    /** Fetch ./fn and eval it as an SQL blob. */
140    evalFile: async function(fn){
141      const sql = await this.fetchFile(fn);
142      this.logHtml("Running",fn,'...');
143      const capi = this.sqlite3.capi, wasm = capi.wasm;
144      let pStmt = 0, pSqlBegin;
145      const stack = wasm.scopedAllocPush();
146      const metrics = Object.create(null);
147      metrics.prepTotal = metrics.stepTotal = 0;
148      metrics.stmtCount = 0;
149      this.blockControls(true);
150      // Use setTimeout() so that the above log messages run before the loop starts.
151      setTimeout((function(){
152        metrics.timeStart = performance.now();
153        try {
154          let t;
155          let sqlByteLen = sql.byteLength;
156          const [ppStmt, pzTail] = wasm.scopedAllocPtr(2);
157          pSqlBegin = wasm.alloc( sqlByteLen + 1/*SQL + NUL*/);
158          let pSql = pSqlBegin;
159          const pSqlEnd = pSqlBegin + sqlByteLen;
160          wasm.heap8().set(sql, pSql);
161          wasm.setMemValue(pSql + sqlByteLen, 0);
162          while(wasm.getMemValue(pSql,'i8')){
163            pStmt = 0;
164            wasm.setPtrValue(ppStmt, 0);
165            wasm.setPtrValue(pzTail, 0);
166            t = performance.now();
167            let rc = capi.sqlite3_prepare_v3(
168              this.pDb, pSql, sqlByteLen, 0, ppStmt, pzTail
169            );
170            metrics.prepTotal += performance.now() - t;
171            this.checkRc(rc);
172            ++metrics.stmtCount;
173            pStmt = wasm.getPtrValue(ppStmt);
174            pSql = wasm.getPtrValue(pzTail);
175            sqlByteLen = pSqlEnd - pSql;
176            if(!pStmt) continue/*empty statement*/;
177            t = performance.now();
178            rc = capi.sqlite3_step(pStmt);
179            metrics.stepTotal += performance.now() - t;
180            switch(rc){
181                case capi.SQLITE_ROW:
182                case capi.SQLITE_DONE: break;
183                default: this.checkRc(rc); toss("Not reached.");
184            }
185          }
186        }catch(e){
187          this.logErr(e.message);
188          throw e;
189        }finally{
190          wasm.dealloc(pSqlBegin);
191          wasm.scopedAllocPop(stack);
192          this.blockControls(false);
193        }
194        metrics.timeEnd = performance.now();
195        metrics.timeTotal = (metrics.timeEnd - metrics.timeStart);
196        this.logHtml("Metrics:");//,JSON.stringify(metrics, undefined, ' '));
197        this.logHtml("prepare() count:",metrics.stmtCount);
198        this.logHtml("Time in prepare_v2():",metrics.prepTotal,"ms",
199                     "("+(metrics.prepTotal / metrics.stmtCount),"ms per prepare())");
200        this.logHtml("Time in step():",metrics.stepTotal,"ms",
201                     "("+(metrics.stepTotal / metrics.stmtCount),"ms per step())");
202        this.logHtml("Total runtime:",metrics.timeTotal,"ms");
203        this.logHtml("Overhead (time - prep - step):",
204                     (metrics.timeTotal - metrics.prepTotal - metrics.stepTotal)+"ms");
205      }.bind(this)), 10);
206    },
207
208    run: function(sqlite3){
209      this.sqlite3 = sqlite3;
210      const capi = sqlite3.capi, wasm = capi.wasm;
211      this.logHtml("Loaded module:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
212      this.logHtml("WASM heap size =",wasm.heap8().length);
213      this.logHtml("WARNING: if the WASMFS/OPFS layer crashes, this page may",
214                   "become unresponsive and need to be closed and ",
215                   "reloaded to recover.");
216      const pDir = capi.sqlite3_web_persistent_dir();
217      const dbFile = pDir ? pDir+"/speedtest.db" : ":memory:";
218      if(pDir){
219        // We initially need a clean db file, so...
220        capi.sqlite3_wasm_vfs_unlink(dbFile);
221      }
222      this.openDb(dbFile);
223      this.loadSqlList();
224      const who = this;
225      this.e.btnClear.addEventListener('click', ()=>this.cls(), false);
226      this.e.btnRun.addEventListener('click', function(){
227        if(!who.e.selSql.value) return;
228        who.evalFile(who.e.selSql.value);
229      }, false);
230    }
231  }/*App*/;
232
233  self.sqlite3TestModule.initSqlite3().then(function(theEmccModule){
234    self._MODULE = theEmccModule /* this is only to facilitate testing from the console */;
235    App.run(theEmccModule.sqlite3);
236  });
237})();
238