blob: 40e615eb86d1f24b191e451ea9f8a75590cd5fe9 [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.
stephan9a557732022-10-04 17:06:51 +0000172 error("Error initializing OPFS asyncer:",err);
stephan56fae742022-09-24 10:12:19 +0000173 promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
174 };
stephanc9e26022022-09-20 10:11:52 +0000175 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
176 const dVfs = pDVfs
177 ? new sqlite3_vfs(pDVfs)
178 : null /* dVfs will be null when sqlite3 is built with
179 SQLITE_OS_OTHER. Though we cannot currently handle
180 that case, the hope is to eventually be able to. */;
181 const opfsVfs = new sqlite3_vfs();
182 const opfsIoMethods = new sqlite3_io_methods();
183 opfsVfs.$iVersion = 2/*yes, two*/;
184 opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
185 opfsVfs.$mxPathname = 1024/*sure, why not?*/;
186 opfsVfs.$zName = wasm.allocCString("opfs");
187 // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
188 opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
189 opfsVfs.ondispose = [
190 '$zName', opfsVfs.$zName,
191 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
192 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
193 ];
194 /**
195 Pedantic sidebar about opfsVfs.ondispose: the entries in that array
196 are items to clean up when opfsVfs.dispose() is called, but in this
197 environment it will never be called. The VFS instance simply
198 hangs around until the WASM module instance is cleaned up. We
199 "could" _hypothetically_ clean it up by "importing" an
200 sqlite3_os_end() impl into the wasm build, but the shutdown order
201 of the wasm engine and the JS one are undefined so there is no
202 guaranty that the opfsVfs instance would be available in one
203 environment or the other when sqlite3_os_end() is called (_if_ it
204 gets called at all in a wasm build, which is undefined).
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;
stephan9a557732022-10-04 17:06:51 +0000227 state.littleEndian = (()=>{
228 const buffer = new ArrayBuffer(2);
229 new DataView(buffer).setInt16(0, 256, true /* littleEndian */);
230 // Int16Array uses the platform's endianness.
231 return new Int16Array(buffer)[0] === 256;
232 })();
stephan56fae742022-09-24 10:12:19 +0000233 /** Whether the async counterpart should log exceptions to
stephane8afca32022-09-21 14:02:47 +0000234 the serialization channel. That produces a great deal of
235 noise for seemingly innocuous things like xAccess() checks
stephan56fae742022-09-24 10:12:19 +0000236 for missing files, so this option may have one of 3 values:
237
238 0 = no exception logging
239
240 1 = only log exceptions for "significant" ops like xOpen(),
241 xRead(), and xWrite().
242
243 2 = log all exceptions.
244 */
245 state.asyncS11nExceptions = 1;
stephanc9e26022022-09-20 10:11:52 +0000246 /* Size of file I/O buffer block. 64k = max sqlite3 page size. */
stephanf3860122022-09-18 17:32:35 +0000247 state.fileBufferSize =
stephanc9e26022022-09-20 10:11:52 +0000248 1024 * 64;
stephan138647a2022-09-20 03:31:02 +0000249 state.sabS11nOffset = state.fileBufferSize;
stephanc9e26022022-09-20 10:11:52 +0000250 /**
251 The size of the block in our SAB for serializing arguments and
stephane8afca32022-09-21 14:02:47 +0000252 result values. Needs to be large enough to hold serialized
stephanc9e26022022-09-20 10:11:52 +0000253 values of any of the proxied APIs. Filenames are the largest
254 part but are limited to opfsVfs.$mxPathname bytes.
255 */
256 state.sabS11nSize = opfsVfs.$mxPathname * 2;
257 /**
258 The SAB used for all data I/O (files and arg/result s11n).
259 */
stephanc4b87be2022-09-20 01:28:47 +0000260 state.sabIO = new SharedArrayBuffer(
stephanc9e26022022-09-20 10:11:52 +0000261 state.fileBufferSize/* file i/o block */
262 + state.sabS11nSize/* argument/result serialization block */
stephanc4b87be2022-09-20 01:28:47 +0000263 );
stephanc5313af2022-09-18 02:35:30 +0000264 state.opIds = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000265 const metrics = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000266 {
stephanc9e26022022-09-20 10:11:52 +0000267 /* Indexes for use in our SharedArrayBuffer... */
stephan3961b262022-08-10 11:26:08 +0000268 let i = 0;
stephanc9e26022022-09-20 10:11:52 +0000269 /* SAB slot used to communicate which operation is desired
270 between both workers. This worker writes to it and the other
271 listens for changes. */
stephan138647a2022-09-20 03:31:02 +0000272 state.opIds.whichOp = i++;
stephan9a557732022-10-04 17:06:51 +0000273 /* Slot for storing return values. This worker listens to that
stephanc9e26022022-09-20 10:11:52 +0000274 slot and the other worker writes to it. */
275 state.opIds.rc = i++;
276 /* Each function gets an ID which this worker writes to
277 the whichOp slot. The async-api worker uses Atomic.wait()
278 on the whichOp slot to figure out which operation to run
279 next. */
stephanc5313af2022-09-18 02:35:30 +0000280 state.opIds.xAccess = i++;
281 state.opIds.xClose = i++;
282 state.opIds.xDelete = i++;
stephanf3860122022-09-18 17:32:35 +0000283 state.opIds.xDeleteNoWait = i++;
stephan56fae742022-09-24 10:12:19 +0000284 state.opIds.xFileControl = i++;
stephanc5313af2022-09-18 02:35:30 +0000285 state.opIds.xFileSize = i++;
stephan9a557732022-10-04 17:06:51 +0000286 state.opIds.xLock = i++;
stephanc5313af2022-09-18 02:35:30 +0000287 state.opIds.xOpen = i++;
288 state.opIds.xRead = i++;
289 state.opIds.xSleep = i++;
290 state.opIds.xSync = i++;
291 state.opIds.xTruncate = i++;
stephan9a557732022-10-04 17:06:51 +0000292 state.opIds.xUnlock = i++;
stephanc5313af2022-09-18 02:35:30 +0000293 state.opIds.xWrite = i++;
stephanf3860122022-09-18 17:32:35 +0000294 state.opIds.mkdir = i++;
stephan3c272ba2022-10-04 00:54:00 +0000295 state.opIds['opfs-async-metrics'] = i++;
296 state.opIds['opfs-async-shutdown'] = i++;
stephanc4b87be2022-09-20 01:28:47 +0000297 state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
stephanf8150112022-09-19 17:09:09 +0000298 opfsUtil.metrics.reset();
stephanc5313af2022-09-18 02:35:30 +0000299 }
stephanc9e26022022-09-20 10:11:52 +0000300 /**
301 SQLITE_xxx constants to export to the async worker
302 counterpart...
303 */
stephanc5313af2022-09-18 02:35:30 +0000304 state.sq3Codes = Object.create(null);
stephanc9e26022022-09-20 10:11:52 +0000305 [
stephanc5313af2022-09-18 02:35:30 +0000306 'SQLITE_ERROR', 'SQLITE_IOERR',
307 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
308 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
309 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
310 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
stephanf3860122022-09-18 17:32:35 +0000311 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
stephanc4b87be2022-09-20 01:28:47 +0000312 'SQLITE_IOERR_DELETE',
stephan9a557732022-10-04 17:06:51 +0000313 'SQLITE_LOCK_NONE',
314 'SQLITE_LOCK_SHARED',
315 'SQLITE_LOCK_RESERVED',
316 'SQLITE_LOCK_PENDING',
317 'SQLITE_LOCK_EXCLUSIVE',
stephanc4b87be2022-09-20 01:28:47 +0000318 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE',
319 'SQLITE_OPEN_READONLY'
stephan9a557732022-10-04 17:06:51 +0000320 ].forEach((k)=>{
321 if(undefined === (state.sq3Codes[k] = capi[k])){
322 toss("Maintenance required: not found:",k);
323 }
stephan3961b262022-08-10 11:26:08 +0000324 });
stephan3961b262022-08-10 11:26:08 +0000325
stephanc5313af2022-09-18 02:35:30 +0000326 /**
stephanc9e26022022-09-20 10:11:52 +0000327 Runs the given operation (by name) in the async worker
328 counterpart, waits for its response, and returns the result
329 which the async worker writes to SAB[state.opIds.rc]. The
330 2nd and subsequent arguments must be the aruguments for the
331 async op.
stephanc5313af2022-09-18 02:35:30 +0000332 */
stephan138647a2022-09-20 03:31:02 +0000333 const opRun = (op,...args)=>{
stephan5e8bb0a2022-09-20 08:27:57 +0000334 const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
335 state.s11n.serialize(...args);
stephanc9e26022022-09-20 10:11:52 +0000336 Atomics.store(state.sabOPView, state.opIds.rc, -1);
stephan5e8bb0a2022-09-20 08:27:57 +0000337 Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
338 Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
stephanf8150112022-09-19 17:09:09 +0000339 const t = performance.now();
stephanc9e26022022-09-20 10:11:52 +0000340 Atomics.wait(state.sabOPView, state.opIds.rc, -1);
341 const rc = Atomics.load(state.sabOPView, state.opIds.rc);
stephane8afca32022-09-21 14:02:47 +0000342 metrics[op].wait += performance.now() - t;
343 if(rc && state.asyncS11nExceptions){
stephan72ab4002022-09-21 12:27:35 +0000344 const err = state.s11n.deserialize();
345 if(err) error(op+"() async error:",...err);
346 }
stephan5e8bb0a2022-09-20 08:27:57 +0000347 return rc;
stephanc5313af2022-09-18 02:35:30 +0000348 };
349
stephan138647a2022-09-20 03:31:02 +0000350 const initS11n = ()=>{
stephanb8c8d4e2022-09-20 13:25:39 +0000351 /**
stephan72ab4002022-09-21 12:27:35 +0000352 ACHTUNG: this code is 100% duplicated in the other half of this
353 proxy! The documentation is maintained in the "synchronous half".
stephanb8c8d4e2022-09-20 13:25:39 +0000354
stephan72ab4002022-09-21 12:27:35 +0000355 This proxy de/serializes cross-thread function arguments and
356 output-pointer values via the state.sabIO SharedArrayBuffer,
357 using the region defined by (state.sabS11nOffset,
358 state.sabS11nOffset]. Only one dataset is recorded at a time.
359
360 This is not a general-purpose format. It only supports the range
361 of operations, and data sizes, needed by the sqlite3_vfs and
362 sqlite3_io_methods operations.
363
364 The data format can be succinctly summarized as:
365
366 Nt...Td...D
367
368 Where:
369
370 - N = number of entries (1 byte)
371
372 - t = type ID of first argument (1 byte)
373
374 - ...T = type IDs of the 2nd and subsequent arguments (1 byte
375 each).
376
377 - d = raw bytes of first argument (per-type size).
378
379 - ...D = raw bytes of the 2nd and subsequent arguments (per-type
380 size).
381
382 All types except strings have fixed sizes. Strings are stored
383 using their TextEncoder/TextDecoder representations. It would
384 arguably make more sense to store them as Int16Arrays of
385 their JS character values, but how best/fastest to get that
386 in and out of string form us an open point.
387
388 Historical note: this impl was initially about 1% this size by
389 using using JSON.stringify/parse(), but using fit-to-purpose
390 serialization saves considerable runtime.
stephanb8c8d4e2022-09-20 13:25:39 +0000391 */
stephan138647a2022-09-20 03:31:02 +0000392 if(state.s11n) return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000393 const textDecoder = new TextDecoder(),
stephan72ab4002022-09-21 12:27:35 +0000394 textEncoder = new TextEncoder('utf-8'),
395 viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
396 viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
stephan138647a2022-09-20 03:31:02 +0000397 state.s11n = Object.create(null);
stephan72ab4002022-09-21 12:27:35 +0000398 /* Only arguments and return values of these types may be
399 serialized. This covers the whole range of types needed by the
400 sqlite3_vfs API. */
stephanb8c8d4e2022-09-20 13:25:39 +0000401 const TypeIds = Object.create(null);
402 TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
403 TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
404 TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
405 TypeIds.string = { id: 4 };
stephan72ab4002022-09-21 12:27:35 +0000406
407 const getTypeId = (v)=>(
408 TypeIds[typeof v]
409 || toss("Maintenance required: this value type cannot be serialized.",v)
410 );
stephanb8c8d4e2022-09-20 13:25:39 +0000411 const getTypeIdById = (tid)=>{
412 switch(tid){
stephan72ab4002022-09-21 12:27:35 +0000413 case TypeIds.number.id: return TypeIds.number;
414 case TypeIds.bigint.id: return TypeIds.bigint;
415 case TypeIds.boolean.id: return TypeIds.boolean;
416 case TypeIds.string.id: return TypeIds.string;
417 default: toss("Invalid type ID:",tid);
stephanb8c8d4e2022-09-20 13:25:39 +0000418 }
419 };
stephan72ab4002022-09-21 12:27:35 +0000420
stephan138647a2022-09-20 03:31:02 +0000421 /**
stephan72ab4002022-09-21 12:27:35 +0000422 Returns an array of the deserialized state stored by the most
423 recent serialize() operation (from from this thread or the
424 counterpart thread), or null if the serialization buffer is empty.
stephan138647a2022-09-20 03:31:02 +0000425 */
426 state.s11n.deserialize = function(){
stephanb8c8d4e2022-09-20 13:25:39 +0000427 ++metrics.s11n.deserialize.count;
428 const t = performance.now();
stephanb8c8d4e2022-09-20 13:25:39 +0000429 const argc = viewU8[0];
stephan72ab4002022-09-21 12:27:35 +0000430 const rc = argc ? [] : null;
stephanb8c8d4e2022-09-20 13:25:39 +0000431 if(argc){
stephan72ab4002022-09-21 12:27:35 +0000432 const typeIds = [];
433 let offset = 1, i, n, v;
stephanb8c8d4e2022-09-20 13:25:39 +0000434 for(i = 0; i < argc; ++i, ++offset){
435 typeIds.push(getTypeIdById(viewU8[offset]));
436 }
437 for(i = 0; i < argc; ++i){
438 const t = typeIds[i];
439 if(t.getter){
440 v = viewDV[t.getter](offset, state.littleEndian);
441 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000442 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000443 n = viewDV.getInt32(offset, state.littleEndian);
444 offset += 4;
445 v = textDecoder.decode(viewU8.slice(offset, offset+n));
446 offset += n;
447 }
448 rc.push(v);
449 }
450 }
451 //log("deserialize:",argc, rc);
452 metrics.s11n.deserialize.time += performance.now() - t;
453 return rc;
454 };
stephan72ab4002022-09-21 12:27:35 +0000455
stephan138647a2022-09-20 03:31:02 +0000456 /**
457 Serializes all arguments to the shared buffer for consumption
stephanb8c8d4e2022-09-20 13:25:39 +0000458 by the counterpart thread.
stephan5e8bb0a2022-09-20 08:27:57 +0000459
stephanb8c8d4e2022-09-20 13:25:39 +0000460 This routine is only intended for serializing OPFS VFS
461 arguments and (in at least one special case) result values,
462 and the buffer is sized to be able to comfortably handle
463 those.
stephan5e8bb0a2022-09-20 08:27:57 +0000464
465 If passed no arguments then it zeroes out the serialization
466 state.
stephan138647a2022-09-20 03:31:02 +0000467 */
468 state.s11n.serialize = function(...args){
stephanb8c8d4e2022-09-20 13:25:39 +0000469 const t = performance.now();
stephan72ab4002022-09-21 12:27:35 +0000470 ++metrics.s11n.serialize.count;
stephan5e8bb0a2022-09-20 08:27:57 +0000471 if(args.length){
stephanb8c8d4e2022-09-20 13:25:39 +0000472 //log("serialize():",args);
stephan72ab4002022-09-21 12:27:35 +0000473 const typeIds = [];
474 let i = 0, offset = 1;
475 viewU8[0] = args.length & 0xff /* header = # of args */;
stephanb8c8d4e2022-09-20 13:25:39 +0000476 for(; i < args.length; ++i, ++offset){
stephan72ab4002022-09-21 12:27:35 +0000477 /* Write the TypeIds.id value into the next args.length
478 bytes. */
stephanb8c8d4e2022-09-20 13:25:39 +0000479 typeIds.push(getTypeId(args[i]));
480 viewU8[offset] = typeIds[i].id;
481 }
482 for(i = 0; i < args.length; ++i) {
stephan72ab4002022-09-21 12:27:35 +0000483 /* Deserialize the following bytes based on their
484 corresponding TypeIds.id from the header. */
stephanb8c8d4e2022-09-20 13:25:39 +0000485 const t = typeIds[i];
486 if(t.setter){
487 viewDV[t.setter](offset, args[i], state.littleEndian);
488 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000489 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000490 const s = textEncoder.encode(args[i]);
491 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
492 offset += 4;
493 viewU8.set(s, offset);
494 offset += s.byteLength;
495 }
496 }
497 //log("serialize() result:",viewU8.slice(0,offset));
stephan5e8bb0a2022-09-20 08:27:57 +0000498 }else{
stephanb8c8d4e2022-09-20 13:25:39 +0000499 viewU8[0] = 0;
stephan5e8bb0a2022-09-20 08:27:57 +0000500 }
stephanb8c8d4e2022-09-20 13:25:39 +0000501 metrics.s11n.serialize.time += performance.now() - t;
stephan138647a2022-09-20 03:31:02 +0000502 };
503 return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000504 }/*initS11n()*/;
stephan138647a2022-09-20 03:31:02 +0000505
stephanc5313af2022-09-18 02:35:30 +0000506 /**
507 Generates a random ASCII string len characters long, intended for
508 use as a temporary file name.
509 */
510 const randomFilename = function f(len=16){
511 if(!f._chars){
512 f._chars = "abcdefghijklmnopqrstuvwxyz"+
513 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
514 "012346789";
515 f._n = f._chars.length;
516 }
517 const a = [];
518 let i = 0;
519 for( ; i < len; ++i){
520 const ndx = Math.random() * (f._n * 64) % f._n | 0;
521 a[i] = f._chars[ndx];
522 }
523 return a.join('');
524 };
525
526 /**
527 Map of sqlite3_file pointers to objects constructed by xOpen().
528 */
529 const __openFiles = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000530
531 /**
532 Installs a StructBinder-bound function pointer member of the
533 given name and function in the given StructType target object.
534 It creates a WASM proxy for the given function and arranges for
535 that proxy to be cleaned up when tgt.dispose() is called. Throws
536 on the slightest hint of error (e.g. tgt is-not-a StructType,
537 name does not map to a struct-bound member, etc.).
538
539 Returns a proxy for this function which is bound to tgt and takes
540 2 args (name,func). That function returns the same thing,
541 permitting calls to be chained.
542
543 If called with only 1 arg, it has no side effects but returns a
544 func with the same signature as described above.
545 */
546 const installMethod = function callee(tgt, name, func){
stephanf3860122022-09-18 17:32:35 +0000547 if(!(tgt instanceof sqlite3.StructBinder.StructType)){
stephanc5313af2022-09-18 02:35:30 +0000548 toss("Usage error: target object is-not-a StructType.");
549 }
550 if(1===arguments.length){
551 return (n,f)=>callee(tgt,n,f);
552 }
553 if(!callee.argcProxy){
554 callee.argcProxy = function(func,sig){
555 return function(...args){
556 if(func.length!==arguments.length){
557 toss("Argument mismatch. Native signature is:",sig);
558 }
559 return func.apply(this, args);
560 }
561 };
562 callee.removeFuncList = function(){
563 if(this.ondispose.__removeFuncList){
564 this.ondispose.__removeFuncList.forEach(
565 (v,ndx)=>{
566 if('number'===typeof v){
567 try{wasm.uninstallFunction(v)}
568 catch(e){/*ignore*/}
569 }
570 /* else it's a descriptive label for the next number in
571 the list. */
572 }
573 );
574 delete this.ondispose.__removeFuncList;
575 }
576 };
577 }/*static init*/
578 const sigN = tgt.memberSignature(name);
579 if(sigN.length<2){
580 toss("Member",name," is not a function pointer. Signature =",sigN);
581 }
582 const memKey = tgt.memberKey(name);
583 //log("installMethod",tgt, name, sigN);
stephanc2ccd672022-09-20 10:47:36 +0000584 const fProxy = 0
stephanc5313af2022-09-18 02:35:30 +0000585 // We can remove this proxy middle-man once the VFS is working
586 ? callee.argcProxy(func, sigN)
587 : func;
588 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
589 tgt[memKey] = pFunc;
590 if(!tgt.ondispose) tgt.ondispose = [];
591 if(!tgt.ondispose.__removeFuncList){
592 tgt.ondispose.push('ondispose.__removeFuncList handler',
593 callee.removeFuncList);
594 tgt.ondispose.__removeFuncList = [];
595 }
596 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
597 return (n,f)=>callee(tgt, n, f);
598 }/*installMethod*/;
stephanf8150112022-09-19 17:09:09 +0000599
600 const opTimer = Object.create(null);
601 opTimer.op = undefined;
602 opTimer.start = undefined;
603 const mTimeStart = (op)=>{
604 opTimer.start = performance.now();
605 opTimer.op = op;
606 //metrics[op] || toss("Maintenance required: missing metrics for",op);
607 ++metrics[op].count;
608 };
609 const mTimeEnd = ()=>(
610 metrics[opTimer.op].time += performance.now() - opTimer.start
611 );
612
stephanc5313af2022-09-18 02:35:30 +0000613 /**
614 Impls for the sqlite3_io_methods methods. Maintenance reminder:
615 members are in alphabetical order to simplify finding them.
616 */
617 const ioSyncWrappers = {
618 xCheckReservedLock: function(pFile,pOut){
619 // Exclusive lock is automatically acquired when opened
620 //warn("xCheckReservedLock(",arguments,") is a no-op");
stephan9a557732022-10-04 17:06:51 +0000621 const f = __openFiles[pFile];
622 wasm.setMemValue(pOut, f.lockMode ? 1 : 0, 'i32');
stephanc5313af2022-09-18 02:35:30 +0000623 return 0;
624 },
625 xClose: function(pFile){
stephanf8150112022-09-19 17:09:09 +0000626 mTimeStart('xClose');
stephanc5313af2022-09-18 02:35:30 +0000627 let rc = 0;
628 const f = __openFiles[pFile];
629 if(f){
630 delete __openFiles[pFile];
631 rc = opRun('xClose', pFile);
632 if(f.sq3File) f.sq3File.dispose();
633 }
stephanf8150112022-09-19 17:09:09 +0000634 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000635 return rc;
636 },
637 xDeviceCharacteristics: function(pFile){
638 //debug("xDeviceCharacteristics(",pFile,")");
639 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
640 },
stephanf8150112022-09-19 17:09:09 +0000641 xFileControl: function(pFile, opId, pArg){
642 mTimeStart('xFileControl');
stephanaec046a2022-09-19 18:22:29 +0000643 const rc = (capi.SQLITE_FCNTL_SYNC===opId)
stephan138647a2022-09-20 03:31:02 +0000644 ? opRun('xSync', pFile, 0)
stephanaec046a2022-09-19 18:22:29 +0000645 : capi.SQLITE_NOTFOUND;
stephanf8150112022-09-19 17:09:09 +0000646 mTimeEnd();
stephanaec046a2022-09-19 18:22:29 +0000647 return rc;
stephanc5313af2022-09-18 02:35:30 +0000648 },
649 xFileSize: function(pFile,pSz64){
stephanf8150112022-09-19 17:09:09 +0000650 mTimeStart('xFileSize');
stephanc5313af2022-09-18 02:35:30 +0000651 const rc = opRun('xFileSize', pFile);
stephane8afca32022-09-21 14:02:47 +0000652 if(0==rc){
stephan138647a2022-09-20 03:31:02 +0000653 const sz = state.s11n.deserialize()[0];
stephan278d3fa2022-09-26 13:55:10 +0000654 wasm.setMemValue(pSz64, sz, 'i64');
stephanc5313af2022-09-18 02:35:30 +0000655 }
stephanf8150112022-09-19 17:09:09 +0000656 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000657 return rc;
658 },
659 xLock: function(pFile,lockType){
stephan9a557732022-10-04 17:06:51 +0000660 mTimeStart('xLock');
661 const f = __openFiles[pFile];
662 let rc = 0;
663 if( capi.SQLITE_LOCK_NONE === f.lockType ) {
664 rc = opRun('xLock', pFile, lockType);
665 if( 0===rc ) f.lockType = lockType;
666 }else{
667 f.lockType = lockType;
668 }
669 mTimeEnd();
670 return rc;
stephanc5313af2022-09-18 02:35:30 +0000671 },
stephan138647a2022-09-20 03:31:02 +0000672 xRead: function(pFile,pDest,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000673 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000674 mTimeStart('xRead');
stephanc5313af2022-09-18 02:35:30 +0000675 const f = __openFiles[pFile];
676 let rc;
677 try {
stephan138647a2022-09-20 03:31:02 +0000678 rc = opRun('xRead',pFile, n, Number(offset64));
stephan862281f2022-09-19 09:25:25 +0000679 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
stephanf8150112022-09-19 17:09:09 +0000680 // set() seems to be the fastest way to copy this...
681 wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
stephan862281f2022-09-19 09:25:25 +0000682 }
stephanc5313af2022-09-18 02:35:30 +0000683 }catch(e){
684 error("xRead(",arguments,") failed:",e,f);
685 rc = capi.SQLITE_IOERR_READ;
686 }
stephanf8150112022-09-19 17:09:09 +0000687 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000688 return rc;
689 },
690 xSync: function(pFile,flags){
stephanaec046a2022-09-19 18:22:29 +0000691 ++metrics.xSync.count;
stephan138647a2022-09-20 03:31:02 +0000692 return 0; // impl'd in xFileControl()
stephanc5313af2022-09-18 02:35:30 +0000693 },
694 xTruncate: function(pFile,sz64){
stephanf8150112022-09-19 17:09:09 +0000695 mTimeStart('xTruncate');
stephan138647a2022-09-20 03:31:02 +0000696 const rc = opRun('xTruncate', pFile, Number(sz64));
stephanf8150112022-09-19 17:09:09 +0000697 mTimeEnd();
698 return rc;
stephanc5313af2022-09-18 02:35:30 +0000699 },
700 xUnlock: function(pFile,lockType){
stephan9a557732022-10-04 17:06:51 +0000701 mTimeStart('xUnlock');
702 const f = __openFiles[pFile];
703 let rc = 0;
704 if( capi.SQLITE_LOCK_NONE === lockType
705 && f.lockType ){
706 rc = opRun('xUnlock', pFile, lockType);
707 }
708 if( 0===rc ) f.lockType = lockType;
709 mTimeEnd();
710 return rc;
stephanc5313af2022-09-18 02:35:30 +0000711 },
stephan138647a2022-09-20 03:31:02 +0000712 xWrite: function(pFile,pSrc,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000713 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000714 mTimeStart('xWrite');
stephanc5313af2022-09-18 02:35:30 +0000715 const f = __openFiles[pFile];
stephanf8150112022-09-19 17:09:09 +0000716 let rc;
stephanc5313af2022-09-18 02:35:30 +0000717 try {
stephanf8150112022-09-19 17:09:09 +0000718 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
stephan138647a2022-09-20 03:31:02 +0000719 rc = opRun('xWrite', pFile, n, Number(offset64));
stephanc5313af2022-09-18 02:35:30 +0000720 }catch(e){
721 error("xWrite(",arguments,") failed:",e,f);
stephanf8150112022-09-19 17:09:09 +0000722 rc = capi.SQLITE_IOERR_WRITE;
stephanc5313af2022-09-18 02:35:30 +0000723 }
stephanf8150112022-09-19 17:09:09 +0000724 mTimeEnd();
725 return rc;
stephanc5313af2022-09-18 02:35:30 +0000726 }
727 }/*ioSyncWrappers*/;
stephan9a557732022-10-04 17:06:51 +0000728
stephanc5313af2022-09-18 02:35:30 +0000729 /**
730 Impls for the sqlite3_vfs methods. Maintenance reminder: members
731 are in alphabetical order to simplify finding them.
732 */
733 const vfsSyncWrappers = {
734 xAccess: function(pVfs,zName,flags,pOut){
stephanf8150112022-09-19 17:09:09 +0000735 mTimeStart('xAccess');
stephan5e8bb0a2022-09-20 08:27:57 +0000736 const rc = opRun('xAccess', wasm.cstringToJs(zName));
737 wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
stephanf8150112022-09-19 17:09:09 +0000738 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000739 return 0;
740 },
741 xCurrentTime: function(pVfs,pOut){
742 /* If it turns out that we need to adjust for timezone, see:
743 https://stackoverflow.com/a/11760121/1458521 */
744 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
745 'double');
746 return 0;
747 },
748 xCurrentTimeInt64: function(pVfs,pOut){
749 // TODO: confirm that this calculation is correct
750 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
751 'i64');
752 return 0;
753 },
754 xDelete: function(pVfs, zName, doSyncDir){
stephanf8150112022-09-19 17:09:09 +0000755 mTimeStart('xDelete');
stephan138647a2022-09-20 03:31:02 +0000756 opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
stephanf3860122022-09-18 17:32:35 +0000757 /* We're ignoring errors because we cannot yet differentiate
758 between harmless and non-harmless failures. */
stephanf8150112022-09-19 17:09:09 +0000759 mTimeEnd();
stephanf3860122022-09-18 17:32:35 +0000760 return 0;
stephanc5313af2022-09-18 02:35:30 +0000761 },
762 xFullPathname: function(pVfs,zName,nOut,pOut){
763 /* Until/unless we have some notion of "current dir"
764 in OPFS, simply copy zName to pOut... */
765 const i = wasm.cstrncpy(pOut, zName, nOut);
766 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
767 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
768 },
769 xGetLastError: function(pVfs,nOut,pOut){
770 /* TODO: store exception.message values from the async
771 partner in a dedicated SharedArrayBuffer, noting that we'd have
772 to encode them... TextEncoder can do that for us. */
773 warn("OPFS xGetLastError() has nothing sensible to return.");
774 return 0;
775 },
stephan8766fd22022-09-19 05:19:04 +0000776 //xSleep is optionally defined below
stephanc5313af2022-09-18 02:35:30 +0000777 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
stephanf8150112022-09-19 17:09:09 +0000778 mTimeStart('xOpen');
stephanc5313af2022-09-18 02:35:30 +0000779 if(!f._){
780 f._ = {
781 fileTypes: {
782 SQLITE_OPEN_MAIN_DB: 'mainDb',
783 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
784 SQLITE_OPEN_TEMP_DB: 'tempDb',
785 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
786 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
787 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
788 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
789 SQLITE_OPEN_WAL: 'wal'
790 },
791 getFileType: function(filename,oflags){
792 const ft = f._.fileTypes;
793 for(let k of Object.keys(ft)){
794 if(oflags & capi[k]) return ft[k];
795 }
796 warn("Cannot determine fileType based on xOpen() flags for file",filename);
797 return '???';
798 }
799 };
800 }
801 if(0===zName){
802 zName = randomFilename();
803 }else if('number'===typeof zName){
804 zName = wasm.cstringToJs(zName);
805 }
stephan138647a2022-09-20 03:31:02 +0000806 const fh = Object.create(null);
807 fh.fid = pFile;
808 fh.filename = zName;
809 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
810 fh.flags = flags;
811 const rc = opRun('xOpen', pFile, zName, flags);
stephanc5313af2022-09-18 02:35:30 +0000812 if(!rc){
813 /* Recall that sqlite3_vfs::xClose() will be called, even on
814 error, unless pFile->pMethods is NULL. */
stephan138647a2022-09-20 03:31:02 +0000815 if(fh.readOnly){
stephanc5313af2022-09-18 02:35:30 +0000816 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
817 }
stephan138647a2022-09-20 03:31:02 +0000818 __openFiles[pFile] = fh;
819 fh.sabView = state.sabFileBufView;
820 fh.sq3File = new sqlite3_file(pFile);
821 fh.sq3File.$pMethods = opfsIoMethods.pointer;
stephan9a557732022-10-04 17:06:51 +0000822 fh.lockType = capi.SQLITE_LOCK_NONE;
stephanc5313af2022-09-18 02:35:30 +0000823 }
stephanf8150112022-09-19 17:09:09 +0000824 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000825 return rc;
826 }/*xOpen()*/
827 }/*vfsSyncWrappers*/;
828
stephan8766fd22022-09-19 05:19:04 +0000829 if(dVfs){
830 opfsVfs.$xRandomness = dVfs.$xRandomness;
831 opfsVfs.$xSleep = dVfs.$xSleep;
832 }
stephanc5313af2022-09-18 02:35:30 +0000833 if(!opfsVfs.$xRandomness){
834 /* If the default VFS has no xRandomness(), add a basic JS impl... */
835 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
836 const heap = wasm.heap8u();
837 let i = 0;
838 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
839 return i;
840 };
841 }
842 if(!opfsVfs.$xSleep){
843 /* If we can inherit an xSleep() impl from the default VFS then
stephan8766fd22022-09-19 05:19:04 +0000844 assume it's sane and use it, otherwise install a JS-based
845 one. */
846 vfsSyncWrappers.xSleep = function(pVfs,ms){
stephanc4b87be2022-09-20 01:28:47 +0000847 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
stephan8766fd22022-09-19 05:19:04 +0000848 return 0;
849 };
stephanc5313af2022-09-18 02:35:30 +0000850 }
851
852 /* Install the vfs/io_methods into their C-level shared instances... */
stephan278d3fa2022-09-26 13:55:10 +0000853 for(let k of Object.keys(ioSyncWrappers)){
854 installMethod(opfsIoMethods, k, ioSyncWrappers[k]);
855 }
856 for(let k of Object.keys(vfsSyncWrappers)){
857 installMethod(opfsVfs, k, vfsSyncWrappers[k]);
858 }
stephanf3860122022-09-18 17:32:35 +0000859
stephanf3860122022-09-18 17:32:35 +0000860 /**
861 Syncronously deletes the given OPFS filesystem entry, ignoring
862 any errors. As this environment has no notion of "current
863 directory", the given name must be an absolute path. If the 2nd
864 argument is truthy, deletion is recursive (use with caution!).
865
stephan278d3fa2022-09-26 13:55:10 +0000866 Returns true if the deletion succeeded and false if it fails,
stephanf3860122022-09-18 17:32:35 +0000867 but cannot report the nature of the failure.
868 */
stephan0e0687c2022-09-19 13:44:23 +0000869 opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
stephan72ab4002022-09-21 12:27:35 +0000870 mTimeStart('xDelete');
871 const rc = opRun('xDelete', fsEntryName, 0, recursive);
872 mTimeEnd();
873 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000874 };
875 /**
stephanf3860122022-09-18 17:32:35 +0000876 Synchronously creates the given directory name, recursively, in
877 the OPFS filesystem. Returns true if it succeeds or the
878 directory already exists, else false.
879 */
stephan5e8bb0a2022-09-20 08:27:57 +0000880 opfsUtil.mkdir = function(absDirName){
stephan72ab4002022-09-21 12:27:35 +0000881 mTimeStart('mkdir');
882 const rc = opRun('mkdir', absDirName);
883 mTimeEnd();
884 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000885 };
886 /**
887 Synchronously checks whether the given OPFS filesystem exists,
888 returning true if it does, false if it doesn't.
889 */
890 opfsUtil.entryExists = function(fsEntryName){
891 return 0===opRun('xAccess', fsEntryName);
892 };
893
894 /**
895 Generates a random ASCII string, intended for use as a
896 temporary file name. Its argument is the length of the string,
897 defaulting to 16.
898 */
899 opfsUtil.randomFilename = randomFilename;
stephan56fae742022-09-24 10:12:19 +0000900
901 /**
902 Re-registers the OPFS VFS. This is intended only for odd use
903 cases which have to call sqlite3_shutdown() as part of their
904 initialization process, which will unregister the VFS
905 registered by installOpfsVfs(). If passed a truthy value, the
906 OPFS VFS is registered as the default VFS, else it is not made
907 the default. Returns the result of the the
908 sqlite3_vfs_register() call.
909
910 Design note: the problem of having to re-register things after
911 a shutdown/initialize pair is more general. How to best plug
912 that in to the library is unclear. In particular, we cannot
913 hook in to any C-side calls to sqlite3_initialize(), so we
914 cannot add an after-initialize callback mechanism.
915 */
stephan3d645482022-09-27 09:17:37 +0000916 opfsUtil.registerVfs = (asDefault=false)=>{
stephan56fae742022-09-24 10:12:19 +0000917 return capi.wasm.exports.sqlite3_vfs_register(
918 opfsVfs.pointer, asDefault ? 1 : 0
919 );
920 };
stephan3d645482022-09-27 09:17:37 +0000921
stephan3c272ba2022-10-04 00:54:00 +0000922 /**
923 Only for test/development use.
924 */
925 opfsUtil.debug = {
926 asyncShutdown: ()=>{
927 warn("Shutting down OPFS async listener. OPFS will no longer work.");
928 opRun('opfs-async-shutdown');
929 },
930 asyncRestart: ()=>{
931 warn("Attempting to restart OPFS async listener. Might work, might not.");
932 W.postMessage({type: 'opfs-async-restart'});
933 }
934 };
935
stephan3d645482022-09-27 09:17:37 +0000936 //TODO to support fiddle db upload:
937 //opfsUtil.createFile = function(absName, content=undefined){...}
938
stephanf3860122022-09-18 17:32:35 +0000939 if(sqlite3.oo1){
940 opfsUtil.OpfsDb = function(...args){
941 const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
942 opt.vfs = opfsVfs.$zName;
943 sqlite3.oo1.dbCtorHelper.call(this, opt);
944 };
945 opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
stephan4f5bbed2022-10-03 13:03:41 +0000946 sqlite3.oo1.dbCtorHelper.setVfsPostOpenSql(
947 opfsVfs.pointer,
stephanc7fb48d2022-10-04 09:12:05 +0000948 [
949 /* Truncate journal mode is faster than delete or wal for
950 this vfs, per speedtest1. */
stephaned3182f2022-10-04 11:14:23 +0000951 "pragma journal_mode=truncate;"
stephanc7fb48d2022-10-04 09:12:05 +0000952 /*
953 This vfs benefits hugely from cache on moderate/large
954 speedtest1 --size 50 and --size 100 workloads. We currently
955 rely on setting a non-default cache size when building
956 sqlite3.wasm. If that policy changes, the cache can
957 be set here.
958 */
959 //"pragma cache_size=-8388608;"
960 ].join('')
stephan4f5bbed2022-10-03 13:03:41 +0000961 );
stephanf3860122022-09-18 17:32:35 +0000962 }
stephan4f5bbed2022-10-03 13:03:41 +0000963
stephanf3860122022-09-18 17:32:35 +0000964 /**
965 Potential TODOs:
966
967 - Expose one or both of the Worker objects via opfsUtil and
968 publish an interface for proxying the higher-level OPFS
969 features like getting a directory listing.
970 */
stephan5e8bb0a2022-09-20 08:27:57 +0000971 const sanityCheck = function(){
stephanc5313af2022-09-18 02:35:30 +0000972 const scope = wasm.scopedAllocPush();
973 const sq3File = new sqlite3_file();
974 try{
975 const fid = sq3File.pointer;
976 const openFlags = capi.SQLITE_OPEN_CREATE
977 | capi.SQLITE_OPEN_READWRITE
978 //| capi.SQLITE_OPEN_DELETEONCLOSE
979 | capi.SQLITE_OPEN_MAIN_DB;
980 const pOut = wasm.scopedAlloc(8);
stephanb8c8d4e2022-09-20 13:25:39 +0000981 const dbFile = "/sanity/check/file"+randomFilename(8);
stephanc5313af2022-09-18 02:35:30 +0000982 const zDbFile = wasm.scopedAllocCString(dbFile);
983 let rc;
stephane8afca32022-09-21 14:02:47 +0000984 state.s11n.serialize("This is ä string.");
985 rc = state.s11n.deserialize();
986 log("deserialize() says:",rc);
987 if("This is ä string."!==rc[0]) toss("String d13n error.");
stephanc5313af2022-09-18 02:35:30 +0000988 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
989 rc = wasm.getMemValue(pOut,'i32');
990 log("xAccess(",dbFile,") exists ?=",rc);
991 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
992 fid, openFlags, pOut);
stephanc4b87be2022-09-20 01:28:47 +0000993 log("open rc =",rc,"state.sabOPView[xOpen] =",
994 state.sabOPView[state.opIds.xOpen]);
stephane8afca32022-09-21 14:02:47 +0000995 if(0!==rc){
stephanc5313af2022-09-18 02:35:30 +0000996 error("open failed with code",rc);
997 return;
998 }
999 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1000 rc = wasm.getMemValue(pOut,'i32');
1001 if(!rc) toss("xAccess() failed to detect file.");
1002 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
1003 if(rc) toss('sync failed w/ rc',rc);
1004 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
1005 if(rc) toss('truncate failed w/ rc',rc);
1006 wasm.setMemValue(pOut,0,'i64');
1007 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
1008 if(rc) toss('xFileSize failed w/ rc',rc);
1009 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
1010 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
1011 if(rc) toss("xWrite() failed!");
1012 const readBuf = wasm.scopedAlloc(16);
1013 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
1014 wasm.setMemValue(readBuf+6,0);
1015 let jRead = wasm.cstringToJs(readBuf);
1016 log("xRead() got:",jRead);
1017 if("sanity"!==jRead) toss("Unexpected xRead() value.");
stephan8766fd22022-09-19 05:19:04 +00001018 if(vfsSyncWrappers.xSleep){
1019 log("xSleep()ing before close()ing...");
1020 vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
1021 log("waking up from xSleep()");
1022 }
stephanc5313af2022-09-18 02:35:30 +00001023 rc = ioSyncWrappers.xClose(fid);
stephanc4b87be2022-09-20 01:28:47 +00001024 log("xClose rc =",rc,"sabOPView =",state.sabOPView);
stephanc5313af2022-09-18 02:35:30 +00001025 log("Deleting file:",dbFile);
1026 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
1027 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
1028 rc = wasm.getMemValue(pOut,'i32');
1029 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
stephanc9e26022022-09-20 10:11:52 +00001030 warn("End of OPFS sanity checks.");
stephanc5313af2022-09-18 02:35:30 +00001031 }finally{
1032 sq3File.dispose();
1033 wasm.scopedAllocPop(scope);
1034 }
1035 }/*sanityCheck()*/;
stephan6559e0a2022-09-27 14:31:34 +00001036
stephanc5313af2022-09-18 02:35:30 +00001037 W.onmessage = function({data}){
1038 //log("Worker.onmessage:",data);
1039 switch(data.type){
stephan138647a2022-09-20 03:31:02 +00001040 case 'opfs-async-loaded':
stephane8afca32022-09-21 14:02:47 +00001041 /*Arrives as soon as the asyc proxy finishes loading.
1042 Pass our config and shared state on to the async worker.*/
stephan5e8bb0a2022-09-20 08:27:57 +00001043 W.postMessage({type: 'opfs-async-init',args: state});
stephanc5313af2022-09-18 02:35:30 +00001044 break;
stephan138647a2022-09-20 03:31:02 +00001045 case 'opfs-async-inited':{
stephane8afca32022-09-21 14:02:47 +00001046 /*Indicates that the async partner has received the 'init'
1047 and has finished initializing, so the real work can
1048 begin...*/
stephanc5313af2022-09-18 02:35:30 +00001049 try {
stephan0e0687c2022-09-19 13:44:23 +00001050 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
stephanc5313af2022-09-18 02:35:30 +00001051 if(rc){
stephanc5313af2022-09-18 02:35:30 +00001052 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
1053 }
1054 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
1055 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
1056 }
1057 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
stephanc4b87be2022-09-20 01:28:47 +00001058 state.sabOPView = new Int32Array(state.sabOP);
stephan138647a2022-09-20 03:31:02 +00001059 state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
1060 state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
1061 initS11n();
stephanc5313af2022-09-18 02:35:30 +00001062 if(options.sanityChecks){
1063 warn("Running sanity checks because of opfs-sanity-check URL arg...");
1064 sanityCheck();
1065 }
stephan1f095d42022-09-26 11:38:58 +00001066 navigator.storage.getDirectory().then((d)=>{
1067 W.onerror = W._originalOnError;
1068 delete W._originalOnError;
1069 sqlite3.opfs = opfsUtil;
1070 opfsUtil.rootDirectory = d;
1071 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
1072 promiseResolve(sqlite3);
1073 });
stephanc5313af2022-09-18 02:35:30 +00001074 }catch(e){
1075 error(e);
1076 promiseReject(e);
1077 }
1078 break;
1079 }
1080 default:
1081 promiseReject(e);
1082 error("Unexpected message from the async worker:",data);
1083 break;
1084 }
1085 };
stephan3c272ba2022-10-04 00:54:00 +00001086
stephanc5313af2022-09-18 02:35:30 +00001087 })/*thePromise*/;
1088 return thePromise;
1089}/*installOpfsVfs()*/;
stephan5b9973d2022-09-27 13:40:12 +00001090installOpfsVfs.defaultProxyUri =
1091 //self.location.pathname.replace(/[^/]*$/, "sqlite3-opfs-async-proxy.js");
1092 "sqlite3-opfs-async-proxy.js";
1093//console.warn("sqlite3.installOpfsVfs.defaultProxyUri =",sqlite3.installOpfsVfs.defaultProxyUri);
stephan9a557732022-10-04 17:06:51 +00001094self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
1095 try{
1096 return installOpfsVfs();
1097 }catch(e){
1098 console.error("installOpfsVfs() exception:",e);
1099 throw e;
1100 }
1101});
stephanc5313af2022-09-18 02:35:30 +00001102}/*sqlite3ApiBootstrap.initializers.push()*/);