@powersync/service-module-mongodb-storage 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 +177 -5
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
- package/dist/storage/MongoBucketStorage.d.ts +71 -18
- package/dist/storage/MongoBucketStorage.js +516 -202
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +71 -29
- package/dist/storage/implementation/MongoBucketBatch.js +140 -66
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +41 -13
- package/dist/storage/implementation/MongoChecksums.js +36 -121
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +28 -90
- package/dist/storage/implementation/MongoCompactor.js +87 -481
- 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 +82 -8
- package/dist/storage/implementation/MongoParameterCompactor.js +312 -76
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +21 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +93 -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 +125 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/implementation/MongoStorageProvider.js +41 -4
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +81 -37
- package/dist/storage/implementation/MongoSyncBucketStorage.js +159 -86
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +23 -11
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +11 -8
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +159 -33
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
- package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +28 -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 +34 -2
- package/dist/storage/implementation/common/PersistedBatch.js +63 -10
- 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.d.ts +4 -1
- package/dist/storage/implementation/db.js +31 -16
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +55 -13
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +135 -107
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +47 -7
- package/dist/storage/implementation/v1/MongoCompactorV1.js +478 -8
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +33 -27
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +146 -73
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
- package/dist/storage/implementation/v1/SingleBucketStoreV1.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/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
- package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
- package/dist/storage/implementation/v3/CompactionLease.js +131 -0
- package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +16 -5
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +393 -262
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +108 -18
- package/dist/storage/implementation/v3/MongoCompactorV3.js +1115 -47
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +12 -3
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +13 -8
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +54 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +319 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +61 -36
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +353 -161
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +8 -3
- package/dist/storage/implementation/v3/PersistedBatchV3.js +131 -29
- package/dist/storage/implementation/v3/PersistedBatchV3.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 +26 -14
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +73 -29
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +13 -0
- package/dist/storage/implementation/v3/bucket-format.js +79 -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 +30 -0
- package/dist/storage/implementation/v3/chunking.js.map +1 -0
- package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
- package/dist/storage/implementation/v3/compact-utils.js +216 -0
- package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
- package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
- package/dist/storage/implementation/v3/compaction-constants.js +6 -0
- package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +187 -23
- package/dist/storage/implementation/v3/models.js +11 -28
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +20 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +48 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +33 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +68 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +154 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +67 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +439 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/source-table-utils.d.ts +63 -0
- package/dist/storage/implementation/v3/source-table-utils.js +206 -0
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
- package/dist/storage/storage-index.d.ts +5 -4
- package/dist/storage/storage-index.js +5 -4
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +71 -3
- package/dist/types/types.js +58 -2
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.d.ts +1 -2
- package/dist/utils/test-utils.js +7 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +9 -2
- package/dist/utils/util.js +131 -5
- package/dist/utils/util.js.map +1 -1
- package/package.json +13 -19
- package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
- package/src/storage/MongoBucketStorage.ts +728 -242
- package/src/storage/implementation/MongoBucketBatch.ts +196 -91
- package/src/storage/implementation/MongoChecksums.ts +93 -153
- package/src/storage/implementation/MongoCompactor.ts +106 -631
- package/src/storage/implementation/MongoHydrationState.ts +61 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +392 -87
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +122 -0
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +156 -0
- package/src/storage/implementation/MongoStorageProvider.ts +48 -4
- package/src/storage/implementation/MongoSyncBucketStorage.ts +226 -118
- package/src/storage/implementation/MongoSyncRulesLock.ts +28 -12
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +194 -52
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +31 -0
- package/src/storage/implementation/common/PersistedBatch.ts +83 -15
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +40 -22
- package/src/storage/implementation/models.ts +58 -14
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +174 -125
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
- package/src/storage/implementation/v1/MongoCompactorV1.ts +594 -25
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +226 -129
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +12 -1
- package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +502 -324
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
- package/src/storage/implementation/v3/MongoCompactorV3.ts +1439 -70
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +19 -9
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +473 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +453 -211
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +169 -45
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +99 -38
- package/src/storage/implementation/v3/bucket-format.ts +99 -0
- package/src/storage/implementation/v3/chunking.ts +40 -0
- package/src/storage/implementation/v3/compact-utils.ts +320 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +228 -58
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +62 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +44 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +182 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +573 -0
- package/src/storage/implementation/v3/source-table-utils.ts +398 -0
- package/src/storage/storage-index.ts +14 -4
- package/src/types/types.ts +76 -2
- package/src/utils/test-utils.ts +8 -3
- package/src/utils/util.ts +188 -7
- package/test/src/__snapshots__/storage.test.ts.snap +4 -54
- package/test/src/__snapshots__/storage_sync.test.ts.snap +652 -645
- package/test/src/cleanup-stopped-sync-configs.test.ts +698 -0
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +61 -0
- package/test/src/helpers/s3TestFactory.ts +69 -0
- package/test/src/object_storage_lifecycle.test.ts +59 -0
- package/test/src/object_storage_usage.test.ts +255 -0
- package/test/src/parameter_compacting_v1.test.ts +120 -0
- package/test/src/parameter_compacting_v3.test.ts +192 -0
- package/test/src/parameter_compaction_fence.test.ts +276 -0
- package/test/src/setup.ts +6 -1
- package/test/src/storage.test.ts +478 -13
- package/test/src/storage_compacting.test.ts +2516 -107
- package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
- package/test/src/storage_s3_checksums.test.ts +190 -0
- package/test/src/storage_s3_compaction_lifecycle.test.ts +543 -0
- package/test/src/storage_s3_reading.test.ts +768 -0
- package/test/src/storage_s3_writing.test.ts +136 -0
- package/test/src/storage_sync.test.ts +1507 -76
- package/test/src/storeCurrentData.test.ts +227 -0
- package/test/src/util.ts +3 -2
- 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/common/SingleBucketStore.d.ts +0 -54
- package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
- package/dist/storage/implementation/common/SingleBucketStore.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/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
- package/dist/storage/implementation/v3/SingleBucketStoreV3.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/common/SingleBucketStore.ts +0 -63
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
|
@@ -1,43 +1,58 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { ReplicationAssertionError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { InternalOpId, storage } from '@powersync/service-core';
|
|
4
|
+
import { EventDefinitionId } from '@powersync/service-sync-rules';
|
|
5
5
|
import * as bson from 'bson';
|
|
6
6
|
import { mongoTableId } from '../../../utils/util.js';
|
|
7
|
-
import {
|
|
7
|
+
import { canCheckpointState } from '../CheckpointState.js';
|
|
8
8
|
import { MongoBucketBatch, MongoBucketBatchOptions } from '../MongoBucketBatch.js';
|
|
9
|
-
import {
|
|
9
|
+
import { MongoParsedSyncConfigSet } from '../MongoParsedSyncConfigSet.js';
|
|
10
|
+
import { stopReplicationStreamPipeline } from '../SyncRuleStateUpdate.js';
|
|
10
11
|
import { PersistedBatch } from '../common/PersistedBatch.js';
|
|
11
12
|
import { SourceRecordStore } from '../common/SourceRecordStore.js';
|
|
12
13
|
import { PersistedBatchV3 } from './PersistedBatchV3.js';
|
|
13
14
|
import { SourceRecordStoreV3 } from './SourceRecordStoreV3.js';
|
|
14
15
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
15
|
-
import { ReplicationStreamDocumentV3, SourceTableDocumentV3 } from './models.js';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
import { CustomCheckpointRequestDocumentV3, ReplicationStreamDocumentV3, SourceTableDocumentV3 } from './models.js';
|
|
17
|
+
import {
|
|
18
|
+
createNewSourceTable,
|
|
19
|
+
overlappingSourceTableFilter,
|
|
20
|
+
planSourceTableReconciliation,
|
|
21
|
+
sourceTableDesiredResolution,
|
|
22
|
+
sourceTableFromDocument,
|
|
23
|
+
SourceTableReconciliationContext
|
|
24
|
+
} from './source-table-utils.js';
|
|
20
25
|
|
|
21
26
|
export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
22
|
-
declare
|
|
27
|
+
declare readonly db: VersionedPowerSyncMongoV3;
|
|
23
28
|
|
|
24
29
|
private readonly store: SourceRecordStore;
|
|
25
|
-
|
|
30
|
+
/** Avoid repeating idempotent createIndexes calls for every checkpoint flush. */
|
|
31
|
+
private readonly initializedCustomCheckpointEventIds = new Set<EventDefinitionId>();
|
|
32
|
+
// Used only to validate supplied checkpoint ids; it does not initialize collections.
|
|
33
|
+
private readonly knownEventIds: ReadonlySet<EventDefinitionId>;
|
|
34
|
+
private readonly syncConfigIds: bson.ObjectId[];
|
|
26
35
|
private needsActivationV3 = true;
|
|
27
36
|
private lastWaitingLogThrottledV3 = 0;
|
|
28
37
|
|
|
29
38
|
constructor(options: MongoBucketBatchOptions) {
|
|
30
39
|
super(options);
|
|
31
|
-
|
|
40
|
+
const syncConfigIds = options.syncConfigIds ?? [];
|
|
41
|
+
if (syncConfigIds.length == 0) {
|
|
32
42
|
throw new ReplicationAssertionError('Missing sync config id for v3 batch');
|
|
33
43
|
}
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
44
|
+
this.syncConfigIds = syncConfigIds;
|
|
45
|
+
this.knownEventIds = new Set(this.mapping.allEventDefinitionIds());
|
|
46
|
+
this.store = new SourceRecordStoreV3(this.db, this.replicationStreamId, this.mapping);
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
protected createPersistedBatch(writtenSize: number): PersistedBatch {
|
|
39
|
-
return new PersistedBatchV3(this.db, this.
|
|
40
|
-
logger: this.logger
|
|
50
|
+
return new PersistedBatchV3(this.db, this.replicationStreamId, this.mapping, writtenSize, {
|
|
51
|
+
logger: this.logger,
|
|
52
|
+
objectStorage: this.options.objectStorage,
|
|
53
|
+
inlineThresholdBytes: this.options.inlineThresholdBytes,
|
|
54
|
+
signal: this.options.signal,
|
|
55
|
+
objectStorageUsageWriterId: this.objectStorageUsageWriterId
|
|
41
56
|
});
|
|
42
57
|
}
|
|
43
58
|
|
|
@@ -45,13 +60,87 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
45
60
|
return this.store;
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
protected override async onReplicationTransactionFlush(
|
|
64
|
+
session: lib_mongo.mongo.ClientSession,
|
|
65
|
+
lastOp: bigint
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
// Durably advance the stream-level head of persisted ops within the flush transaction.
|
|
68
|
+
// This ensures a checkpoint created later (even by an empty commit, or by a freshly-appended
|
|
69
|
+
// config that replicates nothing) covers all ops persisted before a potential crash.
|
|
70
|
+
await this.db.sync_rules.updateOne(
|
|
71
|
+
{
|
|
72
|
+
_id: this.replicationStreamId
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
$max: {
|
|
76
|
+
last_persisted_op: lastOp
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{ session }
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private selectedSyncConfigObjectIds(syncConfigIds: string[]): bson.ObjectId[] {
|
|
84
|
+
const selectedIds = new Set(this.syncConfigIds.map((id) => id.toHexString()));
|
|
85
|
+
return syncConfigIds
|
|
86
|
+
.filter((id) => selectedIds.has(id) && bson.ObjectId.isValid(id))
|
|
87
|
+
.map((id) => new bson.ObjectId(id));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private relevantSyncConfigIds(table: storage.SourceTable): bson.ObjectId[] {
|
|
91
|
+
const bucketDataSourceIds = table.bucketDataSources.map((source) => this.mapping.bucketSourceId(source));
|
|
92
|
+
const parameterLookupSourceIds = table.parameterLookupSources.map((source) =>
|
|
93
|
+
this.mapping.parameterLookupId(source)
|
|
94
|
+
);
|
|
95
|
+
return this.selectedSyncConfigObjectIds(
|
|
96
|
+
this.mapping.syncConfigIdsForSourceTable(
|
|
97
|
+
this.syncConfigIds.map((id) => id.toHexString()),
|
|
98
|
+
table.ref,
|
|
99
|
+
bucketDataSourceIds,
|
|
100
|
+
parameterLookupSourceIds,
|
|
101
|
+
[...table.eventDefinitionIds!]
|
|
102
|
+
)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private relevantSyncConfigIdsForTables(tables: storage.SourceTable[]): bson.ObjectId[] {
|
|
107
|
+
const ids = new Map<string, bson.ObjectId>();
|
|
108
|
+
for (const table of tables) {
|
|
109
|
+
for (const id of this.relevantSyncConfigIds(table)) {
|
|
110
|
+
ids.set(id.toHexString(), id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [...ids.values()];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private snapshotBlockingSourceTablesFilter(): Record<string, unknown> {
|
|
117
|
+
const clauses = this.syncConfigIds.flatMap((syncConfigId) => {
|
|
118
|
+
const filter = this.mapping.snapshotBlockingSourceTablesFilter(syncConfigId.toHexString()) as {
|
|
119
|
+
$or?: Record<string, unknown>[];
|
|
120
|
+
};
|
|
121
|
+
return filter.$or ?? [];
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (clauses.length == 0) {
|
|
125
|
+
return {
|
|
126
|
+
snapshot_done: false,
|
|
127
|
+
_id: { $exists: false }
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
snapshot_done: false,
|
|
133
|
+
$or: clauses
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
48
137
|
protected async cleanupDroppedSourceTables(sourceTables: storage.SourceTable[]) {
|
|
49
138
|
for (const table of sourceTables) {
|
|
50
139
|
await this.db
|
|
51
|
-
.
|
|
140
|
+
.sourceRecords(this.replicationStreamId, mongoTableId(table.id))
|
|
52
141
|
.drop()
|
|
53
142
|
.catch((error) => {
|
|
54
|
-
if (lib_mongo.
|
|
143
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
|
|
55
144
|
return;
|
|
56
145
|
}
|
|
57
146
|
throw error;
|
|
@@ -60,235 +149,120 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
60
149
|
}
|
|
61
150
|
|
|
62
151
|
async resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult> {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
152
|
+
// The test-only override is a whole parsed set, so the sync rules and the mapping
|
|
153
|
+
// used below always come from the same parse.
|
|
154
|
+
const parsedOverride = options.parsedSyncConfig as MongoParsedSyncConfigSet | undefined;
|
|
155
|
+
const syncConfig = parsedOverride?.hydratedSyncConfig ?? this.sync_rules;
|
|
156
|
+
const mapping = parsedOverride?.mapping ?? this.mapping;
|
|
157
|
+
const eventById = (parsedOverride ?? this.options.parsedSyncConfig).eventById;
|
|
66
158
|
|
|
67
159
|
const { connection_id, source } = options;
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
160
|
+
const reconcile = options.reconcileSourceTables ?? storage.defaultSourceTableReconciler;
|
|
161
|
+
const identity = {
|
|
162
|
+
schema: source.schema,
|
|
163
|
+
name: source.name,
|
|
164
|
+
objectId: source.objectId,
|
|
165
|
+
replicaIdColumns: source.replicaIdColumns.map((column) => ({
|
|
166
|
+
name: column.name,
|
|
167
|
+
type: column.type,
|
|
168
|
+
type_oid: column.typeId
|
|
169
|
+
}))
|
|
170
|
+
};
|
|
74
171
|
|
|
75
172
|
let result: storage.ResolveTablesResult | null = null;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
await this.db.client.withSession(async (session) => {
|
|
79
|
-
const col = this.db.commonSourceTables(this.group_id);
|
|
80
|
-
const exactFilter: Record<string, unknown> = {
|
|
81
|
-
connection_id,
|
|
82
|
-
schema_name: schema,
|
|
83
|
-
table_name: name,
|
|
84
|
-
replica_id_columns2: normalizedReplicaIdColumns
|
|
85
|
-
};
|
|
86
|
-
if (objectId != null) {
|
|
87
|
-
exactFilter.relation_id = objectId;
|
|
88
|
-
}
|
|
173
|
+
const session = this.db.client.startSession();
|
|
174
|
+
await using _ = { [Symbol.asyncDispose]: () => session.endSession() };
|
|
89
175
|
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
matchingSources.bucketDataSources.map((source) => [this.mapping.bucketSourceId(source), source] as const)
|
|
93
|
-
);
|
|
94
|
-
const parameterLookupSourceById = new Map(
|
|
95
|
-
matchingSources.parameterLookupSources.map(
|
|
96
|
-
(source) => [this.mapping.parameterLookupId(source), source] as const
|
|
97
|
-
)
|
|
98
|
-
);
|
|
99
|
-
const desiredBucketIds = new Set(bucketSourceById.keys());
|
|
100
|
-
const desiredLookupIds = new Set(parameterLookupSourceById.keys());
|
|
101
|
-
const desiredHasMembership = desiredBucketIds.size > 0 || desiredLookupIds.size > 0;
|
|
102
|
-
const triggersEvent = syncRules.tableTriggersEvent(ref);
|
|
103
|
-
|
|
104
|
-
const coveredBucketIds = new Set<string>();
|
|
105
|
-
const coveredLookupIds = new Set<string>();
|
|
106
|
-
const retainedDocIds: bson.ObjectId[] = [];
|
|
107
|
-
const tables: storage.SourceTable[] = [];
|
|
108
|
-
let retainedEventOnlyTable = false;
|
|
109
|
-
|
|
110
|
-
for (const doc of exactDocs) {
|
|
111
|
-
const bucketDataSourceIds = doc.bucket_data_source_ids.filter((id) => desiredBucketIds.has(id));
|
|
112
|
-
const parameterLookupSourceIds = doc.parameter_lookup_source_ids.filter((id) => desiredLookupIds.has(id));
|
|
113
|
-
const coversDesiredMembership = bucketDataSourceIds.length > 0 || parameterLookupSourceIds.length > 0;
|
|
114
|
-
const coversEventOnlyTable = !desiredHasMembership && triggersEvent && !retainedEventOnlyTable;
|
|
115
|
-
|
|
116
|
-
for (const id of bucketDataSourceIds) {
|
|
117
|
-
coveredBucketIds.add(id);
|
|
118
|
-
}
|
|
119
|
-
for (const id of parameterLookupSourceIds) {
|
|
120
|
-
coveredLookupIds.add(id);
|
|
121
|
-
}
|
|
176
|
+
await session.withTransaction(async () => {
|
|
177
|
+
const col = this.db.sourceTables(this.replicationStreamId);
|
|
122
178
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
127
|
-
await col.updateOne(
|
|
128
|
-
{ _id: doc._id },
|
|
129
|
-
{
|
|
130
|
-
$set: {
|
|
131
|
-
bucket_data_source_ids: bucketDataSourceIds,
|
|
132
|
-
parameter_lookup_source_ids: parameterLookupSourceIds
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
{ session }
|
|
136
|
-
);
|
|
137
|
-
}
|
|
179
|
+
// Find records that overlap by name or relation id.
|
|
180
|
+
const candidateDocs = await col
|
|
181
|
+
.find(overlappingSourceTableFilter(connection_id, identity), { session })
|
|
182
|
+
.toArray();
|
|
138
183
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
this.sourceTableFromDocument(
|
|
146
|
-
{
|
|
147
|
-
...doc,
|
|
148
|
-
bucket_data_source_ids: bucketDataSourceIds,
|
|
149
|
-
parameter_lookup_source_ids: parameterLookupSourceIds
|
|
150
|
-
},
|
|
151
|
-
connectionTag,
|
|
152
|
-
syncRules,
|
|
153
|
-
{
|
|
154
|
-
bucketDataSources: bucketDataSourceIds.map((id) => bucketSourceById.get(id)!),
|
|
155
|
-
parameterLookupSources: parameterLookupSourceIds.map((id) => parameterLookupSourceById.get(id)!)
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
184
|
+
const candidateTables = candidateDocs.map((doc) =>
|
|
185
|
+
sourceTableFromDocument(doc, source.connectionTag, syncConfig, mapping, eventById)
|
|
186
|
+
);
|
|
187
|
+
const candidates = candidateTables.map((table) => table.clone());
|
|
188
|
+
const resolution = await reconcile({ source, candidates });
|
|
189
|
+
storage.validateSourceTableCandidateResolution(candidates, resolution);
|
|
161
190
|
|
|
162
|
-
|
|
163
|
-
const
|
|
191
|
+
// Persist metadata from the reconciler without mutating the queried documents.
|
|
192
|
+
for (const { id, sourceMetadata } of storage.diffSourceTableUpdates(candidateTables, resolution)) {
|
|
193
|
+
await col.updateOne({ _id: mongoTableId(id) }, { $set: { source_metadata: sourceMetadata } }, { session });
|
|
194
|
+
}
|
|
164
195
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
sourceTable.syncData = uncoveredBucketIds.length > 0;
|
|
177
|
-
sourceTable.syncParameters = uncoveredLookupIds.length > 0;
|
|
178
|
-
sourceTable.syncEvent = triggersEvent;
|
|
179
|
-
|
|
180
|
-
const createDoc: SourceTableDocumentV3 = {
|
|
181
|
-
_id: id,
|
|
182
|
-
connection_id,
|
|
183
|
-
relation_id: objectId,
|
|
184
|
-
schema_name: schema,
|
|
185
|
-
table_name: name,
|
|
186
|
-
replica_id_columns: null,
|
|
187
|
-
replica_id_columns2: normalizedReplicaIdColumns,
|
|
188
|
-
snapshot_done: false,
|
|
189
|
-
snapshot_status: undefined,
|
|
190
|
-
bucket_data_source_ids: uncoveredBucketIds,
|
|
191
|
-
parameter_lookup_source_ids: uncoveredLookupIds
|
|
192
|
-
};
|
|
196
|
+
const context: SourceTableReconciliationContext = {
|
|
197
|
+
connectionId: connection_id,
|
|
198
|
+
connectionTag: source.connectionTag,
|
|
199
|
+
identity,
|
|
200
|
+
storeCurrentData: source.sendsCompleteRows !== true,
|
|
201
|
+
syncConfig,
|
|
202
|
+
mapping,
|
|
203
|
+
desired: sourceTableDesiredResolution(syncConfig, source, mapping, eventById),
|
|
204
|
+
sourceCompatibleTables: resolution.compatibleTables,
|
|
205
|
+
newTableSourceMetadata: resolution.newTableValues.sourceMetadata
|
|
206
|
+
};
|
|
193
207
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
retainedDocIds.push(createDoc._id);
|
|
197
|
-
tables.push(sourceTable);
|
|
198
|
-
}
|
|
208
|
+
// Plan record reuse, membership changes, creation, and removal.
|
|
209
|
+
const plan = planSourceTableReconciliation(candidateDocs, context);
|
|
199
210
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
.
|
|
211
|
+
// Persist narrowing for incomplete snapshots only. Snapshot-complete docs keep stale
|
|
212
|
+
// coverage ids so compatible future configs can reuse already-snapshotted data.
|
|
213
|
+
// Narrowing occurs after removing a sync config, meaning we don't process those
|
|
214
|
+
// definitions anymore.
|
|
215
|
+
for (const update of plan.narrowingUpdates) {
|
|
216
|
+
await col.updateOne(
|
|
217
|
+
{ _id: update.id },
|
|
206
218
|
{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
$set: {
|
|
220
|
+
bucket_data_source_ids: update.memberships.bucketDataSourceIds,
|
|
221
|
+
parameter_lookup_source_ids: update.memberships.parameterLookupSourceIds,
|
|
222
|
+
event_definition_ids: update.memberships.eventDefinitionIds
|
|
223
|
+
}
|
|
210
224
|
},
|
|
211
225
|
{ session }
|
|
212
|
-
)
|
|
213
|
-
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Any desired membership not covered by an existing doc gets a new source table.
|
|
230
|
+
// That table snapshots only the uncovered memberships.
|
|
231
|
+
if (plan.newTableMemberships != null) {
|
|
232
|
+
const id = options.idGenerator ? (options.idGenerator() as bson.ObjectId) : new bson.ObjectId();
|
|
233
|
+
const { doc, table } = createNewSourceTable(id, plan.newTableMemberships, context);
|
|
234
|
+
|
|
235
|
+
await col.insertOne(doc, { session });
|
|
236
|
+
await this.db.initializeSourceRecordsCollection(this.replicationStreamId, doc._id, session);
|
|
237
|
+
plan.tables.push(table);
|
|
238
|
+
}
|
|
214
239
|
|
|
215
240
|
result = {
|
|
216
|
-
tables,
|
|
217
|
-
dropTables:
|
|
218
|
-
|
|
241
|
+
tables: plan.tables,
|
|
242
|
+
dropTables: plan.dropDocs.map((doc) =>
|
|
243
|
+
sourceTableFromDocument(doc, context.connectionTag, syncConfig, mapping, eventById)
|
|
219
244
|
)
|
|
220
245
|
};
|
|
221
246
|
});
|
|
222
247
|
|
|
223
|
-
for (const sourceTableId of initializeSourceRecordsFor) {
|
|
224
|
-
await this.db.initializeSourceRecordsCollection(this.group_id, sourceTableId);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
248
|
return result!;
|
|
228
249
|
}
|
|
229
250
|
|
|
230
|
-
private sourceTableFromDocument(
|
|
231
|
-
doc: SourceTableDocumentV3,
|
|
232
|
-
connectionTag: string,
|
|
233
|
-
syncRules: HydratedSyncConfig,
|
|
234
|
-
memberships?: MatchingSources
|
|
235
|
-
): storage.SourceTable {
|
|
236
|
-
const resolvedMemberships = memberships ?? this.sourceTableMembershipsFromDocument(doc, syncRules);
|
|
237
|
-
const table = new storage.SourceTable({
|
|
238
|
-
id: doc._id,
|
|
239
|
-
ref: {
|
|
240
|
-
connectionTag,
|
|
241
|
-
schema: doc.schema_name,
|
|
242
|
-
name: doc.table_name
|
|
243
|
-
},
|
|
244
|
-
objectId: doc.relation_id,
|
|
245
|
-
replicaIdColumns: doc.replica_id_columns2!.map(
|
|
246
|
-
(c) => ({ name: c.name, typeId: c.type_oid, type: c.type }) satisfies ColumnDescriptor
|
|
247
|
-
),
|
|
248
|
-
snapshotComplete: doc.snapshot_done ?? true,
|
|
249
|
-
bucketDataSources: resolvedMemberships.bucketDataSources,
|
|
250
|
-
parameterLookupSources: resolvedMemberships.parameterLookupSources
|
|
251
|
-
});
|
|
252
|
-
table.syncData = table.bucketDataSources.length > 0;
|
|
253
|
-
table.syncParameters = table.parameterLookupSources.length > 0;
|
|
254
|
-
table.syncEvent = syncRules.tableTriggersEvent(table.ref);
|
|
255
|
-
table.snapshotStatus =
|
|
256
|
-
doc.snapshot_status == null
|
|
257
|
-
? undefined
|
|
258
|
-
: {
|
|
259
|
-
lastKey: doc.snapshot_status.last_key?.buffer ?? null,
|
|
260
|
-
totalEstimatedCount: doc.snapshot_status.total_estimated_count,
|
|
261
|
-
replicatedCount: doc.snapshot_status.replicated_count
|
|
262
|
-
};
|
|
263
|
-
return table;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private sourceTableMembershipsFromDocument(
|
|
267
|
-
doc: SourceTableDocumentV3,
|
|
268
|
-
syncRules: HydratedSyncConfig
|
|
269
|
-
): MatchingSources {
|
|
270
|
-
const bucketDataSourceIds = new Set(doc.bucket_data_source_ids);
|
|
271
|
-
const parameterLookupSourceIds = new Set(doc.parameter_lookup_source_ids);
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
bucketDataSources: syncRules.bucketDataSources.filter((source) =>
|
|
275
|
-
bucketDataSourceIds.has(this.mapping.bucketSourceId(source))
|
|
276
|
-
),
|
|
277
|
-
parameterLookupSources: syncRules.bucketParameterLookupSources.filter((source) =>
|
|
278
|
-
parameterLookupSourceIds.has(this.mapping.parameterLookupId(source))
|
|
279
|
-
)
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
251
|
async getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null> {
|
|
284
252
|
const doc = (await this.db
|
|
285
|
-
.
|
|
253
|
+
.sourceTables(this.replicationStreamId)
|
|
286
254
|
.findOne({ _id: mongoTableId(table.id) }, { session: this.session })) as SourceTableDocumentV3 | null;
|
|
287
255
|
if (doc == null) {
|
|
288
256
|
return null;
|
|
289
257
|
}
|
|
290
258
|
|
|
291
|
-
return
|
|
259
|
+
return sourceTableFromDocument(
|
|
260
|
+
doc,
|
|
261
|
+
table.ref.connectionTag,
|
|
262
|
+
this.sync_rules,
|
|
263
|
+
this.mapping,
|
|
264
|
+
this.options.parsedSyncConfig.eventById
|
|
265
|
+
);
|
|
292
266
|
}
|
|
293
267
|
|
|
294
268
|
async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult> {
|
|
@@ -296,6 +270,8 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
296
270
|
|
|
297
271
|
await this.flush(options);
|
|
298
272
|
|
|
273
|
+
using _ = this.tracer.span('storage', 'commit');
|
|
274
|
+
|
|
299
275
|
const now = new Date();
|
|
300
276
|
|
|
301
277
|
await this.db.write_checkpoints.updateMany(
|
|
@@ -313,97 +289,156 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
313
289
|
}
|
|
314
290
|
);
|
|
315
291
|
|
|
316
|
-
const preUpdateDocument = await this.db.sync_rules.findOne(
|
|
292
|
+
const preUpdateDocument = (await this.db.sync_rules.findOne(
|
|
317
293
|
{
|
|
318
|
-
_id: this.
|
|
319
|
-
'sync_configs._id': this.
|
|
294
|
+
_id: this.replicationStreamId,
|
|
295
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
320
296
|
},
|
|
321
297
|
{
|
|
322
298
|
session: this.session,
|
|
323
299
|
projection: {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
$elemMatch: {
|
|
327
|
-
_id: this.syncConfigId
|
|
328
|
-
}
|
|
329
|
-
}
|
|
300
|
+
sync_configs: 1,
|
|
301
|
+
last_persisted_op: 1
|
|
330
302
|
}
|
|
331
303
|
}
|
|
332
|
-
);
|
|
304
|
+
)) as ReplicationStreamDocumentV3 | null;
|
|
333
305
|
|
|
334
|
-
const
|
|
335
|
-
|
|
306
|
+
const states =
|
|
307
|
+
preUpdateDocument?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
308
|
+
if (states.length == 0) {
|
|
336
309
|
throw new ReplicationAssertionError(
|
|
337
|
-
`Failed to update checkpoint - no matching sync_config for _id: ${this.
|
|
310
|
+
`Failed to update checkpoint - no matching sync_config for _id: ${this.replicationStreamId}/${this.syncConfigIds
|
|
311
|
+
.map((id) => id.toHexString())
|
|
312
|
+
.join(',')}`
|
|
338
313
|
);
|
|
339
314
|
}
|
|
315
|
+
// The replication job / MongoBucketBatch must be constructed with all replicating (PROCESSING / ACTIVE)
|
|
316
|
+
// sync configs in the stream, otherwise we'll get inconsistencies. Configs in other states (e.g. STOP)
|
|
317
|
+
// remain embedded in the document and are ignored here.
|
|
318
|
+
const missingSyncConfig = preUpdateDocument!.sync_configs.find(
|
|
319
|
+
(config) =>
|
|
320
|
+
[storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE].includes(config.state) &&
|
|
321
|
+
!this.syncConfigIds.some((id) => id.equals(config._id))
|
|
322
|
+
);
|
|
323
|
+
if (missingSyncConfig != null) {
|
|
324
|
+
throw new ReplicationAssertionError(`Replication job not configured for sync config ${missingSyncConfig._id}`);
|
|
325
|
+
}
|
|
340
326
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
327
|
+
// Effective head of the stream's op sequence.
|
|
328
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and this
|
|
329
|
+
// read uses the same session, so it covers all ops persisted by this batch.
|
|
330
|
+
const newCheckpoint =
|
|
331
|
+
preUpdateDocument?.last_persisted_op == null ? 0n : BigInt(preUpdateDocument.last_persisted_op);
|
|
332
|
+
|
|
333
|
+
let checkpointBlocked = false;
|
|
334
|
+
let checkpointCreated = false;
|
|
335
|
+
let checkpointLogState: unknown = null;
|
|
336
|
+
const unblockedConfigIds: bson.ObjectId[] = [];
|
|
337
|
+
|
|
338
|
+
for (const state of states) {
|
|
339
|
+
if (state.last_checkpoint != null && state.last_checkpoint > newCheckpoint) {
|
|
340
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and
|
|
341
|
+
// checkpoints are only ever created at that head, so a checkpoint past the head means the
|
|
342
|
+
// op sequence or the stored state is corrupt.
|
|
343
|
+
throw new ReplicationAssertionError(
|
|
344
|
+
`Invariant violation: sync config ${state._id} has last_checkpoint ${state.last_checkpoint} > stream head ${newCheckpoint}`
|
|
345
|
+
);
|
|
346
|
+
}
|
|
351
347
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
348
|
+
const canCheckpoint = canCheckpointState(lsn, {
|
|
349
|
+
snapshotDone: state.snapshot_done === true,
|
|
350
|
+
lastCheckpointLsn: state.last_checkpoint_lsn,
|
|
351
|
+
noCheckpointBefore: state.no_checkpoint_before
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (!canCheckpoint) {
|
|
355
|
+
checkpointBlocked = true;
|
|
356
|
+
// Log the first blocked config's state.
|
|
357
|
+
checkpointLogState ??= {
|
|
358
|
+
snapshot_done: state.snapshot_done,
|
|
359
|
+
last_checkpoint_lsn: state.last_checkpoint_lsn,
|
|
360
|
+
no_checkpoint_before: state.no_checkpoint_before
|
|
361
|
+
};
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
checkpointCreated ||= createEmptyCheckpoints || state.last_checkpoint !== newCheckpoint;
|
|
366
|
+
unblockedConfigIds.push(state._id);
|
|
363
367
|
}
|
|
364
368
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
369
|
+
// Every commit advances the stream's resume position: commit() flushes first, so all
|
|
370
|
+
// source changes up to this lsn have been persisted, even when checkpoints are blocked.
|
|
371
|
+
// In the future we could also advance this on flush, when the connector provides the
|
|
372
|
+
// current position (see setResumeLsn, which connectors may already call after flushing).
|
|
373
|
+
const resumeLsnUpdate = { resume_lsn: lsn };
|
|
374
|
+
|
|
375
|
+
if (unblockedConfigIds.length > 0) {
|
|
376
|
+
// All unblocked configs get the SAME new value, so we apply it with a single updateOne
|
|
377
|
+
// (single-document atomicity).
|
|
378
|
+
const updateSet: Record<string, any> = {
|
|
379
|
+
last_keepalive_ts: now,
|
|
380
|
+
last_fatal_error: null,
|
|
381
|
+
last_fatal_error_ts: null
|
|
382
|
+
};
|
|
383
|
+
// Only advance checkpoint fields when an actual (non-empty) checkpoint is created, matching
|
|
384
|
+
// the previous per-config / v1 behaviour.
|
|
385
|
+
if (checkpointCreated) {
|
|
386
|
+
updateSet['sync_configs.$[config].last_checkpoint'] = newCheckpoint;
|
|
387
|
+
updateSet['sync_configs.$[config].last_checkpoint_lsn'] = lsn;
|
|
388
|
+
updateSet['last_checkpoint_ts'] = now;
|
|
376
389
|
}
|
|
377
|
-
);
|
|
378
390
|
|
|
379
|
-
|
|
391
|
+
await this.db.sync_rules.updateOne(
|
|
392
|
+
{
|
|
393
|
+
_id: this.replicationStreamId,
|
|
394
|
+
'sync_configs._id': { $in: unblockedConfigIds }
|
|
395
|
+
},
|
|
396
|
+
{ $set: updateSet, $max: resumeLsnUpdate },
|
|
397
|
+
{
|
|
398
|
+
session: this.session,
|
|
399
|
+
arrayFilters: checkpointCreated ? [{ 'config._id': { $in: unblockedConfigIds } }] : undefined
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
} else {
|
|
403
|
+
// All selected configs are blocked - only update keepalive/error tracking and the
|
|
404
|
+
// resume position.
|
|
405
|
+
await this.db.sync_rules.updateOne(
|
|
406
|
+
{
|
|
407
|
+
_id: this.replicationStreamId
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
$set: {
|
|
411
|
+
last_keepalive_ts: now,
|
|
412
|
+
last_fatal_error: null,
|
|
413
|
+
last_fatal_error_ts: null
|
|
414
|
+
},
|
|
415
|
+
$max: resumeLsnUpdate
|
|
416
|
+
},
|
|
417
|
+
{ session: this.session }
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (checkpointBlocked) {
|
|
380
422
|
if (Date.now() - this.lastWaitingLogThrottledV3 > 5_000) {
|
|
381
423
|
this.logger.info(
|
|
382
|
-
`Waiting before creating checkpoint, currently at ${lsn}
|
|
383
|
-
{
|
|
384
|
-
snapshot_done: state.snapshot_done,
|
|
385
|
-
last_checkpoint_lsn: state.last_checkpoint_lsn,
|
|
386
|
-
no_checkpoint_before: state.no_checkpoint_before
|
|
387
|
-
}
|
|
388
|
-
)}`
|
|
424
|
+
`Waiting before creating checkpoint, currently at ${lsn}. Current state: ${JSON.stringify(checkpointLogState)}`
|
|
389
425
|
);
|
|
390
426
|
this.lastWaitingLogThrottledV3 = Date.now();
|
|
391
427
|
}
|
|
392
428
|
} else {
|
|
393
|
-
if (
|
|
394
|
-
this.logger.debug(`Created checkpoint at ${lsn}
|
|
429
|
+
if (checkpointCreated) {
|
|
430
|
+
this.logger.debug(`Created checkpoint at ${lsn}`);
|
|
395
431
|
}
|
|
396
432
|
await this.autoActivateV3(lsn);
|
|
433
|
+
// All configs are now checkpointed at newCheckpoint (the stream head).
|
|
434
|
+
await this.sourceRecordStore.postCommitCleanup(newCheckpoint, this.logger);
|
|
435
|
+
}
|
|
436
|
+
if (checkpointCreated) {
|
|
397
437
|
await this.db.notifyCheckpoint();
|
|
398
|
-
this.persisted_op = null;
|
|
399
|
-
this.last_checkpoint_lsn = lsn;
|
|
400
|
-
if (checkpointState.newLastCheckpoint != null) {
|
|
401
|
-
await this.sourceRecordStore.postCommitCleanup(checkpointState.newLastCheckpoint, this.logger);
|
|
402
|
-
}
|
|
403
438
|
}
|
|
404
439
|
return {
|
|
405
|
-
checkpointBlocked
|
|
406
|
-
checkpointCreated
|
|
440
|
+
checkpointBlocked,
|
|
441
|
+
checkpointCreated
|
|
407
442
|
};
|
|
408
443
|
}
|
|
409
444
|
|
|
@@ -412,17 +447,23 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
412
447
|
}
|
|
413
448
|
|
|
414
449
|
async setResumeLsn(lsn: string): Promise<void> {
|
|
450
|
+
using _ = this.tracer.span('storage', 'set_resume_lsn');
|
|
415
451
|
await this.db.sync_rules.updateOne(
|
|
416
452
|
{
|
|
417
|
-
_id: this.
|
|
418
|
-
'sync_configs._id': this.syncConfigId
|
|
453
|
+
_id: this.replicationStreamId
|
|
419
454
|
},
|
|
420
455
|
{
|
|
421
456
|
$set: {
|
|
422
|
-
|
|
457
|
+
resume_lsn: lsn
|
|
423
458
|
}
|
|
424
459
|
},
|
|
425
|
-
{
|
|
460
|
+
{
|
|
461
|
+
session: this.session,
|
|
462
|
+
// Losing occasional resume LSN is fine. That may mean reprocessing
|
|
463
|
+
// some source changes in some edge cases, which is not an issue since
|
|
464
|
+
// changes are processed in an idempotent way.
|
|
465
|
+
writeConcern: { w: 1 }
|
|
466
|
+
}
|
|
426
467
|
);
|
|
427
468
|
}
|
|
428
469
|
|
|
@@ -433,36 +474,43 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
433
474
|
|
|
434
475
|
const session = this.session;
|
|
435
476
|
let activated = false;
|
|
477
|
+
let needsFutureActivationCheck = true;
|
|
436
478
|
await session.withTransaction(async () => {
|
|
479
|
+
// Reset on transaction retries.
|
|
480
|
+
needsFutureActivationCheck = true;
|
|
481
|
+
activated = false;
|
|
482
|
+
|
|
437
483
|
const doc = await this.db.sync_rules.findOne(
|
|
438
484
|
{
|
|
439
|
-
_id: this.
|
|
440
|
-
'sync_configs._id': this.
|
|
485
|
+
_id: this.replicationStreamId,
|
|
486
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
441
487
|
},
|
|
442
488
|
{
|
|
443
489
|
session,
|
|
444
490
|
projection: {
|
|
445
491
|
state: 1,
|
|
446
|
-
sync_configs:
|
|
447
|
-
$elemMatch: {
|
|
448
|
-
_id: this.syncConfigId
|
|
449
|
-
}
|
|
450
|
-
}
|
|
492
|
+
sync_configs: 1
|
|
451
493
|
}
|
|
452
494
|
}
|
|
453
495
|
);
|
|
454
|
-
const
|
|
496
|
+
const states =
|
|
497
|
+
(doc as ReplicationStreamDocumentV3)?.sync_configs?.filter((config) =>
|
|
498
|
+
this.syncConfigIds.some((id) => id.equals(config._id))
|
|
499
|
+
) ?? [];
|
|
500
|
+
if (doc == null || states.length == 0) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const processingStates = states.filter((state) => state.state == storage.SyncRuleState.PROCESSING);
|
|
455
505
|
if (
|
|
456
|
-
doc &&
|
|
457
506
|
doc.state == storage.SyncRuleState.PROCESSING &&
|
|
458
|
-
|
|
459
|
-
state.snapshot_done &&
|
|
460
|
-
state.last_checkpoint != null
|
|
507
|
+
processingStates.length == states.length &&
|
|
508
|
+
states.every((state) => state.snapshot_done && state.last_checkpoint != null)
|
|
461
509
|
) {
|
|
462
510
|
await this.db.sync_rules.updateOne(
|
|
463
511
|
{
|
|
464
|
-
_id: this.
|
|
465
|
-
'sync_configs._id': this.
|
|
512
|
+
_id: this.replicationStreamId,
|
|
513
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
466
514
|
},
|
|
467
515
|
{
|
|
468
516
|
$set: {
|
|
@@ -472,35 +520,64 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
472
520
|
},
|
|
473
521
|
{
|
|
474
522
|
session,
|
|
475
|
-
arrayFilters: [{ 'config._id': this.
|
|
523
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
476
524
|
}
|
|
477
525
|
);
|
|
478
526
|
|
|
479
527
|
await this.db.sync_rules.updateMany(
|
|
480
528
|
{
|
|
481
|
-
_id: { $ne: this.
|
|
529
|
+
_id: { $ne: this.replicationStreamId },
|
|
482
530
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
483
531
|
},
|
|
484
|
-
|
|
532
|
+
stopReplicationStreamPipeline(),
|
|
485
533
|
{ session }
|
|
486
534
|
);
|
|
487
535
|
activated = true;
|
|
488
|
-
} else if (
|
|
489
|
-
|
|
536
|
+
} else if (
|
|
537
|
+
doc.state == storage.SyncRuleState.ACTIVE &&
|
|
538
|
+
processingStates.length > 0 &&
|
|
539
|
+
processingStates.every((state) => state.snapshot_done && state.last_checkpoint != null)
|
|
540
|
+
) {
|
|
541
|
+
await this.db.sync_rules.updateOne(
|
|
542
|
+
{
|
|
543
|
+
_id: this.replicationStreamId,
|
|
544
|
+
'sync_configs._id': { $in: processingStates.map((state) => state._id) }
|
|
545
|
+
},
|
|
546
|
+
{
|
|
547
|
+
$set: {
|
|
548
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.STOP,
|
|
549
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.ACTIVE
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
session,
|
|
554
|
+
arrayFilters: [
|
|
555
|
+
{ 'activeConfig.state': storage.SyncRuleState.ACTIVE },
|
|
556
|
+
{ 'processingConfig._id': { $in: processingStates.map((state) => state._id) } }
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
);
|
|
560
|
+
activated = true;
|
|
561
|
+
} else if (doc.state != storage.SyncRuleState.PROCESSING && doc.state != storage.SyncRuleState.ACTIVE) {
|
|
562
|
+
needsFutureActivationCheck = false;
|
|
563
|
+
} else if (doc.state == storage.SyncRuleState.ACTIVE && processingStates.length == 0) {
|
|
564
|
+
needsFutureActivationCheck = false;
|
|
490
565
|
}
|
|
491
566
|
});
|
|
492
567
|
if (activated) {
|
|
493
568
|
this.logger.info(`Activated new replication stream at ${lsn}`);
|
|
494
569
|
await this.db.notifyCheckpoint();
|
|
495
570
|
this.needsActivationV3 = false;
|
|
571
|
+
} else if (!needsFutureActivationCheck) {
|
|
572
|
+
this.needsActivationV3 = false;
|
|
496
573
|
}
|
|
497
574
|
}
|
|
498
575
|
|
|
499
576
|
async markAllSnapshotDone(no_checkpoint_before_lsn: string): Promise<void> {
|
|
500
577
|
await this.db.sync_rules.updateOne(
|
|
501
578
|
{
|
|
502
|
-
_id: this.
|
|
503
|
-
'sync_configs._id': this.
|
|
579
|
+
_id: this.replicationStreamId,
|
|
580
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
504
581
|
},
|
|
505
582
|
{
|
|
506
583
|
$set: {
|
|
@@ -513,7 +590,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
513
590
|
},
|
|
514
591
|
{
|
|
515
592
|
session: this.session,
|
|
516
|
-
arrayFilters: [{ 'config._id': this.
|
|
593
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
517
594
|
}
|
|
518
595
|
);
|
|
519
596
|
}
|
|
@@ -521,16 +598,18 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
521
598
|
async markSnapshotDone(no_checkpoint_before_lsn: string, options?: { throwOnConflict?: boolean }): Promise<void> {
|
|
522
599
|
await this.withTransaction(async () => {
|
|
523
600
|
// Protect against race conditions
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
601
|
+
const blockingTables = await this.db
|
|
602
|
+
.sourceTables(this.replicationStreamId)
|
|
603
|
+
.find(this.snapshotBlockingSourceTablesFilter(), {
|
|
604
|
+
session: this.session,
|
|
605
|
+
projection: { schema_name: 1, table_name: 1 }
|
|
606
|
+
})
|
|
607
|
+
.toArray();
|
|
608
|
+
|
|
609
|
+
if (blockingTables.length > 0) {
|
|
531
610
|
if (options?.throwOnConflict ?? true) {
|
|
532
611
|
throw new ReplicationAssertionError(
|
|
533
|
-
`Cannot mark snapshot done while
|
|
612
|
+
`Cannot mark snapshot done while source tables still require snapshotting. Tables: ${blockingTables.map((t) => `${t.schema_name}.${t.table_name}`).join(', ')}`
|
|
534
613
|
);
|
|
535
614
|
} else {
|
|
536
615
|
return;
|
|
@@ -541,11 +620,16 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
541
620
|
});
|
|
542
621
|
}
|
|
543
622
|
|
|
544
|
-
async markTableSnapshotRequired(
|
|
623
|
+
async markTableSnapshotRequired(table: storage.SourceTable): Promise<void> {
|
|
624
|
+
const syncConfigIds = this.relevantSyncConfigIds(table);
|
|
625
|
+
if (syncConfigIds.length == 0) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
545
629
|
await this.db.sync_rules.updateOne(
|
|
546
630
|
{
|
|
547
|
-
_id: this.
|
|
548
|
-
'sync_configs._id':
|
|
631
|
+
_id: this.replicationStreamId,
|
|
632
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
549
633
|
},
|
|
550
634
|
{
|
|
551
635
|
$set: {
|
|
@@ -554,7 +638,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
554
638
|
},
|
|
555
639
|
{
|
|
556
640
|
session: this.session,
|
|
557
|
-
arrayFilters: [{ 'config._id':
|
|
641
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
558
642
|
}
|
|
559
643
|
);
|
|
560
644
|
}
|
|
@@ -565,9 +649,10 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
565
649
|
): Promise<storage.SourceTable[]> {
|
|
566
650
|
const session = this.session;
|
|
567
651
|
const ids = tables.map((table) => mongoTableId(table.id));
|
|
652
|
+
const syncConfigIds = this.relevantSyncConfigIdsForTables(tables);
|
|
568
653
|
|
|
569
654
|
await this.withTransaction(async () => {
|
|
570
|
-
await this.db.
|
|
655
|
+
await this.db.sourceTables(this.replicationStreamId).updateMany(
|
|
571
656
|
{ _id: { $in: ids } },
|
|
572
657
|
{
|
|
573
658
|
$set: {
|
|
@@ -580,11 +665,11 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
580
665
|
{ session }
|
|
581
666
|
);
|
|
582
667
|
|
|
583
|
-
if (no_checkpoint_before_lsn != null) {
|
|
668
|
+
if (no_checkpoint_before_lsn != null && syncConfigIds.length > 0) {
|
|
584
669
|
await this.db.sync_rules.updateOne(
|
|
585
670
|
{
|
|
586
|
-
_id: this.
|
|
587
|
-
'sync_configs._id':
|
|
671
|
+
_id: this.replicationStreamId,
|
|
672
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
588
673
|
},
|
|
589
674
|
{
|
|
590
675
|
$set: {
|
|
@@ -596,7 +681,8 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
596
681
|
},
|
|
597
682
|
{
|
|
598
683
|
session: this.session,
|
|
599
|
-
|
|
684
|
+
// Only set for sync configs that use this table
|
|
685
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
600
686
|
}
|
|
601
687
|
);
|
|
602
688
|
}
|
|
@@ -607,4 +693,96 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
607
693
|
return copy;
|
|
608
694
|
});
|
|
609
695
|
}
|
|
696
|
+
|
|
697
|
+
protected override async batchCreateCustomWriteCheckpoints(
|
|
698
|
+
session: lib_mongo.mongo.ClientSession,
|
|
699
|
+
opId: InternalOpId
|
|
700
|
+
): Promise<void> {
|
|
701
|
+
if (this.write_checkpoint_batch.length == 0) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const checkpointsByEvent = Map.groupBy(this.write_checkpoint_batch, (checkpoint) =>
|
|
706
|
+
this.validateCustomCheckpointEventId(checkpoint.event_id)
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
for (const [eventId, checkpoints] of checkpointsByEvent) {
|
|
710
|
+
await this.batchCreateEventCustomWriteCheckpoints(session, opId, eventId, checkpoints);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
protected override async prepareCustomWriteCheckpoints(): Promise<void> {
|
|
715
|
+
// Most events never produce custom checkpoints, so create collections lazily
|
|
716
|
+
// only for the event ids present in this batch. This hook runs before the
|
|
717
|
+
// replication transaction because MongoDB cannot create indexes within it.
|
|
718
|
+
const eventIds = new Set(
|
|
719
|
+
this.write_checkpoint_batch.map((checkpoint) => this.validateCustomCheckpointEventId(checkpoint.event_id))
|
|
720
|
+
);
|
|
721
|
+
for (const eventId of eventIds) {
|
|
722
|
+
if (this.initializedCustomCheckpointEventIds.has(eventId)) {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
await this.db.initializeCustomCheckpointRequestsCollection({
|
|
726
|
+
replicationStreamId: this.replicationStreamId,
|
|
727
|
+
eventId
|
|
728
|
+
});
|
|
729
|
+
this.initializedCustomCheckpointEventIds.add(eventId);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private validateCustomCheckpointEventId(eventId: EventDefinitionId | undefined): EventDefinitionId {
|
|
734
|
+
if (eventId == null) {
|
|
735
|
+
throw new ServiceAssertionError('V3 custom checkpoints require an event definition id');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (!this.knownEventIds.has(eventId)) {
|
|
739
|
+
throw new ServiceAssertionError(`Unknown custom checkpoint event definition ${eventId}`);
|
|
740
|
+
}
|
|
741
|
+
return eventId;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private async batchCreateEventCustomWriteCheckpoints(
|
|
745
|
+
session: lib_mongo.mongo.ClientSession,
|
|
746
|
+
opId: InternalOpId,
|
|
747
|
+
eventId: EventDefinitionId,
|
|
748
|
+
checkpoints: storage.CustomWriteCheckpointOptions[]
|
|
749
|
+
): Promise<void> {
|
|
750
|
+
await this.db
|
|
751
|
+
.customCheckpointRequests({
|
|
752
|
+
eventId,
|
|
753
|
+
replicationStreamId: this.replicationStreamId
|
|
754
|
+
})
|
|
755
|
+
.bulkWrite(
|
|
756
|
+
checkpoints.map((checkpointOptions) => {
|
|
757
|
+
const set: Partial<CustomCheckpointRequestDocumentV3> = {
|
|
758
|
+
user_id: checkpointOptions.user_id,
|
|
759
|
+
checkpoint: checkpointOptions.checkpoint,
|
|
760
|
+
op_id: opId
|
|
761
|
+
};
|
|
762
|
+
if (checkpointOptions.checkpoint_requested_at != null) {
|
|
763
|
+
set.checkpoint_requested_at = checkpointOptions.checkpoint_requested_at;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return {
|
|
767
|
+
updateOne: {
|
|
768
|
+
filter: {
|
|
769
|
+
user_id: checkpointOptions.user_id
|
|
770
|
+
},
|
|
771
|
+
update: {
|
|
772
|
+
$set: set,
|
|
773
|
+
...(checkpointOptions.checkpoint_requested_at == null
|
|
774
|
+
? {
|
|
775
|
+
$unset: {
|
|
776
|
+
checkpoint_requested_at: 1
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
: {})
|
|
780
|
+
},
|
|
781
|
+
upsert: true
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
}),
|
|
785
|
+
{ session }
|
|
786
|
+
);
|
|
787
|
+
}
|
|
610
788
|
}
|