@powersync/service-module-mongodb-storage 0.17.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 +19 -0
- package/dist/storage/MongoBucketStorage.d.ts +34 -16
- package/dist/storage/MongoBucketStorage.js +219 -70
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +42 -23
- package/dist/storage/implementation/MongoBucketBatch.js +61 -44
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +10 -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 +2 -0
- 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 +41 -23
- package/dist/storage/implementation/MongoSyncBucketStorage.js +51 -43
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +3 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +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 +1 -2
- package/dist/storage/implementation/common/PersistedBatch.js +5 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +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.js +9 -14
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +4 -13
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +9 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +42 -26
- 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/MongoSyncBucketStorageV1.d.ts +15 -17
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +47 -45
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.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 +10 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -4
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +286 -259
- 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 +19 -2
- 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 +40 -28
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +232 -116
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -1
- 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 +13 -12
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +29 -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 +64 -21
- 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/storage/MongoBucketStorage.ts +334 -87
- package/src/storage/implementation/MongoBucketBatch.ts +86 -58
- package/src/storage/implementation/MongoChecksums.ts +6 -138
- package/src/storage/implementation/MongoCompactor.ts +19 -9
- 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 +70 -56
- package/src/storage/implementation/MongoSyncRulesLock.ts +7 -4
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
- package/src/storage/implementation/common/PersistedBatch.ts +7 -3
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +11 -19
- package/src/storage/implementation/models.ts +4 -14
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +55 -34
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +70 -64
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +11 -1
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +358 -314
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
- package/src/storage/implementation/v3/MongoCompactorV3.ts +646 -29
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +315 -157
- package/src/storage/implementation/v3/PersistedBatchV3.ts +62 -36
- 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 +41 -36
- 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 +93 -55
- 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 +1039 -68
- package/test/src/storeCurrentData.test.ts +17 -7
- 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 -15
- 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 -67
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
- 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 -4
- 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 -75
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
- 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 -11
|
@@ -3,39 +3,86 @@ import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
|
3
3
|
import { storage } from '@powersync/service-core';
|
|
4
4
|
import * as bson from 'bson';
|
|
5
5
|
import { mongoTableId } from '../../../utils/util.js';
|
|
6
|
-
import {
|
|
6
|
+
import { canCheckpointState } from '../CheckpointState.js';
|
|
7
7
|
import { MongoBucketBatch } from '../MongoBucketBatch.js';
|
|
8
8
|
import { syncRuleStateUpdatePipeline } from '../SyncRuleStateUpdate.js';
|
|
9
9
|
import { PersistedBatchV3 } from './PersistedBatchV3.js';
|
|
10
10
|
import { SourceRecordStoreV3 } from './SourceRecordStoreV3.js';
|
|
11
|
-
|
|
12
|
-
return left.length == right.length && left.every((value, index) => value == right[index]);
|
|
13
|
-
}
|
|
11
|
+
import { createNewSourceTable, designateEventCarrier, overlappingSourceTableFilter, planSourceTableReconciliation, sourceTableDesiredResolution, sourceTableFromDocument } from './source-table-utils.js';
|
|
14
12
|
export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
15
13
|
store;
|
|
16
|
-
|
|
14
|
+
syncConfigIds;
|
|
17
15
|
needsActivationV3 = true;
|
|
18
16
|
lastWaitingLogThrottledV3 = 0;
|
|
19
17
|
constructor(options) {
|
|
20
18
|
super(options);
|
|
21
|
-
|
|
19
|
+
const syncConfigIds = options.syncConfigIds ?? [];
|
|
20
|
+
if (syncConfigIds.length == 0) {
|
|
22
21
|
throw new ReplicationAssertionError('Missing sync config id for v3 batch');
|
|
23
22
|
}
|
|
24
|
-
this.
|
|
25
|
-
this.store = new SourceRecordStoreV3(this.db, this.
|
|
23
|
+
this.syncConfigIds = syncConfigIds;
|
|
24
|
+
this.store = new SourceRecordStoreV3(this.db, this.replicationStreamId, this.mapping);
|
|
26
25
|
}
|
|
27
26
|
createPersistedBatch(writtenSize) {
|
|
28
|
-
return new PersistedBatchV3(this.db, this.
|
|
27
|
+
return new PersistedBatchV3(this.db, this.replicationStreamId, this.mapping, writtenSize, {
|
|
29
28
|
logger: this.logger
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
31
|
get sourceRecordStore() {
|
|
33
32
|
return this.store;
|
|
34
33
|
}
|
|
34
|
+
async onReplicationTransactionFlush(session, lastOp) {
|
|
35
|
+
// Durably advance the stream-level head of persisted ops within the flush transaction.
|
|
36
|
+
// This ensures a checkpoint created later (even by an empty commit, or by a freshly-appended
|
|
37
|
+
// config that replicates nothing) covers all ops persisted before a potential crash.
|
|
38
|
+
await this.db.sync_rules.updateOne({
|
|
39
|
+
_id: this.replicationStreamId
|
|
40
|
+
}, {
|
|
41
|
+
$max: {
|
|
42
|
+
last_persisted_op: lastOp
|
|
43
|
+
}
|
|
44
|
+
}, { session });
|
|
45
|
+
}
|
|
46
|
+
selectedSyncConfigObjectIds(syncConfigIds) {
|
|
47
|
+
const selectedIds = new Set(this.syncConfigIds.map((id) => id.toHexString()));
|
|
48
|
+
return syncConfigIds
|
|
49
|
+
.filter((id) => selectedIds.has(id) && bson.ObjectId.isValid(id))
|
|
50
|
+
.map((id) => new bson.ObjectId(id));
|
|
51
|
+
}
|
|
52
|
+
relevantSyncConfigIds(table) {
|
|
53
|
+
const bucketDataSourceIds = table.bucketDataSources.map((source) => this.mapping.bucketSourceId(source));
|
|
54
|
+
const parameterLookupSourceIds = table.parameterLookupSources.map((source) => this.mapping.parameterLookupId(source));
|
|
55
|
+
return this.selectedSyncConfigObjectIds(this.mapping.syncConfigIdsForSourceTable(this.syncConfigIds.map((id) => id.toHexString()), table.ref, bucketDataSourceIds, parameterLookupSourceIds));
|
|
56
|
+
}
|
|
57
|
+
relevantSyncConfigIdsForTables(tables) {
|
|
58
|
+
const ids = new Map();
|
|
59
|
+
for (const table of tables) {
|
|
60
|
+
for (const id of this.relevantSyncConfigIds(table)) {
|
|
61
|
+
ids.set(id.toHexString(), id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...ids.values()];
|
|
65
|
+
}
|
|
66
|
+
snapshotBlockingSourceTablesFilter() {
|
|
67
|
+
const clauses = this.syncConfigIds.flatMap((syncConfigId) => {
|
|
68
|
+
const filter = this.mapping.snapshotBlockingSourceTablesFilter(syncConfigId.toHexString());
|
|
69
|
+
return filter.$or ?? [];
|
|
70
|
+
});
|
|
71
|
+
if (clauses.length == 0) {
|
|
72
|
+
return {
|
|
73
|
+
snapshot_done: false,
|
|
74
|
+
_id: { $exists: false }
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
snapshot_done: false,
|
|
79
|
+
$or: clauses
|
|
80
|
+
};
|
|
81
|
+
}
|
|
35
82
|
async cleanupDroppedSourceTables(sourceTables) {
|
|
36
83
|
for (const table of sourceTables) {
|
|
37
84
|
await this.db
|
|
38
|
-
.
|
|
85
|
+
.sourceRecords(this.replicationStreamId, mongoTableId(table.id))
|
|
39
86
|
.drop()
|
|
40
87
|
.catch((error) => {
|
|
41
88
|
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
|
|
@@ -46,178 +93,86 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
46
93
|
}
|
|
47
94
|
}
|
|
48
95
|
async resolveTables(options) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
96
|
+
// The test-only override is a whole parsed set, so the sync rules and the mapping
|
|
97
|
+
// used below always come from the same parse.
|
|
98
|
+
const parsedOverride = options.parsedSyncConfig;
|
|
99
|
+
const syncConfig = parsedOverride?.hydratedSyncConfig ?? this.sync_rules;
|
|
100
|
+
const mapping = parsedOverride?.mapping ?? this.mapping;
|
|
52
101
|
const { connection_id, source } = options;
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
102
|
+
const context = {
|
|
103
|
+
connectionId: connection_id,
|
|
104
|
+
connectionTag: source.connectionTag,
|
|
105
|
+
identity: {
|
|
106
|
+
schema: source.schema,
|
|
107
|
+
name: source.name,
|
|
108
|
+
objectId: source.objectId,
|
|
109
|
+
replicaIdColumns: source.replicaIdColumns.map((column) => ({
|
|
110
|
+
name: column.name,
|
|
111
|
+
type: column.type,
|
|
112
|
+
type_oid: column.typeId
|
|
113
|
+
}))
|
|
114
|
+
},
|
|
115
|
+
storeCurrentData: source.sendsCompleteRows !== true,
|
|
116
|
+
syncConfig,
|
|
117
|
+
mapping,
|
|
118
|
+
desired: sourceTableDesiredResolution(syncConfig, source, mapping)
|
|
119
|
+
};
|
|
59
120
|
let result = null;
|
|
60
|
-
const
|
|
61
|
-
await
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const retainedDocIds = [];
|
|
82
|
-
const tables = [];
|
|
83
|
-
let retainedEventOnlyTable = false;
|
|
84
|
-
for (const doc of exactDocs) {
|
|
85
|
-
const bucketDataSourceIds = doc.bucket_data_source_ids.filter((id) => desiredBucketIds.has(id));
|
|
86
|
-
const parameterLookupSourceIds = doc.parameter_lookup_source_ids.filter((id) => desiredLookupIds.has(id));
|
|
87
|
-
const coversDesiredMembership = bucketDataSourceIds.length > 0 || parameterLookupSourceIds.length > 0;
|
|
88
|
-
const coversEventOnlyTable = !desiredHasMembership && triggersEvent && !retainedEventOnlyTable;
|
|
89
|
-
for (const id of bucketDataSourceIds) {
|
|
90
|
-
coveredBucketIds.add(id);
|
|
91
|
-
}
|
|
92
|
-
for (const id of parameterLookupSourceIds) {
|
|
93
|
-
coveredLookupIds.add(id);
|
|
94
|
-
}
|
|
95
|
-
const updates = {};
|
|
96
|
-
if (!sameStringArray(doc.bucket_data_source_ids, bucketDataSourceIds) ||
|
|
97
|
-
!sameStringArray(doc.parameter_lookup_source_ids, parameterLookupSourceIds)) {
|
|
98
|
-
updates.bucket_data_source_ids = bucketDataSourceIds;
|
|
99
|
-
updates.parameter_lookup_source_ids = parameterLookupSourceIds;
|
|
100
|
-
}
|
|
101
|
-
if (Object.keys(updates).length > 0) {
|
|
102
|
-
await col.updateOne({ _id: doc._id }, { $set: updates }, { session });
|
|
103
|
-
}
|
|
104
|
-
if (coversDesiredMembership || coversEventOnlyTable) {
|
|
105
|
-
if (coversEventOnlyTable) {
|
|
106
|
-
retainedEventOnlyTable = true;
|
|
121
|
+
const session = this.db.client.startSession();
|
|
122
|
+
await using _ = { [Symbol.asyncDispose]: () => session.endSession() };
|
|
123
|
+
await session.withTransaction(async () => {
|
|
124
|
+
const col = this.db.sourceTables(this.replicationStreamId);
|
|
125
|
+
// Fetch every persisted source-table doc that can overlap this physical table.
|
|
126
|
+
// Exact-identity docs are candidates for reuse; non-exact overlaps are possible drops.
|
|
127
|
+
const candidateDocs = await col
|
|
128
|
+
.find(overlappingSourceTableFilter(connection_id, context.identity), { session })
|
|
129
|
+
.toArray();
|
|
130
|
+
// Pure planning: which docs to retain (and narrow), whether a new doc is needed for
|
|
131
|
+
// uncovered memberships, and which docs conflict with the current identity.
|
|
132
|
+
const plan = planSourceTableReconciliation(candidateDocs, context);
|
|
133
|
+
// Persist narrowing for incomplete snapshots only. Snapshot-complete docs keep stale
|
|
134
|
+
// coverage ids so compatible future configs can reuse already-snapshotted data.
|
|
135
|
+
// Narrowing occurs after removing a sync config, meaning we don't process those
|
|
136
|
+
// definitions anymore.
|
|
137
|
+
for (const update of plan.narrowingUpdates) {
|
|
138
|
+
await col.updateOne({ _id: update.id }, {
|
|
139
|
+
$set: {
|
|
140
|
+
bucket_data_source_ids: update.memberships.bucketDataSourceIds,
|
|
141
|
+
parameter_lookup_source_ids: update.memberships.parameterLookupSourceIds
|
|
107
142
|
}
|
|
108
|
-
|
|
109
|
-
const table = this.sourceTableFromDocument({
|
|
110
|
-
...doc,
|
|
111
|
-
bucket_data_source_ids: bucketDataSourceIds,
|
|
112
|
-
parameter_lookup_source_ids: parameterLookupSourceIds
|
|
113
|
-
}, connectionTag, syncRules, {
|
|
114
|
-
bucketDataSources: bucketDataSourceIds.map((id) => bucketSourceById.get(id)),
|
|
115
|
-
parameterLookupSources: parameterLookupSourceIds.map((id) => parameterLookupSourceById.get(id))
|
|
116
|
-
});
|
|
117
|
-
table.storeCurrentData = sendsCompleteRows !== true;
|
|
118
|
-
tables.push(table);
|
|
119
|
-
}
|
|
143
|
+
}, { session });
|
|
120
144
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
145
|
+
// Any desired membership not covered by an existing doc gets a new source table.
|
|
146
|
+
// That table snapshots only the uncovered memberships.
|
|
147
|
+
if (plan.newTableMemberships != null) {
|
|
124
148
|
const id = options.idGenerator ? options.idGenerator() : new bson.ObjectId();
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
replicaIdColumns,
|
|
130
|
-
snapshotComplete: false,
|
|
131
|
-
bucketDataSources: uncoveredBucketIds.map((id) => bucketSourceById.get(id)),
|
|
132
|
-
parameterLookupSources: uncoveredLookupIds.map((id) => parameterLookupSourceById.get(id))
|
|
133
|
-
});
|
|
134
|
-
sourceTable.syncData = uncoveredBucketIds.length > 0;
|
|
135
|
-
sourceTable.syncParameters = uncoveredLookupIds.length > 0;
|
|
136
|
-
sourceTable.syncEvent = triggersEvent;
|
|
137
|
-
sourceTable.storeCurrentData = sendsCompleteRows !== true;
|
|
138
|
-
const createDoc = {
|
|
139
|
-
_id: id,
|
|
140
|
-
connection_id,
|
|
141
|
-
relation_id: objectId,
|
|
142
|
-
schema_name: schema,
|
|
143
|
-
table_name: name,
|
|
144
|
-
replica_id_columns: null,
|
|
145
|
-
replica_id_columns2: normalizedReplicaIdColumns,
|
|
146
|
-
snapshot_done: false,
|
|
147
|
-
snapshot_status: undefined,
|
|
148
|
-
bucket_data_source_ids: uncoveredBucketIds,
|
|
149
|
-
parameter_lookup_source_ids: uncoveredLookupIds
|
|
150
|
-
};
|
|
151
|
-
await col.insertOne(createDoc, { session });
|
|
152
|
-
initializeSourceRecordsFor.push(createDoc._id);
|
|
153
|
-
retainedDocIds.push(createDoc._id);
|
|
154
|
-
tables.push(sourceTable);
|
|
155
|
-
}
|
|
156
|
-
const conflictFilter = [{ schema_name: schema, table_name: name }];
|
|
157
|
-
if (objectId != null) {
|
|
158
|
-
conflictFilter.push({ relation_id: objectId });
|
|
149
|
+
const { doc, table } = createNewSourceTable(id, plan.newTableMemberships, context);
|
|
150
|
+
await col.insertOne(doc, { session });
|
|
151
|
+
await this.db.initializeSourceRecordsCollection(this.replicationStreamId, doc._id, session);
|
|
152
|
+
plan.tables.push(table);
|
|
159
153
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
connection_id,
|
|
163
|
-
_id: { $nin: retainedDocIds },
|
|
164
|
-
$or: conflictFilter
|
|
165
|
-
}, { session })
|
|
166
|
-
.toArray();
|
|
154
|
+
// If memberships are split across multiple source tables, only one may fire events.
|
|
155
|
+
designateEventCarrier(plan.tables, context.desired.triggersEvent);
|
|
167
156
|
result = {
|
|
168
|
-
tables,
|
|
169
|
-
dropTables:
|
|
157
|
+
tables: plan.tables,
|
|
158
|
+
dropTables: plan.dropDocs.map((doc) => sourceTableFromDocument(doc, context.connectionTag, syncConfig, mapping))
|
|
170
159
|
};
|
|
171
160
|
});
|
|
172
|
-
for (const sourceTableId of initializeSourceRecordsFor) {
|
|
173
|
-
await this.db.initializeSourceRecordsCollection(this.group_id, sourceTableId);
|
|
174
|
-
}
|
|
175
161
|
return result;
|
|
176
162
|
}
|
|
177
|
-
sourceTableFromDocument(doc, connectionTag, syncRules, memberships) {
|
|
178
|
-
const resolvedMemberships = memberships ?? this.sourceTableMembershipsFromDocument(doc, syncRules);
|
|
179
|
-
const table = new storage.SourceTable({
|
|
180
|
-
id: doc._id,
|
|
181
|
-
ref: {
|
|
182
|
-
connectionTag,
|
|
183
|
-
schema: doc.schema_name,
|
|
184
|
-
name: doc.table_name
|
|
185
|
-
},
|
|
186
|
-
objectId: doc.relation_id,
|
|
187
|
-
replicaIdColumns: doc.replica_id_columns2.map((c) => ({ name: c.name, typeId: c.type_oid, type: c.type })),
|
|
188
|
-
snapshotComplete: doc.snapshot_done ?? true,
|
|
189
|
-
bucketDataSources: resolvedMemberships.bucketDataSources,
|
|
190
|
-
parameterLookupSources: resolvedMemberships.parameterLookupSources
|
|
191
|
-
});
|
|
192
|
-
table.syncData = table.bucketDataSources.length > 0;
|
|
193
|
-
table.syncParameters = table.parameterLookupSources.length > 0;
|
|
194
|
-
table.syncEvent = syncRules.tableTriggersEvent(table.ref);
|
|
195
|
-
table.snapshotStatus =
|
|
196
|
-
doc.snapshot_status == null
|
|
197
|
-
? undefined
|
|
198
|
-
: {
|
|
199
|
-
lastKey: doc.snapshot_status.last_key?.buffer ?? null,
|
|
200
|
-
totalEstimatedCount: doc.snapshot_status.total_estimated_count,
|
|
201
|
-
replicatedCount: doc.snapshot_status.replicated_count
|
|
202
|
-
};
|
|
203
|
-
return table;
|
|
204
|
-
}
|
|
205
|
-
sourceTableMembershipsFromDocument(doc, syncRules) {
|
|
206
|
-
const bucketDataSourceIds = new Set(doc.bucket_data_source_ids);
|
|
207
|
-
const parameterLookupSourceIds = new Set(doc.parameter_lookup_source_ids);
|
|
208
|
-
return {
|
|
209
|
-
bucketDataSources: syncRules.bucketDataSources.filter((source) => bucketDataSourceIds.has(this.mapping.bucketSourceId(source))),
|
|
210
|
-
parameterLookupSources: syncRules.bucketParameterLookupSources.filter((source) => parameterLookupSourceIds.has(this.mapping.parameterLookupId(source)))
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
163
|
async getSourceTableStatus(table) {
|
|
214
164
|
const doc = (await this.db
|
|
215
|
-
.
|
|
165
|
+
.sourceTables(this.replicationStreamId)
|
|
216
166
|
.findOne({ _id: mongoTableId(table.id) }, { session: this.session }));
|
|
217
167
|
if (doc == null) {
|
|
218
168
|
return null;
|
|
219
169
|
}
|
|
220
|
-
|
|
170
|
+
const refreshed = sourceTableFromDocument(doc, table.ref.connectionTag, this.sync_rules, this.mapping);
|
|
171
|
+
// The event-carrier designation is decided per resolveTables result and not persisted -
|
|
172
|
+
// preserve the caller's designation instead of recomputing it from the ref, so that
|
|
173
|
+
// refreshing a non-carrier table does not make it fire events.
|
|
174
|
+
refreshed.syncEvent = table.syncEvent;
|
|
175
|
+
return refreshed;
|
|
221
176
|
}
|
|
222
177
|
async commit(lsn, options) {
|
|
223
178
|
const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
|
|
@@ -233,80 +188,125 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
233
188
|
}, {
|
|
234
189
|
session: this.session
|
|
235
190
|
});
|
|
236
|
-
const preUpdateDocument = await this.db.sync_rules.findOne({
|
|
237
|
-
_id: this.
|
|
238
|
-
'sync_configs._id': this.
|
|
191
|
+
const preUpdateDocument = (await this.db.sync_rules.findOne({
|
|
192
|
+
_id: this.replicationStreamId,
|
|
193
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
239
194
|
}, {
|
|
240
195
|
session: this.session,
|
|
241
196
|
projection: {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
$elemMatch: {
|
|
245
|
-
_id: this.syncConfigId
|
|
246
|
-
}
|
|
247
|
-
}
|
|
197
|
+
sync_configs: 1,
|
|
198
|
+
last_persisted_op: 1
|
|
248
199
|
}
|
|
249
|
-
});
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
throw new ReplicationAssertionError(`Failed to update checkpoint - no matching sync_config for _id: ${this.
|
|
200
|
+
}));
|
|
201
|
+
const states = preUpdateDocument?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
202
|
+
if (states.length == 0) {
|
|
203
|
+
throw new ReplicationAssertionError(`Failed to update checkpoint - no matching sync_config for _id: ${this.replicationStreamId}/${this.syncConfigIds
|
|
204
|
+
.map((id) => id.toHexString())
|
|
205
|
+
.join(',')}`);
|
|
253
206
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
persistedOp: this.persisted_op,
|
|
262
|
-
createEmptyCheckpoints
|
|
263
|
-
});
|
|
264
|
-
const updateSet = {
|
|
265
|
-
last_keepalive_ts: now,
|
|
266
|
-
last_fatal_error: null,
|
|
267
|
-
last_fatal_error_ts: null,
|
|
268
|
-
'sync_configs.$[config].keepalive_op': checkpointState.newKeepaliveOp,
|
|
269
|
-
'sync_configs.$[config].last_checkpoint': checkpointState.newLastCheckpoint
|
|
270
|
-
};
|
|
271
|
-
if (checkpointState.checkpointCreated) {
|
|
272
|
-
updateSet['sync_configs.$[config].last_checkpoint_lsn'] = lsn;
|
|
273
|
-
updateSet['snapshot_lsn'] = null;
|
|
274
|
-
updateSet['last_checkpoint_ts'] = now;
|
|
207
|
+
// The replication job / MongoBucketBatch must be constructed with all replicating (PROCESSING / ACTIVE)
|
|
208
|
+
// sync configs in the stream, otherwise we'll get inconsistencies. Configs in other states (e.g. STOP)
|
|
209
|
+
// remain embedded in the document and are ignored here.
|
|
210
|
+
const missingSyncConfig = preUpdateDocument.sync_configs.find((config) => [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE].includes(config.state) &&
|
|
211
|
+
!this.syncConfigIds.some((id) => id.equals(config._id)));
|
|
212
|
+
if (missingSyncConfig != null) {
|
|
213
|
+
throw new ReplicationAssertionError(`Replication job not configured for sync config ${missingSyncConfig._id}`);
|
|
275
214
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
215
|
+
// Effective head of the stream's op sequence.
|
|
216
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and this
|
|
217
|
+
// read uses the same session, so it covers all ops persisted by this batch.
|
|
218
|
+
const newCheckpoint = preUpdateDocument?.last_persisted_op == null ? 0n : BigInt(preUpdateDocument.last_persisted_op);
|
|
219
|
+
let checkpointBlocked = false;
|
|
220
|
+
let checkpointCreated = false;
|
|
221
|
+
let checkpointLogState = null;
|
|
222
|
+
const unblockedConfigIds = [];
|
|
223
|
+
for (const state of states) {
|
|
224
|
+
if (state.last_checkpoint != null && state.last_checkpoint > newCheckpoint) {
|
|
225
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and
|
|
226
|
+
// checkpoints are only ever created at that head, so a checkpoint past the head means the
|
|
227
|
+
// op sequence or the stored state is corrupt.
|
|
228
|
+
throw new ReplicationAssertionError(`Invariant violation: sync config ${state._id} has last_checkpoint ${state.last_checkpoint} > stream head ${newCheckpoint}`);
|
|
229
|
+
}
|
|
230
|
+
const canCheckpoint = canCheckpointState(lsn, {
|
|
231
|
+
snapshotDone: state.snapshot_done === true,
|
|
232
|
+
lastCheckpointLsn: state.last_checkpoint_lsn,
|
|
233
|
+
noCheckpointBefore: state.no_checkpoint_before
|
|
234
|
+
});
|
|
235
|
+
if (!canCheckpoint) {
|
|
236
|
+
checkpointBlocked = true;
|
|
237
|
+
// Log the first blocked config's state.
|
|
238
|
+
checkpointLogState ??= {
|
|
288
239
|
snapshot_done: state.snapshot_done,
|
|
289
240
|
last_checkpoint_lsn: state.last_checkpoint_lsn,
|
|
290
241
|
no_checkpoint_before: state.no_checkpoint_before
|
|
291
|
-
}
|
|
242
|
+
};
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
checkpointCreated ||= createEmptyCheckpoints || state.last_checkpoint !== newCheckpoint;
|
|
246
|
+
unblockedConfigIds.push(state._id);
|
|
247
|
+
}
|
|
248
|
+
// Every commit advances the stream's resume position: commit() flushes first, so all
|
|
249
|
+
// source changes up to this lsn have been persisted, even when checkpoints are blocked.
|
|
250
|
+
// In the future we could also advance this on flush, when the connector provides the
|
|
251
|
+
// current position (see setResumeLsn, which connectors may already call after flushing).
|
|
252
|
+
const resumeLsnUpdate = { resume_lsn: lsn };
|
|
253
|
+
if (unblockedConfigIds.length > 0) {
|
|
254
|
+
// All unblocked configs get the SAME new value, so we apply it with a single updateOne
|
|
255
|
+
// (single-document atomicity).
|
|
256
|
+
const updateSet = {
|
|
257
|
+
last_keepalive_ts: now,
|
|
258
|
+
last_fatal_error: null,
|
|
259
|
+
last_fatal_error_ts: null
|
|
260
|
+
};
|
|
261
|
+
// Only advance checkpoint fields when an actual (non-empty) checkpoint is created, matching
|
|
262
|
+
// the previous per-config / v1 behaviour.
|
|
263
|
+
if (checkpointCreated) {
|
|
264
|
+
updateSet['sync_configs.$[config].last_checkpoint'] = newCheckpoint;
|
|
265
|
+
updateSet['sync_configs.$[config].last_checkpoint_lsn'] = lsn;
|
|
266
|
+
updateSet['last_checkpoint_ts'] = now;
|
|
267
|
+
}
|
|
268
|
+
await this.db.sync_rules.updateOne({
|
|
269
|
+
_id: this.replicationStreamId,
|
|
270
|
+
'sync_configs._id': { $in: unblockedConfigIds }
|
|
271
|
+
}, { $set: updateSet, $max: resumeLsnUpdate }, {
|
|
272
|
+
session: this.session,
|
|
273
|
+
arrayFilters: checkpointCreated ? [{ 'config._id': { $in: unblockedConfigIds } }] : undefined
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
// All selected configs are blocked - only update keepalive/error tracking and the
|
|
278
|
+
// resume position.
|
|
279
|
+
await this.db.sync_rules.updateOne({
|
|
280
|
+
_id: this.replicationStreamId
|
|
281
|
+
}, {
|
|
282
|
+
$set: {
|
|
283
|
+
last_keepalive_ts: now,
|
|
284
|
+
last_fatal_error: null,
|
|
285
|
+
last_fatal_error_ts: null
|
|
286
|
+
},
|
|
287
|
+
$max: resumeLsnUpdate
|
|
288
|
+
}, { session: this.session });
|
|
289
|
+
}
|
|
290
|
+
if (checkpointBlocked) {
|
|
291
|
+
if (Date.now() - this.lastWaitingLogThrottledV3 > 5_000) {
|
|
292
|
+
this.logger.info(`Waiting before creating checkpoint, currently at ${lsn}. Current state: ${JSON.stringify(checkpointLogState)}`);
|
|
292
293
|
this.lastWaitingLogThrottledV3 = Date.now();
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
else {
|
|
296
|
-
if (
|
|
297
|
-
this.logger.debug(`Created checkpoint at ${lsn}
|
|
297
|
+
if (checkpointCreated) {
|
|
298
|
+
this.logger.debug(`Created checkpoint at ${lsn}`);
|
|
298
299
|
}
|
|
299
300
|
await this.autoActivateV3(lsn);
|
|
301
|
+
// All configs are now checkpointed at newCheckpoint (the stream head).
|
|
302
|
+
await this.sourceRecordStore.postCommitCleanup(newCheckpoint, this.logger);
|
|
303
|
+
}
|
|
304
|
+
if (checkpointCreated) {
|
|
300
305
|
await this.db.notifyCheckpoint();
|
|
301
|
-
this.persisted_op = null;
|
|
302
|
-
this.last_checkpoint_lsn = lsn;
|
|
303
|
-
if (checkpointState.newLastCheckpoint != null) {
|
|
304
|
-
await this.sourceRecordStore.postCommitCleanup(checkpointState.newLastCheckpoint, this.logger);
|
|
305
|
-
}
|
|
306
306
|
}
|
|
307
307
|
return {
|
|
308
|
-
checkpointBlocked
|
|
309
|
-
checkpointCreated
|
|
308
|
+
checkpointBlocked,
|
|
309
|
+
checkpointCreated
|
|
310
310
|
};
|
|
311
311
|
}
|
|
312
312
|
async keepalive(lsn) {
|
|
@@ -314,11 +314,10 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
314
314
|
}
|
|
315
315
|
async setResumeLsn(lsn) {
|
|
316
316
|
await this.db.sync_rules.updateOne({
|
|
317
|
-
_id: this.
|
|
318
|
-
'sync_configs._id': this.syncConfigId
|
|
317
|
+
_id: this.replicationStreamId
|
|
319
318
|
}, {
|
|
320
319
|
$set: {
|
|
321
|
-
|
|
320
|
+
resume_lsn: lsn
|
|
322
321
|
}
|
|
323
322
|
}, { session: this.session });
|
|
324
323
|
}
|
|
@@ -330,28 +329,26 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
330
329
|
let activated = false;
|
|
331
330
|
await session.withTransaction(async () => {
|
|
332
331
|
const doc = await this.db.sync_rules.findOne({
|
|
333
|
-
_id: this.
|
|
334
|
-
'sync_configs._id': this.
|
|
332
|
+
_id: this.replicationStreamId,
|
|
333
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
335
334
|
}, {
|
|
336
335
|
session,
|
|
337
336
|
projection: {
|
|
338
337
|
state: 1,
|
|
339
|
-
sync_configs:
|
|
340
|
-
$elemMatch: {
|
|
341
|
-
_id: this.syncConfigId
|
|
342
|
-
}
|
|
343
|
-
}
|
|
338
|
+
sync_configs: 1
|
|
344
339
|
}
|
|
345
340
|
});
|
|
346
|
-
const
|
|
347
|
-
if (doc
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
341
|
+
const states = doc?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
342
|
+
if (doc == null || states.length == 0) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const processingStates = states.filter((state) => state.state == storage.SyncRuleState.PROCESSING);
|
|
346
|
+
if (doc.state == storage.SyncRuleState.PROCESSING &&
|
|
347
|
+
processingStates.length == states.length &&
|
|
348
|
+
states.every((state) => state.snapshot_done && state.last_checkpoint != null)) {
|
|
352
349
|
await this.db.sync_rules.updateOne({
|
|
353
|
-
_id: this.
|
|
354
|
-
'sync_configs._id': this.
|
|
350
|
+
_id: this.replicationStreamId,
|
|
351
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
355
352
|
}, {
|
|
356
353
|
$set: {
|
|
357
354
|
state: storage.SyncRuleState.ACTIVE,
|
|
@@ -359,15 +356,35 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
359
356
|
}
|
|
360
357
|
}, {
|
|
361
358
|
session,
|
|
362
|
-
arrayFilters: [{ 'config._id': this.
|
|
359
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
363
360
|
});
|
|
364
361
|
await this.db.sync_rules.updateMany({
|
|
365
|
-
_id: { $ne: this.
|
|
362
|
+
_id: { $ne: this.replicationStreamId },
|
|
366
363
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
367
364
|
}, syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP), { session });
|
|
368
365
|
activated = true;
|
|
369
366
|
}
|
|
370
|
-
else if (doc
|
|
367
|
+
else if (doc.state == storage.SyncRuleState.ACTIVE &&
|
|
368
|
+
processingStates.length > 0 &&
|
|
369
|
+
processingStates.every((state) => state.snapshot_done && state.last_checkpoint != null)) {
|
|
370
|
+
await this.db.sync_rules.updateOne({
|
|
371
|
+
_id: this.replicationStreamId,
|
|
372
|
+
'sync_configs._id': { $in: processingStates.map((state) => state._id) }
|
|
373
|
+
}, {
|
|
374
|
+
$set: {
|
|
375
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.STOP,
|
|
376
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.ACTIVE
|
|
377
|
+
}
|
|
378
|
+
}, {
|
|
379
|
+
session,
|
|
380
|
+
arrayFilters: [
|
|
381
|
+
{ 'activeConfig.state': storage.SyncRuleState.ACTIVE },
|
|
382
|
+
{ 'processingConfig._id': { $in: processingStates.map((state) => state._id) } }
|
|
383
|
+
]
|
|
384
|
+
});
|
|
385
|
+
activated = true;
|
|
386
|
+
}
|
|
387
|
+
else if (doc.state != storage.SyncRuleState.PROCESSING && doc.state != storage.SyncRuleState.ACTIVE) {
|
|
371
388
|
this.needsActivationV3 = false;
|
|
372
389
|
}
|
|
373
390
|
});
|
|
@@ -379,8 +396,8 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
379
396
|
}
|
|
380
397
|
async markAllSnapshotDone(no_checkpoint_before_lsn) {
|
|
381
398
|
await this.db.sync_rules.updateOne({
|
|
382
|
-
_id: this.
|
|
383
|
-
'sync_configs._id': this.
|
|
399
|
+
_id: this.replicationStreamId,
|
|
400
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
384
401
|
}, {
|
|
385
402
|
$set: {
|
|
386
403
|
'sync_configs.$[config].snapshot_done': true,
|
|
@@ -391,18 +408,22 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
391
408
|
}
|
|
392
409
|
}, {
|
|
393
410
|
session: this.session,
|
|
394
|
-
arrayFilters: [{ 'config._id': this.
|
|
411
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
395
412
|
});
|
|
396
413
|
}
|
|
397
414
|
async markSnapshotDone(no_checkpoint_before_lsn, options) {
|
|
398
415
|
await this.withTransaction(async () => {
|
|
399
416
|
// Protect against race conditions
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
417
|
+
const blockingTables = await this.db
|
|
418
|
+
.sourceTables(this.replicationStreamId)
|
|
419
|
+
.find(this.snapshotBlockingSourceTablesFilter(), {
|
|
420
|
+
session: this.session,
|
|
421
|
+
projection: { schema_name: 1, table_name: 1 }
|
|
422
|
+
})
|
|
423
|
+
.toArray();
|
|
424
|
+
if (blockingTables.length > 0) {
|
|
404
425
|
if (options?.throwOnConflict ?? true) {
|
|
405
|
-
throw new ReplicationAssertionError(`Cannot mark snapshot done while
|
|
426
|
+
throw new ReplicationAssertionError(`Cannot mark snapshot done while source tables still require snapshotting. Tables: ${blockingTables.map((t) => `${t.schema_name}.${t.table_name}`).join(', ')}`);
|
|
406
427
|
}
|
|
407
428
|
else {
|
|
408
429
|
return;
|
|
@@ -411,24 +432,29 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
411
432
|
await this.markAllSnapshotDone(no_checkpoint_before_lsn);
|
|
412
433
|
});
|
|
413
434
|
}
|
|
414
|
-
async markTableSnapshotRequired(
|
|
435
|
+
async markTableSnapshotRequired(table) {
|
|
436
|
+
const syncConfigIds = this.relevantSyncConfigIds(table);
|
|
437
|
+
if (syncConfigIds.length == 0) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
415
440
|
await this.db.sync_rules.updateOne({
|
|
416
|
-
_id: this.
|
|
417
|
-
'sync_configs._id':
|
|
441
|
+
_id: this.replicationStreamId,
|
|
442
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
418
443
|
}, {
|
|
419
444
|
$set: {
|
|
420
445
|
'sync_configs.$[config].snapshot_done': false
|
|
421
446
|
}
|
|
422
447
|
}, {
|
|
423
448
|
session: this.session,
|
|
424
|
-
arrayFilters: [{ 'config._id':
|
|
449
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
425
450
|
});
|
|
426
451
|
}
|
|
427
452
|
async markTableSnapshotDone(tables, no_checkpoint_before_lsn) {
|
|
428
453
|
const session = this.session;
|
|
429
454
|
const ids = tables.map((table) => mongoTableId(table.id));
|
|
455
|
+
const syncConfigIds = this.relevantSyncConfigIdsForTables(tables);
|
|
430
456
|
await this.withTransaction(async () => {
|
|
431
|
-
await this.db.
|
|
457
|
+
await this.db.sourceTables(this.replicationStreamId).updateMany({ _id: { $in: ids } }, {
|
|
432
458
|
$set: {
|
|
433
459
|
snapshot_done: true
|
|
434
460
|
},
|
|
@@ -436,10 +462,10 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
436
462
|
snapshot_status: 1
|
|
437
463
|
}
|
|
438
464
|
}, { session });
|
|
439
|
-
if (no_checkpoint_before_lsn != null) {
|
|
465
|
+
if (no_checkpoint_before_lsn != null && syncConfigIds.length > 0) {
|
|
440
466
|
await this.db.sync_rules.updateOne({
|
|
441
|
-
_id: this.
|
|
442
|
-
'sync_configs._id':
|
|
467
|
+
_id: this.replicationStreamId,
|
|
468
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
443
469
|
}, {
|
|
444
470
|
$set: {
|
|
445
471
|
last_keepalive_ts: new Date()
|
|
@@ -449,7 +475,8 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
449
475
|
}
|
|
450
476
|
}, {
|
|
451
477
|
session: this.session,
|
|
452
|
-
|
|
478
|
+
// Only set for sync configs that use this table
|
|
479
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
453
480
|
});
|
|
454
481
|
}
|
|
455
482
|
});
|