@livestore/wa-sqlite 1.0.1-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/wa-sqlite-async.mjs +16 -0
- package/dist/wa-sqlite-async.wasm +0 -0
- package/dist/wa-sqlite-jspi.mjs +16 -0
- package/dist/wa-sqlite-jspi.wasm +0 -0
- package/dist/wa-sqlite.mjs +16 -0
- package/dist/wa-sqlite.wasm +0 -0
- package/package.json +45 -0
- package/src/FacadeVFS.js +508 -0
- package/src/VFS.js +222 -0
- package/src/WebLocksMixin.js +412 -0
- package/src/examples/AccessHandlePoolVFS.js +458 -0
- package/src/examples/IDBBatchAtomicVFS.js +820 -0
- package/src/examples/IDBMirrorVFS.js +875 -0
- package/src/examples/MemoryAsyncVFS.js +100 -0
- package/src/examples/MemoryVFS.js +176 -0
- package/src/examples/OPFSAdaptiveVFS.js +437 -0
- package/src/examples/OPFSAnyContextVFS.js +300 -0
- package/src/examples/OPFSCoopSyncVFS.js +590 -0
- package/src/examples/OPFSPermutedVFS.js +1214 -0
- package/src/examples/README.md +89 -0
- package/src/examples/tag.js +82 -0
- package/src/sqlite-api.js +914 -0
- package/src/sqlite-constants.js +275 -0
- package/src/types/globals.d.ts +60 -0
- package/src/types/index.d.ts +1302 -0
- package/src/types/tsconfig.json +6 -0
- package/test/AccessHandlePoolVFS.test.js +27 -0
- package/test/IDBBatchAtomicVFS.test.js +97 -0
- package/test/IDBMirrorVFS.test.js +27 -0
- package/test/MemoryAsyncVFS.test.js +27 -0
- package/test/MemoryVFS.test.js +27 -0
- package/test/OPFSAdaptiveVFS.test.js +27 -0
- package/test/OPFSAnyContextVFS.test.js +27 -0
- package/test/OPFSCoopSyncVFS.test.js +27 -0
- package/test/OPFSPermutedVFS.test.js +27 -0
- package/test/TestContext.js +96 -0
- package/test/WebLocksMixin.test.js +521 -0
- package/test/api.test.js +49 -0
- package/test/api_exec.js +89 -0
- package/test/api_misc.js +63 -0
- package/test/api_statements.js +426 -0
- package/test/callbacks.test.js +373 -0
- package/test/sql.test.js +64 -0
- package/test/sql_0001.js +49 -0
- package/test/sql_0002.js +52 -0
- package/test/sql_0003.js +83 -0
- package/test/sql_0004.js +81 -0
- package/test/sql_0005.js +76 -0
- package/test/test-worker.js +204 -0
- package/test/vfs_xAccess.js +2 -0
- package/test/vfs_xClose.js +52 -0
- package/test/vfs_xOpen.js +91 -0
- package/test/vfs_xRead.js +38 -0
- package/test/vfs_xWrite.js +36 -0
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { WebLocksMixin } from "../src/WebLocksMixin.js";
|
|
2
|
+
import * as SQLite from "../src/sqlite-api.js";
|
|
3
|
+
|
|
4
|
+
class Tester extends WebLocksMixin(Object) {
|
|
5
|
+
getFilename(fileId) {
|
|
6
|
+
return fileId.toString();
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
basicTests('exclusive');
|
|
11
|
+
basicTests('shared');
|
|
12
|
+
basicTests('shared+hint');
|
|
13
|
+
|
|
14
|
+
function basicTests(policy) {
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
await clearAllLocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await clearAllLocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe(`WebLocksMixin basics ${policy}`, () => {
|
|
24
|
+
it('should make normal lock transitions', async () => {
|
|
25
|
+
let rc;
|
|
26
|
+
const tester = new Tester(null, null, { lockPolicy: policy });
|
|
27
|
+
|
|
28
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
29
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
30
|
+
|
|
31
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
32
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
33
|
+
|
|
34
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
35
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
36
|
+
|
|
37
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
38
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
39
|
+
|
|
40
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
41
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
42
|
+
|
|
43
|
+
await expectAsync(clearAllLocks()).toBeResolvedTo(0);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should make recovery lock transitions', async () => {
|
|
47
|
+
let rc;
|
|
48
|
+
const tester = new Tester(null, null, { lockPolicy: policy });
|
|
49
|
+
|
|
50
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
51
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
52
|
+
|
|
53
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
54
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
55
|
+
|
|
56
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
57
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
58
|
+
|
|
59
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
60
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
61
|
+
|
|
62
|
+
await expectAsync(clearAllLocks()).toBeResolvedTo(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should ignore repeated state requests', async () => {
|
|
66
|
+
let rc;
|
|
67
|
+
const tester = new Tester(null, null, { lockPolicy: policy });
|
|
68
|
+
|
|
69
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
70
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
71
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
72
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
73
|
+
|
|
74
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
75
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
76
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
77
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
78
|
+
|
|
79
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
80
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
81
|
+
rc = await tester.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
82
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
83
|
+
|
|
84
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
85
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
86
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
87
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
88
|
+
|
|
89
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
90
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
91
|
+
rc = await tester.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
92
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
93
|
+
|
|
94
|
+
await expectAsync(clearAllLocks()).toBeResolvedTo(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
describe('WebLocksMixin exclusive', () => {
|
|
100
|
+
beforeEach(async () => {
|
|
101
|
+
await clearAllLocks();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
afterEach(async () => {
|
|
105
|
+
await clearAllLocks();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should block multiple SHARED connections', async () => {
|
|
109
|
+
let rc;
|
|
110
|
+
const testerA = new Tester(null, null, { lockPolicy: 'exclusive' });
|
|
111
|
+
const testerB = new Tester(null, null, { lockPolicy: 'exclusive' });
|
|
112
|
+
|
|
113
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
114
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
115
|
+
|
|
116
|
+
const result = testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
117
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
118
|
+
await expectAsync(result).toBePending();
|
|
119
|
+
|
|
120
|
+
rc = await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
121
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
122
|
+
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
124
|
+
await expectAsync(result).toBeResolvedTo(SQLite.SQLITE_OK);
|
|
125
|
+
|
|
126
|
+
rc = await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
127
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should timeout', async () => {
|
|
131
|
+
let rc;
|
|
132
|
+
const testerA = new Tester(null, null, { lockPolicy: 'exclusive', lockTimeout: 5 });
|
|
133
|
+
const testerB = new Tester(null, null, { lockPolicy: 'exclusive', lockTimeout: 5 });
|
|
134
|
+
|
|
135
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
136
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
137
|
+
|
|
138
|
+
const result = testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
139
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
140
|
+
await expectAsync(result).toBeResolvedTo(SQLite.SQLITE_BUSY);
|
|
141
|
+
|
|
142
|
+
rc = await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
143
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
144
|
+
|
|
145
|
+
await expectAsync(clearAllLocks()).toBeResolvedTo(0);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('WebLocksMixin shared', () => {
|
|
150
|
+
beforeEach(async () => {
|
|
151
|
+
await clearAllLocks();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
afterEach(async () => {
|
|
155
|
+
await clearAllLocks();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should allow multiple SHARED connections', async () => {
|
|
159
|
+
let rc;
|
|
160
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared' });
|
|
161
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared' });
|
|
162
|
+
|
|
163
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
164
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
165
|
+
|
|
166
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
167
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
168
|
+
|
|
169
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
170
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should allow SHARED and RESERVED connections', async () => {
|
|
174
|
+
let rc;
|
|
175
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared' });
|
|
176
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared' });
|
|
177
|
+
|
|
178
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
179
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
180
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
181
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
182
|
+
|
|
183
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
184
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
185
|
+
|
|
186
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
187
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
188
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should return BUSY on RESERVED deadlock', async () => {
|
|
192
|
+
let rc;
|
|
193
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared' });
|
|
194
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared' });
|
|
195
|
+
|
|
196
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
197
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
198
|
+
|
|
199
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
200
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
201
|
+
|
|
202
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
203
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
204
|
+
|
|
205
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
206
|
+
expect(rc).toBe(SQLite.SQLITE_BUSY);
|
|
207
|
+
|
|
208
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
209
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
210
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should block EXCLUSIVE until SHARED connections are released', async () => {
|
|
214
|
+
let rc;
|
|
215
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared' });
|
|
216
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared' });
|
|
217
|
+
|
|
218
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
219
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
220
|
+
|
|
221
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
222
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
223
|
+
|
|
224
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
225
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
226
|
+
|
|
227
|
+
const result = testerA.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
228
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
229
|
+
await expectAsync(result).toBePending();
|
|
230
|
+
|
|
231
|
+
rc = await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
232
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
233
|
+
|
|
234
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
235
|
+
await expectAsync(result).toBeResolvedTo(SQLite.SQLITE_OK);
|
|
236
|
+
|
|
237
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
238
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should block SHARED until EXCLUSIVE connection is released', async () => {
|
|
242
|
+
let rc;
|
|
243
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared' });
|
|
244
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared' });
|
|
245
|
+
|
|
246
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
247
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
248
|
+
|
|
249
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
250
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
251
|
+
|
|
252
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
253
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
254
|
+
|
|
255
|
+
const result = testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
256
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
257
|
+
await expectAsync(result).toBePending();
|
|
258
|
+
|
|
259
|
+
rc = await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
260
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
261
|
+
|
|
262
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
263
|
+
await expectAsync(result).toBeResolvedTo(SQLite.SQLITE_OK);
|
|
264
|
+
|
|
265
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
266
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should timeout on SHARED', async () => {
|
|
270
|
+
let rc;
|
|
271
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared', lockTimeout: 5 });
|
|
272
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared', lockTimeout: 5 });
|
|
273
|
+
|
|
274
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
275
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
276
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
277
|
+
|
|
278
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
279
|
+
expect(rc).toBe(SQLite.SQLITE_BUSY);
|
|
280
|
+
|
|
281
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
282
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should timeout on EXCLUSIVE', async () => {
|
|
286
|
+
let rc;
|
|
287
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared', lockTimeout: 5 });
|
|
288
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared', lockTimeout: 5 });
|
|
289
|
+
|
|
290
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
291
|
+
await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
292
|
+
await testerB.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
293
|
+
|
|
294
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
295
|
+
expect(rc).toBe(SQLite.SQLITE_BUSY);
|
|
296
|
+
|
|
297
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
298
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
299
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('WebLocksMixin shared+hint', () => {
|
|
304
|
+
beforeEach(async () => {
|
|
305
|
+
await clearAllLocks();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
afterEach(async () => {
|
|
309
|
+
await clearAllLocks();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should allow multiple SHARED connections', async () => {
|
|
313
|
+
let rc;
|
|
314
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
315
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
316
|
+
|
|
317
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
318
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
319
|
+
|
|
320
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
321
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
322
|
+
|
|
323
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
324
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('should allow SHARED and RESERVED connections', async () => {
|
|
328
|
+
let rc;
|
|
329
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
330
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
331
|
+
|
|
332
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
333
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
334
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
335
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
336
|
+
|
|
337
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
338
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
339
|
+
|
|
340
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
341
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
342
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should return BUSY on RESERVED deadlock', async () => {
|
|
346
|
+
let rc;
|
|
347
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
348
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
349
|
+
|
|
350
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
351
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
352
|
+
|
|
353
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
354
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
355
|
+
|
|
356
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
357
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
358
|
+
|
|
359
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
360
|
+
expect(rc).toBe(SQLite.SQLITE_BUSY);
|
|
361
|
+
|
|
362
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
363
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
364
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should block EXCLUSIVE until SHARED connections are released', async () => {
|
|
368
|
+
let rc;
|
|
369
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
370
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
371
|
+
|
|
372
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
373
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
374
|
+
|
|
375
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
376
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
377
|
+
|
|
378
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
379
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
380
|
+
|
|
381
|
+
const result = testerA.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
382
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
383
|
+
await expectAsync(result).toBePending();
|
|
384
|
+
|
|
385
|
+
rc = await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
386
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
387
|
+
|
|
388
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
389
|
+
await expectAsync(result).toBeResolvedTo(SQLite.SQLITE_OK);
|
|
390
|
+
|
|
391
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
392
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it('should block SHARED until EXCLUSIVE connection is released', async () => {
|
|
396
|
+
let rc;
|
|
397
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
398
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
399
|
+
|
|
400
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
401
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
402
|
+
|
|
403
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
404
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
405
|
+
|
|
406
|
+
rc = await testerA.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
407
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
408
|
+
|
|
409
|
+
const result = testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
410
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
411
|
+
await expectAsync(result).toBePending();
|
|
412
|
+
|
|
413
|
+
rc = await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
414
|
+
expect(rc).toBe(SQLite.SQLITE_OK);
|
|
415
|
+
|
|
416
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
417
|
+
await expectAsync(result).toBeResolvedTo(SQLite.SQLITE_OK);
|
|
418
|
+
|
|
419
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
420
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should timeout on SHARED', async () => {
|
|
424
|
+
let rc;
|
|
425
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint', lockTimeout: 5 });
|
|
426
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint', lockTimeout: 5 });
|
|
427
|
+
|
|
428
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
429
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
430
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
431
|
+
|
|
432
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
433
|
+
expect(rc).toBe(SQLite.SQLITE_BUSY);
|
|
434
|
+
|
|
435
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
436
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should timeout on EXCLUSIVE', async () => {
|
|
440
|
+
let rc;
|
|
441
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint', lockTimeout: 5 });
|
|
442
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint', lockTimeout: 5 });
|
|
443
|
+
|
|
444
|
+
await testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
445
|
+
await testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
446
|
+
await testerB.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
447
|
+
|
|
448
|
+
rc = await testerB.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
449
|
+
expect(rc).toBe(SQLite.SQLITE_BUSY);
|
|
450
|
+
|
|
451
|
+
await testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
452
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
453
|
+
await testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should not deadlock with hint', async () => {
|
|
457
|
+
let rc;
|
|
458
|
+
const testerA = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
459
|
+
const testerB = new Tester(null, null, { lockPolicy: 'shared+hint' });
|
|
460
|
+
|
|
461
|
+
const resultA = Promise.resolve().then(() => {
|
|
462
|
+
testerA.jFileControl(1, WebLocksMixin.WRITE_HINT_OP_CODE, null);
|
|
463
|
+
return testerA.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
464
|
+
}).then(result => {
|
|
465
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
466
|
+
return testerA.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
467
|
+
}).then(result => {
|
|
468
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
469
|
+
return testerA.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
470
|
+
}).then(result => {
|
|
471
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
472
|
+
return testerA.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
473
|
+
}).then(result => {
|
|
474
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
475
|
+
return testerA.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const resultB = Promise.resolve().then(() => {
|
|
479
|
+
testerB.jFileControl(1, WebLocksMixin.WRITE_HINT_OP_CODE, null);
|
|
480
|
+
return testerB.jLock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
481
|
+
}).then(result => {
|
|
482
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
483
|
+
return testerB.jLock(1, SQLite.SQLITE_LOCK_RESERVED);
|
|
484
|
+
}).then(result => {
|
|
485
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
486
|
+
return testerB.jLock(1, SQLite.SQLITE_LOCK_EXCLUSIVE);
|
|
487
|
+
}).then(result => {
|
|
488
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
489
|
+
return testerB.jUnlock(1, SQLite.SQLITE_LOCK_SHARED);
|
|
490
|
+
}).then(result => {
|
|
491
|
+
if (result !== SQLite.SQLITE_OK) throw Object.assign(new Error('failed'), { result });
|
|
492
|
+
return testerB.jUnlock(1, SQLite.SQLITE_LOCK_NONE);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
await expectAsync(resultA).toBeResolvedTo(SQLite.SQLITE_OK);
|
|
496
|
+
await expectAsync(resultB).toBeResolvedTo(SQLite.SQLITE_OK);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
async function clearAllLocks() {
|
|
501
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
502
|
+
|
|
503
|
+
let count = 0;
|
|
504
|
+
while (true) {
|
|
505
|
+
const locks = await navigator.locks.query();
|
|
506
|
+
const lockNames = [...locks.held, ...locks.pending]
|
|
507
|
+
.map(lock => lock.name)
|
|
508
|
+
.filter(name => name.startsWith('lock##'));
|
|
509
|
+
if (lockNames.length === 0) {
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
for (const name of new Set(lockNames)) {
|
|
514
|
+
await navigator.locks.request(name, { steal: true }, async lock => {
|
|
515
|
+
});
|
|
516
|
+
count++;
|
|
517
|
+
}
|
|
518
|
+
await new Promise(resolve => setTimeout(resolve));
|
|
519
|
+
}
|
|
520
|
+
return count;
|
|
521
|
+
}
|
package/test/api.test.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { TestContext } from "./TestContext.js";
|
|
2
|
+
import { api_exec } from "./api_exec.js";
|
|
3
|
+
import { api_misc } from "./api_misc.js";
|
|
4
|
+
import { api_statements } from "./api_statements.js";
|
|
5
|
+
|
|
6
|
+
const ALL_BUILDS = ['default', 'asyncify'];
|
|
7
|
+
const ASYNC_BUILDS = ['asyncify'];
|
|
8
|
+
|
|
9
|
+
const supportsJSPI = await TestContext.supportsJSPI();
|
|
10
|
+
if (supportsJSPI) {
|
|
11
|
+
ALL_BUILDS.push('jspi');
|
|
12
|
+
ASYNC_BUILDS.push('jspi');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @type {Map<string, string[]>} */
|
|
16
|
+
const CONFIGS = new Map([
|
|
17
|
+
['', ALL_BUILDS],
|
|
18
|
+
['MemoryVFS', ALL_BUILDS],
|
|
19
|
+
['AccessHandlePoolVFS', ALL_BUILDS],
|
|
20
|
+
['OPFSCoopSyncVFS', ALL_BUILDS],
|
|
21
|
+
['MemoryAsyncVFS', ASYNC_BUILDS],
|
|
22
|
+
['IDBBatchAtomicVFS', ASYNC_BUILDS],
|
|
23
|
+
['IDBMirrorVFS', ASYNC_BUILDS],
|
|
24
|
+
['OPFSAdaptiveVFS', ASYNC_BUILDS],
|
|
25
|
+
['OPFSAnyContextVFS', ASYNC_BUILDS],
|
|
26
|
+
['OPFSPermutedVFS', ASYNC_BUILDS],
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
describe('SQLite API', function() {
|
|
30
|
+
for (const [config, builds] of CONFIGS) {
|
|
31
|
+
describe(config, function() {
|
|
32
|
+
for (const build of builds) {
|
|
33
|
+
describe(build, function() {
|
|
34
|
+
apiSpecs(build, config);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function apiSpecs(build, config) {
|
|
42
|
+
const context = new TestContext({ build, config });
|
|
43
|
+
|
|
44
|
+
describe(`SQLite ${build} ${config}`, function() {
|
|
45
|
+
api_exec(context);
|
|
46
|
+
api_misc(context);
|
|
47
|
+
api_statements(context);
|
|
48
|
+
});
|
|
49
|
+
}
|
package/test/api_exec.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as Comlink from 'comlink';
|
|
2
|
+
import * as SQLite from '../src/sqlite-api.js';
|
|
3
|
+
|
|
4
|
+
export function api_exec(context) {
|
|
5
|
+
describe('exec', function() {
|
|
6
|
+
let proxy, sqlite3, db;
|
|
7
|
+
beforeEach(async function() {
|
|
8
|
+
proxy = await context.create();
|
|
9
|
+
sqlite3 = proxy.sqlite3;
|
|
10
|
+
db = await sqlite3.open_v2('demo');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(async function() {
|
|
14
|
+
await sqlite3.close(db);
|
|
15
|
+
await context.destroy(proxy);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should execute a query', async function() {
|
|
19
|
+
let rc;
|
|
20
|
+
rc = await sqlite3.exec(db, 'CREATE TABLE t(x)');
|
|
21
|
+
expect(rc).toEqual(SQLite.SQLITE_OK);
|
|
22
|
+
|
|
23
|
+
rc = await sqlite3.exec(db, 'INSERT INTO t VALUES (1), (2), (3)');
|
|
24
|
+
expect(rc).toEqual(SQLite.SQLITE_OK);
|
|
25
|
+
|
|
26
|
+
const nChanges = await sqlite3.changes(db);
|
|
27
|
+
expect(nChanges).toEqual(3);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should execute multiple queries', async function() {
|
|
31
|
+
let rc;
|
|
32
|
+
rc = await sqlite3.exec(db, `
|
|
33
|
+
CREATE TABLE t(x);
|
|
34
|
+
INSERT INTO t VALUES (1), (2), (3);
|
|
35
|
+
`);
|
|
36
|
+
expect(rc).toEqual(SQLite.SQLITE_OK);
|
|
37
|
+
await expectAsync(sqlite3.changes(db)).toBeResolvedTo(3);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should return query results via callback', async function() {
|
|
41
|
+
const results = { rows: [], columns: [] };
|
|
42
|
+
const rc = await sqlite3.exec(db, `
|
|
43
|
+
CREATE TABLE t(x);
|
|
44
|
+
INSERT INTO t VALUES (1), (2), (3);
|
|
45
|
+
SELECT * FROM t ORDER BY x;
|
|
46
|
+
`, Comlink.proxy((row, columns) => {
|
|
47
|
+
if (columns.length) {
|
|
48
|
+
results.columns = columns;
|
|
49
|
+
results.rows.push(row);
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
expect(rc).toEqual(SQLite.SQLITE_OK);
|
|
53
|
+
expect(results).toEqual({ columns: ['x'], rows: [[1], [2], [3]] });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should allow a transaction to span multiple calls', async function() {
|
|
57
|
+
let rc;
|
|
58
|
+
rc = await sqlite3.get_autocommit(db);
|
|
59
|
+
expect(rc).not.toEqual(0);
|
|
60
|
+
|
|
61
|
+
rc = await sqlite3.exec(db, 'BEGIN TRANSACTION');
|
|
62
|
+
expect(rc).toEqual(SQLite.SQLITE_OK);
|
|
63
|
+
|
|
64
|
+
rc = await sqlite3.get_autocommit(db);
|
|
65
|
+
expect(rc).toEqual(0);
|
|
66
|
+
|
|
67
|
+
rc = await sqlite3.exec(db, `
|
|
68
|
+
CREATE TABLE t AS
|
|
69
|
+
WITH RECURSIVE cnt(x) AS (
|
|
70
|
+
SELECT 1
|
|
71
|
+
UNION ALL
|
|
72
|
+
SELECT x+1 FROM cnt
|
|
73
|
+
LIMIT 100
|
|
74
|
+
)
|
|
75
|
+
SELECT x FROM cnt;
|
|
76
|
+
`);
|
|
77
|
+
expect(rc).toEqual(SQLite.SQLITE_OK);
|
|
78
|
+
|
|
79
|
+
rc = await sqlite3.get_autocommit(db);
|
|
80
|
+
expect(rc).toEqual(0);
|
|
81
|
+
|
|
82
|
+
rc = await sqlite3.exec(db, 'COMMIT');
|
|
83
|
+
expect(rc).toEqual(SQLite.SQLITE_OK);
|
|
84
|
+
|
|
85
|
+
rc = await sqlite3.get_autocommit(db);
|
|
86
|
+
expect(rc).not.toEqual(0);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|