1453af2f6Sstephan/* 2453af2f6Sstephan 2022-07-22 3453af2f6Sstephan 4453af2f6Sstephan The author disclaims copyright to this source code. In place of a 5453af2f6Sstephan legal notice, here is a blessing: 6453af2f6Sstephan 7453af2f6Sstephan * May you do good and not evil. 8453af2f6Sstephan * May you find forgiveness for yourself and forgive others. 9453af2f6Sstephan * May you share freely, never taking more than you give. 10453af2f6Sstephan 11453af2f6Sstephan *********************************************************************** 12453af2f6Sstephan 13453af2f6Sstephan This file implements the initializer for the sqlite3 "Worker API 14453af2f6Sstephan #1", a very basic DB access API intended to be scripted from a main 15453af2f6Sstephan window thread via Worker-style messages. Because of limitations in 16453af2f6Sstephan that type of communication, this API is minimalistic and only 17453af2f6Sstephan capable of serving relatively basic DB requests (e.g. it cannot 18453af2f6Sstephan process nested query loops concurrently). 19453af2f6Sstephan 20453af2f6Sstephan This file requires that the core C-style sqlite3 API and OO API #1 21453af2f6Sstephan have been loaded. 22453af2f6Sstephan*/ 23453af2f6Sstephan 24453af2f6Sstephan/** 259afff9f3Sstephan sqlite3.initWorker1API() implements a Worker-based wrapper around 269afff9f3Sstephan SQLite3 OO API #1, colloquially known as "Worker API #1". 27453af2f6Sstephan 28453af2f6Sstephan In order to permit this API to be loaded in worker threads without 29453af2f6Sstephan automatically registering onmessage handlers, initializing the 309afff9f3Sstephan worker API requires calling initWorker1API(). If this function is 319afff9f3Sstephan called from a non-worker thread then it throws an exception. It 329afff9f3Sstephan must only be called once per Worker. 33453af2f6Sstephan 34453af2f6Sstephan When initialized, it installs message listeners to receive Worker 35453af2f6Sstephan messages and then it posts a message in the form: 36453af2f6Sstephan 37453af2f6Sstephan ``` 38a9ac2ed0Sstephan {type:'sqlite3-api', result:'worker1-ready'} 39453af2f6Sstephan ``` 40453af2f6Sstephan 41453af2f6Sstephan to let the client know that it has been initialized. Clients may 42453af2f6Sstephan optionally depend on this function not returning until 43453af2f6Sstephan initialization is complete, as the initialization is synchronous. 44453af2f6Sstephan In some contexts, however, listening for the above message is 45453af2f6Sstephan a better fit. 463734401aSstephan 473734401aSstephan Note that the worker-based interface can be slightly quirky because 483734401aSstephan of its async nature. In particular, any number of messages may be posted 493734401aSstephan to the worker before it starts handling any of them. If, e.g., an 503734401aSstephan "open" operation fails, any subsequent messages will fail. The 513734401aSstephan Promise-based wrapper for this API (`sqlite3-worker1-promiser.js`) 523734401aSstephan is more comfortable to use in that regard. 533734401aSstephan 549afff9f3Sstephan The documentation for the input and output worker messages for 559afff9f3Sstephan this API follows... 563734401aSstephan 579afff9f3Sstephan ==================================================================== 589afff9f3Sstephan Common message format... 599afff9f3Sstephan 609afff9f3Sstephan Each message posted to the worker has an operation-independent 619afff9f3Sstephan envelope and operation-dependent arguments: 629afff9f3Sstephan 639afff9f3Sstephan ``` 649afff9f3Sstephan { 659afff9f3Sstephan type: string, // one of: 'open', 'close', 'exec', 'config-get' 669afff9f3Sstephan 679afff9f3Sstephan messageId: OPTIONAL arbitrary value. The worker will copy it as-is 689afff9f3Sstephan into response messages to assist in client-side dispatching. 699afff9f3Sstephan 709afff9f3Sstephan dbId: a db identifier string (returned by 'open') which tells the 719afff9f3Sstephan operation which database instance to work on. If not provided, the 729afff9f3Sstephan first-opened db is used. This is an "opaque" value, with no 739afff9f3Sstephan inherently useful syntax or information. Its value is subject to 749afff9f3Sstephan change with any given build of this API and cannot be used as a 759afff9f3Sstephan basis for anything useful beyond its one intended purpose. 769afff9f3Sstephan 779afff9f3Sstephan args: ...operation-dependent arguments... 789afff9f3Sstephan 799afff9f3Sstephan // the framework may add other properties for testing or debugging 809afff9f3Sstephan // purposes. 819afff9f3Sstephan 829afff9f3Sstephan } 839afff9f3Sstephan ``` 849afff9f3Sstephan 859afff9f3Sstephan Response messages, posted back to the main thread, look like: 869afff9f3Sstephan 879afff9f3Sstephan ``` 889afff9f3Sstephan { 899afff9f3Sstephan type: string. Same as above except for error responses, which have the type 909afff9f3Sstephan 'error', 919afff9f3Sstephan 929afff9f3Sstephan messageId: same value, if any, provided by the inbound message 939afff9f3Sstephan 949afff9f3Sstephan dbId: the id of the db which was operated on, if any, as returned 959afff9f3Sstephan by the corresponding 'open' operation. 969afff9f3Sstephan 979afff9f3Sstephan result: ...operation-dependent result... 989afff9f3Sstephan 999afff9f3Sstephan } 1009afff9f3Sstephan ``` 1019afff9f3Sstephan 1029afff9f3Sstephan ==================================================================== 1039afff9f3Sstephan Error responses 1049afff9f3Sstephan 1059afff9f3Sstephan Errors are reported messages in an operation-independent format: 1069afff9f3Sstephan 1079afff9f3Sstephan ``` 1089afff9f3Sstephan { 109fd31ae3bSstephan type: "error", 1109afff9f3Sstephan 1119afff9f3Sstephan messageId: ...as above..., 1129afff9f3Sstephan 1139afff9f3Sstephan dbId: ...as above... 1149afff9f3Sstephan 1159afff9f3Sstephan result: { 1169afff9f3Sstephan 1179afff9f3Sstephan operation: type of the triggering operation: 'open', 'close', ... 1189afff9f3Sstephan 1199afff9f3Sstephan message: ...error message text... 1209afff9f3Sstephan 1219afff9f3Sstephan errorClass: string. The ErrorClass.name property from the thrown exception. 1229afff9f3Sstephan 1239afff9f3Sstephan input: the message object which triggered the error. 1249afff9f3Sstephan 1259afff9f3Sstephan stack: _if available_, a stack trace array. 1269afff9f3Sstephan 1279afff9f3Sstephan } 1289afff9f3Sstephan 1299afff9f3Sstephan } 1309afff9f3Sstephan ``` 1319afff9f3Sstephan 1329afff9f3Sstephan 1339afff9f3Sstephan ==================================================================== 1349afff9f3Sstephan "config-get" 1359afff9f3Sstephan 1369afff9f3Sstephan This operation fetches the serializable parts of the sqlite3 API 1379afff9f3Sstephan configuration. 1389afff9f3Sstephan 1399afff9f3Sstephan Message format: 1409afff9f3Sstephan 1419afff9f3Sstephan ``` 1429afff9f3Sstephan { 1439afff9f3Sstephan type: "config-get", 1449afff9f3Sstephan messageId: ...as above..., 1459afff9f3Sstephan args: currently ignored and may be elided. 1469afff9f3Sstephan } 1479afff9f3Sstephan ``` 1489afff9f3Sstephan 1499afff9f3Sstephan Response: 1509afff9f3Sstephan 1519afff9f3Sstephan ``` 1529afff9f3Sstephan { 153fd31ae3bSstephan type: "config-get", 1549afff9f3Sstephan messageId: ...as above..., 1559afff9f3Sstephan result: { 1569afff9f3Sstephan 157d18f1bbfSstephan version: sqlite3.version object 1589afff9f3Sstephan 1599afff9f3Sstephan bigIntEnabled: bool. True if BigInt support is enabled. 1609afff9f3Sstephan 161d18f1bbfSstephan wasmfsOpfsDir: path prefix, if any, _intended_ for use with 162ae589b69Sstephan WASMFS OPFS persistent storage. 1639afff9f3Sstephan 164d18f1bbfSstephan wasmfsOpfsEnabled: true if persistent storage is enabled in the 165d18f1bbfSstephan current environment. Only files stored under wasmfsOpfsDir 166d18f1bbfSstephan will persist using that mechanism, however. It is legal to use 167d18f1bbfSstephan the non-WASMFS OPFS VFS to open a database via a URI-style 168d18f1bbfSstephan db filename. 169d18f1bbfSstephan 1708a8244b5Sstephan vfsList: result of sqlite3.capi.sqlite3_js_vfs_list() 1719afff9f3Sstephan } 1729afff9f3Sstephan } 1739afff9f3Sstephan ``` 1749afff9f3Sstephan 1759afff9f3Sstephan 1769afff9f3Sstephan ==================================================================== 1779afff9f3Sstephan "open" a database 1789afff9f3Sstephan 1799afff9f3Sstephan Message format: 1809afff9f3Sstephan 1819afff9f3Sstephan ``` 1829afff9f3Sstephan { 1839afff9f3Sstephan type: "open", 1849afff9f3Sstephan messageId: ...as above..., 1859afff9f3Sstephan args:{ 1869afff9f3Sstephan 1879afff9f3Sstephan filename [=":memory:" or "" (unspecified)]: the db filename. 188d18f1bbfSstephan See the sqlite3.oo1.DB constructor for peculiarities and 189d18f1bbfSstephan transformations, 1909afff9f3Sstephan 19149048b14Sstephan vfs: sqlite3_vfs name. Ignored if filename is ":memory:" or "". 19249048b14Sstephan This may change how the given filename is resolved. 1939afff9f3Sstephan } 1949afff9f3Sstephan } 1959afff9f3Sstephan ``` 1969afff9f3Sstephan 1979afff9f3Sstephan Response: 1989afff9f3Sstephan 1999afff9f3Sstephan ``` 2009afff9f3Sstephan { 201fd31ae3bSstephan type: "open", 2029afff9f3Sstephan messageId: ...as above..., 2039afff9f3Sstephan result: { 2049afff9f3Sstephan filename: db filename, possibly differing from the input. 2059afff9f3Sstephan 2069afff9f3Sstephan dbId: an opaque ID value which must be passed in the message 2079afff9f3Sstephan envelope to other calls in this API to tell them which db to 2089afff9f3Sstephan use. If it is not provided to future calls, they will default to 20902d15a7eSstephan operating on the least-recently-opened db. This property is, for 21002d15a7eSstephan API consistency's sake, also part of the containing message 21102d15a7eSstephan envelope. Only the `open` operation includes it in the `result` 21202d15a7eSstephan property. 2139afff9f3Sstephan 2149afff9f3Sstephan persistent: true if the given filename resides in the 215ae589b69Sstephan known-persistent storage, else false. 216d18f1bbfSstephan 21749048b14Sstephan vfs: name of the VFS the "main" db is using. 2189afff9f3Sstephan } 2199afff9f3Sstephan } 2209afff9f3Sstephan ``` 2219afff9f3Sstephan 2229afff9f3Sstephan ==================================================================== 2239afff9f3Sstephan "close" a database 2249afff9f3Sstephan 2259afff9f3Sstephan Message format: 2269afff9f3Sstephan 2279afff9f3Sstephan ``` 2289afff9f3Sstephan { 2299afff9f3Sstephan type: "close", 2309afff9f3Sstephan messageId: ...as above... 2319afff9f3Sstephan dbId: ...as above... 2328a8244b5Sstephan args: OPTIONAL {unlink: boolean} 2339afff9f3Sstephan } 2349afff9f3Sstephan ``` 2359afff9f3Sstephan 2368a8244b5Sstephan If the `dbId` does not refer to an opened ID, this is a no-op. If 2378a8244b5Sstephan the `args` object contains a truthy `unlink` value then the database 23896b6371dSstephan will be unlinked (deleted) after closing it. The inability to close a 2398a8244b5Sstephan db (because it's not opened) or delete its file does not trigger an 2408a8244b5Sstephan error. 2419afff9f3Sstephan 2429afff9f3Sstephan Response: 2439afff9f3Sstephan 2449afff9f3Sstephan ``` 2459afff9f3Sstephan { 246fd31ae3bSstephan type: "close", 2479afff9f3Sstephan messageId: ...as above..., 2489afff9f3Sstephan result: { 2499afff9f3Sstephan 2509afff9f3Sstephan filename: filename of closed db, or undefined if no db was closed 2519afff9f3Sstephan 2529afff9f3Sstephan } 2539afff9f3Sstephan } 2549afff9f3Sstephan ``` 2559afff9f3Sstephan 2569afff9f3Sstephan ==================================================================== 2579afff9f3Sstephan "exec" SQL 2589afff9f3Sstephan 2599afff9f3Sstephan All SQL execution is processed through the exec operation. It offers 2609afff9f3Sstephan most of the features of the oo1.DB.exec() method, with a few limitations 2619afff9f3Sstephan imposed by the state having to cross thread boundaries. 2629afff9f3Sstephan 2639afff9f3Sstephan Message format: 2649afff9f3Sstephan 2659afff9f3Sstephan ``` 2669afff9f3Sstephan { 2679afff9f3Sstephan type: "exec", 2689afff9f3Sstephan messageId: ...as above... 2699afff9f3Sstephan dbId: ...as above... 2709afff9f3Sstephan args: string (SQL) or {... see below ...} 2719afff9f3Sstephan } 2729afff9f3Sstephan ``` 2739afff9f3Sstephan 2749afff9f3Sstephan Response: 2759afff9f3Sstephan 2769afff9f3Sstephan ``` 2779afff9f3Sstephan { 278fd31ae3bSstephan type: "exec", 2799afff9f3Sstephan messageId: ...as above..., 2809afff9f3Sstephan dbId: ...as above... 2819afff9f3Sstephan result: { 2829afff9f3Sstephan input arguments, possibly modified. See below. 2839afff9f3Sstephan } 2849afff9f3Sstephan } 2859afff9f3Sstephan ``` 2869afff9f3Sstephan 2879afff9f3Sstephan The arguments are in the same form accepted by oo1.DB.exec(), with 2889afff9f3Sstephan the exceptions noted below. 2899afff9f3Sstephan 2909afff9f3Sstephan A function-type args.callback property cannot cross 2919afff9f3Sstephan the window/Worker boundary, so is not useful here. If 2929afff9f3Sstephan args.callback is a string then it is assumed to be a 2939afff9f3Sstephan message type key, in which case a callback function will be 2949afff9f3Sstephan applied which posts each row result via: 2959afff9f3Sstephan 2969afff9f3Sstephan postMessage({type: thatKeyType, 2979afff9f3Sstephan rowNumber: 1-based-#, 2989afff9f3Sstephan row: theRow, 2999afff9f3Sstephan columnNames: anArray 3009afff9f3Sstephan }) 3019afff9f3Sstephan 3029afff9f3Sstephan And, at the end of the result set (whether or not any result rows 3039afff9f3Sstephan were produced), it will post an identical message with 3049afff9f3Sstephan (row=undefined, rowNumber=null) to alert the caller than the result 3059afff9f3Sstephan set is completed. Note that a row value of `null` is a legal row 3069afff9f3Sstephan result for certain arg.rowMode values. 3079afff9f3Sstephan 3089afff9f3Sstephan (Design note: we don't use (row=undefined, rowNumber=undefined) to 3099afff9f3Sstephan indicate end-of-results because fetching those would be 3109afff9f3Sstephan indistinguishable from fetching from an empty object unless the 3119afff9f3Sstephan client used hasOwnProperty() (or similar) to distinguish "missing 3129afff9f3Sstephan property" from "property with the undefined value". Similarly, 3139afff9f3Sstephan `null` is a legal value for `row` in some case , whereas the db 3149afff9f3Sstephan layer won't emit a result value of `undefined`.) 3159afff9f3Sstephan 3169afff9f3Sstephan The callback proxy must not recurse into this interface. An exec() 317ae589b69Sstephan call will tie up the Worker thread, causing any recursion attempt 3189afff9f3Sstephan to wait until the first exec() is completed. 3199afff9f3Sstephan 3209afff9f3Sstephan The response is the input options object (or a synthesized one if 3219afff9f3Sstephan passed only a string), noting that options.resultRows and 3229afff9f3Sstephan options.columnNames may be populated by the call to db.exec(). 3233734401aSstephan 324453af2f6Sstephan*/ 325e3cd6760Sstephanself.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ 326e3cd6760Sstephansqlite3.initWorker1API = function(){ 327453af2f6Sstephan 'use strict'; 328453af2f6Sstephan const toss = (...args)=>{throw new Error(args.join(' '))}; 329fa5aac74Sstephan if('function' !== typeof importScripts){ 330ac51eb77Sstephan toss("initWorker1API() must be run from a Worker thread."); 331453af2f6Sstephan } 332453af2f6Sstephan const self = this.self; 333453af2f6Sstephan const sqlite3 = this.sqlite3 || toss("Missing this.sqlite3 object."); 334ac51eb77Sstephan const DB = sqlite3.oo1.DB; 335453af2f6Sstephan 336453af2f6Sstephan /** 337453af2f6Sstephan Returns the app-wide unique ID for the given db, creating one if 338453af2f6Sstephan needed. 339453af2f6Sstephan */ 340453af2f6Sstephan const getDbId = function(db){ 341453af2f6Sstephan let id = wState.idMap.get(db); 342453af2f6Sstephan if(id) return id; 343453af2f6Sstephan id = 'db#'+(++wState.idSeq)+'@'+db.pointer; 344453af2f6Sstephan /** ^^^ can't simply use db.pointer b/c closing/opening may re-use 345453af2f6Sstephan the same address, which could map pending messages to a wrong 346453af2f6Sstephan instance. */ 347453af2f6Sstephan wState.idMap.set(db, id); 348453af2f6Sstephan return id; 349453af2f6Sstephan }; 350453af2f6Sstephan 351453af2f6Sstephan /** 3523734401aSstephan Internal helper for managing Worker-level state. 353453af2f6Sstephan */ 354453af2f6Sstephan const wState = { 35502d15a7eSstephan /** 35602d15a7eSstephan Each opened DB is added to this.dbList, and the first entry in 35702d15a7eSstephan that list is the default db. As each db is closed, its entry is 35802d15a7eSstephan removed from the list. 35902d15a7eSstephan */ 36002d15a7eSstephan dbList: [], 3619afff9f3Sstephan /** Sequence number of dbId generation. */ 362453af2f6Sstephan idSeq: 0, 3639afff9f3Sstephan /** Map of DB instances to dbId. */ 364453af2f6Sstephan idMap: new WeakMap, 3659afff9f3Sstephan /** Temp holder for "transferable" postMessage() state. */ 3669afff9f3Sstephan xfer: [], 3679a34509aSstephan open: function(opt){ 36849048b14Sstephan const db = new DB(opt); 369453af2f6Sstephan this.dbs[getDbId(db)] = db; 37002d15a7eSstephan if(this.dbList.indexOf(db)<0) this.dbList.push(db); 371453af2f6Sstephan return db; 372453af2f6Sstephan }, 373453af2f6Sstephan close: function(db,alsoUnlink){ 374453af2f6Sstephan if(db){ 375453af2f6Sstephan delete this.dbs[getDbId(db)]; 37614ae1a53Sstephan const filename = db.filename; 3778948fbeeSstephan const pVfs = sqlite3.wasm.sqlite3_wasm_db_vfs(db.pointer, 0); 378453af2f6Sstephan db.close(); 37902d15a7eSstephan const ddNdx = this.dbList.indexOf(db); 38002d15a7eSstephan if(ddNdx>=0) this.dbList.splice(ddNdx, 1); 381842c5ee8Sstephan if(alsoUnlink && filename && pVfs){ 3828948fbeeSstephan sqlite3.wasm.sqlite3_wasm_vfs_unlink(pVfs, filename); 383453af2f6Sstephan } 384453af2f6Sstephan } 385453af2f6Sstephan }, 3863734401aSstephan /** 3873734401aSstephan Posts the given worker message value. If xferList is provided, 3883734401aSstephan it must be an array, in which case a copy of it passed as 3893734401aSstephan postMessage()'s second argument and xferList.length is set to 3903734401aSstephan 0. 3913734401aSstephan */ 392a9ac2ed0Sstephan post: function(msg,xferList){ 3933734401aSstephan if(xferList && xferList.length){ 3943734401aSstephan self.postMessage( msg, Array.from(xferList) ); 395453af2f6Sstephan xferList.length = 0; 396453af2f6Sstephan }else{ 397a9ac2ed0Sstephan self.postMessage(msg); 398453af2f6Sstephan } 399453af2f6Sstephan }, 400453af2f6Sstephan /** Map of DB IDs to DBs. */ 401453af2f6Sstephan dbs: Object.create(null), 4029afff9f3Sstephan /** Fetch the DB for the given id. Throw if require=true and the 4039afff9f3Sstephan id is not valid, else return the db or undefined. */ 404453af2f6Sstephan getDb: function(id,require=true){ 405453af2f6Sstephan return this.dbs[id] 406453af2f6Sstephan || (require ? toss("Unknown (or closed) DB ID:",id) : undefined); 407453af2f6Sstephan } 408453af2f6Sstephan }; 409453af2f6Sstephan 41002d15a7eSstephan /** Throws if the given db is falsy or not opened, else returns its 41102d15a7eSstephan argument. */ 41202d15a7eSstephan const affirmDbOpen = function(db = wState.dbList[0]){ 413453af2f6Sstephan return (db && db.pointer) ? db : toss("DB is not opened."); 414453af2f6Sstephan }; 415453af2f6Sstephan 416453af2f6Sstephan /** Extract dbId from the given message payload. */ 417453af2f6Sstephan const getMsgDb = function(msgData,affirmExists=true){ 41802d15a7eSstephan const db = wState.getDb(msgData.dbId,false) || wState.dbList[0]; 419453af2f6Sstephan return affirmExists ? affirmDbOpen(db) : db; 420453af2f6Sstephan }; 421453af2f6Sstephan 422453af2f6Sstephan const getDefaultDbId = function(){ 42302d15a7eSstephan return wState.dbList[0] && getDbId(wState.dbList[0]); 424453af2f6Sstephan }; 425453af2f6Sstephan 426f45c3370Sstephan const guessVfs = function(filename){ 427f45c3370Sstephan const m = /^file:.+(vfs=(\w+))/.exec(filename); 428f45c3370Sstephan return sqlite3.capi.sqlite3_vfs_find(m ? m[2] : 0); 429f45c3370Sstephan }; 430f45c3370Sstephan 431f45c3370Sstephan const isSpecialDbFilename = (n)=>{ 432*4df2ab57Sstephan return ""===n || ':'===n[0]; 433f45c3370Sstephan }; 434f45c3370Sstephan 435453af2f6Sstephan /** 43602d15a7eSstephan A level of "organizational abstraction" for the Worker1 43702d15a7eSstephan API. Each method in this object must map directly to a Worker1 438453af2f6Sstephan message type key. The onmessage() dispatcher attempts to 439453af2f6Sstephan dispatch all inbound messages to a method of this object, 440453af2f6Sstephan passing it the event.data part of the inbound event object. All 4413734401aSstephan methods must return a plain Object containing any result 442453af2f6Sstephan state, which the dispatcher may amend. All methods must throw 443453af2f6Sstephan on error. 444453af2f6Sstephan */ 445453af2f6Sstephan const wMsgHandler = { 446453af2f6Sstephan open: function(ev){ 4479a34509aSstephan const oargs = Object.create(null), args = (ev.args || Object.create(null)); 448a9ac2ed0Sstephan if(args.simulateError){ // undocumented internal testing option 449453af2f6Sstephan toss("Throwing because of simulateError flag."); 450453af2f6Sstephan } 4513734401aSstephan const rc = Object.create(null); 4525b9973d8Sstephan const pDir = sqlite3.capi.sqlite3_wasmfs_opfs_dir(); 453f45c3370Sstephan let byteArray, pVfs; 454f45c3370Sstephan oargs.vfs = args.vfs; 455f45c3370Sstephan if(isSpecialDbFilename(args.filename)){ 456*4df2ab57Sstephan oargs.filename = args.filename || ""; 4579a34509aSstephan }else{ 4583734401aSstephan oargs.filename = args.filename; 459f45c3370Sstephan byteArray = args.byteArray; 460f45c3370Sstephan if(byteArray) pVfs = guessVfs(args.filename); 461f45c3370Sstephan } 462f45c3370Sstephan if(pVfs){ 463f45c3370Sstephan /* 2022-11-02: this feature is as-yet untested except that 464f45c3370Sstephan sqlite3_wasm_vfs_create_file() has been tested from the 465f45c3370Sstephan browser dev console. */ 466f45c3370Sstephan let pMem; 467f45c3370Sstephan try{ 468f45c3370Sstephan pMem = sqlite3.wasm.allocFromTypedArray(byteArray); 469f45c3370Sstephan const rc = sqlite3.wasm.sqlite3_wasm_vfs_create_file( 470f45c3370Sstephan pVfs, oargs.filename, pMem, byteArray.byteLength 471f45c3370Sstephan ); 472f45c3370Sstephan if(rc) sqlite3.SQLite3Error.toss(rc); 473f45c3370Sstephan }catch(e){ 474f45c3370Sstephan throw new sqlite3.SQLite3Error( 475f45c3370Sstephan e.name+' creating '+args.filename+": "+e.message, { 476f45c3370Sstephan cause: e 477f45c3370Sstephan } 478f45c3370Sstephan ); 479f45c3370Sstephan }finally{ 480f45c3370Sstephan if(pMem) sqlite3.wasm.dealloc(pMem); 481f45c3370Sstephan } 4829a34509aSstephan } 483a9ac2ed0Sstephan const db = wState.open(oargs); 4843734401aSstephan rc.filename = db.filename; 4858a8244b5Sstephan rc.persistent = (!!pDir && db.filename.startsWith(pDir+'/')) 48602d15a7eSstephan || !!sqlite3.capi.sqlite3_js_db_uses_vfs(db.pointer, "opfs"); 4873734401aSstephan rc.dbId = getDbId(db); 48849048b14Sstephan rc.vfs = db.dbVfsName(); 4893734401aSstephan return rc; 490453af2f6Sstephan }, 491453af2f6Sstephan 492453af2f6Sstephan close: function(ev){ 493453af2f6Sstephan const db = getMsgDb(ev,false); 494453af2f6Sstephan const response = { 4959afff9f3Sstephan filename: db && db.filename 496453af2f6Sstephan }; 497453af2f6Sstephan if(db){ 498f45c3370Sstephan const doUnlink = ((ev.args && 'object'===typeof ev.args) 499f45c3370Sstephan ? !!ev.args.unlink : false); 500f45c3370Sstephan wState.close(db, doUnlink); 501453af2f6Sstephan } 502453af2f6Sstephan return response; 503453af2f6Sstephan }, 504453af2f6Sstephan 505453af2f6Sstephan exec: function(ev){ 5063734401aSstephan const rc = ( 507a9ac2ed0Sstephan 'string'===typeof ev.args 508a9ac2ed0Sstephan ) ? {sql: ev.args} : (ev.args || Object.create(null)); 509407f7537Sstephan if('stmt'===rc.rowMode){ 5103734401aSstephan toss("Invalid rowMode for 'exec': stmt mode", 511453af2f6Sstephan "does not work in the Worker API."); 5129afff9f3Sstephan }else if(!rc.sql){ 5139afff9f3Sstephan toss("'exec' requires input SQL."); 514453af2f6Sstephan } 515453af2f6Sstephan const db = getMsgDb(ev); 5163734401aSstephan if(rc.callback || Array.isArray(rc.resultRows)){ 517453af2f6Sstephan // Part of a copy-avoidance optimization for blobs 5183734401aSstephan db._blobXfer = wState.xfer; 519453af2f6Sstephan } 520407f7537Sstephan const theCallback = rc.callback; 5213734401aSstephan let rowNumber = 0; 522407f7537Sstephan const hadColNames = !!rc.columnNames; 523407f7537Sstephan if('string' === typeof theCallback){ 524407f7537Sstephan if(!hadColNames) rc.columnNames = []; 525453af2f6Sstephan /* Treat this as a worker message type and post each 526453af2f6Sstephan row as a message of that type. */ 527407f7537Sstephan rc.callback = function(row,stmt){ 528407f7537Sstephan wState.post({ 529407f7537Sstephan type: theCallback, 530407f7537Sstephan columnNames: rc.columnNames, 531407f7537Sstephan rowNumber: ++rowNumber, 532407f7537Sstephan row: row 533407f7537Sstephan }, wState.xfer); 534407f7537Sstephan } 535453af2f6Sstephan } 536453af2f6Sstephan try { 5373734401aSstephan db.exec(rc); 5383734401aSstephan if(rc.callback instanceof Function){ 539407f7537Sstephan rc.callback = theCallback; 540407f7537Sstephan /* Post a sentinel message to tell the client that the end 541407f7537Sstephan of the result set has been reached (possibly with zero 542407f7537Sstephan rows). */ 543407f7537Sstephan wState.post({ 544407f7537Sstephan type: theCallback, 545407f7537Sstephan columnNames: rc.columnNames, 546407f7537Sstephan rowNumber: null /*null to distinguish from "property not set"*/, 547407f7537Sstephan row: undefined /*undefined because null is a legal row value 548407f7537Sstephan for some rowType values, but undefined is not*/ 549407f7537Sstephan }); 550453af2f6Sstephan } 5513734401aSstephan }finally{ 552453af2f6Sstephan delete db._blobXfer; 553407f7537Sstephan if(rc.callback) rc.callback = theCallback; 554453af2f6Sstephan } 5553734401aSstephan return rc; 556453af2f6Sstephan }/*exec()*/, 5573734401aSstephan 5583734401aSstephan 'config-get': function(){ 5593734401aSstephan const rc = Object.create(null), src = sqlite3.config; 5603734401aSstephan [ 5615b9973d8Sstephan 'wasmfsOpfsDir', 'bigIntEnabled' 5623734401aSstephan ].forEach(function(k){ 5633734401aSstephan if(Object.getOwnPropertyDescriptor(src, k)) rc[k] = src[k]; 5643734401aSstephan }); 5655b9973d8Sstephan rc.wasmfsOpfsEnabled = !!sqlite3.capi.sqlite3_wasmfs_opfs_dir(); 566d18f1bbfSstephan rc.version = sqlite3.version; 5678a8244b5Sstephan rc.vfsList = sqlite3.capi.sqlite3_js_vfs_list(); 56849048b14Sstephan rc.opfsEnabled = !!sqlite3.opfs; 5693734401aSstephan return rc; 5703734401aSstephan }, 5719afff9f3Sstephan 5723734401aSstephan /** 57302d15a7eSstephan Exports the database to a byte array, as per 57402d15a7eSstephan sqlite3_serialize(). Response is an object: 575453af2f6Sstephan 576453af2f6Sstephan { 577f45c3370Sstephan byteArray: Uint8Array (db file contents), 578453af2f6Sstephan filename: the current db filename, 579453af2f6Sstephan mimetype: 'application/x-sqlite3' 580453af2f6Sstephan } 581453af2f6Sstephan */ 582453af2f6Sstephan export: function(ev){ 58302d15a7eSstephan const db = getMsgDb(ev); 584453af2f6Sstephan const response = { 585f45c3370Sstephan byteArray: sqlite3.capi.sqlite3_js_db_export(db.pointer), 586453af2f6Sstephan filename: db.filename, 587453af2f6Sstephan mimetype: 'application/x-sqlite3' 588453af2f6Sstephan }; 589f45c3370Sstephan wState.xfer.push(response.byteArray.buffer); 59002d15a7eSstephan return response; 591453af2f6Sstephan }/*export()*/, 5929afff9f3Sstephan 593453af2f6Sstephan toss: function(ev){ 594453af2f6Sstephan toss("Testing worker exception"); 59549048b14Sstephan }, 59649048b14Sstephan 59749048b14Sstephan 'opfs-tree': async function(ev){ 59849048b14Sstephan if(!sqlite3.opfs) toss("OPFS support is unavailable."); 59949048b14Sstephan const response = await sqlite3.opfs.treeList(); 60049048b14Sstephan return response; 601453af2f6Sstephan } 602453af2f6Sstephan }/*wMsgHandler*/; 603453af2f6Sstephan 60449048b14Sstephan self.onmessage = async function(ev){ 605453af2f6Sstephan ev = ev.data; 606a9ac2ed0Sstephan let result, dbId = ev.dbId, evType = ev.type; 607453af2f6Sstephan const arrivalTime = performance.now(); 608453af2f6Sstephan try { 609453af2f6Sstephan if(wMsgHandler.hasOwnProperty(evType) && 610453af2f6Sstephan wMsgHandler[evType] instanceof Function){ 61149048b14Sstephan result = await wMsgHandler[evType](ev); 612453af2f6Sstephan }else{ 613453af2f6Sstephan toss("Unknown db worker message type:",ev.type); 614453af2f6Sstephan } 615453af2f6Sstephan }catch(err){ 616453af2f6Sstephan evType = 'error'; 617a9ac2ed0Sstephan result = { 6189a34509aSstephan operation: ev.type, 619453af2f6Sstephan message: err.message, 620453af2f6Sstephan errorClass: err.name, 621453af2f6Sstephan input: ev 622453af2f6Sstephan }; 623453af2f6Sstephan if(err.stack){ 624a9ac2ed0Sstephan result.stack = ('string'===typeof err.stack) 625335ad526Sstephan ? err.stack.split(/\n\s*/) : err.stack; 626453af2f6Sstephan } 627453af2f6Sstephan if(0) console.warn("Worker is propagating an exception to main thread.", 628a9ac2ed0Sstephan "Reporting it _here_ for the stack trace:",err,result); 629453af2f6Sstephan } 630453af2f6Sstephan if(!dbId){ 631a9ac2ed0Sstephan dbId = result.dbId/*from 'open' cmd*/ 632453af2f6Sstephan || getDefaultDbId(); 633453af2f6Sstephan } 634453af2f6Sstephan // Timing info is primarily for use in testing this API. It's not part of 635453af2f6Sstephan // the public API. arrivalTime = when the worker got the message. 636a9ac2ed0Sstephan wState.post({ 637a9ac2ed0Sstephan type: evType, 638a9ac2ed0Sstephan dbId: dbId, 639a9ac2ed0Sstephan messageId: ev.messageId, 640a9ac2ed0Sstephan workerReceivedTime: arrivalTime, 641a9ac2ed0Sstephan workerRespondTime: performance.now(), 642a9ac2ed0Sstephan departureTime: ev.departureTime, 6433734401aSstephan // TODO: move the timing bits into... 6443734401aSstephan //timing:{ 6453734401aSstephan // departure: ev.departureTime, 6463734401aSstephan // workerReceived: arrivalTime, 6473734401aSstephan // workerResponse: performance.now(); 6483734401aSstephan //}, 649a9ac2ed0Sstephan result: result 6503734401aSstephan }, wState.xfer); 651453af2f6Sstephan }; 6529a34509aSstephan self.postMessage({type:'sqlite3-api',result:'worker1-ready'}); 653e3cd6760Sstephan}.bind({self, sqlite3}); 654e3cd6760Sstephan}); 655