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