@powersync/service-module-mongodb-storage 0.15.0 → 0.15.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.
@@ -1,41 +1,48 @@
1
1
  import { storage, SyncRulesBucketStorage, updateSyncRulesFromYaml } from '@powersync/service-core';
2
2
  import { bucketRequest, register, test_utils } from '@powersync/service-core-tests';
3
3
  import { describe, expect, test } from 'vitest';
4
+ import { MongoCompactor } from '../../src/storage/implementation/MongoCompactor.js';
4
5
  import { INITIALIZED_MONGO_STORAGE_FACTORY } from './util.js';
5
6
 
6
7
  describe('Mongo Sync Bucket Storage Compact', () => {
7
8
  register.registerCompactTests(INITIALIZED_MONGO_STORAGE_FACTORY);
8
- const TEST_TABLE = test_utils.makeTestTable('test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
9
9
 
10
10
  describe('with blank bucket_state', () => {
11
11
  // This can happen when migrating from older service versions, that did not populate bucket_state yet.
12
- const populate = async (bucketStorage: SyncRulesBucketStorage) => {
13
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
14
- await batch.markAllSnapshotDone('1/1');
15
-
16
- await batch.save({
17
- sourceTable: TEST_TABLE,
18
- tag: storage.SaveOperationTag.INSERT,
19
- after: {
20
- id: 't1',
21
- owner_id: 'u1'
22
- },
23
- afterReplicaId: test_utils.rid('t1')
24
- });
25
-
26
- await batch.save({
27
- sourceTable: TEST_TABLE,
28
- tag: storage.SaveOperationTag.INSERT,
29
- after: {
30
- id: 't2',
31
- owner_id: 'u2'
32
- },
33
- afterReplicaId: test_utils.rid('t2')
34
- });
35
-
36
- await batch.commit('1/1');
12
+ const populate = async (bucketStorage: SyncRulesBucketStorage, sourceTableIndex: number) => {
13
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
14
+
15
+ const sourceTable = await test_utils.resolveTestTable(
16
+ writer,
17
+ 'test',
18
+ ['id'],
19
+ INITIALIZED_MONGO_STORAGE_FACTORY,
20
+ sourceTableIndex
21
+ );
22
+ await writer.markAllSnapshotDone('1/1');
23
+
24
+ await writer.save({
25
+ sourceTable,
26
+ tag: storage.SaveOperationTag.INSERT,
27
+ after: {
28
+ id: 't1',
29
+ owner_id: 'u1'
30
+ },
31
+ afterReplicaId: test_utils.rid('t1')
32
+ });
33
+
34
+ await writer.save({
35
+ sourceTable,
36
+ tag: storage.SaveOperationTag.INSERT,
37
+ after: {
38
+ id: 't2',
39
+ owner_id: 'u2'
40
+ },
41
+ afterReplicaId: test_utils.rid('t2')
37
42
  });
38
43
 
44
+ await writer.commit('1/1');
45
+
39
46
  return bucketStorage.getCheckpoint();
40
47
  };
41
48
 
@@ -50,7 +57,7 @@ bucket_definitions:
50
57
  `)
51
58
  );
52
59
  const bucketStorage = factory.getInstance(syncRules);
53
- const { checkpoint } = await populate(bucketStorage);
60
+ const { checkpoint } = await populate(bucketStorage, 1);
54
61
 
55
62
  return { bucketStorage, checkpoint, factory, syncRules };
56
63
  };
@@ -102,7 +109,7 @@ bucket_definitions:
102
109
  );
103
110
  const bucketStorage = factory.getInstance(syncRules);
104
111
 
105
- await populate(bucketStorage);
112
+ await populate(bucketStorage, 2);
106
113
  const { checkpoint } = await bucketStorage.getCheckpoint();
107
114
 
108
115
  // Default is to small small numbers - should be a no-op
@@ -140,6 +147,68 @@ bucket_definitions:
140
147
  count: 1
141
148
  });
142
149
  });
150
+
151
+ test('dirty bucket discovery handles bigint bucket_state bytes', async () => {
152
+ await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
153
+ const syncRules = await factory.updateSyncRules(
154
+ updateSyncRulesFromYaml(`
155
+ bucket_definitions:
156
+ global:
157
+ data: [select * from test]
158
+ `)
159
+ );
160
+ const bucketStorage = factory.getInstance(syncRules);
161
+
162
+ // This simulates bucket_state created using bigint bytes.
163
+ // This typically happens when buckets get very large (> 2GiB). We don't want to create that much
164
+ // data in the tests, so we directly insert the bucket_state here.
165
+ await factory.db.bucket_state.insertOne({
166
+ _id: {
167
+ g: bucketStorage.group_id,
168
+ b: 'global[]'
169
+ },
170
+ last_op: 5n,
171
+ compacted_state: {
172
+ op_id: 3n,
173
+ count: 3,
174
+ checksum: 0n,
175
+ bytes: 7n
176
+ },
177
+ estimate_since_compact: {
178
+ count: 2,
179
+ bytes: 5n
180
+ }
181
+ });
182
+
183
+ // This test uses a couple of internal APIs of the compactor - there is no simple way
184
+ // to test this using the current public APIs.
185
+ const compactor = new MongoCompactor(bucketStorage, (bucketStorage as any).db, {
186
+ maxOpId: 5n
187
+ });
188
+
189
+ const dirtyBuckets = (compactor as any).dirtyBucketBatches({
190
+ minBucketChanges: 1,
191
+ minChangeRatio: 0.39
192
+ });
193
+ const firstBatch = await dirtyBuckets.next();
194
+
195
+ expect(firstBatch.done).toBe(false);
196
+ expect(firstBatch.value).toHaveLength(1);
197
+ expect(firstBatch.value[0].bucket).toBe('global[]');
198
+ expect(firstBatch.value[0].estimatedCount).toBe(5);
199
+ expect(typeof firstBatch.value[0].estimatedCount).toBe('number');
200
+ expect(firstBatch.value[0].dirtyRatio).toBeCloseTo(5 / 12);
201
+
202
+ const checksumBuckets = await (compactor as any).dirtyBucketBatchForChecksums({
203
+ minBucketChanges: 1
204
+ });
205
+ expect(checksumBuckets).toEqual([
206
+ {
207
+ bucket: 'global[]',
208
+ estimatedCount: 5
209
+ }
210
+ ]);
211
+ });
143
212
  });
144
213
  });
145
214
 
@@ -8,8 +8,6 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
8
8
  storageVersion,
9
9
  tableIdStrings: storageConfig.tableIdStrings
10
10
  });
11
- const TEST_TABLE = test_utils.makeTestTable('test', ['id'], storageConfig);
12
-
13
11
  // The split of returned results can vary depending on storage drivers
14
12
  test('large batch (2)', async () => {
15
13
  // Test syncing a batch of data that is small in count,
@@ -28,56 +26,57 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
28
26
  )
29
27
  );
30
28
  const bucketStorage = factory.getInstance(syncRules);
31
- const globalBucket = bucketRequest(syncRules, 'global[]');
32
29
 
33
- const result = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
34
- const sourceTable = TEST_TABLE;
30
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
31
+
32
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
35
33
 
36
- const largeDescription = '0123456789'.repeat(2_000_00);
34
+ const largeDescription = '0123456789'.repeat(2_000_00);
37
35
 
38
- await batch.save({
39
- sourceTable,
40
- tag: storage.SaveOperationTag.INSERT,
41
- after: {
42
- id: 'test1',
43
- description: 'test1'
44
- },
45
- afterReplicaId: test_utils.rid('test1')
46
- });
36
+ await writer.save({
37
+ sourceTable,
38
+ tag: storage.SaveOperationTag.INSERT,
39
+ after: {
40
+ id: 'test1',
41
+ description: 'test1'
42
+ },
43
+ afterReplicaId: test_utils.rid('test1')
44
+ });
47
45
 
48
- await batch.save({
49
- sourceTable,
50
- tag: storage.SaveOperationTag.INSERT,
51
- after: {
52
- id: 'large1',
53
- description: largeDescription
54
- },
55
- afterReplicaId: test_utils.rid('large1')
56
- });
46
+ await writer.save({
47
+ sourceTable,
48
+ tag: storage.SaveOperationTag.INSERT,
49
+ after: {
50
+ id: 'large1',
51
+ description: largeDescription
52
+ },
53
+ afterReplicaId: test_utils.rid('large1')
54
+ });
57
55
 
58
- // Large enough to split the returned batch
59
- await batch.save({
60
- sourceTable,
61
- tag: storage.SaveOperationTag.INSERT,
62
- after: {
63
- id: 'large2',
64
- description: largeDescription
65
- },
66
- afterReplicaId: test_utils.rid('large2')
67
- });
56
+ // Large enough to split the returned batch
57
+ await writer.save({
58
+ sourceTable,
59
+ tag: storage.SaveOperationTag.INSERT,
60
+ after: {
61
+ id: 'large2',
62
+ description: largeDescription
63
+ },
64
+ afterReplicaId: test_utils.rid('large2')
65
+ });
68
66
 
69
- await batch.save({
70
- sourceTable,
71
- tag: storage.SaveOperationTag.INSERT,
72
- after: {
73
- id: 'test3',
74
- description: 'test3'
75
- },
76
- afterReplicaId: test_utils.rid('test3')
77
- });
67
+ await writer.save({
68
+ sourceTable,
69
+ tag: storage.SaveOperationTag.INSERT,
70
+ after: {
71
+ id: 'test3',
72
+ description: 'test3'
73
+ },
74
+ afterReplicaId: test_utils.rid('test3')
78
75
  });
79
76
 
80
- const checkpoint = result!.flushed_op;
77
+ const flushResult = await writer.flush();
78
+
79
+ const checkpoint = flushResult!.flushed_op;
81
80
 
82
81
  const options: storage.BucketDataBatchOptions = {};
83
82
  const batch1 = await test_utils.fromAsync(