blob: 90f161680955838b22cbba3c1c4a1d305deb72cb [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);
stephan3c272ba2022-10-04 00:54:00 +0000145 W.postMessage({type:'opfs-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++;
stephan3c272ba2022-10-04 00:54:00 +0000288 state.opIds['opfs-async-metrics'] = i++;
289 state.opIds['opfs-async-shutdown'] = i++;
stephanc4b87be2022-09-20 01:28:47 +0000290 state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
stephanf8150112022-09-19 17:09:09 +0000291 opfsUtil.metrics.reset();
stephanc5313af2022-09-18 02:35:30 +0000292 }
293
stephanc9e26022022-09-20 10:11:52 +0000294 /**
295 SQLITE_xxx constants to export to the async worker
296 counterpart...
297 */
stephanc5313af2022-09-18 02:35:30 +0000298 state.sq3Codes = Object.create(null);
stephanc9e26022022-09-20 10:11:52 +0000299 [
stephanc5313af2022-09-18 02:35:30 +0000300 'SQLITE_ERROR', 'SQLITE_IOERR',
301 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
302 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
303 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
304 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
stephanf3860122022-09-18 17:32:35 +0000305 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
stephanc4b87be2022-09-20 01:28:47 +0000306 'SQLITE_IOERR_DELETE',
307 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE',
308 'SQLITE_OPEN_READONLY'
stephanc5313af2022-09-18 02:35:30 +0000309 ].forEach(function(k){
310 state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
stephan3961b262022-08-10 11:26:08 +0000311 });
stephan3961b262022-08-10 11:26:08 +0000312
stephanc5313af2022-09-18 02:35:30 +0000313 /**
stephanc9e26022022-09-20 10:11:52 +0000314 Runs the given operation (by name) in the async worker
315 counterpart, waits for its response, and returns the result
316 which the async worker writes to SAB[state.opIds.rc]. The
317 2nd and subsequent arguments must be the aruguments for the
318 async op.
stephanc5313af2022-09-18 02:35:30 +0000319 */
stephan138647a2022-09-20 03:31:02 +0000320 const opRun = (op,...args)=>{
stephan5e8bb0a2022-09-20 08:27:57 +0000321 const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
322 state.s11n.serialize(...args);
stephanc9e26022022-09-20 10:11:52 +0000323 Atomics.store(state.sabOPView, state.opIds.rc, -1);
stephan5e8bb0a2022-09-20 08:27:57 +0000324 Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
325 Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
stephanf8150112022-09-19 17:09:09 +0000326 const t = performance.now();
stephanc9e26022022-09-20 10:11:52 +0000327 Atomics.wait(state.sabOPView, state.opIds.rc, -1);
328 const rc = Atomics.load(state.sabOPView, state.opIds.rc);
stephane8afca32022-09-21 14:02:47 +0000329 metrics[op].wait += performance.now() - t;
330 if(rc && state.asyncS11nExceptions){
stephan72ab4002022-09-21 12:27:35 +0000331 const err = state.s11n.deserialize();
332 if(err) error(op+"() async error:",...err);
333 }
stephan5e8bb0a2022-09-20 08:27:57 +0000334 return rc;
stephanc5313af2022-09-18 02:35:30 +0000335 };
336
stephan138647a2022-09-20 03:31:02 +0000337 const initS11n = ()=>{
stephanb8c8d4e2022-09-20 13:25:39 +0000338 /**
stephan72ab4002022-09-21 12:27:35 +0000339 ACHTUNG: this code is 100% duplicated in the other half of this
340 proxy! The documentation is maintained in the "synchronous half".
stephanb8c8d4e2022-09-20 13:25:39 +0000341
stephan72ab4002022-09-21 12:27:35 +0000342 This proxy de/serializes cross-thread function arguments and
343 output-pointer values via the state.sabIO SharedArrayBuffer,
344 using the region defined by (state.sabS11nOffset,
345 state.sabS11nOffset]. Only one dataset is recorded at a time.
346
347 This is not a general-purpose format. It only supports the range
348 of operations, and data sizes, needed by the sqlite3_vfs and
349 sqlite3_io_methods operations.
350
351 The data format can be succinctly summarized as:
352
353 Nt...Td...D
354
355 Where:
356
357 - N = number of entries (1 byte)
358
359 - t = type ID of first argument (1 byte)
360
361 - ...T = type IDs of the 2nd and subsequent arguments (1 byte
362 each).
363
364 - d = raw bytes of first argument (per-type size).
365
366 - ...D = raw bytes of the 2nd and subsequent arguments (per-type
367 size).
368
369 All types except strings have fixed sizes. Strings are stored
370 using their TextEncoder/TextDecoder representations. It would
371 arguably make more sense to store them as Int16Arrays of
372 their JS character values, but how best/fastest to get that
373 in and out of string form us an open point.
374
375 Historical note: this impl was initially about 1% this size by
376 using using JSON.stringify/parse(), but using fit-to-purpose
377 serialization saves considerable runtime.
stephanb8c8d4e2022-09-20 13:25:39 +0000378 */
stephan138647a2022-09-20 03:31:02 +0000379 if(state.s11n) return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000380 const textDecoder = new TextDecoder(),
stephan72ab4002022-09-21 12:27:35 +0000381 textEncoder = new TextEncoder('utf-8'),
382 viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
383 viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
stephan138647a2022-09-20 03:31:02 +0000384 state.s11n = Object.create(null);
stephan72ab4002022-09-21 12:27:35 +0000385 /* Only arguments and return values of these types may be
386 serialized. This covers the whole range of types needed by the
387 sqlite3_vfs API. */
stephanb8c8d4e2022-09-20 13:25:39 +0000388 const TypeIds = Object.create(null);
389 TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
390 TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
391 TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
392 TypeIds.string = { id: 4 };
stephan72ab4002022-09-21 12:27:35 +0000393
394 const getTypeId = (v)=>(
395 TypeIds[typeof v]
396 || toss("Maintenance required: this value type cannot be serialized.",v)
397 );
stephanb8c8d4e2022-09-20 13:25:39 +0000398 const getTypeIdById = (tid)=>{
399 switch(tid){
stephan72ab4002022-09-21 12:27:35 +0000400 case TypeIds.number.id: return TypeIds.number;
401 case TypeIds.bigint.id: return TypeIds.bigint;
402 case TypeIds.boolean.id: return TypeIds.boolean;
403 case TypeIds.string.id: return TypeIds.string;
404 default: toss("Invalid type ID:",tid);
stephanb8c8d4e2022-09-20 13:25:39 +0000405 }
406 };
stephan72ab4002022-09-21 12:27:35 +0000407
stephan138647a2022-09-20 03:31:02 +0000408 /**
stephan72ab4002022-09-21 12:27:35 +0000409 Returns an array of the deserialized state stored by the most
410 recent serialize() operation (from from this thread or the
411 counterpart thread), or null if the serialization buffer is empty.
stephan138647a2022-09-20 03:31:02 +0000412 */
413 state.s11n.deserialize = function(){
stephanb8c8d4e2022-09-20 13:25:39 +0000414 ++metrics.s11n.deserialize.count;
415 const t = performance.now();
stephanb8c8d4e2022-09-20 13:25:39 +0000416 const argc = viewU8[0];
stephan72ab4002022-09-21 12:27:35 +0000417 const rc = argc ? [] : null;
stephanb8c8d4e2022-09-20 13:25:39 +0000418 if(argc){
stephan72ab4002022-09-21 12:27:35 +0000419 const typeIds = [];
420 let offset = 1, i, n, v;
stephanb8c8d4e2022-09-20 13:25:39 +0000421 for(i = 0; i < argc; ++i, ++offset){
422 typeIds.push(getTypeIdById(viewU8[offset]));
423 }
424 for(i = 0; i < argc; ++i){
425 const t = typeIds[i];
426 if(t.getter){
427 v = viewDV[t.getter](offset, state.littleEndian);
428 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000429 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000430 n = viewDV.getInt32(offset, state.littleEndian);
431 offset += 4;
432 v = textDecoder.decode(viewU8.slice(offset, offset+n));
433 offset += n;
434 }
435 rc.push(v);
436 }
437 }
438 //log("deserialize:",argc, rc);
439 metrics.s11n.deserialize.time += performance.now() - t;
440 return rc;
441 };
stephan72ab4002022-09-21 12:27:35 +0000442
stephan138647a2022-09-20 03:31:02 +0000443 /**
444 Serializes all arguments to the shared buffer for consumption
stephanb8c8d4e2022-09-20 13:25:39 +0000445 by the counterpart thread.
stephan5e8bb0a2022-09-20 08:27:57 +0000446
stephanb8c8d4e2022-09-20 13:25:39 +0000447 This routine is only intended for serializing OPFS VFS
448 arguments and (in at least one special case) result values,
449 and the buffer is sized to be able to comfortably handle
450 those.
stephan5e8bb0a2022-09-20 08:27:57 +0000451
452 If passed no arguments then it zeroes out the serialization
453 state.
stephan138647a2022-09-20 03:31:02 +0000454 */
455 state.s11n.serialize = function(...args){
stephanb8c8d4e2022-09-20 13:25:39 +0000456 const t = performance.now();
stephan72ab4002022-09-21 12:27:35 +0000457 ++metrics.s11n.serialize.count;
stephan5e8bb0a2022-09-20 08:27:57 +0000458 if(args.length){
stephanb8c8d4e2022-09-20 13:25:39 +0000459 //log("serialize():",args);
stephan72ab4002022-09-21 12:27:35 +0000460 const typeIds = [];
461 let i = 0, offset = 1;
462 viewU8[0] = args.length & 0xff /* header = # of args */;
stephanb8c8d4e2022-09-20 13:25:39 +0000463 for(; i < args.length; ++i, ++offset){
stephan72ab4002022-09-21 12:27:35 +0000464 /* Write the TypeIds.id value into the next args.length
465 bytes. */
stephanb8c8d4e2022-09-20 13:25:39 +0000466 typeIds.push(getTypeId(args[i]));
467 viewU8[offset] = typeIds[i].id;
468 }
469 for(i = 0; i < args.length; ++i) {
stephan72ab4002022-09-21 12:27:35 +0000470 /* Deserialize the following bytes based on their
471 corresponding TypeIds.id from the header. */
stephanb8c8d4e2022-09-20 13:25:39 +0000472 const t = typeIds[i];
473 if(t.setter){
474 viewDV[t.setter](offset, args[i], state.littleEndian);
475 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000476 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000477 const s = textEncoder.encode(args[i]);
478 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
479 offset += 4;
480 viewU8.set(s, offset);
481 offset += s.byteLength;
482 }
483 }
484 //log("serialize() result:",viewU8.slice(0,offset));
stephan5e8bb0a2022-09-20 08:27:57 +0000485 }else{
stephanb8c8d4e2022-09-20 13:25:39 +0000486 viewU8[0] = 0;
stephan5e8bb0a2022-09-20 08:27:57 +0000487 }
stephanb8c8d4e2022-09-20 13:25:39 +0000488 metrics.s11n.serialize.time += performance.now() - t;
stephan138647a2022-09-20 03:31:02 +0000489 };
490 return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000491 }/*initS11n()*/;
stephan138647a2022-09-20 03:31:02 +0000492
stephanc5313af2022-09-18 02:35:30 +0000493 /**
494 Generates a random ASCII string len characters long, intended for
495 use as a temporary file name.
496 */
497 const randomFilename = function f(len=16){
498 if(!f._chars){
499 f._chars = "abcdefghijklmnopqrstuvwxyz"+
500 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
501 "012346789";
502 f._n = f._chars.length;
503 }
504 const a = [];
505 let i = 0;
506 for( ; i < len; ++i){
507 const ndx = Math.random() * (f._n * 64) % f._n | 0;
508 a[i] = f._chars[ndx];
509 }
510 return a.join('');
511 };
512
513 /**
514 Map of sqlite3_file pointers to objects constructed by xOpen().
515 */
516 const __openFiles = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000517
518 /**
519 Installs a StructBinder-bound function pointer member of the
520 given name and function in the given StructType target object.
521 It creates a WASM proxy for the given function and arranges for
522 that proxy to be cleaned up when tgt.dispose() is called. Throws
523 on the slightest hint of error (e.g. tgt is-not-a StructType,
524 name does not map to a struct-bound member, etc.).
525
526 Returns a proxy for this function which is bound to tgt and takes
527 2 args (name,func). That function returns the same thing,
528 permitting calls to be chained.
529
530 If called with only 1 arg, it has no side effects but returns a
531 func with the same signature as described above.
532 */
533 const installMethod = function callee(tgt, name, func){
stephanf3860122022-09-18 17:32:35 +0000534 if(!(tgt instanceof sqlite3.StructBinder.StructType)){
stephanc5313af2022-09-18 02:35:30 +0000535 toss("Usage error: target object is-not-a StructType.");
536 }
537 if(1===arguments.length){
538 return (n,f)=>callee(tgt,n,f);
539 }
540 if(!callee.argcProxy){
541 callee.argcProxy = function(func,sig){
542 return function(...args){
543 if(func.length!==arguments.length){
544 toss("Argument mismatch. Native signature is:",sig);
545 }
546 return func.apply(this, args);
547 }
548 };
549 callee.removeFuncList = function(){
550 if(this.ondispose.__removeFuncList){
551 this.ondispose.__removeFuncList.forEach(
552 (v,ndx)=>{
553 if('number'===typeof v){
554 try{wasm.uninstallFunction(v)}
555 catch(e){/*ignore*/}
556 }
557 /* else it's a descriptive label for the next number in
558 the list. */
559 }
560 );
561 delete this.ondispose.__removeFuncList;
562 }
563 };
564 }/*static init*/
565 const sigN = tgt.memberSignature(name);
566 if(sigN.length<2){
567 toss("Member",name," is not a function pointer. Signature =",sigN);
568 }
569 const memKey = tgt.memberKey(name);
570 //log("installMethod",tgt, name, sigN);
stephanc2ccd672022-09-20 10:47:36 +0000571 const fProxy = 0
stephanc5313af2022-09-18 02:35:30 +0000572 // We can remove this proxy middle-man once the VFS is working
573 ? callee.argcProxy(func, sigN)
574 : func;
575 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
576 tgt[memKey] = pFunc;
577 if(!tgt.ondispose) tgt.ondispose = [];
578 if(!tgt.ondispose.__removeFuncList){
579 tgt.ondispose.push('ondispose.__removeFuncList handler',
580 callee.removeFuncList);
581 tgt.ondispose.__removeFuncList = [];
582 }
583 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
584 return (n,f)=>callee(tgt, n, f);
585 }/*installMethod*/;
stephanf8150112022-09-19 17:09:09 +0000586
587 const opTimer = Object.create(null);
588 opTimer.op = undefined;
589 opTimer.start = undefined;
590 const mTimeStart = (op)=>{
591 opTimer.start = performance.now();
592 opTimer.op = op;
593 //metrics[op] || toss("Maintenance required: missing metrics for",op);
594 ++metrics[op].count;
595 };
596 const mTimeEnd = ()=>(
597 metrics[opTimer.op].time += performance.now() - opTimer.start
598 );
599
stephanc5313af2022-09-18 02:35:30 +0000600 /**
601 Impls for the sqlite3_io_methods methods. Maintenance reminder:
602 members are in alphabetical order to simplify finding them.
603 */
604 const ioSyncWrappers = {
605 xCheckReservedLock: function(pFile,pOut){
606 // Exclusive lock is automatically acquired when opened
607 //warn("xCheckReservedLock(",arguments,") is a no-op");
608 wasm.setMemValue(pOut,1,'i32');
609 return 0;
610 },
611 xClose: function(pFile){
stephanf8150112022-09-19 17:09:09 +0000612 mTimeStart('xClose');
stephanc5313af2022-09-18 02:35:30 +0000613 let rc = 0;
614 const f = __openFiles[pFile];
615 if(f){
616 delete __openFiles[pFile];
617 rc = opRun('xClose', pFile);
618 if(f.sq3File) f.sq3File.dispose();
619 }
stephanf8150112022-09-19 17:09:09 +0000620 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000621 return rc;
622 },
623 xDeviceCharacteristics: function(pFile){
624 //debug("xDeviceCharacteristics(",pFile,")");
625 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
626 },
stephanf8150112022-09-19 17:09:09 +0000627 xFileControl: function(pFile, opId, pArg){
628 mTimeStart('xFileControl');
stephanaec046a2022-09-19 18:22:29 +0000629 const rc = (capi.SQLITE_FCNTL_SYNC===opId)
stephan138647a2022-09-20 03:31:02 +0000630 ? opRun('xSync', pFile, 0)
stephanaec046a2022-09-19 18:22:29 +0000631 : capi.SQLITE_NOTFOUND;
stephanf8150112022-09-19 17:09:09 +0000632 mTimeEnd();
stephanaec046a2022-09-19 18:22:29 +0000633 return rc;
stephanc5313af2022-09-18 02:35:30 +0000634 },
635 xFileSize: function(pFile,pSz64){
stephanf8150112022-09-19 17:09:09 +0000636 mTimeStart('xFileSize');
stephanc5313af2022-09-18 02:35:30 +0000637 const rc = opRun('xFileSize', pFile);
stephane8afca32022-09-21 14:02:47 +0000638 if(0==rc){
stephan138647a2022-09-20 03:31:02 +0000639 const sz = state.s11n.deserialize()[0];
stephan278d3fa2022-09-26 13:55:10 +0000640 wasm.setMemValue(pSz64, sz, 'i64');
stephanc5313af2022-09-18 02:35:30 +0000641 }
stephanf8150112022-09-19 17:09:09 +0000642 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000643 return rc;
644 },
645 xLock: function(pFile,lockType){
646 //2022-09: OPFS handles lock when opened
647 //warn("xLock(",arguments,") is a no-op");
648 return 0;
649 },
stephan138647a2022-09-20 03:31:02 +0000650 xRead: function(pFile,pDest,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000651 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000652 mTimeStart('xRead');
stephanc5313af2022-09-18 02:35:30 +0000653 const f = __openFiles[pFile];
654 let rc;
655 try {
stephan138647a2022-09-20 03:31:02 +0000656 rc = opRun('xRead',pFile, n, Number(offset64));
stephan862281f2022-09-19 09:25:25 +0000657 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
stephanf8150112022-09-19 17:09:09 +0000658 // set() seems to be the fastest way to copy this...
659 wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
stephan862281f2022-09-19 09:25:25 +0000660 }
stephanc5313af2022-09-18 02:35:30 +0000661 }catch(e){
662 error("xRead(",arguments,") failed:",e,f);
663 rc = capi.SQLITE_IOERR_READ;
664 }
stephanf8150112022-09-19 17:09:09 +0000665 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000666 return rc;
667 },
668 xSync: function(pFile,flags){
stephanaec046a2022-09-19 18:22:29 +0000669 ++metrics.xSync.count;
stephan138647a2022-09-20 03:31:02 +0000670 return 0; // impl'd in xFileControl()
stephanc5313af2022-09-18 02:35:30 +0000671 },
672 xTruncate: function(pFile,sz64){
stephanf8150112022-09-19 17:09:09 +0000673 mTimeStart('xTruncate');
stephan138647a2022-09-20 03:31:02 +0000674 const rc = opRun('xTruncate', pFile, Number(sz64));
stephanf8150112022-09-19 17:09:09 +0000675 mTimeEnd();
676 return rc;
stephanc5313af2022-09-18 02:35:30 +0000677 },
678 xUnlock: function(pFile,lockType){
679 //2022-09: OPFS handles lock when opened
680 //warn("xUnlock(",arguments,") is a no-op");
681 return 0;
682 },
stephan138647a2022-09-20 03:31:02 +0000683 xWrite: function(pFile,pSrc,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000684 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000685 mTimeStart('xWrite');
stephanc5313af2022-09-18 02:35:30 +0000686 const f = __openFiles[pFile];
stephanf8150112022-09-19 17:09:09 +0000687 let rc;
stephanc5313af2022-09-18 02:35:30 +0000688 try {
stephanf8150112022-09-19 17:09:09 +0000689 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
stephan138647a2022-09-20 03:31:02 +0000690 rc = opRun('xWrite', pFile, n, Number(offset64));
stephanc5313af2022-09-18 02:35:30 +0000691 }catch(e){
692 error("xWrite(",arguments,") failed:",e,f);
stephanf8150112022-09-19 17:09:09 +0000693 rc = capi.SQLITE_IOERR_WRITE;
stephanc5313af2022-09-18 02:35:30 +0000694 }
stephanf8150112022-09-19 17:09:09 +0000695 mTimeEnd();
696 return rc;
stephanc5313af2022-09-18 02:35:30 +0000697 }
698 }/*ioSyncWrappers*/;
699
700 /**
701 Impls for the sqlite3_vfs methods. Maintenance reminder: members
702 are in alphabetical order to simplify finding them.
703 */
704 const vfsSyncWrappers = {
705 xAccess: function(pVfs,zName,flags,pOut){
stephanf8150112022-09-19 17:09:09 +0000706 mTimeStart('xAccess');
stephan5e8bb0a2022-09-20 08:27:57 +0000707 const rc = opRun('xAccess', wasm.cstringToJs(zName));
708 wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
stephanf8150112022-09-19 17:09:09 +0000709 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000710 return 0;
711 },
712 xCurrentTime: function(pVfs,pOut){
713 /* If it turns out that we need to adjust for timezone, see:
714 https://stackoverflow.com/a/11760121/1458521 */
715 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
716 'double');
717 return 0;
718 },
719 xCurrentTimeInt64: function(pVfs,pOut){
720 // TODO: confirm that this calculation is correct
721 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
722 'i64');
723 return 0;
724 },
725 xDelete: function(pVfs, zName, doSyncDir){
stephanf8150112022-09-19 17:09:09 +0000726 mTimeStart('xDelete');
stephan138647a2022-09-20 03:31:02 +0000727 opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
stephanf3860122022-09-18 17:32:35 +0000728 /* We're ignoring errors because we cannot yet differentiate
729 between harmless and non-harmless failures. */
stephanf8150112022-09-19 17:09:09 +0000730 mTimeEnd();
stephanf3860122022-09-18 17:32:35 +0000731 return 0;
stephanc5313af2022-09-18 02:35:30 +0000732 },
733 xFullPathname: function(pVfs,zName,nOut,pOut){
734 /* Until/unless we have some notion of "current dir"
735 in OPFS, simply copy zName to pOut... */
736 const i = wasm.cstrncpy(pOut, zName, nOut);
737 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
738 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
739 },
740 xGetLastError: function(pVfs,nOut,pOut){
741 /* TODO: store exception.message values from the async
742 partner in a dedicated SharedArrayBuffer, noting that we'd have
743 to encode them... TextEncoder can do that for us. */
744 warn("OPFS xGetLastError() has nothing sensible to return.");
745 return 0;
746 },
stephan8766fd22022-09-19 05:19:04 +0000747 //xSleep is optionally defined below
stephanc5313af2022-09-18 02:35:30 +0000748 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
stephanf8150112022-09-19 17:09:09 +0000749 mTimeStart('xOpen');
stephanc5313af2022-09-18 02:35:30 +0000750 if(!f._){
751 f._ = {
752 fileTypes: {
753 SQLITE_OPEN_MAIN_DB: 'mainDb',
754 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
755 SQLITE_OPEN_TEMP_DB: 'tempDb',
756 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
757 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
758 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
759 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
760 SQLITE_OPEN_WAL: 'wal'
761 },
762 getFileType: function(filename,oflags){
763 const ft = f._.fileTypes;
764 for(let k of Object.keys(ft)){
765 if(oflags & capi[k]) return ft[k];
766 }
767 warn("Cannot determine fileType based on xOpen() flags for file",filename);
768 return '???';
769 }
770 };
771 }
772 if(0===zName){
773 zName = randomFilename();
774 }else if('number'===typeof zName){
775 zName = wasm.cstringToJs(zName);
776 }
stephan138647a2022-09-20 03:31:02 +0000777 const fh = Object.create(null);
778 fh.fid = pFile;
779 fh.filename = zName;
780 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
781 fh.flags = flags;
782 const rc = opRun('xOpen', pFile, zName, flags);
stephanc5313af2022-09-18 02:35:30 +0000783 if(!rc){
784 /* Recall that sqlite3_vfs::xClose() will be called, even on
785 error, unless pFile->pMethods is NULL. */
stephan138647a2022-09-20 03:31:02 +0000786 if(fh.readOnly){
stephanc5313af2022-09-18 02:35:30 +0000787 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
788 }
stephan138647a2022-09-20 03:31:02 +0000789 __openFiles[pFile] = fh;
790 fh.sabView = state.sabFileBufView;
791 fh.sq3File = new sqlite3_file(pFile);
792 fh.sq3File.$pMethods = opfsIoMethods.pointer;
stephanc5313af2022-09-18 02:35:30 +0000793 }
stephanf8150112022-09-19 17:09:09 +0000794 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000795 return rc;
796 }/*xOpen()*/
797 }/*vfsSyncWrappers*/;
798
stephan8766fd22022-09-19 05:19:04 +0000799 if(dVfs){
800 opfsVfs.$xRandomness = dVfs.$xRandomness;
801 opfsVfs.$xSleep = dVfs.$xSleep;
802 }
stephanc5313af2022-09-18 02:35:30 +0000803 if(!opfsVfs.$xRandomness){
804 /* If the default VFS has no xRandomness(), add a basic JS impl... */
805 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
806 const heap = wasm.heap8u();
807 let i = 0;
808 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
809 return i;
810 };
811 }
812 if(!opfsVfs.$xSleep){
813 /* If we can inherit an xSleep() impl from the default VFS then
stephan8766fd22022-09-19 05:19:04 +0000814 assume it's sane and use it, otherwise install a JS-based
815 one. */
816 vfsSyncWrappers.xSleep = function(pVfs,ms){
stephanc4b87be2022-09-20 01:28:47 +0000817 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
stephan8766fd22022-09-19 05:19:04 +0000818 return 0;
819 };
stephanc5313af2022-09-18 02:35:30 +0000820 }
821
822 /* Install the vfs/io_methods into their C-level shared instances... */
stephan278d3fa2022-09-26 13:55:10 +0000823 for(let k of Object.keys(ioSyncWrappers)){
824 installMethod(opfsIoMethods, k, ioSyncWrappers[k]);
825 }
826 for(let k of Object.keys(vfsSyncWrappers)){
827 installMethod(opfsVfs, k, vfsSyncWrappers[k]);
828 }
stephanf3860122022-09-18 17:32:35 +0000829
stephanf3860122022-09-18 17:32:35 +0000830 /**
831 Syncronously deletes the given OPFS filesystem entry, ignoring
832 any errors. As this environment has no notion of "current
833 directory", the given name must be an absolute path. If the 2nd
834 argument is truthy, deletion is recursive (use with caution!).
835
stephan278d3fa2022-09-26 13:55:10 +0000836 Returns true if the deletion succeeded and false if it fails,
stephanf3860122022-09-18 17:32:35 +0000837 but cannot report the nature of the failure.
838 */
stephan0e0687c2022-09-19 13:44:23 +0000839 opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
stephan72ab4002022-09-21 12:27:35 +0000840 mTimeStart('xDelete');
841 const rc = opRun('xDelete', fsEntryName, 0, recursive);
842 mTimeEnd();
843 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000844 };
845 /**
stephanf3860122022-09-18 17:32:35 +0000846 Synchronously creates the given directory name, recursively, in
847 the OPFS filesystem. Returns true if it succeeds or the
848 directory already exists, else false.
849 */
stephan5e8bb0a2022-09-20 08:27:57 +0000850 opfsUtil.mkdir = function(absDirName){
stephan72ab4002022-09-21 12:27:35 +0000851 mTimeStart('mkdir');
852 const rc = opRun('mkdir', absDirName);
853 mTimeEnd();
854 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000855 };
856 /**
857 Synchronously checks whether the given OPFS filesystem exists,
858 returning true if it does, false if it doesn't.
859 */
860 opfsUtil.entryExists = function(fsEntryName){
861 return 0===opRun('xAccess', fsEntryName);
862 };
863
864 /**
865 Generates a random ASCII string, intended for use as a
866 temporary file name. Its argument is the length of the string,
867 defaulting to 16.
868 */
869 opfsUtil.randomFilename = randomFilename;
stephan56fae742022-09-24 10:12:19 +0000870
871 /**
872 Re-registers the OPFS VFS. This is intended only for odd use
873 cases which have to call sqlite3_shutdown() as part of their
874 initialization process, which will unregister the VFS
875 registered by installOpfsVfs(). If passed a truthy value, the
876 OPFS VFS is registered as the default VFS, else it is not made
877 the default. Returns the result of the the
878 sqlite3_vfs_register() call.
879
880 Design note: the problem of having to re-register things after
881 a shutdown/initialize pair is more general. How to best plug
882 that in to the library is unclear. In particular, we cannot
883 hook in to any C-side calls to sqlite3_initialize(), so we
884 cannot add an after-initialize callback mechanism.
885 */
stephan3d645482022-09-27 09:17:37 +0000886 opfsUtil.registerVfs = (asDefault=false)=>{
stephan56fae742022-09-24 10:12:19 +0000887 return capi.wasm.exports.sqlite3_vfs_register(
888 opfsVfs.pointer, asDefault ? 1 : 0
889 );
890 };
stephan3d645482022-09-27 09:17:37 +0000891
stephan3c272ba2022-10-04 00:54:00 +0000892 /**
893 Only for test/development use.
894 */
895 opfsUtil.debug = {
896 asyncShutdown: ()=>{
897 warn("Shutting down OPFS async listener. OPFS will no longer work.");
898 opRun('opfs-async-shutdown');
899 },
900 asyncRestart: ()=>{
901 warn("Attempting to restart OPFS async listener. Might work, might not.");
902 W.postMessage({type: 'opfs-async-restart'});
903 }
904 };
905
stephan3d645482022-09-27 09:17:37 +0000906 //TODO to support fiddle db upload:
907 //opfsUtil.createFile = function(absName, content=undefined){...}
908
stephanf3860122022-09-18 17:32:35 +0000909 if(sqlite3.oo1){
910 opfsUtil.OpfsDb = function(...args){
911 const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
912 opt.vfs = opfsVfs.$zName;
913 sqlite3.oo1.dbCtorHelper.call(this, opt);
914 };
915 opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
stephan4f5bbed2022-10-03 13:03:41 +0000916 sqlite3.oo1.dbCtorHelper.setVfsPostOpenSql(
917 opfsVfs.pointer,
stephanc7fb48d2022-10-04 09:12:05 +0000918 [
919 /* Truncate journal mode is faster than delete or wal for
920 this vfs, per speedtest1. */
921 "pragma journal_mode=truncate;",
922 /*
923 This vfs benefits hugely from cache on moderate/large
924 speedtest1 --size 50 and --size 100 workloads. We currently
925 rely on setting a non-default cache size when building
926 sqlite3.wasm. If that policy changes, the cache can
927 be set here.
928 */
929 //"pragma cache_size=-8388608;"
930 ].join('')
stephan4f5bbed2022-10-03 13:03:41 +0000931 );
stephanf3860122022-09-18 17:32:35 +0000932 }
stephan4f5bbed2022-10-03 13:03:41 +0000933
stephanf3860122022-09-18 17:32:35 +0000934 /**
935 Potential TODOs:
936
937 - Expose one or both of the Worker objects via opfsUtil and
938 publish an interface for proxying the higher-level OPFS
939 features like getting a directory listing.
940 */
stephan5e8bb0a2022-09-20 08:27:57 +0000941 const sanityCheck = function(){
stephanc5313af2022-09-18 02:35:30 +0000942 const scope = wasm.scopedAllocPush();
943 const sq3File = new sqlite3_file();
944 try{
945 const fid = sq3File.pointer;
946 const openFlags = capi.SQLITE_OPEN_CREATE
947 | capi.SQLITE_OPEN_READWRITE
948 //| capi.SQLITE_OPEN_DELETEONCLOSE
949 | capi.SQLITE_OPEN_MAIN_DB;
950 const pOut = wasm.scopedAlloc(8);
stephanb8c8d4e2022-09-20 13:25:39 +0000951 const dbFile = "/sanity/check/file"+randomFilename(8);
stephanc5313af2022-09-18 02:35:30 +0000952 const zDbFile = wasm.scopedAllocCString(dbFile);
953 let rc;
stephane8afca32022-09-21 14:02:47 +0000954 state.s11n.serialize("This is ä string.");
955 rc = state.s11n.deserialize();
956 log("deserialize() says:",rc);
957 if("This is ä string."!==rc[0]) toss("String d13n error.");
stephanc5313af2022-09-18 02:35:30 +0000958 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
959 rc = wasm.getMemValue(pOut,'i32');
960 log("xAccess(",dbFile,") exists ?=",rc);
961 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
962 fid, openFlags, pOut);
stephanc4b87be2022-09-20 01:28:47 +0000963 log("open rc =",rc,"state.sabOPView[xOpen] =",
964 state.sabOPView[state.opIds.xOpen]);
stephane8afca32022-09-21 14:02:47 +0000965 if(0!==rc){
stephanc5313af2022-09-18 02:35:30 +0000966 error("open failed with code",rc);
967 return;
968 }
969 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
970 rc = wasm.getMemValue(pOut,'i32');
971 if(!rc) toss("xAccess() failed to detect file.");
972 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
973 if(rc) toss('sync failed w/ rc',rc);
974 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
975 if(rc) toss('truncate failed w/ rc',rc);
976 wasm.setMemValue(pOut,0,'i64');
977 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
978 if(rc) toss('xFileSize failed w/ rc',rc);
979 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
980 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
981 if(rc) toss("xWrite() failed!");
982 const readBuf = wasm.scopedAlloc(16);
983 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
984 wasm.setMemValue(readBuf+6,0);
985 let jRead = wasm.cstringToJs(readBuf);
986 log("xRead() got:",jRead);
987 if("sanity"!==jRead) toss("Unexpected xRead() value.");
stephan8766fd22022-09-19 05:19:04 +0000988 if(vfsSyncWrappers.xSleep){
989 log("xSleep()ing before close()ing...");
990 vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
991 log("waking up from xSleep()");
992 }
stephanc5313af2022-09-18 02:35:30 +0000993 rc = ioSyncWrappers.xClose(fid);
stephanc4b87be2022-09-20 01:28:47 +0000994 log("xClose rc =",rc,"sabOPView =",state.sabOPView);
stephanc5313af2022-09-18 02:35:30 +0000995 log("Deleting file:",dbFile);
996 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
997 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
998 rc = wasm.getMemValue(pOut,'i32');
999 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
stephanc9e26022022-09-20 10:11:52 +00001000 warn("End of OPFS sanity checks.");
stephanc5313af2022-09-18 02:35:30 +00001001 }finally{
1002 sq3File.dispose();
1003 wasm.scopedAllocPop(scope);
1004 }
1005 }/*sanityCheck()*/;
stephan6559e0a2022-09-27 14:31:34 +00001006
stephanc5313af2022-09-18 02:35:30 +00001007 W.onmessage = function({data}){
1008 //log("Worker.onmessage:",data);
1009 switch(data.type){
stephan138647a2022-09-20 03:31:02 +00001010 case 'opfs-async-loaded':
stephane8afca32022-09-21 14:02:47 +00001011 /*Arrives as soon as the asyc proxy finishes loading.
1012 Pass our config and shared state on to the async worker.*/
stephan5e8bb0a2022-09-20 08:27:57 +00001013 W.postMessage({type: 'opfs-async-init',args: state});
stephanc5313af2022-09-18 02:35:30 +00001014 break;
stephan138647a2022-09-20 03:31:02 +00001015 case 'opfs-async-inited':{
stephane8afca32022-09-21 14:02:47 +00001016 /*Indicates that the async partner has received the 'init'
1017 and has finished initializing, so the real work can
1018 begin...*/
stephanc5313af2022-09-18 02:35:30 +00001019 try {
stephan0e0687c2022-09-19 13:44:23 +00001020 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
stephanc5313af2022-09-18 02:35:30 +00001021 if(rc){
stephanc5313af2022-09-18 02:35:30 +00001022 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
1023 }
1024 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
1025 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
1026 }
1027 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
stephanc4b87be2022-09-20 01:28:47 +00001028 state.sabOPView = new Int32Array(state.sabOP);
stephan138647a2022-09-20 03:31:02 +00001029 state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
1030 state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
1031 initS11n();
stephanc5313af2022-09-18 02:35:30 +00001032 if(options.sanityChecks){
1033 warn("Running sanity checks because of opfs-sanity-check URL arg...");
1034 sanityCheck();
1035 }
stephan1f095d42022-09-26 11:38:58 +00001036 navigator.storage.getDirectory().then((d)=>{
1037 W.onerror = W._originalOnError;
1038 delete W._originalOnError;
1039 sqlite3.opfs = opfsUtil;
1040 opfsUtil.rootDirectory = d;
1041 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
1042 promiseResolve(sqlite3);
1043 });
stephanc5313af2022-09-18 02:35:30 +00001044 }catch(e){
1045 error(e);
1046 promiseReject(e);
1047 }
1048 break;
1049 }
1050 default:
1051 promiseReject(e);
1052 error("Unexpected message from the async worker:",data);
1053 break;
1054 }
1055 };
stephan3c272ba2022-10-04 00:54:00 +00001056
stephanc5313af2022-09-18 02:35:30 +00001057 })/*thePromise*/;
1058 return thePromise;
1059}/*installOpfsVfs()*/;
stephan5b9973d2022-09-27 13:40:12 +00001060installOpfsVfs.defaultProxyUri =
1061 //self.location.pathname.replace(/[^/]*$/, "sqlite3-opfs-async-proxy.js");
1062 "sqlite3-opfs-async-proxy.js";
1063//console.warn("sqlite3.installOpfsVfs.defaultProxyUri =",sqlite3.installOpfsVfs.defaultProxyUri);
1064self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>installOpfsVfs());
stephanc5313af2022-09-18 02:35:30 +00001065}/*sqlite3ApiBootstrap.initializers.push()*/);