/* 2022-09-19 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 demonstration of the SQLite3 "OO#1" API. */ 'use strict'; (function(){ /** Set up our output channel differently depending on whether we are running in a worker thread or the main (UI) thread. */ let logHtml; if(self.window === self /* UI thread */){ console.log("Running demo from main UI thread."); logHtml = function(cssClass,...args){ const ln = document.createElement('div'); if(cssClass) ln.classList.add(cssClass); ln.append(document.createTextNode(args.join(' '))); document.body.append(ln); }; }else{ /* Worker thread */ console.log("Running demo from Worker thread."); logHtml = function(cssClass,...args){ postMessage({ type:'log', payload:{cssClass, args} }); }; } const log = (...args)=>logHtml('',...args); const warn = (...args)=>logHtml('warning',...args); const error = (...args)=>logHtml('error',...args); const demo1 = function(sqlite3){ const capi = sqlite3.capi/*C-style API*/, oo = sqlite3.oo1/*high-level OO API*/; log("sqlite3 version",capi.sqlite3_libversion(), capi.sqlite3_sourceid()); const db = new oo.DB("/mydb.sqlite3"); log("transient db =",db.filename); /** Never(!) rely on garbage collection to clean up DBs and (especially) prepared statements. Always wrap their lifetimes in a try/finally construct, as demonstrated below. By and large, client code can entirely avoid lifetime-related complications of prepared statement objects by using the DB.exec() method for SQL execution. */ try { log("Create a table..."); db.exec("CREATE TABLE IF NOT EXISTS t(a,b)"); //Equivalent: db.exec({ sql:"CREATE TABLE IF NOT EXISTS t(a,b)" // ... numerous other options ... }); // SQL can be either a string or a byte array // or an array of strings which get concatenated // together as-is (so be sure to end each statement // with a semicolon). log("Insert some data using exec()..."); let i; for( i = 20; i <= 25; ++i ){ db.exec({ sql: "insert into t(a,b) values (?,?)", // bind by parameter index... bind: [i, i*2] }); db.exec({ sql: "insert into t(a,b) values ($a,$b)", // bind by parameter name... bind: {$a: i * 10, $b: i * 20} }); } log("Insert using a prepared statement..."); let q = db.prepare([ // SQL may be a string or array of strings // (concatenated w/o separators). "insert into t(a,b) ", "values(?,?)" ]); try { for( i = 100; i < 103; ++i ){ q.bind( [i, i*2] ).step(); q.reset(); } // Equivalent... for( i = 103; i <= 105; ++i ){ q.bind(1, i).bind(2, i*2).stepReset(); } }finally{ q.finalize(); } log("Query data with exec() using rowMode 'array'..."); db.exec({ sql: "select a from t order by a limit 3", rowMode: 'array', // 'array' (default), 'object', or 'stmt' callback: function(row){ log("row ",++this.counter,"=",row); }.bind({counter: 0}) }); log("Query data with exec() using rowMode 'object'..."); db.exec({ sql: "select a as aa, b as bb from t order by aa limit 3", rowMode: 'object', callback: function(row){ log("row ",++this.counter,"=",JSON.stringify(row)); }.bind({counter: 0}) }); log("Query data with exec() using rowMode 'stmt'..."); db.exec({ sql: "select a from t order by a limit 3", rowMode: 'stmt', callback: function(row){ log("row ",++this.counter,"get(0) =",row.get(0)); }.bind({counter: 0}) }); log("Query data with exec() using rowMode INTEGER (result column index)..."); db.exec({ sql: "select a, b from t order by a limit 3", rowMode: 1, // === result column 1 callback: function(row){ log("row ",++this.counter,"b =",row); }.bind({counter: 0}) }); log("Query data with exec() using rowMode $COLNAME (result column name)..."); db.exec({ sql: "select a a, b from t order by a limit 3", rowMode: '$a', callback: function(value){ log("row ",++this.counter,"a =",value); }.bind({counter: 0}) }); log("Query data with exec() without a callback..."); let resultRows = []; db.exec({ sql: "select a, b from t order by a limit 3", rowMode: 'object', resultRows: resultRows }); log("Result rows:",JSON.stringify(resultRows,undefined,2)); log("Create a scalar UDF..."); db.createFunction({ name: 'twice', callback: function(pCx, arg){ // note the call arg count return arg + arg; } }); log("Run scalar UDF and collect result column names..."); let columnNames = []; db.exec({ sql: "select a, twice(a), twice(''||a) from t order by a desc limit 3", columnNames: columnNames, rowMode: 'stmt', callback: function(row){ log("a =",row.get(0), "twice(a) =", row.get(1), "twice(''||a) =",row.get(2)); } }); log("Result column names:",columnNames); try{ log("The following use of the twice() UDF will", "fail because of incorrect arg count..."); db.exec("select twice(1,2,3)"); }catch(e){ warn("Got expected exception:",e.message); } try { db.transaction( function(D) { D.exec("delete from t"); log("In transaction: count(*) from t =",db.selectValue("select count(*) from t")); throw new sqlite3.SQLite3Error("Demonstrating transaction() rollback"); }); }catch(e){ if(e instanceof sqlite3.SQLite3Error){ log("Got expected exception from db.transaction():",e.message); log("count(*) from t =",db.selectValue("select count(*) from t")); }else{ throw e; } } try { db.savepoint( function(D) { D.exec("delete from t"); log("In savepoint: count(*) from t =",db.selectValue("select count(*) from t")); D.savepoint(function(DD){ const rows = []; DD.exec({ sql: ["insert into t(a,b) values(99,100);", "select count(*) from t"], rowMode: 0, resultRows: rows }); log("In nested savepoint. Row count =",rows[0]); throw new sqlite3.SQLite3Error("Demonstrating nested savepoint() rollback"); }) }); }catch(e){ if(e instanceof sqlite3.SQLite3Error){ log("Got expected exception from nested db.savepoint():",e.message); log("count(*) from t =",db.selectValue("select count(*) from t")); }else{ throw e; } } }finally{ db.close(); } log("That's all, folks!"); /** Some of the features of the OO API not demonstrated above... - get change count (total or statement-local, 32- or 64-bit) - get a DB's file name Misc. Stmt features: - Various forms of bind() - clearBindings() - reset() - Various forms of step() - Variants of get() for explicit type treatment/conversion, e.g. getInt(), getFloat(), getBlob(), getJSON() - getColumnName(ndx), getColumnNames() - getParamIndex(name) */ }/*demo1()*/; log("Loading and initializing sqlite3 module..."); if(self.window!==self) /*worker thread*/{ importScripts("sqlite3.js"); } self.sqlite3InitModule({ // We can redirect any stdout/stderr from the module // like so... print: log, printErr: error }).then(function(sqlite3){ //console.log('sqlite3 =',sqlite3); log("Done initializing. Running demo..."); try { demo1(sqlite3); }catch(e){ error("Exception:",e.message); } }); })();