Add a test for the fixes on this branch.
[sqlite.git] / ext / wasm / demo-123.js
blob9f90ca7568d094bb52f86dafc7a51226bc34d214
1 /*
2   2022-09-19
4   The author disclaims copyright to this source code.  In place of a
5   legal notice, here is a blessing:
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.
11   ***********************************************************************
13   A basic demonstration of the SQLite3 "OO#1" API.
15 'use strict';
16 (function(){
17   /**
18      Set up our output channel differently depending
19      on whether we are running in a worker thread or
20      the main (UI) thread.
21   */
22   let logHtml;
23   if(globalThis.window === globalThis /* UI thread */){
24     console.log("Running demo from main UI thread.");
25     logHtml = function(cssClass,...args){
26       const ln = document.createElement('div');
27       if(cssClass) ln.classList.add(cssClass);
28       ln.append(document.createTextNode(args.join(' ')));
29       document.body.append(ln);
30     };
31   }else{ /* Worker thread */
32     console.log("Running demo from Worker thread.");
33     logHtml = function(cssClass,...args){
34       postMessage({
35         type:'log',
36         payload:{cssClass, args}
37       });
38     };
39   }
40   const log = (...args)=>logHtml('',...args);
41   const warn = (...args)=>logHtml('warning',...args);
42   const error = (...args)=>logHtml('error',...args);
44   const demo1 = function(sqlite3){
45     const capi = sqlite3.capi/*C-style API*/,
46           oo = sqlite3.oo1/*high-level OO API*/;
47     log("sqlite3 version",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
48     const db = new oo.DB("/mydb.sqlite3",'ct');
49     log("transient db =",db.filename);
50     /**
51        Never(!) rely on garbage collection to clean up DBs and
52        (especially) prepared statements. Always wrap their lifetimes
53        in a try/finally construct, as demonstrated below. By and
54        large, client code can entirely avoid lifetime-related
55        complications of prepared statement objects by using the
56        DB.exec() method for SQL execution.
57     */
58     try {
59       log("Create a table...");
60       db.exec("CREATE TABLE IF NOT EXISTS t(a,b)");
61       //Equivalent:
62       db.exec({
63         sql:"CREATE TABLE IF NOT EXISTS t(a,b)"
64         // ... numerous other options ... 
65       });
66       // SQL can be either a string or a byte array
67       // or an array of strings which get concatenated
68       // together as-is (so be sure to end each statement
69       // with a semicolon).
71       log("Insert some data using exec()...");
72       let i;
73       for( i = 20; i <= 25; ++i ){
74         db.exec({
75           sql: "insert into t(a,b) values (?,?)",
76           // bind by parameter index...
77           bind: [i, i*2]
78         });
79         db.exec({
80           sql: "insert into t(a,b) values ($a,$b)",
81           // bind by parameter name...
82           bind: {$a: i * 10, $b: i * 20}
83         });
84       }    
86       log("Insert using a prepared statement...");
87       let q = db.prepare([
88         // SQL may be a string or array of strings
89         // (concatenated w/o separators).
90         "insert into t(a,b) ",
91         "values(?,?)"
92       ]);
93       try {
94         for( i = 100; i < 103; ++i ){
95           q.bind( [i, i*2] ).step();
96           q.reset();
97         }
98         // Equivalent...
99         for( i = 103; i <= 105; ++i ){
100           q.bind(1, i).bind(2, i*2).stepReset();
101         }
102       }finally{
103         q.finalize();
104       }
106       log("Query data with exec() using rowMode 'array'...");
107       db.exec({
108         sql: "select a from t order by a limit 3",
109         rowMode: 'array', // 'array' (default), 'object', or 'stmt'
110         callback: function(row){
111           log("row ",++this.counter,"=",row);
112         }.bind({counter: 0})
113       });
115       log("Query data with exec() using rowMode 'object'...");
116       db.exec({
117         sql: "select a as aa, b as bb from t order by aa limit 3",
118         rowMode: 'object',
119         callback: function(row){
120           log("row ",++this.counter,"=",JSON.stringify(row));
121         }.bind({counter: 0})
122       });
124       log("Query data with exec() using rowMode 'stmt'...");
125       db.exec({
126         sql: "select a from t order by a limit 3",
127         rowMode: 'stmt',
128         callback: function(row){
129           log("row ",++this.counter,"get(0) =",row.get(0));
130         }.bind({counter: 0})
131       });
133       log("Query data with exec() using rowMode INTEGER (result column index)...");
134       db.exec({
135         sql: "select a, b from t order by a limit 3",
136         rowMode: 1, // === result column 1
137         callback: function(row){
138           log("row ",++this.counter,"b =",row);
139         }.bind({counter: 0})
140       });
142       log("Query data with exec() using rowMode $COLNAME (result column name)...");
143       db.exec({
144         sql: "select a a, b from t order by a limit 3",
145         rowMode: '$a',
146         callback: function(value){
147           log("row ",++this.counter,"a =",value);
148         }.bind({counter: 0})
149       });
151       log("Query data with exec() without a callback...");
152       let resultRows = [];
153       db.exec({
154         sql: "select a, b from t order by a limit 3",
155         rowMode: 'object',
156         resultRows: resultRows
157       });
158       log("Result rows:",JSON.stringify(resultRows,undefined,2));
160       log("Create a scalar UDF...");
161       db.createFunction({
162         name: 'twice',
163         xFunc: function(pCx, arg){ // note the call arg count
164           return arg + arg;
165         }
166       });
167       log("Run scalar UDF and collect result column names...");
168       let columnNames = [];
169       db.exec({
170         sql: "select a, twice(a), twice(''||a) from t order by a desc limit 3",
171         columnNames: columnNames,
172         rowMode: 'stmt',
173         callback: function(row){
174           log("a =",row.get(0), "twice(a) =", row.get(1),
175               "twice(''||a) =",row.get(2));
176         }
177       });
178       log("Result column names:",columnNames);
180       try{
181         log("The following use of the twice() UDF will",
182             "fail because of incorrect arg count...");
183         db.exec("select twice(1,2,3)");
184       }catch(e){
185         warn("Got expected exception:",e.message);
186       }
188       try {
189         db.transaction( function(D) {
190           D.exec("delete from t");
191           log("In transaction: count(*) from t =",db.selectValue("select count(*) from t"));
192           throw new sqlite3.SQLite3Error("Demonstrating transaction() rollback");
193         });
194       }catch(e){
195         if(e instanceof sqlite3.SQLite3Error){
196           log("Got expected exception from db.transaction():",e.message);
197           log("count(*) from t =",db.selectValue("select count(*) from t"));
198         }else{
199           throw e;
200         }
201       }
203       try {
204         db.savepoint( function(D) {
205           D.exec("delete from t");
206           log("In savepoint: count(*) from t =",db.selectValue("select count(*) from t"));
207           D.savepoint(function(DD){
208             const rows = [];
209             DD.exec({
210               sql: ["insert into t(a,b) values(99,100);",
211                     "select count(*) from t"],
212               rowMode: 0,
213               resultRows: rows
214             });
215             log("In nested savepoint. Row count =",rows[0]);
216             throw new sqlite3.SQLite3Error("Demonstrating nested savepoint() rollback");
217           })
218         });
219       }catch(e){
220         if(e instanceof sqlite3.SQLite3Error){
221           log("Got expected exception from nested db.savepoint():",e.message);
222           log("count(*) from t =",db.selectValue("select count(*) from t"));
223         }else{
224           throw e;
225         }
226       }
227     }finally{
228       db.close();
229     }
231     log("That's all, folks!");
233     /**
234        Some of the features of the OO API not demonstrated above...
236        - get change count (total or statement-local, 32- or 64-bit)
237        - get a DB's file name
239        Misc. Stmt features:
241        - Various forms of bind()
242        - clearBindings()
243        - reset()
244        - Various forms of step()
245        - Variants of get() for explicit type treatment/conversion,
246          e.g. getInt(), getFloat(), getBlob(), getJSON()
247        - getColumnName(ndx), getColumnNames()
248        - getParamIndex(name)
249     */
250   }/*demo1()*/;
252   log("Loading and initializing sqlite3 module...");
253   if(globalThis.window!==globalThis) /*worker thread*/{
254     /*
255       If sqlite3.js is in a directory other than this script, in order
256       to get sqlite3.js to resolve sqlite3.wasm properly, we have to
257       explicitly tell it where sqlite3.js is being loaded from. We do
258       that by passing the `sqlite3.dir=theDirName` URL argument to
259       _this_ script. That URL argument will be seen by the JS/WASM
260       loader and it will adjust the sqlite3.wasm path accordingly. If
261       sqlite3.js/.wasm are in the same directory as this script then
262       that's not needed.
264       URL arguments passed as part of the filename via importScripts()
265       are simply lost, and such scripts see the globalThis.location of
266       _this_ script.
267     */
268     let sqlite3Js = 'sqlite3.js';
269     const urlParams = new URL(globalThis.location.href).searchParams;
270     if(urlParams.has('sqlite3.dir')){
271       sqlite3Js = urlParams.get('sqlite3.dir') + '/' + sqlite3Js;
272     }
273     importScripts(sqlite3Js);
274   }
275   globalThis.sqlite3InitModule({
276     /* We can redirect any stdout/stderr from the module like so, but
277        note that doing so makes use of Emscripten-isms, not
278        well-defined sqlite APIs. */
279     print: log,
280     printErr: error
281   }).then(function(sqlite3){
282     //console.log('sqlite3 =',sqlite3);
283     log("Done initializing. Running demo...");
284     try {
285       demo1(sqlite3);
286     }catch(e){
287       error("Exception:",e.message);
288     }
289   });
290 })();