13961b263Sstephan/*
23961b263Sstephan  2022-07-22
33961b263Sstephan
43961b263Sstephan  The author disclaims copyright to this source code.  In place of a
53961b263Sstephan  legal notice, here is a blessing:
63961b263Sstephan
73961b263Sstephan  *   May you do good and not evil.
83961b263Sstephan  *   May you find forgiveness for yourself and forgive others.
93961b263Sstephan  *   May you share freely, never taking more than you give.
103961b263Sstephan
113961b263Sstephan  ***********************************************************************
123961b263Sstephan
133961b263Sstephan  This file contains the so-called OO #1 API wrapper for the sqlite3
143961b263Sstephan  WASM build. It requires that sqlite3-api-glue.js has already run
153961b263Sstephan  and it installs its deliverable as self.sqlite3.oo1.
163961b263Sstephan*/
17e3cd6760Sstephanself.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
183961b263Sstephan  const toss = (...args)=>{throw new Error(args.join(' '))};
19193ee11fSstephan  const toss3 = (...args)=>{throw new sqlite3.SQLite3Error(...args)};
203961b263Sstephan
218948fbeeSstephan  const capi = sqlite3.capi, wasm = sqlite3.wasm, util = sqlite3.util;
223961b263Sstephan  /* What follows is colloquially known as "OO API #1". It is a
233961b263Sstephan     binding of the sqlite3 API which is designed to be run within
243961b263Sstephan     the same thread (main or worker) as the one in which the
253961b263Sstephan     sqlite3 WASM binding was initialized. This wrapper cannot use
263961b263Sstephan     the sqlite3 binding if, e.g., the wrapper is in the main thread
273961b263Sstephan     and the sqlite3 API is in a worker. */
283961b263Sstephan
293961b263Sstephan  /**
303961b263Sstephan     In order to keep clients from manipulating, perhaps
313961b263Sstephan     inadvertently, the underlying pointer values of DB and Stmt
323961b263Sstephan     instances, we'll gate access to them via the `pointer` property
333961b263Sstephan     accessor and store their real values in this map. Keys = DB/Stmt
343961b263Sstephan     objects, values = pointer values. This also unifies how those are
353961b263Sstephan     accessed, for potential use downstream via custom
3663e9ec2fSstephan     wasm.xWrap() function signatures which know how to extract
373961b263Sstephan     it.
383961b263Sstephan  */
393961b263Sstephan  const __ptrMap = new WeakMap();
403961b263Sstephan  /**
413961b263Sstephan     Map of DB instances to objects, each object being a map of Stmt
423961b263Sstephan     wasm pointers to Stmt objects.
433961b263Sstephan  */
443961b263Sstephan  const __stmtMap = new WeakMap();
453961b263Sstephan
463961b263Sstephan  /** If object opts has _its own_ property named p then that
473961b263Sstephan      property's value is returned, else dflt is returned. */
48824bb5b8Sstephan  const getOwnOption = (opts, p, dflt)=>{
49824bb5b8Sstephan    const d = Object.getOwnPropertyDescriptor(opts,p);
50824bb5b8Sstephan    return d ? d.value : dflt;
51824bb5b8Sstephan  };
523961b263Sstephan
535360f5fcSstephan  // Documented in DB.checkRc()
545360f5fcSstephan  const checkSqlite3Rc = function(dbPtr, sqliteResultCode){
555360f5fcSstephan    if(sqliteResultCode){
565360f5fcSstephan      if(dbPtr instanceof DB) dbPtr = dbPtr.pointer;
57193ee11fSstephan      toss3(
585360f5fcSstephan        "sqlite result code",sqliteResultCode+":",
595360f5fcSstephan        (dbPtr
605360f5fcSstephan         ? capi.sqlite3_errmsg(dbPtr)
615360f5fcSstephan         : capi.sqlite3_errstr(sqliteResultCode))
625360f5fcSstephan      );
635360f5fcSstephan    }
645360f5fcSstephan  };
655360f5fcSstephan
665360f5fcSstephan  /**
674f5bbedbSstephan     sqlite3_trace_v2() callback which gets installed by the DB ctor
684f5bbedbSstephan     if its open-flags contain "t".
694f5bbedbSstephan  */
704f5bbedbSstephan  const __dbTraceToConsole =
714f5bbedbSstephan        wasm.installFunction('i(ippp)', function(t,c,p,x){
724f5bbedbSstephan          if(capi.SQLITE_TRACE_STMT===t){
734f5bbedbSstephan            // x == SQL, p == sqlite3_stmt*
744f5bbedbSstephan            console.log("SQL TRACE #"+(++this.counter),
754f5bbedbSstephan                        wasm.cstringToJs(x));
764f5bbedbSstephan          }
774f5bbedbSstephan        }.bind({counter: 0}));
784f5bbedbSstephan
794f5bbedbSstephan  /**
804f5bbedbSstephan     A map of sqlite3_vfs pointers to SQL code to run when the DB
814f5bbedbSstephan     constructor opens a database with the given VFS.
824f5bbedbSstephan  */
834f5bbedbSstephan  const __vfsPostOpenSql = Object.create(null);
844f5bbedbSstephan
854f5bbedbSstephan  /**
865360f5fcSstephan     A proxy for DB class constructors. It must be called with the
87f3860120Sstephan     being-construct DB object as its "this". See the DB constructor
88f3860120Sstephan     for the argument docs. This is split into a separate function
89f3860120Sstephan     in order to enable simple creation of special-case DB constructors,
9098147dd5Sstephan     e.g. JsStorageDb and OpfsDb.
91f3860120Sstephan
92f3860120Sstephan     Expects to be passed a configuration object with the following
93f3860120Sstephan     properties:
94f3860120Sstephan
95f3860120Sstephan     - `.filename`: the db filename. It may be a special name like ":memory:"
96f3860120Sstephan       or "".
97f3860120Sstephan
98f3860120Sstephan     - `.flags`: as documented in the DB constructor.
99f3860120Sstephan
100f3860120Sstephan     - `.vfs`: as documented in the DB constructor.
101f3860120Sstephan
102f3860120Sstephan     It also accepts those as the first 3 arguments.
1035360f5fcSstephan  */
104f3860120Sstephan  const dbCtorHelper = function ctor(...args){
1055360f5fcSstephan    if(!ctor._name2vfs){
106fa5aac74Sstephan      /**
107fa5aac74Sstephan         Map special filenames which we handle here (instead of in C)
108fa5aac74Sstephan         to some helpful metadata...
109fa5aac74Sstephan
110fa5aac74Sstephan         As of 2022-09-20, the C API supports the names :localStorage:
111fa5aac74Sstephan         and :sessionStorage: for kvvfs. However, C code cannot
112fa5aac74Sstephan         determine (without embedded JS code, e.g. via Emscripten's
113fa5aac74Sstephan         EM_JS()) whether the kvvfs is legal in the current browser
114fa5aac74Sstephan         context (namely the main UI thread). In order to help client
115fa5aac74Sstephan         code fail early on, instead of it being delayed until they
116fa5aac74Sstephan         try to read or write a kvvfs-backed db, we'll check for those
117fa5aac74Sstephan         names here and throw if they're not legal in the current
118fa5aac74Sstephan         context.
119fa5aac74Sstephan      */
1205360f5fcSstephan      ctor._name2vfs = Object.create(null);
121fa5aac74Sstephan      const isWorkerThread = ('function'===typeof importScripts/*===running in worker thread*/)
122fa5aac74Sstephan            ? (n)=>toss3("The VFS for",n,"is only available in the main window thread.")
123fa5aac74Sstephan            : false;
1245360f5fcSstephan      ctor._name2vfs[':localStorage:'] = {
1254f5bbedbSstephan        vfs: 'kvvfs', filename: isWorkerThread || (()=>'local')
1265360f5fcSstephan      };
1275360f5fcSstephan      ctor._name2vfs[':sessionStorage:'] = {
1284f5bbedbSstephan        vfs: 'kvvfs', filename: isWorkerThread || (()=>'session')
1295360f5fcSstephan      };
1305360f5fcSstephan    }
131f3860120Sstephan    const opt = ctor.normalizeArgs(...args);
132f3860120Sstephan    let fn = opt.filename, vfsName = opt.vfs, flagsStr = opt.flags;
133f3860120Sstephan    if(('string'!==typeof fn && 'number'!==typeof fn)
134f3860120Sstephan       || 'string'!==typeof flagsStr
135f3860120Sstephan       || (vfsName && ('string'!==typeof vfsName && 'number'!==typeof vfsName))){
136f3860120Sstephan      console.error("Invalid DB ctor args",opt,arguments);
137f3860120Sstephan      toss3("Invalid arguments for DB constructor.");
1385360f5fcSstephan    }
13963e9ec2fSstephan    let fnJs = ('number'===typeof fn) ? wasm.cstringToJs(fn) : fn;
140f3860120Sstephan    const vfsCheck = ctor._name2vfs[fnJs];
1415360f5fcSstephan    if(vfsCheck){
1425360f5fcSstephan      vfsName = vfsCheck.vfs;
143f3860120Sstephan      fn = fnJs = vfsCheck.filename(fnJs);
1445360f5fcSstephan    }
1454f5bbedbSstephan    let pDb, oflags = 0;
146f3860120Sstephan    if( flagsStr.indexOf('c')>=0 ){
1475360f5fcSstephan      oflags |= capi.SQLITE_OPEN_CREATE | capi.SQLITE_OPEN_READWRITE;
1485360f5fcSstephan    }
149f3860120Sstephan    if( flagsStr.indexOf('w')>=0 ) oflags |= capi.SQLITE_OPEN_READWRITE;
1505360f5fcSstephan    if( 0===oflags ) oflags |= capi.SQLITE_OPEN_READONLY;
1515360f5fcSstephan    oflags |= capi.SQLITE_OPEN_EXRESCODE;
15296b6371dSstephan    const stack = wasm.pstack.pointer;
1535360f5fcSstephan    try {
15496b6371dSstephan      const pPtr = wasm.pstack.allocPtr() /* output (sqlite3**) arg */;
15598147dd5Sstephan      let rc = capi.sqlite3_open_v2(fn, pPtr, oflags, vfsName || 0);
1564f5bbedbSstephan      pDb = wasm.getPtrValue(pPtr);
1574f5bbedbSstephan      checkSqlite3Rc(pDb, rc);
1584f5bbedbSstephan      if(flagsStr.indexOf('t')>=0){
1594f5bbedbSstephan        capi.sqlite3_trace_v2(pDb, capi.SQLITE_TRACE_STMT,
1604f5bbedbSstephan                              __dbTraceToConsole, 0);
1614f5bbedbSstephan      }
1624f5bbedbSstephan      // Check for per-VFS post-open SQL...
16396b6371dSstephan      const pVfs = capi.sqlite3_js_db_vfs(pDb);
16496b6371dSstephan      //console.warn("Opened db",fn,"with vfs",vfsName,pVfs);
16596b6371dSstephan      if(!pVfs) toss3("Internal error: cannot get VFS for new db handle.");
16696b6371dSstephan      const postInitSql = __vfsPostOpenSql[pVfs];
1674f5bbedbSstephan      if(postInitSql){
1684f5bbedbSstephan        rc = capi.sqlite3_exec(pDb, postInitSql, 0, 0, 0);
1694f5bbedbSstephan        checkSqlite3Rc(pDb, rc);
1704f5bbedbSstephan      }
1715360f5fcSstephan    }catch( e ){
1724f5bbedbSstephan      if( pDb ) capi.sqlite3_close_v2(pDb);
1735360f5fcSstephan      throw e;
1745360f5fcSstephan    }finally{
17596b6371dSstephan      wasm.pstack.restore(stack);
1765360f5fcSstephan    }
177f3860120Sstephan    this.filename = fnJs;
1784f5bbedbSstephan    __ptrMap.set(this, pDb);
1795360f5fcSstephan    __stmtMap.set(this, Object.create(null));
1805360f5fcSstephan  };
1815360f5fcSstephan
1823961b263Sstephan  /**
1834f5bbedbSstephan     Sets SQL which should be exec()'d on a DB instance after it is
1844f5bbedbSstephan     opened with the given VFS pointer. This is intended only for use
1854f5bbedbSstephan     by DB subclasses or sqlite3_vfs implementations.
1864f5bbedbSstephan  */
1874f5bbedbSstephan  dbCtorHelper.setVfsPostOpenSql = function(pVfs, sql){
1884f5bbedbSstephan    __vfsPostOpenSql[pVfs] = sql;
1894f5bbedbSstephan  };
1904f5bbedbSstephan
1914f5bbedbSstephan  /**
192f3860120Sstephan     A helper for DB constructors. It accepts either a single
193f3860120Sstephan     config-style object or up to 3 arguments (filename, dbOpenFlags,
194f3860120Sstephan     dbVfsName). It returns a new object containing:
195f3860120Sstephan
196f3860120Sstephan     { filename: ..., flags: ..., vfs: ... }
197f3860120Sstephan
198f3860120Sstephan     If passed an object, any additional properties it has are copied
199f3860120Sstephan     as-is into the new object.
200f3860120Sstephan  */
201193ee11fSstephan  dbCtorHelper.normalizeArgs = function(filename=':memory:',flags = 'c',vfs = null){
202f3860120Sstephan    const arg = {};
203f3860120Sstephan    if(1===arguments.length && 'object'===typeof arguments[0]){
204f3860120Sstephan      const x = arguments[0];
205f3860120Sstephan      Object.keys(x).forEach((k)=>arg[k] = x[k]);
206f3860120Sstephan      if(undefined===arg.flags) arg.flags = 'c';
207f3860120Sstephan      if(undefined===arg.vfs) arg.vfs = null;
208193ee11fSstephan      if(undefined===arg.filename) arg.filename = ':memory:';
209f3860120Sstephan    }else{
210f3860120Sstephan      arg.filename = filename;
211f3860120Sstephan      arg.flags = flags;
212f3860120Sstephan      arg.vfs = vfs;
213f3860120Sstephan    }
214f3860120Sstephan    return arg;
215f3860120Sstephan  };
216f3860120Sstephan  /**
2173961b263Sstephan     The DB class provides a high-level OO wrapper around an sqlite3
2183961b263Sstephan     db handle.
2193961b263Sstephan
2203961b263Sstephan     The given db filename must be resolvable using whatever
2213961b263Sstephan     filesystem layer (virtual or otherwise) is set up for the default
2223961b263Sstephan     sqlite3 VFS.
2233961b263Sstephan
2243961b263Sstephan     Note that the special sqlite3 db names ":memory:" and ""
2253961b263Sstephan     (temporary db) have their normal special meanings here and need
2263961b263Sstephan     not resolve to real filenames, but "" uses an on-storage
2273961b263Sstephan     temporary database and requires that the VFS support that.
2283961b263Sstephan
229e0c58285Sstephan     The second argument specifies the open/create mode for the
230e0c58285Sstephan     database. It must be string containing a sequence of letters (in
231e0c58285Sstephan     any order, but case sensitive) specifying the mode:
232e0c58285Sstephan
2334f5bbedbSstephan     - "c": create if it does not exist, else fail if it does not
234e0c58285Sstephan       exist. Implies the "w" flag.
235e0c58285Sstephan
2364f5bbedbSstephan     - "w": write. Implies "r": a db cannot be write-only.
237e0c58285Sstephan
2384f5bbedbSstephan     - "r": read-only if neither "w" nor "c" are provided, else it
239e0c58285Sstephan       is ignored.
240e0c58285Sstephan
2414f5bbedbSstephan     - "t": enable tracing of SQL executed on this database handle,
24257956a9cSstephan       sending it to `console.log()`. To disable it later, call
24357956a9cSstephan       `sqlite3.capi.sqlite3_trace_v2(thisDb.pointer, 0, 0, 0)`.
2444f5bbedbSstephan
24557956a9cSstephan     If "w" is not provided, the db is implicitly read-only, noting
24657956a9cSstephan     that "rc" is meaningless
247e0c58285Sstephan
248e0c58285Sstephan     Any other letters are currently ignored. The default is
249e0c58285Sstephan     "c". These modes are ignored for the special ":memory:" and ""
25057956a9cSstephan     names and _may_ be ignored altogether for certain VFSes.
251e0c58285Sstephan
2525360f5fcSstephan     The final argument is analogous to the final argument of
2535360f5fcSstephan     sqlite3_open_v2(): the name of an sqlite3 VFS. Pass a falsy value,
254193ee11fSstephan     or none at all, to use the default. If passed a value, it must
25557956a9cSstephan     be the string name of a VFS.
2563961b263Sstephan
257f3860120Sstephan     The constructor optionally (and preferably) takes its arguments
258f3860120Sstephan     in the form of a single configuration object with the following
259f3860120Sstephan     properties:
260f3860120Sstephan
26149048b14Sstephan     - `filename`: database file name
26249048b14Sstephan     - `flags`: open-mode flags
26349048b14Sstephan     - `vfs`: the VFS fname
264f3860120Sstephan
265f3860120Sstephan     The `filename` and `vfs` arguments may be either JS strings or
26657956a9cSstephan     C-strings allocated via WASM. `flags` is required to be a JS
26757956a9cSstephan     string (because it's specific to this API, which is specific
26857956a9cSstephan     to JS).
269f3860120Sstephan
2703961b263Sstephan     For purposes of passing a DB instance to C-style sqlite3
271e0c58285Sstephan     functions, the DB object's read-only `pointer` property holds its
272e0c58285Sstephan     `sqlite3*` pointer value. That property can also be used to check
273e0c58285Sstephan     whether this DB instance is still open.
2745360f5fcSstephan
27557956a9cSstephan     In the main window thread, the filenames `":localStorage:"` and
27657956a9cSstephan     `":sessionStorage:"` are special: they cause the db to use either
2774f5bbedbSstephan     localStorage or sessionStorage for storing the database using
27857956a9cSstephan     the kvvfs. If one of these names are used, they trump
27957956a9cSstephan     any vfs name set in the arguments.
2803961b263Sstephan  */
281f3860120Sstephan  const DB = function(...args){
282f3860120Sstephan    dbCtorHelper.apply(this, args);
2833961b263Sstephan  };
284e681b651Sstephan  DB.dbCtorHelper = dbCtorHelper;
2853961b263Sstephan
2863961b263Sstephan  /**
2873961b263Sstephan     Internal-use enum for mapping JS types to DB-bindable types.
2883961b263Sstephan     These do not (and need not) line up with the SQLITE_type
2893961b263Sstephan     values. All values in this enum must be truthy and distinct
2903961b263Sstephan     but they need not be numbers.
2913961b263Sstephan  */
2923961b263Sstephan  const BindTypes = {
2933961b263Sstephan    null: 1,
2943961b263Sstephan    number: 2,
2953961b263Sstephan    string: 3,
2963961b263Sstephan    boolean: 4,
2973961b263Sstephan    blob: 5
2983961b263Sstephan  };
2993961b263Sstephan  BindTypes['undefined'] == BindTypes.null;
30063e9ec2fSstephan  if(wasm.bigIntEnabled){
3013961b263Sstephan    BindTypes.bigint = BindTypes.number;
3023961b263Sstephan  }
3033961b263Sstephan
3043961b263Sstephan  /**
3053961b263Sstephan     This class wraps sqlite3_stmt. Calling this constructor
3063961b263Sstephan     directly will trigger an exception. Use DB.prepare() to create
3073961b263Sstephan     new instances.
3083961b263Sstephan
3093961b263Sstephan     For purposes of passing a Stmt instance to C-style sqlite3
3103961b263Sstephan     functions, its read-only `pointer` property holds its `sqlite3_stmt*`
3113961b263Sstephan     pointer value.
31249cb8d73Sstephan
31349cb8d73Sstephan     Other non-function properties include:
31449cb8d73Sstephan
31549cb8d73Sstephan     - `db`: the DB object which created the statement.
31649cb8d73Sstephan
31749cb8d73Sstephan     - `columnCount`: the number of result columns in the query, or 0 for
31849cb8d73Sstephan     queries which cannot return results.
31949cb8d73Sstephan
32049cb8d73Sstephan     - `parameterCount`: the number of bindable paramters in the query.
3213961b263Sstephan  */
3223961b263Sstephan  const Stmt = function(){
3233961b263Sstephan    if(BindTypes!==arguments[2]){
3243961b263Sstephan      toss3("Do not call the Stmt constructor directly. Use DB.prepare().");
3253961b263Sstephan    }
3263961b263Sstephan    this.db = arguments[0];
3273961b263Sstephan    __ptrMap.set(this, arguments[1]);
3283961b263Sstephan    this.columnCount = capi.sqlite3_column_count(this.pointer);
3293961b263Sstephan    this.parameterCount = capi.sqlite3_bind_parameter_count(this.pointer);
3303961b263Sstephan  };
3313961b263Sstephan
3323961b263Sstephan  /** Throws if the given DB has been closed, else it is returned. */
3333961b263Sstephan  const affirmDbOpen = function(db){
3343961b263Sstephan    if(!db.pointer) toss3("DB has been closed.");
3353961b263Sstephan    return db;
3363961b263Sstephan  };
3373961b263Sstephan
3383961b263Sstephan  /** Throws if ndx is not an integer or if it is out of range
3393961b263Sstephan      for stmt.columnCount, else returns stmt.
3403961b263Sstephan
3413961b263Sstephan      Reminder: this will also fail after the statement is finalized
3423961b263Sstephan      but the resulting error will be about an out-of-bounds column
34309aa80d1Sstephan      index rather than a statement-is-finalized error.
3443961b263Sstephan  */
3453961b263Sstephan  const affirmColIndex = function(stmt,ndx){
3463961b263Sstephan    if((ndx !== (ndx|0)) || ndx<0 || ndx>=stmt.columnCount){
3473961b263Sstephan      toss3("Column index",ndx,"is out of range.");
3483961b263Sstephan    }
3493961b263Sstephan    return stmt;
3503961b263Sstephan  };
3513961b263Sstephan
3523961b263Sstephan  /**
353335ad526Sstephan     Expects to be passed the `arguments` object from DB.exec(). Does
354335ad526Sstephan     the argument processing/validation, throws on error, and returns
355335ad526Sstephan     a new object on success:
3563961b263Sstephan
3573961b263Sstephan     { sql: the SQL, opt: optionsObj, cbArg: function}
3583961b263Sstephan
35909aa80d1Sstephan     The opt object is a normalized copy of any passed to this
36009aa80d1Sstephan     function. The sql will be converted to a string if it is provided
36109aa80d1Sstephan     in one of the supported non-string formats.
36209aa80d1Sstephan
36309aa80d1Sstephan     cbArg is only set if the opt.callback or opt.resultRows are set,
36409aa80d1Sstephan     in which case it's a function which expects to be passed the
36509aa80d1Sstephan     current Stmt and returns the callback argument of the type
36609aa80d1Sstephan     indicated by the input arguments.
3673961b263Sstephan  */
3681acfe915Sstephan  const parseExecArgs = function(db, args){
3693961b263Sstephan    const out = Object.create(null);
3703961b263Sstephan    out.opt = Object.create(null);
3713961b263Sstephan    switch(args.length){
3723961b263Sstephan        case 1:
3733961b263Sstephan          if('string'===typeof args[0] || util.isSQLableTypedArray(args[0])){
3743961b263Sstephan            out.sql = args[0];
375a6ca996eSstephan          }else if(Array.isArray(args[0])){
376a6ca996eSstephan            out.sql = args[0];
3773961b263Sstephan          }else if(args[0] && 'object'===typeof args[0]){
3783961b263Sstephan            out.opt = args[0];
3793961b263Sstephan            out.sql = out.opt.sql;
3803961b263Sstephan          }
3813961b263Sstephan          break;
3823961b263Sstephan        case 2:
3833961b263Sstephan          out.sql = args[0];
3843961b263Sstephan          out.opt = args[1];
3853961b263Sstephan          break;
3863961b263Sstephan        default: toss3("Invalid argument count for exec().");
3873961b263Sstephan    };
3881acfe915Sstephan    out.sql = util.flexibleString(out.sql);
3891acfe915Sstephan    if('string'!==typeof out.sql){
390a6ca996eSstephan      toss3("Missing SQL argument or unsupported SQL value type.");
3913961b263Sstephan    }
3921acfe915Sstephan    const opt = out.opt;
3931acfe915Sstephan    switch(opt.returnValue){
3941acfe915Sstephan        case 'resultRows':
3951acfe915Sstephan          if(!opt.resultRows) opt.resultRows = [];
3961acfe915Sstephan          out.returnVal = ()=>opt.resultRows;
3971acfe915Sstephan          break;
3981acfe915Sstephan        case 'saveSql':
3991acfe915Sstephan          if(!opt.saveSql) opt.saveSql = [];
4001acfe915Sstephan          out.returnVal = ()=>opt.saveSql;
4011acfe915Sstephan          break;
4021acfe915Sstephan        case undefined:
4031acfe915Sstephan        case 'this':
404*0f32760eSstephan          out.returnVal = ()=>db;
4051acfe915Sstephan          break;
4061acfe915Sstephan        default:
4071acfe915Sstephan          toss3("Invalid returnValue value:",opt.returnValue);
4081acfe915Sstephan    }
4091acfe915Sstephan    if(opt.callback || opt.resultRows){
4101acfe915Sstephan      switch((undefined===opt.rowMode)
4111acfe915Sstephan             ? 'array' : opt.rowMode) {
412335ad526Sstephan          case 'object': out.cbArg = (stmt)=>stmt.get(Object.create(null)); break;
4133961b263Sstephan          case 'array': out.cbArg = (stmt)=>stmt.get([]); break;
4143961b263Sstephan          case 'stmt':
4151acfe915Sstephan            if(Array.isArray(opt.resultRows)){
416335ad526Sstephan              toss3("exec(): invalid rowMode for a resultRows array: must",
4173961b263Sstephan                    "be one of 'array', 'object',",
418407f7537Sstephan                    "a result column number, or column name reference.");
4193961b263Sstephan            }
4203961b263Sstephan            out.cbArg = (stmt)=>stmt;
4213961b263Sstephan            break;
4223961b263Sstephan          default:
4231acfe915Sstephan            if(util.isInt32(opt.rowMode)){
4241acfe915Sstephan              out.cbArg = (stmt)=>stmt.get(opt.rowMode);
4253961b263Sstephan              break;
4261acfe915Sstephan            }else if('string'===typeof opt.rowMode && opt.rowMode.length>1){
427407f7537Sstephan              /* "$X", ":X", and "@X" fetch column named "X" (case-sensitive!) */
4281acfe915Sstephan              const prefix = opt.rowMode[0];
429407f7537Sstephan              if(':'===prefix || '@'===prefix || '$'===prefix){
430407f7537Sstephan                out.cbArg = function(stmt){
431335ad526Sstephan                  const rc = stmt.get(this.obj)[this.colName];
432335ad526Sstephan                  return (undefined===rc) ? toss3("exec(): unknown result column:",this.colName) : rc;
433335ad526Sstephan                }.bind({
434335ad526Sstephan                  obj:Object.create(null),
4351acfe915Sstephan                  colName: opt.rowMode.substr(1)
436335ad526Sstephan                });
4373734401aSstephan                break;
4383734401aSstephan              }
439407f7537Sstephan            }
4401acfe915Sstephan            toss3("Invalid rowMode:",opt.rowMode);
4413961b263Sstephan      }
4423961b263Sstephan    }
4433961b263Sstephan    return out;
4443961b263Sstephan  };
4453961b263Sstephan
4463961b263Sstephan  /**
4471acfe915Sstephan     Internal impl of the DB.selectArray() and
4481acfe915Sstephan     selectObject() methods.
44950ef0139Sstephan  */
45050ef0139Sstephan  const __selectFirstRow = (db, sql, bind, getArg)=>{
45150ef0139Sstephan    let stmt, rc;
45250ef0139Sstephan    try {
45350ef0139Sstephan      stmt = db.prepare(sql).bind(bind);
45450ef0139Sstephan      if(stmt.step()) rc = stmt.get(getArg);
45550ef0139Sstephan    }finally{
45650ef0139Sstephan      if(stmt) stmt.finalize();
45750ef0139Sstephan    }
45850ef0139Sstephan    return rc;
45950ef0139Sstephan  };
46050ef0139Sstephan
46150ef0139Sstephan  /**
46249cb8d73Sstephan     Expects to be given a DB instance or an `sqlite3*` pointer (may
46349cb8d73Sstephan     be null) and an sqlite3 API result code. If the result code is
46449cb8d73Sstephan     not falsy, this function throws an SQLite3Error with an error
46549cb8d73Sstephan     message from sqlite3_errmsg(), using dbPtr as the db handle, or
46649cb8d73Sstephan     sqlite3_errstr() if dbPtr is falsy. Note that if it's passed a
46749cb8d73Sstephan     non-error code like SQLITE_ROW or SQLITE_DONE, it will still
46849cb8d73Sstephan     throw but the error string might be "Not an error."  The various
46949cb8d73Sstephan     non-0 non-error codes need to be checked for in
47049cb8d73Sstephan     client code where they are expected.
4713961b263Sstephan  */
4725360f5fcSstephan  DB.checkRc = checkSqlite3Rc;
4733961b263Sstephan
4743961b263Sstephan  DB.prototype = {
47507c0b722Sstephan    /** Returns true if this db handle is open, else false. */
47607c0b722Sstephan    isOpen: function(){
47707c0b722Sstephan      return !!this.pointer;
47807c0b722Sstephan    },
47907c0b722Sstephan    /** Throws if this given DB has been closed, else returns `this`. */
48007c0b722Sstephan    affirmOpen: function(){
48107c0b722Sstephan      return affirmDbOpen(this);
48207c0b722Sstephan    },
4833961b263Sstephan    /**
4843961b263Sstephan       Finalizes all open statements and closes this database
4853961b263Sstephan       connection. This is a no-op if the db has already been
4863961b263Sstephan       closed. After calling close(), `this.pointer` will resolve to
4873961b263Sstephan       `undefined`, so that can be used to check whether the db
4883961b263Sstephan       instance is still opened.
489f3860120Sstephan
490f3860120Sstephan       If this.onclose.before is a function then it is called before
491f3860120Sstephan       any close-related cleanup.
492f3860120Sstephan
493f3860120Sstephan       If this.onclose.after is a function then it is called after the
494f3860120Sstephan       db is closed but before auxiliary state like this.filename is
495f3860120Sstephan       cleared.
496f3860120Sstephan
497f3860120Sstephan       Both onclose handlers are passed this object. If this db is not
498f3860120Sstephan       opened, neither of the handlers are called. Any exceptions the
499f3860120Sstephan       handlers throw are ignored because "destructors must not
500f3860120Sstephan       throw."
501f3860120Sstephan
502f3860120Sstephan       Note that garbage collection of a db handle, if it happens at
503f3860120Sstephan       all, will never trigger close(), so onclose handlers are not a
504f3860120Sstephan       reliable way to implement close-time cleanup or maintenance of
505f3860120Sstephan       a db.
5063961b263Sstephan    */
5073961b263Sstephan    close: function(){
5083961b263Sstephan      if(this.pointer){
509f3860120Sstephan        if(this.onclose && (this.onclose.before instanceof Function)){
510f3860120Sstephan          try{this.onclose.before(this)}
511f3860120Sstephan          catch(e){/*ignore*/}
512f3860120Sstephan        }
5133961b263Sstephan        const pDb = this.pointer;
5143961b263Sstephan        Object.keys(__stmtMap.get(this)).forEach((k,s)=>{
5153961b263Sstephan          if(s && s.pointer) s.finalize();
5163961b263Sstephan        });
5173961b263Sstephan        __ptrMap.delete(this);
5183961b263Sstephan        __stmtMap.delete(this);
5193961b263Sstephan        capi.sqlite3_close_v2(pDb);
520f3860120Sstephan        if(this.onclose && (this.onclose.after instanceof Function)){
521f3860120Sstephan          try{this.onclose.after(this)}
522f3860120Sstephan          catch(e){/*ignore*/}
523f3860120Sstephan        }
5243961b263Sstephan        delete this.filename;
5253961b263Sstephan      }
5263961b263Sstephan    },
5273961b263Sstephan    /**
5283961b263Sstephan       Returns the number of changes, as per sqlite3_changes()
5293961b263Sstephan       (if the first argument is false) or sqlite3_total_changes()
5303961b263Sstephan       (if it's true). If the 2nd argument is true, it uses
5313961b263Sstephan       sqlite3_changes64() or sqlite3_total_changes64(), which
5323961b263Sstephan       will trigger an exception if this build does not have
5333961b263Sstephan       BigInt support enabled.
5343961b263Sstephan    */
5353961b263Sstephan    changes: function(total=false,sixtyFour=false){
5363961b263Sstephan      const p = affirmDbOpen(this).pointer;
5373961b263Sstephan      if(total){
5383961b263Sstephan        return sixtyFour
5393961b263Sstephan          ? capi.sqlite3_total_changes64(p)
5403961b263Sstephan          : capi.sqlite3_total_changes(p);
5413961b263Sstephan      }else{
5423961b263Sstephan        return sixtyFour
5433961b263Sstephan          ? capi.sqlite3_changes64(p)
5443961b263Sstephan          : capi.sqlite3_changes(p);
5453961b263Sstephan      }
5463961b263Sstephan    },
5473961b263Sstephan    /**
54834b92b19Sstephan       Similar to the this.filename but returns the
54934b92b19Sstephan       sqlite3_db_filename() value for the given database name,
55034b92b19Sstephan       defaulting to "main".  The argument may be either a JS string
55134b92b19Sstephan       or a pointer to a WASM-allocated C-string.
55234b92b19Sstephan    */
55334b92b19Sstephan    dbFilename: function(dbName='main'){
55434b92b19Sstephan      return capi.sqlite3_db_filename(affirmDbOpen(this).pointer, dbName);
55534b92b19Sstephan    },
55634b92b19Sstephan    /**
5573961b263Sstephan       Returns the name of the given 0-based db number, as documented
5583961b263Sstephan       for sqlite3_db_name().
5593961b263Sstephan    */
5603961b263Sstephan    dbName: function(dbNumber=0){
5613961b263Sstephan      return capi.sqlite3_db_name(affirmDbOpen(this).pointer, dbNumber);
5623961b263Sstephan    },
5633961b263Sstephan    /**
56449048b14Sstephan       Returns the name of the sqlite3_vfs used by the given database
56549048b14Sstephan       of this connection (defaulting to 'main'). The argument may be
56649048b14Sstephan       either a JS string or a WASM C-string. Returns undefined if the
56749048b14Sstephan       given db name is invalid. Throws if this object has been
56849048b14Sstephan       close()d.
56949048b14Sstephan    */
57049048b14Sstephan    dbVfsName: function(dbName=0){
57149048b14Sstephan      let rc;
57249048b14Sstephan      const pVfs = capi.sqlite3_js_db_vfs(
57349048b14Sstephan        affirmDbOpen(this).pointer, dbName
57449048b14Sstephan      );
57549048b14Sstephan      if(pVfs){
57649048b14Sstephan        const v = new capi.sqlite3_vfs(pVfs);
57749048b14Sstephan        try{ rc = wasm.cstringToJs(v.$zName) }
57849048b14Sstephan        finally { v.dispose() }
57949048b14Sstephan      }
58049048b14Sstephan      return rc;
58149048b14Sstephan    },
58249048b14Sstephan    /**
5833961b263Sstephan       Compiles the given SQL and returns a prepared Stmt. This is
5843961b263Sstephan       the only way to create new Stmt objects. Throws on error.
5853961b263Sstephan
5863d645484Sstephan       The given SQL must be a string, a Uint8Array holding SQL, a
5873d645484Sstephan       WASM pointer to memory holding the NUL-terminated SQL string,
5883d645484Sstephan       or an array of strings. In the latter case, the array is
5893d645484Sstephan       concatenated together, with no separators, to form the SQL
5903d645484Sstephan       string (arrays are often a convenient way to formulate long
5913d645484Sstephan       statements).  If the SQL contains no statements, an
5923d645484Sstephan       SQLite3Error is thrown.
5933961b263Sstephan
5943961b263Sstephan       Design note: the C API permits empty SQL, reporting it as a 0
5953961b263Sstephan       result code and a NULL stmt pointer. Supporting that case here
5963961b263Sstephan       would cause extra work for all clients: any use of the Stmt API
5973961b263Sstephan       on such a statement will necessarily throw, so clients would be
5983961b263Sstephan       required to check `stmt.pointer` after calling `prepare()` in
5993961b263Sstephan       order to determine whether the Stmt instance is empty or not.
6003961b263Sstephan       Long-time practice (with other sqlite3 script bindings)
60149cb8d73Sstephan       suggests that the empty-prepare case is sufficiently rare that
60249cb8d73Sstephan       supporting it here would simply hurt overall usability.
6033961b263Sstephan    */
6043961b263Sstephan    prepare: function(sql){
6053961b263Sstephan      affirmDbOpen(this);
60663e9ec2fSstephan      const stack = wasm.pstack.pointer;
6073961b263Sstephan      let ppStmt, pStmt;
6083961b263Sstephan      try{
60963e9ec2fSstephan        ppStmt = wasm.pstack.alloc(8)/* output (sqlite3_stmt**) arg */;
6103961b263Sstephan        DB.checkRc(this, capi.sqlite3_prepare_v2(this.pointer, sql, -1, ppStmt, null));
61163e9ec2fSstephan        pStmt = wasm.getPtrValue(ppStmt);
6123961b263Sstephan      }
613193ee11fSstephan      finally {
61463e9ec2fSstephan        wasm.pstack.restore(stack);
615193ee11fSstephan      }
6163961b263Sstephan      if(!pStmt) toss3("Cannot prepare empty SQL.");
6173961b263Sstephan      const stmt = new Stmt(this, pStmt, BindTypes);
6183961b263Sstephan      __stmtMap.get(this)[pStmt] = stmt;
6193961b263Sstephan      return stmt;
6203961b263Sstephan    },
6213961b263Sstephan    /**
6223961b263Sstephan       Executes one or more SQL statements in the form of a single
6233961b263Sstephan       string. Its arguments must be either (sql,optionsObject) or
6241acfe915Sstephan       (optionsObject). In the latter case, optionsObject.sql must
6251acfe915Sstephan       contain the SQL to execute. By default it returns this object
6261acfe915Sstephan       but that can be changed via the `returnValue` option as
6271acfe915Sstephan       described below. Throws on error.
6283961b263Sstephan
6293961b263Sstephan       If no SQL is provided, or a non-string is provided, an
6303961b263Sstephan       exception is triggered. Empty SQL, on the other hand, is
6313961b263Sstephan       simply a no-op.
6323961b263Sstephan
6333961b263Sstephan       The optional options object may contain any of the following
6343961b263Sstephan       properties:
6353961b263Sstephan
6361acfe915Sstephan       - `sql` = the SQL to run (unless it's provided as the first
637335ad526Sstephan       argument). This must be of type string, Uint8Array, or an array
638335ad526Sstephan       of strings. In the latter case they're concatenated together
639335ad526Sstephan       as-is, _with no separator_ between elements, before evaluation.
640335ad526Sstephan       The array form is often simpler for long hand-written queries.
6413961b263Sstephan
6421acfe915Sstephan       - `bind` = a single value valid as an argument for
643335ad526Sstephan       Stmt.bind(). This is _only_ applied to the _first_ non-empty
644335ad526Sstephan       statement in the SQL which has any bindable parameters. (Empty
645335ad526Sstephan       statements are skipped entirely.)
646335ad526Sstephan
6471acfe915Sstephan       - `saveSql` = an optional array. If set, the SQL of each
648335ad526Sstephan       executed statement is appended to this array before the
64949cb8d73Sstephan       statement is executed (but after it is prepared - we don't have
6501acfe915Sstephan       the string until after that). Empty SQL statements are elided
6511acfe915Sstephan       but can have odd effects in the output. e.g. SQL of: `"select
6521acfe915Sstephan       1; -- empty\n; select 2"` will result in an array containing
6531acfe915Sstephan       `["select 1;", "--empty \n; select 2"]`. That's simply how
6541acfe915Sstephan       sqlite3 records the SQL for the 2nd statement.
655335ad526Sstephan
656335ad526Sstephan       ==================================================================
657335ad526Sstephan       The following options apply _only_ to the _first_ statement
658335ad526Sstephan       which has a non-zero result column count, regardless of whether
659335ad526Sstephan       the statement actually produces any result rows.
660335ad526Sstephan       ==================================================================
6613961b263Sstephan
6621acfe915Sstephan       - `columnNames`: if this is an array, the column names of the
6635b915007Sstephan       result set are stored in this array before the callback (if
6645b915007Sstephan       any) is triggered (regardless of whether the query produces any
6655b915007Sstephan       result rows). If no statement has result columns, this value is
6665b915007Sstephan       unchanged. Achtung: an SQL result may have multiple columns
6675b915007Sstephan       with identical names.
6685b915007Sstephan
6691acfe915Sstephan       - `callback` = a function which gets called for each row of
67049cb8d73Sstephan       the result set, but only if that statement has any result
671409505c7Sstephan       _rows_. The callback's "this" is the options object, noting
672409505c7Sstephan       that this function synthesizes one if the caller does not pass
673409505c7Sstephan       one to exec(). The second argument passed to the callback is
674409505c7Sstephan       always the current Stmt object, as it's needed if the caller
675409505c7Sstephan       wants to fetch the column names or some such (noting that they
676409505c7Sstephan       could also be fetched via `this.columnNames`, if the client
677409505c7Sstephan       provides the `columnNames` option).
6783961b263Sstephan
679335ad526Sstephan       ACHTUNG: The callback MUST NOT modify the Stmt object. Calling
680335ad526Sstephan       any of the Stmt.get() variants, Stmt.getColumnName(), or
681335ad526Sstephan       similar, is legal, but calling step() or finalize() is
682409505c7Sstephan       not. Member methods which are illegal in this context will
683409505c7Sstephan       trigger an exception.
684407f7537Sstephan
685335ad526Sstephan       The first argument passed to the callback defaults to an array of
686335ad526Sstephan       values from the current result row but may be changed with ...
687407f7537Sstephan
6881acfe915Sstephan       - `rowMode` = specifies the type of he callback's first argument.
689335ad526Sstephan       It may be any of...
690335ad526Sstephan
691335ad526Sstephan       A) A string describing what type of argument should be passed
692407f7537Sstephan       as the first argument to the callback:
693407f7537Sstephan
69449cb8d73Sstephan         A.1) `'array'` (the default) causes the results of
6955b915007Sstephan         `stmt.get([])` to be passed to the `callback` and/or appended
6961acfe915Sstephan         to `resultRows`
697407f7537Sstephan
69849cb8d73Sstephan         A.2) `'object'` causes the results of
699335ad526Sstephan         `stmt.get(Object.create(null))` to be passed to the
700335ad526Sstephan         `callback` and/or appended to `resultRows`.  Achtung: an SQL
701335ad526Sstephan         result may have multiple columns with identical names. In
702335ad526Sstephan         that case, the right-most column will be the one set in this
703335ad526Sstephan         object!
704407f7537Sstephan
70549cb8d73Sstephan         A.3) `'stmt'` causes the current Stmt to be passed to the
706407f7537Sstephan         callback, but this mode will trigger an exception if
707407f7537Sstephan         `resultRows` is an array because appending the statement to
7085b915007Sstephan         the array would be downright unhelpful.
709407f7537Sstephan
710335ad526Sstephan       B) An integer, indicating a zero-based column in the result
711335ad526Sstephan       row. Only that one single value will be passed on.
712335ad526Sstephan
713407f7537Sstephan       C) A string with a minimum length of 2 and leading character of
714407f7537Sstephan       ':', '$', or '@' will fetch the row as an object, extract that
715407f7537Sstephan       one field, and pass that field's value to the callback. Note
716407f7537Sstephan       that these keys are case-sensitive so must match the case used
7175b915007Sstephan       in the SQL. e.g. `"select a A from t"` with a `rowMode` of
7185b915007Sstephan       `'$A'` would work but `'$a'` would not. A reference to a column
7195b915007Sstephan       not in the result set will trigger an exception on the first
7205b915007Sstephan       row (as the check is not performed until rows are fetched).
7215b915007Sstephan       Note also that `$` is a legal identifier character in JS so
7225b915007Sstephan       need not be quoted. (Design note: those 3 characters were
7235b915007Sstephan       chosen because they are the characters support for naming bound
7245b915007Sstephan       parameters.)
725407f7537Sstephan
726407f7537Sstephan       Any other `rowMode` value triggers an exception.
7273961b263Sstephan
7281acfe915Sstephan       - `resultRows`: if this is an array, it functions similarly to
729335ad526Sstephan       the `callback` option: each row of the result set (if any),
730335ad526Sstephan       with the exception that the `rowMode` 'stmt' is not legal. It
731335ad526Sstephan       is legal to use both `resultRows` and `callback`, but
732335ad526Sstephan       `resultRows` is likely much simpler to use for small data sets
733335ad526Sstephan       and can be used over a WebWorker-style message interface.
734335ad526Sstephan       exec() throws if `resultRows` is set and `rowMode` is 'stmt'.
7353961b263Sstephan
7361acfe915Sstephan       - `returnValue`: is a string specifying what this function
7371acfe915Sstephan       should return:
7381acfe915Sstephan
7391acfe915Sstephan         A) The default value is `"this"`, meaning that the
7401acfe915Sstephan            DB object itself should be returned.
7411acfe915Sstephan
7421acfe915Sstephan         B) `"resultRows"` means to return the value of the
7431acfe915Sstephan            `resultRows` option. If `resultRows` is not set, this
7441acfe915Sstephan            function behaves as if it were set to an empty array.
7451acfe915Sstephan
7461acfe915Sstephan         C) `"saveSql"` means to return the value of the
7471acfe915Sstephan            `saveSql` option. If `saveSql` is not set, this
7481acfe915Sstephan            function behaves as if it were set to an empty array.
7495b915007Sstephan
7505b915007Sstephan       Potential TODOs:
7515b915007Sstephan
7521acfe915Sstephan       - `bind`: permit an array of arrays/objects to bind. The first
7535b915007Sstephan       sub-array would act on the first statement which has bindable
7545b915007Sstephan       parameters (as it does now). The 2nd would act on the next such
7555b915007Sstephan       statement, etc.
7565b915007Sstephan
7571acfe915Sstephan       - `callback` and `resultRows`: permit an array entries with
7581acfe915Sstephan       semantics similar to those described for `bind` above.
7595b915007Sstephan
7603961b263Sstephan    */
761335ad526Sstephan    exec: function(/*(sql [,obj]) || (obj)*/){
7623961b263Sstephan      affirmDbOpen(this);
7631acfe915Sstephan      const arg = parseExecArgs(this, arguments);
764335ad526Sstephan      if(!arg.sql){
765*0f32760eSstephan        return toss3("exec() requires an SQL string.");
766335ad526Sstephan      }
7673961b263Sstephan      const opt = arg.opt;
7683961b263Sstephan      const callback = opt.callback;
769*0f32760eSstephan      const resultRows =
770*0f32760eSstephan            Array.isArray(opt.resultRows) ? opt.resultRows : undefined;
7713961b263Sstephan      let stmt;
7723961b263Sstephan      let bind = opt.bind;
77349cb8d73Sstephan      let evalFirstResult = !!(arg.cbArg || opt.columnNames) /* true to evaluate the first result-returning query */;
7743961b263Sstephan      const stack = wasm.scopedAllocPush();
7753961b263Sstephan      try{
7763961b263Sstephan        const isTA = util.isSQLableTypedArray(arg.sql)
7773961b263Sstephan        /* Optimization: if the SQL is a TypedArray we can save some string
7783961b263Sstephan           conversion costs. */;
7793961b263Sstephan        /* Allocate the two output pointers (ppStmt, pzTail) and heap
7803961b263Sstephan           space for the SQL (pSql). When prepare_v2() returns, pzTail
7813961b263Sstephan           will point to somewhere in pSql. */
7823961b263Sstephan        let sqlByteLen = isTA ? arg.sql.byteLength : wasm.jstrlen(arg.sql);
7833961b263Sstephan        const ppStmt  = wasm.scopedAlloc(/* output (sqlite3_stmt**) arg and pzTail */
7843961b263Sstephan          (2 * wasm.ptrSizeof)
7853961b263Sstephan          + (sqlByteLen + 1/* SQL + NUL */));
7863961b263Sstephan        const pzTail = ppStmt + wasm.ptrSizeof /* final arg to sqlite3_prepare_v2() */;
7873961b263Sstephan        let pSql = pzTail + wasm.ptrSizeof;
7883961b263Sstephan        const pSqlEnd = pSql + sqlByteLen;
7893961b263Sstephan        if(isTA) wasm.heap8().set(arg.sql, pSql);
7903961b263Sstephan        else wasm.jstrcpy(arg.sql, wasm.heap8(), pSql, sqlByteLen, false);
7913961b263Sstephan        wasm.setMemValue(pSql + sqlByteLen, 0/*NUL terminator*/);
7922f06bf25Sstephan        while(pSql && wasm.getMemValue(pSql, 'i8')
793407f7537Sstephan              /* Maintenance reminder:^^^ _must_ be 'i8' or else we
7943961b263Sstephan                 will very likely cause an endless loop. What that's
7953961b263Sstephan                 doing is checking for a terminating NUL byte. If we
7963961b263Sstephan                 use i32 or similar then we read 4 bytes, read stuff
7973961b263Sstephan                 around the NUL terminator, and get stuck in and
7983961b263Sstephan                 endless loop at the end of the SQL, endlessly
7993961b263Sstephan                 re-preparing an empty statement. */ ){
8002f06bf25Sstephan          wasm.setPtrValue(ppStmt, 0);
8012f06bf25Sstephan          wasm.setPtrValue(pzTail, 0);
802335ad526Sstephan          DB.checkRc(this, capi.sqlite3_prepare_v3(
803335ad526Sstephan            this.pointer, pSql, sqlByteLen, 0, ppStmt, pzTail
8043961b263Sstephan          ));
805f2e624eaSstephan          const pStmt = wasm.getPtrValue(ppStmt);
806f2e624eaSstephan          pSql = wasm.getPtrValue(pzTail);
8073961b263Sstephan          sqlByteLen = pSqlEnd - pSql;
8083961b263Sstephan          if(!pStmt) continue;
8093961b263Sstephan          if(Array.isArray(opt.saveSql)){
8103961b263Sstephan            opt.saveSql.push(capi.sqlite3_sql(pStmt).trim());
8113961b263Sstephan          }
8123961b263Sstephan          stmt = new Stmt(this, pStmt, BindTypes);
8133961b263Sstephan          if(bind && stmt.parameterCount){
8143961b263Sstephan            stmt.bind(bind);
8153961b263Sstephan            bind = null;
8163961b263Sstephan          }
81749cb8d73Sstephan          if(evalFirstResult && stmt.columnCount){
8183961b263Sstephan            /* Only forward SELECT results for the FIRST query
8193961b263Sstephan               in the SQL which potentially has them. */
82049cb8d73Sstephan            evalFirstResult = false;
821335ad526Sstephan            if(Array.isArray(opt.columnNames)){
822335ad526Sstephan              stmt.getColumnNames(opt.columnNames);
823335ad526Sstephan            }
824d7d1098bSstephan            while(!!arg.cbArg && stmt.step()){
8253961b263Sstephan              stmt._isLocked = true;
8263961b263Sstephan              const row = arg.cbArg(stmt);
8273961b263Sstephan              if(resultRows) resultRows.push(row);
8281acfe915Sstephan              if(callback) callback.call(opt, row, stmt);
8293961b263Sstephan              stmt._isLocked = false;
8303961b263Sstephan            }
8313961b263Sstephan          }else{
8323961b263Sstephan            stmt.step();
8333961b263Sstephan          }
8343961b263Sstephan          stmt.finalize();
8353961b263Sstephan          stmt = null;
8363961b263Sstephan        }
837335ad526Sstephan      }/*catch(e){
838335ad526Sstephan        console.warn("DB.exec() is propagating exception",opt,e);
8393961b263Sstephan        throw e;
840335ad526Sstephan      }*/finally{
8413961b263Sstephan        if(stmt){
8423961b263Sstephan          delete stmt._isLocked;
8433961b263Sstephan          stmt.finalize();
8443961b263Sstephan        }
8453961b263Sstephan        wasm.scopedAllocPop(stack);
8463961b263Sstephan      }
8471acfe915Sstephan      return arg.returnVal();
848335ad526Sstephan    }/*exec()*/,
8493961b263Sstephan    /**
8503961b263Sstephan       Creates a new scalar UDF (User-Defined Function) which is
8513961b263Sstephan       accessible via SQL code. This function may be called in any
8523961b263Sstephan       of the following forms:
8533961b263Sstephan
8543961b263Sstephan       - (name, function)
8553961b263Sstephan       - (name, function, optionsObject)
8563961b263Sstephan       - (name, optionsObject)
8573961b263Sstephan       - (optionsObject)
8583961b263Sstephan
8593961b263Sstephan       In the final two cases, the function must be defined as the
860d92c652aSstephan       `callback` property of the options object (optionally called
861d92c652aSstephan       `xFunc` to align with the C API documentation). In the final
8623961b263Sstephan       case, the function's name must be the 'name' property.
8633961b263Sstephan
864824bb5b8Sstephan       The first two call forms can only be used for creating scalar
865a6ca996eSstephan       functions. Creating an aggregate or window function requires
866a6ca996eSstephan       the options-object form (see below for details).
867d92c652aSstephan
868d92c652aSstephan       UDFs cannot currently be removed from a DB handle after they're
869824bb5b8Sstephan       added. More correctly, they can be removed as documented for
870824bb5b8Sstephan       sqlite3_create_function_v2(), but doing so will "leak" the
871824bb5b8Sstephan       JS-created WASM binding of those functions.
8723961b263Sstephan
8733961b263Sstephan       On success, returns this object. Throws on error.
8743961b263Sstephan
8759892883eSstephan       When called from SQL arguments to the UDF, and its result,
8769892883eSstephan       will be converted between JS and SQL with as much fidelity as
8779892883eSstephan       is feasible, triggering an exception if a type conversion
8789892883eSstephan       cannot be determined. The docs for sqlite3_create_function_v2()
8799892883eSstephan       describe the conversions in more detail.
8803961b263Sstephan
881824bb5b8Sstephan       The values set in the options object differ for scalar and
882824bb5b8Sstephan       aggregate functions:
883824bb5b8Sstephan
884824bb5b8Sstephan       - Scalar: set the `xFunc` function-type property to the UDF
885824bb5b8Sstephan         function.
886824bb5b8Sstephan
887824bb5b8Sstephan       - Aggregate: set the `xStep` and `xFinal` function-type
888824bb5b8Sstephan         properties to the "step" and "final" callbacks for the
889824bb5b8Sstephan         aggregate. Do not set the `xFunc` property.
890824bb5b8Sstephan
891a6ca996eSstephan       - Window: set the `xStep`, `xFinal`, `xValue`, and `xInverse`
892a6ca996eSstephan         function-type properties. Do not set the `xFunc` property.
893824bb5b8Sstephan
894a6ca996eSstephan       The options object may optionally have an `xDestroy`
895a6ca996eSstephan       function-type property, as per sqlite3_create_function_v2().
896a6ca996eSstephan       Its argument will be the WASM-pointer-type value of the `pApp`
897a6ca996eSstephan       property, and this function will throw if `pApp` is defined but
898a6ca996eSstephan       is not null, undefined, or a numeric (WASM pointer)
899a6ca996eSstephan       value. i.e. `pApp`, if set, must be value suitable for use as a
900a6ca996eSstephan       WASM pointer argument, noting that `null` or `undefined` will
901a6ca996eSstephan       translate to 0 for that purpose.
902a6ca996eSstephan
903a6ca996eSstephan       The options object may contain flags to modify how
9043961b263Sstephan       the function is defined:
9053961b263Sstephan
906824bb5b8Sstephan       - `arity`: the number of arguments which SQL calls to this
907824bb5b8Sstephan       function expect or require. The default value is `xFunc.length`
908824bb5b8Sstephan       or `xStep.length` (i.e. the number of declared parameters it
909824bb5b8Sstephan       has) **MINUS 1** (see below for why). As a special case, if the
910824bb5b8Sstephan       `length` is 0, its arity is also 0 instead of -1. A negative
911824bb5b8Sstephan       arity value means that the function is variadic and may accept
912824bb5b8Sstephan       any number of arguments, up to sqlite3's compile-time
913824bb5b8Sstephan       limits. sqlite3 will enforce the argument count if is zero or
914a6ca996eSstephan       greater. The callback always receives a pointer to an
915a6ca996eSstephan       `sqlite3_context` object as its first argument. Any arguments
916a6ca996eSstephan       after that are from SQL code. The leading context argument does
917a6ca996eSstephan       _not_ count towards the function's arity. See the docs for
918510a9d1cSstephan       sqlite3.capi.sqlite3_create_function_v2() for why that argument
919510a9d1cSstephan       is needed in the interface.
9203961b263Sstephan
921a6ca996eSstephan       The following options-object properties correspond to flags
922a6ca996eSstephan       documented at:
9233961b263Sstephan
9243961b263Sstephan       https://sqlite.org/c3ref/create_function.html
9253961b263Sstephan
926a6ca996eSstephan       - `deterministic` = sqlite3.capi.SQLITE_DETERMINISTIC
927a6ca996eSstephan       - `directOnly` = sqlite3.capi.SQLITE_DIRECTONLY
928a6ca996eSstephan       - `innocuous` = sqlite3.capi.SQLITE_INNOCUOUS
929a6ca996eSstephan
930a6ca996eSstephan       Sidebar: the ability to add new WASM-accessible functions to
931a6ca996eSstephan       the runtime requires that the WASM build is compiled with the
932a6ca996eSstephan       equivalent functionality as that provided by Emscripten's
933a6ca996eSstephan       `-sALLOW_TABLE_GROWTH` flag.
9343961b263Sstephan    */
935824bb5b8Sstephan    createFunction: function f(name, xFunc, opt){
936824bb5b8Sstephan      const isFunc = (f)=>(f instanceof Function);
9373961b263Sstephan      switch(arguments.length){
9383961b263Sstephan          case 1: /* (optionsObject) */
9393961b263Sstephan            opt = name;
9403961b263Sstephan            name = opt.name;
941a6ca996eSstephan            xFunc = opt.xFunc || 0;
9423961b263Sstephan            break;
9433961b263Sstephan          case 2: /* (name, callback|optionsObject) */
944824bb5b8Sstephan            if(!isFunc(xFunc)){
945824bb5b8Sstephan              opt = xFunc;
946a6ca996eSstephan              xFunc = opt.xFunc || 0;
9473961b263Sstephan            }
9483961b263Sstephan            break;
949824bb5b8Sstephan          case 3: /* name, xFunc, opt */
950824bb5b8Sstephan            break;
9513961b263Sstephan          default: break;
9523961b263Sstephan      }
9533961b263Sstephan      if(!opt) opt = {};
954824bb5b8Sstephan      if('string' !== typeof name){
9553961b263Sstephan        toss3("Invalid arguments: missing function name.");
9563961b263Sstephan      }
957a6ca996eSstephan      let xStep = opt.xStep || 0;
958a6ca996eSstephan      let xFinal = opt.xFinal || 0;
959a6ca996eSstephan      const xValue = opt.xValue || 0;
960a6ca996eSstephan      const xInverse = opt.xInverse || 0;
961a6ca996eSstephan      let isWindow = undefined;
962824bb5b8Sstephan      if(isFunc(xFunc)){
963a6ca996eSstephan        isWindow = false;
964824bb5b8Sstephan        if(isFunc(xStep) || isFunc(xFinal)){
965824bb5b8Sstephan          toss3("Ambiguous arguments: scalar or aggregate?");
966824bb5b8Sstephan        }
967824bb5b8Sstephan        xStep = xFinal = null;
968824bb5b8Sstephan      }else if(isFunc(xStep)){
969824bb5b8Sstephan        if(!isFunc(xFinal)){
970a6ca996eSstephan          toss3("Missing xFinal() callback for aggregate or window UDF.");
971824bb5b8Sstephan        }
972824bb5b8Sstephan        xFunc = null;
973824bb5b8Sstephan      }else if(isFunc(xFinal)){
974a6ca996eSstephan        toss3("Missing xStep() callback for aggregate or window UDF.");
975824bb5b8Sstephan      }else{
976824bb5b8Sstephan        toss3("Missing function-type properties.");
977824bb5b8Sstephan      }
978a6ca996eSstephan      if(false === isWindow){
979a6ca996eSstephan        if(isFunc(xValue) || isFunc(xInverse)){
980a6ca996eSstephan          toss3("xValue and xInverse are not permitted for non-window UDFs.");
981a6ca996eSstephan        }
982a6ca996eSstephan      }else if(isFunc(xValue)){
983a6ca996eSstephan        if(!isFunc(xInverse)){
984a6ca996eSstephan          toss3("xInverse must be provided if xValue is.");
985a6ca996eSstephan        }
986a6ca996eSstephan        isWindow = true;
987a6ca996eSstephan      }else if(isFunc(xInverse)){
988a6ca996eSstephan        toss3("xValue must be provided if xInverse is.");
989a6ca996eSstephan      }
990824bb5b8Sstephan      const pApp = opt.pApp;
9912676fc58Sstephan      if(undefined!==pApp &&
9922676fc58Sstephan         null!==pApp &&
9938948fbeeSstephan         (('number'!==typeof pApp) || !util.isInt32(pApp))){
994824bb5b8Sstephan        toss3("Invalid value for pApp property. Must be a legal WASM pointer value.");
995824bb5b8Sstephan      }
996a6ca996eSstephan      const xDestroy = opt.xDestroy || 0;
997824bb5b8Sstephan      if(xDestroy && !isFunc(xDestroy)){
998824bb5b8Sstephan        toss3("xDestroy property must be a function.");
999824bb5b8Sstephan      }
10003961b263Sstephan      let fFlags = 0 /*flags for sqlite3_create_function_v2()*/;
10013961b263Sstephan      if(getOwnOption(opt, 'deterministic')) fFlags |= capi.SQLITE_DETERMINISTIC;
10023961b263Sstephan      if(getOwnOption(opt, 'directOnly')) fFlags |= capi.SQLITE_DIRECTONLY;
10033961b263Sstephan      if(getOwnOption(opt, 'innocuous')) fFlags |= capi.SQLITE_INNOCUOUS;
10043961b263Sstephan      name = name.toLowerCase();
1005824bb5b8Sstephan      const xArity = xFunc || xStep;
1006824bb5b8Sstephan      const arity = getOwnOption(opt, 'arity');
1007a6ca996eSstephan      const arityArg = ('number'===typeof arity
1008824bb5b8Sstephan                        ? arity
1009a6ca996eSstephan                        : (xArity.length ? xArity.length-1/*for pCtx arg*/ : 0));
1010a6ca996eSstephan      let rc;
1011a6ca996eSstephan      if( isWindow ){
1012a6ca996eSstephan        rc = capi.sqlite3_create_window_function(
1013a6ca996eSstephan          this.pointer, name, arityArg,
1014a6ca996eSstephan          capi.SQLITE_UTF8 | fFlags, pApp || 0,
1015a6ca996eSstephan          xStep, xFinal, xValue, xInverse, xDestroy);
1016a6ca996eSstephan      }else{
1017a6ca996eSstephan        rc = capi.sqlite3_create_function_v2(
1018a6ca996eSstephan          this.pointer, name, arityArg,
1019a6ca996eSstephan          capi.SQLITE_UTF8 | fFlags, pApp || 0,
1020a6ca996eSstephan          xFunc, xStep, xFinal, xDestroy);
1021a6ca996eSstephan      }
1022a6ca996eSstephan      DB.checkRc(this, rc);
10233961b263Sstephan      return this;
10243961b263Sstephan    }/*createFunction()*/,
10253961b263Sstephan    /**
10263961b263Sstephan       Prepares the given SQL, step()s it one time, and returns
10273961b263Sstephan       the value of the first result column. If it has no results,
10283961b263Sstephan       undefined is returned.
10293961b263Sstephan
10303961b263Sstephan       If passed a second argument, it is treated like an argument
10313961b263Sstephan       to Stmt.bind(), so may be any type supported by that
10323961b263Sstephan       function. Passing the undefined value is the same as passing
10333961b263Sstephan       no value, which is useful when...
10343961b263Sstephan
10353961b263Sstephan       If passed a 3rd argument, it is expected to be one of the
10363961b263Sstephan       SQLITE_{typename} constants. Passing the undefined value is
10373961b263Sstephan       the same as not passing a value.
10383961b263Sstephan
10393961b263Sstephan       Throws on error (e.g. malformed SQL).
10403961b263Sstephan    */
10413961b263Sstephan    selectValue: function(sql,bind,asType){
10423961b263Sstephan      let stmt, rc;
10433961b263Sstephan      try {
10443961b263Sstephan        stmt = this.prepare(sql).bind(bind);
10453961b263Sstephan        if(stmt.step()) rc = stmt.get(0,asType);
10463961b263Sstephan      }finally{
10473961b263Sstephan        if(stmt) stmt.finalize();
10483961b263Sstephan      }
10493961b263Sstephan      return rc;
10503961b263Sstephan    },
105150ef0139Sstephan    /**
105250ef0139Sstephan       Prepares the given SQL, step()s it one time, and returns an
105350ef0139Sstephan       array containing the values of the first result row. If it has
105450ef0139Sstephan       no results, `undefined` is returned.
105550ef0139Sstephan
105650ef0139Sstephan       If passed a second argument other than `undefined`, it is
105750ef0139Sstephan       treated like an argument to Stmt.bind(), so may be any type
105850ef0139Sstephan       supported by that function.
105950ef0139Sstephan
106050ef0139Sstephan       Throws on error (e.g. malformed SQL).
106150ef0139Sstephan    */
106250ef0139Sstephan    selectArray: function(sql,bind){
106350ef0139Sstephan      return __selectFirstRow(this, sql, bind, []);
106450ef0139Sstephan    },
106550ef0139Sstephan
106650ef0139Sstephan    /**
106750ef0139Sstephan       Prepares the given SQL, step()s it one time, and returns an
106850ef0139Sstephan       object containing the key/value pairs of the first result
106950ef0139Sstephan       row. If it has no results, `undefined` is returned.
107050ef0139Sstephan
107150ef0139Sstephan       Note that the order of returned object's keys is not guaranteed
107250ef0139Sstephan       to be the same as the order of the fields in the query string.
107350ef0139Sstephan
107450ef0139Sstephan       If passed a second argument other than `undefined`, it is
107550ef0139Sstephan       treated like an argument to Stmt.bind(), so may be any type
107650ef0139Sstephan       supported by that function.
107750ef0139Sstephan
107850ef0139Sstephan       Throws on error (e.g. malformed SQL).
107950ef0139Sstephan    */
108050ef0139Sstephan    selectObject: function(sql,bind){
108150ef0139Sstephan      return __selectFirstRow(this, sql, bind, {});
108250ef0139Sstephan    },
10833961b263Sstephan
10843961b263Sstephan    /**
10853961b263Sstephan       Returns the number of currently-opened Stmt handles for this db
10863961b263Sstephan       handle, or 0 if this DB instance is closed.
10873961b263Sstephan    */
10883961b263Sstephan    openStatementCount: function(){
10893961b263Sstephan      return this.pointer ? Object.keys(__stmtMap.get(this)).length : 0;
10903961b263Sstephan    },
10913961b263Sstephan
10923961b263Sstephan    /**
1093f79451eeSstephan       Starts a transaction, calls the given callback, and then either
1094d869a21dSstephan       rolls back or commits the savepoint, depending on whether the
1095d869a21dSstephan       callback throws. The callback is passed this db object as its
1096d869a21dSstephan       only argument. On success, returns the result of the
1097d869a21dSstephan       callback. Throws on error.
1098d869a21dSstephan
1099d869a21dSstephan       Note that transactions may not be nested, so this will throw if
1100d869a21dSstephan       it is called recursively. For nested transactions, use the
1101d869a21dSstephan       savepoint() method or manually manage SAVEPOINTs using exec().
1102f79451eeSstephan     */
1103d869a21dSstephan    transaction: function(callback){
1104d869a21dSstephan      affirmDbOpen(this).exec("BEGIN");
1105d869a21dSstephan      try {
1106d869a21dSstephan        const rc = callback(this);
1107d869a21dSstephan        this.exec("COMMIT");
1108f79451eeSstephan        return rc;
1109d869a21dSstephan      }catch(e){
1110d869a21dSstephan        this.exec("ROLLBACK");
1111d869a21dSstephan        throw e;
1112d869a21dSstephan      }
1113d869a21dSstephan    },
1114d869a21dSstephan
1115d869a21dSstephan    /**
1116d869a21dSstephan       This works similarly to transaction() but uses sqlite3's SAVEPOINT
1117d869a21dSstephan       feature. This function starts a savepoint (with an unspecified name)
1118d869a21dSstephan       and calls the given callback function, passing it this db object.
1119d869a21dSstephan       If the callback returns, the savepoint is released (committed). If
1120d869a21dSstephan       the callback throws, the savepoint is rolled back. If it does not
1121d869a21dSstephan       throw, it returns the result of the callback.
1122d869a21dSstephan    */
1123d869a21dSstephan    savepoint: function(callback){
1124d869a21dSstephan      affirmDbOpen(this).exec("SAVEPOINT oo1");
1125d869a21dSstephan      try {
1126d869a21dSstephan        const rc = callback(this);
1127d869a21dSstephan        this.exec("RELEASE oo1");
1128d869a21dSstephan        return rc;
1129d869a21dSstephan      }catch(e){
1130335ad526Sstephan        this.exec("ROLLBACK to SAVEPOINT oo1; RELEASE SAVEPOINT oo1");
1131d869a21dSstephan        throw e;
1132d869a21dSstephan      }
11333961b263Sstephan    }
11343961b263Sstephan  }/*DB.prototype*/;
11353961b263Sstephan
11363961b263Sstephan
11373961b263Sstephan  /** Throws if the given Stmt has been finalized, else stmt is
11383961b263Sstephan      returned. */
11393961b263Sstephan  const affirmStmtOpen = function(stmt){
11403961b263Sstephan    if(!stmt.pointer) toss3("Stmt has been closed.");
11413961b263Sstephan    return stmt;
11423961b263Sstephan  };
11433961b263Sstephan
11443961b263Sstephan  /** Returns an opaque truthy value from the BindTypes
11453961b263Sstephan      enum if v's type is a valid bindable type, else
11463961b263Sstephan      returns a falsy value. As a special case, a value of
11473961b263Sstephan      undefined is treated as a bind type of null. */
11483961b263Sstephan  const isSupportedBindType = function(v){
11493961b263Sstephan    let t = BindTypes[(null===v||undefined===v) ? 'null' : typeof v];
11503961b263Sstephan    switch(t){
11513961b263Sstephan        case BindTypes.boolean:
11523961b263Sstephan        case BindTypes.null:
11533961b263Sstephan        case BindTypes.number:
11543961b263Sstephan        case BindTypes.string:
11553961b263Sstephan          return t;
11563961b263Sstephan        case BindTypes.bigint:
115763e9ec2fSstephan          if(wasm.bigIntEnabled) return t;
11583961b263Sstephan          /* else fall through */
11593961b263Sstephan        default:
11603961b263Sstephan          //console.log("isSupportedBindType",t,v);
11613961b263Sstephan          return util.isBindableTypedArray(v) ? BindTypes.blob : undefined;
11623961b263Sstephan    }
11633961b263Sstephan  };
11643961b263Sstephan
11653961b263Sstephan  /**
11663961b263Sstephan     If isSupportedBindType(v) returns a truthy value, this
11673961b263Sstephan     function returns that value, else it throws.
11683961b263Sstephan  */
11693961b263Sstephan  const affirmSupportedBindType = function(v){
11703961b263Sstephan    //console.log('affirmSupportedBindType',v);
11713961b263Sstephan    return isSupportedBindType(v) || toss3("Unsupported bind() argument type:",typeof v);
11723961b263Sstephan  };
11733961b263Sstephan
11743961b263Sstephan  /**
11753961b263Sstephan     If key is a number and within range of stmt's bound parameter
11763961b263Sstephan     count, key is returned.
11773961b263Sstephan
11783961b263Sstephan     If key is not a number then it is checked against named
11793961b263Sstephan     parameters. If a match is found, its index is returned.
11803961b263Sstephan
11813961b263Sstephan     Else it throws.
11823961b263Sstephan  */
11833961b263Sstephan  const affirmParamIndex = function(stmt,key){
11843961b263Sstephan    const n = ('number'===typeof key)
11853961b263Sstephan          ? key : capi.sqlite3_bind_parameter_index(stmt.pointer, key);
11863961b263Sstephan    if(0===n || !util.isInt32(n)){
11873961b263Sstephan      toss3("Invalid bind() parameter name: "+key);
11883961b263Sstephan    }
11893961b263Sstephan    else if(n<1 || n>stmt.parameterCount) toss3("Bind index",key,"is out of range.");
11903961b263Sstephan    return n;
11913961b263Sstephan  };
11923961b263Sstephan
11933961b263Sstephan  /**
11943961b263Sstephan     If stmt._isLocked is truthy, this throws an exception
11953961b263Sstephan     complaining that the 2nd argument (an operation name,
11963961b263Sstephan     e.g. "bind()") is not legal while the statement is "locked".
11973961b263Sstephan     Locking happens before an exec()-like callback is passed a
11983961b263Sstephan     statement, to ensure that the callback does not mutate or
11993961b263Sstephan     finalize the statement. If it does not throw, it returns stmt.
12003961b263Sstephan  */
12013961b263Sstephan  const affirmUnlocked = function(stmt,currentOpName){
12023961b263Sstephan    if(stmt._isLocked){
12033961b263Sstephan      toss3("Operation is illegal when statement is locked:",currentOpName);
12043961b263Sstephan    }
12053961b263Sstephan    return stmt;
12063961b263Sstephan  };
12073961b263Sstephan
12083961b263Sstephan  /**
12093961b263Sstephan     Binds a single bound parameter value on the given stmt at the
12103961b263Sstephan     given index (numeric or named) using the given bindType (see
12113961b263Sstephan     the BindTypes enum) and value. Throws on error. Returns stmt on
12123961b263Sstephan     success.
12133961b263Sstephan  */
12143961b263Sstephan  const bindOne = function f(stmt,ndx,bindType,val){
12153961b263Sstephan    affirmUnlocked(stmt, 'bind()');
12163961b263Sstephan    if(!f._){
1217510a9d1cSstephan      f._tooBigInt = (v)=>toss3(
1218510a9d1cSstephan        "BigInt value is too big to store without precision loss:", v
1219510a9d1cSstephan      );
12203961b263Sstephan      /* Reminder: when not in BigInt mode, it's impossible for
12213961b263Sstephan         JS to represent a number out of the range we can bind,
12223961b263Sstephan         so we have no range checking. */
12233961b263Sstephan      f._ = {
12243961b263Sstephan        string: function(stmt, ndx, val, asBlob){
12253961b263Sstephan          if(1){
12263961b263Sstephan            /* _Hypothetically_ more efficient than the impl in the 'else' block. */
122763e9ec2fSstephan            const stack = wasm.scopedAllocPush();
12283961b263Sstephan            try{
122963e9ec2fSstephan              const n = wasm.jstrlen(val);
123063e9ec2fSstephan              const pStr = wasm.scopedAlloc(n);
123163e9ec2fSstephan              wasm.jstrcpy(val, wasm.heap8u(), pStr, n, false);
12323961b263Sstephan              const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
12333961b263Sstephan              return f(stmt.pointer, ndx, pStr, n, capi.SQLITE_TRANSIENT);
12343961b263Sstephan            }finally{
123563e9ec2fSstephan              wasm.scopedAllocPop(stack);
12363961b263Sstephan            }
12373961b263Sstephan          }else{
123863e9ec2fSstephan            const bytes = wasm.jstrToUintArray(val,false);
123963e9ec2fSstephan            const pStr = wasm.alloc(bytes.length || 1);
124063e9ec2fSstephan            wasm.heap8u().set(bytes.length ? bytes : [0], pStr);
12413961b263Sstephan            try{
12423961b263Sstephan              const f = asBlob ? capi.sqlite3_bind_blob : capi.sqlite3_bind_text;
12433961b263Sstephan              return f(stmt.pointer, ndx, pStr, bytes.length, capi.SQLITE_TRANSIENT);
12443961b263Sstephan            }finally{
124563e9ec2fSstephan              wasm.dealloc(pStr);
12463961b263Sstephan            }
12473961b263Sstephan          }
12483961b263Sstephan        }
12493961b263Sstephan      };
1250193ee11fSstephan    }/* static init */
12513961b263Sstephan    affirmSupportedBindType(val);
12523961b263Sstephan    ndx = affirmParamIndex(stmt,ndx);
12533961b263Sstephan    let rc = 0;
12543961b263Sstephan    switch((null===val || undefined===val) ? BindTypes.null : bindType){
12553961b263Sstephan        case BindTypes.null:
12563961b263Sstephan          rc = capi.sqlite3_bind_null(stmt.pointer, ndx);
12573961b263Sstephan          break;
12583961b263Sstephan        case BindTypes.string:
12593961b263Sstephan          rc = f._.string(stmt, ndx, val, false);
12603961b263Sstephan          break;
12613961b263Sstephan        case BindTypes.number: {
12623961b263Sstephan          let m;
12633961b263Sstephan          if(util.isInt32(val)) m = capi.sqlite3_bind_int;
1264193ee11fSstephan          else if('bigint'===typeof val){
1265510a9d1cSstephan            if(!util.bigIntFits64(val)){
1266510a9d1cSstephan              f._tooBigInt(val);
126763e9ec2fSstephan            }else if(wasm.bigIntEnabled){
12683961b263Sstephan              m = capi.sqlite3_bind_int64;
1269510a9d1cSstephan            }else if(util.bigIntFitsDouble(val)){
1270193ee11fSstephan              val = Number(val);
1271193ee11fSstephan              m = capi.sqlite3_bind_double;
1272193ee11fSstephan            }else{
1273510a9d1cSstephan              f._tooBigInt(val);
1274193ee11fSstephan            }
1275193ee11fSstephan          }else{ // !int32, !bigint
1276193ee11fSstephan            val = Number(val);
127763e9ec2fSstephan            if(wasm.bigIntEnabled && Number.isInteger(val)){
12783961b263Sstephan              m = capi.sqlite3_bind_int64;
12793961b263Sstephan            }else{
12803961b263Sstephan              m = capi.sqlite3_bind_double;
12813961b263Sstephan            }
1282193ee11fSstephan          }
12833961b263Sstephan          rc = m(stmt.pointer, ndx, val);
12843961b263Sstephan          break;
12853961b263Sstephan        }
12863961b263Sstephan        case BindTypes.boolean:
12873961b263Sstephan          rc = capi.sqlite3_bind_int(stmt.pointer, ndx, val ? 1 : 0);
12883961b263Sstephan          break;
12893961b263Sstephan        case BindTypes.blob: {
12903961b263Sstephan          if('string'===typeof val){
12913961b263Sstephan            rc = f._.string(stmt, ndx, val, true);
12923961b263Sstephan          }else if(!util.isBindableTypedArray(val)){
12933961b263Sstephan            toss3("Binding a value as a blob requires",
12943961b263Sstephan                  "that it be a string, Uint8Array, or Int8Array.");
12953961b263Sstephan          }else if(1){
12963961b263Sstephan            /* _Hypothetically_ more efficient than the impl in the 'else' block. */
129763e9ec2fSstephan            const stack = wasm.scopedAllocPush();
12983961b263Sstephan            try{
129963e9ec2fSstephan              const pBlob = wasm.scopedAlloc(val.byteLength || 1);
130063e9ec2fSstephan              wasm.heap8().set(val.byteLength ? val : [0], pBlob)
13013961b263Sstephan              rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
13023961b263Sstephan                                         capi.SQLITE_TRANSIENT);
13033961b263Sstephan            }finally{
130463e9ec2fSstephan              wasm.scopedAllocPop(stack);
13053961b263Sstephan            }
13063961b263Sstephan          }else{
130763e9ec2fSstephan            const pBlob = wasm.allocFromTypedArray(val);
13083961b263Sstephan            try{
13093961b263Sstephan              rc = capi.sqlite3_bind_blob(stmt.pointer, ndx, pBlob, val.byteLength,
13103961b263Sstephan                                         capi.SQLITE_TRANSIENT);
13113961b263Sstephan            }finally{
131263e9ec2fSstephan              wasm.dealloc(pBlob);
13133961b263Sstephan            }
13143961b263Sstephan          }
13153961b263Sstephan          break;
13163961b263Sstephan        }
13173961b263Sstephan        default:
13183961b263Sstephan          console.warn("Unsupported bind() argument type:",val);
13193961b263Sstephan          toss3("Unsupported bind() argument type: "+(typeof val));
13203961b263Sstephan    }
1321f79451eeSstephan    if(rc) DB.checkRc(stmt.db.pointer, rc);
13223961b263Sstephan    return stmt;
13233961b263Sstephan  };
13243961b263Sstephan
13253961b263Sstephan  Stmt.prototype = {
13263961b263Sstephan    /**
13273961b263Sstephan       "Finalizes" this statement. This is a no-op if the
13283961b263Sstephan       statement has already been finalizes. Returns
13293961b263Sstephan       undefined. Most methods in this class will throw if called
13303961b263Sstephan       after this is.
13313961b263Sstephan    */
13323961b263Sstephan    finalize: function(){
13333961b263Sstephan      if(this.pointer){
13343961b263Sstephan        affirmUnlocked(this,'finalize()');
13353961b263Sstephan        delete __stmtMap.get(this.db)[this.pointer];
13363961b263Sstephan        capi.sqlite3_finalize(this.pointer);
13373961b263Sstephan        __ptrMap.delete(this);
1338e0c58285Sstephan        delete this._mayGet;
13393961b263Sstephan        delete this.columnCount;
13403961b263Sstephan        delete this.parameterCount;
13413961b263Sstephan        delete this.db;
13423961b263Sstephan        delete this._isLocked;
13433961b263Sstephan      }
13443961b263Sstephan    },
13453961b263Sstephan    /** Clears all bound values. Returns this object.
13463961b263Sstephan        Throws if this statement has been finalized. */
13473961b263Sstephan    clearBindings: function(){
13483961b263Sstephan      affirmUnlocked(affirmStmtOpen(this), 'clearBindings()')
13493961b263Sstephan      capi.sqlite3_clear_bindings(this.pointer);
13503961b263Sstephan      this._mayGet = false;
13513961b263Sstephan      return this;
13523961b263Sstephan    },
13533961b263Sstephan    /**
13543961b263Sstephan       Resets this statement so that it may be step()ed again
13553961b263Sstephan       from the beginning. Returns this object. Throws if this
13563961b263Sstephan       statement has been finalized.
13573961b263Sstephan
13583961b263Sstephan       If passed a truthy argument then this.clearBindings() is
13593961b263Sstephan       also called, otherwise any existing bindings, along with
13603961b263Sstephan       any memory allocated for them, are retained.
13613961b263Sstephan    */
13623961b263Sstephan    reset: function(alsoClearBinds){
13633961b263Sstephan      affirmUnlocked(this,'reset()');
13643961b263Sstephan      if(alsoClearBinds) this.clearBindings();
13653961b263Sstephan      capi.sqlite3_reset(affirmStmtOpen(this).pointer);
13663961b263Sstephan      this._mayGet = false;
13673961b263Sstephan      return this;
13683961b263Sstephan    },
13693961b263Sstephan    /**
13703961b263Sstephan       Binds one or more values to its bindable parameters. It
13713961b263Sstephan       accepts 1 or 2 arguments:
13723961b263Sstephan
13733961b263Sstephan       If passed a single argument, it must be either an array, an
13743961b263Sstephan       object, or a value of a bindable type (see below).
13753961b263Sstephan
13763961b263Sstephan       If passed 2 arguments, the first one is the 1-based bind
13773961b263Sstephan       index or bindable parameter name and the second one must be
13783961b263Sstephan       a value of a bindable type.
13793961b263Sstephan
13803961b263Sstephan       Bindable value types:
13813961b263Sstephan
13823961b263Sstephan       - null is bound as NULL.
13833961b263Sstephan
13843961b263Sstephan       - undefined as a standalone value is a no-op intended to
1385193ee11fSstephan         simplify certain client-side use cases: passing undefined as
1386193ee11fSstephan         a value to this function will not actually bind anything and
1387193ee11fSstephan         this function will skip confirmation that binding is even
1388193ee11fSstephan         legal. (Those semantics simplify certain client-side uses.)
1389193ee11fSstephan         Conversely, a value of undefined as an array or object
1390193ee11fSstephan         property when binding an array/object (see below) is treated
1391193ee11fSstephan         the same as null.
13923961b263Sstephan
1393193ee11fSstephan       - Numbers are bound as either doubles or integers: doubles if
1394193ee11fSstephan         they are larger than 32 bits, else double or int32, depending
1395193ee11fSstephan         on whether they have a fractional part. Booleans are bound as
1396193ee11fSstephan         integer 0 or 1. It is not expected the distinction of binding
1397193ee11fSstephan         doubles which have no fractional parts is integers is
1398193ee11fSstephan         significant for the majority of clients due to sqlite3's data
1399193ee11fSstephan         typing model. If [BigInt] support is enabled then this
1400193ee11fSstephan         routine will bind BigInt values as 64-bit integers if they'll
1401193ee11fSstephan         fit in 64 bits. If that support disabled, it will store the
1402193ee11fSstephan         BigInt as an int32 or a double if it can do so without loss
1403193ee11fSstephan         of precision. If the BigInt is _too BigInt_ then it will
1404193ee11fSstephan         throw.
14053961b263Sstephan
14063961b263Sstephan       - Strings are bound as strings (use bindAsBlob() to force
14073961b263Sstephan         blob binding).
14083961b263Sstephan
14093961b263Sstephan       - Uint8Array and Int8Array instances are bound as blobs.
14103961b263Sstephan         (TODO: binding the other TypedArray types.)
14113961b263Sstephan
14123961b263Sstephan       If passed an array, each element of the array is bound at
14133961b263Sstephan       the parameter index equal to the array index plus 1
14143961b263Sstephan       (because arrays are 0-based but binding is 1-based).
14153961b263Sstephan
14163961b263Sstephan       If passed an object, each object key is treated as a
14173961b263Sstephan       bindable parameter name. The object keys _must_ match any
14183961b263Sstephan       bindable parameter names, including any `$`, `@`, or `:`
14193961b263Sstephan       prefix. Because `$` is a legal identifier chararacter in
14203961b263Sstephan       JavaScript, that is the suggested prefix for bindable
14213961b263Sstephan       parameters: `stmt.bind({$a: 1, $b: 2})`.
14223961b263Sstephan
14233961b263Sstephan       It returns this object on success and throws on
14243961b263Sstephan       error. Errors include:
14253961b263Sstephan
14263961b263Sstephan       - Any bind index is out of range, a named bind parameter
14273961b263Sstephan       does not match, or this statement has no bindable
14283961b263Sstephan       parameters.
14293961b263Sstephan
14303961b263Sstephan       - Any value to bind is of an unsupported type.
14313961b263Sstephan
14323961b263Sstephan       - Passed no arguments or more than two.
14333961b263Sstephan
14343961b263Sstephan       - The statement has been finalized.
14353961b263Sstephan    */
14363961b263Sstephan    bind: function(/*[ndx,] arg*/){
14373961b263Sstephan      affirmStmtOpen(this);
14383961b263Sstephan      let ndx, arg;
14393961b263Sstephan      switch(arguments.length){
14403961b263Sstephan          case 1: ndx = 1; arg = arguments[0]; break;
14413961b263Sstephan          case 2: ndx = arguments[0]; arg = arguments[1]; break;
14423961b263Sstephan          default: toss3("Invalid bind() arguments.");
14433961b263Sstephan      }
14443961b263Sstephan      if(undefined===arg){
14453961b263Sstephan        /* It might seem intuitive to bind undefined as NULL
14463961b263Sstephan           but this approach simplifies certain client-side
14473961b263Sstephan           uses when passing on arguments between 2+ levels of
14483961b263Sstephan           functions. */
14493961b263Sstephan        return this;
14503961b263Sstephan      }else if(!this.parameterCount){
14513961b263Sstephan        toss3("This statement has no bindable parameters.");
14523961b263Sstephan      }
14533961b263Sstephan      this._mayGet = false;
14543961b263Sstephan      if(null===arg){
14553961b263Sstephan        /* bind NULL */
14563961b263Sstephan        return bindOne(this, ndx, BindTypes.null, arg);
14573961b263Sstephan      }
14583961b263Sstephan      else if(Array.isArray(arg)){
14593961b263Sstephan        /* bind each entry by index */
14603961b263Sstephan        if(1!==arguments.length){
14613961b263Sstephan          toss3("When binding an array, an index argument is not permitted.");
14623961b263Sstephan        }
14633961b263Sstephan        arg.forEach((v,i)=>bindOne(this, i+1, affirmSupportedBindType(v), v));
14643961b263Sstephan        return this;
14653961b263Sstephan      }
14663961b263Sstephan      else if('object'===typeof arg/*null was checked above*/
14673961b263Sstephan              && !util.isBindableTypedArray(arg)){
14683961b263Sstephan        /* Treat each property of arg as a named bound parameter. */
14693961b263Sstephan        if(1!==arguments.length){
14703961b263Sstephan          toss3("When binding an object, an index argument is not permitted.");
14713961b263Sstephan        }
14723961b263Sstephan        Object.keys(arg)
14733961b263Sstephan          .forEach(k=>bindOne(this, k,
14743961b263Sstephan                              affirmSupportedBindType(arg[k]),
14753961b263Sstephan                              arg[k]));
14763961b263Sstephan        return this;
14773961b263Sstephan      }else{
14783961b263Sstephan        return bindOne(this, ndx, affirmSupportedBindType(arg), arg);
14793961b263Sstephan      }
14803961b263Sstephan      toss3("Should not reach this point.");
14813961b263Sstephan    },
14823961b263Sstephan    /**
14833961b263Sstephan       Special case of bind() which binds the given value using the
14843961b263Sstephan       BLOB binding mechanism instead of the default selected one for
14853961b263Sstephan       the value. The ndx may be a numbered or named bind index. The
14863961b263Sstephan       value must be of type string, null/undefined (both get treated
14873961b263Sstephan       as null), or a TypedArray of a type supported by the bind()
14883961b263Sstephan       API.
14893961b263Sstephan
14903961b263Sstephan       If passed a single argument, a bind index of 1 is assumed and
14913961b263Sstephan       the first argument is the value.
14923961b263Sstephan    */
14933961b263Sstephan    bindAsBlob: function(ndx,arg){
14943961b263Sstephan      affirmStmtOpen(this);
14953961b263Sstephan      if(1===arguments.length){
14963961b263Sstephan        arg = ndx;
14973961b263Sstephan        ndx = 1;
14983961b263Sstephan      }
14993961b263Sstephan      const t = affirmSupportedBindType(arg);
15003961b263Sstephan      if(BindTypes.string !== t && BindTypes.blob !== t
15013961b263Sstephan         && BindTypes.null !== t){
15023961b263Sstephan        toss3("Invalid value type for bindAsBlob()");
15033961b263Sstephan      }
15043961b263Sstephan      bindOne(this, ndx, BindTypes.blob, arg);
15053961b263Sstephan      this._mayGet = false;
15063961b263Sstephan      return this;
15073961b263Sstephan    },
15083961b263Sstephan    /**
1509f79451eeSstephan       Steps the statement one time. If the result indicates that a
1510f79451eeSstephan       row of data is available, a truthy value is returned.
1511f79451eeSstephan       If no row of data is available, a falsy
1512f79451eeSstephan       value is returned.  Throws on error.
15133961b263Sstephan    */
15143961b263Sstephan    step: function(){
15153961b263Sstephan      affirmUnlocked(this, 'step()');
15163961b263Sstephan      const rc = capi.sqlite3_step(affirmStmtOpen(this).pointer);
15173961b263Sstephan      switch(rc){
15183961b263Sstephan          case capi.SQLITE_DONE: return this._mayGet = false;
15193961b263Sstephan          case capi.SQLITE_ROW: return this._mayGet = true;
15203961b263Sstephan          default:
15213961b263Sstephan            this._mayGet = false;
1522510a9d1cSstephan            console.warn("sqlite3_step() rc=",rc,
15238a8244b5Sstephan                         capi.sqlite3_js_rc_str(rc),
1524510a9d1cSstephan                         "SQL =", capi.sqlite3_sql(this.pointer));
1525f79451eeSstephan            DB.checkRc(this.db.pointer, rc);
1526f79451eeSstephan      }
1527f79451eeSstephan    },
1528f79451eeSstephan    /**
1529e0c58285Sstephan       Functions exactly like step() except that...
1530e0c58285Sstephan
1531e0c58285Sstephan       1) On success, it calls this.reset() and returns this object.
1532e0c58285Sstephan       2) On error, it throws and does not call reset().
1533e0c58285Sstephan
1534e0c58285Sstephan       This is intended to simplify constructs like:
1535e0c58285Sstephan
1536e0c58285Sstephan       ```
1537e0c58285Sstephan       for(...) {
1538e0c58285Sstephan         stmt.bind(...).stepReset();
1539e0c58285Sstephan       }
1540e0c58285Sstephan       ```
1541e0c58285Sstephan
1542e0c58285Sstephan       Note that the reset() call makes it illegal to call this.get()
1543e0c58285Sstephan       after the step.
1544e0c58285Sstephan    */
1545e0c58285Sstephan    stepReset: function(){
1546e0c58285Sstephan      this.step();
1547e0c58285Sstephan      return this.reset();
1548e0c58285Sstephan    },
1549e0c58285Sstephan    /**
155028ef9bddSstephan       Functions like step() except that it finalizes this statement
155128ef9bddSstephan       immediately after stepping unless the step cannot be performed
155228ef9bddSstephan       because the statement is locked. Throws on error, but any error
155328ef9bddSstephan       other than the statement-is-locked case will also trigger
155428ef9bddSstephan       finalization of this statement.
1555f79451eeSstephan
1556f79451eeSstephan       On success, it returns true if the step indicated that a row of
1557f79451eeSstephan       data was available, else it returns false.
1558f79451eeSstephan
1559f79451eeSstephan       This is intended to simplify use cases such as:
1560f79451eeSstephan
1561f79451eeSstephan       ```
1562193ee11fSstephan       aDb.prepare("insert into foo(a) values(?)").bind(123).stepFinalize();
1563f79451eeSstephan       ```
1564f79451eeSstephan    */
1565f79451eeSstephan    stepFinalize: function(){
1566e0c58285Sstephan      const rc = this.step();
1567f79451eeSstephan      this.finalize();
1568e0c58285Sstephan      return rc;
15693961b263Sstephan    },
15703961b263Sstephan    /**
15713961b263Sstephan       Fetches the value from the given 0-based column index of
15723961b263Sstephan       the current data row, throwing if index is out of range.
15733961b263Sstephan
15743961b263Sstephan       Requires that step() has just returned a truthy value, else
15753961b263Sstephan       an exception is thrown.
15763961b263Sstephan
15773961b263Sstephan       By default it will determine the data type of the result
15783961b263Sstephan       automatically. If passed a second arugment, it must be one
15793961b263Sstephan       of the enumeration values for sqlite3 types, which are
15803961b263Sstephan       defined as members of the sqlite3 module: SQLITE_INTEGER,
15813961b263Sstephan       SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB. Any other value,
15823961b263Sstephan       except for undefined, will trigger an exception. Passing
15833961b263Sstephan       undefined is the same as not passing a value. It is legal
15843961b263Sstephan       to, e.g., fetch an integer value as a string, in which case
15853961b263Sstephan       sqlite3 will convert the value to a string.
15863961b263Sstephan
15873961b263Sstephan       If ndx is an array, this function behaves a differently: it
15883961b263Sstephan       assigns the indexes of the array, from 0 to the number of
15893961b263Sstephan       result columns, to the values of the corresponding column,
15903961b263Sstephan       and returns that array.
15913961b263Sstephan
15923961b263Sstephan       If ndx is a plain object, this function behaves even
15933961b263Sstephan       differentlier: it assigns the properties of the object to
15943961b263Sstephan       the values of their corresponding result columns.
15953961b263Sstephan
15963961b263Sstephan       Blobs are returned as Uint8Array instances.
15973961b263Sstephan
15983961b263Sstephan       Potential TODO: add type ID SQLITE_JSON, which fetches the
15993961b263Sstephan       result as a string and passes it (if it's not null) to
16003961b263Sstephan       JSON.parse(), returning the result of that. Until then,
16013961b263Sstephan       getJSON() can be used for that.
16023961b263Sstephan    */
16033961b263Sstephan    get: function(ndx,asType){
16043961b263Sstephan      if(!affirmStmtOpen(this)._mayGet){
16053961b263Sstephan        toss3("Stmt.step() has not (recently) returned true.");
16063961b263Sstephan      }
16073961b263Sstephan      if(Array.isArray(ndx)){
16083961b263Sstephan        let i = 0;
16093961b263Sstephan        while(i<this.columnCount){
16103961b263Sstephan          ndx[i] = this.get(i++);
16113961b263Sstephan        }
16123961b263Sstephan        return ndx;
16133961b263Sstephan      }else if(ndx && 'object'===typeof ndx){
16143961b263Sstephan        let i = 0;
16153961b263Sstephan        while(i<this.columnCount){
16163961b263Sstephan          ndx[capi.sqlite3_column_name(this.pointer,i)] = this.get(i++);
16173961b263Sstephan        }
16183961b263Sstephan        return ndx;
16193961b263Sstephan      }
16203961b263Sstephan      affirmColIndex(this, ndx);
16213961b263Sstephan      switch(undefined===asType
16223961b263Sstephan             ? capi.sqlite3_column_type(this.pointer, ndx)
16233961b263Sstephan             : asType){
16243961b263Sstephan          case capi.SQLITE_NULL: return null;
16253961b263Sstephan          case capi.SQLITE_INTEGER:{
162663e9ec2fSstephan            if(wasm.bigIntEnabled){
16273961b263Sstephan              const rc = capi.sqlite3_column_int64(this.pointer, ndx);
16283961b263Sstephan              if(rc>=Number.MIN_SAFE_INTEGER && rc<=Number.MAX_SAFE_INTEGER){
16293961b263Sstephan                /* Coerce "normal" number ranges to normal number values,
16303961b263Sstephan                   and only return BigInt-type values for numbers out of this
16313961b263Sstephan                   range. */
16323961b263Sstephan                return Number(rc).valueOf();
16333961b263Sstephan              }
16343961b263Sstephan              return rc;
16353961b263Sstephan            }else{
16363961b263Sstephan              const rc = capi.sqlite3_column_double(this.pointer, ndx);
16373961b263Sstephan              if(rc>Number.MAX_SAFE_INTEGER || rc<Number.MIN_SAFE_INTEGER){
16383961b263Sstephan                /* Throwing here is arguable but, since we're explicitly
16393961b263Sstephan                   extracting an SQLITE_INTEGER-type value, it seems fair to throw
16403961b263Sstephan                   if the extracted number is out of range for that type.
16413961b263Sstephan                   This policy may be laxened to simply pass on the number and
16423961b263Sstephan                   hope for the best, as the C API would do. */
16433961b263Sstephan                toss3("Integer is out of range for JS integer range: "+rc);
16443961b263Sstephan              }
16453961b263Sstephan              //console.log("get integer rc=",rc,isInt32(rc));
16463961b263Sstephan              return util.isInt32(rc) ? (rc | 0) : rc;
16473961b263Sstephan            }
16483961b263Sstephan          }
16493961b263Sstephan          case capi.SQLITE_FLOAT:
16503961b263Sstephan            return capi.sqlite3_column_double(this.pointer, ndx);
16513961b263Sstephan          case capi.SQLITE_TEXT:
16523961b263Sstephan            return capi.sqlite3_column_text(this.pointer, ndx);
16533961b263Sstephan          case capi.SQLITE_BLOB: {
16543961b263Sstephan            const n = capi.sqlite3_column_bytes(this.pointer, ndx),
16553961b263Sstephan                  ptr = capi.sqlite3_column_blob(this.pointer, ndx),
16563961b263Sstephan                  rc = new Uint8Array(n);
165763e9ec2fSstephan            //heap = n ? wasm.heap8() : false;
165863e9ec2fSstephan            if(n) rc.set(wasm.heap8u().slice(ptr, ptr+n), 0);
16593961b263Sstephan            //for(let i = 0; i < n; ++i) rc[i] = heap[ptr + i];
16603961b263Sstephan            if(n && this.db._blobXfer instanceof Array){
16613961b263Sstephan              /* This is an optimization soley for the
16623961b263Sstephan                 Worker-based API. These values will be
16633961b263Sstephan                 transfered to the main thread directly
16643961b263Sstephan                 instead of being copied. */
16653961b263Sstephan              this.db._blobXfer.push(rc.buffer);
16663961b263Sstephan            }
16673961b263Sstephan            return rc;
16683961b263Sstephan          }
16693961b263Sstephan          default: toss3("Don't know how to translate",
16703961b263Sstephan                         "type of result column #"+ndx+".");
16713961b263Sstephan      }
1672e0c58285Sstephan      toss3("Not reached.");
16733961b263Sstephan    },
16743961b263Sstephan    /** Equivalent to get(ndx) but coerces the result to an
16753961b263Sstephan        integer. */
16763961b263Sstephan    getInt: function(ndx){return this.get(ndx,capi.SQLITE_INTEGER)},
16773961b263Sstephan    /** Equivalent to get(ndx) but coerces the result to a
16783961b263Sstephan        float. */
16793961b263Sstephan    getFloat: function(ndx){return this.get(ndx,capi.SQLITE_FLOAT)},
16803961b263Sstephan    /** Equivalent to get(ndx) but coerces the result to a
16813961b263Sstephan        string. */
16823961b263Sstephan    getString: function(ndx){return this.get(ndx,capi.SQLITE_TEXT)},
16833961b263Sstephan    /** Equivalent to get(ndx) but coerces the result to a
16843961b263Sstephan        Uint8Array. */
16853961b263Sstephan    getBlob: function(ndx){return this.get(ndx,capi.SQLITE_BLOB)},
16863961b263Sstephan    /**
16873961b263Sstephan       A convenience wrapper around get() which fetches the value
16883961b263Sstephan       as a string and then, if it is not null, passes it to
16893961b263Sstephan       JSON.parse(), returning that result. Throws if parsing
16903961b263Sstephan       fails. If the result is null, null is returned. An empty
16913961b263Sstephan       string, on the other hand, will trigger an exception.
16923961b263Sstephan    */
16933961b263Sstephan    getJSON: function(ndx){
16943961b263Sstephan      const s = this.get(ndx, capi.SQLITE_STRING);
16953961b263Sstephan      return null===s ? s : JSON.parse(s);
16963961b263Sstephan    },
16973961b263Sstephan    // Design note: the only reason most of these getters have a 'get'
16983961b263Sstephan    // prefix is for consistency with getVALUE_TYPE().  The latter
1699193ee11fSstephan    // arguably really need that prefix for API readability and the
17003961b263Sstephan    // rest arguably don't, but consistency is a powerful thing.
17013961b263Sstephan    /**
17023961b263Sstephan       Returns the result column name of the given index, or
17033961b263Sstephan       throws if index is out of bounds or this statement has been
17043961b263Sstephan       finalized. This can be used without having run step()
17053961b263Sstephan       first.
17063961b263Sstephan    */
17073961b263Sstephan    getColumnName: function(ndx){
17083961b263Sstephan      return capi.sqlite3_column_name(
17093961b263Sstephan        affirmColIndex(affirmStmtOpen(this),ndx).pointer, ndx
17103961b263Sstephan      );
17113961b263Sstephan    },
17123961b263Sstephan    /**
17133961b263Sstephan       If this statement potentially has result columns, this
17143961b263Sstephan       function returns an array of all such names. If passed an
17153961b263Sstephan       array, it is used as the target and all names are appended
17163961b263Sstephan       to it. Returns the target array. Throws if this statement
17173961b263Sstephan       cannot have result columns. This object's columnCount member
17183961b263Sstephan       holds the number of columns.
17193961b263Sstephan    */
1720193ee11fSstephan    getColumnNames: function(tgt=[]){
17213961b263Sstephan      affirmColIndex(affirmStmtOpen(this),0);
17223961b263Sstephan      for(let i = 0; i < this.columnCount; ++i){
17233961b263Sstephan        tgt.push(capi.sqlite3_column_name(this.pointer, i));
17243961b263Sstephan      }
17253961b263Sstephan      return tgt;
17263961b263Sstephan    },
17273961b263Sstephan    /**
17283961b263Sstephan       If this statement has named bindable parameters and the
17293961b263Sstephan       given name matches one, its 1-based bind index is
17303961b263Sstephan       returned. If no match is found, 0 is returned. If it has no
17313961b263Sstephan       bindable parameters, the undefined value is returned.
17323961b263Sstephan    */
17333961b263Sstephan    getParamIndex: function(name){
17343961b263Sstephan      return (affirmStmtOpen(this).parameterCount
17353961b263Sstephan              ? capi.sqlite3_bind_parameter_index(this.pointer, name)
17363961b263Sstephan              : undefined);
17373961b263Sstephan    }
17383961b263Sstephan  }/*Stmt.prototype*/;
17393961b263Sstephan
17403961b263Sstephan  {/* Add the `pointer` property to DB and Stmt. */
17413961b263Sstephan    const prop = {
17423961b263Sstephan      enumerable: true,
17433961b263Sstephan      get: function(){return __ptrMap.get(this)},
17443961b263Sstephan      set: ()=>toss3("The pointer property is read-only.")
17453961b263Sstephan    }
17463961b263Sstephan    Object.defineProperty(Stmt.prototype, 'pointer', prop);
17473961b263Sstephan    Object.defineProperty(DB.prototype, 'pointer', prop);
17483961b263Sstephan  }
17493961b263Sstephan
17503961b263Sstephan  /** The OO API's public namespace. */
17513961b263Sstephan  sqlite3.oo1 = {
17523961b263Sstephan    version: {
17533961b263Sstephan      lib: capi.sqlite3_libversion(),
17543961b263Sstephan      ooApi: "0.1"
17553961b263Sstephan    },
17563961b263Sstephan    DB,
1757e681b651Sstephan    Stmt
1758e3cd6760Sstephan  }/*oo1 object*/;
1759f6c686c9Sstephan
1760a6ca996eSstephan  if(util.isUIThread()){
1761f6c686c9Sstephan    /**
1762f6c686c9Sstephan       Functionally equivalent to DB(storageName,'c','kvvfs') except
1763f6c686c9Sstephan       that it throws if the given storage name is not one of 'local'
1764f6c686c9Sstephan       or 'session'.
1765f6c686c9Sstephan    */
1766f6c686c9Sstephan    sqlite3.oo1.JsStorageDb = function(storageName='session'){
1767f6c686c9Sstephan      if('session'!==storageName && 'local'!==storageName){
1768f6c686c9Sstephan        toss3("JsStorageDb db name must be one of 'session' or 'local'.");
1769f6c686c9Sstephan      }
1770f6c686c9Sstephan      dbCtorHelper.call(this, {
1771f6c686c9Sstephan        filename: storageName,
1772f6c686c9Sstephan        flags: 'c',
1773f6c686c9Sstephan        vfs: "kvvfs"
1774f6c686c9Sstephan      });
1775f6c686c9Sstephan    };
177607c0b722Sstephan    const jdb = sqlite3.oo1.JsStorageDb;
177707c0b722Sstephan    jdb.prototype = Object.create(DB.prototype);
17788a8244b5Sstephan    /** Equivalent to sqlite3_js_kvvfs_clear(). */
17798a8244b5Sstephan    jdb.clearStorage = capi.sqlite3_js_kvvfs_clear;
178007c0b722Sstephan    /**
178107c0b722Sstephan       Clears this database instance's storage or throws if this
178207c0b722Sstephan       instance has been closed. Returns the number of
178307c0b722Sstephan       database blocks which were cleaned up.
178407c0b722Sstephan    */
178507c0b722Sstephan    jdb.prototype.clearStorage = function(){
178607c0b722Sstephan      return jdb.clearStorage(affirmDbOpen(this).filename);
178707c0b722Sstephan    };
17888a8244b5Sstephan    /** Equivalent to sqlite3_js_kvvfs_size(). */
17898a8244b5Sstephan    jdb.storageSize = capi.sqlite3_js_kvvfs_size;
179007c0b722Sstephan    /**
179107c0b722Sstephan       Returns the _approximate_ number of bytes this database takes
179207c0b722Sstephan       up in its storage or throws if this instance has been closed.
179307c0b722Sstephan    */
179407c0b722Sstephan    jdb.prototype.storageSize = function(){
179507c0b722Sstephan      return jdb.storageSize(affirmDbOpen(this).filename);
179607c0b722Sstephan    };
179707c0b722Sstephan  }/*main-window-only bits*/
17985360f5fcSstephan
1799e3cd6760Sstephan});
1800e3cd6760Sstephan
1801