@powersync/service-core-tests 0.14.0 → 0.15.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/CHANGELOG.md +19 -0
- package/dist/test-utils/general-utils.d.ts +9 -2
- package/dist/test-utils/general-utils.js +26 -2
- package/dist/test-utils/general-utils.js.map +1 -1
- package/dist/tests/register-compacting-tests.d.ts +1 -1
- package/dist/tests/register-compacting-tests.js +122 -68
- package/dist/tests/register-compacting-tests.js.map +1 -1
- package/dist/tests/register-data-storage-checkpoint-tests.d.ts +1 -1
- package/dist/tests/register-data-storage-checkpoint-tests.js +38 -6
- package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
- package/dist/tests/register-data-storage-data-tests.d.ts +2 -2
- package/dist/tests/register-data-storage-data-tests.js +666 -142
- package/dist/tests/register-data-storage-data-tests.js.map +1 -1
- package/dist/tests/register-data-storage-parameter-tests.d.ts +1 -1
- package/dist/tests/register-data-storage-parameter-tests.js +60 -33
- package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
- package/dist/tests/register-parameter-compacting-tests.d.ts +1 -1
- package/dist/tests/register-parameter-compacting-tests.js +8 -4
- package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
- package/dist/tests/register-sync-tests.d.ts +2 -1
- package/dist/tests/register-sync-tests.js +40 -18
- package/dist/tests/register-sync-tests.js.map +1 -1
- package/dist/tests/util.d.ts +5 -4
- package/dist/tests/util.js +27 -12
- package/dist/tests/util.js.map +1 -1
- package/package.json +3 -3
- package/src/test-utils/general-utils.ts +41 -3
- package/src/tests/register-compacting-tests.ts +127 -82
- package/src/tests/register-data-storage-checkpoint-tests.ts +64 -11
- package/src/tests/register-data-storage-data-tests.ts +640 -52
- package/src/tests/register-data-storage-parameter-tests.ts +101 -47
- package/src/tests/register-parameter-compacting-tests.ts +9 -4
- package/src/tests/register-sync-tests.ts +45 -19
- package/src/tests/util.ts +46 -17
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BucketDataBatchOptions,
|
|
3
|
+
CURRENT_STORAGE_VERSION,
|
|
3
4
|
getUuidReplicaIdentityBson,
|
|
4
5
|
OplogEntry,
|
|
6
|
+
reduceBucket,
|
|
5
7
|
storage,
|
|
6
8
|
updateSyncRulesFromYaml
|
|
7
9
|
} from '@powersync/service-core';
|
|
8
10
|
import { describe, expect, test } from 'vitest';
|
|
9
11
|
import * as test_utils from '../test-utils/test-utils-index.js';
|
|
10
|
-
import { bucketRequest
|
|
12
|
+
import { bucketRequest } from '../test-utils/test-utils-index.js';
|
|
13
|
+
import { bucketRequestMap, bucketRequests } from './util.js';
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Normalize data from OplogEntries for comparison in tests.
|
|
@@ -30,21 +33,30 @@ const normalizeOplogData = (data: OplogEntry['data']) => {
|
|
|
30
33
|
*
|
|
31
34
|
* ```
|
|
32
35
|
*/
|
|
33
|
-
export function registerDataStorageDataTests(
|
|
36
|
+
export function registerDataStorageDataTests(config: storage.TestStorageConfig) {
|
|
37
|
+
const generateStorageFactory = config.factory;
|
|
38
|
+
const storageVersion = config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
|
|
39
|
+
|
|
40
|
+
const TEST_TABLE = test_utils.makeTestTable('test', ['id'], config);
|
|
41
|
+
|
|
34
42
|
test('removing row', async () => {
|
|
35
43
|
await using factory = await generateStorageFactory();
|
|
36
44
|
const syncRules = await factory.updateSyncRules(
|
|
37
|
-
updateSyncRulesFromYaml(
|
|
45
|
+
updateSyncRulesFromYaml(
|
|
46
|
+
`
|
|
38
47
|
bucket_definitions:
|
|
39
48
|
global:
|
|
40
49
|
data:
|
|
41
50
|
- SELECT id, description FROM "%"
|
|
42
|
-
|
|
51
|
+
`,
|
|
52
|
+
{ storageVersion }
|
|
53
|
+
)
|
|
43
54
|
);
|
|
44
55
|
const bucketStorage = factory.getInstance(syncRules);
|
|
45
56
|
|
|
46
57
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
47
58
|
const sourceTable = TEST_TABLE;
|
|
59
|
+
await batch.markAllSnapshotDone('1/1');
|
|
48
60
|
|
|
49
61
|
await batch.save({
|
|
50
62
|
sourceTable,
|
|
@@ -89,27 +101,327 @@ bucket_definitions:
|
|
|
89
101
|
];
|
|
90
102
|
expect(checksums).toEqual([
|
|
91
103
|
{
|
|
92
|
-
bucket: bucketRequest(syncRules, 'global[]'),
|
|
104
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
93
105
|
checksum: (c1 + c2) & 0xffffffff,
|
|
94
106
|
count: 2
|
|
95
107
|
}
|
|
96
108
|
]);
|
|
97
109
|
});
|
|
98
110
|
|
|
111
|
+
test('insert after delete in new batch', async () => {
|
|
112
|
+
await using factory = await generateStorageFactory();
|
|
113
|
+
const syncRules = await factory.updateSyncRules(
|
|
114
|
+
updateSyncRulesFromYaml(
|
|
115
|
+
`
|
|
116
|
+
bucket_definitions:
|
|
117
|
+
global:
|
|
118
|
+
data:
|
|
119
|
+
- SELECT id, description FROM "%"
|
|
120
|
+
`,
|
|
121
|
+
{ storageVersion }
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
125
|
+
|
|
126
|
+
const sourceTable = TEST_TABLE;
|
|
127
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
128
|
+
await batch.markAllSnapshotDone('1/1');
|
|
129
|
+
|
|
130
|
+
await batch.save({
|
|
131
|
+
sourceTable,
|
|
132
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
133
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await batch.commit('0/1');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
140
|
+
const sourceTable = TEST_TABLE;
|
|
141
|
+
|
|
142
|
+
await batch.save({
|
|
143
|
+
sourceTable,
|
|
144
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
145
|
+
after: {
|
|
146
|
+
id: 'test1',
|
|
147
|
+
description: 'test1'
|
|
148
|
+
},
|
|
149
|
+
afterReplicaId: test_utils.rid('test1')
|
|
150
|
+
});
|
|
151
|
+
await batch.commit('2/1');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
155
|
+
|
|
156
|
+
const batch = await test_utils.fromAsync(
|
|
157
|
+
bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]))
|
|
158
|
+
);
|
|
159
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
160
|
+
return {
|
|
161
|
+
op: d.op,
|
|
162
|
+
object_id: d.object_id,
|
|
163
|
+
checksum: d.checksum
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const c1 = 2871785649;
|
|
168
|
+
|
|
169
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
170
|
+
|
|
171
|
+
const checksums = [
|
|
172
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
173
|
+
];
|
|
174
|
+
expect(checksums).toEqual([
|
|
175
|
+
{
|
|
176
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
177
|
+
checksum: c1 & 0xffffffff,
|
|
178
|
+
count: 1
|
|
179
|
+
}
|
|
180
|
+
]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test('update after delete in new batch', async () => {
|
|
184
|
+
// Update after delete may not be common, but the storage layer should handle it in an eventually-consistent way.
|
|
185
|
+
await using factory = await generateStorageFactory();
|
|
186
|
+
const syncRules = await factory.updateSyncRules(
|
|
187
|
+
updateSyncRulesFromYaml(
|
|
188
|
+
`
|
|
189
|
+
bucket_definitions:
|
|
190
|
+
global:
|
|
191
|
+
data:
|
|
192
|
+
- SELECT id, description FROM "%"
|
|
193
|
+
`,
|
|
194
|
+
{ storageVersion }
|
|
195
|
+
)
|
|
196
|
+
);
|
|
197
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
198
|
+
|
|
199
|
+
const sourceTable = TEST_TABLE;
|
|
200
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
201
|
+
await batch.markAllSnapshotDone('1/1');
|
|
202
|
+
|
|
203
|
+
await batch.save({
|
|
204
|
+
sourceTable,
|
|
205
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
206
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await batch.commit('0/1');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
213
|
+
const sourceTable = TEST_TABLE;
|
|
214
|
+
|
|
215
|
+
await batch.save({
|
|
216
|
+
sourceTable,
|
|
217
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
218
|
+
before: {
|
|
219
|
+
id: 'test1'
|
|
220
|
+
},
|
|
221
|
+
after: {
|
|
222
|
+
id: 'test1',
|
|
223
|
+
description: 'test1'
|
|
224
|
+
},
|
|
225
|
+
beforeReplicaId: test_utils.rid('test1'),
|
|
226
|
+
afterReplicaId: test_utils.rid('test1')
|
|
227
|
+
});
|
|
228
|
+
await batch.commit('2/1');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
232
|
+
|
|
233
|
+
const batch = await test_utils.fromAsync(
|
|
234
|
+
bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]))
|
|
235
|
+
);
|
|
236
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
237
|
+
return {
|
|
238
|
+
op: d.op,
|
|
239
|
+
object_id: d.object_id,
|
|
240
|
+
checksum: d.checksum
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const c1 = 2871785649;
|
|
245
|
+
|
|
246
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
247
|
+
|
|
248
|
+
const checksums = [
|
|
249
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
250
|
+
];
|
|
251
|
+
expect(checksums).toEqual([
|
|
252
|
+
{
|
|
253
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
254
|
+
checksum: c1 & 0xffffffff,
|
|
255
|
+
count: 1
|
|
256
|
+
}
|
|
257
|
+
]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test('insert after delete in same batch', async () => {
|
|
261
|
+
await using factory = await generateStorageFactory();
|
|
262
|
+
const syncRules = await factory.updateSyncRules(
|
|
263
|
+
updateSyncRulesFromYaml(
|
|
264
|
+
`
|
|
265
|
+
bucket_definitions:
|
|
266
|
+
global:
|
|
267
|
+
data:
|
|
268
|
+
- SELECT id, description FROM "%"
|
|
269
|
+
`,
|
|
270
|
+
{
|
|
271
|
+
storageVersion
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
276
|
+
|
|
277
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
278
|
+
const sourceTable = TEST_TABLE;
|
|
279
|
+
await batch.markAllSnapshotDone('1/1');
|
|
280
|
+
|
|
281
|
+
await batch.save({
|
|
282
|
+
sourceTable,
|
|
283
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
284
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
285
|
+
});
|
|
286
|
+
await batch.save({
|
|
287
|
+
sourceTable,
|
|
288
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
289
|
+
after: {
|
|
290
|
+
id: 'test1',
|
|
291
|
+
description: 'test1'
|
|
292
|
+
},
|
|
293
|
+
afterReplicaId: test_utils.rid('test1')
|
|
294
|
+
});
|
|
295
|
+
await batch.commit('1/1');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
299
|
+
|
|
300
|
+
const batch = await test_utils.fromAsync(
|
|
301
|
+
bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]))
|
|
302
|
+
);
|
|
303
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
304
|
+
return {
|
|
305
|
+
op: d.op,
|
|
306
|
+
object_id: d.object_id,
|
|
307
|
+
checksum: d.checksum
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const c1 = 2871785649;
|
|
312
|
+
|
|
313
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
314
|
+
|
|
315
|
+
const checksums = [
|
|
316
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
317
|
+
];
|
|
318
|
+
expect(checksums).toEqual([
|
|
319
|
+
{
|
|
320
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
321
|
+
checksum: c1 & 0xffffffff,
|
|
322
|
+
count: 1
|
|
323
|
+
}
|
|
324
|
+
]);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
test('(insert, delete, insert), (delete)', async () => {
|
|
328
|
+
await using factory = await generateStorageFactory();
|
|
329
|
+
const syncRules = await factory.updateSyncRules(
|
|
330
|
+
updateSyncRulesFromYaml(
|
|
331
|
+
`
|
|
332
|
+
bucket_definitions:
|
|
333
|
+
global:
|
|
334
|
+
data:
|
|
335
|
+
- SELECT id, description FROM "%"
|
|
336
|
+
`,
|
|
337
|
+
{
|
|
338
|
+
storageVersion
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
);
|
|
342
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
343
|
+
|
|
344
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
345
|
+
const sourceTable = TEST_TABLE;
|
|
346
|
+
await batch.markAllSnapshotDone('1/1');
|
|
347
|
+
|
|
348
|
+
await batch.save({
|
|
349
|
+
sourceTable,
|
|
350
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
351
|
+
after: {
|
|
352
|
+
id: 'test1',
|
|
353
|
+
description: 'test1'
|
|
354
|
+
},
|
|
355
|
+
afterReplicaId: test_utils.rid('test1')
|
|
356
|
+
});
|
|
357
|
+
await batch.save({
|
|
358
|
+
sourceTable,
|
|
359
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
360
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
361
|
+
});
|
|
362
|
+
await batch.save({
|
|
363
|
+
sourceTable,
|
|
364
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
365
|
+
after: {
|
|
366
|
+
id: 'test1',
|
|
367
|
+
description: 'test1'
|
|
368
|
+
},
|
|
369
|
+
afterReplicaId: test_utils.rid('test1')
|
|
370
|
+
});
|
|
371
|
+
await batch.commit('1/1');
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
375
|
+
const sourceTable = TEST_TABLE;
|
|
376
|
+
await batch.markAllSnapshotDone('1/1');
|
|
377
|
+
|
|
378
|
+
await batch.save({
|
|
379
|
+
sourceTable,
|
|
380
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
381
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
382
|
+
});
|
|
383
|
+
await batch.commit('2/1');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
387
|
+
|
|
388
|
+
const batch = await test_utils.fromAsync(
|
|
389
|
+
bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]))
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
expect(reduceBucket(batch[0].chunkData.data).slice(1)).toEqual([]);
|
|
393
|
+
|
|
394
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
395
|
+
return {
|
|
396
|
+
op: d.op,
|
|
397
|
+
object_id: d.object_id,
|
|
398
|
+
checksum: d.checksum
|
|
399
|
+
};
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
expect(data).toMatchSnapshot();
|
|
403
|
+
});
|
|
404
|
+
|
|
99
405
|
test('changing client ids', async () => {
|
|
100
406
|
await using factory = await generateStorageFactory();
|
|
101
407
|
const syncRules = await factory.updateSyncRules(
|
|
102
|
-
updateSyncRulesFromYaml(
|
|
408
|
+
updateSyncRulesFromYaml(
|
|
409
|
+
`
|
|
103
410
|
bucket_definitions:
|
|
104
411
|
global:
|
|
105
412
|
data:
|
|
106
413
|
- SELECT client_id as id, description FROM "%"
|
|
107
|
-
|
|
414
|
+
`,
|
|
415
|
+
{
|
|
416
|
+
storageVersion
|
|
417
|
+
}
|
|
418
|
+
)
|
|
108
419
|
);
|
|
109
420
|
const bucketStorage = factory.getInstance(syncRules);
|
|
110
421
|
|
|
111
422
|
const sourceTable = TEST_TABLE;
|
|
112
423
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
424
|
+
await batch.markAllSnapshotDone('1/1');
|
|
113
425
|
await batch.save({
|
|
114
426
|
sourceTable,
|
|
115
427
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -166,17 +478,23 @@ bucket_definitions:
|
|
|
166
478
|
test('re-apply delete', async () => {
|
|
167
479
|
await using factory = await generateStorageFactory();
|
|
168
480
|
const syncRules = await factory.updateSyncRules(
|
|
169
|
-
updateSyncRulesFromYaml(
|
|
481
|
+
updateSyncRulesFromYaml(
|
|
482
|
+
`
|
|
170
483
|
bucket_definitions:
|
|
171
484
|
global:
|
|
172
485
|
data:
|
|
173
486
|
- SELECT id, description FROM "%"
|
|
174
|
-
|
|
487
|
+
`,
|
|
488
|
+
{
|
|
489
|
+
storageVersion
|
|
490
|
+
}
|
|
491
|
+
)
|
|
175
492
|
);
|
|
176
493
|
const bucketStorage = factory.getInstance(syncRules);
|
|
177
494
|
|
|
178
495
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
179
496
|
const sourceTable = TEST_TABLE;
|
|
497
|
+
await batch.markAllSnapshotDone('1/1');
|
|
180
498
|
|
|
181
499
|
await batch.save({
|
|
182
500
|
sourceTable,
|
|
@@ -237,7 +555,7 @@ bucket_definitions:
|
|
|
237
555
|
];
|
|
238
556
|
expect(checksums).toEqual([
|
|
239
557
|
{
|
|
240
|
-
bucket: bucketRequest(syncRules, 'global[]'),
|
|
558
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
241
559
|
checksum: (c1 + c2) & 0xffffffff,
|
|
242
560
|
count: 2
|
|
243
561
|
}
|
|
@@ -247,16 +565,20 @@ bucket_definitions:
|
|
|
247
565
|
test('re-apply update + delete', async () => {
|
|
248
566
|
await using factory = await generateStorageFactory();
|
|
249
567
|
const syncRules = await factory.updateSyncRules(
|
|
250
|
-
updateSyncRulesFromYaml(
|
|
568
|
+
updateSyncRulesFromYaml(
|
|
569
|
+
`
|
|
251
570
|
bucket_definitions:
|
|
252
571
|
global:
|
|
253
572
|
data:
|
|
254
573
|
- SELECT id, description FROM "%"
|
|
255
|
-
|
|
574
|
+
`,
|
|
575
|
+
{ storageVersion }
|
|
576
|
+
)
|
|
256
577
|
);
|
|
257
578
|
const bucketStorage = factory.getInstance(syncRules);
|
|
258
579
|
|
|
259
580
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
581
|
+
await batch.markAllSnapshotDone('1/1');
|
|
260
582
|
const sourceTable = TEST_TABLE;
|
|
261
583
|
|
|
262
584
|
await batch.save({
|
|
@@ -271,6 +593,7 @@ bucket_definitions:
|
|
|
271
593
|
});
|
|
272
594
|
|
|
273
595
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
596
|
+
await batch.markAllSnapshotDone('1/1');
|
|
274
597
|
const sourceTable = TEST_TABLE;
|
|
275
598
|
|
|
276
599
|
await batch.save({
|
|
@@ -303,6 +626,7 @@ bucket_definitions:
|
|
|
303
626
|
});
|
|
304
627
|
|
|
305
628
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
629
|
+
await batch.markAllSnapshotDone('1/1');
|
|
306
630
|
const sourceTable = TEST_TABLE;
|
|
307
631
|
|
|
308
632
|
await batch.save({
|
|
@@ -363,7 +687,7 @@ bucket_definitions:
|
|
|
363
687
|
];
|
|
364
688
|
expect(checksums).toEqual([
|
|
365
689
|
{
|
|
366
|
-
bucket: bucketRequest(syncRules, 'global[]'),
|
|
690
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
367
691
|
checksum: (c1 + c1 + c1 + c2) & 0xffffffff,
|
|
368
692
|
count: 4
|
|
369
693
|
}
|
|
@@ -381,17 +705,21 @@ bucket_definitions:
|
|
|
381
705
|
|
|
382
706
|
await using factory = await generateStorageFactory();
|
|
383
707
|
const syncRules = await factory.updateSyncRules(
|
|
384
|
-
updateSyncRulesFromYaml(
|
|
708
|
+
updateSyncRulesFromYaml(
|
|
709
|
+
`
|
|
385
710
|
bucket_definitions:
|
|
386
711
|
global:
|
|
387
712
|
data:
|
|
388
713
|
- SELECT id, description FROM "test"
|
|
389
|
-
|
|
714
|
+
`,
|
|
715
|
+
{ storageVersion }
|
|
716
|
+
)
|
|
390
717
|
);
|
|
391
718
|
const bucketStorage = factory.getInstance(syncRules);
|
|
392
719
|
|
|
393
720
|
// Pre-setup
|
|
394
721
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
722
|
+
await batch.markAllSnapshotDone('1/1');
|
|
395
723
|
const sourceTable = TEST_TABLE;
|
|
396
724
|
|
|
397
725
|
await batch.save({
|
|
@@ -539,19 +867,25 @@ bucket_definitions:
|
|
|
539
867
|
}
|
|
540
868
|
await using factory = await generateStorageFactory();
|
|
541
869
|
const syncRules = await factory.updateSyncRules(
|
|
542
|
-
updateSyncRulesFromYaml(
|
|
870
|
+
updateSyncRulesFromYaml(
|
|
871
|
+
`
|
|
543
872
|
bucket_definitions:
|
|
544
873
|
global:
|
|
545
874
|
data:
|
|
546
875
|
- SELECT id, description FROM "test"
|
|
547
|
-
|
|
876
|
+
`,
|
|
877
|
+
{
|
|
878
|
+
storageVersion
|
|
879
|
+
}
|
|
880
|
+
)
|
|
548
881
|
);
|
|
549
882
|
const bucketStorage = factory.getInstance(syncRules);
|
|
550
883
|
|
|
551
|
-
const sourceTable = test_utils.makeTestTable('test', ['id', 'description']);
|
|
884
|
+
const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
|
|
552
885
|
|
|
553
886
|
// Pre-setup
|
|
554
887
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
888
|
+
await batch.markAllSnapshotDone('1/1');
|
|
555
889
|
await batch.save({
|
|
556
890
|
sourceTable,
|
|
557
891
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -647,19 +981,25 @@ bucket_definitions:
|
|
|
647
981
|
|
|
648
982
|
await using factory = await generateStorageFactory();
|
|
649
983
|
const syncRules = await factory.updateSyncRules(
|
|
650
|
-
updateSyncRulesFromYaml(
|
|
984
|
+
updateSyncRulesFromYaml(
|
|
985
|
+
`
|
|
651
986
|
bucket_definitions:
|
|
652
987
|
global:
|
|
653
988
|
data:
|
|
654
989
|
- SELECT id, description FROM "test"
|
|
655
|
-
|
|
990
|
+
`,
|
|
991
|
+
{
|
|
992
|
+
storageVersion
|
|
993
|
+
}
|
|
994
|
+
)
|
|
656
995
|
);
|
|
657
996
|
const bucketStorage = factory.getInstance(syncRules);
|
|
658
997
|
|
|
659
|
-
const sourceTable = test_utils.makeTestTable('test', ['id', 'description']);
|
|
998
|
+
const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
|
|
660
999
|
|
|
661
1000
|
// Pre-setup
|
|
662
1001
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1002
|
+
await batch.markAllSnapshotDone('1/1');
|
|
663
1003
|
await batch.save({
|
|
664
1004
|
sourceTable,
|
|
665
1005
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -745,16 +1085,22 @@ bucket_definitions:
|
|
|
745
1085
|
// and the test will have to updated when other implementations are added.
|
|
746
1086
|
await using factory = await generateStorageFactory();
|
|
747
1087
|
const syncRules = await factory.updateSyncRules(
|
|
748
|
-
updateSyncRulesFromYaml(
|
|
1088
|
+
updateSyncRulesFromYaml(
|
|
1089
|
+
`
|
|
749
1090
|
bucket_definitions:
|
|
750
1091
|
global:
|
|
751
1092
|
data:
|
|
752
1093
|
- SELECT id, description FROM "%"
|
|
753
|
-
|
|
1094
|
+
`,
|
|
1095
|
+
{
|
|
1096
|
+
storageVersion
|
|
1097
|
+
}
|
|
1098
|
+
)
|
|
754
1099
|
);
|
|
755
1100
|
const bucketStorage = factory.getInstance(syncRules);
|
|
756
1101
|
|
|
757
1102
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1103
|
+
await batch.markAllSnapshotDone('1/1');
|
|
758
1104
|
const sourceTable = TEST_TABLE;
|
|
759
1105
|
|
|
760
1106
|
const largeDescription = '0123456789'.repeat(12_000_00);
|
|
@@ -854,16 +1200,22 @@ bucket_definitions:
|
|
|
854
1200
|
// Test syncing a batch of data that is limited by count.
|
|
855
1201
|
await using factory = await generateStorageFactory();
|
|
856
1202
|
const syncRules = await factory.updateSyncRules(
|
|
857
|
-
updateSyncRulesFromYaml(
|
|
1203
|
+
updateSyncRulesFromYaml(
|
|
1204
|
+
`
|
|
858
1205
|
bucket_definitions:
|
|
859
1206
|
global:
|
|
860
1207
|
data:
|
|
861
1208
|
- SELECT id, description FROM "%"
|
|
862
|
-
|
|
1209
|
+
`,
|
|
1210
|
+
{
|
|
1211
|
+
storageVersion
|
|
1212
|
+
}
|
|
1213
|
+
)
|
|
863
1214
|
);
|
|
864
1215
|
const bucketStorage = factory.getInstance(syncRules);
|
|
865
1216
|
|
|
866
1217
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1218
|
+
await batch.markAllSnapshotDone('1/1');
|
|
867
1219
|
const sourceTable = TEST_TABLE;
|
|
868
1220
|
|
|
869
1221
|
for (let i = 1; i <= 6; i++) {
|
|
@@ -938,7 +1290,8 @@ bucket_definitions:
|
|
|
938
1290
|
const setup = async (options: BucketDataBatchOptions) => {
|
|
939
1291
|
await using factory = await generateStorageFactory();
|
|
940
1292
|
const syncRules = await factory.updateSyncRules(
|
|
941
|
-
updateSyncRulesFromYaml(
|
|
1293
|
+
updateSyncRulesFromYaml(
|
|
1294
|
+
`
|
|
942
1295
|
bucket_definitions:
|
|
943
1296
|
global1:
|
|
944
1297
|
data:
|
|
@@ -946,11 +1299,14 @@ bucket_definitions:
|
|
|
946
1299
|
global2:
|
|
947
1300
|
data:
|
|
948
1301
|
- SELECT id, description FROM test WHERE bucket = 'global2'
|
|
949
|
-
|
|
1302
|
+
`,
|
|
1303
|
+
{ storageVersion }
|
|
1304
|
+
)
|
|
950
1305
|
);
|
|
951
1306
|
const bucketStorage = factory.getInstance(syncRules);
|
|
952
1307
|
|
|
953
1308
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1309
|
+
await batch.markAllSnapshotDone('1/1');
|
|
954
1310
|
const sourceTable = TEST_TABLE;
|
|
955
1311
|
|
|
956
1312
|
for (let i = 1; i <= 10; i++) {
|
|
@@ -988,8 +1344,8 @@ bucket_definitions:
|
|
|
988
1344
|
const { batch, syncRules } = await setup({ limit: 5 });
|
|
989
1345
|
expect(batch.length).toEqual(2);
|
|
990
1346
|
|
|
991
|
-
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]'));
|
|
992
|
-
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]'));
|
|
1347
|
+
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
|
|
1348
|
+
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
993
1349
|
|
|
994
1350
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
995
1351
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
@@ -1019,8 +1375,8 @@ bucket_definitions:
|
|
|
1019
1375
|
const { batch, syncRules } = await setup({ limit: 11 });
|
|
1020
1376
|
expect(batch.length).toEqual(2);
|
|
1021
1377
|
|
|
1022
|
-
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]'));
|
|
1023
|
-
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]'));
|
|
1378
|
+
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
|
|
1379
|
+
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
1024
1380
|
|
|
1025
1381
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
1026
1382
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
@@ -1056,9 +1412,9 @@ bucket_definitions:
|
|
|
1056
1412
|
const { batch, syncRules } = await setup({ limit: 3, chunkLimitBytes: 50 });
|
|
1057
1413
|
|
|
1058
1414
|
expect(batch.length).toEqual(3);
|
|
1059
|
-
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]'));
|
|
1060
|
-
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]'));
|
|
1061
|
-
expect(batch[2].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]'));
|
|
1415
|
+
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
|
|
1416
|
+
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
1417
|
+
expect(batch[2].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
1062
1418
|
|
|
1063
1419
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
1064
1420
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
@@ -1103,6 +1459,7 @@ bucket_definitions:
|
|
|
1103
1459
|
const r = await f.configureSyncRules(updateSyncRulesFromYaml('bucket_definitions: {}'));
|
|
1104
1460
|
const storage = f.getInstance(r.persisted_sync_rules!);
|
|
1105
1461
|
await storage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1462
|
+
await batch.markAllSnapshotDone('1/0');
|
|
1106
1463
|
await batch.keepalive('1/0');
|
|
1107
1464
|
});
|
|
1108
1465
|
|
|
@@ -1116,20 +1473,26 @@ bucket_definitions:
|
|
|
1116
1473
|
// Similar to the above test, but splits over 1MB chunks.
|
|
1117
1474
|
await using factory = await generateStorageFactory();
|
|
1118
1475
|
const syncRules = await factory.updateSyncRules(
|
|
1119
|
-
updateSyncRulesFromYaml(
|
|
1476
|
+
updateSyncRulesFromYaml(
|
|
1477
|
+
`
|
|
1120
1478
|
bucket_definitions:
|
|
1121
1479
|
global:
|
|
1122
1480
|
data:
|
|
1123
1481
|
- SELECT id FROM test
|
|
1124
1482
|
- SELECT id FROM test_ignore WHERE false
|
|
1125
|
-
|
|
1483
|
+
`,
|
|
1484
|
+
{
|
|
1485
|
+
storageVersion
|
|
1486
|
+
}
|
|
1487
|
+
)
|
|
1126
1488
|
);
|
|
1127
1489
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1128
1490
|
|
|
1129
|
-
const sourceTable = test_utils.makeTestTable('test', ['id']);
|
|
1130
|
-
const sourceTableIgnore = test_utils.makeTestTable('test_ignore', ['id']);
|
|
1491
|
+
const sourceTable = test_utils.makeTestTable('test', ['id'], config);
|
|
1492
|
+
const sourceTableIgnore = test_utils.makeTestTable('test_ignore', ['id'], config);
|
|
1131
1493
|
|
|
1132
1494
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1495
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1133
1496
|
// This saves a record to current_data, but not bucket_data.
|
|
1134
1497
|
// This causes a checkpoint to be created without increasing the op_id sequence.
|
|
1135
1498
|
await batch.save({
|
|
@@ -1163,17 +1526,23 @@ bucket_definitions:
|
|
|
1163
1526
|
test('unchanged checksums', async () => {
|
|
1164
1527
|
await using factory = await generateStorageFactory();
|
|
1165
1528
|
const syncRules = await factory.updateSyncRules(
|
|
1166
|
-
updateSyncRulesFromYaml(
|
|
1529
|
+
updateSyncRulesFromYaml(
|
|
1530
|
+
`
|
|
1167
1531
|
bucket_definitions:
|
|
1168
1532
|
global:
|
|
1169
1533
|
data:
|
|
1170
1534
|
- SELECT client_id as id, description FROM "%"
|
|
1171
|
-
|
|
1535
|
+
`,
|
|
1536
|
+
{
|
|
1537
|
+
storageVersion
|
|
1538
|
+
}
|
|
1539
|
+
)
|
|
1172
1540
|
);
|
|
1173
1541
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1174
1542
|
|
|
1175
1543
|
const sourceTable = TEST_TABLE;
|
|
1176
1544
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1545
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1177
1546
|
await batch.save({
|
|
1178
1547
|
sourceTable,
|
|
1179
1548
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -1190,14 +1559,226 @@ bucket_definitions:
|
|
|
1190
1559
|
const checksums = [
|
|
1191
1560
|
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
1192
1561
|
];
|
|
1193
|
-
expect(checksums).toEqual([
|
|
1562
|
+
expect(checksums).toEqual([
|
|
1563
|
+
{ bucket: bucketRequest(syncRules, 'global[]').bucket, checksum: 1917136889, count: 1 }
|
|
1564
|
+
]);
|
|
1194
1565
|
const checksums2 = [
|
|
1195
1566
|
...(await bucketStorage.getChecksums(checkpoint + 1n, bucketRequests(syncRules, ['global[]']))).values()
|
|
1196
1567
|
];
|
|
1197
|
-
expect(checksums2).toEqual([
|
|
1568
|
+
expect(checksums2).toEqual([
|
|
1569
|
+
{ bucket: bucketRequest(syncRules, 'global[]').bucket, checksum: 1917136889, count: 1 }
|
|
1570
|
+
]);
|
|
1198
1571
|
});
|
|
1199
1572
|
|
|
1200
|
-
testChecksumBatching(
|
|
1573
|
+
testChecksumBatching(config);
|
|
1574
|
+
|
|
1575
|
+
test('empty checkpoints (1)', async () => {
|
|
1576
|
+
await using factory = await generateStorageFactory();
|
|
1577
|
+
const syncRules = await factory.updateSyncRules(
|
|
1578
|
+
updateSyncRulesFromYaml(
|
|
1579
|
+
`
|
|
1580
|
+
bucket_definitions:
|
|
1581
|
+
global:
|
|
1582
|
+
data:
|
|
1583
|
+
- SELECT id, description FROM "%"
|
|
1584
|
+
`,
|
|
1585
|
+
{ storageVersion }
|
|
1586
|
+
)
|
|
1587
|
+
);
|
|
1588
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1589
|
+
|
|
1590
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1591
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1592
|
+
await batch.commit('1/1');
|
|
1593
|
+
|
|
1594
|
+
const cp1 = await bucketStorage.getCheckpoint();
|
|
1595
|
+
expect(cp1.lsn).toEqual('1/1');
|
|
1596
|
+
|
|
1597
|
+
await batch.commit('2/1', { createEmptyCheckpoints: true });
|
|
1598
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1599
|
+
expect(cp2.lsn).toEqual('2/1');
|
|
1600
|
+
|
|
1601
|
+
await batch.keepalive('3/1');
|
|
1602
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1603
|
+
expect(cp3.lsn).toEqual('3/1');
|
|
1604
|
+
|
|
1605
|
+
// For the last one, we skip creating empty checkpoints
|
|
1606
|
+
// This means the LSN stays at 3/1.
|
|
1607
|
+
await batch.commit('4/1', { createEmptyCheckpoints: false });
|
|
1608
|
+
const cp4 = await bucketStorage.getCheckpoint();
|
|
1609
|
+
expect(cp4.lsn).toEqual('3/1');
|
|
1610
|
+
});
|
|
1611
|
+
});
|
|
1612
|
+
|
|
1613
|
+
test('empty checkpoints (2)', async () => {
|
|
1614
|
+
await using factory = await generateStorageFactory();
|
|
1615
|
+
const syncRules = await factory.updateSyncRules(
|
|
1616
|
+
updateSyncRulesFromYaml(
|
|
1617
|
+
`
|
|
1618
|
+
bucket_definitions:
|
|
1619
|
+
global:
|
|
1620
|
+
data:
|
|
1621
|
+
- SELECT id, description FROM "%"
|
|
1622
|
+
`,
|
|
1623
|
+
{
|
|
1624
|
+
storageVersion
|
|
1625
|
+
}
|
|
1626
|
+
)
|
|
1627
|
+
);
|
|
1628
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1629
|
+
|
|
1630
|
+
const sourceTable = TEST_TABLE;
|
|
1631
|
+
// We simulate two concurrent batches, but nesting is the easiest way to do this.
|
|
1632
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch1) => {
|
|
1633
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch2) => {
|
|
1634
|
+
await batch1.markAllSnapshotDone('1/1');
|
|
1635
|
+
await batch1.commit('1/1');
|
|
1636
|
+
|
|
1637
|
+
await batch1.commit('2/1', { createEmptyCheckpoints: false });
|
|
1638
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1639
|
+
expect(cp2.lsn).toEqual('1/1'); // checkpoint 2/1 skipped
|
|
1640
|
+
|
|
1641
|
+
await batch2.save({
|
|
1642
|
+
sourceTable,
|
|
1643
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1644
|
+
after: {
|
|
1645
|
+
id: 'test1',
|
|
1646
|
+
description: 'test1a'
|
|
1647
|
+
},
|
|
1648
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1649
|
+
});
|
|
1650
|
+
// This simulates what happens on a snapshot processor.
|
|
1651
|
+
// This may later change to a flush() rather than commit().
|
|
1652
|
+
await batch2.commit(test_utils.BATCH_OPTIONS.zeroLSN);
|
|
1653
|
+
|
|
1654
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1655
|
+
expect(cp3.lsn).toEqual('1/1'); // Still unchanged
|
|
1656
|
+
|
|
1657
|
+
// This now needs to advance the LSN, despite {createEmptyCheckpoints: false}
|
|
1658
|
+
await batch1.commit('4/1', { createEmptyCheckpoints: false });
|
|
1659
|
+
const cp4 = await bucketStorage.getCheckpoint();
|
|
1660
|
+
expect(cp4.lsn).toEqual('4/1');
|
|
1661
|
+
});
|
|
1662
|
+
});
|
|
1663
|
+
});
|
|
1664
|
+
|
|
1665
|
+
test('empty checkpoints (sync rule activation)', async () => {
|
|
1666
|
+
await using factory = await generateStorageFactory();
|
|
1667
|
+
const syncRules = await factory.updateSyncRules(
|
|
1668
|
+
updateSyncRulesFromYaml(
|
|
1669
|
+
`
|
|
1670
|
+
bucket_definitions:
|
|
1671
|
+
global:
|
|
1672
|
+
data:
|
|
1673
|
+
- SELECT id, description FROM "%"
|
|
1674
|
+
`,
|
|
1675
|
+
{
|
|
1676
|
+
storageVersion
|
|
1677
|
+
}
|
|
1678
|
+
)
|
|
1679
|
+
);
|
|
1680
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1681
|
+
|
|
1682
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1683
|
+
const result = await batch.commit('1/1', { createEmptyCheckpoints: false });
|
|
1684
|
+
expect(result).toEqual({ checkpointBlocked: true, checkpointCreated: false });
|
|
1685
|
+
// Snapshot is only valid once we reach 3/1
|
|
1686
|
+
await batch.markAllSnapshotDone('3/1');
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1690
|
+
// 2/1 < 3/1 - snapshot not valid yet, block checkpoint
|
|
1691
|
+
const result = await batch.commit('2/1', { createEmptyCheckpoints: false });
|
|
1692
|
+
expect(result).toEqual({ checkpointBlocked: true, checkpointCreated: false });
|
|
1693
|
+
});
|
|
1694
|
+
|
|
1695
|
+
// No empty checkpoint should be created by the commit above.
|
|
1696
|
+
const cp1 = await bucketStorage.getCheckpoint();
|
|
1697
|
+
expect(cp1.lsn).toEqual(null);
|
|
1698
|
+
|
|
1699
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1700
|
+
// After this commit, the snapshot should be valid.
|
|
1701
|
+
// We specifically check that this is done even if createEmptyCheckpoints: false.
|
|
1702
|
+
const result = await batch.commit('3/1', { createEmptyCheckpoints: false });
|
|
1703
|
+
expect(result).toEqual({ checkpointBlocked: false, checkpointCreated: true });
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
// Now, the checkpoint should advance the sync rules active.
|
|
1707
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1708
|
+
expect(cp2.lsn).toEqual('3/1');
|
|
1709
|
+
|
|
1710
|
+
const activeSyncRules = await factory.getActiveSyncRulesContent();
|
|
1711
|
+
expect(activeSyncRules?.id).toEqual(syncRules.id);
|
|
1712
|
+
|
|
1713
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1714
|
+
// At this point, it should be a truely empty checkpoint
|
|
1715
|
+
const result = await batch.commit('4/1', { createEmptyCheckpoints: false });
|
|
1716
|
+
expect(result).toEqual({ checkpointBlocked: false, checkpointCreated: false });
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
// Unchanged
|
|
1720
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1721
|
+
expect(cp3.lsn).toEqual('3/1');
|
|
1722
|
+
});
|
|
1723
|
+
|
|
1724
|
+
test.runIf(storageVersion >= 3)('deleting while streaming', async () => {
|
|
1725
|
+
await using factory = await generateStorageFactory();
|
|
1726
|
+
const syncRules = await factory.updateSyncRules(
|
|
1727
|
+
updateSyncRulesFromYaml(
|
|
1728
|
+
`
|
|
1729
|
+
bucket_definitions:
|
|
1730
|
+
global:
|
|
1731
|
+
data:
|
|
1732
|
+
- SELECT id, description FROM "%"
|
|
1733
|
+
`,
|
|
1734
|
+
{
|
|
1735
|
+
storageVersion
|
|
1736
|
+
}
|
|
1737
|
+
)
|
|
1738
|
+
);
|
|
1739
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1740
|
+
|
|
1741
|
+
const sourceTable = TEST_TABLE;
|
|
1742
|
+
// We simulate two concurrent batches, and nesting is the easiest way to do this.
|
|
1743
|
+
// For this test, we assume that we start with a row "test1", which is picked up by a snapshot
|
|
1744
|
+
// query, right before the delete is streamed. But the snapshot query is only persisted _after_
|
|
1745
|
+
// the delete is streamed, and we need to ensure that the streamed delete takes precedence.
|
|
1746
|
+
await bucketStorage.startBatch({ ...test_utils.BATCH_OPTIONS, skipExistingRows: true }, async (snapshotBatch) => {
|
|
1747
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (streamingBatch) => {
|
|
1748
|
+
streamingBatch.save({
|
|
1749
|
+
sourceTable,
|
|
1750
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
1751
|
+
before: {
|
|
1752
|
+
id: 'test1'
|
|
1753
|
+
},
|
|
1754
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
1755
|
+
});
|
|
1756
|
+
await streamingBatch.commit('2/1');
|
|
1757
|
+
|
|
1758
|
+
await snapshotBatch.save({
|
|
1759
|
+
sourceTable,
|
|
1760
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1761
|
+
after: {
|
|
1762
|
+
id: 'test1',
|
|
1763
|
+
description: 'test1a'
|
|
1764
|
+
},
|
|
1765
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1766
|
+
});
|
|
1767
|
+
await snapshotBatch.markAllSnapshotDone('3/1');
|
|
1768
|
+
await snapshotBatch.commit('1/1');
|
|
1769
|
+
|
|
1770
|
+
await streamingBatch.keepalive('3/1');
|
|
1771
|
+
});
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
const cp = await bucketStorage.getCheckpoint();
|
|
1775
|
+
expect(cp.lsn).toEqual('3/1');
|
|
1776
|
+
const data = await test_utils.fromAsync(
|
|
1777
|
+
bucketStorage.getBucketDataBatch(cp.checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]))
|
|
1778
|
+
);
|
|
1779
|
+
|
|
1780
|
+
expect(data).toEqual([]);
|
|
1781
|
+
});
|
|
1201
1782
|
}
|
|
1202
1783
|
|
|
1203
1784
|
/**
|
|
@@ -1205,22 +1786,29 @@ bucket_definitions:
|
|
|
1205
1786
|
*
|
|
1206
1787
|
* Exposed as a separate test so we can test with more storage parameters.
|
|
1207
1788
|
*/
|
|
1208
|
-
export function testChecksumBatching(
|
|
1789
|
+
export function testChecksumBatching(config: storage.TestStorageConfig) {
|
|
1790
|
+
const storageVersion = config.storageVersion ?? CURRENT_STORAGE_VERSION;
|
|
1209
1791
|
test('checksums for multiple buckets', async () => {
|
|
1210
|
-
await using factory = await
|
|
1792
|
+
await using factory = await config.factory();
|
|
1211
1793
|
const syncRules = await factory.updateSyncRules(
|
|
1212
|
-
updateSyncRulesFromYaml(
|
|
1794
|
+
updateSyncRulesFromYaml(
|
|
1795
|
+
`
|
|
1213
1796
|
bucket_definitions:
|
|
1214
1797
|
user:
|
|
1215
1798
|
parameters: select request.user_id() as user_id
|
|
1216
1799
|
data:
|
|
1217
1800
|
- select id, description from test where user_id = bucket.user_id
|
|
1218
|
-
|
|
1801
|
+
`,
|
|
1802
|
+
{
|
|
1803
|
+
storageVersion
|
|
1804
|
+
}
|
|
1805
|
+
)
|
|
1219
1806
|
);
|
|
1220
1807
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1221
1808
|
|
|
1222
|
-
const sourceTable =
|
|
1809
|
+
const sourceTable = test_utils.makeTestTable('test', ['id'], config);
|
|
1223
1810
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1811
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1224
1812
|
for (let u of ['u1', 'u2', 'u3', 'u4']) {
|
|
1225
1813
|
for (let t of ['t1', 't2', 't3', 't4']) {
|
|
1226
1814
|
const id = `${t}_${u}`;
|
|
@@ -1245,10 +1833,10 @@ bucket_definitions:
|
|
|
1245
1833
|
const checksums = [...(await bucketStorage.getChecksums(checkpoint, buckets)).values()];
|
|
1246
1834
|
checksums.sort((a, b) => a.bucket.localeCompare(b.bucket));
|
|
1247
1835
|
expect(checksums).toEqual([
|
|
1248
|
-
{ bucket: bucketRequest(syncRules, 'user["u1"]'), count: 4, checksum: 346204588 },
|
|
1249
|
-
{ bucket: bucketRequest(syncRules, 'user["u2"]'), count: 4, checksum: 5261081 },
|
|
1250
|
-
{ bucket: bucketRequest(syncRules, 'user["u3"]'), count: 4, checksum: 134760718 },
|
|
1251
|
-
{ bucket: bucketRequest(syncRules, 'user["u4"]'), count: 4, checksum: -302639724 }
|
|
1836
|
+
{ bucket: bucketRequest(syncRules, 'user["u1"]').bucket, count: 4, checksum: 346204588 },
|
|
1837
|
+
{ bucket: bucketRequest(syncRules, 'user["u2"]').bucket, count: 4, checksum: 5261081 },
|
|
1838
|
+
{ bucket: bucketRequest(syncRules, 'user["u3"]').bucket, count: 4, checksum: 134760718 },
|
|
1839
|
+
{ bucket: bucketRequest(syncRules, 'user["u4"]').bucket, count: 4, checksum: -302639724 }
|
|
1252
1840
|
]);
|
|
1253
1841
|
});
|
|
1254
1842
|
}
|