blob: abdf90d49ba718ad713c66dbbc99c6c2e0f98169 [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/**
23 sqlite3.installOpfsVfs() returns a Promise which, on success, installs
24 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
36 Significant notes and limitations:
37
38 - As of this writing, OPFS is still very much in flux and only
39 available in bleeding-edge versions of Chrome (v102+, noting that
40 that number will increase as the OPFS API matures).
41
stephanc5313af2022-09-18 02:35:30 +000042 - The OPFS features used here are only available in dedicated Worker
stephanf3860122022-09-18 17:32:35 +000043 threads. This file tries to detect that case, resulting in a
44 rejected Promise if those features do not seem to be available.
stephanc5313af2022-09-18 02:35:30 +000045
46 - It requires the SharedArrayBuffer and Atomics classes, and the
47 former is only available if the HTTP server emits the so-called
48 COOP and COEP response headers. These features are required for
49 proxying OPFS's synchronous API via the synchronous interface
50 required by the sqlite3_vfs API.
51
52 - This function may only be called a single time and it must be
53 called from the client, as opposed to the library initialization,
54 in case the client requires a custom path for this API's
55 "counterpart": this function's argument is the relative URI to
56 this module's "asynchronous half". When called, this function removes
57 itself from the sqlite3 object.
58
59 The argument may optionally be a plain object with the following
60 configuration options:
61
62 - proxyUri: as described above
63
64 - verbose (=2): an integer 0-3. 0 disables all logging, 1 enables
65 logging of errors. 2 enables logging of warnings and errors. 3
66 additionally enables debugging info.
67
68 - sanityChecks (=false): if true, some basic sanity tests are
69 run on the OPFS VFS API after it's initialized, before the
70 returned Promise resolves.
71
72 On success, the Promise resolves to the top-most sqlite3 namespace
stephanf3860122022-09-18 17:32:35 +000073 object and that object gets a new object installed in its
74 `opfs` property, containing several OPFS-specific utilities.
stephan3961b262022-08-10 11:26:08 +000075*/
stephanc5313af2022-09-18 02:35:30 +000076sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri){
stephan509f4052022-09-19 09:58:01 +000077 delete sqlite3.installOpfsVfs;
stephan4cffb642022-09-20 16:20:35 +000078 if(!self.SharedArrayBuffer ||
stephan509f4052022-09-19 09:58:01 +000079 !self.FileSystemHandle ||
80 !self.FileSystemDirectoryHandle ||
81 !self.FileSystemFileHandle ||
82 !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
83 !navigator.storage.getDirectory){
84 return Promise.reject(
85 new Error("This environment does not have OPFS support.")
86 );
87 }
stephanc5313af2022-09-18 02:35:30 +000088 const options = (asyncProxyUri && 'object'===asyncProxyUri) ? asyncProxyUri : {
89 proxyUri: asyncProxyUri
stephan3961b262022-08-10 11:26:08 +000090 };
stephan509f4052022-09-19 09:58:01 +000091 const urlParams = new URL(self.location.href).searchParams;
stephanc5313af2022-09-18 02:35:30 +000092 if(undefined===options.verbose){
stephan509f4052022-09-19 09:58:01 +000093 options.verbose = urlParams.has('opfs-verbose') ? 3 : 2;
stephan3961b262022-08-10 11:26:08 +000094 }
stephanc5313af2022-09-18 02:35:30 +000095 if(undefined===options.sanityChecks){
stephan509f4052022-09-19 09:58:01 +000096 options.sanityChecks = urlParams.has('opfs-sanity-check');
stephanc5313af2022-09-18 02:35:30 +000097 }
98 if(undefined===options.proxyUri){
99 options.proxyUri = callee.defaultProxyUri;
100 }
stephanf3860122022-09-18 17:32:35 +0000101
stephane8afca32022-09-21 14:02:47 +0000102 const thePromise = new Promise(function(promiseResolve, promiseReject_){
stephan509f4052022-09-19 09:58:01 +0000103 const loggers = {
104 0:console.error.bind(console),
105 1:console.warn.bind(console),
106 2:console.log.bind(console)
stephanc5313af2022-09-18 02:35:30 +0000107 };
stephan509f4052022-09-19 09:58:01 +0000108 const logImpl = (level,...args)=>{
109 if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
110 };
111 const log = (...args)=>logImpl(2, ...args);
112 const warn = (...args)=>logImpl(1, ...args);
113 const error = (...args)=>logImpl(0, ...args);
stephanc5313af2022-09-18 02:35:30 +0000114 warn("The OPFS VFS feature is very much experimental and under construction.");
115 const toss = function(...args){throw new Error(args.join(' '))};
stephanc5313af2022-09-18 02:35:30 +0000116 const capi = sqlite3.capi;
117 const wasm = capi.wasm;
118 const sqlite3_vfs = capi.sqlite3_vfs;
119 const sqlite3_file = capi.sqlite3_file;
120 const sqlite3_io_methods = capi.sqlite3_io_methods;
stephan509f4052022-09-19 09:58:01 +0000121 /**
122 Generic utilities for working with OPFS. This will get filled out
123 by the Promise setup and, on success, installed as sqlite3.opfs.
124 */
125 const opfsUtil = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000126 /**
127 Not part of the public API. Solely for internal/development
128 use.
129 */
130 opfsUtil.metrics = {
131 dump: function(){
stephanaec046a2022-09-19 18:22:29 +0000132 let k, n = 0, t = 0, w = 0;
133 for(k in state.opIds){
stephanf8150112022-09-19 17:09:09 +0000134 const m = metrics[k];
135 n += m.count;
136 t += m.time;
stephanaec046a2022-09-19 18:22:29 +0000137 w += m.wait;
stephanf8150112022-09-19 17:09:09 +0000138 m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
139 m.avgWait = (m.count && m.wait) ? (m.wait / m.count) : 0;
140 }
stephanaec046a2022-09-19 18:22:29 +0000141 console.log(self.location.href,
142 "metrics for",self.location.href,":",metrics,
143 "\nTotal of",n,"op(s) for",t,
144 "ms (incl. "+w+" ms of waiting on the async side)");
stephanb8c8d4e2022-09-20 13:25:39 +0000145 console.log("Serialization metrics:",JSON.stringify(metrics.s11n,0,2));
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*/;
stephanc5313af2022-09-18 02:35:30 +0000163
stephanc9e26022022-09-20 10:11:52 +0000164 const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
165 const dVfs = pDVfs
166 ? new sqlite3_vfs(pDVfs)
167 : null /* dVfs will be null when sqlite3 is built with
168 SQLITE_OS_OTHER. Though we cannot currently handle
169 that case, the hope is to eventually be able to. */;
170 const opfsVfs = new sqlite3_vfs();
171 const opfsIoMethods = new sqlite3_io_methods();
172 opfsVfs.$iVersion = 2/*yes, two*/;
173 opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
174 opfsVfs.$mxPathname = 1024/*sure, why not?*/;
175 opfsVfs.$zName = wasm.allocCString("opfs");
176 // All C-side memory of opfsVfs is zeroed out, but just to be explicit:
177 opfsVfs.$xDlOpen = opfsVfs.$xDlError = opfsVfs.$xDlSym = opfsVfs.$xDlClose = null;
178 opfsVfs.ondispose = [
179 '$zName', opfsVfs.$zName,
180 'cleanup default VFS wrapper', ()=>(dVfs ? dVfs.dispose() : null),
181 'cleanup opfsIoMethods', ()=>opfsIoMethods.dispose()
182 ];
183 /**
184 Pedantic sidebar about opfsVfs.ondispose: the entries in that array
185 are items to clean up when opfsVfs.dispose() is called, but in this
186 environment it will never be called. The VFS instance simply
187 hangs around until the WASM module instance is cleaned up. We
188 "could" _hypothetically_ clean it up by "importing" an
189 sqlite3_os_end() impl into the wasm build, but the shutdown order
190 of the wasm engine and the JS one are undefined so there is no
191 guaranty that the opfsVfs instance would be available in one
192 environment or the other when sqlite3_os_end() is called (_if_ it
193 gets called at all in a wasm build, which is undefined).
194 */
195
stephane8afca32022-09-21 14:02:47 +0000196 const promiseReject = function(err){
197 opfsVfs.dispose();
198 return promiseReject_(err);
199 };
200 const W = new Worker(options.proxyUri);
201 W._originalOnError = W.onerror /* will be restored later */;
202 W.onerror = function(err){
203 // The error object doesn't contain any useful info when the
204 // failure is, e.g., that the remote script is 404.
205 promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
206 };
207
stephanc5313af2022-09-18 02:35:30 +0000208 /**
209 State which we send to the async-api Worker or share with it.
210 This object must initially contain only cloneable or sharable
211 objects. After the worker's "inited" message arrives, other types
212 of data may be added to it.
stephanf3860122022-09-18 17:32:35 +0000213
214 For purposes of Atomics.wait() and Atomics.notify(), we use a
215 SharedArrayBuffer with one slot reserved for each of the API
216 proxy's methods. The sync side of the API uses Atomics.wait()
217 on the corresponding slot and the async side uses
218 Atomics.notify() on that slot.
219
220 The approach of using a single SAB to serialize comms for all
221 instances might(?) lead to deadlock situations in multi-db
222 cases. We should probably have one SAB here with a single slot
223 for locking a per-file initialization step and then allocate a
224 separate SAB like the above one for each file. That will
225 require a bit of acrobatics but should be feasible.
stephanc5313af2022-09-18 02:35:30 +0000226 */
227 const state = Object.create(null);
228 state.verbose = options.verbose;
stephane8afca32022-09-21 14:02:47 +0000229 state.littleEndian = true;
230 /** If true, the async counterpart should log exceptions to
231 the serialization channel. That produces a great deal of
232 noise for seemingly innocuous things like xAccess() checks
233 for missing files. */
234 state.asyncS11nExceptions = false;
stephanc9e26022022-09-20 10:11:52 +0000235 /* Size of file I/O buffer block. 64k = max sqlite3 page size. */
stephanf3860122022-09-18 17:32:35 +0000236 state.fileBufferSize =
stephanc9e26022022-09-20 10:11:52 +0000237 1024 * 64;
stephan138647a2022-09-20 03:31:02 +0000238 state.sabS11nOffset = state.fileBufferSize;
stephanc9e26022022-09-20 10:11:52 +0000239 /**
240 The size of the block in our SAB for serializing arguments and
stephane8afca32022-09-21 14:02:47 +0000241 result values. Needs to be large enough to hold serialized
stephanc9e26022022-09-20 10:11:52 +0000242 values of any of the proxied APIs. Filenames are the largest
243 part but are limited to opfsVfs.$mxPathname bytes.
244 */
245 state.sabS11nSize = opfsVfs.$mxPathname * 2;
246 /**
247 The SAB used for all data I/O (files and arg/result s11n).
248 */
stephanc4b87be2022-09-20 01:28:47 +0000249 state.sabIO = new SharedArrayBuffer(
stephanc9e26022022-09-20 10:11:52 +0000250 state.fileBufferSize/* file i/o block */
251 + state.sabS11nSize/* argument/result serialization block */
stephanc4b87be2022-09-20 01:28:47 +0000252 );
stephanc5313af2022-09-18 02:35:30 +0000253 state.opIds = Object.create(null);
stephanf8150112022-09-19 17:09:09 +0000254 const metrics = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000255 {
stephanc9e26022022-09-20 10:11:52 +0000256 /* Indexes for use in our SharedArrayBuffer... */
stephan3961b262022-08-10 11:26:08 +0000257 let i = 0;
stephanc9e26022022-09-20 10:11:52 +0000258 /* SAB slot used to communicate which operation is desired
259 between both workers. This worker writes to it and the other
260 listens for changes. */
stephan138647a2022-09-20 03:31:02 +0000261 state.opIds.whichOp = i++;
stephanc9e26022022-09-20 10:11:52 +0000262 /* Slot for storing return values. This work listens to that
263 slot and the other worker writes to it. */
264 state.opIds.rc = i++;
265 /* Each function gets an ID which this worker writes to
266 the whichOp slot. The async-api worker uses Atomic.wait()
267 on the whichOp slot to figure out which operation to run
268 next. */
stephanc5313af2022-09-18 02:35:30 +0000269 state.opIds.xAccess = i++;
270 state.opIds.xClose = i++;
271 state.opIds.xDelete = i++;
stephanf3860122022-09-18 17:32:35 +0000272 state.opIds.xDeleteNoWait = i++;
stephanc5313af2022-09-18 02:35:30 +0000273 state.opIds.xFileSize = i++;
274 state.opIds.xOpen = i++;
275 state.opIds.xRead = i++;
276 state.opIds.xSleep = i++;
277 state.opIds.xSync = i++;
278 state.opIds.xTruncate = i++;
279 state.opIds.xWrite = i++;
stephanf3860122022-09-18 17:32:35 +0000280 state.opIds.mkdir = i++;
stephan5e8bb0a2022-09-20 08:27:57 +0000281 state.opIds.xFileControl = i++;
stephanc4b87be2022-09-20 01:28:47 +0000282 state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
stephanf8150112022-09-19 17:09:09 +0000283 opfsUtil.metrics.reset();
stephanc5313af2022-09-18 02:35:30 +0000284 }
285
stephanc9e26022022-09-20 10:11:52 +0000286 /**
287 SQLITE_xxx constants to export to the async worker
288 counterpart...
289 */
stephanc5313af2022-09-18 02:35:30 +0000290 state.sq3Codes = Object.create(null);
stephanc9e26022022-09-20 10:11:52 +0000291 [
stephanc5313af2022-09-18 02:35:30 +0000292 'SQLITE_ERROR', 'SQLITE_IOERR',
293 'SQLITE_NOTFOUND', 'SQLITE_MISUSE',
294 'SQLITE_IOERR_READ', 'SQLITE_IOERR_SHORT_READ',
295 'SQLITE_IOERR_WRITE', 'SQLITE_IOERR_FSYNC',
296 'SQLITE_IOERR_TRUNCATE', 'SQLITE_IOERR_DELETE',
stephanf3860122022-09-18 17:32:35 +0000297 'SQLITE_IOERR_ACCESS', 'SQLITE_IOERR_CLOSE',
stephanc4b87be2022-09-20 01:28:47 +0000298 'SQLITE_IOERR_DELETE',
299 'SQLITE_OPEN_CREATE', 'SQLITE_OPEN_DELETEONCLOSE',
300 'SQLITE_OPEN_READONLY'
stephanc5313af2022-09-18 02:35:30 +0000301 ].forEach(function(k){
302 state.sq3Codes[k] = capi[k] || toss("Maintenance required: not found:",k);
stephan3961b262022-08-10 11:26:08 +0000303 });
stephan3961b262022-08-10 11:26:08 +0000304
stephanc5313af2022-09-18 02:35:30 +0000305 /**
stephanc9e26022022-09-20 10:11:52 +0000306 Runs the given operation (by name) in the async worker
307 counterpart, waits for its response, and returns the result
308 which the async worker writes to SAB[state.opIds.rc]. The
309 2nd and subsequent arguments must be the aruguments for the
310 async op.
stephanc5313af2022-09-18 02:35:30 +0000311 */
stephan138647a2022-09-20 03:31:02 +0000312 const opRun = (op,...args)=>{
stephan5e8bb0a2022-09-20 08:27:57 +0000313 const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
314 state.s11n.serialize(...args);
stephanc9e26022022-09-20 10:11:52 +0000315 Atomics.store(state.sabOPView, state.opIds.rc, -1);
stephan5e8bb0a2022-09-20 08:27:57 +0000316 Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
317 Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
stephanf8150112022-09-19 17:09:09 +0000318 const t = performance.now();
stephanc9e26022022-09-20 10:11:52 +0000319 Atomics.wait(state.sabOPView, state.opIds.rc, -1);
320 const rc = Atomics.load(state.sabOPView, state.opIds.rc);
stephane8afca32022-09-21 14:02:47 +0000321 metrics[op].wait += performance.now() - t;
322 if(rc && state.asyncS11nExceptions){
stephan72ab4002022-09-21 12:27:35 +0000323 const err = state.s11n.deserialize();
324 if(err) error(op+"() async error:",...err);
325 }
stephan5e8bb0a2022-09-20 08:27:57 +0000326 return rc;
stephanc5313af2022-09-18 02:35:30 +0000327 };
328
stephan138647a2022-09-20 03:31:02 +0000329 const initS11n = ()=>{
stephanb8c8d4e2022-09-20 13:25:39 +0000330 /**
stephan72ab4002022-09-21 12:27:35 +0000331 ACHTUNG: this code is 100% duplicated in the other half of this
332 proxy! The documentation is maintained in the "synchronous half".
stephanb8c8d4e2022-09-20 13:25:39 +0000333
stephan72ab4002022-09-21 12:27:35 +0000334 This proxy de/serializes cross-thread function arguments and
335 output-pointer values via the state.sabIO SharedArrayBuffer,
336 using the region defined by (state.sabS11nOffset,
337 state.sabS11nOffset]. Only one dataset is recorded at a time.
338
339 This is not a general-purpose format. It only supports the range
340 of operations, and data sizes, needed by the sqlite3_vfs and
341 sqlite3_io_methods operations.
342
343 The data format can be succinctly summarized as:
344
345 Nt...Td...D
346
347 Where:
348
349 - N = number of entries (1 byte)
350
351 - t = type ID of first argument (1 byte)
352
353 - ...T = type IDs of the 2nd and subsequent arguments (1 byte
354 each).
355
356 - d = raw bytes of first argument (per-type size).
357
358 - ...D = raw bytes of the 2nd and subsequent arguments (per-type
359 size).
360
361 All types except strings have fixed sizes. Strings are stored
362 using their TextEncoder/TextDecoder representations. It would
363 arguably make more sense to store them as Int16Arrays of
364 their JS character values, but how best/fastest to get that
365 in and out of string form us an open point.
366
367 Historical note: this impl was initially about 1% this size by
368 using using JSON.stringify/parse(), but using fit-to-purpose
369 serialization saves considerable runtime.
stephanb8c8d4e2022-09-20 13:25:39 +0000370 */
stephan138647a2022-09-20 03:31:02 +0000371 if(state.s11n) return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000372 const textDecoder = new TextDecoder(),
stephan72ab4002022-09-21 12:27:35 +0000373 textEncoder = new TextEncoder('utf-8'),
374 viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
375 viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
stephan138647a2022-09-20 03:31:02 +0000376 state.s11n = Object.create(null);
stephan72ab4002022-09-21 12:27:35 +0000377 /* Only arguments and return values of these types may be
378 serialized. This covers the whole range of types needed by the
379 sqlite3_vfs API. */
stephanb8c8d4e2022-09-20 13:25:39 +0000380 const TypeIds = Object.create(null);
381 TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
382 TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
383 TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
384 TypeIds.string = { id: 4 };
stephan72ab4002022-09-21 12:27:35 +0000385
386 const getTypeId = (v)=>(
387 TypeIds[typeof v]
388 || toss("Maintenance required: this value type cannot be serialized.",v)
389 );
stephanb8c8d4e2022-09-20 13:25:39 +0000390 const getTypeIdById = (tid)=>{
391 switch(tid){
stephan72ab4002022-09-21 12:27:35 +0000392 case TypeIds.number.id: return TypeIds.number;
393 case TypeIds.bigint.id: return TypeIds.bigint;
394 case TypeIds.boolean.id: return TypeIds.boolean;
395 case TypeIds.string.id: return TypeIds.string;
396 default: toss("Invalid type ID:",tid);
stephanb8c8d4e2022-09-20 13:25:39 +0000397 }
398 };
stephan72ab4002022-09-21 12:27:35 +0000399
stephan138647a2022-09-20 03:31:02 +0000400 /**
stephan72ab4002022-09-21 12:27:35 +0000401 Returns an array of the deserialized state stored by the most
402 recent serialize() operation (from from this thread or the
403 counterpart thread), or null if the serialization buffer is empty.
stephan138647a2022-09-20 03:31:02 +0000404 */
405 state.s11n.deserialize = function(){
stephanb8c8d4e2022-09-20 13:25:39 +0000406 ++metrics.s11n.deserialize.count;
407 const t = performance.now();
stephanb8c8d4e2022-09-20 13:25:39 +0000408 const argc = viewU8[0];
stephan72ab4002022-09-21 12:27:35 +0000409 const rc = argc ? [] : null;
stephanb8c8d4e2022-09-20 13:25:39 +0000410 if(argc){
stephan72ab4002022-09-21 12:27:35 +0000411 const typeIds = [];
412 let offset = 1, i, n, v;
stephanb8c8d4e2022-09-20 13:25:39 +0000413 for(i = 0; i < argc; ++i, ++offset){
414 typeIds.push(getTypeIdById(viewU8[offset]));
415 }
416 for(i = 0; i < argc; ++i){
417 const t = typeIds[i];
418 if(t.getter){
419 v = viewDV[t.getter](offset, state.littleEndian);
420 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000421 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000422 n = viewDV.getInt32(offset, state.littleEndian);
423 offset += 4;
424 v = textDecoder.decode(viewU8.slice(offset, offset+n));
425 offset += n;
426 }
427 rc.push(v);
428 }
429 }
430 //log("deserialize:",argc, rc);
431 metrics.s11n.deserialize.time += performance.now() - t;
432 return rc;
433 };
stephan72ab4002022-09-21 12:27:35 +0000434
stephan138647a2022-09-20 03:31:02 +0000435 /**
436 Serializes all arguments to the shared buffer for consumption
stephanb8c8d4e2022-09-20 13:25:39 +0000437 by the counterpart thread.
stephan5e8bb0a2022-09-20 08:27:57 +0000438
stephanb8c8d4e2022-09-20 13:25:39 +0000439 This routine is only intended for serializing OPFS VFS
440 arguments and (in at least one special case) result values,
441 and the buffer is sized to be able to comfortably handle
442 those.
stephan5e8bb0a2022-09-20 08:27:57 +0000443
444 If passed no arguments then it zeroes out the serialization
445 state.
stephan138647a2022-09-20 03:31:02 +0000446 */
447 state.s11n.serialize = function(...args){
stephanb8c8d4e2022-09-20 13:25:39 +0000448 const t = performance.now();
stephan72ab4002022-09-21 12:27:35 +0000449 ++metrics.s11n.serialize.count;
stephan5e8bb0a2022-09-20 08:27:57 +0000450 if(args.length){
stephanb8c8d4e2022-09-20 13:25:39 +0000451 //log("serialize():",args);
stephan72ab4002022-09-21 12:27:35 +0000452 const typeIds = [];
453 let i = 0, offset = 1;
454 viewU8[0] = args.length & 0xff /* header = # of args */;
stephanb8c8d4e2022-09-20 13:25:39 +0000455 for(; i < args.length; ++i, ++offset){
stephan72ab4002022-09-21 12:27:35 +0000456 /* Write the TypeIds.id value into the next args.length
457 bytes. */
stephanb8c8d4e2022-09-20 13:25:39 +0000458 typeIds.push(getTypeId(args[i]));
459 viewU8[offset] = typeIds[i].id;
460 }
461 for(i = 0; i < args.length; ++i) {
stephan72ab4002022-09-21 12:27:35 +0000462 /* Deserialize the following bytes based on their
463 corresponding TypeIds.id from the header. */
stephanb8c8d4e2022-09-20 13:25:39 +0000464 const t = typeIds[i];
465 if(t.setter){
466 viewDV[t.setter](offset, args[i], state.littleEndian);
467 offset += t.size;
stephan72ab4002022-09-21 12:27:35 +0000468 }else{/*String*/
stephanb8c8d4e2022-09-20 13:25:39 +0000469 const s = textEncoder.encode(args[i]);
470 viewDV.setInt32(offset, s.byteLength, state.littleEndian);
471 offset += 4;
472 viewU8.set(s, offset);
473 offset += s.byteLength;
474 }
475 }
476 //log("serialize() result:",viewU8.slice(0,offset));
stephan5e8bb0a2022-09-20 08:27:57 +0000477 }else{
stephanb8c8d4e2022-09-20 13:25:39 +0000478 viewU8[0] = 0;
stephan5e8bb0a2022-09-20 08:27:57 +0000479 }
stephanb8c8d4e2022-09-20 13:25:39 +0000480 metrics.s11n.serialize.time += performance.now() - t;
stephan138647a2022-09-20 03:31:02 +0000481 };
482 return state.s11n;
stephanb8c8d4e2022-09-20 13:25:39 +0000483 }/*initS11n()*/;
stephan138647a2022-09-20 03:31:02 +0000484
stephanc5313af2022-09-18 02:35:30 +0000485 /**
486 Generates a random ASCII string len characters long, intended for
487 use as a temporary file name.
488 */
489 const randomFilename = function f(len=16){
490 if(!f._chars){
491 f._chars = "abcdefghijklmnopqrstuvwxyz"+
492 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
493 "012346789";
494 f._n = f._chars.length;
495 }
496 const a = [];
497 let i = 0;
498 for( ; i < len; ++i){
499 const ndx = Math.random() * (f._n * 64) % f._n | 0;
500 a[i] = f._chars[ndx];
501 }
502 return a.join('');
503 };
504
505 /**
506 Map of sqlite3_file pointers to objects constructed by xOpen().
507 */
508 const __openFiles = Object.create(null);
stephanc5313af2022-09-18 02:35:30 +0000509
510 /**
511 Installs a StructBinder-bound function pointer member of the
512 given name and function in the given StructType target object.
513 It creates a WASM proxy for the given function and arranges for
514 that proxy to be cleaned up when tgt.dispose() is called. Throws
515 on the slightest hint of error (e.g. tgt is-not-a StructType,
516 name does not map to a struct-bound member, etc.).
517
518 Returns a proxy for this function which is bound to tgt and takes
519 2 args (name,func). That function returns the same thing,
520 permitting calls to be chained.
521
522 If called with only 1 arg, it has no side effects but returns a
523 func with the same signature as described above.
524 */
525 const installMethod = function callee(tgt, name, func){
stephanf3860122022-09-18 17:32:35 +0000526 if(!(tgt instanceof sqlite3.StructBinder.StructType)){
stephanc5313af2022-09-18 02:35:30 +0000527 toss("Usage error: target object is-not-a StructType.");
528 }
529 if(1===arguments.length){
530 return (n,f)=>callee(tgt,n,f);
531 }
532 if(!callee.argcProxy){
533 callee.argcProxy = function(func,sig){
534 return function(...args){
535 if(func.length!==arguments.length){
536 toss("Argument mismatch. Native signature is:",sig);
537 }
538 return func.apply(this, args);
539 }
540 };
541 callee.removeFuncList = function(){
542 if(this.ondispose.__removeFuncList){
543 this.ondispose.__removeFuncList.forEach(
544 (v,ndx)=>{
545 if('number'===typeof v){
546 try{wasm.uninstallFunction(v)}
547 catch(e){/*ignore*/}
548 }
549 /* else it's a descriptive label for the next number in
550 the list. */
551 }
552 );
553 delete this.ondispose.__removeFuncList;
554 }
555 };
556 }/*static init*/
557 const sigN = tgt.memberSignature(name);
558 if(sigN.length<2){
559 toss("Member",name," is not a function pointer. Signature =",sigN);
560 }
561 const memKey = tgt.memberKey(name);
562 //log("installMethod",tgt, name, sigN);
stephanc2ccd672022-09-20 10:47:36 +0000563 const fProxy = 0
stephanc5313af2022-09-18 02:35:30 +0000564 // We can remove this proxy middle-man once the VFS is working
565 ? callee.argcProxy(func, sigN)
566 : func;
567 const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
568 tgt[memKey] = pFunc;
569 if(!tgt.ondispose) tgt.ondispose = [];
570 if(!tgt.ondispose.__removeFuncList){
571 tgt.ondispose.push('ondispose.__removeFuncList handler',
572 callee.removeFuncList);
573 tgt.ondispose.__removeFuncList = [];
574 }
575 tgt.ondispose.__removeFuncList.push(memKey, pFunc);
576 return (n,f)=>callee(tgt, n, f);
577 }/*installMethod*/;
stephanf8150112022-09-19 17:09:09 +0000578
579 const opTimer = Object.create(null);
580 opTimer.op = undefined;
581 opTimer.start = undefined;
582 const mTimeStart = (op)=>{
583 opTimer.start = performance.now();
584 opTimer.op = op;
585 //metrics[op] || toss("Maintenance required: missing metrics for",op);
586 ++metrics[op].count;
587 };
588 const mTimeEnd = ()=>(
589 metrics[opTimer.op].time += performance.now() - opTimer.start
590 );
591
stephanc5313af2022-09-18 02:35:30 +0000592 /**
593 Impls for the sqlite3_io_methods methods. Maintenance reminder:
594 members are in alphabetical order to simplify finding them.
595 */
596 const ioSyncWrappers = {
597 xCheckReservedLock: function(pFile,pOut){
598 // Exclusive lock is automatically acquired when opened
599 //warn("xCheckReservedLock(",arguments,") is a no-op");
600 wasm.setMemValue(pOut,1,'i32');
601 return 0;
602 },
603 xClose: function(pFile){
stephanf8150112022-09-19 17:09:09 +0000604 mTimeStart('xClose');
stephanc5313af2022-09-18 02:35:30 +0000605 let rc = 0;
606 const f = __openFiles[pFile];
607 if(f){
608 delete __openFiles[pFile];
609 rc = opRun('xClose', pFile);
610 if(f.sq3File) f.sq3File.dispose();
611 }
stephanf8150112022-09-19 17:09:09 +0000612 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000613 return rc;
614 },
615 xDeviceCharacteristics: function(pFile){
616 //debug("xDeviceCharacteristics(",pFile,")");
617 return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
618 },
stephanf8150112022-09-19 17:09:09 +0000619 xFileControl: function(pFile, opId, pArg){
620 mTimeStart('xFileControl');
stephanaec046a2022-09-19 18:22:29 +0000621 const rc = (capi.SQLITE_FCNTL_SYNC===opId)
stephan138647a2022-09-20 03:31:02 +0000622 ? opRun('xSync', pFile, 0)
stephanaec046a2022-09-19 18:22:29 +0000623 : capi.SQLITE_NOTFOUND;
stephanf8150112022-09-19 17:09:09 +0000624 mTimeEnd();
stephanaec046a2022-09-19 18:22:29 +0000625 return rc;
stephanc5313af2022-09-18 02:35:30 +0000626 },
627 xFileSize: function(pFile,pSz64){
stephanf8150112022-09-19 17:09:09 +0000628 mTimeStart('xFileSize');
stephanc5313af2022-09-18 02:35:30 +0000629 const rc = opRun('xFileSize', pFile);
stephane8afca32022-09-21 14:02:47 +0000630 if(0==rc){
stephan138647a2022-09-20 03:31:02 +0000631 const sz = state.s11n.deserialize()[0];
632 wasm.setMemValue(pSz64, BigInt(sz), 'i64');
stephanc5313af2022-09-18 02:35:30 +0000633 }
stephanf8150112022-09-19 17:09:09 +0000634 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000635 return rc;
636 },
637 xLock: function(pFile,lockType){
638 //2022-09: OPFS handles lock when opened
639 //warn("xLock(",arguments,") is a no-op");
640 return 0;
641 },
stephan138647a2022-09-20 03:31:02 +0000642 xRead: function(pFile,pDest,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000643 /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000644 mTimeStart('xRead');
stephanc5313af2022-09-18 02:35:30 +0000645 const f = __openFiles[pFile];
646 let rc;
647 try {
stephan138647a2022-09-20 03:31:02 +0000648 rc = opRun('xRead',pFile, n, Number(offset64));
stephan862281f2022-09-19 09:25:25 +0000649 if(0===rc || capi.SQLITE_IOERR_SHORT_READ===rc){
stephanf8150112022-09-19 17:09:09 +0000650 // set() seems to be the fastest way to copy this...
651 wasm.heap8u().set(f.sabView.subarray(0, n), pDest);
stephan862281f2022-09-19 09:25:25 +0000652 }
stephanc5313af2022-09-18 02:35:30 +0000653 }catch(e){
654 error("xRead(",arguments,") failed:",e,f);
655 rc = capi.SQLITE_IOERR_READ;
656 }
stephanf8150112022-09-19 17:09:09 +0000657 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000658 return rc;
659 },
660 xSync: function(pFile,flags){
stephanaec046a2022-09-19 18:22:29 +0000661 ++metrics.xSync.count;
stephan138647a2022-09-20 03:31:02 +0000662 return 0; // impl'd in xFileControl()
stephanc5313af2022-09-18 02:35:30 +0000663 },
664 xTruncate: function(pFile,sz64){
stephanf8150112022-09-19 17:09:09 +0000665 mTimeStart('xTruncate');
stephan138647a2022-09-20 03:31:02 +0000666 const rc = opRun('xTruncate', pFile, Number(sz64));
stephanf8150112022-09-19 17:09:09 +0000667 mTimeEnd();
668 return rc;
stephanc5313af2022-09-18 02:35:30 +0000669 },
670 xUnlock: function(pFile,lockType){
671 //2022-09: OPFS handles lock when opened
672 //warn("xUnlock(",arguments,") is a no-op");
673 return 0;
674 },
stephan138647a2022-09-20 03:31:02 +0000675 xWrite: function(pFile,pSrc,n,offset64){
stephanc5313af2022-09-18 02:35:30 +0000676 /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
stephanf8150112022-09-19 17:09:09 +0000677 mTimeStart('xWrite');
stephanc5313af2022-09-18 02:35:30 +0000678 const f = __openFiles[pFile];
stephanf8150112022-09-19 17:09:09 +0000679 let rc;
stephanc5313af2022-09-18 02:35:30 +0000680 try {
stephanf8150112022-09-19 17:09:09 +0000681 f.sabView.set(wasm.heap8u().subarray(pSrc, pSrc+n));
stephan138647a2022-09-20 03:31:02 +0000682 rc = opRun('xWrite', pFile, n, Number(offset64));
stephanc5313af2022-09-18 02:35:30 +0000683 }catch(e){
684 error("xWrite(",arguments,") failed:",e,f);
stephanf8150112022-09-19 17:09:09 +0000685 rc = capi.SQLITE_IOERR_WRITE;
stephanc5313af2022-09-18 02:35:30 +0000686 }
stephanf8150112022-09-19 17:09:09 +0000687 mTimeEnd();
688 return rc;
stephanc5313af2022-09-18 02:35:30 +0000689 }
690 }/*ioSyncWrappers*/;
691
692 /**
693 Impls for the sqlite3_vfs methods. Maintenance reminder: members
694 are in alphabetical order to simplify finding them.
695 */
696 const vfsSyncWrappers = {
697 xAccess: function(pVfs,zName,flags,pOut){
stephanf8150112022-09-19 17:09:09 +0000698 mTimeStart('xAccess');
stephan5e8bb0a2022-09-20 08:27:57 +0000699 const rc = opRun('xAccess', wasm.cstringToJs(zName));
700 wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
stephanf8150112022-09-19 17:09:09 +0000701 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000702 return 0;
703 },
704 xCurrentTime: function(pVfs,pOut){
705 /* If it turns out that we need to adjust for timezone, see:
706 https://stackoverflow.com/a/11760121/1458521 */
707 wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
708 'double');
709 return 0;
710 },
711 xCurrentTimeInt64: function(pVfs,pOut){
712 // TODO: confirm that this calculation is correct
713 wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
714 'i64');
715 return 0;
716 },
717 xDelete: function(pVfs, zName, doSyncDir){
stephanf8150112022-09-19 17:09:09 +0000718 mTimeStart('xDelete');
stephan138647a2022-09-20 03:31:02 +0000719 opRun('xDelete', wasm.cstringToJs(zName), doSyncDir, false);
stephanf3860122022-09-18 17:32:35 +0000720 /* We're ignoring errors because we cannot yet differentiate
721 between harmless and non-harmless failures. */
stephanf8150112022-09-19 17:09:09 +0000722 mTimeEnd();
stephanf3860122022-09-18 17:32:35 +0000723 return 0;
stephanc5313af2022-09-18 02:35:30 +0000724 },
725 xFullPathname: function(pVfs,zName,nOut,pOut){
726 /* Until/unless we have some notion of "current dir"
727 in OPFS, simply copy zName to pOut... */
728 const i = wasm.cstrncpy(pOut, zName, nOut);
729 return i<nOut ? 0 : capi.SQLITE_CANTOPEN
730 /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
731 },
732 xGetLastError: function(pVfs,nOut,pOut){
733 /* TODO: store exception.message values from the async
734 partner in a dedicated SharedArrayBuffer, noting that we'd have
735 to encode them... TextEncoder can do that for us. */
736 warn("OPFS xGetLastError() has nothing sensible to return.");
737 return 0;
738 },
stephan8766fd22022-09-19 05:19:04 +0000739 //xSleep is optionally defined below
stephanc5313af2022-09-18 02:35:30 +0000740 xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
stephanf8150112022-09-19 17:09:09 +0000741 mTimeStart('xOpen');
stephanc5313af2022-09-18 02:35:30 +0000742 if(!f._){
743 f._ = {
744 fileTypes: {
745 SQLITE_OPEN_MAIN_DB: 'mainDb',
746 SQLITE_OPEN_MAIN_JOURNAL: 'mainJournal',
747 SQLITE_OPEN_TEMP_DB: 'tempDb',
748 SQLITE_OPEN_TEMP_JOURNAL: 'tempJournal',
749 SQLITE_OPEN_TRANSIENT_DB: 'transientDb',
750 SQLITE_OPEN_SUBJOURNAL: 'subjournal',
751 SQLITE_OPEN_SUPER_JOURNAL: 'superJournal',
752 SQLITE_OPEN_WAL: 'wal'
753 },
754 getFileType: function(filename,oflags){
755 const ft = f._.fileTypes;
756 for(let k of Object.keys(ft)){
757 if(oflags & capi[k]) return ft[k];
758 }
759 warn("Cannot determine fileType based on xOpen() flags for file",filename);
760 return '???';
761 }
762 };
763 }
764 if(0===zName){
765 zName = randomFilename();
766 }else if('number'===typeof zName){
767 zName = wasm.cstringToJs(zName);
768 }
stephan138647a2022-09-20 03:31:02 +0000769 const fh = Object.create(null);
770 fh.fid = pFile;
771 fh.filename = zName;
772 fh.sab = new SharedArrayBuffer(state.fileBufferSize);
773 fh.flags = flags;
774 const rc = opRun('xOpen', pFile, zName, flags);
stephanc5313af2022-09-18 02:35:30 +0000775 if(!rc){
776 /* Recall that sqlite3_vfs::xClose() will be called, even on
777 error, unless pFile->pMethods is NULL. */
stephan138647a2022-09-20 03:31:02 +0000778 if(fh.readOnly){
stephanc5313af2022-09-18 02:35:30 +0000779 wasm.setMemValue(pOutFlags, capi.SQLITE_OPEN_READONLY, 'i32');
780 }
stephan138647a2022-09-20 03:31:02 +0000781 __openFiles[pFile] = fh;
782 fh.sabView = state.sabFileBufView;
783 fh.sq3File = new sqlite3_file(pFile);
784 fh.sq3File.$pMethods = opfsIoMethods.pointer;
stephanc5313af2022-09-18 02:35:30 +0000785 }
stephanf8150112022-09-19 17:09:09 +0000786 mTimeEnd();
stephanc5313af2022-09-18 02:35:30 +0000787 return rc;
788 }/*xOpen()*/
789 }/*vfsSyncWrappers*/;
790
stephan8766fd22022-09-19 05:19:04 +0000791 if(dVfs){
792 opfsVfs.$xRandomness = dVfs.$xRandomness;
793 opfsVfs.$xSleep = dVfs.$xSleep;
794 }
stephanc5313af2022-09-18 02:35:30 +0000795 if(!opfsVfs.$xRandomness){
796 /* If the default VFS has no xRandomness(), add a basic JS impl... */
797 vfsSyncWrappers.xRandomness = function(pVfs, nOut, pOut){
798 const heap = wasm.heap8u();
799 let i = 0;
800 for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
801 return i;
802 };
803 }
804 if(!opfsVfs.$xSleep){
805 /* If we can inherit an xSleep() impl from the default VFS then
stephan8766fd22022-09-19 05:19:04 +0000806 assume it's sane and use it, otherwise install a JS-based
807 one. */
808 vfsSyncWrappers.xSleep = function(pVfs,ms){
stephanc4b87be2022-09-20 01:28:47 +0000809 Atomics.wait(state.sabOPView, state.opIds.xSleep, 0, ms);
stephan8766fd22022-09-19 05:19:04 +0000810 return 0;
811 };
stephanc5313af2022-09-18 02:35:30 +0000812 }
813
814 /* Install the vfs/io_methods into their C-level shared instances... */
815 let inst = installMethod(opfsIoMethods);
816 for(let k of Object.keys(ioSyncWrappers)) inst(k, ioSyncWrappers[k]);
817 inst = installMethod(opfsVfs);
818 for(let k of Object.keys(vfsSyncWrappers)) inst(k, vfsSyncWrappers[k]);
stephanf3860122022-09-18 17:32:35 +0000819
stephanf3860122022-09-18 17:32:35 +0000820 /**
821 Syncronously deletes the given OPFS filesystem entry, ignoring
822 any errors. As this environment has no notion of "current
823 directory", the given name must be an absolute path. If the 2nd
824 argument is truthy, deletion is recursive (use with caution!).
825
826 Returns true if the deletion succeeded and fails if it fails,
827 but cannot report the nature of the failure.
828 */
stephan0e0687c2022-09-19 13:44:23 +0000829 opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
stephan72ab4002022-09-21 12:27:35 +0000830 mTimeStart('xDelete');
831 const rc = opRun('xDelete', fsEntryName, 0, recursive);
832 mTimeEnd();
833 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000834 };
835 /**
stephanf3860122022-09-18 17:32:35 +0000836 Synchronously creates the given directory name, recursively, in
837 the OPFS filesystem. Returns true if it succeeds or the
838 directory already exists, else false.
839 */
stephan5e8bb0a2022-09-20 08:27:57 +0000840 opfsUtil.mkdir = function(absDirName){
stephan72ab4002022-09-21 12:27:35 +0000841 mTimeStart('mkdir');
842 const rc = opRun('mkdir', absDirName);
843 mTimeEnd();
844 return 0===rc;
stephanf3860122022-09-18 17:32:35 +0000845 };
846 /**
847 Synchronously checks whether the given OPFS filesystem exists,
848 returning true if it does, false if it doesn't.
849 */
850 opfsUtil.entryExists = function(fsEntryName){
851 return 0===opRun('xAccess', fsEntryName);
852 };
853
854 /**
855 Generates a random ASCII string, intended for use as a
856 temporary file name. Its argument is the length of the string,
857 defaulting to 16.
858 */
859 opfsUtil.randomFilename = randomFilename;
860
861 if(sqlite3.oo1){
862 opfsUtil.OpfsDb = function(...args){
863 const opt = sqlite3.oo1.dbCtorHelper.normalizeArgs(...args);
864 opt.vfs = opfsVfs.$zName;
865 sqlite3.oo1.dbCtorHelper.call(this, opt);
866 };
867 opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
868 }
stephanf8150112022-09-19 17:09:09 +0000869
stephanf3860122022-09-18 17:32:35 +0000870 /**
871 Potential TODOs:
872
873 - Expose one or both of the Worker objects via opfsUtil and
874 publish an interface for proxying the higher-level OPFS
875 features like getting a directory listing.
876 */
stephanc5313af2022-09-18 02:35:30 +0000877
stephan5e8bb0a2022-09-20 08:27:57 +0000878 const sanityCheck = function(){
stephanc5313af2022-09-18 02:35:30 +0000879 const scope = wasm.scopedAllocPush();
880 const sq3File = new sqlite3_file();
881 try{
882 const fid = sq3File.pointer;
883 const openFlags = capi.SQLITE_OPEN_CREATE
884 | capi.SQLITE_OPEN_READWRITE
885 //| capi.SQLITE_OPEN_DELETEONCLOSE
886 | capi.SQLITE_OPEN_MAIN_DB;
887 const pOut = wasm.scopedAlloc(8);
stephanb8c8d4e2022-09-20 13:25:39 +0000888 const dbFile = "/sanity/check/file"+randomFilename(8);
stephanc5313af2022-09-18 02:35:30 +0000889 const zDbFile = wasm.scopedAllocCString(dbFile);
890 let rc;
stephane8afca32022-09-21 14:02:47 +0000891 state.s11n.serialize("This is ä string.");
892 rc = state.s11n.deserialize();
893 log("deserialize() says:",rc);
894 if("This is ä string."!==rc[0]) toss("String d13n error.");
stephanc5313af2022-09-18 02:35:30 +0000895 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
896 rc = wasm.getMemValue(pOut,'i32');
897 log("xAccess(",dbFile,") exists ?=",rc);
898 rc = vfsSyncWrappers.xOpen(opfsVfs.pointer, zDbFile,
899 fid, openFlags, pOut);
stephanc4b87be2022-09-20 01:28:47 +0000900 log("open rc =",rc,"state.sabOPView[xOpen] =",
901 state.sabOPView[state.opIds.xOpen]);
stephane8afca32022-09-21 14:02:47 +0000902 if(0!==rc){
stephanc5313af2022-09-18 02:35:30 +0000903 error("open failed with code",rc);
904 return;
905 }
906 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
907 rc = wasm.getMemValue(pOut,'i32');
908 if(!rc) toss("xAccess() failed to detect file.");
909 rc = ioSyncWrappers.xSync(sq3File.pointer, 0);
910 if(rc) toss('sync failed w/ rc',rc);
911 rc = ioSyncWrappers.xTruncate(sq3File.pointer, 1024);
912 if(rc) toss('truncate failed w/ rc',rc);
913 wasm.setMemValue(pOut,0,'i64');
914 rc = ioSyncWrappers.xFileSize(sq3File.pointer, pOut);
915 if(rc) toss('xFileSize failed w/ rc',rc);
916 log("xFileSize says:",wasm.getMemValue(pOut, 'i64'));
917 rc = ioSyncWrappers.xWrite(sq3File.pointer, zDbFile, 10, 1);
918 if(rc) toss("xWrite() failed!");
919 const readBuf = wasm.scopedAlloc(16);
920 rc = ioSyncWrappers.xRead(sq3File.pointer, readBuf, 6, 2);
921 wasm.setMemValue(readBuf+6,0);
922 let jRead = wasm.cstringToJs(readBuf);
923 log("xRead() got:",jRead);
924 if("sanity"!==jRead) toss("Unexpected xRead() value.");
stephan8766fd22022-09-19 05:19:04 +0000925 if(vfsSyncWrappers.xSleep){
926 log("xSleep()ing before close()ing...");
927 vfsSyncWrappers.xSleep(opfsVfs.pointer,2000);
928 log("waking up from xSleep()");
929 }
stephanc5313af2022-09-18 02:35:30 +0000930 rc = ioSyncWrappers.xClose(fid);
stephanc4b87be2022-09-20 01:28:47 +0000931 log("xClose rc =",rc,"sabOPView =",state.sabOPView);
stephanc5313af2022-09-18 02:35:30 +0000932 log("Deleting file:",dbFile);
933 vfsSyncWrappers.xDelete(opfsVfs.pointer, zDbFile, 0x1234);
934 vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
935 rc = wasm.getMemValue(pOut,'i32');
936 if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
stephanc9e26022022-09-20 10:11:52 +0000937 warn("End of OPFS sanity checks.");
stephanc5313af2022-09-18 02:35:30 +0000938 }finally{
939 sq3File.dispose();
940 wasm.scopedAllocPop(scope);
941 }
942 }/*sanityCheck()*/;
943
stephanf8150112022-09-19 17:09:09 +0000944
stephanc5313af2022-09-18 02:35:30 +0000945 W.onmessage = function({data}){
946 //log("Worker.onmessage:",data);
947 switch(data.type){
stephan138647a2022-09-20 03:31:02 +0000948 case 'opfs-async-loaded':
stephane8afca32022-09-21 14:02:47 +0000949 /*Arrives as soon as the asyc proxy finishes loading.
950 Pass our config and shared state on to the async worker.*/
stephan5e8bb0a2022-09-20 08:27:57 +0000951 W.postMessage({type: 'opfs-async-init',args: state});
stephanc5313af2022-09-18 02:35:30 +0000952 break;
stephan138647a2022-09-20 03:31:02 +0000953 case 'opfs-async-inited':{
stephane8afca32022-09-21 14:02:47 +0000954 /*Indicates that the async partner has received the 'init'
955 and has finished initializing, so the real work can
956 begin...*/
stephanc5313af2022-09-18 02:35:30 +0000957 try {
stephan0e0687c2022-09-19 13:44:23 +0000958 const rc = capi.sqlite3_vfs_register(opfsVfs.pointer, 0);
stephanc5313af2022-09-18 02:35:30 +0000959 if(rc){
stephanc5313af2022-09-18 02:35:30 +0000960 toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
961 }
962 if(opfsVfs.pointer !== capi.sqlite3_vfs_find("opfs")){
963 toss("BUG: sqlite3_vfs_find() failed for just-installed OPFS VFS");
964 }
965 capi.sqlite3_vfs_register.addReference(opfsVfs, opfsIoMethods);
stephanc4b87be2022-09-20 01:28:47 +0000966 state.sabOPView = new Int32Array(state.sabOP);
stephan138647a2022-09-20 03:31:02 +0000967 state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
968 state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
969 initS11n();
stephanc5313af2022-09-18 02:35:30 +0000970 if(options.sanityChecks){
971 warn("Running sanity checks because of opfs-sanity-check URL arg...");
972 sanityCheck();
973 }
stephanf3860122022-09-18 17:32:35 +0000974 W.onerror = W._originalOnError;
975 delete W._originalOnError;
976 sqlite3.opfs = opfsUtil;
stephanc5313af2022-09-18 02:35:30 +0000977 log("End of OPFS sqlite3_vfs setup.", opfsVfs);
stephan509f4052022-09-19 09:58:01 +0000978 promiseResolve(sqlite3);
stephanc5313af2022-09-18 02:35:30 +0000979 }catch(e){
980 error(e);
981 promiseReject(e);
982 }
983 break;
984 }
985 default:
986 promiseReject(e);
987 error("Unexpected message from the async worker:",data);
988 break;
989 }
990 };
991 })/*thePromise*/;
992 return thePromise;
993}/*installOpfsVfs()*/;
994sqlite3.installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js";
995}/*sqlite3ApiBootstrap.initializers.push()*/);