blob: 42c0e11662c05d82b866fb4c8aea247d559f1712 [file] [log] [blame]
stephan3961b262022-08-10 11:26:08 +00001/*
stephanc5313af2022-09-18 02:35:30 +00002 2022-09-18
stephan3961b262022-08-10 11:26:08 +00003
4 The author disclaims copyright to this source code. In place of a
5 legal notice, here is a blessing:
6
7 * May you do good and not evil.
8 * May you find forgiveness for yourself and forgive others.
9 * May you share freely, never taking more than you give.
10
11 ***********************************************************************
12
stephanc5313af2022-09-18 02:35:30 +000013 This file holds the synchronous half of an sqlite3_vfs
14 implementation which proxies, in a synchronous fashion, the
15 asynchronous Origin-Private FileSystem (OPFS) APIs using a second
16 Worker, implemented in sqlite3-opfs-async-proxy.js. This file is
17 intended to be appended to the main sqlite3 JS deliverable somewhere
18 after sqlite3-api-glue.js and before sqlite3-api-cleanup.js.
stephanc5313af2022-09-18 02:35:30 +000019*/
stephanc5313af2022-09-18 02:35:30 +000020'use strict';
21self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
22/**
stephan5b9973d2022-09-27 13:40:12 +000023 installOpfsVfs() returns a Promise which, on success, installs
stephanc5313af2022-09-18 02:35:30 +000024 an sqlite3_vfs named "opfs", suitable for use with all sqlite3 APIs
25 which accept a VFS. It uses the Origin-Private FileSystem API for
26 all file storage. On error it is rejected with an exception
27 explaining the problem. Reasons for rejection include, but are
28 not limited to:
29
30 - The counterpart Worker (see below) could not be loaded.
31
32 - The environment does not support OPFS. That includes when
33 this function is called from the main window thread.
34
stephan3961b262022-08-10 11:26:08 +000035 Significant notes and limitations:
36
37 - As of this writing, OPFS is still very much in flux and only
38 available in bleeding-edge versions of Chrome (v102+, noting that
39 that number will increase as the OPFS API matures).
40
stephanc5313af2022-09-18 02:35:30 +000041 - The OPFS features used here are only available in dedicated Worker
stephanf3860122022-09-18 17:32:35 +000042 threads. This file tries to detect that case, resulting in a
43 rejected Promise if those features do not seem to be available.
stephanc5313af2022-09-18 02:35:30 +000044
45 - It requires the SharedArrayBuffer and Atomics classes, and the
46 former is only available if the HTTP server emits the so-called
47 COOP and COEP response headers. These features are required for
48 proxying OPFS's synchronous API via the synchronous interface
49 required by the sqlite3_vfs API.
50
51 - This function may only be called a single time and it must be
52 called from the client, as opposed to the library initialization,
53 in case the client requires a custom path for this API's
54 "counterpart": this function's argument is the relative URI to
55 this module's "asynchronous half". When called, this function removes
56 itself from the sqlite3 object.
57
58 The argument may optionally be a plain object with the following
59 configuration options:
60
61 - proxyUri: as described above
62
63 - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
64 logging of errors. 2 enables logging of warnings and errors. 3
65 additionally enables debugging info.
66
67 - sanityChecks (=false): if true, some basic sanity tests are
68 run on the OPFS VFS API after it's initialized, before the
69 returned Promise resolves.
70
71 On success, the Promise resolves to the top-most sqlite3 namespace
stephanf3860122022-09-18 17:32:35 +000072 object and that object gets a new object installed in its
73 `opfs` property, containing several OPFS-specific utilities.
stephan3961b262022-08-10 11:26:08 +000074*/
stephan5b9973d2022-09-27 13:40:12 +000075const installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
stephan4cffb642022-09-20 16:20:35 +000076 if(!self.SharedArrayBuffer ||
stephanf6c686c2022-09-30 11:01:44 +000077 !self.Atomics ||
stephan509f4052022-09-19 09:58:01 +000078 !self.FileSystemHandle ||
79 !self.FileSystemDirectoryHandle ||
80 !self.FileSystemFileHandle ||
81 !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
82 !navigator.storage.getDirectory){
83 return Promise.reject(
84 new Error("This environment does not have OPFS support.")
85 );
86 }
stephanc5313af2022-09-18 02:35:30 +000087 const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
88 proxyUri: asyncProxyUri
stephan3961b262022-08-10 11:26:08 +000089 };
stephan509f4052022-09-19 09:58:01 +000090 const urlParams = new URL(self.location.href).searchParams;
stephanc5313af2022-09-18 02:35:30 +000091 if(undefined===options.verbose){
stephan509f4052022-09-19 09:58:01 +000092 options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
stephan3961b262022-08-10 11:26:08 +000093 }
stephanc5313af2022-09-18 02:35:30 +000094 if(undefined===options.sanityChecks){
stephan509f4052022-09-19 09:58:01 +000095 options.sanityChecks = urlParams.has('opfs-sanity-check');
stephanc5313af2022-09-18 02:35:30 +000096 }
97 if(undefined===options.proxyUri){
98 options.proxyUri = callee.defaultProxyUri;
99 }
stephanf3860122022-09-18 17:32:35 +0000100
stephane8afca32022-09-21 14:02:47 +0000101 const thePromise = new Promise(function(promiseResolve, promiseReject_){
stephan509f4052022-09-19 09:58:01 +0000102 const loggers = {
103 0:console.error.bind(console),
104 1:console.warn.bind(console),
105 2:console.log.bind(console)
stephanc5313af2022-09-18 02:35:30 +0000106 };
stephan509f4052022-09-19 09:58:01 +0000107 const logImpl = (level,...args)=>{
108 if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
109 };
110 const log = (...args)=>logImpl(2, ...args);
111 const warn = (...args)=>logImpl(1, ...args);
112 const error = (...args)=>logImpl(0, ...args);
stephanf6c686c2022-09-30 11:01:44 +0000113 //warn("The OPFS VFS feature is very much experimental and under construction.");
stephanc5313af2022-09-18 02:35:30 +0000114 const toss = function(...args){throw new Error(args.join(' '))};
stephanc5313af2022-09-18 02:35:30 +0000115 const capi = sqlite3.capi;
116 const wasm = capi.wasm;
117 const sqlite3_vfs = capi.sqlite3_vfs;
118 const sqlite3_file = capi.sqlite3_file;
119 const sqlite3_io_methods = capi.sqlite3_io_methods;
stephan509f4052022-09-19 09:58:01 +0000120 /**
121 Generic utilities for working with OPFS. This will get filled out
122 by the Promise setup and, on success, installed as sqlite3.opfs.
123 */
124 const opfsUtil = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000125 /**
126 Not part of the public API. Solely for internal/development
127 use.
128 */
129 opfsUtil.metrics = {
130 dump: function(){
stephanaec046a2022-09-19 18:22:29 +0000131 let k, n = 0, t = 0, w = 0;
132 for(k in state.opIds){
stephanf8150112022-09-19 17:09:09 +0000133 const m = metrics[k];
134 n += m.count;
135 t += m.time;
stephanaec046a2022-09-19 18:22:29 +0000136 w += m.wait;
stephanf8150112022-09-19 17:09:09 +0000137 m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
138 m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
139 }
stephanaec046a2022-09-19 18:22:29 +0000140 console.log(self.location.href,
141 "metrics for",self.location.href,":",metrics,
142 "\nTotal of",n,"op(s) for",t,
143 "ms (incl. "+w+" ms of waiting on the async side)");
stephan56fae742022-09-24 10:12:19 +0000144 console.log("Serialization metrics:",metrics.s11n);
145 opRun('async-metrics');
stephanf8150112022-09-19 17:09:09 +0000146 },
147 reset: function(){
148 let k;
149 const r = (m)=>(m.count = m.time = m.wait = 0);
150 for(k in state.opIds){
151 r(metrics[k] = Object.create(null));
152 }
stephanb8c8d4e2022-09-20 13:25:39 +0000153 let s = metrics.s11n = Object.create(null);
154 s = s.serialize = Object.create(null);
155 s.count = s.time = 0;
156 s = metrics.s11n.deserialize = Object.create(null);
157 s.count = s.time = 0;
stephanaec046a2022-09-19 18:22:29 +0000158 //[ // timed routines which are not in state.opIds
159 // 'xFileControl'
160 //].forEach((k)=>r(metrics[k] = Object.create(null)));
stephanf8150112022-09-19 17:09:09 +0000161 }
162 }/*metrics*/;
stephan56fae742022-09-24 10:12:19 +0000163 const promiseReject = function(err){
164 opfsVfs.dispose();
165 return promiseReject_(err);
166 };
167 const W = new Worker(options.proxyUri);
168 W._originalOnError = W.onerror /* will be restored later */;
169 W.onerror = function(err){
170 // The error object doesn't contain any useful info when the
171 // failure is, e.g., that the remote script is 404.
172 promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
173 };
stephanc9e26022022-09-20 10:11:52 +0000174 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
175 const dVfs = pDVfs
176 ? new sqlite3_vfs(pDVfs)
177 : null /* dVfs will be null when sqlite3 is built with
178 SQLITE_OS_OTHER. Though we cannot currently handle
179 that case, the hope is to eventually be able to. */;
180 const opfsVfs = new sqlite3_vfs();
181 const opfsIoMethods = new sqlite3_io_methods();
182 opfsVfs.$iVersion = 2/*yes, two*/;
183 opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
184 opfsVfs.$mxPathname = 1024/*sure, why not?*/;
185 opfsVfs.$zName = wasm.allocCString("opfs");
186 // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
187 opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
188 opfsVfs.ondispose = [
189 '$zName', opfsVfs.$zName,
190 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
191 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
192 ];
193 /**
194 Pedantic sidebar about opfsVfs.ondispose: the entries in that array
195 are items to clean up when opfsVfs.dispose() is called, but in this
196 environment it will never be called. The VFS instance simply
197 hangs around until the WASM module instance is cleaned up. We
198 "could" _hypothetically_ clean it up by "importing" an
199 sqlite3_os_end() impl into the wasm build, but the shutdown order
200 of the wasm engine and the JS one are undefined so there is no
201 guaranty that the opfsVfs instance would be available in one
202 environment or the other when sqlite3_os_end() is called (_if_ it
203 gets called at all in a wasm build, which is undefined).
204 */
205
stephanc5313af2022-09-18 02:35:30 +0000206 /**
207 State which we send to the async-api Worker or share with it.
208 This object must initially contain only cloneable or sharable
209 objects. After the worker's "inited" message arrives, other types
210 of data may be added to it.
stephanf3860122022-09-18 17:32:35 +0000211
212 For purposes of Atomics.wait() and Atomics.notify(), we use a
213 SharedArrayBuffer with one slot reserved for each of the API
214 proxy's methods. The sync side of the API uses Atomics.wait()
215 on the corresponding slot and the async side uses
216 Atomics.notify() on that slot.
217
218 The approach of using a single SAB to serialize comms for all
219 instances might(?) lead to deadlock situations in multi-db
220 cases. We should probably have one SAB here with a single slot
221 for locking a per-file initialization step and then allocate a
222 separate SAB like the above one for each file. That will
223 require a bit of acrobatics but should be feasible.
stephanc5313af2022-09-18 02:35:30 +0000224 */
225 const state = Object.create(null);
226 state.verbose = options.verbose;
stephane8afca32022-09-21 14:02:47 +0000227 state.littleEndian = true;
stephan56fae742022-09-24 10:12:19 +0000228 /** Whether the async counterpart should log exceptions to
stephane8afca32022-09-21 14:02:47 +0000229 the serialization channel. That produces a great deal of
230 noise for seemingly innocuous things like xAccess() checks
stephan56fae742022-09-24 10:12:19 +0000231 for missing files, so this option may have one of 3 values:
232
233 0 = no exception logging
234
235 1 = only log exceptions for "significant" ops like xOpen(),
236 xRead(), and xWrite().
237
238 2 = log all exceptions.
239 */
240 state.asyncS11nExceptions = 1;
stephanc9e26022022-09-20 10:11:52 +0000241 /* Size of file I/O buffer block. 64k = max sqlite3 page size. */
stephanf3860122022-09-18 17:32:35 +0000242 state.fileBufferSize =
stephanc9e26022022-09-20 10:11:52 +0000243 1024 * 64;
stephan138647a2022-09-20 03:31:02 +0000244 state.sabS11nOffset = state.fileBufferSize;
stephanc9e26022022-09-20 10:11:52 +0000245 /**
246 The size of the block in our SAB for serializing arguments and
stephane8afca32022-09-21 14:02:47 +0000247 result values. Needs to be large enough to hold serialized
stephanc9e26022022-09-20 10:11:52 +0000248 values of any of the proxied APIs. Filenames are the largest
249 part but are limited to opfsVfs.$mxPathname bytes.
250 */
251 state.sabS11nSize = opfsVfs.$mxPathname * 2;
252 /**
253 The SAB used for all data I/O (files and arg/result s11n).
254 */
stephanc4b87be2022-09-20 01:28:47 +0000255 state.sabIO = new SharedArrayBuffer(
stephanc9e26022022-09-20 10:11:52 +0000256 state.fileBufferSize/* file i/o block */
257 + state.sabS11nSize/* argument/result serialization block */
stephanc4b87be2022-09-20 01:28:47 +0000258 );
stephanc5313af2022-09-18 02:35:30 +0000259 state.opIds = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000260 const metrics = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000261 {
stephanc9e26022022-09-20 10:11:52 +0000262 /* Indexes for use in our SharedArrayBuffer... */
stephan3961b262022-08-10 11:26:08 +0000263 let i = 0;
stephanc9e26022022-09-20 10:11:52 +0000264 /* SAB slot used to communicate which operation is desired
265 between both workers. This worker writes to it and the other
266 listens for changes. */
stephan138647a2022-09-20 03:31:02 +0000267 state.opIds.whichOp = i++;
stephanc9e26022022-09-20 10:11:52 +0000268 /* Slot for storing return values. This work listens to that
269 slot and the other worker writes to it. */
270 state.opIds.rc = i++;
271 /* Each function gets an ID which this worker writes to
272 the whichOp slot. The async-api worker uses Atomic.wait()
273 on the whichOp slot to figure out which operation to run
274 next. */
stephanc5313af2022-09-18 02:35:30 +0000275 state.opIds.xAccess = i++;
276 state.opIds.xClose = i++;
277 state.opIds.xDelete = i++;
stephanf3860122022-09-18 17:32:35 +0000278 state.opIds.xDeleteNoWait = i++;
stephan56fae742022-09-24 10:12:19 +0000279 state.opIds.xFileControl = i++;
stephanc5313af2022-09-18 02:35:30 +0000280 state.opIds.xFileSize = i++;
281 state.opIds.xOpen = i++;
282 state.opIds.xRead = i++;
283 state.opIds.xSleep = i++;
284 state.opIds.xSync = i++;
285 state.opIds.xTruncate = i++;
286 state.opIds.xWrite = i++;
stephanf3860122022-09-18 17:32:35 +0000287 state.opIds.mkdir = i++;
stephan56fae742022-09-24 10:12:19 +0000288 state.opIds['async-metrics'] = i++;
stephanc4b87be2022-09-20 01:28:47 +0000289 state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
stephanf8150112022-09-19 17:09:09 +0000290 opfsUtil.metrics.reset();
stephanc5313af2022-09-18 02:35:30 +0000291 }
292
stephanc9e26022022-09-20 10:11:52 +0000293 /**
294 SQLITE_xxx constants to export to the async worker
295 counterpart...
296 */
stephanc5313af2022-09-18 02:35:30 +0000297 state.sq3Codes = Object.create(null);
stephanc9e26022022-09-20 10:11:52 +0000298 [
stephanc5313af2022-09-18 02:35:30 +0000299 'SQLITE_ERROR', 'SQLITE_IOERR',
300 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
301 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
302 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
303 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
stephanf3860122022-09-18 17:32:35 +0000304 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
stephanc4b87be2022-09-20 01:28:47 +0000305 'SQLITE_IOERR_DELETE',
306 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE',
307 'SQLITE_OPEN_READONLY'
stephanc5313af2022-09-18 02:35:30 +0000308 ].forEach(function(k){
309 state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
stephan3961b262022-08-10 11:26:08 +0000310 });
stephan3961b262022-08-10 11:26:08 +0000311
stephanc5313af2022-09-18 02:35:30 +0000312 /**
stephanc9e26022022-09-20 10:11:52 +0000313 Runs the given operation (by name) in the async worker
314 counterpart, waits for its response, and returns the result
315 which the async worker writes to SAB[state.opIds.rc]. The
316 2nd and subsequent arguments must be the aruguments for the
317 async op.
stephanc5313af2022-09-18 02:35:30 +0000318 */
stephan138647a2022-09-20 03:31:02 +0000319 const opRun = (op,...args)=>{
stephan5e8bb0a2022-09-20 08:27:57 +0000320 const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
321 state.s11n.serialize(...args);
stephanc9e26022022-09-20 10:11:52 +0000322 Atomics.store(state.sabOPView, state.opIds.rc, -1);
stephan5e8bb0a2022-09-20 08:27:57 +0000323 Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
324 Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
stephanf8150112022-09-19 17:09:09 +0000325 const t = performance.now();
stephanc9e26022022-09-20 10:11:52 +0000326 Atomics.wait(state.sabOPView, state.opIds.rc, -1);
327 const rc = Atomics.load(state.sabOPView, state.opIds.rc);
stephane8afca32022-09-21 14:02:47 +0000328 metrics[op].wait += performance.now() - t;
329 if(rc && state.asyncS11nExceptions){
stephan72ab4002022-09-21 12:27:35 +0000330 const err = state.s11n.deserialize();
331 if(err) error(op+"() async error:",...err);
332 }
stephan5e8bb0a2022-09-20 08:27:57 +0000333 return rc;
stephanc5313af2022-09-18 02:35:30 +0000334 };
335
stephan138647a2022-09-20 03:31:02 +0000336 const initS11n = ()=>{
stephanb8c8d4e2022-09-20 13:25:39 +0000337 /**
stephan72ab4002022-09-21 12:27:35 +0000338 ACHTUNG: this code is 100% duplicated in the other half of this
339 proxy! The documentation is maintained in the "synchronous half".
stephanb8c8d4e2022-09-20 13:25:39 +0000340
stephan72ab4002022-09-21 12:27:35 +0000341 This proxy de/serializes cross-thread function arguments and
342 output-pointer values via the state.sabIO SharedArrayBuffer,
343 using the region defined by (state.sabS11nOffset,
344 state.sabS11nOffset]. Only one dataset is recorded at a time.
345
346 This is not a general-purpose format. It only supports the range
347 of operations, and data sizes, needed by the sqlite3_vfs and
348 sqlite3_io_methods operations.
349
350 The data format can be succinctly summarized as:
351
352 Nt...Td...D
353
354 Where:
355
356 - N = number of entries (1 byte)
357
358 - t = type ID of first argument (1 byte)
359
360 - ...T = type IDs of the 2nd and subsequent arguments (1 byte
361 each).
362
363 - d = raw bytes of first argument (per-type size).
364
365 - ...D = raw bytes of the 2nd and subsequent arguments (per-type
366 size).
367
368 All types except strings have fixed sizes. Strings are stored
369 using their TextEncoder/TextDecoder representations. It would
370 arguably make more sense to store them as Int16Arrays of
371 their JS character values, but how best/fastest to get that
372 in and out of string form us an open point.
373
374 Historical note: this impl was initially about 1% this size by
375 using using JSON.stringify/parse(), but using fit-to-purpose
376 serialization saves considerable runtime.
stephanb8c8d4e2022-09-20 13:25:39 +0000377 */
stephan138647a2022-09-20 03:31:02 +0000378 if(state.s11n) return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000379 const textDecoder = new TextDecoder(),
stephan72ab4002022-09-21 12:27:35 +0000380 textEncoder = new TextEncoder('utf-8'),
381 viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
382 viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
stephan138647a2022-09-20 03:31:02 +0000383 state.s11n = Object.create(null);
stephan72ab4002022-09-21 12:27:35 +0000384 /* Only arguments and return values of these types may be
385 serialized. This covers the whole range of types needed by the
386 sqlite3_vfs API. */
stephanb8c8d4e2022-09-20 13:25:39 +0000387 const TypeIds = Object.create(null);
388 TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
389 TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
390 TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
391 TypeIds.string = { id: 4 };
stephan72ab4002022-09-21 12:27:35 +0000392
393 const getTypeId = (v)=>(
394 TypeIds[typeof v]
395 || toss("Maintenance required: this value type cannot be serialized.",v)
396 );
stephanb8c8d4e2022-09-20 13:25:39 +0000397 const getTypeIdById = (tid)=>{
398 switch(tid){
stephan72ab4002022-09-21 12:27:35 +0000399 case TypeIds.number.id: return TypeIds.number;
400 case TypeIds.bigint.id: return TypeIds.bigint;
401 case TypeIds.boolean.id: return TypeIds.boolean;
402 case TypeIds.string.id: return TypeIds.string;
403 default: toss("Invalid type ID:",tid);
stephanb8c8d4e2022-09-20 13:25:39 +0000404 }
405 };
stephan72ab4002022-09-21 12:27:35 +0000406
stephan138647a2022-09-20 03:31:02 +0000407 /**
stephan72ab4002022-09-21 12:27:35 +0000408 Returns an array of the deserialized state stored by the most
409 recent serialize() operation (from from this thread or the
410 counterpart thread), or null if the serialization buffer is empty.
stephan138647a2022-09-20 03:31:02 +0000411 */
412 state.s11n.deserialize = function(){
stephanb8c8d4e2022-09-20 13:25:39 +0000413 ++metrics.s11n.deserialize.count;
414 const t = performance.now();
stephanb8c8d4e2022-09-20 13:25:39 +0000415 const argc = viewU8[0];
stephan72ab4002022-09-21 12:27:35 +0000416 const rc = argc ? [] : null;
stephanb8c8d4e2022-09-20 13:25:39 +0000417 if(argc){
stephan72ab4002022-09-21 12:27:35 +0000418 const typeIds = [];
419 let offset = 1, i, n, v;
stephanb8c8d4e2022-09-20 13:25:39 +0000420 for(i = 0; i < argc; ++i, ++offset){
421 typeIds.push(getTypeIdById(viewU8[offset]));
422 }
423 for(i = 0; i < argc; ++i){
424 const t = typeIds[i];
425 if(t.getter){
426 v = viewDV[t.getter](offset, state.littleEndian);
427 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000428 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000429 n = viewDV.getInt32(offset, state.littleEndian);
430 offset += 4;
431 v = textDecoder.decode(viewU8.slice(offset, offset+n));
432 offset += n;
433 }
434 rc.push(v);
435 }
436 }
437 //log("deserialize:",argc, rc);
438 metrics.s11n.deserialize.time += performance.now() - t;
439 return rc;
440 };
stephan72ab4002022-09-21 12:27:35 +0000441
stephan138647a2022-09-20 03:31:02 +0000442 /**
443 Serializes all arguments to the shared buffer for consumption
stephanb8c8d4e2022-09-20 13:25:39 +0000444 by the counterpart thread.
stephan5e8bb0a2022-09-20 08:27:57 +0000445
stephanb8c8d4e2022-09-20 13:25:39 +0000446 This routine is only intended for serializing OPFS VFS
447 arguments and (in at least one special case) result values,
448 and the buffer is sized to be able to comfortably handle
449 those.
stephan5e8bb0a2022-09-20 08:27:57 +0000450
451 If passed no arguments then it zeroes out the serialization
452 state.
stephan138647a2022-09-20 03:31:02 +0000453 */
454 state.s11n.serialize = function(...args){
stephanb8c8d4e2022-09-20 13:25:39 +0000455 const t = performance.now();
stephan72ab4002022-09-21 12:27:35 +0000456 ++metrics.s11n.serialize.count;
stephan5e8bb0a2022-09-20 08:27:57 +0000457 if(args.length){
stephanb8c8d4e2022-09-20 13:25:39 +0000458 //log("serialize():",args);
stephan72ab4002022-09-21 12:27:35 +0000459 const typeIds = [];
460 let i = 0, offset = 1;
461 viewU8[0] = args.length & 0xff /* header = # of args */;
stephanb8c8d4e2022-09-20 13:25:39 +0000462 for(; i < args.length; ++i, ++offset){
stephan72ab4002022-09-21 12:27:35 +0000463 /* Write the TypeIds.id value into the next args.length
464 bytes. */
stephanb8c8d4e2022-09-20 13:25:39 +0000465 typeIds.push(getTypeId(args[i]));
466 viewU8[offset] = typeIds[i].id;
467 }
468 for(i = 0; i < args.length; ++i) {
stephan72ab4002022-09-21 12:27:35 +0000469 /* Deserialize the following bytes based on their
470 corresponding TypeIds.id from the header. */
stephanb8c8d4e2022-09-20 13:25:39 +0000471 const t = typeIds[i];
472 if(t.setter){
473 viewDV[t.setter](offset, args[i], state.littleEndian);
474 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000475 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000476 const s = textEncoder.encode(args[i]);
477 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
478 offset += 4;
479 viewU8.set(s, offset);
480 offset += s.byteLength;
481 }
482 }
483 //log("serialize() result:",viewU8.slice(0,offset));
stephan5e8bb0a2022-09-20 08:27:57 +0000484 }else{
stephanb8c8d4e2022-09-20 13:25:39 +0000485 viewU8[0] = 0;
stephan5e8bb0a2022-09-20 08:27:57 +0000486 }
stephanb8c8d4e2022-09-20 13:25:39 +0000487 metrics.s11n.serialize.time += performance.now() - t;
stephan138647a2022-09-20 03:31:02 +0000488 };
489 return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000490 }/*initS11n()*/;
stephan138647a2022-09-20 03:31:02 +0000491
stephanc5313af2022-09-18 02:35:30 +0000492 /**
493 Generates a random ASCII string len characters long, intended for
494 use as a temporary file name.
495 */
496 const randomFilename = function f(len=16){
497 if(!f._chars){
498 f._chars = "abcdefghijklmnopqrstuvwxyz"+
499 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
500 "012346789";
501 f._n = f._chars.length;
502 }
503 const a = [];
504 let i = 0;
505 for( ; i < len; ++i){
506 const ndx = Math.random() * (f._n * 64) % f._n | 0;
507 a[i] = f._chars[ndx];
508 }
509 return a.join('');
510 };
511
512 /**
513 Map of sqlite3_file pointers to objects constructed by xOpen().
514 */
515 const __openFiles = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000516
517 /**
518 Installs a StructBinder-bound function pointer member of the
519 given name and function in the given StructType target object.
520 It creates a WASM proxy for the given function and arranges for
521 that proxy to be cleaned up when tgt.dispose() is called. Throws
522 on the slightest hint of error (e.g. tgt is-not-a StructType,
523 name does not map to a struct-bound member, etc.).
524
525 Returns a proxy for this function which is bound to tgt and takes
526 2 args (name,func). That function returns the same thing,
527 permitting calls to be chained.
528
529 If called with only 1 arg, it has no side effects but returns a
530 func with the same signature as described above.
531 */
532 const installMethod = function callee(tgt, name, func){
stephanf3860122022-09-18 17:32:35 +0000533 if(!(tgt instanceof sqlite3.StructBinder.StructType)){
stephanc5313af2022-09-18 02:35:30 +0000534 toss("Usage error: target object is-not-a StructType.");
535 }
536 if(1===arguments.length){
537 return (n,f)=>callee(tgt,n,f);
538 }
539 if(!callee.argcProxy){
540 callee.argcProxy = function(func,sig){
541 return function(...args){
542 if(func.length!==arguments.length){
543 toss("Argument mismatch. Native signature is:",sig);
544 }
545 return func.apply(this, args);
546 }
547 };
548 callee.removeFuncList = function(){
549 if(this.ondispose.__removeFuncList){
550 this.ondispose.__removeFuncList.forEach(
551 (v,ndx)=>{
552 if('number'===typeof v){
553 try{wasm.uninstallFunction(v)}
554 catch(e){/*ignore*/}
555 }
556 /* else it's a descriptive label for the next number in
557 the list. */
558 }
559 );
560 delete this.ondispose.__removeFuncList;
561 }
562 };
563 }/*static init*/
564 const sigN = tgt.memberSignature(name);
565 if(sigN.length<2){
566 toss("Member",name," is not a function pointer. Signature =",sigN);
567 }
568 const memKey = tgt.memberKey(name);
569 //log("installMethod",tgt, name, sigN);
stephanc2ccd672022-09-20 10:47:36 +0000570 const fProxy = 0
stephanc5313af2022-09-18 02:35:30 +0000571 // We can remove this proxy middle-man once the VFS is working
572 ? callee.argcProxy(func, sigN)
573 : func;
574 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
575 tgt[memKey] = pFunc;
576 if(!tgt.ondispose) tgt.ondispose = [];
577 if(!tgt.ondispose.__removeFuncList){
578 tgt.ondispose.push('ondispose.__removeFuncList handler',
579 callee.removeFuncList);
580 tgt.ondispose.__removeFuncList = [];
581 }
582 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
583 return (n,f)=>callee(tgt, n, f);
584 }/*installMethod*/;
stephanf8150112022-09-19 17:09:09 +0000585
586 const opTimer = Object.create(null);
587 opTimer.op = undefined;
588 opTimer.start = undefined;
589 const mTimeStart = (op)=>{
590 opTimer.start = performance.now();
591 opTimer.op = op;
592 //metrics[op] || toss("Maintenance required: missing metrics for",op);
593 ++metrics[op].count;
594 };
595 const mTimeEnd = ()=>(
596 metrics[opTimer.op].time += performance.now() - opTimer.start
597 );
598
stephanc5313af2022-09-18 02:35:30 +0000599 /**
600 Impls for the sqlite3_io_methods methods. Maintenance reminder:
601 members are in alphabetical order to simplify finding them.
602 */
603 const ioSyncWrappers = {
604 xCheckReservedLock: function(pFile,pOut){
605 // Exclusive lock is automatically acquired when opened
606 //warn("xCheckReservedLock(",arguments,") is a no-op");
607 wasm.setMemValue(pOut,1,'i32');
608 return 0;
609 },
610 xClose: function(pFile){
stephanf8150112022-09-19 17:09:09 +0000611 mTimeStart('xClose');
stephanc5313af2022-09-18 02:35:30 +0000612 let rc = 0;
613 const f = __openFiles[pFile];
614 if(f){
615 delete __openFiles[pFile];
616 rc = opRun('xClose', pFile);
617 if(f.sq3File) f.sq3File.dispose();
618 }
stephanf8150112022-09-19 17:09:09 +0000619 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000620 return rc;
621 },
622 xDeviceCharacteristics: function(pFile){
623 //debug("xDeviceCharacteristics(",pFile,")");
624 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
625 },
stephanf8150112022-09-19 17:09:09 +0000626 xFileControl: function(pFile, opId, pArg){
627 mTimeStart('xFileControl');
stephanaec046a2022-09-19 18:22:29 +0000628 const rc = (capi.SQLITE_FCNTL_SYNC===opId)
stephan138647a2022-09-20 03:31:02 +0000629 ? opRun('xSync', pFile, 0)
stephanaec046a2022-09-19 18:22:29 +0000630 : capi.SQLITE_NOTFOUND;
stephanf8150112022-09-19 17:09:09 +0000631 mTimeEnd();
stephanaec046a2022-09-19 18:22:29 +0000632 return rc;
stephanc5313af2022-09-18 02:35:30 +0000633 },
634 xFileSize: function(pFile,pSz64){
stephanf8150112022-09-19 17:09:09 +0000635 mTimeStart('xFileSize');
stephanc5313af2022-09-18 02:35:30 +0000636 const rc = opRun('xFileSize', pFile);
stephane8afca32022-09-21 14:02:47 +0000637 if(0==rc){
stephan138647a2022-09-20 03:31:02 +0000638 const sz = state.s11n.deserialize()[0];
stephan278d3fa2022-09-26 13:55:10 +0000639 wasm.setMemValue(pSz64, sz, 'i64');
stephanc5313af2022-09-18 02:35:30 +0000640 }
stephanf8150112022-09-19 17:09:09 +0000641 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000642 return rc;
643 },
644 xLock: function(pFile,lockType){
645 //2022-09: OPFS handles lock when opened
646 //warn("xLock(",arguments,") is a no-op");
647 return 0;
648 },
stephan138647a2022-09-20 03:31:02 +0000649 xRead: function(pFile,pDest,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000650 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000651 mTimeStart('xRead');
stephanc5313af2022-09-18 02:35:30 +0000652 const f = __openFiles[pFile];
653 let rc;
654 try {
stephan138647a2022-09-20 03:31:02 +0000655 rc = opRun('xRead',pFile, n, Number(offset64));
stephan862281f2022-09-19 09:25:25 +0000656 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
stephanf8150112022-09-19 17:09:09 +0000657 // set() seems to be the fastest way to copy this...
658 wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
stephan862281f2022-09-19 09:25:25 +0000659 }
stephanc5313af2022-09-18 02:35:30 +0000660 }catch(e){
661 error("xRead(",arguments,") failed:",e,f);
662 rc = capi.SQLITE_IOERR_READ;
663 }
stephanf8150112022-09-19 17:09:09 +0000664 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000665 return rc;
666 },
667 xSync: function(pFile,flags){
stephanaec046a2022-09-19 18:22:29 +0000668 ++metrics.xSync.count;
stephan138647a2022-09-20 03:31:02 +0000669 return 0; // impl'd in xFileControl()
stephanc5313af2022-09-18 02:35:30 +0000670 },
671 xTruncate: function(pFile,sz64){
stephanf8150112022-09-19 17:09:09 +0000672 mTimeStart('xTruncate');
stephan138647a2022-09-20 03:31:02 +0000673 const rc = opRun('xTruncate', pFile, Number(sz64));
stephanf8150112022-09-19 17:09:09 +0000674 mTimeEnd();
675 return rc;
stephanc5313af2022-09-18 02:35:30 +0000676 },
677 xUnlock: function(pFile,lockType){
678 //2022-09: OPFS handles lock when opened
679 //warn("xUnlock(",arguments,") is a no-op");
680 return 0;
681 },
stephan138647a2022-09-20 03:31:02 +0000682 xWrite: function(pFile,pSrc,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000683 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000684 mTimeStart('xWrite');
stephanc5313af2022-09-18 02:35:30 +0000685 const f = __openFiles[pFile];
stephanf8150112022-09-19 17:09:09 +0000686 let rc;
stephanc5313af2022-09-18 02:35:30 +0000687 try {
stephanf8150112022-09-19 17:09:09 +0000688 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
stephan138647a2022-09-20 03:31:02 +0000689 rc = opRun('xWrite', pFile, n, Number(offset64));
stephanc5313af2022-09-18 02:35:30 +0000690 }catch(e){
691 error("xWrite(",arguments,") failed:",e,f);
stephanf8150112022-09-19 17:09:09 +0000692 rc = capi.SQLITE_IOERR_WRITE;
stephanc5313af2022-09-18 02:35:30 +0000693 }
stephanf8150112022-09-19 17:09:09 +0000694 mTimeEnd();
695 return rc;
stephanc5313af2022-09-18 02:35:30 +0000696 }
697 }/*ioSyncWrappers*/;
698
699 /**
700 Impls for the sqlite3_vfs methods. Maintenance reminder: members
701 are in alphabetical order to simplify finding them.
702 */
703 const vfsSyncWrappers = {
704 xAccess: function(pVfs,zName,flags,pOut){
stephanf8150112022-09-19 17:09:09 +0000705 mTimeStart('xAccess');
stephan5e8bb0a2022-09-20 08:27:57 +0000706 const rc = opRun('xAccess', wasm.cstringToJs(zName));
707 wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
stephanf8150112022-09-19 17:09:09 +0000708 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000709 return 0;
710 },
711 xCurrentTime: function(pVfs,pOut){
712 /* If it turns out that we need to adjust for timezone, see:
713 https://stackoverflow.com/a/11760121/1458521 */
714 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
715 'double');
716 return 0;
717 },
718 xCurrentTimeInt64: function(pVfs,pOut){
719 // TODO: confirm that this calculation is correct
720 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
721 'i64');
722 return 0;
723 },
724 xDelete: function(pVfs, zName, doSyncDir){
stephanf8150112022-09-19 17:09:09 +0000725 mTimeStart('xDelete');
stephan138647a2022-09-20 03:31:02 +0000726 opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
stephanf3860122022-09-18 17:32:35 +0000727 /* We're ignoring errors because we cannot yet differentiate
728 between harmless and non-harmless failures. */
stephanf8150112022-09-19 17:09:09 +0000729 mTimeEnd();
stephanf3860122022-09-18 17:32:35 +0000730 return 0;
stephanc5313af2022-09-18 02:35:30 +0000731 },
732 xFullPathname: function(pVfs,zName,nOut,pOut){
733 /* Until/unless we have some notion of "current dir"
734 in OPFS, simply copy zName to pOut... */
735 const i = wasm.cstrncpy(pOut, zName, nOut);
736 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
737 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
738 },
739 xGetLastError: function(pVfs,nOut,pOut){
740 /* TODO: store exception.message values from the async
741 partner in a dedicated SharedArrayBuffer, noting that we'd have
742 to encode them... TextEncoder can do that for us. */
743 warn("OPFS xGetLastError() has nothing sensible to return.");
744 return 0;
745 },
stephan8766fd22022-09-19 05:19:04 +0000746 //xSleep is optionally defined below
stephanc5313af2022-09-18 02:35:30 +0000747 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
stephanf8150112022-09-19 17:09:09 +0000748 mTimeStart('xOpen');
stephanc5313af2022-09-18 02:35:30 +0000749 if(!f._){
750 f._ = {
751 fileTypes: {
752 SQLITE_OPEN_MAIN_DB: 'mainDb',
753 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
754 SQLITE_OPEN_TEMP_DB: 'tempDb',
755 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
756 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
757 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
758 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
759 SQLITE_OPEN_WAL: 'wal'
760 },
761 getFileType: function(filename,oflags){
762 const ft = f._.fileTypes;
763 for(let k of Object.keys(ft)){
764 if(oflags & capi[k]) return ft[k];
765 }
766 warn("Cannot determine fileType based on xOpen() flags for file",filename);
767 return '???';
768 }
769 };
770 }
771 if(0===zName){
772 zName = randomFilename();
773 }else if('number'===typeof zName){
774 zName = wasm.cstringToJs(zName);
775 }
stephan138647a2022-09-20 03:31:02 +0000776 const fh = Object.create(null);
777 fh.fid = pFile;
778 fh.filename = zName;
779 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
780 fh.flags = flags;
781 const rc = opRun('xOpen', pFile, zName, flags);
stephanc5313af2022-09-18 02:35:30 +0000782 if(!rc){
783 /* Recall that sqlite3_vfs::xClose() will be called, even on
784 error, unless pFile->pMethods is NULL. */
stephan138647a2022-09-20 03:31:02 +0000785 if(fh.readOnly){
stephanc5313af2022-09-18 02:35:30 +0000786 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
787 }
stephan138647a2022-09-20 03:31:02 +0000788 __openFiles[pFile] = fh;
789 fh.sabView = state.sabFileBufView;
790 fh.sq3File = new sqlite3_file(pFile);
791 fh.sq3File.$pMethods = opfsIoMethods.pointer;
stephanc5313af2022-09-18 02:35:30 +0000792 }
stephanf8150112022-09-19 17:09:09 +0000793 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000794 return rc;
795 }/*xOpen()*/
796 }/*vfsSyncWrappers*/;
797
stephan8766fd22022-09-19 05:19:04 +0000798 if(dVfs){
799 opfsVfs.$xRandomness = dVfs.$xRandomness;
800 opfsVfs.$xSleep = dVfs.$xSleep;
801 }
stephanc5313af2022-09-18 02:35:30 +0000802 if(!opfsVfs.$xRandomness){
803 /* If the default VFS has no xRandomness(), add a basic JS impl... */
804 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
805 const heap = wasm.heap8u();
806 let i = 0;
807 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
808 return i;
809 };
810 }
811 if(!opfsVfs.$xSleep){
812 /* If we can inherit an xSleep() impl from the default VFS then
stephan8766fd22022-09-19 05:19:04 +0000813 assume it's sane and use it, otherwise install a JS-based
814 one. */
815 vfsSyncWrappers.xSleep = function(pVfs,ms){
stephanc4b87be2022-09-20 01:28:47 +0000816 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
stephan8766fd22022-09-19 05:19:04 +0000817 return 0;
818 };
stephanc5313af2022-09-18 02:35:30 +0000819 }
820
821 /* Install the vfs/io_methods into their C-level shared instances... */
stephan278d3fa2022-09-26 13:55:10 +0000822 for(let k of Object.keys(ioSyncWrappers)){
823 installMethod(opfsIoMethods, k, ioSyncWrappers[k]);
824 }
825 for(let k of Object.keys(vfsSyncWrappers)){
826 installMethod(opfsVfs, k, vfsSyncWrappers[k]);
827 }
stephanf3860122022-09-18 17:32:35 +0000828
stephanf3860122022-09-18 17:32:35 +0000829 /**
830 Syncronously deletes the given OPFS filesystem entry, ignoring
831 any errors. As this environment has no notion of "current
832 directory", the given name must be an absolute path. If the 2nd
833 argument is truthy, deletion is recursive (use with caution!).
834
stephan278d3fa2022-09-26 13:55:10 +0000835 Returns true if the deletion succeeded and false if it fails,
stephanf3860122022-09-18 17:32:35 +0000836 but cannot report the nature of the failure.
837 */
stephan0e0687c2022-09-19 13:44:23 +0000838 opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
stephan72ab4002022-09-21 12:27:35 +0000839 mTimeStart('xDelete');
840 const rc = opRun('xDelete', fsEntryName, 0, recursive);
841 mTimeEnd();
842 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000843 };
844 /**
stephanf3860122022-09-18 17:32:35 +0000845 Synchronously creates the given directory name, recursively, in
846 the OPFS filesystem. Returns true if it succeeds or the
847 directory already exists, else false.
848 */
stephan5e8bb0a2022-09-20 08:27:57 +0000849 opfsUtil.mkdir = function(absDirName){
stephan72ab4002022-09-21 12:27:35 +0000850 mTimeStart('mkdir');
851 const rc = opRun('mkdir', absDirName);
852 mTimeEnd();
853 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000854 };
855 /**
856 Synchronously checks whether the given OPFS filesystem exists,
857 returning true if it does, false if it doesn't.
858 */
859 opfsUtil.entryExists = function(fsEntryName){
860 return 0===opRun('xAccess', fsEntryName);
861 };
862
863 /**
864 Generates a random ASCII string, intended for use as a
865 temporary file name. Its argument is the length of the string,
866 defaulting to 16.
867 */
868 opfsUtil.randomFilename = randomFilename;
stephan56fae742022-09-24 10:12:19 +0000869
870 /**
871 Re-registers the OPFS VFS. This is intended only for odd use
872 cases which have to call sqlite3_shutdown() as part of their
873 initialization process, which will unregister the VFS
874 registered by installOpfsVfs(). If passed a truthy value, the
875 OPFS VFS is registered as the default VFS, else it is not made
876 the default. Returns the result of the the
877 sqlite3_vfs_register() call.
878
879 Design note: the problem of having to re-register things after
880 a shutdown/initialize pair is more general. How to best plug
881 that in to the library is unclear. In particular, we cannot
882 hook in to any C-side calls to sqlite3_initialize(), so we
883 cannot add an after-initialize callback mechanism.
884 */
stephan3d645482022-09-27 09:17:37 +0000885 opfsUtil.registerVfs = (asDefault=false)=>{
stephan56fae742022-09-24 10:12:19 +0000886 return capi.wasm.exports.sqlite3_vfs_register(
887 opfsVfs.pointer, asDefault ? 1 : 0
888 );
889 };
stephan3d645482022-09-27 09:17:37 +0000890
891 //TODO to support fiddle db upload:
892 //opfsUtil.createFile = function(absName, content=undefined){...}
893
stephanf3860122022-09-18 17:32:35 +0000894 if(sqlite3.oo1){
895 opfsUtil.OpfsDb = function(...args){
896 const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
897 opt.vfs = opfsVfs.$zName;
898 sqlite3.oo1.dbCtorHelper.call(this, opt);
899 };
900 opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
stephan4f5bbed2022-10-03 13:03:41 +0000901 sqlite3.oo1.dbCtorHelper.setVfsPostOpenSql(
902 opfsVfs.pointer,
903 /* Truncate journal mode is faster than delete or wal for
904 OPFS, per speedtest1. */
905 "pragma journal_mode=truncate"
906 );
stephanf3860122022-09-18 17:32:35 +0000907 }
stephan4f5bbed2022-10-03 13:03:41 +0000908
stephanf3860122022-09-18 17:32:35 +0000909 /**
910 Potential TODOs:
911
912 - Expose one or both of the Worker objects via opfsUtil and
913 publish an interface for proxying the higher-level OPFS
914 features like getting a directory listing.
915 */
stephan5e8bb0a2022-09-20 08:27:57 +0000916 const sanityCheck = function(){
stephanc5313af2022-09-18 02:35:30 +0000917 const scope = wasm.scopedAllocPush();
918 const sq3File = new sqlite3_file();
919 try{
920 const fid = sq3File.pointer;
921 const openFlags = capi.SQLITE_OPEN_CREATE
922 | capi.SQLITE_OPEN_READWRITE
923 //| capi.SQLITE_OPEN_DELETEONCLOSE
924 | capi.SQLITE_OPEN_MAIN_DB;
925 const pOut = wasm.scopedAlloc(8);
stephanb8c8d4e2022-09-20 13:25:39 +0000926 const dbFile = "/sanity/check/file"+randomFilename(8);
stephanc5313af2022-09-18 02:35:30 +0000927 const zDbFile = wasm.scopedAllocCString(dbFile);
928 let rc;
stephane8afca32022-09-21 14:02:47 +0000929 state.s11n.serialize("This is ä string.");
930 rc = state.s11n.deserialize();
931 log("deserialize() says:",rc);
932 if("This is ä string."!==rc[0]) toss("String d13n error.");
stephanc5313af2022-09-18 02:35:30 +0000933 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
934 rc = wasm.getMemValue(pOut,'i32');
935 log("xAccess(",dbFile,") exists ?=",rc);
936 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
937 fid, openFlags, pOut);
stephanc4b87be2022-09-20 01:28:47 +0000938 log("open rc =",rc,"state.sabOPView[xOpen] =",
939 state.sabOPView[state.opIds.xOpen]);
stephane8afca32022-09-21 14:02:47 +0000940 if(0!==rc){
stephanc5313af2022-09-18 02:35:30 +0000941 error("open failed with code",rc);
942 return;
943 }
944 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
945 rc = wasm.getMemValue(pOut,'i32');
946 if(!rc) toss("xAccess() failed to detect file.");
947 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
948 if(rc) toss('sync failed w/ rc',rc);
949 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
950 if(rc) toss('truncate failed w/ rc',rc);
951 wasm.setMemValue(pOut,0,'i64');
952 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
953 if(rc) toss('xFileSize failed w/ rc',rc);
954 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
955 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
956 if(rc) toss("xWrite() failed!");
957 const readBuf = wasm.scopedAlloc(16);
958 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
959 wasm.setMemValue(readBuf+6,0);
960 let jRead = wasm.cstringToJs(readBuf);
961 log("xRead() got:",jRead);
962 if("sanity"!==jRead) toss("Unexpected xRead() value.");
stephan8766fd22022-09-19 05:19:04 +0000963 if(vfsSyncWrappers.xSleep){
964 log("xSleep()ing before close()ing...");
965 vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
966 log("waking up from xSleep()");
967 }
stephanc5313af2022-09-18 02:35:30 +0000968 rc = ioSyncWrappers.xClose(fid);
stephanc4b87be2022-09-20 01:28:47 +0000969 log("xClose rc =",rc,"sabOPView =",state.sabOPView);
stephanc5313af2022-09-18 02:35:30 +0000970 log("Deleting file:",dbFile);
971 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
972 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
973 rc = wasm.getMemValue(pOut,'i32');
974 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
stephanc9e26022022-09-20 10:11:52 +0000975 warn("End of OPFS sanity checks.");
stephanc5313af2022-09-18 02:35:30 +0000976 }finally{
977 sq3File.dispose();
978 wasm.scopedAllocPop(scope);
979 }
980 }/*sanityCheck()*/;
stephan6559e0a2022-09-27 14:31:34 +0000981
stephanc5313af2022-09-18 02:35:30 +0000982 W.onmessage = function({data}){
983 //log("Worker.onmessage:",data);
984 switch(data.type){
stephan138647a2022-09-20 03:31:02 +0000985 case 'opfs-async-loaded':
stephane8afca32022-09-21 14:02:47 +0000986 /*Arrives as soon as the asyc proxy finishes loading.
987 Pass our config and shared state on to the async worker.*/
stephan5e8bb0a2022-09-20 08:27:57 +0000988 W.postMessage({type: 'opfs-async-init',args: state});
stephanc5313af2022-09-18 02:35:30 +0000989 break;
stephan138647a2022-09-20 03:31:02 +0000990 case 'opfs-async-inited':{
stephane8afca32022-09-21 14:02:47 +0000991 /*Indicates that the async partner has received the 'init'
992 and has finished initializing, so the real work can
993 begin...*/
stephanc5313af2022-09-18 02:35:30 +0000994 try {
stephan0e0687c2022-09-19 13:44:23 +0000995 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
stephanc5313af2022-09-18 02:35:30 +0000996 if(rc){
stephanc5313af2022-09-18 02:35:30 +0000997 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
998 }
999 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
1000 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
1001 }
1002 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
stephanc4b87be2022-09-20 01:28:47 +00001003 state.sabOPView = new Int32Array(state.sabOP);
stephan138647a2022-09-20 03:31:02 +00001004 state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
1005 state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
1006 initS11n();
stephanc5313af2022-09-18 02:35:30 +00001007 if(options.sanityChecks){
1008 warn("Running sanity checks because of opfs-sanity-check URL arg...");
1009 sanityCheck();
1010 }
stephan1f095d42022-09-26 11:38:58 +00001011 navigator.storage.getDirectory().then((d)=>{
1012 W.onerror = W._originalOnError;
1013 delete W._originalOnError;
1014 sqlite3.opfs = opfsUtil;
1015 opfsUtil.rootDirectory = d;
1016 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
1017 promiseResolve(sqlite3);
1018 });
stephanc5313af2022-09-18 02:35:30 +00001019 }catch(e){
1020 error(e);
1021 promiseReject(e);
1022 }
1023 break;
1024 }
1025 default:
1026 promiseReject(e);
1027 error("Unexpected message from the async worker:",data);
1028 break;
1029 }
1030 };
1031 })/*thePromise*/;
1032 return thePromise;
1033}/*installOpfsVfs()*/;
stephan5b9973d2022-09-27 13:40:12 +00001034installOpfsVfs.defaultProxyUri =
1035 //self.location.pathname.replace(/[^/]*$/, "sqlite3-opfs-async-proxy.js");
1036 "sqlite3-opfs-async-proxy.js";
1037//console.warn("sqlite3.installOpfsVfs.defaultProxyUri =",sqlite3.installOpfsVfs.defaultProxyUri);
1038self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>installOpfsVfs());
stephanc5313af2022-09-18 02:35:30 +00001039}/*sqlite3ApiBootstrap.initializers.push()*/);