@powersync/service-module-mongodb-storage 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/dist/storage/MongoBucketStorage.d.ts +35 -15
- package/dist/storage/MongoBucketStorage.js +309 -86
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/CheckpointState.d.ts +20 -0
- package/dist/storage/implementation/CheckpointState.js +31 -0
- package/dist/storage/implementation/CheckpointState.js.map +1 -0
- package/dist/storage/implementation/MongoBucketBatch.d.ts +69 -39
- package/dist/storage/implementation/MongoBucketBatch.js +104 -313
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +11 -9
- package/dist/storage/implementation/MongoChecksums.js +3 -111
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +3 -1
- package/dist/storage/implementation/MongoCompactor.js +18 -9
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
- package/dist/storage/implementation/MongoHydrationState.js +49 -0
- package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +6 -5
- package/dist/storage/implementation/MongoParameterCompactor.js +24 -1
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +121 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/implementation/MongoStorageProvider.js +5 -2
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +50 -21
- package/dist/storage/implementation/MongoSyncBucketStorage.js +62 -199
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +9 -5
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
- package/dist/storage/implementation/common/BucketDataDoc.d.ts +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +6 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -3
- package/dist/storage/implementation/common/PersistedBatch.js +5 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +10 -2
- package/dist/storage/implementation/db.js +9 -14
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +25 -50
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +23 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +403 -2
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +114 -3
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +22 -15
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +100 -27
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +1 -2
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.d.ts +21 -1
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +21 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +459 -3
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +27 -7
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +202 -18
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +20 -3
- package/dist/storage/implementation/v3/MongoCompactorV3.js +511 -22
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +62 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +350 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +50 -16
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +292 -75
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -3
- package/dist/storage/implementation/v3/PersistedBatchV3.js +43 -20
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +52 -16
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +16 -14
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +32 -27
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +4 -0
- package/dist/storage/implementation/v3/bucket-format.js +58 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
- package/dist/storage/implementation/v3/chunking.d.ts +10 -0
- package/dist/storage/implementation/v3/chunking.js +28 -0
- package/dist/storage/implementation/v3/chunking.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +111 -10
- package/dist/storage/implementation/v3/models.js +11 -28
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
- package/dist/storage/implementation/v3/source-table-utils.js +214 -0
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
- package/dist/storage/storage-index.d.ts +4 -4
- package/dist/storage/storage-index.js +4 -4
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +2 -2
- package/dist/utils/test-utils.d.ts +1 -2
- package/dist/utils/test-utils.js +5 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +1 -1
- package/dist/utils/util.js +3 -2
- package/dist/utils/util.js.map +1 -1
- package/package.json +9 -19
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +464 -95
- package/src/storage/implementation/CheckpointState.ts +59 -0
- package/src/storage/implementation/MongoBucketBatch.ts +159 -405
- package/src/storage/implementation/MongoChecksums.ts +7 -138
- package/src/storage/implementation/MongoCompactor.ts +20 -10
- package/src/storage/implementation/MongoHydrationState.ts +61 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +24 -5
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +152 -0
- package/src/storage/implementation/MongoStorageProvider.ts +5 -2
- package/src/storage/implementation/MongoSyncBucketStorage.ts +89 -250
- package/src/storage/implementation/MongoSyncRulesLock.ts +14 -5
- package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
- package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
- package/src/storage/implementation/common/PersistedBatch.ts +8 -4
- package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +16 -21
- package/src/storage/implementation/models.ts +28 -61
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +503 -5
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
- package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +150 -49
- package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +25 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +611 -4
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
- package/src/storage/implementation/v3/MongoCompactorV3.ts +647 -30
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +397 -111
- package/src/storage/implementation/v3/PersistedBatchV3.ts +63 -38
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +59 -19
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +48 -38
- package/src/storage/implementation/v3/bucket-format.ts +70 -0
- package/src/storage/implementation/v3/chunking.ts +38 -0
- package/src/storage/implementation/v3/models.ts +150 -44
- package/src/storage/implementation/v3/source-table-utils.ts +392 -0
- package/src/storage/storage-index.ts +12 -4
- package/src/utils/test-utils.ts +6 -3
- package/src/utils/util.ts +3 -2
- package/test/src/__snapshots__/storage.test.ts.snap +0 -75
- package/test/src/__snapshots__/storage_sync.test.ts.snap +48 -48
- package/test/src/cleanup-stopped-sync-configs.test.ts +648 -0
- package/test/src/storage.test.ts +11 -11
- package/test/src/storage_compacting.test.ts +1956 -6
- package/test/src/storage_sync.test.ts +1429 -42
- package/test/src/storeCurrentData.test.ts +221 -0
- package/test/src/util.ts +2 -1
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -17
- package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
- package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -64
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -15
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -42
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -5
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
- package/src/storage/implementation/BucketDefinitionMapping.ts +0 -72
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -76
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -56
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -12
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
2
3
|
import { storage } from '@powersync/service-core';
|
|
4
|
+
import * as bson from 'bson';
|
|
3
5
|
import { mongoTableId } from '../../../utils/util.js';
|
|
6
|
+
import { canCheckpointState } from '../CheckpointState.js';
|
|
4
7
|
import { MongoBucketBatch, MongoBucketBatchOptions } from '../MongoBucketBatch.js';
|
|
8
|
+
import { MongoParsedSyncConfigSet } from '../MongoParsedSyncConfigSet.js';
|
|
9
|
+
import { syncRuleStateUpdatePipeline } from '../SyncRuleStateUpdate.js';
|
|
5
10
|
import { PersistedBatch } from '../common/PersistedBatch.js';
|
|
6
11
|
import { SourceRecordStore } from '../common/SourceRecordStore.js';
|
|
7
12
|
import { PersistedBatchV3 } from './PersistedBatchV3.js';
|
|
8
13
|
import { SourceRecordStoreV3 } from './SourceRecordStoreV3.js';
|
|
9
14
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
15
|
+
import { ReplicationStreamDocumentV3, SourceTableDocumentV3 } from './models.js';
|
|
16
|
+
import {
|
|
17
|
+
createNewSourceTable,
|
|
18
|
+
designateEventCarrier,
|
|
19
|
+
overlappingSourceTableFilter,
|
|
20
|
+
planSourceTableReconciliation,
|
|
21
|
+
sourceTableDesiredResolution,
|
|
22
|
+
sourceTableFromDocument,
|
|
23
|
+
SourceTableReconciliationContext
|
|
24
|
+
} from './source-table-utils.js';
|
|
10
25
|
|
|
11
26
|
export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
12
|
-
declare
|
|
27
|
+
declare readonly db: VersionedPowerSyncMongoV3;
|
|
13
28
|
|
|
14
29
|
private readonly store: SourceRecordStore;
|
|
30
|
+
private readonly syncConfigIds: bson.ObjectId[];
|
|
31
|
+
private needsActivationV3 = true;
|
|
32
|
+
private lastWaitingLogThrottledV3 = 0;
|
|
15
33
|
|
|
16
34
|
constructor(options: MongoBucketBatchOptions) {
|
|
17
35
|
super(options);
|
|
18
|
-
|
|
36
|
+
const syncConfigIds = options.syncConfigIds ?? [];
|
|
37
|
+
if (syncConfigIds.length == 0) {
|
|
38
|
+
throw new ReplicationAssertionError('Missing sync config id for v3 batch');
|
|
39
|
+
}
|
|
40
|
+
this.syncConfigIds = syncConfigIds;
|
|
41
|
+
this.store = new SourceRecordStoreV3(this.db, this.replicationStreamId, this.mapping);
|
|
19
42
|
}
|
|
20
43
|
|
|
21
44
|
protected createPersistedBatch(writtenSize: number): PersistedBatch {
|
|
22
|
-
return new PersistedBatchV3(this.db, this.
|
|
45
|
+
return new PersistedBatchV3(this.db, this.replicationStreamId, this.mapping, writtenSize, {
|
|
23
46
|
logger: this.logger
|
|
24
47
|
});
|
|
25
48
|
}
|
|
@@ -28,10 +51,83 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
28
51
|
return this.store;
|
|
29
52
|
}
|
|
30
53
|
|
|
54
|
+
protected override async onReplicationTransactionFlush(
|
|
55
|
+
session: lib_mongo.mongo.ClientSession,
|
|
56
|
+
lastOp: bigint
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
// Durably advance the stream-level head of persisted ops within the flush transaction.
|
|
59
|
+
// This ensures a checkpoint created later (even by an empty commit, or by a freshly-appended
|
|
60
|
+
// config that replicates nothing) covers all ops persisted before a potential crash.
|
|
61
|
+
await this.db.sync_rules.updateOne(
|
|
62
|
+
{
|
|
63
|
+
_id: this.replicationStreamId
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
$max: {
|
|
67
|
+
last_persisted_op: lastOp
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{ session }
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private selectedSyncConfigObjectIds(syncConfigIds: string[]): bson.ObjectId[] {
|
|
75
|
+
const selectedIds = new Set(this.syncConfigIds.map((id) => id.toHexString()));
|
|
76
|
+
return syncConfigIds
|
|
77
|
+
.filter((id) => selectedIds.has(id) && bson.ObjectId.isValid(id))
|
|
78
|
+
.map((id) => new bson.ObjectId(id));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private relevantSyncConfigIds(table: storage.SourceTable): bson.ObjectId[] {
|
|
82
|
+
const bucketDataSourceIds = table.bucketDataSources.map((source) => this.mapping.bucketSourceId(source));
|
|
83
|
+
const parameterLookupSourceIds = table.parameterLookupSources.map((source) =>
|
|
84
|
+
this.mapping.parameterLookupId(source)
|
|
85
|
+
);
|
|
86
|
+
return this.selectedSyncConfigObjectIds(
|
|
87
|
+
this.mapping.syncConfigIdsForSourceTable(
|
|
88
|
+
this.syncConfigIds.map((id) => id.toHexString()),
|
|
89
|
+
table.ref,
|
|
90
|
+
bucketDataSourceIds,
|
|
91
|
+
parameterLookupSourceIds
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private relevantSyncConfigIdsForTables(tables: storage.SourceTable[]): bson.ObjectId[] {
|
|
97
|
+
const ids = new Map<string, bson.ObjectId>();
|
|
98
|
+
for (const table of tables) {
|
|
99
|
+
for (const id of this.relevantSyncConfigIds(table)) {
|
|
100
|
+
ids.set(id.toHexString(), id);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [...ids.values()];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private snapshotBlockingSourceTablesFilter(): Record<string, unknown> {
|
|
107
|
+
const clauses = this.syncConfigIds.flatMap((syncConfigId) => {
|
|
108
|
+
const filter = this.mapping.snapshotBlockingSourceTablesFilter(syncConfigId.toHexString()) as {
|
|
109
|
+
$or?: Record<string, unknown>[];
|
|
110
|
+
};
|
|
111
|
+
return filter.$or ?? [];
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (clauses.length == 0) {
|
|
115
|
+
return {
|
|
116
|
+
snapshot_done: false,
|
|
117
|
+
_id: { $exists: false }
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
snapshot_done: false,
|
|
123
|
+
$or: clauses
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
31
127
|
protected async cleanupDroppedSourceTables(sourceTables: storage.SourceTable[]) {
|
|
32
128
|
for (const table of sourceTables) {
|
|
33
129
|
await this.db
|
|
34
|
-
.
|
|
130
|
+
.sourceRecords(this.replicationStreamId, mongoTableId(table.id))
|
|
35
131
|
.drop()
|
|
36
132
|
.catch((error) => {
|
|
37
133
|
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
|
|
@@ -41,4 +137,515 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
41
137
|
});
|
|
42
138
|
}
|
|
43
139
|
}
|
|
140
|
+
|
|
141
|
+
async resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult> {
|
|
142
|
+
// The test-only override is a whole parsed set, so the sync rules and the mapping
|
|
143
|
+
// used below always come from the same parse.
|
|
144
|
+
const parsedOverride = options.parsedSyncConfig as MongoParsedSyncConfigSet | undefined;
|
|
145
|
+
const syncConfig = parsedOverride?.hydratedSyncConfig ?? this.sync_rules;
|
|
146
|
+
const mapping = parsedOverride?.mapping ?? this.mapping;
|
|
147
|
+
|
|
148
|
+
const { connection_id, source } = options;
|
|
149
|
+
const context: SourceTableReconciliationContext = {
|
|
150
|
+
connectionId: connection_id,
|
|
151
|
+
connectionTag: source.connectionTag,
|
|
152
|
+
identity: {
|
|
153
|
+
schema: source.schema,
|
|
154
|
+
name: source.name,
|
|
155
|
+
objectId: source.objectId,
|
|
156
|
+
replicaIdColumns: source.replicaIdColumns.map((column) => ({
|
|
157
|
+
name: column.name,
|
|
158
|
+
type: column.type,
|
|
159
|
+
type_oid: column.typeId
|
|
160
|
+
}))
|
|
161
|
+
},
|
|
162
|
+
storeCurrentData: source.sendsCompleteRows !== true,
|
|
163
|
+
syncConfig,
|
|
164
|
+
mapping,
|
|
165
|
+
desired: sourceTableDesiredResolution(syncConfig, source, mapping)
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
let result: storage.ResolveTablesResult | null = null;
|
|
169
|
+
const session = this.db.client.startSession();
|
|
170
|
+
await using _ = { [Symbol.asyncDispose]: () => session.endSession() };
|
|
171
|
+
|
|
172
|
+
await session.withTransaction(async () => {
|
|
173
|
+
const col = this.db.sourceTables(this.replicationStreamId);
|
|
174
|
+
|
|
175
|
+
// Fetch every persisted source-table doc that can overlap this physical table.
|
|
176
|
+
// Exact-identity docs are candidates for reuse; non-exact overlaps are possible drops.
|
|
177
|
+
const candidateDocs = await col
|
|
178
|
+
.find(overlappingSourceTableFilter(connection_id, context.identity), { session })
|
|
179
|
+
.toArray();
|
|
180
|
+
|
|
181
|
+
// Pure planning: which docs to retain (and narrow), whether a new doc is needed for
|
|
182
|
+
// uncovered memberships, and which docs conflict with the current identity.
|
|
183
|
+
const plan = planSourceTableReconciliation(candidateDocs, context);
|
|
184
|
+
|
|
185
|
+
// Persist narrowing for incomplete snapshots only. Snapshot-complete docs keep stale
|
|
186
|
+
// coverage ids so compatible future configs can reuse already-snapshotted data.
|
|
187
|
+
// Narrowing occurs after removing a sync config, meaning we don't process those
|
|
188
|
+
// definitions anymore.
|
|
189
|
+
for (const update of plan.narrowingUpdates) {
|
|
190
|
+
await col.updateOne(
|
|
191
|
+
{ _id: update.id },
|
|
192
|
+
{
|
|
193
|
+
$set: {
|
|
194
|
+
bucket_data_source_ids: update.memberships.bucketDataSourceIds,
|
|
195
|
+
parameter_lookup_source_ids: update.memberships.parameterLookupSourceIds
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
{ session }
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Any desired membership not covered by an existing doc gets a new source table.
|
|
203
|
+
// That table snapshots only the uncovered memberships.
|
|
204
|
+
if (plan.newTableMemberships != null) {
|
|
205
|
+
const id = options.idGenerator ? (options.idGenerator() as bson.ObjectId) : new bson.ObjectId();
|
|
206
|
+
const { doc, table } = createNewSourceTable(id, plan.newTableMemberships, context);
|
|
207
|
+
|
|
208
|
+
await col.insertOne(doc, { session });
|
|
209
|
+
await this.db.initializeSourceRecordsCollection(this.replicationStreamId, doc._id, session);
|
|
210
|
+
plan.tables.push(table);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// If memberships are split across multiple source tables, only one may fire events.
|
|
214
|
+
designateEventCarrier(plan.tables, context.desired.triggersEvent);
|
|
215
|
+
|
|
216
|
+
result = {
|
|
217
|
+
tables: plan.tables,
|
|
218
|
+
dropTables: plan.dropDocs.map((doc) => sourceTableFromDocument(doc, context.connectionTag, syncConfig, mapping))
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return result!;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null> {
|
|
226
|
+
const doc = (await this.db
|
|
227
|
+
.sourceTables(this.replicationStreamId)
|
|
228
|
+
.findOne({ _id: mongoTableId(table.id) }, { session: this.session })) as SourceTableDocumentV3 | null;
|
|
229
|
+
if (doc == null) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const refreshed = sourceTableFromDocument(doc, table.ref.connectionTag, this.sync_rules, this.mapping);
|
|
234
|
+
// The event-carrier designation is decided per resolveTables result and not persisted -
|
|
235
|
+
// preserve the caller's designation instead of recomputing it from the ref, so that
|
|
236
|
+
// refreshing a non-carrier table does not make it fire events.
|
|
237
|
+
refreshed.syncEvent = table.syncEvent;
|
|
238
|
+
return refreshed;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult> {
|
|
242
|
+
const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
|
|
243
|
+
|
|
244
|
+
await this.flush(options);
|
|
245
|
+
|
|
246
|
+
const now = new Date();
|
|
247
|
+
|
|
248
|
+
await this.db.write_checkpoints.updateMany(
|
|
249
|
+
{
|
|
250
|
+
processed_at_lsn: null,
|
|
251
|
+
'lsns.1': { $lte: lsn }
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
$set: {
|
|
255
|
+
processed_at_lsn: lsn
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
session: this.session
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const preUpdateDocument = (await this.db.sync_rules.findOne(
|
|
264
|
+
{
|
|
265
|
+
_id: this.replicationStreamId,
|
|
266
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
session: this.session,
|
|
270
|
+
projection: {
|
|
271
|
+
sync_configs: 1,
|
|
272
|
+
last_persisted_op: 1
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
)) as ReplicationStreamDocumentV3 | null;
|
|
276
|
+
|
|
277
|
+
const states =
|
|
278
|
+
preUpdateDocument?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
279
|
+
if (states.length == 0) {
|
|
280
|
+
throw new ReplicationAssertionError(
|
|
281
|
+
`Failed to update checkpoint - no matching sync_config for _id: ${this.replicationStreamId}/${this.syncConfigIds
|
|
282
|
+
.map((id) => id.toHexString())
|
|
283
|
+
.join(',')}`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
// The replication job / MongoBucketBatch must be constructed with all replicating (PROCESSING / ACTIVE)
|
|
287
|
+
// sync configs in the stream, otherwise we'll get inconsistencies. Configs in other states (e.g. STOP)
|
|
288
|
+
// remain embedded in the document and are ignored here.
|
|
289
|
+
const missingSyncConfig = preUpdateDocument!.sync_configs.find(
|
|
290
|
+
(config) =>
|
|
291
|
+
[storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE].includes(config.state) &&
|
|
292
|
+
!this.syncConfigIds.some((id) => id.equals(config._id))
|
|
293
|
+
);
|
|
294
|
+
if (missingSyncConfig != null) {
|
|
295
|
+
throw new ReplicationAssertionError(`Replication job not configured for sync config ${missingSyncConfig._id}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Effective head of the stream's op sequence.
|
|
299
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and this
|
|
300
|
+
// read uses the same session, so it covers all ops persisted by this batch.
|
|
301
|
+
const newCheckpoint =
|
|
302
|
+
preUpdateDocument?.last_persisted_op == null ? 0n : BigInt(preUpdateDocument.last_persisted_op);
|
|
303
|
+
|
|
304
|
+
let checkpointBlocked = false;
|
|
305
|
+
let checkpointCreated = false;
|
|
306
|
+
let checkpointLogState: unknown = null;
|
|
307
|
+
const unblockedConfigIds: bson.ObjectId[] = [];
|
|
308
|
+
|
|
309
|
+
for (const state of states) {
|
|
310
|
+
if (state.last_checkpoint != null && state.last_checkpoint > newCheckpoint) {
|
|
311
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and
|
|
312
|
+
// checkpoints are only ever created at that head, so a checkpoint past the head means the
|
|
313
|
+
// op sequence or the stored state is corrupt.
|
|
314
|
+
throw new ReplicationAssertionError(
|
|
315
|
+
`Invariant violation: sync config ${state._id} has last_checkpoint ${state.last_checkpoint} > stream head ${newCheckpoint}`
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const canCheckpoint = canCheckpointState(lsn, {
|
|
320
|
+
snapshotDone: state.snapshot_done === true,
|
|
321
|
+
lastCheckpointLsn: state.last_checkpoint_lsn,
|
|
322
|
+
noCheckpointBefore: state.no_checkpoint_before
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (!canCheckpoint) {
|
|
326
|
+
checkpointBlocked = true;
|
|
327
|
+
// Log the first blocked config's state.
|
|
328
|
+
checkpointLogState ??= {
|
|
329
|
+
snapshot_done: state.snapshot_done,
|
|
330
|
+
last_checkpoint_lsn: state.last_checkpoint_lsn,
|
|
331
|
+
no_checkpoint_before: state.no_checkpoint_before
|
|
332
|
+
};
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
checkpointCreated ||= createEmptyCheckpoints || state.last_checkpoint !== newCheckpoint;
|
|
337
|
+
unblockedConfigIds.push(state._id);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Every commit advances the stream's resume position: commit() flushes first, so all
|
|
341
|
+
// source changes up to this lsn have been persisted, even when checkpoints are blocked.
|
|
342
|
+
// In the future we could also advance this on flush, when the connector provides the
|
|
343
|
+
// current position (see setResumeLsn, which connectors may already call after flushing).
|
|
344
|
+
const resumeLsnUpdate = { resume_lsn: lsn };
|
|
345
|
+
|
|
346
|
+
if (unblockedConfigIds.length > 0) {
|
|
347
|
+
// All unblocked configs get the SAME new value, so we apply it with a single updateOne
|
|
348
|
+
// (single-document atomicity).
|
|
349
|
+
const updateSet: Record<string, any> = {
|
|
350
|
+
last_keepalive_ts: now,
|
|
351
|
+
last_fatal_error: null,
|
|
352
|
+
last_fatal_error_ts: null
|
|
353
|
+
};
|
|
354
|
+
// Only advance checkpoint fields when an actual (non-empty) checkpoint is created, matching
|
|
355
|
+
// the previous per-config / v1 behaviour.
|
|
356
|
+
if (checkpointCreated) {
|
|
357
|
+
updateSet['sync_configs.$[config].last_checkpoint'] = newCheckpoint;
|
|
358
|
+
updateSet['sync_configs.$[config].last_checkpoint_lsn'] = lsn;
|
|
359
|
+
updateSet['last_checkpoint_ts'] = now;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
await this.db.sync_rules.updateOne(
|
|
363
|
+
{
|
|
364
|
+
_id: this.replicationStreamId,
|
|
365
|
+
'sync_configs._id': { $in: unblockedConfigIds }
|
|
366
|
+
},
|
|
367
|
+
{ $set: updateSet, $max: resumeLsnUpdate },
|
|
368
|
+
{
|
|
369
|
+
session: this.session,
|
|
370
|
+
arrayFilters: checkpointCreated ? [{ 'config._id': { $in: unblockedConfigIds } }] : undefined
|
|
371
|
+
}
|
|
372
|
+
);
|
|
373
|
+
} else {
|
|
374
|
+
// All selected configs are blocked - only update keepalive/error tracking and the
|
|
375
|
+
// resume position.
|
|
376
|
+
await this.db.sync_rules.updateOne(
|
|
377
|
+
{
|
|
378
|
+
_id: this.replicationStreamId
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
$set: {
|
|
382
|
+
last_keepalive_ts: now,
|
|
383
|
+
last_fatal_error: null,
|
|
384
|
+
last_fatal_error_ts: null
|
|
385
|
+
},
|
|
386
|
+
$max: resumeLsnUpdate
|
|
387
|
+
},
|
|
388
|
+
{ session: this.session }
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (checkpointBlocked) {
|
|
393
|
+
if (Date.now() - this.lastWaitingLogThrottledV3 > 5_000) {
|
|
394
|
+
this.logger.info(
|
|
395
|
+
`Waiting before creating checkpoint, currently at ${lsn}. Current state: ${JSON.stringify(checkpointLogState)}`
|
|
396
|
+
);
|
|
397
|
+
this.lastWaitingLogThrottledV3 = Date.now();
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
if (checkpointCreated) {
|
|
401
|
+
this.logger.debug(`Created checkpoint at ${lsn}`);
|
|
402
|
+
}
|
|
403
|
+
await this.autoActivateV3(lsn);
|
|
404
|
+
// All configs are now checkpointed at newCheckpoint (the stream head).
|
|
405
|
+
await this.sourceRecordStore.postCommitCleanup(newCheckpoint, this.logger);
|
|
406
|
+
}
|
|
407
|
+
if (checkpointCreated) {
|
|
408
|
+
await this.db.notifyCheckpoint();
|
|
409
|
+
}
|
|
410
|
+
return {
|
|
411
|
+
checkpointBlocked,
|
|
412
|
+
checkpointCreated
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async keepalive(lsn: string): Promise<storage.CheckpointResult> {
|
|
417
|
+
return await this.commit(lsn, { createEmptyCheckpoints: true });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async setResumeLsn(lsn: string): Promise<void> {
|
|
421
|
+
await this.db.sync_rules.updateOne(
|
|
422
|
+
{
|
|
423
|
+
_id: this.replicationStreamId
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
$set: {
|
|
427
|
+
resume_lsn: lsn
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
{ session: this.session }
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private async autoActivateV3(lsn: string): Promise<void> {
|
|
435
|
+
if (!this.needsActivationV3) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const session = this.session;
|
|
440
|
+
let activated = false;
|
|
441
|
+
await session.withTransaction(async () => {
|
|
442
|
+
const doc = await this.db.sync_rules.findOne(
|
|
443
|
+
{
|
|
444
|
+
_id: this.replicationStreamId,
|
|
445
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
session,
|
|
449
|
+
projection: {
|
|
450
|
+
state: 1,
|
|
451
|
+
sync_configs: 1
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
);
|
|
455
|
+
const states =
|
|
456
|
+
(doc as ReplicationStreamDocumentV3)?.sync_configs?.filter((config) =>
|
|
457
|
+
this.syncConfigIds.some((id) => id.equals(config._id))
|
|
458
|
+
) ?? [];
|
|
459
|
+
if (doc == null || states.length == 0) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const processingStates = states.filter((state) => state.state == storage.SyncRuleState.PROCESSING);
|
|
464
|
+
if (
|
|
465
|
+
doc.state == storage.SyncRuleState.PROCESSING &&
|
|
466
|
+
processingStates.length == states.length &&
|
|
467
|
+
states.every((state) => state.snapshot_done && state.last_checkpoint != null)
|
|
468
|
+
) {
|
|
469
|
+
await this.db.sync_rules.updateOne(
|
|
470
|
+
{
|
|
471
|
+
_id: this.replicationStreamId,
|
|
472
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
$set: {
|
|
476
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
477
|
+
'sync_configs.$[config].state': storage.SyncRuleState.ACTIVE
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
session,
|
|
482
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
await this.db.sync_rules.updateMany(
|
|
487
|
+
{
|
|
488
|
+
_id: { $ne: this.replicationStreamId },
|
|
489
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
490
|
+
},
|
|
491
|
+
syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
|
|
492
|
+
{ session }
|
|
493
|
+
);
|
|
494
|
+
activated = true;
|
|
495
|
+
} else if (
|
|
496
|
+
doc.state == storage.SyncRuleState.ACTIVE &&
|
|
497
|
+
processingStates.length > 0 &&
|
|
498
|
+
processingStates.every((state) => state.snapshot_done && state.last_checkpoint != null)
|
|
499
|
+
) {
|
|
500
|
+
await this.db.sync_rules.updateOne(
|
|
501
|
+
{
|
|
502
|
+
_id: this.replicationStreamId,
|
|
503
|
+
'sync_configs._id': { $in: processingStates.map((state) => state._id) }
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
$set: {
|
|
507
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.STOP,
|
|
508
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.ACTIVE
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
session,
|
|
513
|
+
arrayFilters: [
|
|
514
|
+
{ 'activeConfig.state': storage.SyncRuleState.ACTIVE },
|
|
515
|
+
{ 'processingConfig._id': { $in: processingStates.map((state) => state._id) } }
|
|
516
|
+
]
|
|
517
|
+
}
|
|
518
|
+
);
|
|
519
|
+
activated = true;
|
|
520
|
+
} else if (doc.state != storage.SyncRuleState.PROCESSING && doc.state != storage.SyncRuleState.ACTIVE) {
|
|
521
|
+
this.needsActivationV3 = false;
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
if (activated) {
|
|
525
|
+
this.logger.info(`Activated new replication stream at ${lsn}`);
|
|
526
|
+
await this.db.notifyCheckpoint();
|
|
527
|
+
this.needsActivationV3 = false;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
async markAllSnapshotDone(no_checkpoint_before_lsn: string): Promise<void> {
|
|
532
|
+
await this.db.sync_rules.updateOne(
|
|
533
|
+
{
|
|
534
|
+
_id: this.replicationStreamId,
|
|
535
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
$set: {
|
|
539
|
+
'sync_configs.$[config].snapshot_done': true,
|
|
540
|
+
last_keepalive_ts: new Date()
|
|
541
|
+
},
|
|
542
|
+
$max: {
|
|
543
|
+
'sync_configs.$[config].no_checkpoint_before': no_checkpoint_before_lsn
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
session: this.session,
|
|
548
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
549
|
+
}
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async markSnapshotDone(no_checkpoint_before_lsn: string, options?: { throwOnConflict?: boolean }): Promise<void> {
|
|
554
|
+
await this.withTransaction(async () => {
|
|
555
|
+
// Protect against race conditions
|
|
556
|
+
const blockingTables = await this.db
|
|
557
|
+
.sourceTables(this.replicationStreamId)
|
|
558
|
+
.find(this.snapshotBlockingSourceTablesFilter(), {
|
|
559
|
+
session: this.session,
|
|
560
|
+
projection: { schema_name: 1, table_name: 1 }
|
|
561
|
+
})
|
|
562
|
+
.toArray();
|
|
563
|
+
|
|
564
|
+
if (blockingTables.length > 0) {
|
|
565
|
+
if (options?.throwOnConflict ?? true) {
|
|
566
|
+
throw new ReplicationAssertionError(
|
|
567
|
+
`Cannot mark snapshot done while source tables still require snapshotting. Tables: ${blockingTables.map((t) => `${t.schema_name}.${t.table_name}`).join(', ')}`
|
|
568
|
+
);
|
|
569
|
+
} else {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
await this.markAllSnapshotDone(no_checkpoint_before_lsn);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async markTableSnapshotRequired(table: storage.SourceTable): Promise<void> {
|
|
579
|
+
const syncConfigIds = this.relevantSyncConfigIds(table);
|
|
580
|
+
if (syncConfigIds.length == 0) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
await this.db.sync_rules.updateOne(
|
|
585
|
+
{
|
|
586
|
+
_id: this.replicationStreamId,
|
|
587
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
$set: {
|
|
591
|
+
'sync_configs.$[config].snapshot_done': false
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
session: this.session,
|
|
596
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async markTableSnapshotDone(
|
|
602
|
+
tables: storage.SourceTable[],
|
|
603
|
+
no_checkpoint_before_lsn?: string
|
|
604
|
+
): Promise<storage.SourceTable[]> {
|
|
605
|
+
const session = this.session;
|
|
606
|
+
const ids = tables.map((table) => mongoTableId(table.id));
|
|
607
|
+
const syncConfigIds = this.relevantSyncConfigIdsForTables(tables);
|
|
608
|
+
|
|
609
|
+
await this.withTransaction(async () => {
|
|
610
|
+
await this.db.sourceTables(this.replicationStreamId).updateMany(
|
|
611
|
+
{ _id: { $in: ids } },
|
|
612
|
+
{
|
|
613
|
+
$set: {
|
|
614
|
+
snapshot_done: true
|
|
615
|
+
},
|
|
616
|
+
$unset: {
|
|
617
|
+
snapshot_status: 1
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
{ session }
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
if (no_checkpoint_before_lsn != null && syncConfigIds.length > 0) {
|
|
624
|
+
await this.db.sync_rules.updateOne(
|
|
625
|
+
{
|
|
626
|
+
_id: this.replicationStreamId,
|
|
627
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
$set: {
|
|
631
|
+
last_keepalive_ts: new Date()
|
|
632
|
+
},
|
|
633
|
+
$max: {
|
|
634
|
+
'sync_configs.$[config].no_checkpoint_before': no_checkpoint_before_lsn
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
session: this.session,
|
|
639
|
+
// Only set for sync configs that use this table
|
|
640
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
return tables.map((table) => {
|
|
646
|
+
const copy = table.clone();
|
|
647
|
+
copy.snapshotComplete = true;
|
|
648
|
+
return copy;
|
|
649
|
+
});
|
|
650
|
+
}
|
|
44
651
|
}
|