@powersync/service-core-tests 0.16.0 → 0.17.1
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 +34 -0
- package/dist/test-utils/AbstractStreamTestContext.d.ts +6 -5
- package/dist/test-utils/AbstractStreamTestContext.js +26 -22
- package/dist/test-utils/AbstractStreamTestContext.js.map +1 -1
- package/dist/test-utils/StorageDataHelpers.d.ts +5 -5
- package/dist/test-utils/StorageDataHelpers.js.map +1 -1
- package/dist/test-utils/bucket-validation.d.ts +3 -3
- package/dist/test-utils/bucket-validation.js +3 -3
- package/dist/test-utils/general-utils.d.ts +13 -2
- package/dist/test-utils/general-utils.js +24 -3
- package/dist/test-utils/general-utils.js.map +1 -1
- package/dist/tests/register-compacting-tests.js +28 -21
- package/dist/tests/register-compacting-tests.js.map +1 -1
- package/dist/tests/register-data-storage-checkpoint-tests.js +8 -2
- package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
- package/dist/tests/register-data-storage-data-tests.js +148 -101
- package/dist/tests/register-data-storage-data-tests.js.map +1 -1
- package/dist/tests/register-data-storage-parameter-tests.js +44 -42
- package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
- package/dist/tests/register-sync-tests.js +141 -8
- package/dist/tests/register-sync-tests.js.map +1 -1
- package/dist/tests/util.d.ts +2 -2
- package/dist/tests/util.js.map +1 -1
- package/package.json +6 -7
- package/src/test-utils/AbstractStreamTestContext.ts +29 -24
- package/src/test-utils/StorageDataHelpers.ts +7 -6
- package/src/test-utils/bucket-validation.ts +3 -3
- package/src/test-utils/general-utils.ts +35 -7
- package/src/tests/register-compacting-tests.ts +45 -22
- package/src/tests/register-data-storage-checkpoint-tests.ts +12 -2
- package/src/tests/register-data-storage-data-tests.ts +210 -107
- package/src/tests/register-data-storage-parameter-tests.ts +58 -42
- package/src/tests/register-sync-tests.ts +159 -8
- package/src/tests/util.ts +2 -2
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -26,7 +26,8 @@ export function registerDataStorageParameterTests(config: storage.TestStorageCon
|
|
|
26
26
|
|
|
27
27
|
test('save and load parameters', async () => {
|
|
28
28
|
await using factory = await generateStorageFactory();
|
|
29
|
-
const syncRules = await
|
|
29
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
30
|
+
factory,
|
|
30
31
|
updateSyncRulesFromYaml(
|
|
31
32
|
`
|
|
32
33
|
bucket_definitions:
|
|
@@ -40,8 +41,8 @@ bucket_definitions:
|
|
|
40
41
|
}
|
|
41
42
|
)
|
|
42
43
|
);
|
|
43
|
-
const bucketStorage = factory.getInstance(
|
|
44
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
44
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
45
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
45
46
|
|
|
46
47
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
47
48
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -92,7 +93,8 @@ bucket_definitions:
|
|
|
92
93
|
|
|
93
94
|
test('it should use the latest version', async () => {
|
|
94
95
|
await using factory = await generateStorageFactory();
|
|
95
|
-
const syncRules = await
|
|
96
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
97
|
+
factory,
|
|
96
98
|
updateSyncRulesFromYaml(
|
|
97
99
|
`
|
|
98
100
|
bucket_definitions:
|
|
@@ -106,8 +108,8 @@ bucket_definitions:
|
|
|
106
108
|
}
|
|
107
109
|
)
|
|
108
110
|
);
|
|
109
|
-
const bucketStorage = factory.getInstance(
|
|
110
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
111
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
112
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
111
113
|
|
|
112
114
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
113
115
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -163,7 +165,8 @@ bucket_definitions:
|
|
|
163
165
|
|
|
164
166
|
test('it should use the latest version after updates', async () => {
|
|
165
167
|
await using factory = await generateStorageFactory();
|
|
166
|
-
const syncRules = await
|
|
168
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
169
|
+
factory,
|
|
167
170
|
updateSyncRulesFromYaml(
|
|
168
171
|
`
|
|
169
172
|
bucket_definitions:
|
|
@@ -177,8 +180,8 @@ bucket_definitions:
|
|
|
177
180
|
{ storageVersion }
|
|
178
181
|
)
|
|
179
182
|
);
|
|
180
|
-
const bucketStorage = factory.getInstance(
|
|
181
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
183
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
184
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
182
185
|
|
|
183
186
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
184
187
|
const table = await test_utils.resolveTestTable(writer, 'todos', ['id', 'list_id'], config);
|
|
@@ -250,7 +253,8 @@ bucket_definitions:
|
|
|
250
253
|
|
|
251
254
|
test('save and load parameters with different number types', async () => {
|
|
252
255
|
await using factory = await generateStorageFactory();
|
|
253
|
-
const syncRules = await
|
|
256
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
257
|
+
factory,
|
|
254
258
|
updateSyncRulesFromYaml(
|
|
255
259
|
`
|
|
256
260
|
bucket_definitions:
|
|
@@ -264,8 +268,8 @@ bucket_definitions:
|
|
|
264
268
|
}
|
|
265
269
|
)
|
|
266
270
|
);
|
|
267
|
-
const bucketStorage = factory.getInstance(
|
|
268
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
271
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
272
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
269
273
|
|
|
270
274
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
271
275
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -318,7 +322,8 @@ bucket_definitions:
|
|
|
318
322
|
// test this to ensure correct deserialization.
|
|
319
323
|
|
|
320
324
|
await using factory = await generateStorageFactory();
|
|
321
|
-
const syncRules = await
|
|
325
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
326
|
+
factory,
|
|
322
327
|
updateSyncRulesFromYaml(
|
|
323
328
|
`
|
|
324
329
|
bucket_definitions:
|
|
@@ -332,8 +337,8 @@ bucket_definitions:
|
|
|
332
337
|
}
|
|
333
338
|
)
|
|
334
339
|
);
|
|
335
|
-
const bucketStorage = factory.getInstance(
|
|
336
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
340
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
341
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
337
342
|
|
|
338
343
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
339
344
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -385,7 +390,8 @@ bucket_definitions:
|
|
|
385
390
|
|
|
386
391
|
test('save and load parameters with workspaceId', async () => {
|
|
387
392
|
await using factory = await generateStorageFactory();
|
|
388
|
-
const syncRules = await
|
|
393
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
394
|
+
factory,
|
|
389
395
|
updateSyncRulesFromYaml(
|
|
390
396
|
`
|
|
391
397
|
bucket_definitions:
|
|
@@ -400,8 +406,8 @@ bucket_definitions:
|
|
|
400
406
|
}
|
|
401
407
|
)
|
|
402
408
|
);
|
|
403
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
404
|
-
const bucketStorage = factory.getInstance(
|
|
409
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
410
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
405
411
|
|
|
406
412
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
407
413
|
const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', ['id'], config);
|
|
@@ -443,7 +449,8 @@ bucket_definitions:
|
|
|
443
449
|
|
|
444
450
|
test('save and load parameters with dynamic global buckets', async () => {
|
|
445
451
|
await using factory = await generateStorageFactory();
|
|
446
|
-
const syncRules = await
|
|
452
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
453
|
+
factory,
|
|
447
454
|
updateSyncRulesFromYaml(
|
|
448
455
|
`
|
|
449
456
|
bucket_definitions:
|
|
@@ -458,8 +465,8 @@ bucket_definitions:
|
|
|
458
465
|
}
|
|
459
466
|
)
|
|
460
467
|
);
|
|
461
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
462
|
-
const bucketStorage = factory.getInstance(
|
|
468
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
469
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
463
470
|
|
|
464
471
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
465
472
|
const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
|
|
@@ -535,7 +542,8 @@ bucket_definitions:
|
|
|
535
542
|
|
|
536
543
|
test('multiple parameter queries', async () => {
|
|
537
544
|
await using factory = await generateStorageFactory();
|
|
538
|
-
const syncRules = await
|
|
545
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
546
|
+
factory,
|
|
539
547
|
updateSyncRulesFromYaml(
|
|
540
548
|
`
|
|
541
549
|
bucket_definitions:
|
|
@@ -552,8 +560,8 @@ bucket_definitions:
|
|
|
552
560
|
}
|
|
553
561
|
)
|
|
554
562
|
);
|
|
555
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
556
|
-
const bucketStorage = factory.getInstance(
|
|
563
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
564
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
557
565
|
|
|
558
566
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
559
567
|
const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
|
|
@@ -639,7 +647,8 @@ bucket_definitions:
|
|
|
639
647
|
|
|
640
648
|
test('truncate parameters', async () => {
|
|
641
649
|
await using factory = await generateStorageFactory();
|
|
642
|
-
const syncRules = await
|
|
650
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
651
|
+
factory,
|
|
643
652
|
updateSyncRulesFromYaml(
|
|
644
653
|
`
|
|
645
654
|
bucket_definitions:
|
|
@@ -653,8 +662,8 @@ bucket_definitions:
|
|
|
653
662
|
}
|
|
654
663
|
)
|
|
655
664
|
);
|
|
656
|
-
const bucketStorage = factory.getInstance(
|
|
657
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
665
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
666
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
658
667
|
|
|
659
668
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
660
669
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -732,7 +741,8 @@ bucket_definitions:
|
|
|
732
741
|
|
|
733
742
|
test('sync streams smoke test', async () => {
|
|
734
743
|
await using factory = await generateStorageFactory();
|
|
735
|
-
const syncRules = await
|
|
744
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
745
|
+
factory,
|
|
736
746
|
updateSyncRulesFromYaml(`
|
|
737
747
|
config:
|
|
738
748
|
edition: 3
|
|
@@ -744,8 +754,8 @@ streams:
|
|
|
744
754
|
WHERE data.foo = param.bar AND param.baz = auth.user_id()
|
|
745
755
|
`)
|
|
746
756
|
);
|
|
747
|
-
const bucketStorage = factory.getInstance(
|
|
748
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
757
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
758
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
749
759
|
|
|
750
760
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
751
761
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -799,7 +809,8 @@ streams:
|
|
|
799
809
|
|
|
800
810
|
test('respects parameter limit', async () => {
|
|
801
811
|
await using factory = await generateStorageFactory();
|
|
802
|
-
const syncRules = await
|
|
812
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
813
|
+
factory,
|
|
803
814
|
updateSyncRulesFromYaml(
|
|
804
815
|
`
|
|
805
816
|
config:
|
|
@@ -815,8 +826,8 @@ streams:
|
|
|
815
826
|
}
|
|
816
827
|
)
|
|
817
828
|
);
|
|
818
|
-
const bucketStorage = factory.getInstance(
|
|
819
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
829
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
830
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
820
831
|
|
|
821
832
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
822
833
|
const testTable = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
|
|
@@ -851,7 +862,8 @@ streams:
|
|
|
851
862
|
|
|
852
863
|
test('sync streams store multiple parameter outputs for a single source row and lookup', async () => {
|
|
853
864
|
await using factory = await generateStorageFactory();
|
|
854
|
-
const syncRules = await
|
|
865
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
866
|
+
factory,
|
|
855
867
|
updateSyncRulesFromYaml(`
|
|
856
868
|
config:
|
|
857
869
|
edition: 3
|
|
@@ -864,8 +876,8 @@ streams:
|
|
|
864
876
|
WHERE a.x = x.value AND y.value = auth.user_id()
|
|
865
877
|
`)
|
|
866
878
|
);
|
|
867
|
-
const bucketStorage = factory.getInstance(
|
|
868
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
879
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
880
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
869
881
|
|
|
870
882
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
871
883
|
const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
|
|
@@ -979,7 +991,8 @@ streams:
|
|
|
979
991
|
|
|
980
992
|
test('can request multiple lookups at once', async () => {
|
|
981
993
|
await using factory = await generateStorageFactory();
|
|
982
|
-
const syncRules = await
|
|
994
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
995
|
+
factory,
|
|
983
996
|
updateSyncRulesFromYaml(`
|
|
984
997
|
config:
|
|
985
998
|
edition: 3
|
|
@@ -992,10 +1005,12 @@ streams:
|
|
|
992
1005
|
query: SELECT * FROM b WHERE p IN (SELECT id FROM param_b WHERE u = auth.user_id())
|
|
993
1006
|
`)
|
|
994
1007
|
);
|
|
995
|
-
const bucketStorage = factory.getInstance(
|
|
1008
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
996
1009
|
const parsedSyncRules = syncRules.parsed(test_utils.PARSE_OPTIONS);
|
|
997
1010
|
const hydrationState = parsedSyncRules.hydrationState;
|
|
998
|
-
const
|
|
1011
|
+
const [parsedSyncConfig] = parsedSyncRules.syncConfigs;
|
|
1012
|
+
expect(parsedSyncConfig).toBeDefined();
|
|
1013
|
+
const syncConfig = parsedSyncConfig.config;
|
|
999
1014
|
|
|
1000
1015
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1001
1016
|
const paramATable = await test_utils.resolveTestTable(writer, 'param_a', ['id'], config, 1);
|
|
@@ -1069,7 +1084,8 @@ streams:
|
|
|
1069
1084
|
|
|
1070
1085
|
test('sync streams preserve duplicate downstream lookups with different provenance', async () => {
|
|
1071
1086
|
await using factory = await generateStorageFactory();
|
|
1072
|
-
const syncRules = await
|
|
1087
|
+
const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
|
|
1088
|
+
factory,
|
|
1073
1089
|
updateSyncRulesFromYaml(
|
|
1074
1090
|
`
|
|
1075
1091
|
config:
|
|
@@ -1090,8 +1106,8 @@ streams:
|
|
|
1090
1106
|
}
|
|
1091
1107
|
)
|
|
1092
1108
|
);
|
|
1093
|
-
const bucketStorage = factory.getInstance(
|
|
1094
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig
|
|
1109
|
+
const bucketStorage = factory.getInstance(replicationStream);
|
|
1110
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
|
|
1095
1111
|
|
|
1096
1112
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1097
1113
|
const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config, 1);
|
|
@@ -687,7 +687,7 @@ bucket_definitions:
|
|
|
687
687
|
receivedCompletions++;
|
|
688
688
|
if (receivedCompletions == 1) {
|
|
689
689
|
// Trigger an empty bucket update.
|
|
690
|
-
await bucketStorage.
|
|
690
|
+
await bucketStorage.createManagedWriteCheckpoints([{ user_id: '', heads: { '1': '1/0' } }]);
|
|
691
691
|
await writer.commit('1/0');
|
|
692
692
|
} else {
|
|
693
693
|
break;
|
|
@@ -892,7 +892,8 @@ bucket_definitions:
|
|
|
892
892
|
|
|
893
893
|
const checkpoint2 = await getCheckpointLines(iter);
|
|
894
894
|
|
|
895
|
-
const
|
|
895
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
896
|
+
const { bucket } = test_utils.bucketRequest(syncRulesContent, 'by_user["user1"]');
|
|
896
897
|
expect(
|
|
897
898
|
(checkpoint2[0] as StreamingSyncCheckpointDiff).checkpoint_diff?.updated_buckets?.map((b) => b.bucket)
|
|
898
899
|
).toEqual([bucket]);
|
|
@@ -947,7 +948,8 @@ bucket_definitions:
|
|
|
947
948
|
iter.return?.();
|
|
948
949
|
});
|
|
949
950
|
|
|
950
|
-
const
|
|
951
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
952
|
+
const { bucket } = bucketRequest(syncRulesContent, 'by_user["user1"]');
|
|
951
953
|
const checkpoint1 = await getCheckpointLines(iter);
|
|
952
954
|
|
|
953
955
|
expect((checkpoint1[0] as StreamingSyncCheckpoint).checkpoint?.buckets?.map((b) => b.bucket)).toEqual([bucket]);
|
|
@@ -1038,7 +1040,8 @@ bucket_definitions:
|
|
|
1038
1040
|
|
|
1039
1041
|
await writer.commit('0/1');
|
|
1040
1042
|
|
|
1041
|
-
const
|
|
1043
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
1044
|
+
const { bucket } = test_utils.bucketRequest(syncRulesContent, 'by_user["user1"]');
|
|
1042
1045
|
|
|
1043
1046
|
const checkpoint2 = await getCheckpointLines(iter);
|
|
1044
1047
|
expect(
|
|
@@ -1235,6 +1238,150 @@ bucket_definitions:
|
|
|
1235
1238
|
});
|
|
1236
1239
|
});
|
|
1237
1240
|
|
|
1241
|
+
test('compacting high-priority data invalidates checkpoint before lower priorities', async (context) => {
|
|
1242
|
+
// Scenario:
|
|
1243
|
+
// 1. Bucket priorities are used.
|
|
1244
|
+
// 2. Client syncs a high-priority bucket.
|
|
1245
|
+
// 3. Due to a concurrent compaction, the checkpoint is invalidated (via target_op).
|
|
1246
|
+
// 4. The service picks up that invalidation, and the batch stops, without emitting a "partial_checkpoint_complete". The intention is continuing with the next checkpoint.
|
|
1247
|
+
// 5. However, the loop handling priorities does not see that invalidation, it only sees "bucket data for this priority is done".
|
|
1248
|
+
// 6. The priority loop incorrectly continues with the next priority.
|
|
1249
|
+
// 7. The service emits the data, as well as a final checkpoint_complete.
|
|
1250
|
+
// 8. The client now received a checkpoint_complete without the full data for that checkpoint. It would pick up the missing data in the checksum check, requiring a full re-download of the affected buckets.
|
|
1251
|
+
await using f = await factory();
|
|
1252
|
+
|
|
1253
|
+
const syncRules = await updateSyncRules(f, {
|
|
1254
|
+
content: `
|
|
1255
|
+
bucket_definitions:
|
|
1256
|
+
high_priority:
|
|
1257
|
+
priority: 1
|
|
1258
|
+
data:
|
|
1259
|
+
- SELECT * FROM test WHERE substring(id, 1, 4) = 'high';
|
|
1260
|
+
low_priority:
|
|
1261
|
+
priority: 2
|
|
1262
|
+
data:
|
|
1263
|
+
- SELECT * FROM test WHERE id = 'low';
|
|
1264
|
+
`
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
const bucketStorage = await f.getInstance(syncRules);
|
|
1268
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1269
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
1270
|
+
|
|
1271
|
+
await writer.markAllSnapshotDone('0/1');
|
|
1272
|
+
await writer.save({
|
|
1273
|
+
sourceTable: testTable,
|
|
1274
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1275
|
+
after: {
|
|
1276
|
+
id: 'high1',
|
|
1277
|
+
description: 'High priority 1'
|
|
1278
|
+
},
|
|
1279
|
+
afterReplicaId: 'high1'
|
|
1280
|
+
});
|
|
1281
|
+
await writer.save({
|
|
1282
|
+
sourceTable: testTable,
|
|
1283
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1284
|
+
after: {
|
|
1285
|
+
id: 'high2',
|
|
1286
|
+
description: 'High priority 2'
|
|
1287
|
+
},
|
|
1288
|
+
afterReplicaId: 'high2'
|
|
1289
|
+
});
|
|
1290
|
+
await writer.save({
|
|
1291
|
+
sourceTable: testTable,
|
|
1292
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1293
|
+
after: {
|
|
1294
|
+
id: 'low',
|
|
1295
|
+
description: 'Low priority'
|
|
1296
|
+
},
|
|
1297
|
+
afterReplicaId: 'low'
|
|
1298
|
+
});
|
|
1299
|
+
await writer.commit('0/1');
|
|
1300
|
+
|
|
1301
|
+
const stream = sync.streamResponse({
|
|
1302
|
+
syncContext,
|
|
1303
|
+
bucketStorage,
|
|
1304
|
+
syncRules: bucketStorage.getParsedSyncRules(test_utils.PARSE_OPTIONS),
|
|
1305
|
+
params: {
|
|
1306
|
+
buckets: [],
|
|
1307
|
+
include_checksum: true,
|
|
1308
|
+
raw_data: true
|
|
1309
|
+
},
|
|
1310
|
+
tracker,
|
|
1311
|
+
token: new JwtPayload({ sub: '', exp: Date.now() / 1000 + 10 }),
|
|
1312
|
+
isEncodingAsBson: false
|
|
1313
|
+
});
|
|
1314
|
+
|
|
1315
|
+
const iter = stream[Symbol.asyncIterator]();
|
|
1316
|
+
await using _ = {
|
|
1317
|
+
async [Symbol.asyncDispose]() {
|
|
1318
|
+
await iter.return?.();
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
const checkpoint = await consumeIterator(iter, {
|
|
1323
|
+
consume: false,
|
|
1324
|
+
isDone: (line) => (line as any)?.checkpoint != null
|
|
1325
|
+
});
|
|
1326
|
+
expect(checkpoint[0]).toEqual({
|
|
1327
|
+
checkpoint: expect.objectContaining({
|
|
1328
|
+
last_op_id: '3'
|
|
1329
|
+
})
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
await writer.save({
|
|
1333
|
+
sourceTable: testTable,
|
|
1334
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
1335
|
+
after: {
|
|
1336
|
+
id: 'high1',
|
|
1337
|
+
description: 'Updated high priority 1'
|
|
1338
|
+
},
|
|
1339
|
+
afterReplicaId: 'high1'
|
|
1340
|
+
});
|
|
1341
|
+
await writer.save({
|
|
1342
|
+
sourceTable: testTable,
|
|
1343
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
1344
|
+
after: {
|
|
1345
|
+
id: 'high2',
|
|
1346
|
+
description: 'Updated high priority 2'
|
|
1347
|
+
},
|
|
1348
|
+
afterReplicaId: 'high2'
|
|
1349
|
+
});
|
|
1350
|
+
await writer.commit('0/2');
|
|
1351
|
+
|
|
1352
|
+
await bucketStorage.compact({
|
|
1353
|
+
minBucketChanges: 1,
|
|
1354
|
+
minChangeRatio: 0
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
const lines = await getCheckpointLines(iter, { consume: true });
|
|
1358
|
+
const nextCheckpointIndex = lines.findIndex((line) => (line as any)?.checkpoint_diff != null);
|
|
1359
|
+
expect(nextCheckpointIndex).toBeGreaterThan(0);
|
|
1360
|
+
|
|
1361
|
+
const invalidatedCheckpointLines = lines.slice(0, nextCheckpointIndex);
|
|
1362
|
+
expect(invalidatedCheckpointLines).not.toContainEqual(
|
|
1363
|
+
expect.objectContaining({ checkpoint_complete: expect.anything() })
|
|
1364
|
+
);
|
|
1365
|
+
expect(invalidatedCheckpointLines).not.toContainEqual(
|
|
1366
|
+
expect.objectContaining({
|
|
1367
|
+
data: expect.objectContaining({
|
|
1368
|
+
bucket: expect.stringContaining('low_priority')
|
|
1369
|
+
})
|
|
1370
|
+
})
|
|
1371
|
+
);
|
|
1372
|
+
|
|
1373
|
+
expect(lines[nextCheckpointIndex]).toEqual({
|
|
1374
|
+
checkpoint_diff: expect.objectContaining({
|
|
1375
|
+
last_op_id: '5'
|
|
1376
|
+
})
|
|
1377
|
+
});
|
|
1378
|
+
expect(lines.at(-1)).toEqual({
|
|
1379
|
+
checkpoint_complete: expect.objectContaining({
|
|
1380
|
+
last_op_id: '5'
|
|
1381
|
+
})
|
|
1382
|
+
});
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1238
1385
|
test('write checkpoint', async () => {
|
|
1239
1386
|
await using f = await factory();
|
|
1240
1387
|
|
|
@@ -1249,10 +1396,14 @@ bucket_definitions:
|
|
|
1249
1396
|
// <= the managed write checkpoint LSN below
|
|
1250
1397
|
await writer.commit('0/1');
|
|
1251
1398
|
|
|
1252
|
-
const checkpoint =
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1399
|
+
const checkpoint = (
|
|
1400
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
1401
|
+
{
|
|
1402
|
+
user_id: 'test',
|
|
1403
|
+
heads: { '1': '1/0' }
|
|
1404
|
+
}
|
|
1405
|
+
])
|
|
1406
|
+
).get('test')!;
|
|
1256
1407
|
|
|
1257
1408
|
const params: sync.SyncStreamParameters = {
|
|
1258
1409
|
syncContext,
|
package/src/tests/util.ts
CHANGED
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
import { bucketRequest } from '../test-utils/general-utils.js';
|
|
10
10
|
|
|
11
11
|
export function bucketRequestMap(
|
|
12
|
-
syncRules: storage.
|
|
12
|
+
syncRules: storage.PersistedSyncConfigContent,
|
|
13
13
|
buckets: Iterable<readonly [string, bigint]>
|
|
14
14
|
): storage.BucketDataRequest[] {
|
|
15
15
|
return Array.from(buckets, ([bucketName, opId]) => bucketRequest(syncRules, bucketName, opId));
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function bucketRequests(
|
|
19
|
-
syncRules: storage.
|
|
19
|
+
syncRules: storage.PersistedSyncConfigContent,
|
|
20
20
|
bucketNames: string[]
|
|
21
21
|
): storage.BucketChecksumRequest[] {
|
|
22
22
|
return bucketNames.map((bucketName) => {
|
package/tsconfig.json
CHANGED