blob: 5dac79991865b8b9a81f803663aac9786d48bc59 [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 ||
stephan509f4052022-09-19 09:58:01 +000077 !self.FileSystemHandle ||
78 !self.FileSystemDirectoryHandle ||
79 !self.FileSystemFileHandle ||
80 !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
81 !navigator.storage.getDirectory){
82 return Promise.reject(
83 new Error("This environment does not have OPFS support.")
84 );
85 }
stephanc5313af2022-09-18 02:35:30 +000086 const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
87 proxyUri: asyncProxyUri
stephan3961b262022-08-10 11:26:08 +000088 };
stephan509f4052022-09-19 09:58:01 +000089 const urlParams = new URL(self.location.href).searchParams;
stephanc5313af2022-09-18 02:35:30 +000090 if(undefined===options.verbose){
stephan509f4052022-09-19 09:58:01 +000091 options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
stephan3961b262022-08-10 11:26:08 +000092 }
stephanc5313af2022-09-18 02:35:30 +000093 if(undefined===options.sanityChecks){
stephan509f4052022-09-19 09:58:01 +000094 options.sanityChecks = urlParams.has('opfs-sanity-check');
stephanc5313af2022-09-18 02:35:30 +000095 }
96 if(undefined===options.proxyUri){
97 options.proxyUri = callee.defaultProxyUri;
98 }
stephanf3860122022-09-18 17:32:35 +000099
stephane8afca32022-09-21 14:02:47 +0000100 const thePromise = new Promise(function(promiseResolve, promiseReject_){
stephan509f4052022-09-19 09:58:01 +0000101 const loggers = {
102 0:console.error.bind(console),
103 1:console.warn.bind(console),
104 2:console.log.bind(console)
stephanc5313af2022-09-18 02:35:30 +0000105 };
stephan509f4052022-09-19 09:58:01 +0000106 const logImpl = (level,...args)=>{
107 if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
108 };
109 const log = (...args)=>logImpl(2, ...args);
110 const warn = (...args)=>logImpl(1, ...args);
111 const error = (...args)=>logImpl(0, ...args);
stephanc5313af2022-09-18 02:35:30 +0000112 warn("The OPFS VFS feature is very much experimental and under construction.");
113 const toss = function(...args){throw new Error(args.join(' '))};
stephanc5313af2022-09-18 02:35:30 +0000114 const capi = sqlite3.capi;
115 const wasm = capi.wasm;
116 const sqlite3_vfs = capi.sqlite3_vfs;
117 const sqlite3_file = capi.sqlite3_file;
118 const sqlite3_io_methods = capi.sqlite3_io_methods;
stephan509f4052022-09-19 09:58:01 +0000119 /**
120 Generic utilities for working with OPFS. This will get filled out
121 by the Promise setup and, on success, installed as sqlite3.opfs.
122 */
123 const opfsUtil = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000124 /**
125 Not part of the public API. Solely for internal/development
126 use.
127 */
128 opfsUtil.metrics = {
129 dump: function(){
stephanaec046a2022-09-19 18:22:29 +0000130 let k, n = 0, t = 0, w = 0;
131 for(k in state.opIds){
stephanf8150112022-09-19 17:09:09 +0000132 const m = metrics[k];
133 n += m.count;
134 t += m.time;
stephanaec046a2022-09-19 18:22:29 +0000135 w += m.wait;
stephanf8150112022-09-19 17:09:09 +0000136 m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
137 m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
138 }
stephanaec046a2022-09-19 18:22:29 +0000139 console.log(self.location.href,
140 "metrics for",self.location.href,":",metrics,
141 "\nTotal of",n,"op(s) for",t,
142 "ms (incl. "+w+" ms of waiting on the async side)");
stephan56fae742022-09-24 10:12:19 +0000143 console.log("Serialization metrics:",metrics.s11n);
144 opRun('async-metrics');
stephanf8150112022-09-19 17:09:09 +0000145 },
146 reset: function(){
147 let k;
148 const r = (m)=>(m.count = m.time = m.wait = 0);
149 for(k in state.opIds){
150 r(metrics[k] = Object.create(null));
151 }
stephanb8c8d4e2022-09-20 13:25:39 +0000152 let s = metrics.s11n = Object.create(null);
153 s = s.serialize = Object.create(null);
154 s.count = s.time = 0;
155 s = metrics.s11n.deserialize = Object.create(null);
156 s.count = s.time = 0;
stephanaec046a2022-09-19 18:22:29 +0000157 //[ // timed routines which are not in state.opIds
158 // 'xFileControl'
159 //].forEach((k)=>r(metrics[k] = Object.create(null)));
stephanf8150112022-09-19 17:09:09 +0000160 }
161 }/*metrics*/;
stephan56fae742022-09-24 10:12:19 +0000162 const promiseReject = function(err){
163 opfsVfs.dispose();
164 return promiseReject_(err);
165 };
166 const W = new Worker(options.proxyUri);
167 W._originalOnError = W.onerror /* will be restored later */;
168 W.onerror = function(err){
169 // The error object doesn't contain any useful info when the
170 // failure is, e.g., that the remote script is 404.
171 promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
172 };
stephanc9e26022022-09-20 10:11:52 +0000173 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
174 const dVfs = pDVfs
175 ? new sqlite3_vfs(pDVfs)
176 : null /* dVfs will be null when sqlite3 is built with
177 SQLITE_OS_OTHER. Though we cannot currently handle
178 that case, the hope is to eventually be able to. */;
179 const opfsVfs = new sqlite3_vfs();
180 const opfsIoMethods = new sqlite3_io_methods();
181 opfsVfs.$iVersion = 2/*yes, two*/;
182 opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
183 opfsVfs.$mxPathname = 1024/*sure, why not?*/;
184 opfsVfs.$zName = wasm.allocCString("opfs");
185 // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
186 opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
187 opfsVfs.ondispose = [
188 '$zName', opfsVfs.$zName,
189 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
190 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
191 ];
192 /**
193 Pedantic sidebar about opfsVfs.ondispose: the entries in that array
194 are items to clean up when opfsVfs.dispose() is called, but in this
195 environment it will never be called. The VFS instance simply
196 hangs around until the WASM module instance is cleaned up. We
197 "could" _hypothetically_ clean it up by "importing" an
198 sqlite3_os_end() impl into the wasm build, but the shutdown order
199 of the wasm engine and the JS one are undefined so there is no
200 guaranty that the opfsVfs instance would be available in one
201 environment or the other when sqlite3_os_end() is called (_if_ it
202 gets called at all in a wasm build, which is undefined).
203 */
204
stephanc5313af2022-09-18 02:35:30 +0000205 /**
206 State which we send to the async-api Worker or share with it.
207 This object must initially contain only cloneable or sharable
208 objects. After the worker's "inited" message arrives, other types
209 of data may be added to it.
stephanf3860122022-09-18 17:32:35 +0000210
211 For purposes of Atomics.wait() and Atomics.notify(), we use a
212 SharedArrayBuffer with one slot reserved for each of the API
213 proxy's methods. The sync side of the API uses Atomics.wait()
214 on the corresponding slot and the async side uses
215 Atomics.notify() on that slot.
216
217 The approach of using a single SAB to serialize comms for all
218 instances might(?) lead to deadlock situations in multi-db
219 cases. We should probably have one SAB here with a single slot
220 for locking a per-file initialization step and then allocate a
221 separate SAB like the above one for each file. That will
222 require a bit of acrobatics but should be feasible.
stephanc5313af2022-09-18 02:35:30 +0000223 */
224 const state = Object.create(null);
225 state.verbose = options.verbose;
stephane8afca32022-09-21 14:02:47 +0000226 state.littleEndian = true;
stephan56fae742022-09-24 10:12:19 +0000227 /** Whether the async counterpart should log exceptions to
stephane8afca32022-09-21 14:02:47 +0000228 the serialization channel. That produces a great deal of
229 noise for seemingly innocuous things like xAccess() checks
stephan56fae742022-09-24 10:12:19 +0000230 for missing files, so this option may have one of 3 values:
231
232 0 = no exception logging
233
234 1 = only log exceptions for "significant" ops like xOpen(),
235 xRead(), and xWrite().
236
237 2 = log all exceptions.
238 */
239 state.asyncS11nExceptions = 1;
stephanc9e26022022-09-20 10:11:52 +0000240 /* Size of file I/O buffer block. 64k = max sqlite3 page size. */
stephanf3860122022-09-18 17:32:35 +0000241 state.fileBufferSize =
stephanc9e26022022-09-20 10:11:52 +0000242 1024 * 64;
stephan138647a2022-09-20 03:31:02 +0000243 state.sabS11nOffset = state.fileBufferSize;
stephanc9e26022022-09-20 10:11:52 +0000244 /**
245 The size of the block in our SAB for serializing arguments and
stephane8afca32022-09-21 14:02:47 +0000246 result values. Needs to be large enough to hold serialized
stephanc9e26022022-09-20 10:11:52 +0000247 values of any of the proxied APIs. Filenames are the largest
248 part but are limited to opfsVfs.$mxPathname bytes.
249 */
250 state.sabS11nSize = opfsVfs.$mxPathname * 2;
251 /**
252 The SAB used for all data I/O (files and arg/result s11n).
253 */
stephanc4b87be2022-09-20 01:28:47 +0000254 state.sabIO = new SharedArrayBuffer(
stephanc9e26022022-09-20 10:11:52 +0000255 state.fileBufferSize/* file i/o block */
256 + state.sabS11nSize/* argument/result serialization block */
stephanc4b87be2022-09-20 01:28:47 +0000257 );
stephanc5313af2022-09-18 02:35:30 +0000258 state.opIds = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000259 const metrics = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000260 {
stephanc9e26022022-09-20 10:11:52 +0000261 /* Indexes for use in our SharedArrayBuffer... */
stephan3961b262022-08-10 11:26:08 +0000262 let i = 0;
stephanc9e26022022-09-20 10:11:52 +0000263 /* SAB slot used to communicate which operation is desired
264 between both workers. This worker writes to it and the other
265 listens for changes. */
stephan138647a2022-09-20 03:31:02 +0000266 state.opIds.whichOp = i++;
stephanc9e26022022-09-20 10:11:52 +0000267 /* Slot for storing return values. This work listens to that
268 slot and the other worker writes to it. */
269 state.opIds.rc = i++;
270 /* Each function gets an ID which this worker writes to
271 the whichOp slot. The async-api worker uses Atomic.wait()
272 on the whichOp slot to figure out which operation to run
273 next. */
stephanc5313af2022-09-18 02:35:30 +0000274 state.opIds.xAccess = i++;
275 state.opIds.xClose = i++;
276 state.opIds.xDelete = i++;
stephanf3860122022-09-18 17:32:35 +0000277 state.opIds.xDeleteNoWait = i++;
stephan56fae742022-09-24 10:12:19 +0000278 state.opIds.xFileControl = i++;
stephanc5313af2022-09-18 02:35:30 +0000279 state.opIds.xFileSize = i++;
280 state.opIds.xOpen = i++;
281 state.opIds.xRead = i++;
282 state.opIds.xSleep = i++;
283 state.opIds.xSync = i++;
284 state.opIds.xTruncate = i++;
285 state.opIds.xWrite = i++;
stephanf3860122022-09-18 17:32:35 +0000286 state.opIds.mkdir = i++;
stephan56fae742022-09-24 10:12:19 +0000287 state.opIds['async-metrics'] = i++;
stephanc4b87be2022-09-20 01:28:47 +0000288 state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
stephanf8150112022-09-19 17:09:09 +0000289 opfsUtil.metrics.reset();
stephanc5313af2022-09-18 02:35:30 +0000290 }
291
stephanc9e26022022-09-20 10:11:52 +0000292 /**
293 SQLITE_xxx constants to export to the async worker
294 counterpart...
295 */
stephanc5313af2022-09-18 02:35:30 +0000296 state.sq3Codes = Object.create(null);
stephanc9e26022022-09-20 10:11:52 +0000297 [
stephanc5313af2022-09-18 02:35:30 +0000298 'SQLITE_ERROR', 'SQLITE_IOERR',
299 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
300 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
301 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
302 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
stephanf3860122022-09-18 17:32:35 +0000303 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
stephanc4b87be2022-09-20 01:28:47 +0000304 'SQLITE_IOERR_DELETE',
305 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE',
306 'SQLITE_OPEN_READONLY'
stephanc5313af2022-09-18 02:35:30 +0000307 ].forEach(function(k){
308 state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
stephan3961b262022-08-10 11:26:08 +0000309 });
stephan3961b262022-08-10 11:26:08 +0000310
stephanc5313af2022-09-18 02:35:30 +0000311 /**
stephanc9e26022022-09-20 10:11:52 +0000312 Runs the given operation (by name) in the async worker
313 counterpart, waits for its response, and returns the result
314 which the async worker writes to SAB[state.opIds.rc]. The
315 2nd and subsequent arguments must be the aruguments for the
316 async op.
stephanc5313af2022-09-18 02:35:30 +0000317 */
stephan138647a2022-09-20 03:31:02 +0000318 const opRun = (op,...args)=>{
stephan5e8bb0a2022-09-20 08:27:57 +0000319 const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
320 state.s11n.serialize(...args);
stephanc9e26022022-09-20 10:11:52 +0000321 Atomics.store(state.sabOPView, state.opIds.rc, -1);
stephan5e8bb0a2022-09-20 08:27:57 +0000322 Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
323 Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
stephanf8150112022-09-19 17:09:09 +0000324 const t = performance.now();
stephanc9e26022022-09-20 10:11:52 +0000325 Atomics.wait(state.sabOPView, state.opIds.rc, -1);
326 const rc = Atomics.load(state.sabOPView, state.opIds.rc);
stephane8afca32022-09-21 14:02:47 +0000327 metrics[op].wait += performance.now() - t;
328 if(rc && state.asyncS11nExceptions){
stephan72ab4002022-09-21 12:27:35 +0000329 const err = state.s11n.deserialize();
330 if(err) error(op+"() async error:",...err);
331 }
stephan5e8bb0a2022-09-20 08:27:57 +0000332 return rc;
stephanc5313af2022-09-18 02:35:30 +0000333 };
334
stephan138647a2022-09-20 03:31:02 +0000335 const initS11n = ()=>{
stephanb8c8d4e2022-09-20 13:25:39 +0000336 /**
stephan72ab4002022-09-21 12:27:35 +0000337 ACHTUNG: this code is 100% duplicated in the other half of this
338 proxy! The documentation is maintained in the "synchronous half".
stephanb8c8d4e2022-09-20 13:25:39 +0000339
stephan72ab4002022-09-21 12:27:35 +0000340 This proxy de/serializes cross-thread function arguments and
341 output-pointer values via the state.sabIO SharedArrayBuffer,
342 using the region defined by (state.sabS11nOffset,
343 state.sabS11nOffset]. Only one dataset is recorded at a time.
344
345 This is not a general-purpose format. It only supports the range
346 of operations, and data sizes, needed by the sqlite3_vfs and
347 sqlite3_io_methods operations.
348
349 The data format can be succinctly summarized as:
350
351 Nt...Td...D
352
353 Where:
354
355 - N = number of entries (1 byte)
356
357 - t = type ID of first argument (1 byte)
358
359 - ...T = type IDs of the 2nd and subsequent arguments (1 byte
360 each).
361
362 - d = raw bytes of first argument (per-type size).
363
364 - ...D = raw bytes of the 2nd and subsequent arguments (per-type
365 size).
366
367 All types except strings have fixed sizes. Strings are stored
368 using their TextEncoder/TextDecoder representations. It would
369 arguably make more sense to store them as Int16Arrays of
370 their JS character values, but how best/fastest to get that
371 in and out of string form us an open point.
372
373 Historical note: this impl was initially about 1% this size by
374 using using JSON.stringify/parse(), but using fit-to-purpose
375 serialization saves considerable runtime.
stephanb8c8d4e2022-09-20 13:25:39 +0000376 */
stephan138647a2022-09-20 03:31:02 +0000377 if(state.s11n) return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000378 const textDecoder = new TextDecoder(),
stephan72ab4002022-09-21 12:27:35 +0000379 textEncoder = new TextEncoder('utf-8'),
380 viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
381 viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
stephan138647a2022-09-20 03:31:02 +0000382 state.s11n = Object.create(null);
stephan72ab4002022-09-21 12:27:35 +0000383 /* Only arguments and return values of these types may be
384 serialized. This covers the whole range of types needed by the
385 sqlite3_vfs API. */
stephanb8c8d4e2022-09-20 13:25:39 +0000386 const TypeIds = Object.create(null);
387 TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
388 TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
389 TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
390 TypeIds.string = { id: 4 };
stephan72ab4002022-09-21 12:27:35 +0000391
392 const getTypeId = (v)=>(
393 TypeIds[typeof v]
394 || toss("Maintenance required: this value type cannot be serialized.",v)
395 );
stephanb8c8d4e2022-09-20 13:25:39 +0000396 const getTypeIdById = (tid)=>{
397 switch(tid){
stephan72ab4002022-09-21 12:27:35 +0000398 case TypeIds.number.id: return TypeIds.number;
399 case TypeIds.bigint.id: return TypeIds.bigint;
400 case TypeIds.boolean.id: return TypeIds.boolean;
401 case TypeIds.string.id: return TypeIds.string;
402 default: toss("Invalid type ID:",tid);
stephanb8c8d4e2022-09-20 13:25:39 +0000403 }
404 };
stephan72ab4002022-09-21 12:27:35 +0000405
stephan138647a2022-09-20 03:31:02 +0000406 /**
stephan72ab4002022-09-21 12:27:35 +0000407 Returns an array of the deserialized state stored by the most
408 recent serialize() operation (from from this thread or the
409 counterpart thread), or null if the serialization buffer is empty.
stephan138647a2022-09-20 03:31:02 +0000410 */
411 state.s11n.deserialize = function(){
stephanb8c8d4e2022-09-20 13:25:39 +0000412 ++metrics.s11n.deserialize.count;
413 const t = performance.now();
stephanb8c8d4e2022-09-20 13:25:39 +0000414 const argc = viewU8[0];
stephan72ab4002022-09-21 12:27:35 +0000415 const rc = argc ? [] : null;
stephanb8c8d4e2022-09-20 13:25:39 +0000416 if(argc){
stephan72ab4002022-09-21 12:27:35 +0000417 const typeIds = [];
418 let offset = 1, i, n, v;
stephanb8c8d4e2022-09-20 13:25:39 +0000419 for(i = 0; i < argc; ++i, ++offset){
420 typeIds.push(getTypeIdById(viewU8[offset]));
421 }
422 for(i = 0; i < argc; ++i){
423 const t = typeIds[i];
424 if(t.getter){
425 v = viewDV[t.getter](offset, state.littleEndian);
426 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000427 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000428 n = viewDV.getInt32(offset, state.littleEndian);
429 offset += 4;
430 v = textDecoder.decode(viewU8.slice(offset, offset+n));
431 offset += n;
432 }
433 rc.push(v);
434 }
435 }
436 //log("deserialize:",argc, rc);
437 metrics.s11n.deserialize.time += performance.now() - t;
438 return rc;
439 };
stephan72ab4002022-09-21 12:27:35 +0000440
stephan138647a2022-09-20 03:31:02 +0000441 /**
442 Serializes all arguments to the shared buffer for consumption
stephanb8c8d4e2022-09-20 13:25:39 +0000443 by the counterpart thread.
stephan5e8bb0a2022-09-20 08:27:57 +0000444
stephanb8c8d4e2022-09-20 13:25:39 +0000445 This routine is only intended for serializing OPFS VFS
446 arguments and (in at least one special case) result values,
447 and the buffer is sized to be able to comfortably handle
448 those.
stephan5e8bb0a2022-09-20 08:27:57 +0000449
450 If passed no arguments then it zeroes out the serialization
451 state.
stephan138647a2022-09-20 03:31:02 +0000452 */
453 state.s11n.serialize = function(...args){
stephanb8c8d4e2022-09-20 13:25:39 +0000454 const t = performance.now();
stephan72ab4002022-09-21 12:27:35 +0000455 ++metrics.s11n.serialize.count;
stephan5e8bb0a2022-09-20 08:27:57 +0000456 if(args.length){
stephanb8c8d4e2022-09-20 13:25:39 +0000457 //log("serialize():",args);
stephan72ab4002022-09-21 12:27:35 +0000458 const typeIds = [];
459 let i = 0, offset = 1;
460 viewU8[0] = args.length & 0xff /* header = # of args */;
stephanb8c8d4e2022-09-20 13:25:39 +0000461 for(; i < args.length; ++i, ++offset){
stephan72ab4002022-09-21 12:27:35 +0000462 /* Write the TypeIds.id value into the next args.length
463 bytes. */
stephanb8c8d4e2022-09-20 13:25:39 +0000464 typeIds.push(getTypeId(args[i]));
465 viewU8[offset] = typeIds[i].id;
466 }
467 for(i = 0; i < args.length; ++i) {
stephan72ab4002022-09-21 12:27:35 +0000468 /* Deserialize the following bytes based on their
469 corresponding TypeIds.id from the header. */
stephanb8c8d4e2022-09-20 13:25:39 +0000470 const t = typeIds[i];
471 if(t.setter){
472 viewDV[t.setter](offset, args[i], state.littleEndian);
473 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000474 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000475 const s = textEncoder.encode(args[i]);
476 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
477 offset += 4;
478 viewU8.set(s, offset);
479 offset += s.byteLength;
480 }
481 }
482 //log("serialize() result:",viewU8.slice(0,offset));
stephan5e8bb0a2022-09-20 08:27:57 +0000483 }else{
stephanb8c8d4e2022-09-20 13:25:39 +0000484 viewU8[0] = 0;
stephan5e8bb0a2022-09-20 08:27:57 +0000485 }
stephanb8c8d4e2022-09-20 13:25:39 +0000486 metrics.s11n.serialize.time += performance.now() - t;
stephan138647a2022-09-20 03:31:02 +0000487 };
488 return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000489 }/*initS11n()*/;
stephan138647a2022-09-20 03:31:02 +0000490
stephanc5313af2022-09-18 02:35:30 +0000491 /**
492 Generates a random ASCII string len characters long, intended for
493 use as a temporary file name.
494 */
495 const randomFilename = function f(len=16){
496 if(!f._chars){
497 f._chars = "abcdefghijklmnopqrstuvwxyz"+
498 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
499 "012346789";
500 f._n = f._chars.length;
501 }
502 const a = [];
503 let i = 0;
504 for( ; i < len; ++i){
505 const ndx = Math.random() * (f._n * 64) % f._n | 0;
506 a[i] = f._chars[ndx];
507 }
508 return a.join('');
509 };
510
511 /**
512 Map of sqlite3_file pointers to objects constructed by xOpen().
513 */
514 const __openFiles = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000515
516 /**
517 Installs a StructBinder-bound function pointer member of the
518 given name and function in the given StructType target object.
519 It creates a WASM proxy for the given function and arranges for
520 that proxy to be cleaned up when tgt.dispose() is called. Throws
521 on the slightest hint of error (e.g. tgt is-not-a StructType,
522 name does not map to a struct-bound member, etc.).
523
524 Returns a proxy for this function which is bound to tgt and takes
525 2 args (name,func). That function returns the same thing,
526 permitting calls to be chained.
527
528 If called with only 1 arg, it has no side effects but returns a
529 func with the same signature as described above.
530 */
531 const installMethod = function callee(tgt, name, func){
stephanf3860122022-09-18 17:32:35 +0000532 if(!(tgt instanceof sqlite3.StructBinder.StructType)){
stephanc5313af2022-09-18 02:35:30 +0000533 toss("Usage error: target object is-not-a StructType.");
534 }
535 if(1===arguments.length){
536 return (n,f)=>callee(tgt,n,f);
537 }
538 if(!callee.argcProxy){
539 callee.argcProxy = function(func,sig){
540 return function(...args){
541 if(func.length!==arguments.length){
542 toss("Argument mismatch. Native signature is:",sig);
543 }
544 return func.apply(this, args);
545 }
546 };
547 callee.removeFuncList = function(){
548 if(this.ondispose.__removeFuncList){
549 this.ondispose.__removeFuncList.forEach(
550 (v,ndx)=>{
551 if('number'===typeof v){
552 try{wasm.uninstallFunction(v)}
553 catch(e){/*ignore*/}
554 }
555 /* else it's a descriptive label for the next number in
556 the list. */
557 }
558 );
559 delete this.ondispose.__removeFuncList;
560 }
561 };
562 }/*static init*/
563 const sigN = tgt.memberSignature(name);
564 if(sigN.length<2){
565 toss("Member",name," is not a function pointer. Signature =",sigN);
566 }
567 const memKey = tgt.memberKey(name);
568 //log("installMethod",tgt, name, sigN);
stephanc2ccd672022-09-20 10:47:36 +0000569 const fProxy = 0
stephanc5313af2022-09-18 02:35:30 +0000570 // We can remove this proxy middle-man once the VFS is working
571 ? callee.argcProxy(func, sigN)
572 : func;
573 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
574 tgt[memKey] = pFunc;
575 if(!tgt.ondispose) tgt.ondispose = [];
576 if(!tgt.ondispose.__removeFuncList){
577 tgt.ondispose.push('ondispose.__removeFuncList handler',
578 callee.removeFuncList);
579 tgt.ondispose.__removeFuncList = [];
580 }
581 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
582 return (n,f)=>callee(tgt, n, f);
583 }/*installMethod*/;
stephanf8150112022-09-19 17:09:09 +0000584
585 const opTimer = Object.create(null);
586 opTimer.op = undefined;
587 opTimer.start = undefined;
588 const mTimeStart = (op)=>{
589 opTimer.start = performance.now();
590 opTimer.op = op;
591 //metrics[op] || toss("Maintenance required: missing metrics for",op);
592 ++metrics[op].count;
593 };
594 const mTimeEnd = ()=>(
595 metrics[opTimer.op].time += performance.now() - opTimer.start
596 );
597
stephanc5313af2022-09-18 02:35:30 +0000598 /**
599 Impls for the sqlite3_io_methods methods. Maintenance reminder:
600 members are in alphabetical order to simplify finding them.
601 */
602 const ioSyncWrappers = {
603 xCheckReservedLock: function(pFile,pOut){
604 // Exclusive lock is automatically acquired when opened
605 //warn("xCheckReservedLock(",arguments,") is a no-op");
606 wasm.setMemValue(pOut,1,'i32');
607 return 0;
608 },
609 xClose: function(pFile){
stephanf8150112022-09-19 17:09:09 +0000610 mTimeStart('xClose');
stephanc5313af2022-09-18 02:35:30 +0000611 let rc = 0;
612 const f = __openFiles[pFile];
613 if(f){
614 delete __openFiles[pFile];
615 rc = opRun('xClose', pFile);
616 if(f.sq3File) f.sq3File.dispose();
617 }
stephanf8150112022-09-19 17:09:09 +0000618 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000619 return rc;
620 },
621 xDeviceCharacteristics: function(pFile){
622 //debug("xDeviceCharacteristics(",pFile,")");
623 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
624 },
stephanf8150112022-09-19 17:09:09 +0000625 xFileControl: function(pFile, opId, pArg){
626 mTimeStart('xFileControl');
stephanaec046a2022-09-19 18:22:29 +0000627 const rc = (capi.SQLITE_FCNTL_SYNC===opId)
stephan138647a2022-09-20 03:31:02 +0000628 ? opRun('xSync', pFile, 0)
stephanaec046a2022-09-19 18:22:29 +0000629 : capi.SQLITE_NOTFOUND;
stephanf8150112022-09-19 17:09:09 +0000630 mTimeEnd();
stephanaec046a2022-09-19 18:22:29 +0000631 return rc;
stephanc5313af2022-09-18 02:35:30 +0000632 },
633 xFileSize: function(pFile,pSz64){
stephanf8150112022-09-19 17:09:09 +0000634 mTimeStart('xFileSize');
stephanc5313af2022-09-18 02:35:30 +0000635 const rc = opRun('xFileSize', pFile);
stephane8afca32022-09-21 14:02:47 +0000636 if(0==rc){
stephan138647a2022-09-20 03:31:02 +0000637 const sz = state.s11n.deserialize()[0];
stephan278d3fa2022-09-26 13:55:10 +0000638 wasm.setMemValue(pSz64, sz, 'i64');
stephanc5313af2022-09-18 02:35:30 +0000639 }
stephanf8150112022-09-19 17:09:09 +0000640 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000641 return rc;
642 },
643 xLock: function(pFile,lockType){
644 //2022-09: OPFS handles lock when opened
645 //warn("xLock(",arguments,") is a no-op");
646 return 0;
647 },
stephan138647a2022-09-20 03:31:02 +0000648 xRead: function(pFile,pDest,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000649 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000650 mTimeStart('xRead');
stephanc5313af2022-09-18 02:35:30 +0000651 const f = __openFiles[pFile];
652 let rc;
653 try {
stephan138647a2022-09-20 03:31:02 +0000654 rc = opRun('xRead',pFile, n, Number(offset64));
stephan862281f2022-09-19 09:25:25 +0000655 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
stephanf8150112022-09-19 17:09:09 +0000656 // set() seems to be the fastest way to copy this...
657 wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
stephan862281f2022-09-19 09:25:25 +0000658 }
stephanc5313af2022-09-18 02:35:30 +0000659 }catch(e){
660 error("xRead(",arguments,") failed:",e,f);
661 rc = capi.SQLITE_IOERR_READ;
662 }
stephanf8150112022-09-19 17:09:09 +0000663 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000664 return rc;
665 },
666 xSync: function(pFile,flags){
stephanaec046a2022-09-19 18:22:29 +0000667 ++metrics.xSync.count;
stephan138647a2022-09-20 03:31:02 +0000668 return 0; // impl'd in xFileControl()
stephanc5313af2022-09-18 02:35:30 +0000669 },
670 xTruncate: function(pFile,sz64){
stephanf8150112022-09-19 17:09:09 +0000671 mTimeStart('xTruncate');
stephan138647a2022-09-20 03:31:02 +0000672 const rc = opRun('xTruncate', pFile, Number(sz64));
stephanf8150112022-09-19 17:09:09 +0000673 mTimeEnd();
674 return rc;
stephanc5313af2022-09-18 02:35:30 +0000675 },
676 xUnlock: function(pFile,lockType){
677 //2022-09: OPFS handles lock when opened
678 //warn("xUnlock(",arguments,") is a no-op");
679 return 0;
680 },
stephan138647a2022-09-20 03:31:02 +0000681 xWrite: function(pFile,pSrc,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000682 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000683 mTimeStart('xWrite');
stephanc5313af2022-09-18 02:35:30 +0000684 const f = __openFiles[pFile];
stephanf8150112022-09-19 17:09:09 +0000685 let rc;
stephanc5313af2022-09-18 02:35:30 +0000686 try {
stephanf8150112022-09-19 17:09:09 +0000687 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
stephan138647a2022-09-20 03:31:02 +0000688 rc = opRun('xWrite', pFile, n, Number(offset64));
stephanc5313af2022-09-18 02:35:30 +0000689 }catch(e){
690 error("xWrite(",arguments,") failed:",e,f);
stephanf8150112022-09-19 17:09:09 +0000691 rc = capi.SQLITE_IOERR_WRITE;
stephanc5313af2022-09-18 02:35:30 +0000692 }
stephanf8150112022-09-19 17:09:09 +0000693 mTimeEnd();
694 return rc;
stephanc5313af2022-09-18 02:35:30 +0000695 }
696 }/*ioSyncWrappers*/;
697
698 /**
699 Impls for the sqlite3_vfs methods. Maintenance reminder: members
700 are in alphabetical order to simplify finding them.
701 */
702 const vfsSyncWrappers = {
703 xAccess: function(pVfs,zName,flags,pOut){
stephanf8150112022-09-19 17:09:09 +0000704 mTimeStart('xAccess');
stephan5e8bb0a2022-09-20 08:27:57 +0000705 const rc = opRun('xAccess', wasm.cstringToJs(zName));
706 wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
stephanf8150112022-09-19 17:09:09 +0000707 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000708 return 0;
709 },
710 xCurrentTime: function(pVfs,pOut){
711 /* If it turns out that we need to adjust for timezone, see:
712 https://stackoverflow.com/a/11760121/1458521 */
713 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
714 'double');
715 return 0;
716 },
717 xCurrentTimeInt64: function(pVfs,pOut){
718 // TODO: confirm that this calculation is correct
719 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
720 'i64');
721 return 0;
722 },
723 xDelete: function(pVfs, zName, doSyncDir){
stephanf8150112022-09-19 17:09:09 +0000724 mTimeStart('xDelete');
stephan138647a2022-09-20 03:31:02 +0000725 opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
stephanf3860122022-09-18 17:32:35 +0000726 /* We're ignoring errors because we cannot yet differentiate
727 between harmless and non-harmless failures. */
stephanf8150112022-09-19 17:09:09 +0000728 mTimeEnd();
stephanf3860122022-09-18 17:32:35 +0000729 return 0;
stephanc5313af2022-09-18 02:35:30 +0000730 },
731 xFullPathname: function(pVfs,zName,nOut,pOut){
732 /* Until/unless we have some notion of "current dir"
733 in OPFS, simply copy zName to pOut... */
734 const i = wasm.cstrncpy(pOut, zName, nOut);
735 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
736 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
737 },
738 xGetLastError: function(pVfs,nOut,pOut){
739 /* TODO: store exception.message values from the async
740 partner in a dedicated SharedArrayBuffer, noting that we'd have
741 to encode them... TextEncoder can do that for us. */
742 warn("OPFS xGetLastError() has nothing sensible to return.");
743 return 0;
744 },
stephan8766fd22022-09-19 05:19:04 +0000745 //xSleep is optionally defined below
stephanc5313af2022-09-18 02:35:30 +0000746 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
stephanf8150112022-09-19 17:09:09 +0000747 mTimeStart('xOpen');
stephanc5313af2022-09-18 02:35:30 +0000748 if(!f._){
749 f._ = {
750 fileTypes: {
751 SQLITE_OPEN_MAIN_DB: 'mainDb',
752 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
753 SQLITE_OPEN_TEMP_DB: 'tempDb',
754 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
755 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
756 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
757 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
758 SQLITE_OPEN_WAL: 'wal'
759 },
760 getFileType: function(filename,oflags){
761 const ft = f._.fileTypes;
762 for(let k of Object.keys(ft)){
763 if(oflags & capi[k]) return ft[k];
764 }
765 warn("Cannot determine fileType based on xOpen() flags for file",filename);
766 return '???';
767 }
768 };
769 }
770 if(0===zName){
771 zName = randomFilename();
772 }else if('number'===typeof zName){
773 zName = wasm.cstringToJs(zName);
774 }
stephan138647a2022-09-20 03:31:02 +0000775 const fh = Object.create(null);
776 fh.fid = pFile;
777 fh.filename = zName;
778 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
779 fh.flags = flags;
780 const rc = opRun('xOpen', pFile, zName, flags);
stephanc5313af2022-09-18 02:35:30 +0000781 if(!rc){
782 /* Recall that sqlite3_vfs::xClose() will be called, even on
783 error, unless pFile->pMethods is NULL. */
stephan138647a2022-09-20 03:31:02 +0000784 if(fh.readOnly){
stephanc5313af2022-09-18 02:35:30 +0000785 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
786 }
stephan138647a2022-09-20 03:31:02 +0000787 __openFiles[pFile] = fh;
788 fh.sabView = state.sabFileBufView;
789 fh.sq3File = new sqlite3_file(pFile);
790 fh.sq3File.$pMethods = opfsIoMethods.pointer;
stephanc5313af2022-09-18 02:35:30 +0000791 }
stephanf8150112022-09-19 17:09:09 +0000792 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000793 return rc;
794 }/*xOpen()*/
795 }/*vfsSyncWrappers*/;
796
stephan8766fd22022-09-19 05:19:04 +0000797 if(dVfs){
798 opfsVfs.$xRandomness = dVfs.$xRandomness;
799 opfsVfs.$xSleep = dVfs.$xSleep;
800 }
stephanc5313af2022-09-18 02:35:30 +0000801 if(!opfsVfs.$xRandomness){
802 /* If the default VFS has no xRandomness(), add a basic JS impl... */
803 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
804 const heap = wasm.heap8u();
805 let i = 0;
806 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
807 return i;
808 };
809 }
810 if(!opfsVfs.$xSleep){
811 /* If we can inherit an xSleep() impl from the default VFS then
stephan8766fd22022-09-19 05:19:04 +0000812 assume it's sane and use it, otherwise install a JS-based
813 one. */
814 vfsSyncWrappers.xSleep = function(pVfs,ms){
stephanc4b87be2022-09-20 01:28:47 +0000815 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
stephan8766fd22022-09-19 05:19:04 +0000816 return 0;
817 };
stephanc5313af2022-09-18 02:35:30 +0000818 }
819
820 /* Install the vfs/io_methods into their C-level shared instances... */
stephan278d3fa2022-09-26 13:55:10 +0000821 for(let k of Object.keys(ioSyncWrappers)){
822 installMethod(opfsIoMethods, k, ioSyncWrappers[k]);
823 }
824 for(let k of Object.keys(vfsSyncWrappers)){
825 installMethod(opfsVfs, k, vfsSyncWrappers[k]);
826 }
stephanf3860122022-09-18 17:32:35 +0000827
stephanf3860122022-09-18 17:32:35 +0000828 /**
829 Syncronously deletes the given OPFS filesystem entry, ignoring
830 any errors. As this environment has no notion of "current
831 directory", the given name must be an absolute path. If the 2nd
832 argument is truthy, deletion is recursive (use with caution!).
833
stephan278d3fa2022-09-26 13:55:10 +0000834 Returns true if the deletion succeeded and false if it fails,
stephanf3860122022-09-18 17:32:35 +0000835 but cannot report the nature of the failure.
836 */
stephan0e0687c2022-09-19 13:44:23 +0000837 opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
stephan72ab4002022-09-21 12:27:35 +0000838 mTimeStart('xDelete');
839 const rc = opRun('xDelete', fsEntryName, 0, recursive);
840 mTimeEnd();
841 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000842 };
843 /**
stephanf3860122022-09-18 17:32:35 +0000844 Synchronously creates the given directory name, recursively, in
845 the OPFS filesystem. Returns true if it succeeds or the
846 directory already exists, else false.
847 */
stephan5e8bb0a2022-09-20 08:27:57 +0000848 opfsUtil.mkdir = function(absDirName){
stephan72ab4002022-09-21 12:27:35 +0000849 mTimeStart('mkdir');
850 const rc = opRun('mkdir', absDirName);
851 mTimeEnd();
852 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000853 };
854 /**
855 Synchronously checks whether the given OPFS filesystem exists,
856 returning true if it does, false if it doesn't.
857 */
858 opfsUtil.entryExists = function(fsEntryName){
859 return 0===opRun('xAccess', fsEntryName);
860 };
861
862 /**
863 Generates a random ASCII string, intended for use as a
864 temporary file name. Its argument is the length of the string,
865 defaulting to 16.
866 */
867 opfsUtil.randomFilename = randomFilename;
stephan56fae742022-09-24 10:12:19 +0000868
869 /**
870 Re-registers the OPFS VFS. This is intended only for odd use
871 cases which have to call sqlite3_shutdown() as part of their
872 initialization process, which will unregister the VFS
873 registered by installOpfsVfs(). If passed a truthy value, the
874 OPFS VFS is registered as the default VFS, else it is not made
875 the default. Returns the result of the the
876 sqlite3_vfs_register() call.
877
878 Design note: the problem of having to re-register things after
879 a shutdown/initialize pair is more general. How to best plug
880 that in to the library is unclear. In particular, we cannot
881 hook in to any C-side calls to sqlite3_initialize(), so we
882 cannot add an after-initialize callback mechanism.
883 */
stephan3d645482022-09-27 09:17:37 +0000884 opfsUtil.registerVfs = (asDefault=false)=>{
stephan56fae742022-09-24 10:12:19 +0000885 return capi.wasm.exports.sqlite3_vfs_register(
886 opfsVfs.pointer, asDefault ? 1 : 0
887 );
888 };
stephan3d645482022-09-27 09:17:37 +0000889
890 //TODO to support fiddle db upload:
891 //opfsUtil.createFile = function(absName, content=undefined){...}
892
stephanf3860122022-09-18 17:32:35 +0000893 if(sqlite3.oo1){
894 opfsUtil.OpfsDb = function(...args){
895 const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
896 opt.vfs = opfsVfs.$zName;
897 sqlite3.oo1.dbCtorHelper.call(this, opt);
898 };
899 opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
900 }
stephanf8150112022-09-19 17:09:09 +0000901
stephanf3860122022-09-18 17:32:35 +0000902 /**
903 Potential TODOs:
904
905 - Expose one or both of the Worker objects via opfsUtil and
906 publish an interface for proxying the higher-level OPFS
907 features like getting a directory listing.
908 */
stephanc5313af2022-09-18 02:35:30 +0000909
stephan5e8bb0a2022-09-20 08:27:57 +0000910 const sanityCheck = function(){
stephanc5313af2022-09-18 02:35:30 +0000911 const scope = wasm.scopedAllocPush();
912 const sq3File = new sqlite3_file();
913 try{
914 const fid = sq3File.pointer;
915 const openFlags = capi.SQLITE_OPEN_CREATE
916 | capi.SQLITE_OPEN_READWRITE
917 //| capi.SQLITE_OPEN_DELETEONCLOSE
918 | capi.SQLITE_OPEN_MAIN_DB;
919 const pOut = wasm.scopedAlloc(8);
stephanb8c8d4e2022-09-20 13:25:39 +0000920 const dbFile = "/sanity/check/file"+randomFilename(8);
stephanc5313af2022-09-18 02:35:30 +0000921 const zDbFile = wasm.scopedAllocCString(dbFile);
922 let rc;
stephane8afca32022-09-21 14:02:47 +0000923 state.s11n.serialize("This is ä string.");
924 rc = state.s11n.deserialize();
925 log("deserialize() says:",rc);
926 if("This is ä string."!==rc[0]) toss("String d13n error.");
stephanc5313af2022-09-18 02:35:30 +0000927 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
928 rc = wasm.getMemValue(pOut,'i32');
929 log("xAccess(",dbFile,") exists ?=",rc);
930 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
931 fid, openFlags, pOut);
stephanc4b87be2022-09-20 01:28:47 +0000932 log("open rc =",rc,"state.sabOPView[xOpen] =",
933 state.sabOPView[state.opIds.xOpen]);
stephane8afca32022-09-21 14:02:47 +0000934 if(0!==rc){
stephanc5313af2022-09-18 02:35:30 +0000935 error("open failed with code",rc);
936 return;
937 }
938 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
939 rc = wasm.getMemValue(pOut,'i32');
940 if(!rc) toss("xAccess() failed to detect file.");
941 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
942 if(rc) toss('sync failed w/ rc',rc);
943 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
944 if(rc) toss('truncate failed w/ rc',rc);
945 wasm.setMemValue(pOut,0,'i64');
946 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
947 if(rc) toss('xFileSize failed w/ rc',rc);
948 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
949 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
950 if(rc) toss("xWrite() failed!");
951 const readBuf = wasm.scopedAlloc(16);
952 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
953 wasm.setMemValue(readBuf+6,0);
954 let jRead = wasm.cstringToJs(readBuf);
955 log("xRead() got:",jRead);
956 if("sanity"!==jRead) toss("Unexpected xRead() value.");
stephan8766fd22022-09-19 05:19:04 +0000957 if(vfsSyncWrappers.xSleep){
958 log("xSleep()ing before close()ing...");
959 vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
960 log("waking up from xSleep()");
961 }
stephanc5313af2022-09-18 02:35:30 +0000962 rc = ioSyncWrappers.xClose(fid);
stephanc4b87be2022-09-20 01:28:47 +0000963 log("xClose rc =",rc,"sabOPView =",state.sabOPView);
stephanc5313af2022-09-18 02:35:30 +0000964 log("Deleting file:",dbFile);
965 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
966 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
967 rc = wasm.getMemValue(pOut,'i32');
968 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
stephanc9e26022022-09-20 10:11:52 +0000969 warn("End of OPFS sanity checks.");
stephanc5313af2022-09-18 02:35:30 +0000970 }finally{
971 sq3File.dispose();
972 wasm.scopedAllocPop(scope);
973 }
974 }/*sanityCheck()*/;
stephanf8150112022-09-19 17:09:09 +0000975
stephanc5313af2022-09-18 02:35:30 +0000976 W.onmessage = function({data}){
977 //log("Worker.onmessage:",data);
978 switch(data.type){
stephan138647a2022-09-20 03:31:02 +0000979 case 'opfs-async-loaded':
stephane8afca32022-09-21 14:02:47 +0000980 /*Arrives as soon as the asyc proxy finishes loading.
981 Pass our config and shared state on to the async worker.*/
stephan5e8bb0a2022-09-20 08:27:57 +0000982 W.postMessage({type: 'opfs-async-init',args: state});
stephanc5313af2022-09-18 02:35:30 +0000983 break;
stephan138647a2022-09-20 03:31:02 +0000984 case 'opfs-async-inited':{
stephane8afca32022-09-21 14:02:47 +0000985 /*Indicates that the async partner has received the 'init'
986 and has finished initializing, so the real work can
987 begin...*/
stephanc5313af2022-09-18 02:35:30 +0000988 try {
stephan0e0687c2022-09-19 13:44:23 +0000989 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
stephanc5313af2022-09-18 02:35:30 +0000990 if(rc){
stephanc5313af2022-09-18 02:35:30 +0000991 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
992 }
993 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
994 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
995 }
996 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
stephanc4b87be2022-09-20 01:28:47 +0000997 state.sabOPView = new Int32Array(state.sabOP);
stephan138647a2022-09-20 03:31:02 +0000998 state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
999 state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
1000 initS11n();
stephanc5313af2022-09-18 02:35:30 +00001001 if(options.sanityChecks){
1002 warn("Running sanity checks because of opfs-sanity-check URL arg...");
1003 sanityCheck();
1004 }
stephan1f095d42022-09-26 11:38:58 +00001005 navigator.storage.getDirectory().then((d)=>{
1006 W.onerror = W._originalOnError;
1007 delete W._originalOnError;
1008 sqlite3.opfs = opfsUtil;
1009 opfsUtil.rootDirectory = d;
1010 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
1011 promiseResolve(sqlite3);
1012 });
stephanc5313af2022-09-18 02:35:30 +00001013 }catch(e){
1014 error(e);
1015 promiseReject(e);
1016 }
1017 break;
1018 }
1019 default:
1020 promiseReject(e);
1021 error("Unexpected message from the async worker:",data);
1022 break;
1023 }
1024 };
1025 })/*thePromise*/;
1026 return thePromise;
1027}/*installOpfsVfs()*/;
stephan5b9973d2022-09-27 13:40:12 +00001028installOpfsVfs.defaultProxyUri =
1029 //self.location.pathname.replace(/[^/]*$/, "sqlite3-opfs-async-proxy.js");
1030 "sqlite3-opfs-async-proxy.js";
1031//console.warn("sqlite3.installOpfsVfs.defaultProxyUri =",sqlite3.installOpfsVfs.defaultProxyUri);
1032self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>installOpfsVfs());
stephanc5313af2022-09-18 02:35:30 +00001033}/*sqlite3ApiBootstrap.initializers.push()*/);