@powersync/service-module-mongodb-storage 0.17.0 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -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 +123 -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 +154 -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 +1077 -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
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import { MongoSyncBucketStorageV3 } from '@module/storage/implementation/v3/MongoSyncBucketStorageV3.js';
|
|
2
|
+
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
3
|
+
import { test_utils } from '@powersync/service-core-tests';
|
|
4
|
+
import * as bson from 'bson';
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
import { VersionedPowerSyncMongoV3 } from '../../src/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
|
|
7
|
+
import { ReplicationStreamDocumentV3 } from '../../src/storage/implementation/v3/models.js';
|
|
8
|
+
import { INITIALIZED_MONGO_STORAGE_FACTORY } from './util.js';
|
|
9
|
+
|
|
10
|
+
function sourceDescriptor(name: string, options: { objectId?: string } = {}): storage.SourceEntityDescriptor {
|
|
11
|
+
return {
|
|
12
|
+
connectionTag: storage.SourceTable.DEFAULT_TAG,
|
|
13
|
+
objectId: options.objectId ?? name,
|
|
14
|
+
schema: 'public',
|
|
15
|
+
name,
|
|
16
|
+
replicaIdColumns: [
|
|
17
|
+
{
|
|
18
|
+
name: 'id',
|
|
19
|
+
type: 'VARCHAR',
|
|
20
|
+
typeId: 25
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function objectIdGenerator(id: string) {
|
|
27
|
+
let used = false;
|
|
28
|
+
return () => {
|
|
29
|
+
if (used) {
|
|
30
|
+
throw new Error(`Can only generate a single id using ${id}`);
|
|
31
|
+
}
|
|
32
|
+
used = true;
|
|
33
|
+
return new bson.ObjectId(id);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function collectionExists(db: VersionedPowerSyncMongoV3, name: string) {
|
|
38
|
+
return await db.db.listCollections({ name }, { nameOnly: true }).hasNext();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function cleanupStoppedSyncConfigs(bucketStorage: MongoSyncBucketStorageV3) {
|
|
42
|
+
return bucketStorage.cleanupStoppedSyncConfigs({
|
|
43
|
+
defaultSchema: 'public',
|
|
44
|
+
sourceConnectionTag: storage.SourceTable.DEFAULT_TAG
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('cleanupStoppedSyncConfigs - mongodb', () => {
|
|
49
|
+
test('cleans up unused stopped sync config storage', async () => {
|
|
50
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
51
|
+
|
|
52
|
+
const first = await factory.updateSyncRules(
|
|
53
|
+
updateSyncRulesFromYaml(
|
|
54
|
+
`
|
|
55
|
+
config:
|
|
56
|
+
edition: 3
|
|
57
|
+
|
|
58
|
+
streams:
|
|
59
|
+
by_project:
|
|
60
|
+
query: |
|
|
61
|
+
SELECT id, project FROM "Scene"
|
|
62
|
+
WHERE project IN (
|
|
63
|
+
SELECT project FROM "ProjectInvitation"
|
|
64
|
+
WHERE project = subscription.parameter('project')
|
|
65
|
+
)
|
|
66
|
+
`,
|
|
67
|
+
{ storageVersion: 3 }
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
|
|
71
|
+
const db = firstStorage.db as VersionedPowerSyncMongoV3;
|
|
72
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
73
|
+
|
|
74
|
+
const sceneTable = (
|
|
75
|
+
await firstWriter.resolveTables({
|
|
76
|
+
connection_id: 1,
|
|
77
|
+
source: sourceDescriptor('Scene', { objectId: 'scene-relation' }),
|
|
78
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38350')
|
|
79
|
+
})
|
|
80
|
+
).tables[0];
|
|
81
|
+
const invitationTable = (
|
|
82
|
+
await firstWriter.resolveTables({
|
|
83
|
+
connection_id: 1,
|
|
84
|
+
source: sourceDescriptor('ProjectInvitation', { objectId: 'project-invitation-relation' }),
|
|
85
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38351')
|
|
86
|
+
})
|
|
87
|
+
).tables[0];
|
|
88
|
+
await firstWriter.save({
|
|
89
|
+
sourceTable: sceneTable,
|
|
90
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
91
|
+
after: {
|
|
92
|
+
id: 'scene-1',
|
|
93
|
+
project: 'project-1'
|
|
94
|
+
},
|
|
95
|
+
afterReplicaId: test_utils.rid('scene-1')
|
|
96
|
+
});
|
|
97
|
+
await firstWriter.save({
|
|
98
|
+
sourceTable: invitationTable,
|
|
99
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
100
|
+
after: {
|
|
101
|
+
id: 'invitation-1',
|
|
102
|
+
project: 'project-1'
|
|
103
|
+
},
|
|
104
|
+
afterReplicaId: test_utils.rid('invitation-1')
|
|
105
|
+
});
|
|
106
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
107
|
+
await firstWriter.commit('1/1');
|
|
108
|
+
|
|
109
|
+
const stoppedMapping = first.syncConfigContent[0].mapping;
|
|
110
|
+
const stoppedBucketDefinitionId = stoppedMapping.allBucketDefinitionIds()[0];
|
|
111
|
+
const stoppedParameterIndexId = stoppedMapping.allParameterIndexIds()[0];
|
|
112
|
+
expect(stoppedBucketDefinitionId).toBeDefined();
|
|
113
|
+
expect(stoppedParameterIndexId).toBeDefined();
|
|
114
|
+
const bucketDataCollection = db.bucketData(first.replicationStreamId, stoppedBucketDefinitionId!).collectionName;
|
|
115
|
+
const parameterIndexCollection = db.parameterIndex(
|
|
116
|
+
first.replicationStreamId,
|
|
117
|
+
stoppedParameterIndexId!
|
|
118
|
+
).collectionName;
|
|
119
|
+
const sceneRecordsCollection = db.sourceRecords(
|
|
120
|
+
first.replicationStreamId,
|
|
121
|
+
sceneTable.id as bson.ObjectId
|
|
122
|
+
).collectionName;
|
|
123
|
+
const invitationRecordsCollection = db.sourceRecords(
|
|
124
|
+
first.replicationStreamId,
|
|
125
|
+
invitationTable.id as bson.ObjectId
|
|
126
|
+
).collectionName;
|
|
127
|
+
expect(await collectionExists(db, bucketDataCollection)).toBe(true);
|
|
128
|
+
expect(await collectionExists(db, parameterIndexCollection)).toBe(true);
|
|
129
|
+
expect(await collectionExists(db, sceneRecordsCollection)).toBe(true);
|
|
130
|
+
expect(await collectionExists(db, invitationRecordsCollection)).toBe(true);
|
|
131
|
+
|
|
132
|
+
const second = await factory.updateSyncRules(
|
|
133
|
+
updateSyncRulesFromYaml(
|
|
134
|
+
`
|
|
135
|
+
config:
|
|
136
|
+
edition: 3
|
|
137
|
+
|
|
138
|
+
streams:
|
|
139
|
+
by_owner:
|
|
140
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
141
|
+
`,
|
|
142
|
+
{ storageVersion: 3 }
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
expect(second.replicationStreamId).toBe(first.replicationStreamId);
|
|
146
|
+
const replicatingStreams = await factory.getReplicatingReplicationStreams();
|
|
147
|
+
const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
|
|
148
|
+
await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
149
|
+
await secondWriter.markAllSnapshotDone('2/1');
|
|
150
|
+
await secondWriter.commit('2/1');
|
|
151
|
+
|
|
152
|
+
const result = await cleanupStoppedSyncConfigs(
|
|
153
|
+
(await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
expect(result).toEqual({
|
|
157
|
+
stoppedSyncConfigsRemoved: 1,
|
|
158
|
+
bucketDataCollectionsDropped: 1,
|
|
159
|
+
parameterIndexCollectionsDropped: 1,
|
|
160
|
+
bucketStateDocumentsDeleted: 1,
|
|
161
|
+
sourceRecordCollectionsDropped: 2,
|
|
162
|
+
sourceTablesUpdated: 0,
|
|
163
|
+
sourceTablesDeleted: 2
|
|
164
|
+
});
|
|
165
|
+
expect(await collectionExists(db, bucketDataCollection)).toBe(false);
|
|
166
|
+
expect(await collectionExists(db, parameterIndexCollection)).toBe(false);
|
|
167
|
+
expect(await collectionExists(db, sceneRecordsCollection)).toBe(false);
|
|
168
|
+
expect(await collectionExists(db, invitationRecordsCollection)).toBe(false);
|
|
169
|
+
expect(
|
|
170
|
+
await db.sourceTables(first.replicationStreamId).countDocuments({ _id: sceneTable.id as bson.ObjectId })
|
|
171
|
+
).toBe(0);
|
|
172
|
+
expect(
|
|
173
|
+
await db.sourceTables(first.replicationStreamId).countDocuments({ _id: invitationTable.id as bson.ObjectId })
|
|
174
|
+
).toBe(0);
|
|
175
|
+
|
|
176
|
+
const streamDoc = (await db.sync_rules.findOne({ _id: first.replicationStreamId })) as ReplicationStreamDocumentV3;
|
|
177
|
+
expect(streamDoc.sync_configs.map((config) => config.state)).toEqual([storage.SyncRuleState.ACTIVE]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('keeps source table membership still used by a live sync config', async () => {
|
|
181
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
182
|
+
|
|
183
|
+
const first = await factory.updateSyncRules(
|
|
184
|
+
updateSyncRulesFromYaml(
|
|
185
|
+
`
|
|
186
|
+
config:
|
|
187
|
+
edition: 3
|
|
188
|
+
|
|
189
|
+
streams:
|
|
190
|
+
by_owner:
|
|
191
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
192
|
+
by_project:
|
|
193
|
+
query: SELECT * FROM todos WHERE project_id = subscription.parameter('project_id')
|
|
194
|
+
`,
|
|
195
|
+
{ storageVersion: 3 }
|
|
196
|
+
)
|
|
197
|
+
);
|
|
198
|
+
const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
|
|
199
|
+
const db = firstStorage.db as VersionedPowerSyncMongoV3;
|
|
200
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
201
|
+
const resolved = await firstWriter.resolveTables({
|
|
202
|
+
connection_id: 1,
|
|
203
|
+
source: sourceDescriptor('todos', { objectId: 'todos-relation' }),
|
|
204
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38352')
|
|
205
|
+
});
|
|
206
|
+
await firstWriter.save({
|
|
207
|
+
sourceTable: resolved.tables[0],
|
|
208
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
209
|
+
after: {
|
|
210
|
+
id: 'todo-1',
|
|
211
|
+
owner_id: 'user-1',
|
|
212
|
+
project_id: 'project-1'
|
|
213
|
+
},
|
|
214
|
+
afterReplicaId: test_utils.rid('todo-1')
|
|
215
|
+
});
|
|
216
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
217
|
+
await firstWriter.commit('1/1');
|
|
218
|
+
|
|
219
|
+
const sourceTableId = resolved.tables[0].id as bson.ObjectId;
|
|
220
|
+
const sourceRecordsCollection = db.sourceRecords(first.replicationStreamId, sourceTableId).collectionName;
|
|
221
|
+
const [ownerDefinitionId, projectDefinitionId] = first.syncConfigContent[0].mapping.allBucketDefinitionIds();
|
|
222
|
+
const ownerBucketDataCollection = db.bucketData(first.replicationStreamId, ownerDefinitionId).collectionName;
|
|
223
|
+
expect(await collectionExists(db, ownerBucketDataCollection)).toBe(true);
|
|
224
|
+
expect(
|
|
225
|
+
(await db.sourceTables(first.replicationStreamId).findOne({ _id: sourceTableId }))?.bucket_data_source_ids
|
|
226
|
+
).toEqual([ownerDefinitionId, projectDefinitionId]);
|
|
227
|
+
|
|
228
|
+
const second = await factory.updateSyncRules(
|
|
229
|
+
updateSyncRulesFromYaml(
|
|
230
|
+
`
|
|
231
|
+
config:
|
|
232
|
+
edition: 3
|
|
233
|
+
|
|
234
|
+
streams:
|
|
235
|
+
by_project:
|
|
236
|
+
query: SELECT * FROM todos WHERE project_id = subscription.parameter('project_id')
|
|
237
|
+
`,
|
|
238
|
+
{ storageVersion: 3 }
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
expect(second.replicationStreamId).toBe(first.replicationStreamId);
|
|
242
|
+
const secondDefinitionId = second.syncConfigContent[1].mapping.allBucketDefinitionIds()[0];
|
|
243
|
+
expect(secondDefinitionId).toBe(projectDefinitionId);
|
|
244
|
+
|
|
245
|
+
const replicatingStreams = await factory.getReplicatingReplicationStreams();
|
|
246
|
+
const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
|
|
247
|
+
await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
248
|
+
await secondWriter.markAllSnapshotDone('2/1');
|
|
249
|
+
await secondWriter.commit('2/1');
|
|
250
|
+
|
|
251
|
+
const result = await cleanupStoppedSyncConfigs(
|
|
252
|
+
(await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
expect(result).toEqual({
|
|
256
|
+
stoppedSyncConfigsRemoved: 1,
|
|
257
|
+
bucketDataCollectionsDropped: 1,
|
|
258
|
+
parameterIndexCollectionsDropped: 0,
|
|
259
|
+
bucketStateDocumentsDeleted: 1,
|
|
260
|
+
sourceRecordCollectionsDropped: 0,
|
|
261
|
+
sourceTablesUpdated: 1,
|
|
262
|
+
sourceTablesDeleted: 0
|
|
263
|
+
});
|
|
264
|
+
const sourceTable = await db.sourceTables(first.replicationStreamId).findOne({ _id: sourceTableId });
|
|
265
|
+
expect(sourceTable?.bucket_data_source_ids).toEqual([secondDefinitionId]);
|
|
266
|
+
expect(await collectionExists(db, ownerBucketDataCollection)).toBe(false);
|
|
267
|
+
expect(await collectionExists(db, sourceRecordsCollection)).toBe(true);
|
|
268
|
+
|
|
269
|
+
const activeStorage = (await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3;
|
|
270
|
+
await using activeWriter = await activeStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
271
|
+
const activeTable = (
|
|
272
|
+
await activeWriter.resolveTables({
|
|
273
|
+
connection_id: 1,
|
|
274
|
+
source: sourceDescriptor('todos', { objectId: 'todos-relation' }),
|
|
275
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38354')
|
|
276
|
+
})
|
|
277
|
+
).tables[0];
|
|
278
|
+
await activeWriter.save({
|
|
279
|
+
sourceTable: activeTable,
|
|
280
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
281
|
+
before: {
|
|
282
|
+
id: 'todo-1'
|
|
283
|
+
},
|
|
284
|
+
after: {
|
|
285
|
+
id: 'todo-1',
|
|
286
|
+
owner_id: 'user-1',
|
|
287
|
+
project_id: 'project-2'
|
|
288
|
+
},
|
|
289
|
+
beforeReplicaId: test_utils.rid('todo-1'),
|
|
290
|
+
afterReplicaId: test_utils.rid('todo-1')
|
|
291
|
+
});
|
|
292
|
+
await activeWriter.commit('3/1');
|
|
293
|
+
|
|
294
|
+
expect(await collectionExists(db, ownerBucketDataCollection)).toBe(false);
|
|
295
|
+
const sourceRecord = await db.sourceRecords(first.replicationStreamId, sourceTableId).findOne({
|
|
296
|
+
_id: test_utils.rid('todo-1') as any
|
|
297
|
+
});
|
|
298
|
+
expect(sourceRecord?.buckets.map((bucket) => bucket.def)).toEqual([secondDefinitionId]);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('drops current_data when a table becomes event-only but is kept by a live event', async () => {
|
|
302
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
303
|
+
|
|
304
|
+
// The first config syncs `todos` data and also fires events for it.
|
|
305
|
+
const first = await factory.updateSyncRules(
|
|
306
|
+
updateSyncRulesFromYaml(
|
|
307
|
+
`
|
|
308
|
+
config:
|
|
309
|
+
edition: 3
|
|
310
|
+
|
|
311
|
+
streams:
|
|
312
|
+
by_owner:
|
|
313
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
314
|
+
|
|
315
|
+
event_definitions:
|
|
316
|
+
audit:
|
|
317
|
+
payloads:
|
|
318
|
+
- SELECT id, owner_id FROM todos
|
|
319
|
+
`,
|
|
320
|
+
{ storageVersion: 3 }
|
|
321
|
+
)
|
|
322
|
+
);
|
|
323
|
+
const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
|
|
324
|
+
const db = firstStorage.db as VersionedPowerSyncMongoV3;
|
|
325
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
326
|
+
const resolved = await firstWriter.resolveTables({
|
|
327
|
+
connection_id: 1,
|
|
328
|
+
source: sourceDescriptor('todos', { objectId: 'todos-relation' }),
|
|
329
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38370')
|
|
330
|
+
});
|
|
331
|
+
await firstWriter.save({
|
|
332
|
+
sourceTable: resolved.tables[0],
|
|
333
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
334
|
+
after: {
|
|
335
|
+
id: 'todo-1',
|
|
336
|
+
owner_id: 'user-1'
|
|
337
|
+
},
|
|
338
|
+
afterReplicaId: test_utils.rid('todo-1')
|
|
339
|
+
});
|
|
340
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
341
|
+
await firstWriter.commit('1/1');
|
|
342
|
+
|
|
343
|
+
const sourceTableId = resolved.tables[0].id as bson.ObjectId;
|
|
344
|
+
const sourceRecordsCollection = db.sourceRecords(first.replicationStreamId, sourceTableId).collectionName;
|
|
345
|
+
const ownerDefinitionId = first.syncConfigContent[0].mapping.allBucketDefinitionIds()[0];
|
|
346
|
+
expect(
|
|
347
|
+
(await db.sourceTables(first.replicationStreamId).findOne({ _id: sourceTableId }))?.bucket_data_source_ids
|
|
348
|
+
).toEqual([ownerDefinitionId]);
|
|
349
|
+
// The prior snapshot is persisted in current_data.
|
|
350
|
+
expect(await collectionExists(db, sourceRecordsCollection)).toBe(true);
|
|
351
|
+
|
|
352
|
+
// The second (active) config drops the `todos` data stream but keeps the audit event, so the
|
|
353
|
+
// `todos` source table becomes event-only while staying alive for the live event.
|
|
354
|
+
const second = await factory.updateSyncRules(
|
|
355
|
+
updateSyncRulesFromYaml(
|
|
356
|
+
`
|
|
357
|
+
config:
|
|
358
|
+
edition: 3
|
|
359
|
+
|
|
360
|
+
streams:
|
|
361
|
+
by_id:
|
|
362
|
+
query: SELECT * FROM other WHERE id = subscription.parameter('id')
|
|
363
|
+
|
|
364
|
+
event_definitions:
|
|
365
|
+
audit:
|
|
366
|
+
payloads:
|
|
367
|
+
- SELECT id, owner_id FROM todos
|
|
368
|
+
`,
|
|
369
|
+
{ storageVersion: 3 }
|
|
370
|
+
)
|
|
371
|
+
);
|
|
372
|
+
expect(second.replicationStreamId).toBe(first.replicationStreamId);
|
|
373
|
+
|
|
374
|
+
const replicatingStreams = await factory.getReplicatingReplicationStreams();
|
|
375
|
+
const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
|
|
376
|
+
await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
377
|
+
await secondWriter.markAllSnapshotDone('2/1');
|
|
378
|
+
await secondWriter.commit('2/1');
|
|
379
|
+
|
|
380
|
+
const result = await cleanupStoppedSyncConfigs(
|
|
381
|
+
(await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
expect(result).toEqual({
|
|
385
|
+
stoppedSyncConfigsRemoved: 1,
|
|
386
|
+
bucketDataCollectionsDropped: 1,
|
|
387
|
+
parameterIndexCollectionsDropped: 0,
|
|
388
|
+
bucketStateDocumentsDeleted: 1,
|
|
389
|
+
sourceRecordCollectionsDropped: 1,
|
|
390
|
+
sourceTablesUpdated: 1,
|
|
391
|
+
sourceTablesDeleted: 0
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// The source table row survives (the live event still needs it), narrowed to empty
|
|
395
|
+
// memberships, but its now-unused current_data collection is dropped.
|
|
396
|
+
const sourceTable = await db.sourceTables(first.replicationStreamId).findOne({ _id: sourceTableId });
|
|
397
|
+
expect(sourceTable).not.toBeNull();
|
|
398
|
+
expect(sourceTable?.bucket_data_source_ids).toEqual([]);
|
|
399
|
+
expect(sourceTable?.parameter_lookup_source_ids).toEqual([]);
|
|
400
|
+
expect(await collectionExists(db, sourceRecordsCollection)).toBe(false);
|
|
401
|
+
|
|
402
|
+
const streamDoc = (await db.sync_rules.findOne({ _id: first.replicationStreamId })) as ReplicationStreamDocumentV3;
|
|
403
|
+
expect(streamDoc.sync_configs.map((config) => config.state)).toEqual([storage.SyncRuleState.ACTIVE]);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('cleans up event-only source tables no longer triggered by a live sync config', async () => {
|
|
407
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
408
|
+
|
|
409
|
+
// The first config syncs `todos` and additionally fires events for `audit_log`. The
|
|
410
|
+
// `audit_log` table is referenced only by the event trigger, so its source table carries
|
|
411
|
+
// empty membership arrays (an event-only table).
|
|
412
|
+
const first = await factory.updateSyncRules(
|
|
413
|
+
updateSyncRulesFromYaml(
|
|
414
|
+
`
|
|
415
|
+
config:
|
|
416
|
+
edition: 3
|
|
417
|
+
|
|
418
|
+
streams:
|
|
419
|
+
by_owner:
|
|
420
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
421
|
+
|
|
422
|
+
event_definitions:
|
|
423
|
+
audit:
|
|
424
|
+
payloads:
|
|
425
|
+
- SELECT id FROM audit_log
|
|
426
|
+
`,
|
|
427
|
+
{ storageVersion: 3 }
|
|
428
|
+
)
|
|
429
|
+
);
|
|
430
|
+
const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
|
|
431
|
+
const db = firstStorage.db as VersionedPowerSyncMongoV3;
|
|
432
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
433
|
+
|
|
434
|
+
const todosTable = (
|
|
435
|
+
await firstWriter.resolveTables({
|
|
436
|
+
connection_id: 1,
|
|
437
|
+
source: sourceDescriptor('todos', { objectId: 'todos-relation' }),
|
|
438
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38360')
|
|
439
|
+
})
|
|
440
|
+
).tables[0];
|
|
441
|
+
const auditTable = (
|
|
442
|
+
await firstWriter.resolveTables({
|
|
443
|
+
connection_id: 1,
|
|
444
|
+
source: sourceDescriptor('audit_log', { objectId: 'audit-log-relation' }),
|
|
445
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38361')
|
|
446
|
+
})
|
|
447
|
+
).tables[0];
|
|
448
|
+
await firstWriter.save({
|
|
449
|
+
sourceTable: todosTable,
|
|
450
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
451
|
+
after: {
|
|
452
|
+
id: 'todo-1',
|
|
453
|
+
owner_id: 'user-1'
|
|
454
|
+
},
|
|
455
|
+
afterReplicaId: test_utils.rid('todo-1')
|
|
456
|
+
});
|
|
457
|
+
await firstWriter.save({
|
|
458
|
+
sourceTable: auditTable,
|
|
459
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
460
|
+
after: {
|
|
461
|
+
id: 'audit-1'
|
|
462
|
+
},
|
|
463
|
+
afterReplicaId: test_utils.rid('audit-1')
|
|
464
|
+
});
|
|
465
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
466
|
+
await firstWriter.commit('1/1');
|
|
467
|
+
|
|
468
|
+
const todosTableId = todosTable.id as bson.ObjectId;
|
|
469
|
+
const auditTableId = auditTable.id as bson.ObjectId;
|
|
470
|
+
// The event-only table is persisted with empty membership arrays, so the membership filter
|
|
471
|
+
// never selects it.
|
|
472
|
+
const auditSourceTable = await db.sourceTables(first.replicationStreamId).findOne({ _id: auditTableId });
|
|
473
|
+
expect(auditSourceTable?.bucket_data_source_ids).toEqual([]);
|
|
474
|
+
expect(auditSourceTable?.parameter_lookup_source_ids).toEqual([]);
|
|
475
|
+
const auditRecordsCollection = db.sourceRecords(first.replicationStreamId, auditTableId).collectionName;
|
|
476
|
+
|
|
477
|
+
// The second (active) config keeps the same `by_owner` stream but drops the audit event.
|
|
478
|
+
// The shared bucket definition stays in use, so no bucket/parameter ids become unused and
|
|
479
|
+
// the membership-cleanup block is skipped entirely.
|
|
480
|
+
const second = await factory.updateSyncRules(
|
|
481
|
+
updateSyncRulesFromYaml(
|
|
482
|
+
`
|
|
483
|
+
config:
|
|
484
|
+
edition: 3
|
|
485
|
+
|
|
486
|
+
streams:
|
|
487
|
+
by_owner:
|
|
488
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
489
|
+
`,
|
|
490
|
+
{ storageVersion: 3 }
|
|
491
|
+
)
|
|
492
|
+
);
|
|
493
|
+
expect(second.replicationStreamId).toBe(first.replicationStreamId);
|
|
494
|
+
|
|
495
|
+
const replicatingStreams = await factory.getReplicatingReplicationStreams();
|
|
496
|
+
const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
|
|
497
|
+
await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
498
|
+
await secondWriter.markAllSnapshotDone('2/1');
|
|
499
|
+
await secondWriter.commit('2/1');
|
|
500
|
+
|
|
501
|
+
const result = await cleanupStoppedSyncConfigs(
|
|
502
|
+
(await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
expect(result).toEqual({
|
|
506
|
+
stoppedSyncConfigsRemoved: 1,
|
|
507
|
+
bucketDataCollectionsDropped: 0,
|
|
508
|
+
parameterIndexCollectionsDropped: 0,
|
|
509
|
+
bucketStateDocumentsDeleted: 0,
|
|
510
|
+
sourceRecordCollectionsDropped: 1,
|
|
511
|
+
sourceTablesUpdated: 0,
|
|
512
|
+
sourceTablesDeleted: 1
|
|
513
|
+
});
|
|
514
|
+
// The orphaned event-only table and its source records are removed...
|
|
515
|
+
expect(await db.sourceTables(first.replicationStreamId).countDocuments({ _id: auditTableId })).toBe(0);
|
|
516
|
+
expect(await collectionExists(db, auditRecordsCollection)).toBe(false);
|
|
517
|
+
// ...while the data table still used by the live config is retained.
|
|
518
|
+
expect(await db.sourceTables(first.replicationStreamId).countDocuments({ _id: todosTableId })).toBe(1);
|
|
519
|
+
|
|
520
|
+
const streamDoc = (await db.sync_rules.findOne({ _id: first.replicationStreamId })) as ReplicationStreamDocumentV3;
|
|
521
|
+
expect(streamDoc.sync_configs.map((config) => config.state)).toEqual([storage.SyncRuleState.ACTIVE]);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test('does not recreate dropped parameter indexes from stale source record lookups', async () => {
|
|
525
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
526
|
+
|
|
527
|
+
const first = await factory.updateSyncRules(
|
|
528
|
+
updateSyncRulesFromYaml(
|
|
529
|
+
`
|
|
530
|
+
config:
|
|
531
|
+
edition: 3
|
|
532
|
+
|
|
533
|
+
streams:
|
|
534
|
+
by_role:
|
|
535
|
+
query: |
|
|
536
|
+
SELECT * FROM todos
|
|
537
|
+
WHERE role_id IN (
|
|
538
|
+
SELECT role_id FROM memberships
|
|
539
|
+
WHERE role_id = subscription.parameter('role_id')
|
|
540
|
+
)
|
|
541
|
+
by_org:
|
|
542
|
+
query: |
|
|
543
|
+
SELECT * FROM todos
|
|
544
|
+
WHERE org_id IN (
|
|
545
|
+
SELECT org_id FROM memberships
|
|
546
|
+
WHERE org_id = subscription.parameter('org_id')
|
|
547
|
+
)
|
|
548
|
+
`,
|
|
549
|
+
{ storageVersion: 3 }
|
|
550
|
+
)
|
|
551
|
+
);
|
|
552
|
+
const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
|
|
553
|
+
const db = firstStorage.db as VersionedPowerSyncMongoV3;
|
|
554
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
555
|
+
const resolved = await firstWriter.resolveTables({
|
|
556
|
+
connection_id: 1,
|
|
557
|
+
source: sourceDescriptor('memberships', { objectId: 'memberships-relation' }),
|
|
558
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38355')
|
|
559
|
+
});
|
|
560
|
+
await firstWriter.save({
|
|
561
|
+
sourceTable: resolved.tables[0],
|
|
562
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
563
|
+
after: {
|
|
564
|
+
id: 'membership-1',
|
|
565
|
+
org_id: 'org-1',
|
|
566
|
+
role_id: 'role-1'
|
|
567
|
+
},
|
|
568
|
+
afterReplicaId: test_utils.rid('membership-1')
|
|
569
|
+
});
|
|
570
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
571
|
+
await firstWriter.commit('1/1');
|
|
572
|
+
|
|
573
|
+
const sourceTableId = resolved.tables[0].id as bson.ObjectId;
|
|
574
|
+
const sourceRecordsCollection = db.sourceRecords(first.replicationStreamId, sourceTableId).collectionName;
|
|
575
|
+
const [roleIndexId, orgIndexId] = first.syncConfigContent[0].mapping.allParameterIndexIds();
|
|
576
|
+
const orgParameterIndexCollection = db.parameterIndex(first.replicationStreamId, orgIndexId).collectionName;
|
|
577
|
+
expect(await collectionExists(db, orgParameterIndexCollection)).toBe(true);
|
|
578
|
+
expect(
|
|
579
|
+
(await db.sourceTables(first.replicationStreamId).findOne({ _id: sourceTableId }))?.parameter_lookup_source_ids
|
|
580
|
+
).toEqual([roleIndexId, orgIndexId]);
|
|
581
|
+
|
|
582
|
+
const second = await factory.updateSyncRules(
|
|
583
|
+
updateSyncRulesFromYaml(
|
|
584
|
+
`
|
|
585
|
+
config:
|
|
586
|
+
edition: 3
|
|
587
|
+
|
|
588
|
+
streams:
|
|
589
|
+
by_role:
|
|
590
|
+
query: |
|
|
591
|
+
SELECT * FROM todos
|
|
592
|
+
WHERE role_id IN (
|
|
593
|
+
SELECT role_id FROM memberships
|
|
594
|
+
WHERE role_id = subscription.parameter('role_id')
|
|
595
|
+
)
|
|
596
|
+
`,
|
|
597
|
+
{ storageVersion: 3 }
|
|
598
|
+
)
|
|
599
|
+
);
|
|
600
|
+
expect(second.replicationStreamId).toBe(first.replicationStreamId);
|
|
601
|
+
const secondIndexId = second.syncConfigContent[1].mapping.allParameterIndexIds()[0];
|
|
602
|
+
expect(secondIndexId).toBe(roleIndexId);
|
|
603
|
+
|
|
604
|
+
const replicatingStreams = await factory.getReplicatingReplicationStreams();
|
|
605
|
+
const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
|
|
606
|
+
await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
607
|
+
await secondWriter.markAllSnapshotDone('2/1');
|
|
608
|
+
await secondWriter.commit('2/1');
|
|
609
|
+
|
|
610
|
+
const result = await cleanupStoppedSyncConfigs(
|
|
611
|
+
(await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3
|
|
612
|
+
);
|
|
613
|
+
expect(result.parameterIndexCollectionsDropped).toBe(1);
|
|
614
|
+
expect(await collectionExists(db, orgParameterIndexCollection)).toBe(false);
|
|
615
|
+
expect(await collectionExists(db, sourceRecordsCollection)).toBe(true);
|
|
616
|
+
|
|
617
|
+
const activeStorage = (await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3;
|
|
618
|
+
await using activeWriter = await activeStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
619
|
+
const activeTable = (
|
|
620
|
+
await activeWriter.resolveTables({
|
|
621
|
+
connection_id: 1,
|
|
622
|
+
source: sourceDescriptor('memberships', { objectId: 'memberships-relation' }),
|
|
623
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38357')
|
|
624
|
+
})
|
|
625
|
+
).tables[0];
|
|
626
|
+
await activeWriter.save({
|
|
627
|
+
sourceTable: activeTable,
|
|
628
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
629
|
+
before: {
|
|
630
|
+
id: 'membership-1'
|
|
631
|
+
},
|
|
632
|
+
after: {
|
|
633
|
+
id: 'membership-1',
|
|
634
|
+
org_id: 'org-1',
|
|
635
|
+
role_id: 'role-2'
|
|
636
|
+
},
|
|
637
|
+
beforeReplicaId: test_utils.rid('membership-1'),
|
|
638
|
+
afterReplicaId: test_utils.rid('membership-1')
|
|
639
|
+
});
|
|
640
|
+
await activeWriter.commit('3/1');
|
|
641
|
+
|
|
642
|
+
expect(await collectionExists(db, orgParameterIndexCollection)).toBe(false);
|
|
643
|
+
const sourceRecord = await db.sourceRecords(first.replicationStreamId, sourceTableId).findOne({
|
|
644
|
+
_id: test_utils.rid('membership-1') as any
|
|
645
|
+
});
|
|
646
|
+
expect(sourceRecord?.lookups.map((lookup) => lookup.i)).toEqual([secondIndexId]);
|
|
647
|
+
});
|
|
648
|
+
});
|
package/test/src/storage.test.ts
CHANGED
|
@@ -9,7 +9,11 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
9
9
|
register.registerDataStorageParameterTests({ ...INITIALIZED_MONGO_STORAGE_FACTORY, storageVersion }));
|
|
10
10
|
|
|
11
11
|
describe(`Mongo Sync Bucket Storage - Data - v${storageVersion}`, () =>
|
|
12
|
-
register.registerDataStorageDataTests({
|
|
12
|
+
register.registerDataStorageDataTests({
|
|
13
|
+
...INITIALIZED_MONGO_STORAGE_FACTORY,
|
|
14
|
+
storageVersion,
|
|
15
|
+
compressedBucketStorage: storageVersion >= 3
|
|
16
|
+
}));
|
|
13
17
|
|
|
14
18
|
describe(`Mongo Sync Bucket Storage - Checkpoints - v${storageVersion}`, () =>
|
|
15
19
|
register.registerDataStorageCheckpointTests({ ...INITIALIZED_MONGO_STORAGE_FACTORY, storageVersion }));
|
|
@@ -22,11 +26,9 @@ describe('Mongo Sync Bucket Storage - split operations', () =>
|
|
|
22
26
|
mongoTestStorageFactoryGenerator({
|
|
23
27
|
url: env.MONGO_TEST_URL,
|
|
24
28
|
isCI: env.CI,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
operationBatchLimit: 1
|
|
29
|
-
}
|
|
29
|
+
checksumOptions: {
|
|
30
|
+
bucketBatchLimit: 100,
|
|
31
|
+
operationBatchLimit: 1
|
|
30
32
|
}
|
|
31
33
|
})
|
|
32
34
|
));
|
|
@@ -36,11 +38,9 @@ describe('Mongo Sync Bucket Storage - split buckets', () =>
|
|
|
36
38
|
mongoTestStorageFactoryGenerator({
|
|
37
39
|
url: env.MONGO_TEST_URL,
|
|
38
40
|
isCI: env.CI,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
operationBatchLimit: 100
|
|
43
|
-
}
|
|
41
|
+
checksumOptions: {
|
|
42
|
+
bucketBatchLimit: 1,
|
|
43
|
+
operationBatchLimit: 100
|
|
44
44
|
}
|
|
45
45
|
})
|
|
46
46
|
));
|