@powersync/service-core-tests 0.13.2 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/test-utils/general-utils.d.ts +9 -3
  3. package/dist/test-utils/general-utils.js +26 -26
  4. package/dist/test-utils/general-utils.js.map +1 -1
  5. package/dist/tests/register-compacting-tests.d.ts +1 -1
  6. package/dist/tests/register-compacting-tests.js +136 -93
  7. package/dist/tests/register-compacting-tests.js.map +1 -1
  8. package/dist/tests/register-data-storage-checkpoint-tests.d.ts +1 -1
  9. package/dist/tests/register-data-storage-checkpoint-tests.js +44 -27
  10. package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
  11. package/dist/tests/register-data-storage-data-tests.d.ts +2 -2
  12. package/dist/tests/register-data-storage-data-tests.js +715 -207
  13. package/dist/tests/register-data-storage-data-tests.js.map +1 -1
  14. package/dist/tests/register-data-storage-parameter-tests.d.ts +1 -1
  15. package/dist/tests/register-data-storage-parameter-tests.js +123 -58
  16. package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
  17. package/dist/tests/register-parameter-compacting-tests.d.ts +1 -1
  18. package/dist/tests/register-parameter-compacting-tests.js +13 -13
  19. package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
  20. package/dist/tests/register-sync-tests.d.ts +4 -1
  21. package/dist/tests/register-sync-tests.js +63 -34
  22. package/dist/tests/register-sync-tests.js.map +1 -1
  23. package/dist/tests/util.d.ts +6 -1
  24. package/dist/tests/util.js +31 -2
  25. package/dist/tests/util.js.map +1 -1
  26. package/package.json +3 -3
  27. package/src/test-utils/general-utils.ts +42 -28
  28. package/src/tests/register-compacting-tests.ts +153 -103
  29. package/src/tests/register-data-storage-checkpoint-tests.ts +70 -22
  30. package/src/tests/register-data-storage-data-tests.ts +732 -110
  31. package/src/tests/register-data-storage-parameter-tests.ts +168 -59
  32. package/src/tests/register-parameter-compacting-tests.ts +18 -13
  33. package/src/tests/register-sync-tests.ts +71 -35
  34. package/src/tests/util.ts +52 -2
  35. package/tsconfig.tsbuildinfo +1 -1
@@ -50,10 +50,11 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
50
50
  var e = new Error(message);
51
51
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
52
52
  });
53
- import { getUuidReplicaIdentityBson, storage } from '@powersync/service-core';
53
+ import { CURRENT_STORAGE_VERSION, getUuidReplicaIdentityBson, reduceBucket, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
54
54
  import { describe, expect, test } from 'vitest';
55
55
  import * as test_utils from '../test-utils/test-utils-index.js';
56
- import { TEST_TABLE } from './util.js';
56
+ import { bucketRequest } from '../test-utils/test-utils-index.js';
57
+ import { bucketRequestMap, bucketRequests } from './util.js';
57
58
  /**
58
59
  * Normalize data from OplogEntries for comparison in tests.
59
60
  * Tests typically expect the stringified result
@@ -74,22 +75,24 @@ const normalizeOplogData = (data) => {
74
75
  *
75
76
  * ```
76
77
  */
77
- export function registerDataStorageDataTests(generateStorageFactory) {
78
+ export function registerDataStorageDataTests(config) {
79
+ const generateStorageFactory = config.factory;
80
+ const storageVersion = config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
81
+ const TEST_TABLE = test_utils.makeTestTable('test', ['id'], config);
78
82
  test('removing row', async () => {
79
83
  const env_1 = { stack: [], error: void 0, hasError: false };
80
84
  try {
81
85
  const factory = __addDisposableResource(env_1, await generateStorageFactory(), true);
82
- const syncRules = await factory.updateSyncRules({
83
- content: `
86
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
84
87
  bucket_definitions:
85
88
  global:
86
89
  data:
87
90
  - SELECT id, description FROM "%"
88
- `
89
- });
91
+ `, { storageVersion }));
90
92
  const bucketStorage = factory.getInstance(syncRules);
91
93
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
92
94
  const sourceTable = TEST_TABLE;
95
+ await batch.markAllSnapshotDone('1/1');
93
96
  await batch.save({
94
97
  sourceTable,
95
98
  tag: storage.SaveOperationTag.INSERT,
@@ -107,7 +110,7 @@ bucket_definitions:
107
110
  await batch.commit('1/1');
108
111
  });
109
112
  const { checkpoint } = await bucketStorage.getCheckpoint();
110
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', 0n]])));
113
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
111
114
  const data = batch[0].chunkData.data.map((d) => {
112
115
  return {
113
116
  op: d.op,
@@ -121,10 +124,12 @@ bucket_definitions:
121
124
  { op: 'PUT', object_id: 'test1', checksum: c1 },
122
125
  { op: 'REMOVE', object_id: 'test1', checksum: c2 }
123
126
  ]);
124
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, ['global[]'])).values()];
127
+ const checksums = [
128
+ ...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
129
+ ];
125
130
  expect(checksums).toEqual([
126
131
  {
127
- bucket: 'global[]',
132
+ bucket: bucketRequest(syncRules, 'global[]').bucket,
128
133
  checksum: (c1 + c2) & 0xffffffff,
129
134
  count: 2
130
135
  }
@@ -140,21 +145,296 @@ bucket_definitions:
140
145
  await result_1;
141
146
  }
142
147
  });
143
- test('changing client ids', async () => {
148
+ test('insert after delete in new batch', async () => {
144
149
  const env_2 = { stack: [], error: void 0, hasError: false };
145
150
  try {
146
151
  const factory = __addDisposableResource(env_2, await generateStorageFactory(), true);
147
- const syncRules = await factory.updateSyncRules({
148
- content: `
152
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
149
153
  bucket_definitions:
150
154
  global:
151
155
  data:
152
- - SELECT client_id as id, description FROM "%"
153
- `
156
+ - SELECT id, description FROM "%"
157
+ `, { storageVersion }));
158
+ const bucketStorage = factory.getInstance(syncRules);
159
+ const sourceTable = TEST_TABLE;
160
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
161
+ await batch.markAllSnapshotDone('1/1');
162
+ await batch.save({
163
+ sourceTable,
164
+ tag: storage.SaveOperationTag.DELETE,
165
+ beforeReplicaId: test_utils.rid('test1')
166
+ });
167
+ await batch.commit('0/1');
168
+ });
169
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
170
+ const sourceTable = TEST_TABLE;
171
+ await batch.save({
172
+ sourceTable,
173
+ tag: storage.SaveOperationTag.INSERT,
174
+ after: {
175
+ id: 'test1',
176
+ description: 'test1'
177
+ },
178
+ afterReplicaId: test_utils.rid('test1')
179
+ });
180
+ await batch.commit('2/1');
181
+ });
182
+ const { checkpoint } = await bucketStorage.getCheckpoint();
183
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
184
+ const data = batch[0].chunkData.data.map((d) => {
185
+ return {
186
+ op: d.op,
187
+ object_id: d.object_id,
188
+ checksum: d.checksum
189
+ };
190
+ });
191
+ const c1 = 2871785649;
192
+ expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
193
+ const checksums = [
194
+ ...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
195
+ ];
196
+ expect(checksums).toEqual([
197
+ {
198
+ bucket: bucketRequest(syncRules, 'global[]').bucket,
199
+ checksum: c1 & 0xffffffff,
200
+ count: 1
201
+ }
202
+ ]);
203
+ }
204
+ catch (e_2) {
205
+ env_2.error = e_2;
206
+ env_2.hasError = true;
207
+ }
208
+ finally {
209
+ const result_2 = __disposeResources(env_2);
210
+ if (result_2)
211
+ await result_2;
212
+ }
213
+ });
214
+ test('update after delete in new batch', async () => {
215
+ const env_3 = { stack: [], error: void 0, hasError: false };
216
+ try {
217
+ // Update after delete may not be common, but the storage layer should handle it in an eventually-consistent way.
218
+ const factory = __addDisposableResource(env_3, await generateStorageFactory(), true);
219
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
220
+ bucket_definitions:
221
+ global:
222
+ data:
223
+ - SELECT id, description FROM "%"
224
+ `, { storageVersion }));
225
+ const bucketStorage = factory.getInstance(syncRules);
226
+ const sourceTable = TEST_TABLE;
227
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
228
+ await batch.markAllSnapshotDone('1/1');
229
+ await batch.save({
230
+ sourceTable,
231
+ tag: storage.SaveOperationTag.DELETE,
232
+ beforeReplicaId: test_utils.rid('test1')
233
+ });
234
+ await batch.commit('0/1');
235
+ });
236
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
237
+ const sourceTable = TEST_TABLE;
238
+ await batch.save({
239
+ sourceTable,
240
+ tag: storage.SaveOperationTag.UPDATE,
241
+ before: {
242
+ id: 'test1'
243
+ },
244
+ after: {
245
+ id: 'test1',
246
+ description: 'test1'
247
+ },
248
+ beforeReplicaId: test_utils.rid('test1'),
249
+ afterReplicaId: test_utils.rid('test1')
250
+ });
251
+ await batch.commit('2/1');
252
+ });
253
+ const { checkpoint } = await bucketStorage.getCheckpoint();
254
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
255
+ const data = batch[0].chunkData.data.map((d) => {
256
+ return {
257
+ op: d.op,
258
+ object_id: d.object_id,
259
+ checksum: d.checksum
260
+ };
261
+ });
262
+ const c1 = 2871785649;
263
+ expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
264
+ const checksums = [
265
+ ...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
266
+ ];
267
+ expect(checksums).toEqual([
268
+ {
269
+ bucket: bucketRequest(syncRules, 'global[]').bucket,
270
+ checksum: c1 & 0xffffffff,
271
+ count: 1
272
+ }
273
+ ]);
274
+ }
275
+ catch (e_3) {
276
+ env_3.error = e_3;
277
+ env_3.hasError = true;
278
+ }
279
+ finally {
280
+ const result_3 = __disposeResources(env_3);
281
+ if (result_3)
282
+ await result_3;
283
+ }
284
+ });
285
+ test('insert after delete in same batch', async () => {
286
+ const env_4 = { stack: [], error: void 0, hasError: false };
287
+ try {
288
+ const factory = __addDisposableResource(env_4, await generateStorageFactory(), true);
289
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
290
+ bucket_definitions:
291
+ global:
292
+ data:
293
+ - SELECT id, description FROM "%"
294
+ `, {
295
+ storageVersion
296
+ }));
297
+ const bucketStorage = factory.getInstance(syncRules);
298
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
299
+ const sourceTable = TEST_TABLE;
300
+ await batch.markAllSnapshotDone('1/1');
301
+ await batch.save({
302
+ sourceTable,
303
+ tag: storage.SaveOperationTag.DELETE,
304
+ beforeReplicaId: test_utils.rid('test1')
305
+ });
306
+ await batch.save({
307
+ sourceTable,
308
+ tag: storage.SaveOperationTag.INSERT,
309
+ after: {
310
+ id: 'test1',
311
+ description: 'test1'
312
+ },
313
+ afterReplicaId: test_utils.rid('test1')
314
+ });
315
+ await batch.commit('1/1');
154
316
  });
317
+ const { checkpoint } = await bucketStorage.getCheckpoint();
318
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
319
+ const data = batch[0].chunkData.data.map((d) => {
320
+ return {
321
+ op: d.op,
322
+ object_id: d.object_id,
323
+ checksum: d.checksum
324
+ };
325
+ });
326
+ const c1 = 2871785649;
327
+ expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
328
+ const checksums = [
329
+ ...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
330
+ ];
331
+ expect(checksums).toEqual([
332
+ {
333
+ bucket: bucketRequest(syncRules, 'global[]').bucket,
334
+ checksum: c1 & 0xffffffff,
335
+ count: 1
336
+ }
337
+ ]);
338
+ }
339
+ catch (e_4) {
340
+ env_4.error = e_4;
341
+ env_4.hasError = true;
342
+ }
343
+ finally {
344
+ const result_4 = __disposeResources(env_4);
345
+ if (result_4)
346
+ await result_4;
347
+ }
348
+ });
349
+ test('(insert, delete, insert), (delete)', async () => {
350
+ const env_5 = { stack: [], error: void 0, hasError: false };
351
+ try {
352
+ const factory = __addDisposableResource(env_5, await generateStorageFactory(), true);
353
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
354
+ bucket_definitions:
355
+ global:
356
+ data:
357
+ - SELECT id, description FROM "%"
358
+ `, {
359
+ storageVersion
360
+ }));
361
+ const bucketStorage = factory.getInstance(syncRules);
362
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
363
+ const sourceTable = TEST_TABLE;
364
+ await batch.markAllSnapshotDone('1/1');
365
+ await batch.save({
366
+ sourceTable,
367
+ tag: storage.SaveOperationTag.INSERT,
368
+ after: {
369
+ id: 'test1',
370
+ description: 'test1'
371
+ },
372
+ afterReplicaId: test_utils.rid('test1')
373
+ });
374
+ await batch.save({
375
+ sourceTable,
376
+ tag: storage.SaveOperationTag.DELETE,
377
+ beforeReplicaId: test_utils.rid('test1')
378
+ });
379
+ await batch.save({
380
+ sourceTable,
381
+ tag: storage.SaveOperationTag.INSERT,
382
+ after: {
383
+ id: 'test1',
384
+ description: 'test1'
385
+ },
386
+ afterReplicaId: test_utils.rid('test1')
387
+ });
388
+ await batch.commit('1/1');
389
+ });
390
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
391
+ const sourceTable = TEST_TABLE;
392
+ await batch.markAllSnapshotDone('1/1');
393
+ await batch.save({
394
+ sourceTable,
395
+ tag: storage.SaveOperationTag.DELETE,
396
+ beforeReplicaId: test_utils.rid('test1')
397
+ });
398
+ await batch.commit('2/1');
399
+ });
400
+ const { checkpoint } = await bucketStorage.getCheckpoint();
401
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
402
+ expect(reduceBucket(batch[0].chunkData.data).slice(1)).toEqual([]);
403
+ const data = batch[0].chunkData.data.map((d) => {
404
+ return {
405
+ op: d.op,
406
+ object_id: d.object_id,
407
+ checksum: d.checksum
408
+ };
409
+ });
410
+ expect(data).toMatchSnapshot();
411
+ }
412
+ catch (e_5) {
413
+ env_5.error = e_5;
414
+ env_5.hasError = true;
415
+ }
416
+ finally {
417
+ const result_5 = __disposeResources(env_5);
418
+ if (result_5)
419
+ await result_5;
420
+ }
421
+ });
422
+ test('changing client ids', async () => {
423
+ const env_6 = { stack: [], error: void 0, hasError: false };
424
+ try {
425
+ const factory = __addDisposableResource(env_6, await generateStorageFactory(), true);
426
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
427
+ bucket_definitions:
428
+ global:
429
+ data:
430
+ - SELECT client_id as id, description FROM "%"
431
+ `, {
432
+ storageVersion
433
+ }));
155
434
  const bucketStorage = factory.getInstance(syncRules);
156
435
  const sourceTable = TEST_TABLE;
157
436
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
437
+ await batch.markAllSnapshotDone('1/1');
158
438
  await batch.save({
159
439
  sourceTable,
160
440
  tag: storage.SaveOperationTag.INSERT,
@@ -188,7 +468,7 @@ bucket_definitions:
188
468
  await batch.commit('1/1');
189
469
  });
190
470
  const { checkpoint } = await bucketStorage.getCheckpoint();
191
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', 0n]])));
471
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
192
472
  const data = batch[0].chunkData.data.map((d) => {
193
473
  return {
194
474
  op: d.op,
@@ -202,31 +482,32 @@ bucket_definitions:
202
482
  { op: 'PUT', object_id: 'client2' }
203
483
  ]);
204
484
  }
205
- catch (e_2) {
206
- env_2.error = e_2;
207
- env_2.hasError = true;
485
+ catch (e_6) {
486
+ env_6.error = e_6;
487
+ env_6.hasError = true;
208
488
  }
209
489
  finally {
210
- const result_2 = __disposeResources(env_2);
211
- if (result_2)
212
- await result_2;
490
+ const result_6 = __disposeResources(env_6);
491
+ if (result_6)
492
+ await result_6;
213
493
  }
214
494
  });
215
495
  test('re-apply delete', async () => {
216
- const env_3 = { stack: [], error: void 0, hasError: false };
496
+ const env_7 = { stack: [], error: void 0, hasError: false };
217
497
  try {
218
- const factory = __addDisposableResource(env_3, await generateStorageFactory(), true);
219
- const syncRules = await factory.updateSyncRules({
220
- content: `
498
+ const factory = __addDisposableResource(env_7, await generateStorageFactory(), true);
499
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
221
500
  bucket_definitions:
222
501
  global:
223
502
  data:
224
503
  - SELECT id, description FROM "%"
225
- `
226
- });
504
+ `, {
505
+ storageVersion
506
+ }));
227
507
  const bucketStorage = factory.getInstance(syncRules);
228
508
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
229
509
  const sourceTable = TEST_TABLE;
510
+ await batch.markAllSnapshotDone('1/1');
230
511
  await batch.save({
231
512
  sourceTable,
232
513
  tag: storage.SaveOperationTag.INSERT,
@@ -255,7 +536,7 @@ bucket_definitions:
255
536
  });
256
537
  });
257
538
  const { checkpoint } = await bucketStorage.getCheckpoint();
258
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', 0n]])));
539
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
259
540
  const data = batch[0].chunkData.data.map((d) => {
260
541
  return {
261
542
  op: d.op,
@@ -269,39 +550,40 @@ bucket_definitions:
269
550
  { op: 'PUT', object_id: 'test1', checksum: c1 },
270
551
  { op: 'REMOVE', object_id: 'test1', checksum: c2 }
271
552
  ]);
272
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, ['global[]'])).values()];
553
+ const checksums = [
554
+ ...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
555
+ ];
273
556
  expect(checksums).toEqual([
274
557
  {
275
- bucket: 'global[]',
558
+ bucket: bucketRequest(syncRules, 'global[]').bucket,
276
559
  checksum: (c1 + c2) & 0xffffffff,
277
560
  count: 2
278
561
  }
279
562
  ]);
280
563
  }
281
- catch (e_3) {
282
- env_3.error = e_3;
283
- env_3.hasError = true;
564
+ catch (e_7) {
565
+ env_7.error = e_7;
566
+ env_7.hasError = true;
284
567
  }
285
568
  finally {
286
- const result_3 = __disposeResources(env_3);
287
- if (result_3)
288
- await result_3;
569
+ const result_7 = __disposeResources(env_7);
570
+ if (result_7)
571
+ await result_7;
289
572
  }
290
573
  });
291
574
  test('re-apply update + delete', async () => {
292
- const env_4 = { stack: [], error: void 0, hasError: false };
575
+ const env_8 = { stack: [], error: void 0, hasError: false };
293
576
  try {
294
- const factory = __addDisposableResource(env_4, await generateStorageFactory(), true);
295
- const syncRules = await factory.updateSyncRules({
296
- content: `
577
+ const factory = __addDisposableResource(env_8, await generateStorageFactory(), true);
578
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
297
579
  bucket_definitions:
298
580
  global:
299
581
  data:
300
582
  - SELECT id, description FROM "%"
301
- `
302
- });
583
+ `, { storageVersion }));
303
584
  const bucketStorage = factory.getInstance(syncRules);
304
585
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
586
+ await batch.markAllSnapshotDone('1/1');
305
587
  const sourceTable = TEST_TABLE;
306
588
  await batch.save({
307
589
  sourceTable,
@@ -314,6 +596,7 @@ bucket_definitions:
314
596
  });
315
597
  });
316
598
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
599
+ await batch.markAllSnapshotDone('1/1');
317
600
  const sourceTable = TEST_TABLE;
318
601
  await batch.save({
319
602
  sourceTable,
@@ -341,6 +624,7 @@ bucket_definitions:
341
624
  await batch.commit('1/1');
342
625
  });
343
626
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
627
+ await batch.markAllSnapshotDone('1/1');
344
628
  const sourceTable = TEST_TABLE;
345
629
  await batch.save({
346
630
  sourceTable,
@@ -368,7 +652,7 @@ bucket_definitions:
368
652
  await batch.commit('2/1');
369
653
  });
370
654
  const { checkpoint } = await bucketStorage.getCheckpoint();
371
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', 0n]])));
655
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
372
656
  const data = batch[0].chunkData.data.map((d) => {
373
657
  return {
374
658
  op: d.op,
@@ -384,27 +668,29 @@ bucket_definitions:
384
668
  { op: 'PUT', object_id: 'test1', checksum: c1 },
385
669
  { op: 'REMOVE', object_id: 'test1', checksum: c2 }
386
670
  ]);
387
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, ['global[]'])).values()];
671
+ const checksums = [
672
+ ...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
673
+ ];
388
674
  expect(checksums).toEqual([
389
675
  {
390
- bucket: 'global[]',
676
+ bucket: bucketRequest(syncRules, 'global[]').bucket,
391
677
  checksum: (c1 + c1 + c1 + c2) & 0xffffffff,
392
678
  count: 4
393
679
  }
394
680
  ]);
395
681
  }
396
- catch (e_4) {
397
- env_4.error = e_4;
398
- env_4.hasError = true;
682
+ catch (e_8) {
683
+ env_8.error = e_8;
684
+ env_8.hasError = true;
399
685
  }
400
686
  finally {
401
- const result_4 = __disposeResources(env_4);
402
- if (result_4)
403
- await result_4;
687
+ const result_8 = __disposeResources(env_8);
688
+ if (result_8)
689
+ await result_8;
404
690
  }
405
691
  });
406
692
  test('batch with overlapping replica ids', async () => {
407
- const env_5 = { stack: [], error: void 0, hasError: false };
693
+ const env_9 = { stack: [], error: void 0, hasError: false };
408
694
  try {
409
695
  // This test checks that we get the correct output when processing rows with:
410
696
  // 1. changing replica ids
@@ -413,18 +699,17 @@ bucket_definitions:
413
699
  // It can break at two places:
414
700
  // 1. Not getting the correct "current_data" state for each operation.
415
701
  // 2. Output order not being correct.
416
- const factory = __addDisposableResource(env_5, await generateStorageFactory(), true);
417
- const syncRules = await factory.updateSyncRules({
418
- content: `
702
+ const factory = __addDisposableResource(env_9, await generateStorageFactory(), true);
703
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
419
704
  bucket_definitions:
420
705
  global:
421
706
  data:
422
707
  - SELECT id, description FROM "test"
423
- `
424
- });
708
+ `, { storageVersion }));
425
709
  const bucketStorage = factory.getInstance(syncRules);
426
710
  // Pre-setup
427
711
  const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
712
+ await batch.markAllSnapshotDone('1/1');
428
713
  const sourceTable = TEST_TABLE;
429
714
  await batch.save({
430
715
  sourceTable,
@@ -520,7 +805,7 @@ bucket_definitions:
520
805
  });
521
806
  });
522
807
  const checkpoint2 = result2.flushed_op;
523
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint2, new Map([['global[]', checkpoint1]])));
808
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint2, bucketRequestMap(syncRules, [['global[]', checkpoint1]])));
524
809
  const data = batch[0].chunkData.data.map((d) => {
525
810
  return {
526
811
  op: d.op,
@@ -544,18 +829,18 @@ bucket_definitions:
544
829
  { op: 'PUT', object_id: 'test5', data: JSON.stringify({ id: 'test5', description: 'test5d' }) }
545
830
  ]);
546
831
  }
547
- catch (e_5) {
548
- env_5.error = e_5;
549
- env_5.hasError = true;
832
+ catch (e_9) {
833
+ env_9.error = e_9;
834
+ env_9.hasError = true;
550
835
  }
551
836
  finally {
552
- const result_5 = __disposeResources(env_5);
553
- if (result_5)
554
- await result_5;
837
+ const result_9 = __disposeResources(env_9);
838
+ if (result_9)
839
+ await result_9;
555
840
  }
556
841
  });
557
842
  test('changed data with replica identity full', async () => {
558
- const env_6 = { stack: [], error: void 0, hasError: false };
843
+ const env_10 = { stack: [], error: void 0, hasError: false };
559
844
  try {
560
845
  function rid2(id, description) {
561
846
  return getUuidReplicaIdentityBson({ id, description }, [
@@ -563,19 +848,20 @@ bucket_definitions:
563
848
  { name: 'description', type: 'VARCHAR', typeId: 25 }
564
849
  ]);
565
850
  }
566
- const factory = __addDisposableResource(env_6, await generateStorageFactory(), true);
567
- const syncRules = await factory.updateSyncRules({
568
- content: `
851
+ const factory = __addDisposableResource(env_10, await generateStorageFactory(), true);
852
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
569
853
  bucket_definitions:
570
854
  global:
571
855
  data:
572
856
  - SELECT id, description FROM "test"
573
- `
574
- });
857
+ `, {
858
+ storageVersion
859
+ }));
575
860
  const bucketStorage = factory.getInstance(syncRules);
576
- const sourceTable = test_utils.makeTestTable('test', ['id', 'description']);
861
+ const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
577
862
  // Pre-setup
578
863
  const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
864
+ await batch.markAllSnapshotDone('1/1');
579
865
  await batch.save({
580
866
  sourceTable,
581
867
  tag: storage.SaveOperationTag.INSERT,
@@ -618,7 +904,7 @@ bucket_definitions:
618
904
  });
619
905
  });
620
906
  const checkpoint3 = result3.flushed_op;
621
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3, new Map([['global[]', checkpoint1]])));
907
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3, bucketRequestMap(syncRules, [['global[]', checkpoint1]])));
622
908
  const data = batch[0].chunkData.data.map((d) => {
623
909
  return {
624
910
  op: d.op,
@@ -652,18 +938,18 @@ bucket_definitions:
652
938
  }
653
939
  ]);
654
940
  }
655
- catch (e_6) {
656
- env_6.error = e_6;
657
- env_6.hasError = true;
941
+ catch (e_10) {
942
+ env_10.error = e_10;
943
+ env_10.hasError = true;
658
944
  }
659
945
  finally {
660
- const result_6 = __disposeResources(env_6);
661
- if (result_6)
662
- await result_6;
946
+ const result_10 = __disposeResources(env_10);
947
+ if (result_10)
948
+ await result_10;
663
949
  }
664
950
  });
665
951
  test('unchanged data with replica identity full', async () => {
666
- const env_7 = { stack: [], error: void 0, hasError: false };
952
+ const env_11 = { stack: [], error: void 0, hasError: false };
667
953
  try {
668
954
  function rid2(id, description) {
669
955
  return getUuidReplicaIdentityBson({ id, description }, [
@@ -671,19 +957,20 @@ bucket_definitions:
671
957
  { name: 'description', type: 'VARCHAR', typeId: 25 }
672
958
  ]);
673
959
  }
674
- const factory = __addDisposableResource(env_7, await generateStorageFactory(), true);
675
- const syncRules = await factory.updateSyncRules({
676
- content: `
960
+ const factory = __addDisposableResource(env_11, await generateStorageFactory(), true);
961
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
677
962
  bucket_definitions:
678
963
  global:
679
964
  data:
680
965
  - SELECT id, description FROM "test"
681
- `
682
- });
966
+ `, {
967
+ storageVersion
968
+ }));
683
969
  const bucketStorage = factory.getInstance(syncRules);
684
- const sourceTable = test_utils.makeTestTable('test', ['id', 'description']);
970
+ const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
685
971
  // Pre-setup
686
972
  const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
973
+ await batch.markAllSnapshotDone('1/1');
687
974
  await batch.save({
688
975
  sourceTable,
689
976
  tag: storage.SaveOperationTag.INSERT,
@@ -726,7 +1013,7 @@ bucket_definitions:
726
1013
  });
727
1014
  });
728
1015
  const checkpoint3 = result3.flushed_op;
729
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3, new Map([['global[]', checkpoint1]])));
1016
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3, bucketRequestMap(syncRules, [['global[]', checkpoint1]])));
730
1017
  const data = batch[0].chunkData.data.map((d) => {
731
1018
  return {
732
1019
  op: d.op,
@@ -753,34 +1040,35 @@ bucket_definitions:
753
1040
  }
754
1041
  ]);
755
1042
  }
756
- catch (e_7) {
757
- env_7.error = e_7;
758
- env_7.hasError = true;
1043
+ catch (e_11) {
1044
+ env_11.error = e_11;
1045
+ env_11.hasError = true;
759
1046
  }
760
1047
  finally {
761
- const result_7 = __disposeResources(env_7);
762
- if (result_7)
763
- await result_7;
1048
+ const result_11 = __disposeResources(env_11);
1049
+ if (result_11)
1050
+ await result_11;
764
1051
  }
765
1052
  });
766
1053
  test('large batch', async () => {
767
- const env_8 = { stack: [], error: void 0, hasError: false };
1054
+ const env_12 = { stack: [], error: void 0, hasError: false };
768
1055
  try {
769
1056
  // Test syncing a batch of data that is small in count,
770
1057
  // but large enough in size to be split over multiple returned batches.
771
1058
  // The specific batch splits is an implementation detail of the storage driver,
772
1059
  // and the test will have to updated when other implementations are added.
773
- const factory = __addDisposableResource(env_8, await generateStorageFactory(), true);
774
- const syncRules = await factory.updateSyncRules({
775
- content: `
1060
+ const factory = __addDisposableResource(env_12, await generateStorageFactory(), true);
1061
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
776
1062
  bucket_definitions:
777
1063
  global:
778
1064
  data:
779
1065
  - SELECT id, description FROM "%"
780
- `
781
- });
1066
+ `, {
1067
+ storageVersion
1068
+ }));
782
1069
  const bucketStorage = factory.getInstance(syncRules);
783
1070
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1071
+ await batch.markAllSnapshotDone('1/1');
784
1072
  const sourceTable = TEST_TABLE;
785
1073
  const largeDescription = '0123456789'.repeat(12_000_00);
786
1074
  await batch.save({
@@ -826,7 +1114,7 @@ bucket_definitions:
826
1114
  const options = {
827
1115
  chunkLimitBytes: 16 * 1024 * 1024
828
1116
  };
829
- const batch1 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', 0n]]), options));
1117
+ const batch1 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]), options));
830
1118
  expect(test_utils.getBatchData(batch1)).toEqual([
831
1119
  { op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
832
1120
  { op_id: '2', op: 'PUT', object_id: 'large1', checksum: 454746904 }
@@ -836,7 +1124,7 @@ bucket_definitions:
836
1124
  has_more: true,
837
1125
  next_after: '2'
838
1126
  });
839
- const batch2 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', BigInt(batch1[0].chunkData.next_after)]]), options));
1127
+ const batch2 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch1[0].chunkData.next_after)]]), options));
840
1128
  expect(test_utils.getBatchData(batch2)).toEqual([
841
1129
  { op_id: '3', op: 'PUT', object_id: 'large2', checksum: 1795508474 },
842
1130
  { op_id: '4', op: 'PUT', object_id: 'test3', checksum: 1359888332 }
@@ -846,35 +1134,36 @@ bucket_definitions:
846
1134
  has_more: false,
847
1135
  next_after: '4'
848
1136
  });
849
- const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', BigInt(batch2[0].chunkData.next_after)]]), options));
1137
+ const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch2[0].chunkData.next_after)]]), options));
850
1138
  expect(test_utils.getBatchData(batch3)).toEqual([]);
851
1139
  expect(test_utils.getBatchMeta(batch3)).toEqual(null);
852
1140
  }
853
- catch (e_8) {
854
- env_8.error = e_8;
855
- env_8.hasError = true;
1141
+ catch (e_12) {
1142
+ env_12.error = e_12;
1143
+ env_12.hasError = true;
856
1144
  }
857
1145
  finally {
858
- const result_8 = __disposeResources(env_8);
859
- if (result_8)
860
- await result_8;
1146
+ const result_12 = __disposeResources(env_12);
1147
+ if (result_12)
1148
+ await result_12;
861
1149
  }
862
1150
  });
863
1151
  test('long batch', async () => {
864
- const env_9 = { stack: [], error: void 0, hasError: false };
1152
+ const env_13 = { stack: [], error: void 0, hasError: false };
865
1153
  try {
866
1154
  // Test syncing a batch of data that is limited by count.
867
- const factory = __addDisposableResource(env_9, await generateStorageFactory(), true);
868
- const syncRules = await factory.updateSyncRules({
869
- content: `
1155
+ const factory = __addDisposableResource(env_13, await generateStorageFactory(), true);
1156
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
870
1157
  bucket_definitions:
871
1158
  global:
872
1159
  data:
873
1160
  - SELECT id, description FROM "%"
874
- `
875
- });
1161
+ `, {
1162
+ storageVersion
1163
+ }));
876
1164
  const bucketStorage = factory.getInstance(syncRules);
877
1165
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1166
+ await batch.markAllSnapshotDone('1/1');
878
1167
  const sourceTable = TEST_TABLE;
879
1168
  for (let i = 1; i <= 6; i++) {
880
1169
  await batch.save({
@@ -890,7 +1179,7 @@ bucket_definitions:
890
1179
  await batch.commit('1/1');
891
1180
  });
892
1181
  const { checkpoint } = await bucketStorage.getCheckpoint();
893
- const batch1 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', 0n]]), { limit: 4 }));
1182
+ const batch1 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]), { limit: 4 }));
894
1183
  expect(test_utils.getBatchData(batch1)).toEqual([
895
1184
  { op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
896
1185
  { op_id: '2', op: 'PUT', object_id: 'test2', checksum: 730027011 },
@@ -902,7 +1191,7 @@ bucket_definitions:
902
1191
  has_more: true,
903
1192
  next_after: '4'
904
1193
  });
905
- const batch2 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', BigInt(batch1.chunkData.next_after)]]), {
1194
+ const batch2 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch1.chunkData.next_after)]]), {
906
1195
  limit: 4
907
1196
  }));
908
1197
  expect(test_utils.getBatchData(batch2)).toEqual([
@@ -914,29 +1203,28 @@ bucket_definitions:
914
1203
  has_more: false,
915
1204
  next_after: '6'
916
1205
  });
917
- const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([['global[]', BigInt(batch2.chunkData.next_after)]]), {
1206
+ const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch2.chunkData.next_after)]]), {
918
1207
  limit: 4
919
1208
  }));
920
1209
  expect(test_utils.getBatchData(batch3)).toEqual([]);
921
1210
  expect(test_utils.getBatchMeta(batch3)).toEqual(null);
922
1211
  }
923
- catch (e_9) {
924
- env_9.error = e_9;
925
- env_9.hasError = true;
1212
+ catch (e_13) {
1213
+ env_13.error = e_13;
1214
+ env_13.hasError = true;
926
1215
  }
927
1216
  finally {
928
- const result_9 = __disposeResources(env_9);
929
- if (result_9)
930
- await result_9;
1217
+ const result_13 = __disposeResources(env_13);
1218
+ if (result_13)
1219
+ await result_13;
931
1220
  }
932
1221
  });
933
1222
  describe('batch has_more', () => {
934
1223
  const setup = async (options) => {
935
- const env_10 = { stack: [], error: void 0, hasError: false };
1224
+ const env_14 = { stack: [], error: void 0, hasError: false };
936
1225
  try {
937
- const factory = __addDisposableResource(env_10, await generateStorageFactory(), true);
938
- const syncRules = await factory.updateSyncRules({
939
- content: `
1226
+ const factory = __addDisposableResource(env_14, await generateStorageFactory(), true);
1227
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
940
1228
  bucket_definitions:
941
1229
  global1:
942
1230
  data:
@@ -944,10 +1232,10 @@ bucket_definitions:
944
1232
  global2:
945
1233
  data:
946
1234
  - SELECT id, description FROM test WHERE bucket = 'global2'
947
- `
948
- });
1235
+ `, { storageVersion }));
949
1236
  const bucketStorage = factory.getInstance(syncRules);
950
1237
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1238
+ await batch.markAllSnapshotDone('1/1');
951
1239
  const sourceTable = TEST_TABLE;
952
1240
  for (let i = 1; i <= 10; i++) {
953
1241
  await batch.save({
@@ -964,26 +1252,27 @@ bucket_definitions:
964
1252
  await batch.commit('1/1');
965
1253
  });
966
1254
  const { checkpoint } = await bucketStorage.getCheckpoint();
967
- return await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, new Map([
1255
+ const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [
968
1256
  ['global1[]', 0n],
969
1257
  ['global2[]', 0n]
970
1258
  ]), options));
1259
+ return { syncRules, batch };
971
1260
  }
972
- catch (e_10) {
973
- env_10.error = e_10;
974
- env_10.hasError = true;
1261
+ catch (e_14) {
1262
+ env_14.error = e_14;
1263
+ env_14.hasError = true;
975
1264
  }
976
1265
  finally {
977
- const result_10 = __disposeResources(env_10);
978
- if (result_10)
979
- await result_10;
1266
+ const result_14 = __disposeResources(env_14);
1267
+ if (result_14)
1268
+ await result_14;
980
1269
  }
981
1270
  };
982
1271
  test('batch has_more (1)', async () => {
983
- const batch = await setup({ limit: 5 });
1272
+ const { batch, syncRules } = await setup({ limit: 5 });
984
1273
  expect(batch.length).toEqual(2);
985
- expect(batch[0].chunkData.bucket).toEqual('global1[]');
986
- expect(batch[1].chunkData.bucket).toEqual('global2[]');
1274
+ expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
1275
+ expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
987
1276
  expect(test_utils.getBatchData(batch[0])).toEqual([
988
1277
  { op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
989
1278
  ]);
@@ -1005,10 +1294,10 @@ bucket_definitions:
1005
1294
  });
1006
1295
  });
1007
1296
  test('batch has_more (2)', async () => {
1008
- const batch = await setup({ limit: 11 });
1297
+ const { batch, syncRules } = await setup({ limit: 11 });
1009
1298
  expect(batch.length).toEqual(2);
1010
- expect(batch[0].chunkData.bucket).toEqual('global1[]');
1011
- expect(batch[1].chunkData.bucket).toEqual('global2[]');
1299
+ expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
1300
+ expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
1012
1301
  expect(test_utils.getBatchData(batch[0])).toEqual([
1013
1302
  { op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
1014
1303
  ]);
@@ -1036,11 +1325,11 @@ bucket_definitions:
1036
1325
  });
1037
1326
  test('batch has_more (3)', async () => {
1038
1327
  // 50 bytes is more than 1 row, less than 2 rows
1039
- const batch = await setup({ limit: 3, chunkLimitBytes: 50 });
1328
+ const { batch, syncRules } = await setup({ limit: 3, chunkLimitBytes: 50 });
1040
1329
  expect(batch.length).toEqual(3);
1041
- expect(batch[0].chunkData.bucket).toEqual('global1[]');
1042
- expect(batch[1].chunkData.bucket).toEqual('global2[]');
1043
- expect(batch[2].chunkData.bucket).toEqual('global2[]');
1330
+ expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
1331
+ expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
1332
+ expect(batch[2].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
1044
1333
  expect(test_utils.getBatchData(batch[0])).toEqual([
1045
1334
  { op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
1046
1335
  ]);
@@ -1068,53 +1357,54 @@ bucket_definitions:
1068
1357
  });
1069
1358
  });
1070
1359
  test('empty storage metrics', async () => {
1071
- const env_11 = { stack: [], error: void 0, hasError: false };
1360
+ const env_15 = { stack: [], error: void 0, hasError: false };
1072
1361
  try {
1073
- const f = __addDisposableResource(env_11, await generateStorageFactory({ dropAll: true }), true);
1362
+ const f = __addDisposableResource(env_15, await generateStorageFactory({ dropAll: true }), true);
1074
1363
  const metrics = await f.getStorageMetrics();
1075
1364
  expect(metrics).toEqual({
1076
1365
  operations_size_bytes: 0,
1077
1366
  parameters_size_bytes: 0,
1078
1367
  replication_size_bytes: 0
1079
1368
  });
1080
- const r = await f.configureSyncRules({ content: 'bucket_definitions: {}', validate: false });
1369
+ const r = await f.configureSyncRules(updateSyncRulesFromYaml('bucket_definitions: {}'));
1081
1370
  const storage = f.getInstance(r.persisted_sync_rules);
1082
1371
  await storage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1372
+ await batch.markAllSnapshotDone('1/0');
1083
1373
  await batch.keepalive('1/0');
1084
1374
  });
1085
- const metrics2 = await f.getStorageMetrics();
1086
- expect(metrics2).toMatchSnapshot();
1375
+ await f.getStorageMetrics();
1087
1376
  }
1088
- catch (e_11) {
1089
- env_11.error = e_11;
1090
- env_11.hasError = true;
1377
+ catch (e_15) {
1378
+ env_15.error = e_15;
1379
+ env_15.hasError = true;
1091
1380
  }
1092
1381
  finally {
1093
- const result_11 = __disposeResources(env_11);
1094
- if (result_11)
1095
- await result_11;
1382
+ const result_15 = __disposeResources(env_15);
1383
+ if (result_15)
1384
+ await result_15;
1096
1385
  }
1097
1386
  });
1098
1387
  test('op_id initialization edge case', async () => {
1099
- const env_12 = { stack: [], error: void 0, hasError: false };
1388
+ const env_16 = { stack: [], error: void 0, hasError: false };
1100
1389
  try {
1101
1390
  // Test syncing a batch of data that is small in count,
1102
1391
  // but large enough in size to be split over multiple returned chunks.
1103
1392
  // Similar to the above test, but splits over 1MB chunks.
1104
- const factory = __addDisposableResource(env_12, await generateStorageFactory(), true);
1105
- const syncRules = await factory.updateSyncRules({
1106
- content: `
1393
+ const factory = __addDisposableResource(env_16, await generateStorageFactory(), true);
1394
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
1107
1395
  bucket_definitions:
1108
1396
  global:
1109
1397
  data:
1110
1398
  - SELECT id FROM test
1111
1399
  - SELECT id FROM test_ignore WHERE false
1112
- `
1113
- });
1400
+ `, {
1401
+ storageVersion
1402
+ }));
1114
1403
  const bucketStorage = factory.getInstance(syncRules);
1115
- const sourceTable = test_utils.makeTestTable('test', ['id']);
1116
- const sourceTableIgnore = test_utils.makeTestTable('test_ignore', ['id']);
1404
+ const sourceTable = test_utils.makeTestTable('test', ['id'], config);
1405
+ const sourceTableIgnore = test_utils.makeTestTable('test_ignore', ['id'], config);
1117
1406
  const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1407
+ await batch.markAllSnapshotDone('1/1');
1118
1408
  // This saves a record to current_data, but not bucket_data.
1119
1409
  // This causes a checkpoint to be created without increasing the op_id sequence.
1120
1410
  await batch.save({
@@ -1141,31 +1431,32 @@ bucket_definitions:
1141
1431
  // we expect 0n and 1n, or 1n and 2n.
1142
1432
  expect(checkpoint2).toBeGreaterThan(checkpoint1);
1143
1433
  }
1144
- catch (e_12) {
1145
- env_12.error = e_12;
1146
- env_12.hasError = true;
1434
+ catch (e_16) {
1435
+ env_16.error = e_16;
1436
+ env_16.hasError = true;
1147
1437
  }
1148
1438
  finally {
1149
- const result_12 = __disposeResources(env_12);
1150
- if (result_12)
1151
- await result_12;
1439
+ const result_16 = __disposeResources(env_16);
1440
+ if (result_16)
1441
+ await result_16;
1152
1442
  }
1153
1443
  });
1154
1444
  test('unchanged checksums', async () => {
1155
- const env_13 = { stack: [], error: void 0, hasError: false };
1445
+ const env_17 = { stack: [], error: void 0, hasError: false };
1156
1446
  try {
1157
- const factory = __addDisposableResource(env_13, await generateStorageFactory(), true);
1158
- const syncRules = await factory.updateSyncRules({
1159
- content: `
1447
+ const factory = __addDisposableResource(env_17, await generateStorageFactory(), true);
1448
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
1160
1449
  bucket_definitions:
1161
1450
  global:
1162
1451
  data:
1163
1452
  - SELECT client_id as id, description FROM "%"
1164
- `
1165
- });
1453
+ `, {
1454
+ storageVersion
1455
+ }));
1166
1456
  const bucketStorage = factory.getInstance(syncRules);
1167
1457
  const sourceTable = TEST_TABLE;
1168
1458
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1459
+ await batch.markAllSnapshotDone('1/1');
1169
1460
  await batch.save({
1170
1461
  sourceTable,
1171
1462
  tag: storage.SaveOperationTag.INSERT,
@@ -1178,45 +1469,262 @@ bucket_definitions:
1178
1469
  await batch.commit('1/1');
1179
1470
  });
1180
1471
  const { checkpoint } = await bucketStorage.getCheckpoint();
1181
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, ['global[]'])).values()];
1182
- expect(checksums).toEqual([{ bucket: 'global[]', checksum: 1917136889, count: 1 }]);
1183
- const checksums2 = [...(await bucketStorage.getChecksums(checkpoint + 1n, ['global[]'])).values()];
1184
- expect(checksums2).toEqual([{ bucket: 'global[]', checksum: 1917136889, count: 1 }]);
1472
+ const checksums = [
1473
+ ...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
1474
+ ];
1475
+ expect(checksums).toEqual([
1476
+ { bucket: bucketRequest(syncRules, 'global[]').bucket, checksum: 1917136889, count: 1 }
1477
+ ]);
1478
+ const checksums2 = [
1479
+ ...(await bucketStorage.getChecksums(checkpoint + 1n, bucketRequests(syncRules, ['global[]']))).values()
1480
+ ];
1481
+ expect(checksums2).toEqual([
1482
+ { bucket: bucketRequest(syncRules, 'global[]').bucket, checksum: 1917136889, count: 1 }
1483
+ ]);
1185
1484
  }
1186
- catch (e_13) {
1187
- env_13.error = e_13;
1188
- env_13.hasError = true;
1485
+ catch (e_17) {
1486
+ env_17.error = e_17;
1487
+ env_17.hasError = true;
1189
1488
  }
1190
1489
  finally {
1191
- const result_13 = __disposeResources(env_13);
1192
- if (result_13)
1193
- await result_13;
1490
+ const result_17 = __disposeResources(env_17);
1491
+ if (result_17)
1492
+ await result_17;
1493
+ }
1494
+ });
1495
+ testChecksumBatching(config);
1496
+ test('empty checkpoints (1)', async () => {
1497
+ const env_18 = { stack: [], error: void 0, hasError: false };
1498
+ try {
1499
+ const factory = __addDisposableResource(env_18, await generateStorageFactory(), true);
1500
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
1501
+ bucket_definitions:
1502
+ global:
1503
+ data:
1504
+ - SELECT id, description FROM "%"
1505
+ `, { storageVersion }));
1506
+ const bucketStorage = factory.getInstance(syncRules);
1507
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1508
+ await batch.markAllSnapshotDone('1/1');
1509
+ await batch.commit('1/1');
1510
+ const cp1 = await bucketStorage.getCheckpoint();
1511
+ expect(cp1.lsn).toEqual('1/1');
1512
+ await batch.commit('2/1', { createEmptyCheckpoints: true });
1513
+ const cp2 = await bucketStorage.getCheckpoint();
1514
+ expect(cp2.lsn).toEqual('2/1');
1515
+ await batch.keepalive('3/1');
1516
+ const cp3 = await bucketStorage.getCheckpoint();
1517
+ expect(cp3.lsn).toEqual('3/1');
1518
+ // For the last one, we skip creating empty checkpoints
1519
+ // This means the LSN stays at 3/1.
1520
+ await batch.commit('4/1', { createEmptyCheckpoints: false });
1521
+ const cp4 = await bucketStorage.getCheckpoint();
1522
+ expect(cp4.lsn).toEqual('3/1');
1523
+ });
1524
+ }
1525
+ catch (e_18) {
1526
+ env_18.error = e_18;
1527
+ env_18.hasError = true;
1528
+ }
1529
+ finally {
1530
+ const result_18 = __disposeResources(env_18);
1531
+ if (result_18)
1532
+ await result_18;
1533
+ }
1534
+ });
1535
+ test('empty checkpoints (2)', async () => {
1536
+ const env_19 = { stack: [], error: void 0, hasError: false };
1537
+ try {
1538
+ const factory = __addDisposableResource(env_19, await generateStorageFactory(), true);
1539
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
1540
+ bucket_definitions:
1541
+ global:
1542
+ data:
1543
+ - SELECT id, description FROM "%"
1544
+ `, {
1545
+ storageVersion
1546
+ }));
1547
+ const bucketStorage = factory.getInstance(syncRules);
1548
+ const sourceTable = TEST_TABLE;
1549
+ // We simulate two concurrent batches, but nesting is the easiest way to do this.
1550
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch1) => {
1551
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch2) => {
1552
+ await batch1.markAllSnapshotDone('1/1');
1553
+ await batch1.commit('1/1');
1554
+ await batch1.commit('2/1', { createEmptyCheckpoints: false });
1555
+ const cp2 = await bucketStorage.getCheckpoint();
1556
+ expect(cp2.lsn).toEqual('1/1'); // checkpoint 2/1 skipped
1557
+ await batch2.save({
1558
+ sourceTable,
1559
+ tag: storage.SaveOperationTag.INSERT,
1560
+ after: {
1561
+ id: 'test1',
1562
+ description: 'test1a'
1563
+ },
1564
+ afterReplicaId: test_utils.rid('test1')
1565
+ });
1566
+ // This simulates what happens on a snapshot processor.
1567
+ // This may later change to a flush() rather than commit().
1568
+ await batch2.commit(test_utils.BATCH_OPTIONS.zeroLSN);
1569
+ const cp3 = await bucketStorage.getCheckpoint();
1570
+ expect(cp3.lsn).toEqual('1/1'); // Still unchanged
1571
+ // This now needs to advance the LSN, despite {createEmptyCheckpoints: false}
1572
+ await batch1.commit('4/1', { createEmptyCheckpoints: false });
1573
+ const cp4 = await bucketStorage.getCheckpoint();
1574
+ expect(cp4.lsn).toEqual('4/1');
1575
+ });
1576
+ });
1577
+ }
1578
+ catch (e_19) {
1579
+ env_19.error = e_19;
1580
+ env_19.hasError = true;
1581
+ }
1582
+ finally {
1583
+ const result_19 = __disposeResources(env_19);
1584
+ if (result_19)
1585
+ await result_19;
1586
+ }
1587
+ });
1588
+ test('empty checkpoints (sync rule activation)', async () => {
1589
+ const env_20 = { stack: [], error: void 0, hasError: false };
1590
+ try {
1591
+ const factory = __addDisposableResource(env_20, await generateStorageFactory(), true);
1592
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
1593
+ bucket_definitions:
1594
+ global:
1595
+ data:
1596
+ - SELECT id, description FROM "%"
1597
+ `, {
1598
+ storageVersion
1599
+ }));
1600
+ const bucketStorage = factory.getInstance(syncRules);
1601
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1602
+ const result = await batch.commit('1/1', { createEmptyCheckpoints: false });
1603
+ expect(result).toEqual({ checkpointBlocked: true, checkpointCreated: false });
1604
+ // Snapshot is only valid once we reach 3/1
1605
+ await batch.markAllSnapshotDone('3/1');
1606
+ });
1607
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1608
+ // 2/1 < 3/1 - snapshot not valid yet, block checkpoint
1609
+ const result = await batch.commit('2/1', { createEmptyCheckpoints: false });
1610
+ expect(result).toEqual({ checkpointBlocked: true, checkpointCreated: false });
1611
+ });
1612
+ // No empty checkpoint should be created by the commit above.
1613
+ const cp1 = await bucketStorage.getCheckpoint();
1614
+ expect(cp1.lsn).toEqual(null);
1615
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1616
+ // After this commit, the snapshot should be valid.
1617
+ // We specifically check that this is done even if createEmptyCheckpoints: false.
1618
+ const result = await batch.commit('3/1', { createEmptyCheckpoints: false });
1619
+ expect(result).toEqual({ checkpointBlocked: false, checkpointCreated: true });
1620
+ });
1621
+ // Now, the checkpoint should advance the sync rules active.
1622
+ const cp2 = await bucketStorage.getCheckpoint();
1623
+ expect(cp2.lsn).toEqual('3/1');
1624
+ const activeSyncRules = await factory.getActiveSyncRulesContent();
1625
+ expect(activeSyncRules?.id).toEqual(syncRules.id);
1626
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1627
+ // At this point, it should be a truely empty checkpoint
1628
+ const result = await batch.commit('4/1', { createEmptyCheckpoints: false });
1629
+ expect(result).toEqual({ checkpointBlocked: false, checkpointCreated: false });
1630
+ });
1631
+ // Unchanged
1632
+ const cp3 = await bucketStorage.getCheckpoint();
1633
+ expect(cp3.lsn).toEqual('3/1');
1634
+ }
1635
+ catch (e_20) {
1636
+ env_20.error = e_20;
1637
+ env_20.hasError = true;
1638
+ }
1639
+ finally {
1640
+ const result_20 = __disposeResources(env_20);
1641
+ if (result_20)
1642
+ await result_20;
1643
+ }
1644
+ });
1645
+ test.runIf(storageVersion >= 3)('deleting while streaming', async () => {
1646
+ const env_21 = { stack: [], error: void 0, hasError: false };
1647
+ try {
1648
+ const factory = __addDisposableResource(env_21, await generateStorageFactory(), true);
1649
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
1650
+ bucket_definitions:
1651
+ global:
1652
+ data:
1653
+ - SELECT id, description FROM "%"
1654
+ `, {
1655
+ storageVersion
1656
+ }));
1657
+ const bucketStorage = factory.getInstance(syncRules);
1658
+ const sourceTable = TEST_TABLE;
1659
+ // We simulate two concurrent batches, and nesting is the easiest way to do this.
1660
+ // For this test, we assume that we start with a row "test1", which is picked up by a snapshot
1661
+ // query, right before the delete is streamed. But the snapshot query is only persisted _after_
1662
+ // the delete is streamed, and we need to ensure that the streamed delete takes precedence.
1663
+ await bucketStorage.startBatch({ ...test_utils.BATCH_OPTIONS, skipExistingRows: true }, async (snapshotBatch) => {
1664
+ await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (streamingBatch) => {
1665
+ streamingBatch.save({
1666
+ sourceTable,
1667
+ tag: storage.SaveOperationTag.DELETE,
1668
+ before: {
1669
+ id: 'test1'
1670
+ },
1671
+ beforeReplicaId: test_utils.rid('test1')
1672
+ });
1673
+ await streamingBatch.commit('2/1');
1674
+ await snapshotBatch.save({
1675
+ sourceTable,
1676
+ tag: storage.SaveOperationTag.INSERT,
1677
+ after: {
1678
+ id: 'test1',
1679
+ description: 'test1a'
1680
+ },
1681
+ afterReplicaId: test_utils.rid('test1')
1682
+ });
1683
+ await snapshotBatch.markAllSnapshotDone('3/1');
1684
+ await snapshotBatch.commit('1/1');
1685
+ await streamingBatch.keepalive('3/1');
1686
+ });
1687
+ });
1688
+ const cp = await bucketStorage.getCheckpoint();
1689
+ expect(cp.lsn).toEqual('3/1');
1690
+ const data = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(cp.checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
1691
+ expect(data).toEqual([]);
1692
+ }
1693
+ catch (e_21) {
1694
+ env_21.error = e_21;
1695
+ env_21.hasError = true;
1696
+ }
1697
+ finally {
1698
+ const result_21 = __disposeResources(env_21);
1699
+ if (result_21)
1700
+ await result_21;
1194
1701
  }
1195
1702
  });
1196
- testChecksumBatching(generateStorageFactory);
1197
1703
  }
1198
1704
  /**
1199
1705
  * This specifically tests an issue we ran into with MongoDB storage.
1200
1706
  *
1201
1707
  * Exposed as a separate test so we can test with more storage parameters.
1202
1708
  */
1203
- export function testChecksumBatching(generateStorageFactory) {
1709
+ export function testChecksumBatching(config) {
1710
+ const storageVersion = config.storageVersion ?? CURRENT_STORAGE_VERSION;
1204
1711
  test('checksums for multiple buckets', async () => {
1205
- const env_14 = { stack: [], error: void 0, hasError: false };
1712
+ const env_22 = { stack: [], error: void 0, hasError: false };
1206
1713
  try {
1207
- const factory = __addDisposableResource(env_14, await generateStorageFactory(), true);
1208
- const syncRules = await factory.updateSyncRules({
1209
- content: `
1714
+ const factory = __addDisposableResource(env_22, await config.factory(), true);
1715
+ const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
1210
1716
  bucket_definitions:
1211
1717
  user:
1212
1718
  parameters: select request.user_id() as user_id
1213
1719
  data:
1214
1720
  - select id, description from test where user_id = bucket.user_id
1215
- `
1216
- });
1721
+ `, {
1722
+ storageVersion
1723
+ }));
1217
1724
  const bucketStorage = factory.getInstance(syncRules);
1218
- const sourceTable = TEST_TABLE;
1725
+ const sourceTable = test_utils.makeTestTable('test', ['id'], config);
1219
1726
  await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1727
+ await batch.markAllSnapshotDone('1/1');
1220
1728
  for (let u of ['u1', 'u2', 'u3', 'u4']) {
1221
1729
  for (let t of ['t1', 't2', 't3', 't4']) {
1222
1730
  const id = `${t}_${u}`;
@@ -1236,24 +1744,24 @@ bucket_definitions:
1236
1744
  });
1237
1745
  const { checkpoint } = await bucketStorage.getCheckpoint();
1238
1746
  bucketStorage.clearChecksumCache();
1239
- const buckets = ['user["u1"]', 'user["u2"]', 'user["u3"]', 'user["u4"]'];
1747
+ const buckets = bucketRequests(syncRules, ['user["u1"]', 'user["u2"]', 'user["u3"]', 'user["u4"]']);
1240
1748
  const checksums = [...(await bucketStorage.getChecksums(checkpoint, buckets)).values()];
1241
1749
  checksums.sort((a, b) => a.bucket.localeCompare(b.bucket));
1242
1750
  expect(checksums).toEqual([
1243
- { bucket: 'user["u1"]', count: 4, checksum: 346204588 },
1244
- { bucket: 'user["u2"]', count: 4, checksum: 5261081 },
1245
- { bucket: 'user["u3"]', count: 4, checksum: 134760718 },
1246
- { bucket: 'user["u4"]', count: 4, checksum: -302639724 }
1751
+ { bucket: bucketRequest(syncRules, 'user["u1"]').bucket, count: 4, checksum: 346204588 },
1752
+ { bucket: bucketRequest(syncRules, 'user["u2"]').bucket, count: 4, checksum: 5261081 },
1753
+ { bucket: bucketRequest(syncRules, 'user["u3"]').bucket, count: 4, checksum: 134760718 },
1754
+ { bucket: bucketRequest(syncRules, 'user["u4"]').bucket, count: 4, checksum: -302639724 }
1247
1755
  ]);
1248
1756
  }
1249
- catch (e_14) {
1250
- env_14.error = e_14;
1251
- env_14.hasError = true;
1757
+ catch (e_22) {
1758
+ env_22.error = e_22;
1759
+ env_22.hasError = true;
1252
1760
  }
1253
1761
  finally {
1254
- const result_14 = __disposeResources(env_14);
1255
- if (result_14)
1256
- await result_14;
1762
+ const result_22 = __disposeResources(env_22);
1763
+ if (result_22)
1764
+ await result_22;
1257
1765
  }
1258
1766
  });
1259
1767
  }