@powersync/service-module-mongodb 0.0.0-dev-20260602073133 → 0.0.0-dev-20260909133214
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 +153 -5
- package/dist/api/MongoRouteAPIAdapter.d.ts +4 -2
- package/dist/api/MongoRouteAPIAdapter.js +23 -32
- package/dist/api/MongoRouteAPIAdapter.js.map +1 -1
- package/dist/common/SentinelLSN.d.ts +37 -0
- package/dist/common/SentinelLSN.js +59 -0
- package/dist/common/SentinelLSN.js.map +1 -0
- package/dist/module/MongoModule.js +2 -1
- package/dist/module/MongoModule.js.map +1 -1
- package/dist/replication/ChangeStream.d.ts +24 -0
- package/dist/replication/ChangeStream.js +203 -67
- package/dist/replication/ChangeStream.js.map +1 -1
- package/dist/replication/ChangeStreamReplicationJob.js +3 -2
- package/dist/replication/ChangeStreamReplicationJob.js.map +1 -1
- package/dist/replication/ChangeStreamReplicator.js +1 -1
- package/dist/replication/ChangeStreamReplicator.js.map +1 -1
- package/dist/replication/MongoRelation.d.ts +36 -1
- package/dist/replication/MongoRelation.js +133 -18
- package/dist/replication/MongoRelation.js.map +1 -1
- package/dist/replication/MongoSnapshotter.d.ts +9 -1
- package/dist/replication/MongoSnapshotter.js +129 -67
- package/dist/replication/MongoSnapshotter.js.map +1 -1
- package/dist/replication/RawChangeStream.d.ts +4 -1
- package/dist/replication/RawChangeStream.js +6 -10
- package/dist/replication/RawChangeStream.js.map +1 -1
- package/dist/replication/checkpoints/CheckpointImplementation.d.ts +135 -0
- package/dist/replication/checkpoints/CheckpointImplementation.js +22 -0
- package/dist/replication/checkpoints/CheckpointImplementation.js.map +1 -0
- package/dist/replication/checkpoints/SentinelCheckpointImplementation.d.ts +58 -0
- package/dist/replication/checkpoints/SentinelCheckpointImplementation.js +224 -0
- package/dist/replication/checkpoints/SentinelCheckpointImplementation.js.map +1 -0
- package/dist/replication/checkpoints/TimestampCheckpointImplementation.d.ts +27 -0
- package/dist/replication/checkpoints/TimestampCheckpointImplementation.js +116 -0
- package/dist/replication/checkpoints/TimestampCheckpointImplementation.js.map +1 -0
- package/dist/replication/checkpoints/create-checkpoint-implementation.d.ts +6 -0
- package/dist/replication/checkpoints/create-checkpoint-implementation.js +10 -0
- package/dist/replication/checkpoints/create-checkpoint-implementation.js.map +1 -0
- package/dist/replication/replication-utils.d.ts +6 -0
- package/dist/replication/replication-utils.js +19 -2
- package/dist/replication/replication-utils.js.map +1 -1
- package/dist/types/types.d.ts +7 -2
- package/dist/types/types.js +20 -2
- package/dist/types/types.js.map +1 -1
- package/package.json +11 -21
- package/src/api/MongoRouteAPIAdapter.ts +28 -39
- package/src/common/SentinelLSN.ts +78 -0
- package/src/module/MongoModule.ts +2 -1
- package/src/replication/ChangeStream.ts +223 -78
- package/src/replication/ChangeStreamReplicationJob.ts +3 -2
- package/src/replication/ChangeStreamReplicator.ts +1 -1
- package/src/replication/MongoRelation.ts +153 -21
- package/src/replication/MongoSnapshotter.ts +148 -73
- package/src/replication/RawChangeStream.ts +27 -32
- package/src/replication/checkpoints/CheckpointImplementation.ts +167 -0
- package/src/replication/checkpoints/SentinelCheckpointImplementation.ts +269 -0
- package/src/replication/checkpoints/TimestampCheckpointImplementation.ts +145 -0
- package/src/replication/checkpoints/create-checkpoint-implementation.ts +14 -0
- package/src/replication/replication-utils.ts +23 -2
- package/src/types/types.ts +27 -2
- package/test/DOCUMENTDB_TESTING.md +115 -0
- package/test/src/DatabaseType.ts +25 -0
- package/test/src/change_stream.test.ts +180 -71
- package/test/src/change_stream_utils.ts +117 -29
- package/test/src/checkpoint_retry.test.ts +134 -0
- package/test/src/config.test.ts +34 -0
- package/test/src/documentdb_helpers.test.ts +124 -0
- package/test/src/documentdb_mode.test.ts +1039 -0
- package/test/src/mongo_test.test.ts +15 -5
- package/test/src/raw_change_stream.test.ts +147 -126
- package/test/src/resume.test.ts +2 -2
- package/test/src/resume_token.test.ts +30 -0
- package/test/src/slow_tests.test.ts +4 -1
- package/test/src/test-timeouts.ts +23 -0
- package/test/src/util.ts +14 -32
- package/tsconfig.json +1 -4
- package/tsconfig.scripts.json +1 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -3,12 +3,14 @@ import { setTimeout } from 'node:timers/promises';
|
|
|
3
3
|
import { describe, expect, test, vi } from 'vitest';
|
|
4
4
|
|
|
5
5
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
6
|
-
import { createWriteCheckpoint, storage } from '@powersync/service-core';
|
|
6
|
+
import { createWriteCheckpoint, storage, WriteCheckpointBatcher } from '@powersync/service-core';
|
|
7
7
|
import { test_utils } from '@powersync/service-core-tests';
|
|
8
8
|
|
|
9
9
|
import { MongoRouteAPIAdapter } from '@module/api/MongoRouteAPIAdapter.js';
|
|
10
10
|
import { PostImagesOption } from '@module/types/types.js';
|
|
11
11
|
import { ChangeStreamTestContext } from './change_stream_utils.js';
|
|
12
|
+
import { DATABASE_TYPE, DatabaseType } from './DatabaseType.js';
|
|
13
|
+
import { testTimeout } from './test-timeouts.js';
|
|
12
14
|
import { describeWithStorage, StorageVersionTestContext, TEST_CONNECTION_OPTIONS } from './util.js';
|
|
13
15
|
|
|
14
16
|
const BASIC_SYNC_RULES = `
|
|
@@ -19,7 +21,7 @@ bucket_definitions:
|
|
|
19
21
|
`;
|
|
20
22
|
|
|
21
23
|
describe('change stream', () => {
|
|
22
|
-
describeWithStorage({ timeout: 20_000 }, defineChangeStreamTests);
|
|
24
|
+
describeWithStorage({ timeout: testTimeout(20_000, { cloudOverride: 40_000 }) }, defineChangeStreamTests);
|
|
23
25
|
});
|
|
24
26
|
|
|
25
27
|
function defineChangeStreamTests({ factory, storageVersion }: StorageVersionTestContext) {
|
|
@@ -28,7 +30,8 @@ function defineChangeStreamTests({ factory, storageVersion }: StorageVersionTest
|
|
|
28
30
|
const openContext = (options?: Parameters<typeof ChangeStreamTestContext.open>[1]) => {
|
|
29
31
|
return ChangeStreamTestContext.open(factory, { ...options, storageVersion });
|
|
30
32
|
};
|
|
31
|
-
|
|
33
|
+
// DocumentDB does not support changeStreamPreAndPostImages, which this test requires via postImages: READ_ONLY.
|
|
34
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('replicating basic values', async () => {
|
|
32
35
|
await using context = await openContext({
|
|
33
36
|
mongoOptions: { postImages: PostImagesOption.READ_ONLY }
|
|
34
37
|
});
|
|
@@ -74,7 +77,10 @@ bucket_definitions:
|
|
|
74
77
|
- SELECT _id as id, description, num FROM "test_%"`);
|
|
75
78
|
|
|
76
79
|
await db.createCollection('test_data', {
|
|
77
|
-
|
|
80
|
+
// DocumentDB does not support changeStreamPreAndPostImages, even when
|
|
81
|
+
// explicitly disabled. This test only needs a collection to exercise
|
|
82
|
+
// fatal error recovery, so omit the option in DocumentDB mode.
|
|
83
|
+
...(DATABASE_TYPE == DatabaseType.MONGODB ? { changeStreamPreAndPostImages: { enabled: false } } : {})
|
|
78
84
|
});
|
|
79
85
|
const collection = db.collection('test_data');
|
|
80
86
|
|
|
@@ -152,7 +158,7 @@ bucket_definitions:
|
|
|
152
158
|
await context.updateSyncRules(BASIC_SYNC_RULES);
|
|
153
159
|
|
|
154
160
|
await db.createCollection('test_data', {
|
|
155
|
-
changeStreamPreAndPostImages: { enabled: false }
|
|
161
|
+
changeStreamPreAndPostImages: DATABASE_TYPE == DatabaseType.MONGODB ? { enabled: false } : undefined
|
|
156
162
|
});
|
|
157
163
|
testId = new mongo.ObjectId();
|
|
158
164
|
collection = db.collection('test_data');
|
|
@@ -167,7 +173,9 @@ bucket_definitions:
|
|
|
167
173
|
expect(test_utils.reduceBucket(data).slice(1)).toEqual([]);
|
|
168
174
|
});
|
|
169
175
|
|
|
170
|
-
|
|
176
|
+
// DocumentDB always materializes a write-time post-image for updateLookup, so the 'fullDocument unavailable'
|
|
177
|
+
// case this test exercises (update treated as a remove) cannot occur there.
|
|
178
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('updateLookup - no fullDocument available', async () => {
|
|
171
179
|
await using context = await openContext({
|
|
172
180
|
mongoOptions: { postImages: PostImagesOption.OFF }
|
|
173
181
|
});
|
|
@@ -179,6 +187,9 @@ bucket_definitions:
|
|
|
179
187
|
- SELECT _id as id, description, num FROM "test_data"`);
|
|
180
188
|
|
|
181
189
|
await db.createCollection('test_data', {
|
|
190
|
+
// DocumentDB does not support changeStreamPreAndPostImages, even when
|
|
191
|
+
// explicitly disabled. This test only needs a collection to exercise
|
|
192
|
+
// fatal error recovery, so omit the option in DocumentDB mode.
|
|
182
193
|
changeStreamPreAndPostImages: { enabled: false }
|
|
183
194
|
});
|
|
184
195
|
const collection = db.collection('test_data');
|
|
@@ -211,7 +222,8 @@ bucket_definitions:
|
|
|
211
222
|
]);
|
|
212
223
|
});
|
|
213
224
|
|
|
214
|
-
|
|
225
|
+
// DocumentDB does not support changeStreamPreAndPostImages (post-images).
|
|
226
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('postImages - autoConfigure', async () => {
|
|
215
227
|
// Similar to the above test, but with postImages enabled.
|
|
216
228
|
// This resolves the consistency issue.
|
|
217
229
|
await using context = await openContext({
|
|
@@ -259,7 +271,8 @@ bucket_definitions:
|
|
|
259
271
|
]);
|
|
260
272
|
});
|
|
261
273
|
|
|
262
|
-
|
|
274
|
+
// DocumentDB does not support changeStreamPreAndPostImages (post-images).
|
|
275
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('postImages - on', async () => {
|
|
263
276
|
// Similar to postImages - autoConfigure, but does not auto-configure.
|
|
264
277
|
// changeStreamPreAndPostImages must be manually configured.
|
|
265
278
|
await using context = await openContext({
|
|
@@ -408,7 +421,9 @@ bucket_definitions:
|
|
|
408
421
|
]);
|
|
409
422
|
});
|
|
410
423
|
|
|
411
|
-
|
|
424
|
+
// Collection drop events are not currently replicated on DocumentDB: they are not delivered through the
|
|
425
|
+
// cluster-level change stream the way standard MongoDB delivers them, so the drop is not applied as a remove.
|
|
426
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('replicating dropCollection', async () => {
|
|
412
427
|
await using context = await openContext();
|
|
413
428
|
const { db } = context;
|
|
414
429
|
const syncRuleContent = `
|
|
@@ -440,7 +455,9 @@ bucket_definitions:
|
|
|
440
455
|
]);
|
|
441
456
|
});
|
|
442
457
|
|
|
443
|
-
|
|
458
|
+
// Collection rename events are not currently replicated on DocumentDB: they are not delivered through the
|
|
459
|
+
// cluster-level change stream the way standard MongoDB delivers them.
|
|
460
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('replicating renameCollection', async () => {
|
|
444
461
|
await using context = await openContext();
|
|
445
462
|
const { db } = context;
|
|
446
463
|
const syncRuleContent = `
|
|
@@ -471,7 +488,9 @@ bucket_definitions:
|
|
|
471
488
|
]);
|
|
472
489
|
});
|
|
473
490
|
|
|
474
|
-
test
|
|
491
|
+
// Concurrent-snapshot regression test driven by collection drop/recreate DDL events, which DocumentDB does
|
|
492
|
+
// not deliver through the cluster-level change stream the way standard MongoDB does.
|
|
493
|
+
test.runIf(supportsConcurrentSnapshots && DATABASE_TYPE != DatabaseType.DOCUMENTDB)(
|
|
475
494
|
'collection recreated while queued snapshot is waiting does not stall checkpoints',
|
|
476
495
|
async () => {
|
|
477
496
|
// This is a regression test for a specific timing issue in concurrent snapshot logic.
|
|
@@ -603,6 +622,79 @@ bucket_definitions:
|
|
|
603
622
|
expect(data).toMatchObject([test_utils.putOp('test_data', { id: test_id, description: 'test1' })]);
|
|
604
623
|
});
|
|
605
624
|
|
|
625
|
+
test.runIf(storageVersion >= 3)(
|
|
626
|
+
'does not resnapshot an unchanged event definition during incremental reprocessing',
|
|
627
|
+
async (testContext) => {
|
|
628
|
+
const eventDefinition = `
|
|
629
|
+
event_definitions:
|
|
630
|
+
checkpoint_requests:
|
|
631
|
+
payloads:
|
|
632
|
+
- SELECT user_id, checkpoint, client_id, CASE WHEN checkpoint_requested_at IS NULL THEN true ELSE false END AS is_legacy FROM checkpoints
|
|
633
|
+
`;
|
|
634
|
+
const syncConfig = (deploymentComment: string) => `
|
|
635
|
+
config:
|
|
636
|
+
edition: 3
|
|
637
|
+
storage_version: 3
|
|
638
|
+
# ${deploymentComment}
|
|
639
|
+
|
|
640
|
+
${eventDefinition}
|
|
641
|
+
streams:
|
|
642
|
+
global:
|
|
643
|
+
auto_subscribe: true
|
|
644
|
+
queries:
|
|
645
|
+
- SELECT *, _id AS id FROM lists
|
|
646
|
+
- SELECT *, _id AS id FROM todos
|
|
647
|
+
`;
|
|
648
|
+
|
|
649
|
+
let replicationStreamId: number;
|
|
650
|
+
{
|
|
651
|
+
await using context = await openContext();
|
|
652
|
+
const firstStorage = await context.updateSyncRules(syncConfig('initial deployment'));
|
|
653
|
+
if (
|
|
654
|
+
!('incrementalReprocessing' in firstStorage.storageConfig) ||
|
|
655
|
+
firstStorage.storageConfig.incrementalReprocessing !== true
|
|
656
|
+
) {
|
|
657
|
+
testContext.skip('storage does not support incremental reprocessing');
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const { db } = context;
|
|
661
|
+
await db.collection('lists').insertOne({ name: 'list' });
|
|
662
|
+
await db.collection('todos').insertOne({ description: 'todo' });
|
|
663
|
+
await db.collection('checkpoints').insertOne({
|
|
664
|
+
user_id: 'user-1',
|
|
665
|
+
checkpoint: 1n,
|
|
666
|
+
client_id: 'client-1',
|
|
667
|
+
checkpoint_requested_at: null
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
replicationStreamId = firstStorage.replicationStreamId;
|
|
671
|
+
await context.replicateSnapshot();
|
|
672
|
+
context.startStreaming();
|
|
673
|
+
await context.getCheckpoint();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const resnapshottedCollections: string[] = [];
|
|
677
|
+
await using context = await openContext({
|
|
678
|
+
doNotClear: true,
|
|
679
|
+
streamOptions: {
|
|
680
|
+
snapshotHooks: {
|
|
681
|
+
beforeSnapshotStarted: async (table) => {
|
|
682
|
+
resnapshottedCollections.push(table.name);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const processingStorage = await context.updateSyncRules(syncConfig('redeployed without behavioral changes'));
|
|
689
|
+
expect(processingStorage.replicationStreamId).toBe(replicationStreamId);
|
|
690
|
+
expect(await context.factory.getDeployingSyncConfig()).not.toBeNull();
|
|
691
|
+
|
|
692
|
+
await context.replicateSnapshot();
|
|
693
|
+
|
|
694
|
+
expect(resnapshottedCollections).toEqual([]);
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
|
|
606
698
|
test('coalesces standalone checkpoints when backlog is buffered', async () => {
|
|
607
699
|
await using context = await openContext();
|
|
608
700
|
await context.updateSyncRules(BASIC_SYNC_RULES);
|
|
@@ -625,6 +717,10 @@ bucket_definitions:
|
|
|
625
717
|
type: 'mongodb',
|
|
626
718
|
...TEST_CONNECTION_OPTIONS
|
|
627
719
|
});
|
|
720
|
+
const writeCheckpointBatcher = new WriteCheckpointBatcher(
|
|
721
|
+
() => api,
|
|
722
|
+
() => context.factory
|
|
723
|
+
);
|
|
628
724
|
|
|
629
725
|
context.startStreaming();
|
|
630
726
|
|
|
@@ -641,8 +737,7 @@ bucket_definitions:
|
|
|
641
737
|
createWriteCheckpoint({
|
|
642
738
|
userId: 'test_user',
|
|
643
739
|
clientId: 'test_client' + i,
|
|
644
|
-
|
|
645
|
-
storage: context.factory
|
|
740
|
+
batcher: writeCheckpointBatcher
|
|
646
741
|
})
|
|
647
742
|
)
|
|
648
743
|
);
|
|
@@ -652,12 +747,14 @@ bucket_definitions:
|
|
|
652
747
|
|
|
653
748
|
// We need at least 1 commit.
|
|
654
749
|
expect(commitCount).toBeGreaterThan(0);
|
|
655
|
-
// The previous implementation
|
|
750
|
+
// The previous implementation created 1 commit per checkpoint, which is bad for performance.
|
|
656
751
|
// We expect a small number here - typically 2-10, but allow for anything less than the total number of checkpoints.
|
|
657
752
|
expect(commitCount).toBeLessThan(checkpointCount + 1);
|
|
658
753
|
});
|
|
659
754
|
|
|
660
|
-
test
|
|
755
|
+
test
|
|
756
|
+
// DocumentDBDB does not support changeStreamSplitLargeEvent
|
|
757
|
+
.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)('large record', async () => {
|
|
661
758
|
// Test a large update.
|
|
662
759
|
|
|
663
760
|
// Without $changeStreamSplitLargeEvent, we get this error:
|
|
@@ -729,75 +826,83 @@ bucket_definitions:
|
|
|
729
826
|
expect(data).toMatchObject([]);
|
|
730
827
|
});
|
|
731
828
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
829
|
+
// DocumentDB does not support changeStreamPreAndPostImages (post-images).
|
|
830
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)(
|
|
831
|
+
'postImages - new collection with postImages enabled',
|
|
832
|
+
async () => {
|
|
833
|
+
await using context = await openContext({
|
|
834
|
+
mongoOptions: { postImages: PostImagesOption.AUTO_CONFIGURE }
|
|
835
|
+
});
|
|
836
|
+
const { db } = context;
|
|
837
|
+
await context.updateSyncRules(`
|
|
738
838
|
bucket_definitions:
|
|
739
839
|
global:
|
|
740
840
|
data:
|
|
741
841
|
- SELECT _id as id, description FROM "test_%"`);
|
|
742
842
|
|
|
743
|
-
|
|
843
|
+
await context.replicateSnapshot();
|
|
744
844
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
845
|
+
await db.createCollection('test_data', {
|
|
846
|
+
// enabled: true here - everything should work
|
|
847
|
+
changeStreamPreAndPostImages: { enabled: true }
|
|
848
|
+
});
|
|
849
|
+
const collection = db.collection('test_data');
|
|
850
|
+
const result = await collection.insertOne({ description: 'test1' });
|
|
851
|
+
const test_id = result.insertedId;
|
|
852
|
+
await collection.updateOne({ _id: test_id }, { $set: { description: 'test2' } });
|
|
753
853
|
|
|
754
|
-
|
|
854
|
+
context.startStreaming();
|
|
755
855
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
856
|
+
const data = await context.getBucketData('global[]');
|
|
857
|
+
if (data.length == 3) {
|
|
858
|
+
expect(data).toMatchObject([
|
|
859
|
+
// An extra op here, since this triggers a snapshot in addition to getting the event.
|
|
860
|
+
test_utils.putOp('test_data', { id: test_id!.toHexString(), description: 'test2' }),
|
|
861
|
+
test_utils.putOp('test_data', { id: test_id!.toHexString(), description: 'test1' }),
|
|
862
|
+
test_utils.putOp('test_data', { id: test_id!.toHexString(), description: 'test2' })
|
|
863
|
+
]);
|
|
864
|
+
} else {
|
|
865
|
+
expect(data).toMatchObject([
|
|
866
|
+
test_utils.putOp('test_data', { id: test_id!.toHexString(), description: 'test1' }),
|
|
867
|
+
test_utils.putOp('test_data', { id: test_id!.toHexString(), description: 'test2' })
|
|
868
|
+
]);
|
|
869
|
+
}
|
|
769
870
|
}
|
|
770
|
-
|
|
871
|
+
);
|
|
771
872
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
873
|
+
// DocumentDB does not support changeStreamPreAndPostImages (post-images).
|
|
874
|
+
test.skipIf(DATABASE_TYPE == DatabaseType.DOCUMENTDB)(
|
|
875
|
+
'postImages - new collection with postImages disabled',
|
|
876
|
+
async () => {
|
|
877
|
+
await using context = await openContext({
|
|
878
|
+
mongoOptions: { postImages: PostImagesOption.AUTO_CONFIGURE }
|
|
879
|
+
});
|
|
880
|
+
const { db } = context;
|
|
881
|
+
await context.updateSyncRules(`
|
|
778
882
|
bucket_definitions:
|
|
779
883
|
global:
|
|
780
884
|
data:
|
|
781
885
|
- SELECT _id as id, description FROM "test_data%"`);
|
|
782
886
|
|
|
783
|
-
|
|
887
|
+
await context.replicateSnapshot();
|
|
784
888
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
889
|
+
await db.createCollection('test_data', {
|
|
890
|
+
// enabled: false here, but autoConfigure will enable it.
|
|
891
|
+
// Unfortunately, that is too late, and replication must be restarted.
|
|
892
|
+
changeStreamPreAndPostImages: { enabled: false }
|
|
893
|
+
});
|
|
894
|
+
const collection = db.collection('test_data');
|
|
895
|
+
const result = await collection.insertOne({ description: 'test1' });
|
|
896
|
+
const test_id = result.insertedId;
|
|
897
|
+
await collection.updateOne({ _id: test_id }, { $set: { description: 'test2' } });
|
|
794
898
|
|
|
795
|
-
|
|
899
|
+
context.startStreaming();
|
|
796
900
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
901
|
+
await expect(() => context.getBucketData('global[]')).rejects.toMatchObject({
|
|
902
|
+
message: expect.stringContaining('stream was configured to require a post-image for all update events')
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
);
|
|
801
906
|
|
|
802
907
|
test('recover from error', async () => {
|
|
803
908
|
await using context = await openContext();
|
|
@@ -809,7 +914,10 @@ bucket_definitions:
|
|
|
809
914
|
- SELECT _id as id, description, num FROM "test_data"`);
|
|
810
915
|
|
|
811
916
|
await db.createCollection('test_data', {
|
|
812
|
-
|
|
917
|
+
// DocumentDB does not support changeStreamPreAndPostImages, even when
|
|
918
|
+
// explicitly disabled. This test only needs a collection to exercise
|
|
919
|
+
// fatal error recovery, so omit the option in DocumentDB mode.
|
|
920
|
+
changeStreamPreAndPostImages: DATABASE_TYPE == DatabaseType.MONGODB ? { enabled: false } : undefined
|
|
813
921
|
});
|
|
814
922
|
|
|
815
923
|
const collection = db.collection('test_data');
|
|
@@ -820,9 +928,9 @@ bucket_definitions:
|
|
|
820
928
|
|
|
821
929
|
// Simulate an error
|
|
822
930
|
await context.storage!.reportError(new Error('simulated error'));
|
|
823
|
-
const syncRules = await context.factory.
|
|
931
|
+
const syncRules = (await context.factory.getActiveSyncConfig())?.content;
|
|
824
932
|
expect(syncRules).toBeTruthy();
|
|
825
|
-
expect(syncRules?.last_fatal_error).toEqual('simulated error');
|
|
933
|
+
expect((await syncRules?.getSyncConfigStatus())?.last_fatal_error).toEqual('simulated error');
|
|
826
934
|
|
|
827
935
|
// The next checkpoint should clear the error.
|
|
828
936
|
await context.getCheckpoint();
|
|
@@ -830,10 +938,11 @@ bucket_definitions:
|
|
|
830
938
|
// Just wait, and check that the error is cleared automatically.
|
|
831
939
|
await vi.waitUntil(
|
|
832
940
|
async () => {
|
|
833
|
-
const
|
|
941
|
+
const syncRules = (await context.factory.getActiveSyncConfig())?.content;
|
|
942
|
+
const error = (await syncRules?.getSyncConfigStatus())?.last_fatal_error;
|
|
834
943
|
return error == null;
|
|
835
944
|
},
|
|
836
|
-
{ timeout: 2_000 }
|
|
945
|
+
{ timeout: testTimeout(2_000, { cloudOverride: 5_000 }) }
|
|
837
946
|
);
|
|
838
947
|
});
|
|
839
948
|
}
|