@powersync/service-core-tests 0.14.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.
- package/CHANGELOG.md +40 -0
- package/dist/test-utils/general-utils.d.ts +22 -3
- package/dist/test-utils/general-utils.js +56 -3
- package/dist/test-utils/general-utils.js.map +1 -1
- package/dist/test-utils/stream_utils.js +2 -2
- package/dist/test-utils/stream_utils.js.map +1 -1
- package/dist/tests/register-compacting-tests.d.ts +1 -1
- package/dist/tests/register-compacting-tests.js +360 -297
- package/dist/tests/register-compacting-tests.js.map +1 -1
- package/dist/tests/register-data-storage-checkpoint-tests.d.ts +1 -1
- package/dist/tests/register-data-storage-checkpoint-tests.js +59 -48
- package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
- package/dist/tests/register-data-storage-data-tests.d.ts +2 -2
- package/dist/tests/register-data-storage-data-tests.js +1112 -612
- package/dist/tests/register-data-storage-data-tests.js.map +1 -1
- package/dist/tests/register-data-storage-parameter-tests.d.ts +1 -1
- package/dist/tests/register-data-storage-parameter-tests.js +273 -254
- package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
- package/dist/tests/register-parameter-compacting-tests.d.ts +1 -1
- package/dist/tests/register-parameter-compacting-tests.js +83 -87
- package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
- package/dist/tests/register-sync-tests.d.ts +2 -1
- package/dist/tests/register-sync-tests.js +479 -451
- package/dist/tests/register-sync-tests.js.map +1 -1
- package/dist/tests/util.d.ts +5 -4
- package/dist/tests/util.js +27 -12
- package/dist/tests/util.js.map +1 -1
- package/package.json +3 -3
- package/src/test-utils/general-utils.ts +81 -4
- package/src/test-utils/stream_utils.ts +2 -2
- package/src/tests/register-compacting-tests.ts +376 -322
- package/src/tests/register-data-storage-checkpoint-tests.ts +85 -53
- package/src/tests/register-data-storage-data-tests.ts +1050 -559
- package/src/tests/register-data-storage-parameter-tests.ts +330 -288
- package/src/tests/register-parameter-compacting-tests.ts +87 -90
- package/src/tests/register-sync-tests.ts +390 -380
- package/src/tests/util.ts +46 -17
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BucketDataBatchOptions,
|
|
3
|
+
CURRENT_STORAGE_VERSION,
|
|
3
4
|
getUuidReplicaIdentityBson,
|
|
4
5
|
OplogEntry,
|
|
6
|
+
reduceBucket,
|
|
5
7
|
storage,
|
|
6
8
|
updateSyncRulesFromYaml
|
|
7
9
|
} from '@powersync/service-core';
|
|
8
10
|
import { describe, expect, test } from 'vitest';
|
|
9
11
|
import * as test_utils from '../test-utils/test-utils-index.js';
|
|
10
|
-
import { bucketRequest
|
|
12
|
+
import { bucketRequest } from '../test-utils/test-utils-index.js';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Normalize data from OplogEntries for comparison in tests.
|
|
@@ -30,44 +32,49 @@ const normalizeOplogData = (data: OplogEntry['data']) => {
|
|
|
30
32
|
*
|
|
31
33
|
* ```
|
|
32
34
|
*/
|
|
33
|
-
export function registerDataStorageDataTests(
|
|
35
|
+
export function registerDataStorageDataTests(config: storage.TestStorageConfig) {
|
|
36
|
+
const generateStorageFactory = config.factory;
|
|
37
|
+
const storageVersion = config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
|
|
38
|
+
|
|
34
39
|
test('removing row', async () => {
|
|
35
40
|
await using factory = await generateStorageFactory();
|
|
36
41
|
const syncRules = await factory.updateSyncRules(
|
|
37
|
-
updateSyncRulesFromYaml(
|
|
42
|
+
updateSyncRulesFromYaml(
|
|
43
|
+
`
|
|
38
44
|
bucket_definitions:
|
|
39
45
|
global:
|
|
40
46
|
data:
|
|
41
47
|
- SELECT id, description FROM "%"
|
|
42
|
-
|
|
48
|
+
`,
|
|
49
|
+
{ storageVersion }
|
|
50
|
+
)
|
|
43
51
|
);
|
|
44
52
|
const bucketStorage = factory.getInstance(syncRules);
|
|
53
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
54
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
45
55
|
|
|
46
|
-
await
|
|
47
|
-
const sourceTable = TEST_TABLE;
|
|
56
|
+
await writer.markAllSnapshotDone('1/1');
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
58
|
-
await batch.save({
|
|
59
|
-
sourceTable,
|
|
60
|
-
tag: storage.SaveOperationTag.DELETE,
|
|
61
|
-
beforeReplicaId: test_utils.rid('test1')
|
|
62
|
-
});
|
|
63
|
-
await batch.commit('1/1');
|
|
58
|
+
await writer.save({
|
|
59
|
+
sourceTable: testTable,
|
|
60
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
61
|
+
after: {
|
|
62
|
+
id: 'test1',
|
|
63
|
+
description: 'test1'
|
|
64
|
+
},
|
|
65
|
+
afterReplicaId: test_utils.rid('test1')
|
|
64
66
|
});
|
|
67
|
+
await writer.save({
|
|
68
|
+
sourceTable: testTable,
|
|
69
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
70
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
71
|
+
});
|
|
72
|
+
await writer.commit('1/1');
|
|
65
73
|
|
|
66
74
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
67
75
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
);
|
|
76
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
77
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
71
78
|
const data = batch[0].chunkData.data.map((d) => {
|
|
72
79
|
return {
|
|
73
80
|
op: d.op,
|
|
@@ -84,69 +91,342 @@ bucket_definitions:
|
|
|
84
91
|
{ op: 'REMOVE', object_id: 'test1', checksum: c2 }
|
|
85
92
|
]);
|
|
86
93
|
|
|
87
|
-
const checksums = [
|
|
88
|
-
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
89
|
-
];
|
|
94
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
|
|
90
95
|
expect(checksums).toEqual([
|
|
91
96
|
{
|
|
92
|
-
bucket:
|
|
97
|
+
bucket: request.bucket,
|
|
93
98
|
checksum: (c1 + c2) & 0xffffffff,
|
|
94
99
|
count: 2
|
|
95
100
|
}
|
|
96
101
|
]);
|
|
97
102
|
});
|
|
98
103
|
|
|
99
|
-
test('
|
|
104
|
+
test('insert after delete in new batch', async () => {
|
|
100
105
|
await using factory = await generateStorageFactory();
|
|
101
106
|
const syncRules = await factory.updateSyncRules(
|
|
102
|
-
updateSyncRulesFromYaml(
|
|
107
|
+
updateSyncRulesFromYaml(
|
|
108
|
+
`
|
|
103
109
|
bucket_definitions:
|
|
104
110
|
global:
|
|
105
111
|
data:
|
|
106
|
-
- SELECT
|
|
107
|
-
|
|
112
|
+
- SELECT id, description FROM "%"
|
|
113
|
+
`,
|
|
114
|
+
{ storageVersion }
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
118
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
119
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
120
|
+
await writer.markAllSnapshotDone('1/1');
|
|
121
|
+
|
|
122
|
+
await writer.save({
|
|
123
|
+
sourceTable: testTable,
|
|
124
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
125
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await writer.commit('0/1');
|
|
129
|
+
|
|
130
|
+
await writer.save({
|
|
131
|
+
sourceTable: testTable,
|
|
132
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
133
|
+
after: {
|
|
134
|
+
id: 'test1',
|
|
135
|
+
description: 'test1'
|
|
136
|
+
},
|
|
137
|
+
afterReplicaId: test_utils.rid('test1')
|
|
138
|
+
});
|
|
139
|
+
await writer.commit('2/1');
|
|
140
|
+
|
|
141
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
142
|
+
|
|
143
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
144
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
145
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
146
|
+
return {
|
|
147
|
+
op: d.op,
|
|
148
|
+
object_id: d.object_id,
|
|
149
|
+
checksum: d.checksum
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const c1 = 2871785649;
|
|
154
|
+
|
|
155
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
156
|
+
|
|
157
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
|
|
158
|
+
expect(checksums).toEqual([
|
|
159
|
+
{
|
|
160
|
+
bucket: request.bucket,
|
|
161
|
+
checksum: c1 & 0xffffffff,
|
|
162
|
+
count: 1
|
|
163
|
+
}
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('update after delete in new batch', async () => {
|
|
168
|
+
// Update after delete may not be common, but the storage layer should handle it in an eventually-consistent way.
|
|
169
|
+
await using factory = await generateStorageFactory();
|
|
170
|
+
const syncRules = await factory.updateSyncRules(
|
|
171
|
+
updateSyncRulesFromYaml(
|
|
172
|
+
`
|
|
173
|
+
bucket_definitions:
|
|
174
|
+
global:
|
|
175
|
+
data:
|
|
176
|
+
- SELECT id, description FROM "%"
|
|
177
|
+
`,
|
|
178
|
+
{ storageVersion }
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
182
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
183
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
184
|
+
await writer.markAllSnapshotDone('1/1');
|
|
185
|
+
|
|
186
|
+
await writer.save({
|
|
187
|
+
sourceTable: testTable,
|
|
188
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
189
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await writer.commit('0/1');
|
|
193
|
+
|
|
194
|
+
await writer.save({
|
|
195
|
+
sourceTable: testTable,
|
|
196
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
197
|
+
before: {
|
|
198
|
+
id: 'test1'
|
|
199
|
+
},
|
|
200
|
+
after: {
|
|
201
|
+
id: 'test1',
|
|
202
|
+
description: 'test1'
|
|
203
|
+
},
|
|
204
|
+
beforeReplicaId: test_utils.rid('test1'),
|
|
205
|
+
afterReplicaId: test_utils.rid('test1')
|
|
206
|
+
});
|
|
207
|
+
await writer.commit('2/1');
|
|
208
|
+
|
|
209
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
210
|
+
|
|
211
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
212
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
213
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
214
|
+
return {
|
|
215
|
+
op: d.op,
|
|
216
|
+
object_id: d.object_id,
|
|
217
|
+
checksum: d.checksum
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const c1 = 2871785649;
|
|
222
|
+
|
|
223
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
224
|
+
|
|
225
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
|
|
226
|
+
expect(checksums).toEqual([
|
|
227
|
+
{
|
|
228
|
+
bucket: request.bucket,
|
|
229
|
+
checksum: c1 & 0xffffffff,
|
|
230
|
+
count: 1
|
|
231
|
+
}
|
|
232
|
+
]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('insert after delete in same batch', async () => {
|
|
236
|
+
await using factory = await generateStorageFactory();
|
|
237
|
+
const syncRules = await factory.updateSyncRules(
|
|
238
|
+
updateSyncRulesFromYaml(
|
|
239
|
+
`
|
|
240
|
+
bucket_definitions:
|
|
241
|
+
global:
|
|
242
|
+
data:
|
|
243
|
+
- SELECT id, description FROM "%"
|
|
244
|
+
`,
|
|
245
|
+
{
|
|
246
|
+
storageVersion
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
);
|
|
250
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
251
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
252
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
253
|
+
await writer.markAllSnapshotDone('1/1');
|
|
254
|
+
|
|
255
|
+
await writer.save({
|
|
256
|
+
sourceTable: testTable,
|
|
257
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
258
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
259
|
+
});
|
|
260
|
+
await writer.save({
|
|
261
|
+
sourceTable: testTable,
|
|
262
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
263
|
+
after: {
|
|
264
|
+
id: 'test1',
|
|
265
|
+
description: 'test1'
|
|
266
|
+
},
|
|
267
|
+
afterReplicaId: test_utils.rid('test1')
|
|
268
|
+
});
|
|
269
|
+
await writer.commit('1/1');
|
|
270
|
+
|
|
271
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
272
|
+
|
|
273
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
274
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
275
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
276
|
+
return {
|
|
277
|
+
op: d.op,
|
|
278
|
+
object_id: d.object_id,
|
|
279
|
+
checksum: d.checksum
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const c1 = 2871785649;
|
|
284
|
+
|
|
285
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
286
|
+
|
|
287
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
|
|
288
|
+
expect(checksums).toEqual([
|
|
289
|
+
{
|
|
290
|
+
bucket: request.bucket,
|
|
291
|
+
checksum: c1 & 0xffffffff,
|
|
292
|
+
count: 1
|
|
293
|
+
}
|
|
294
|
+
]);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('(insert, delete, insert), (delete)', async () => {
|
|
298
|
+
await using factory = await generateStorageFactory();
|
|
299
|
+
const syncRules = await factory.updateSyncRules(
|
|
300
|
+
updateSyncRulesFromYaml(
|
|
301
|
+
`
|
|
302
|
+
bucket_definitions:
|
|
303
|
+
global:
|
|
304
|
+
data:
|
|
305
|
+
- SELECT id, description FROM "%"
|
|
306
|
+
`,
|
|
307
|
+
{
|
|
308
|
+
storageVersion
|
|
309
|
+
}
|
|
310
|
+
)
|
|
108
311
|
);
|
|
109
312
|
const bucketStorage = factory.getInstance(syncRules);
|
|
313
|
+
{
|
|
314
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
315
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
316
|
+
await writer.markAllSnapshotDone('1/1');
|
|
110
317
|
|
|
111
|
-
|
|
112
|
-
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
113
|
-
await batch.save({
|
|
318
|
+
await writer.save({
|
|
114
319
|
sourceTable,
|
|
115
320
|
tag: storage.SaveOperationTag.INSERT,
|
|
116
321
|
after: {
|
|
117
322
|
id: 'test1',
|
|
118
|
-
|
|
119
|
-
description: 'test1a'
|
|
323
|
+
description: 'test1'
|
|
120
324
|
},
|
|
121
325
|
afterReplicaId: test_utils.rid('test1')
|
|
122
326
|
});
|
|
123
|
-
await
|
|
327
|
+
await writer.save({
|
|
328
|
+
sourceTable,
|
|
329
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
330
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
331
|
+
});
|
|
332
|
+
await writer.save({
|
|
124
333
|
sourceTable,
|
|
125
|
-
tag: storage.SaveOperationTag.
|
|
334
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
126
335
|
after: {
|
|
127
336
|
id: 'test1',
|
|
128
|
-
|
|
129
|
-
description: 'test1b'
|
|
337
|
+
description: 'test1'
|
|
130
338
|
},
|
|
131
339
|
afterReplicaId: test_utils.rid('test1')
|
|
132
340
|
});
|
|
341
|
+
await writer.commit('1/1');
|
|
342
|
+
}
|
|
133
343
|
|
|
134
|
-
|
|
344
|
+
{
|
|
345
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
346
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
347
|
+
await writer.markAllSnapshotDone('1/1');
|
|
348
|
+
|
|
349
|
+
await writer.save({
|
|
135
350
|
sourceTable,
|
|
136
|
-
tag: storage.SaveOperationTag.
|
|
137
|
-
|
|
138
|
-
id: 'test2',
|
|
139
|
-
client_id: 'client2',
|
|
140
|
-
description: 'test2'
|
|
141
|
-
},
|
|
142
|
-
afterReplicaId: test_utils.rid('test2')
|
|
351
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
352
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
143
353
|
});
|
|
354
|
+
await writer.commit('2/1');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
358
|
+
|
|
359
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
360
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
361
|
+
|
|
362
|
+
expect(reduceBucket(batch[0].chunkData.data).slice(1)).toEqual([]);
|
|
363
|
+
|
|
364
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
365
|
+
return {
|
|
366
|
+
op: d.op,
|
|
367
|
+
object_id: d.object_id,
|
|
368
|
+
checksum: d.checksum
|
|
369
|
+
};
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(data).toMatchSnapshot();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('changing client ids', async () => {
|
|
376
|
+
await using factory = await generateStorageFactory();
|
|
377
|
+
const syncRules = await factory.updateSyncRules(
|
|
378
|
+
updateSyncRulesFromYaml(
|
|
379
|
+
`
|
|
380
|
+
bucket_definitions:
|
|
381
|
+
global:
|
|
382
|
+
data:
|
|
383
|
+
- SELECT client_id as id, description FROM "%"
|
|
384
|
+
`,
|
|
385
|
+
{
|
|
386
|
+
storageVersion
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
);
|
|
390
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
391
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
392
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
393
|
+
await writer.markAllSnapshotDone('1/1');
|
|
394
|
+
await writer.save({
|
|
395
|
+
sourceTable,
|
|
396
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
397
|
+
after: {
|
|
398
|
+
id: 'test1',
|
|
399
|
+
client_id: 'client1a',
|
|
400
|
+
description: 'test1a'
|
|
401
|
+
},
|
|
402
|
+
afterReplicaId: test_utils.rid('test1')
|
|
403
|
+
});
|
|
404
|
+
await writer.save({
|
|
405
|
+
sourceTable,
|
|
406
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
407
|
+
after: {
|
|
408
|
+
id: 'test1',
|
|
409
|
+
client_id: 'client1b',
|
|
410
|
+
description: 'test1b'
|
|
411
|
+
},
|
|
412
|
+
afterReplicaId: test_utils.rid('test1')
|
|
413
|
+
});
|
|
144
414
|
|
|
145
|
-
|
|
415
|
+
await writer.save({
|
|
416
|
+
sourceTable,
|
|
417
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
418
|
+
after: {
|
|
419
|
+
id: 'test2',
|
|
420
|
+
client_id: 'client2',
|
|
421
|
+
description: 'test2'
|
|
422
|
+
},
|
|
423
|
+
afterReplicaId: test_utils.rid('test2')
|
|
146
424
|
});
|
|
425
|
+
|
|
426
|
+
await writer.commit('1/1');
|
|
147
427
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
148
428
|
const batch = await test_utils.fromAsync(
|
|
149
|
-
bucketStorage.getBucketDataBatch(checkpoint,
|
|
429
|
+
bucketStorage.getBucketDataBatch(checkpoint, [bucketRequest(syncRules, 'global[]')])
|
|
150
430
|
);
|
|
151
431
|
const data = batch[0].chunkData.data.map((d) => {
|
|
152
432
|
return {
|
|
@@ -166,56 +446,53 @@ bucket_definitions:
|
|
|
166
446
|
test('re-apply delete', async () => {
|
|
167
447
|
await using factory = await generateStorageFactory();
|
|
168
448
|
const syncRules = await factory.updateSyncRules(
|
|
169
|
-
updateSyncRulesFromYaml(
|
|
449
|
+
updateSyncRulesFromYaml(
|
|
450
|
+
`
|
|
170
451
|
bucket_definitions:
|
|
171
452
|
global:
|
|
172
453
|
data:
|
|
173
454
|
- SELECT id, description FROM "%"
|
|
174
|
-
|
|
455
|
+
`,
|
|
456
|
+
{
|
|
457
|
+
storageVersion
|
|
458
|
+
}
|
|
459
|
+
)
|
|
175
460
|
);
|
|
176
461
|
const bucketStorage = factory.getInstance(syncRules);
|
|
177
|
-
|
|
178
|
-
await
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
});
|
|
462
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
463
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
464
|
+
await writer.markAllSnapshotDone('1/1');
|
|
465
|
+
|
|
466
|
+
await writer.save({
|
|
467
|
+
sourceTable,
|
|
468
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
469
|
+
after: {
|
|
470
|
+
id: 'test1',
|
|
471
|
+
description: 'test1'
|
|
472
|
+
},
|
|
473
|
+
afterReplicaId: test_utils.rid('test1')
|
|
190
474
|
});
|
|
475
|
+
await writer.flush();
|
|
191
476
|
|
|
192
|
-
await
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
sourceTable,
|
|
197
|
-
tag: storage.SaveOperationTag.DELETE,
|
|
198
|
-
beforeReplicaId: test_utils.rid('test1')
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await batch.commit('1/1');
|
|
477
|
+
await writer.save({
|
|
478
|
+
sourceTable,
|
|
479
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
480
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
202
481
|
});
|
|
203
482
|
|
|
204
|
-
await
|
|
205
|
-
const sourceTable = TEST_TABLE;
|
|
483
|
+
await writer.commit('1/1');
|
|
206
484
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
485
|
+
await writer.save({
|
|
486
|
+
sourceTable,
|
|
487
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
488
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
212
489
|
});
|
|
490
|
+
await writer.flush();
|
|
213
491
|
|
|
214
492
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
215
493
|
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
);
|
|
494
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
495
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
219
496
|
const data = batch[0].chunkData.data.map((d) => {
|
|
220
497
|
return {
|
|
221
498
|
op: d.op,
|
|
@@ -232,12 +509,10 @@ bucket_definitions:
|
|
|
232
509
|
{ op: 'REMOVE', object_id: 'test1', checksum: c2 }
|
|
233
510
|
]);
|
|
234
511
|
|
|
235
|
-
const checksums = [
|
|
236
|
-
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
237
|
-
];
|
|
512
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
|
|
238
513
|
expect(checksums).toEqual([
|
|
239
514
|
{
|
|
240
|
-
bucket: bucketRequest(syncRules, 'global[]'),
|
|
515
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
241
516
|
checksum: (c1 + c2) & 0xffffffff,
|
|
242
517
|
count: 2
|
|
243
518
|
}
|
|
@@ -247,98 +522,96 @@ bucket_definitions:
|
|
|
247
522
|
test('re-apply update + delete', async () => {
|
|
248
523
|
await using factory = await generateStorageFactory();
|
|
249
524
|
const syncRules = await factory.updateSyncRules(
|
|
250
|
-
updateSyncRulesFromYaml(
|
|
525
|
+
updateSyncRulesFromYaml(
|
|
526
|
+
`
|
|
251
527
|
bucket_definitions:
|
|
252
528
|
global:
|
|
253
529
|
data:
|
|
254
530
|
- SELECT id, description FROM "%"
|
|
255
|
-
|
|
531
|
+
`,
|
|
532
|
+
{ storageVersion }
|
|
533
|
+
)
|
|
256
534
|
);
|
|
257
535
|
const bucketStorage = factory.getInstance(syncRules);
|
|
258
|
-
|
|
259
|
-
await
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
});
|
|
536
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
537
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
538
|
+
await writer.markAllSnapshotDone('1/1');
|
|
539
|
+
|
|
540
|
+
await writer.save({
|
|
541
|
+
sourceTable,
|
|
542
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
543
|
+
after: {
|
|
544
|
+
id: 'test1',
|
|
545
|
+
description: 'test1'
|
|
546
|
+
},
|
|
547
|
+
afterReplicaId: test_utils.rid('test1')
|
|
271
548
|
});
|
|
549
|
+
await writer.flush();
|
|
272
550
|
|
|
273
|
-
await
|
|
274
|
-
const sourceTable = TEST_TABLE;
|
|
275
|
-
|
|
276
|
-
await batch.save({
|
|
277
|
-
sourceTable,
|
|
278
|
-
tag: storage.SaveOperationTag.UPDATE,
|
|
279
|
-
after: {
|
|
280
|
-
id: 'test1',
|
|
281
|
-
description: undefined
|
|
282
|
-
},
|
|
283
|
-
afterReplicaId: test_utils.rid('test1')
|
|
284
|
-
});
|
|
551
|
+
await writer.markAllSnapshotDone('1/1');
|
|
285
552
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
553
|
+
await writer.save({
|
|
554
|
+
sourceTable,
|
|
555
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
556
|
+
after: {
|
|
557
|
+
id: 'test1',
|
|
558
|
+
description: undefined
|
|
559
|
+
},
|
|
560
|
+
afterReplicaId: test_utils.rid('test1')
|
|
561
|
+
});
|
|
295
562
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
563
|
+
await writer.save({
|
|
564
|
+
sourceTable,
|
|
565
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
566
|
+
after: {
|
|
567
|
+
id: 'test1',
|
|
568
|
+
description: undefined
|
|
569
|
+
},
|
|
570
|
+
afterReplicaId: test_utils.rid('test1')
|
|
571
|
+
});
|
|
301
572
|
|
|
302
|
-
|
|
573
|
+
await writer.save({
|
|
574
|
+
sourceTable,
|
|
575
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
576
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
303
577
|
});
|
|
304
578
|
|
|
305
|
-
await
|
|
306
|
-
const sourceTable = TEST_TABLE;
|
|
579
|
+
await writer.commit('1/1');
|
|
307
580
|
|
|
308
|
-
|
|
309
|
-
sourceTable,
|
|
310
|
-
tag: storage.SaveOperationTag.UPDATE,
|
|
311
|
-
after: {
|
|
312
|
-
id: 'test1',
|
|
313
|
-
description: undefined
|
|
314
|
-
},
|
|
315
|
-
afterReplicaId: test_utils.rid('test1')
|
|
316
|
-
});
|
|
581
|
+
await writer.markAllSnapshotDone('1/1');
|
|
317
582
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
583
|
+
await writer.save({
|
|
584
|
+
sourceTable,
|
|
585
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
586
|
+
after: {
|
|
587
|
+
id: 'test1',
|
|
588
|
+
description: undefined
|
|
589
|
+
},
|
|
590
|
+
afterReplicaId: test_utils.rid('test1')
|
|
591
|
+
});
|
|
327
592
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
593
|
+
await writer.save({
|
|
594
|
+
sourceTable,
|
|
595
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
596
|
+
after: {
|
|
597
|
+
id: 'test1',
|
|
598
|
+
description: undefined
|
|
599
|
+
},
|
|
600
|
+
afterReplicaId: test_utils.rid('test1')
|
|
601
|
+
});
|
|
333
602
|
|
|
334
|
-
|
|
603
|
+
await writer.save({
|
|
604
|
+
sourceTable,
|
|
605
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
606
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
335
607
|
});
|
|
336
608
|
|
|
609
|
+
await writer.commit('2/1');
|
|
610
|
+
|
|
337
611
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
338
612
|
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
);
|
|
613
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
614
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
342
615
|
|
|
343
616
|
const data = batch[0].chunkData.data.map((d) => {
|
|
344
617
|
return {
|
|
@@ -358,12 +631,10 @@ bucket_definitions:
|
|
|
358
631
|
{ op: 'REMOVE', object_id: 'test1', checksum: c2 }
|
|
359
632
|
]);
|
|
360
633
|
|
|
361
|
-
const checksums = [
|
|
362
|
-
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
363
|
-
];
|
|
634
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
|
|
364
635
|
expect(checksums).toEqual([
|
|
365
636
|
{
|
|
366
|
-
bucket: bucketRequest(syncRules, 'global[]'),
|
|
637
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
367
638
|
checksum: (c1 + c1 + c1 + c2) & 0xffffffff,
|
|
368
639
|
count: 4
|
|
369
640
|
}
|
|
@@ -381,127 +652,128 @@ bucket_definitions:
|
|
|
381
652
|
|
|
382
653
|
await using factory = await generateStorageFactory();
|
|
383
654
|
const syncRules = await factory.updateSyncRules(
|
|
384
|
-
updateSyncRulesFromYaml(
|
|
655
|
+
updateSyncRulesFromYaml(
|
|
656
|
+
`
|
|
385
657
|
bucket_definitions:
|
|
386
658
|
global:
|
|
387
659
|
data:
|
|
388
660
|
- SELECT id, description FROM "test"
|
|
389
|
-
|
|
661
|
+
`,
|
|
662
|
+
{ storageVersion }
|
|
663
|
+
)
|
|
390
664
|
);
|
|
391
665
|
const bucketStorage = factory.getInstance(syncRules);
|
|
666
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
667
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
392
668
|
|
|
393
669
|
// Pre-setup
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
});
|
|
670
|
+
await writer.markAllSnapshotDone('1/1');
|
|
671
|
+
|
|
672
|
+
await writer.save({
|
|
673
|
+
sourceTable,
|
|
674
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
675
|
+
after: {
|
|
676
|
+
id: 'test1',
|
|
677
|
+
description: 'test1a'
|
|
678
|
+
},
|
|
679
|
+
afterReplicaId: test_utils.rid('test1')
|
|
680
|
+
});
|
|
406
681
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
});
|
|
682
|
+
await writer.save({
|
|
683
|
+
sourceTable,
|
|
684
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
685
|
+
after: {
|
|
686
|
+
id: 'test2',
|
|
687
|
+
description: 'test2a'
|
|
688
|
+
},
|
|
689
|
+
afterReplicaId: test_utils.rid('test2')
|
|
416
690
|
});
|
|
691
|
+
const result1 = await writer.flush();
|
|
417
692
|
|
|
418
693
|
const checkpoint1 = result1?.flushed_op ?? 0n;
|
|
419
694
|
|
|
420
695
|
// Test batch
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
afterReplicaId: test_utils.rid('test1')
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
await batch.save({
|
|
435
|
-
sourceTable,
|
|
436
|
-
tag: storage.SaveOperationTag.UPDATE,
|
|
437
|
-
before: {
|
|
438
|
-
id: 'test1'
|
|
439
|
-
},
|
|
440
|
-
beforeReplicaId: test_utils.rid('test1'),
|
|
441
|
-
after: {
|
|
442
|
-
id: 'test2',
|
|
443
|
-
description: 'test2b'
|
|
444
|
-
},
|
|
445
|
-
afterReplicaId: test_utils.rid('test2')
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
await batch.save({
|
|
449
|
-
sourceTable,
|
|
450
|
-
tag: storage.SaveOperationTag.UPDATE,
|
|
451
|
-
before: {
|
|
452
|
-
id: 'test2'
|
|
453
|
-
},
|
|
454
|
-
beforeReplicaId: test_utils.rid('test2'),
|
|
455
|
-
after: {
|
|
456
|
-
id: 'test3',
|
|
457
|
-
description: 'test3b'
|
|
458
|
-
},
|
|
696
|
+
// b
|
|
697
|
+
await writer.save({
|
|
698
|
+
sourceTable,
|
|
699
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
700
|
+
after: {
|
|
701
|
+
id: 'test1',
|
|
702
|
+
description: 'test1b'
|
|
703
|
+
},
|
|
704
|
+
afterReplicaId: test_utils.rid('test1')
|
|
705
|
+
});
|
|
459
706
|
|
|
460
|
-
|
|
461
|
-
|
|
707
|
+
await writer.save({
|
|
708
|
+
sourceTable,
|
|
709
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
710
|
+
before: {
|
|
711
|
+
id: 'test1'
|
|
712
|
+
},
|
|
713
|
+
beforeReplicaId: test_utils.rid('test1'),
|
|
714
|
+
after: {
|
|
715
|
+
id: 'test2',
|
|
716
|
+
description: 'test2b'
|
|
717
|
+
},
|
|
718
|
+
afterReplicaId: test_utils.rid('test2')
|
|
719
|
+
});
|
|
462
720
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
721
|
+
await writer.save({
|
|
722
|
+
sourceTable,
|
|
723
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
724
|
+
before: {
|
|
725
|
+
id: 'test2'
|
|
726
|
+
},
|
|
727
|
+
beforeReplicaId: test_utils.rid('test2'),
|
|
728
|
+
after: {
|
|
729
|
+
id: 'test3',
|
|
730
|
+
description: 'test3b'
|
|
731
|
+
},
|
|
473
732
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
sourceTable,
|
|
477
|
-
tag: storage.SaveOperationTag.INSERT,
|
|
478
|
-
after: {
|
|
479
|
-
id: 'test4',
|
|
480
|
-
description: 'test4d'
|
|
481
|
-
},
|
|
482
|
-
afterReplicaId: test_utils.rid('test4')
|
|
483
|
-
});
|
|
733
|
+
afterReplicaId: test_utils.rid('test3')
|
|
734
|
+
});
|
|
484
735
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
736
|
+
// c
|
|
737
|
+
await writer.save({
|
|
738
|
+
sourceTable,
|
|
739
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
740
|
+
after: {
|
|
741
|
+
id: 'test2',
|
|
742
|
+
description: 'test2c'
|
|
743
|
+
},
|
|
744
|
+
afterReplicaId: test_utils.rid('test2')
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
// d
|
|
748
|
+
await writer.save({
|
|
749
|
+
sourceTable,
|
|
750
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
751
|
+
after: {
|
|
752
|
+
id: 'test4',
|
|
753
|
+
description: 'test4d'
|
|
754
|
+
},
|
|
755
|
+
afterReplicaId: test_utils.rid('test4')
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
await writer.save({
|
|
759
|
+
sourceTable,
|
|
760
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
761
|
+
before: {
|
|
762
|
+
id: 'test4'
|
|
763
|
+
},
|
|
764
|
+
beforeReplicaId: test_utils.rid('test4'),
|
|
765
|
+
after: {
|
|
766
|
+
id: 'test5',
|
|
767
|
+
description: 'test5d'
|
|
768
|
+
},
|
|
769
|
+
afterReplicaId: test_utils.rid('test5')
|
|
498
770
|
});
|
|
771
|
+
const result2 = await writer.flush();
|
|
499
772
|
|
|
500
773
|
const checkpoint2 = result2!.flushed_op;
|
|
501
774
|
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
);
|
|
775
|
+
const request = bucketRequest(syncRules, 'global[]', checkpoint1);
|
|
776
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint2, [request]));
|
|
505
777
|
|
|
506
778
|
const data = batch[0].chunkData.data.map((d) => {
|
|
507
779
|
return {
|
|
@@ -539,68 +811,73 @@ bucket_definitions:
|
|
|
539
811
|
}
|
|
540
812
|
await using factory = await generateStorageFactory();
|
|
541
813
|
const syncRules = await factory.updateSyncRules(
|
|
542
|
-
updateSyncRulesFromYaml(
|
|
814
|
+
updateSyncRulesFromYaml(
|
|
815
|
+
`
|
|
543
816
|
bucket_definitions:
|
|
544
817
|
global:
|
|
545
818
|
data:
|
|
546
819
|
- SELECT id, description FROM "test"
|
|
547
|
-
|
|
820
|
+
`,
|
|
821
|
+
{
|
|
822
|
+
storageVersion
|
|
823
|
+
}
|
|
824
|
+
)
|
|
548
825
|
);
|
|
549
826
|
const bucketStorage = factory.getInstance(syncRules);
|
|
550
827
|
|
|
551
|
-
|
|
828
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
829
|
+
const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
|
|
552
830
|
|
|
553
831
|
// Pre-setup
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
});
|
|
832
|
+
await writer.markAllSnapshotDone('1/1');
|
|
833
|
+
await writer.save({
|
|
834
|
+
sourceTable,
|
|
835
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
836
|
+
after: {
|
|
837
|
+
id: 'test1',
|
|
838
|
+
description: 'test1a'
|
|
839
|
+
},
|
|
840
|
+
afterReplicaId: rid2('test1', 'test1a')
|
|
564
841
|
});
|
|
842
|
+
const result1 = await writer.flush();
|
|
565
843
|
|
|
566
844
|
const checkpoint1 = result1?.flushed_op ?? 0n;
|
|
567
845
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
afterReplicaId: rid2('test1', 'test1b')
|
|
583
|
-
});
|
|
846
|
+
// Unchanged, but has a before id
|
|
847
|
+
await writer.save({
|
|
848
|
+
sourceTable,
|
|
849
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
850
|
+
before: {
|
|
851
|
+
id: 'test1',
|
|
852
|
+
description: 'test1a'
|
|
853
|
+
},
|
|
854
|
+
beforeReplicaId: rid2('test1', 'test1a'),
|
|
855
|
+
after: {
|
|
856
|
+
id: 'test1',
|
|
857
|
+
description: 'test1b'
|
|
858
|
+
},
|
|
859
|
+
afterReplicaId: rid2('test1', 'test1b')
|
|
584
860
|
});
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
});
|
|
861
|
+
const result2 = await writer.flush();
|
|
862
|
+
|
|
863
|
+
// Delete
|
|
864
|
+
await writer.save({
|
|
865
|
+
sourceTable,
|
|
866
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
867
|
+
before: {
|
|
868
|
+
id: 'test1',
|
|
869
|
+
description: 'test1b'
|
|
870
|
+
},
|
|
871
|
+
beforeReplicaId: rid2('test1', 'test1b'),
|
|
872
|
+
after: undefined
|
|
598
873
|
});
|
|
874
|
+
const result3 = await writer.flush();
|
|
599
875
|
|
|
600
876
|
const checkpoint3 = result3!.flushed_op;
|
|
601
877
|
|
|
878
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
602
879
|
const batch = await test_utils.fromAsync(
|
|
603
|
-
bucketStorage.getBucketDataBatch(checkpoint3,
|
|
880
|
+
bucketStorage.getBucketDataBatch(checkpoint3, [{ ...request, start: checkpoint1 }])
|
|
604
881
|
);
|
|
605
882
|
const data = batch[0].chunkData.data.map((d) => {
|
|
606
883
|
return {
|
|
@@ -647,68 +924,73 @@ bucket_definitions:
|
|
|
647
924
|
|
|
648
925
|
await using factory = await generateStorageFactory();
|
|
649
926
|
const syncRules = await factory.updateSyncRules(
|
|
650
|
-
updateSyncRulesFromYaml(
|
|
927
|
+
updateSyncRulesFromYaml(
|
|
928
|
+
`
|
|
651
929
|
bucket_definitions:
|
|
652
930
|
global:
|
|
653
931
|
data:
|
|
654
932
|
- SELECT id, description FROM "test"
|
|
655
|
-
|
|
933
|
+
`,
|
|
934
|
+
{
|
|
935
|
+
storageVersion
|
|
936
|
+
}
|
|
937
|
+
)
|
|
656
938
|
);
|
|
657
939
|
const bucketStorage = factory.getInstance(syncRules);
|
|
658
940
|
|
|
659
|
-
|
|
941
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
942
|
+
const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
|
|
660
943
|
|
|
661
944
|
// Pre-setup
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
});
|
|
945
|
+
await writer.markAllSnapshotDone('1/1');
|
|
946
|
+
await writer.save({
|
|
947
|
+
sourceTable,
|
|
948
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
949
|
+
after: {
|
|
950
|
+
id: 'test1',
|
|
951
|
+
description: 'test1a'
|
|
952
|
+
},
|
|
953
|
+
afterReplicaId: rid2('test1', 'test1a')
|
|
672
954
|
});
|
|
955
|
+
const result1 = await writer.flush();
|
|
673
956
|
|
|
674
957
|
const checkpoint1 = result1?.flushed_op ?? 0n;
|
|
675
958
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
afterReplicaId: rid2('test1', 'test1a')
|
|
691
|
-
});
|
|
959
|
+
// Unchanged, but has a before id
|
|
960
|
+
await writer.save({
|
|
961
|
+
sourceTable,
|
|
962
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
963
|
+
before: {
|
|
964
|
+
id: 'test1',
|
|
965
|
+
description: 'test1a'
|
|
966
|
+
},
|
|
967
|
+
beforeReplicaId: rid2('test1', 'test1a'),
|
|
968
|
+
after: {
|
|
969
|
+
id: 'test1',
|
|
970
|
+
description: 'test1a'
|
|
971
|
+
},
|
|
972
|
+
afterReplicaId: rid2('test1', 'test1a')
|
|
692
973
|
});
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
});
|
|
974
|
+
const result2 = await writer.flush();
|
|
975
|
+
|
|
976
|
+
// Delete
|
|
977
|
+
await writer.save({
|
|
978
|
+
sourceTable,
|
|
979
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
980
|
+
before: {
|
|
981
|
+
id: 'test1',
|
|
982
|
+
description: 'test1a'
|
|
983
|
+
},
|
|
984
|
+
beforeReplicaId: rid2('test1', 'test1a'),
|
|
985
|
+
after: undefined
|
|
706
986
|
});
|
|
987
|
+
const result3 = await writer.flush();
|
|
707
988
|
|
|
708
989
|
const checkpoint3 = result3!.flushed_op;
|
|
709
990
|
|
|
991
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
710
992
|
const batch = await test_utils.fromAsync(
|
|
711
|
-
bucketStorage.getBucketDataBatch(checkpoint3,
|
|
993
|
+
bucketStorage.getBucketDataBatch(checkpoint3, [{ ...request, start: checkpoint1 }])
|
|
712
994
|
);
|
|
713
995
|
const data = batch[0].chunkData.data.map((d) => {
|
|
714
996
|
return {
|
|
@@ -745,73 +1027,76 @@ bucket_definitions:
|
|
|
745
1027
|
// and the test will have to updated when other implementations are added.
|
|
746
1028
|
await using factory = await generateStorageFactory();
|
|
747
1029
|
const syncRules = await factory.updateSyncRules(
|
|
748
|
-
updateSyncRulesFromYaml(
|
|
1030
|
+
updateSyncRulesFromYaml(
|
|
1031
|
+
`
|
|
749
1032
|
bucket_definitions:
|
|
750
1033
|
global:
|
|
751
1034
|
data:
|
|
752
1035
|
- SELECT id, description FROM "%"
|
|
753
|
-
|
|
1036
|
+
`,
|
|
1037
|
+
{
|
|
1038
|
+
storageVersion
|
|
1039
|
+
}
|
|
1040
|
+
)
|
|
754
1041
|
);
|
|
755
1042
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1043
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1044
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
1045
|
+
await writer.markAllSnapshotDone('1/1');
|
|
1046
|
+
|
|
1047
|
+
const largeDescription = '0123456789'.repeat(12_000_00);
|
|
1048
|
+
|
|
1049
|
+
await writer.save({
|
|
1050
|
+
sourceTable,
|
|
1051
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1052
|
+
after: {
|
|
1053
|
+
id: 'test1',
|
|
1054
|
+
description: 'test1'
|
|
1055
|
+
},
|
|
1056
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1057
|
+
});
|
|
756
1058
|
|
|
757
|
-
await
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
id: 'test1',
|
|
767
|
-
description: 'test1'
|
|
768
|
-
},
|
|
769
|
-
afterReplicaId: test_utils.rid('test1')
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
await batch.save({
|
|
773
|
-
sourceTable,
|
|
774
|
-
tag: storage.SaveOperationTag.INSERT,
|
|
775
|
-
after: {
|
|
776
|
-
id: 'large1',
|
|
777
|
-
description: largeDescription
|
|
778
|
-
},
|
|
779
|
-
afterReplicaId: test_utils.rid('large1')
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// Large enough to split the returned batch
|
|
783
|
-
await batch.save({
|
|
784
|
-
sourceTable,
|
|
785
|
-
tag: storage.SaveOperationTag.INSERT,
|
|
786
|
-
after: {
|
|
787
|
-
id: 'large2',
|
|
788
|
-
description: largeDescription
|
|
789
|
-
},
|
|
790
|
-
afterReplicaId: test_utils.rid('large2')
|
|
791
|
-
});
|
|
1059
|
+
await writer.save({
|
|
1060
|
+
sourceTable,
|
|
1061
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1062
|
+
after: {
|
|
1063
|
+
id: 'large1',
|
|
1064
|
+
description: largeDescription
|
|
1065
|
+
},
|
|
1066
|
+
afterReplicaId: test_utils.rid('large1')
|
|
1067
|
+
});
|
|
792
1068
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1069
|
+
// Large enough to split the returned batch
|
|
1070
|
+
await writer.save({
|
|
1071
|
+
sourceTable,
|
|
1072
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1073
|
+
after: {
|
|
1074
|
+
id: 'large2',
|
|
1075
|
+
description: largeDescription
|
|
1076
|
+
},
|
|
1077
|
+
afterReplicaId: test_utils.rid('large2')
|
|
1078
|
+
});
|
|
802
1079
|
|
|
803
|
-
|
|
1080
|
+
await writer.save({
|
|
1081
|
+
sourceTable,
|
|
1082
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1083
|
+
after: {
|
|
1084
|
+
id: 'test3',
|
|
1085
|
+
description: 'test3'
|
|
1086
|
+
},
|
|
1087
|
+
afterReplicaId: test_utils.rid('test3')
|
|
804
1088
|
});
|
|
805
1089
|
|
|
1090
|
+
await writer.commit('1/1');
|
|
1091
|
+
|
|
806
1092
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
807
1093
|
|
|
808
1094
|
const options: storage.BucketDataBatchOptions = {
|
|
809
1095
|
chunkLimitBytes: 16 * 1024 * 1024
|
|
810
1096
|
};
|
|
811
1097
|
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
);
|
|
1098
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
1099
|
+
const batch1 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request], options));
|
|
815
1100
|
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
816
1101
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
|
|
817
1102
|
{ op_id: '2', op: 'PUT', object_id: 'large1', checksum: 454746904 }
|
|
@@ -825,7 +1110,7 @@ bucket_definitions:
|
|
|
825
1110
|
const batch2 = await test_utils.fromAsync(
|
|
826
1111
|
bucketStorage.getBucketDataBatch(
|
|
827
1112
|
checkpoint,
|
|
828
|
-
|
|
1113
|
+
[{ ...request, start: BigInt(batch1[0].chunkData.next_after) }],
|
|
829
1114
|
options
|
|
830
1115
|
)
|
|
831
1116
|
);
|
|
@@ -842,7 +1127,7 @@ bucket_definitions:
|
|
|
842
1127
|
const batch3 = await test_utils.fromAsync(
|
|
843
1128
|
bucketStorage.getBucketDataBatch(
|
|
844
1129
|
checkpoint,
|
|
845
|
-
|
|
1130
|
+
[{ ...request, start: BigInt(batch2[0].chunkData.next_after) }],
|
|
846
1131
|
options
|
|
847
1132
|
)
|
|
848
1133
|
);
|
|
@@ -854,38 +1139,41 @@ bucket_definitions:
|
|
|
854
1139
|
// Test syncing a batch of data that is limited by count.
|
|
855
1140
|
await using factory = await generateStorageFactory();
|
|
856
1141
|
const syncRules = await factory.updateSyncRules(
|
|
857
|
-
updateSyncRulesFromYaml(
|
|
1142
|
+
updateSyncRulesFromYaml(
|
|
1143
|
+
`
|
|
858
1144
|
bucket_definitions:
|
|
859
1145
|
global:
|
|
860
1146
|
data:
|
|
861
1147
|
- SELECT id, description FROM "%"
|
|
862
|
-
|
|
1148
|
+
`,
|
|
1149
|
+
{
|
|
1150
|
+
storageVersion
|
|
1151
|
+
}
|
|
1152
|
+
)
|
|
863
1153
|
);
|
|
864
1154
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1155
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1156
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
1157
|
+
await writer.markAllSnapshotDone('1/1');
|
|
865
1158
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
afterReplicaId: `test${i}`
|
|
878
|
-
});
|
|
879
|
-
}
|
|
1159
|
+
for (let i = 1; i <= 6; i++) {
|
|
1160
|
+
await writer.save({
|
|
1161
|
+
sourceTable,
|
|
1162
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1163
|
+
after: {
|
|
1164
|
+
id: `test${i}`,
|
|
1165
|
+
description: `test${i}`
|
|
1166
|
+
},
|
|
1167
|
+
afterReplicaId: `test${i}`
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
880
1170
|
|
|
881
|
-
|
|
882
|
-
});
|
|
1171
|
+
await writer.commit('1/1');
|
|
883
1172
|
|
|
884
1173
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
885
1174
|
|
|
886
|
-
const
|
|
887
|
-
|
|
888
|
-
);
|
|
1175
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
1176
|
+
const batch1 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request], { limit: 4 }));
|
|
889
1177
|
|
|
890
1178
|
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
891
1179
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
|
|
@@ -901,13 +1189,9 @@ bucket_definitions:
|
|
|
901
1189
|
});
|
|
902
1190
|
|
|
903
1191
|
const batch2 = await test_utils.oneFromAsync(
|
|
904
|
-
bucketStorage.getBucketDataBatch(
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
{
|
|
908
|
-
limit: 4
|
|
909
|
-
}
|
|
910
|
-
)
|
|
1192
|
+
bucketStorage.getBucketDataBatch(checkpoint, [{ ...request, start: BigInt(batch1.chunkData.next_after) }], {
|
|
1193
|
+
limit: 4
|
|
1194
|
+
})
|
|
911
1195
|
);
|
|
912
1196
|
expect(test_utils.getBatchData(batch2)).toEqual([
|
|
913
1197
|
{ op_id: '5', op: 'PUT', object_id: 'test5', checksum: 3686902721 },
|
|
@@ -921,13 +1205,9 @@ bucket_definitions:
|
|
|
921
1205
|
});
|
|
922
1206
|
|
|
923
1207
|
const batch3 = await test_utils.fromAsync(
|
|
924
|
-
bucketStorage.getBucketDataBatch(
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
{
|
|
928
|
-
limit: 4
|
|
929
|
-
}
|
|
930
|
-
)
|
|
1208
|
+
bucketStorage.getBucketDataBatch(checkpoint, [{ ...request, start: BigInt(batch2.chunkData.next_after) }], {
|
|
1209
|
+
limit: 4
|
|
1210
|
+
})
|
|
931
1211
|
);
|
|
932
1212
|
expect(test_utils.getBatchData(batch3)).toEqual([]);
|
|
933
1213
|
|
|
@@ -938,7 +1218,8 @@ bucket_definitions:
|
|
|
938
1218
|
const setup = async (options: BucketDataBatchOptions) => {
|
|
939
1219
|
await using factory = await generateStorageFactory();
|
|
940
1220
|
const syncRules = await factory.updateSyncRules(
|
|
941
|
-
updateSyncRulesFromYaml(
|
|
1221
|
+
updateSyncRulesFromYaml(
|
|
1222
|
+
`
|
|
942
1223
|
bucket_definitions:
|
|
943
1224
|
global1:
|
|
944
1225
|
data:
|
|
@@ -946,50 +1227,46 @@ bucket_definitions:
|
|
|
946
1227
|
global2:
|
|
947
1228
|
data:
|
|
948
1229
|
- SELECT id, description FROM test WHERE bucket = 'global2'
|
|
949
|
-
|
|
1230
|
+
`,
|
|
1231
|
+
{ storageVersion }
|
|
1232
|
+
)
|
|
950
1233
|
);
|
|
951
1234
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1235
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1236
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
1237
|
+
await writer.markAllSnapshotDone('1/1');
|
|
952
1238
|
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
afterReplicaId: `test${i}`
|
|
966
|
-
});
|
|
967
|
-
}
|
|
1239
|
+
for (let i = 1; i <= 10; i++) {
|
|
1240
|
+
await writer.save({
|
|
1241
|
+
sourceTable,
|
|
1242
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1243
|
+
after: {
|
|
1244
|
+
id: `test${i}`,
|
|
1245
|
+
description: `test${i}`,
|
|
1246
|
+
bucket: i == 1 ? 'global1' : 'global2'
|
|
1247
|
+
},
|
|
1248
|
+
afterReplicaId: `test${i}`
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
968
1251
|
|
|
969
|
-
|
|
970
|
-
});
|
|
1252
|
+
await writer.commit('1/1');
|
|
971
1253
|
|
|
972
1254
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
1255
|
+
const global1Request = bucketRequest(syncRules, 'global1[]', 0n);
|
|
1256
|
+
const global2Request = bucketRequest(syncRules, 'global2[]', 0n);
|
|
973
1257
|
const batch = await test_utils.fromAsync(
|
|
974
|
-
bucketStorage.getBucketDataBatch(
|
|
975
|
-
checkpoint,
|
|
976
|
-
bucketRequestMap(syncRules, [
|
|
977
|
-
['global1[]', 0n],
|
|
978
|
-
['global2[]', 0n]
|
|
979
|
-
]),
|
|
980
|
-
options
|
|
981
|
-
)
|
|
1258
|
+
bucketStorage.getBucketDataBatch(checkpoint, [global1Request, global2Request], options)
|
|
982
1259
|
);
|
|
983
1260
|
|
|
984
|
-
return {
|
|
1261
|
+
return { batch, global1Request, global2Request };
|
|
985
1262
|
};
|
|
986
1263
|
|
|
987
1264
|
test('batch has_more (1)', async () => {
|
|
988
|
-
const { batch,
|
|
1265
|
+
const { batch, global1Request, global2Request } = await setup({ limit: 5 });
|
|
989
1266
|
expect(batch.length).toEqual(2);
|
|
990
1267
|
|
|
991
|
-
expect(batch[0].chunkData.bucket).toEqual(
|
|
992
|
-
expect(batch[1].chunkData.bucket).toEqual(
|
|
1268
|
+
expect(batch[0].chunkData.bucket).toEqual(global1Request.bucket);
|
|
1269
|
+
expect(batch[1].chunkData.bucket).toEqual(global2Request.bucket);
|
|
993
1270
|
|
|
994
1271
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
995
1272
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
@@ -1016,11 +1293,11 @@ bucket_definitions:
|
|
|
1016
1293
|
});
|
|
1017
1294
|
|
|
1018
1295
|
test('batch has_more (2)', async () => {
|
|
1019
|
-
const { batch,
|
|
1296
|
+
const { batch, global1Request, global2Request } = await setup({ limit: 11 });
|
|
1020
1297
|
expect(batch.length).toEqual(2);
|
|
1021
1298
|
|
|
1022
|
-
expect(batch[0].chunkData.bucket).toEqual(
|
|
1023
|
-
expect(batch[1].chunkData.bucket).toEqual(
|
|
1299
|
+
expect(batch[0].chunkData.bucket).toEqual(global1Request.bucket);
|
|
1300
|
+
expect(batch[1].chunkData.bucket).toEqual(global2Request.bucket);
|
|
1024
1301
|
|
|
1025
1302
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
1026
1303
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
@@ -1053,12 +1330,12 @@ bucket_definitions:
|
|
|
1053
1330
|
|
|
1054
1331
|
test('batch has_more (3)', async () => {
|
|
1055
1332
|
// 50 bytes is more than 1 row, less than 2 rows
|
|
1056
|
-
const { batch,
|
|
1333
|
+
const { batch, global1Request, global2Request } = await setup({ limit: 3, chunkLimitBytes: 50 });
|
|
1057
1334
|
|
|
1058
1335
|
expect(batch.length).toEqual(3);
|
|
1059
|
-
expect(batch[0].chunkData.bucket).toEqual(
|
|
1060
|
-
expect(batch[1].chunkData.bucket).toEqual(
|
|
1061
|
-
expect(batch[2].chunkData.bucket).toEqual(
|
|
1336
|
+
expect(batch[0].chunkData.bucket).toEqual(global1Request.bucket);
|
|
1337
|
+
expect(batch[1].chunkData.bucket).toEqual(global2Request.bucket);
|
|
1338
|
+
expect(batch[2].chunkData.bucket).toEqual(global2Request.bucket);
|
|
1062
1339
|
|
|
1063
1340
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
1064
1341
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
@@ -1102,9 +1379,9 @@ bucket_definitions:
|
|
|
1102
1379
|
|
|
1103
1380
|
const r = await f.configureSyncRules(updateSyncRulesFromYaml('bucket_definitions: {}'));
|
|
1104
1381
|
const storage = f.getInstance(r.persisted_sync_rules!);
|
|
1105
|
-
await storage.
|
|
1106
|
-
|
|
1107
|
-
|
|
1382
|
+
await using writer = await storage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1383
|
+
await writer.markAllSnapshotDone('1/0');
|
|
1384
|
+
await writer.keepalive('1/0');
|
|
1108
1385
|
|
|
1109
1386
|
await f.getStorageMetrics();
|
|
1110
1387
|
// We don't care about the specific values here
|
|
@@ -1116,44 +1393,49 @@ bucket_definitions:
|
|
|
1116
1393
|
// Similar to the above test, but splits over 1MB chunks.
|
|
1117
1394
|
await using factory = await generateStorageFactory();
|
|
1118
1395
|
const syncRules = await factory.updateSyncRules(
|
|
1119
|
-
updateSyncRulesFromYaml(
|
|
1396
|
+
updateSyncRulesFromYaml(
|
|
1397
|
+
`
|
|
1120
1398
|
bucket_definitions:
|
|
1121
1399
|
global:
|
|
1122
1400
|
data:
|
|
1123
1401
|
- SELECT id FROM test
|
|
1124
1402
|
- SELECT id FROM test_ignore WHERE false
|
|
1125
|
-
|
|
1403
|
+
`,
|
|
1404
|
+
{
|
|
1405
|
+
storageVersion
|
|
1406
|
+
}
|
|
1407
|
+
)
|
|
1126
1408
|
);
|
|
1127
1409
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1410
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1411
|
+
|
|
1412
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config, 1);
|
|
1413
|
+
const sourceTableIgnore = await test_utils.resolveTestTable(writer, 'test_ignore', ['id'], config, 2);
|
|
1414
|
+
|
|
1415
|
+
await writer.markAllSnapshotDone('1/1');
|
|
1416
|
+
// This saves a record to current_data, but not bucket_data.
|
|
1417
|
+
// This causes a checkpoint to be created without increasing the op_id sequence.
|
|
1418
|
+
await writer.save({
|
|
1419
|
+
sourceTable: sourceTableIgnore,
|
|
1420
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1421
|
+
after: {
|
|
1422
|
+
id: 'test1'
|
|
1423
|
+
},
|
|
1424
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1143
1425
|
});
|
|
1426
|
+
const result1 = await writer.flush();
|
|
1144
1427
|
|
|
1145
1428
|
const checkpoint1 = result1!.flushed_op;
|
|
1146
1429
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
afterReplicaId: test_utils.rid('test2')
|
|
1155
|
-
});
|
|
1430
|
+
await writer.save({
|
|
1431
|
+
sourceTable: sourceTable,
|
|
1432
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1433
|
+
after: {
|
|
1434
|
+
id: 'test2'
|
|
1435
|
+
},
|
|
1436
|
+
afterReplicaId: test_utils.rid('test2')
|
|
1156
1437
|
});
|
|
1438
|
+
const result2 = await writer.flush();
|
|
1157
1439
|
|
|
1158
1440
|
const checkpoint2 = result2!.flushed_op;
|
|
1159
1441
|
// we expect 0n and 1n, or 1n and 2n.
|
|
@@ -1163,41 +1445,242 @@ bucket_definitions:
|
|
|
1163
1445
|
test('unchanged checksums', async () => {
|
|
1164
1446
|
await using factory = await generateStorageFactory();
|
|
1165
1447
|
const syncRules = await factory.updateSyncRules(
|
|
1166
|
-
updateSyncRulesFromYaml(
|
|
1448
|
+
updateSyncRulesFromYaml(
|
|
1449
|
+
`
|
|
1167
1450
|
bucket_definitions:
|
|
1168
1451
|
global:
|
|
1169
1452
|
data:
|
|
1170
1453
|
- SELECT client_id as id, description FROM "%"
|
|
1171
|
-
|
|
1454
|
+
`,
|
|
1455
|
+
{
|
|
1456
|
+
storageVersion
|
|
1457
|
+
}
|
|
1458
|
+
)
|
|
1172
1459
|
);
|
|
1173
1460
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
await
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
await batch.commit('1/1');
|
|
1461
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1462
|
+
|
|
1463
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
1464
|
+
await writer.markAllSnapshotDone('1/1');
|
|
1465
|
+
await writer.save({
|
|
1466
|
+
sourceTable,
|
|
1467
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1468
|
+
after: {
|
|
1469
|
+
id: 'test1',
|
|
1470
|
+
description: 'test1a'
|
|
1471
|
+
},
|
|
1472
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1187
1473
|
});
|
|
1474
|
+
await writer.commit('1/1');
|
|
1188
1475
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
1189
1476
|
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1192
|
-
];
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1477
|
+
const request = bucketRequest(syncRules, 'global[]');
|
|
1478
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
|
|
1479
|
+
expect(checksums).toEqual([{ bucket: request.bucket, checksum: 1917136889, count: 1 }]);
|
|
1480
|
+
const checksums2 = [...(await bucketStorage.getChecksums(checkpoint + 1n, [request])).values()];
|
|
1481
|
+
expect(checksums2).toEqual([{ bucket: request.bucket, checksum: 1917136889, count: 1 }]);
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
testChecksumBatching(config);
|
|
1485
|
+
|
|
1486
|
+
test('empty checkpoints (1)', async () => {
|
|
1487
|
+
await using factory = await generateStorageFactory();
|
|
1488
|
+
const syncRules = await factory.updateSyncRules(
|
|
1489
|
+
updateSyncRulesFromYaml(
|
|
1490
|
+
`
|
|
1491
|
+
bucket_definitions:
|
|
1492
|
+
global:
|
|
1493
|
+
data:
|
|
1494
|
+
- SELECT id, description FROM "%"
|
|
1495
|
+
`,
|
|
1496
|
+
{ storageVersion }
|
|
1497
|
+
)
|
|
1498
|
+
);
|
|
1499
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1500
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1501
|
+
await writer.markAllSnapshotDone('1/1');
|
|
1502
|
+
await writer.commit('1/1');
|
|
1503
|
+
|
|
1504
|
+
const cp1 = await bucketStorage.getCheckpoint();
|
|
1505
|
+
expect(cp1.lsn).toEqual('1/1');
|
|
1506
|
+
|
|
1507
|
+
await writer.commit('2/1', { createEmptyCheckpoints: true });
|
|
1508
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1509
|
+
expect(cp2.lsn).toEqual('2/1');
|
|
1510
|
+
|
|
1511
|
+
await writer.keepalive('3/1');
|
|
1512
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1513
|
+
expect(cp3.lsn).toEqual('3/1');
|
|
1514
|
+
|
|
1515
|
+
// For the last one, we skip creating empty checkpoints
|
|
1516
|
+
// This means the LSN stays at 3/1.
|
|
1517
|
+
await writer.commit('4/1', { createEmptyCheckpoints: false });
|
|
1518
|
+
const cp4 = await bucketStorage.getCheckpoint();
|
|
1519
|
+
expect(cp4.lsn).toEqual('3/1');
|
|
1520
|
+
});
|
|
1521
|
+
|
|
1522
|
+
test('empty checkpoints (2)', async () => {
|
|
1523
|
+
await using factory = await generateStorageFactory();
|
|
1524
|
+
const syncRules = await factory.updateSyncRules(
|
|
1525
|
+
updateSyncRulesFromYaml(
|
|
1526
|
+
`
|
|
1527
|
+
bucket_definitions:
|
|
1528
|
+
global:
|
|
1529
|
+
data:
|
|
1530
|
+
- SELECT id, description FROM "%"
|
|
1531
|
+
`,
|
|
1532
|
+
{
|
|
1533
|
+
storageVersion
|
|
1534
|
+
}
|
|
1535
|
+
)
|
|
1536
|
+
);
|
|
1537
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1538
|
+
await using writer1 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1539
|
+
await using writer2 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1540
|
+
const sourceTable = await test_utils.resolveTestTable(writer2, 'test', ['id'], config);
|
|
1541
|
+
|
|
1542
|
+
// We simulate two concurrent batches, but sequential calls are enough for this test.
|
|
1543
|
+
await writer1.markAllSnapshotDone('1/1');
|
|
1544
|
+
await writer1.commit('1/1');
|
|
1545
|
+
|
|
1546
|
+
await writer1.commit('2/1', { createEmptyCheckpoints: false });
|
|
1547
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1548
|
+
expect(cp2.lsn).toEqual('1/1'); // checkpoint 2/1 skipped
|
|
1549
|
+
|
|
1550
|
+
await writer2.save({
|
|
1551
|
+
sourceTable,
|
|
1552
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1553
|
+
after: {
|
|
1554
|
+
id: 'test1',
|
|
1555
|
+
description: 'test1a'
|
|
1556
|
+
},
|
|
1557
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1558
|
+
});
|
|
1559
|
+
// This simulates what happens on a snapshot processor.
|
|
1560
|
+
// This may later change to a flush() rather than commit().
|
|
1561
|
+
await writer2.commit(test_utils.BATCH_OPTIONS.zeroLSN);
|
|
1562
|
+
|
|
1563
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1564
|
+
expect(cp3.lsn).toEqual('1/1'); // Still unchanged
|
|
1565
|
+
|
|
1566
|
+
// This now needs to advance the LSN, despite {createEmptyCheckpoints: false}
|
|
1567
|
+
await writer1.commit('4/1', { createEmptyCheckpoints: false });
|
|
1568
|
+
const cp4 = await bucketStorage.getCheckpoint();
|
|
1569
|
+
expect(cp4.lsn).toEqual('4/1');
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
test('empty checkpoints (sync rule activation)', async () => {
|
|
1573
|
+
await using factory = await generateStorageFactory();
|
|
1574
|
+
const syncRules = await factory.updateSyncRules(
|
|
1575
|
+
updateSyncRulesFromYaml(
|
|
1576
|
+
`
|
|
1577
|
+
bucket_definitions:
|
|
1578
|
+
global:
|
|
1579
|
+
data:
|
|
1580
|
+
- SELECT id, description FROM "%"
|
|
1581
|
+
`,
|
|
1582
|
+
{
|
|
1583
|
+
storageVersion
|
|
1584
|
+
}
|
|
1585
|
+
)
|
|
1586
|
+
);
|
|
1587
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1588
|
+
|
|
1589
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1590
|
+
const result1 = await writer.commit('1/1', { createEmptyCheckpoints: false });
|
|
1591
|
+
expect(result1).toEqual({ checkpointBlocked: true, checkpointCreated: false });
|
|
1592
|
+
// Snapshot is only valid once we reach 3/1
|
|
1593
|
+
await writer.markAllSnapshotDone('3/1');
|
|
1594
|
+
|
|
1595
|
+
// 2/1 < 3/1 - snapshot not valid yet, block checkpoint
|
|
1596
|
+
const result2 = await writer.commit('2/1', { createEmptyCheckpoints: false });
|
|
1597
|
+
expect(result2).toEqual({ checkpointBlocked: true, checkpointCreated: false });
|
|
1598
|
+
|
|
1599
|
+
// No empty checkpoint should be created by the commit above.
|
|
1600
|
+
const cp1 = await bucketStorage.getCheckpoint();
|
|
1601
|
+
expect(cp1.lsn).toEqual(null);
|
|
1602
|
+
|
|
1603
|
+
// After this commit, the snapshot should be valid.
|
|
1604
|
+
// We specifically check that this is done even if createEmptyCheckpoints: false.
|
|
1605
|
+
const result3 = await writer.commit('3/1', { createEmptyCheckpoints: false });
|
|
1606
|
+
expect(result3).toEqual({ checkpointBlocked: false, checkpointCreated: true });
|
|
1607
|
+
|
|
1608
|
+
// Now, the checkpoint should advance the sync rules active.
|
|
1609
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1610
|
+
expect(cp2.lsn).toEqual('3/1');
|
|
1611
|
+
|
|
1612
|
+
const activeSyncRules = await factory.getActiveSyncRulesContent();
|
|
1613
|
+
expect(activeSyncRules?.id).toEqual(syncRules.id);
|
|
1614
|
+
|
|
1615
|
+
// At this point, it should be a truely empty checkpoint
|
|
1616
|
+
const result4 = await writer.commit('4/1', { createEmptyCheckpoints: false });
|
|
1617
|
+
expect(result4).toEqual({ checkpointBlocked: false, checkpointCreated: false });
|
|
1618
|
+
|
|
1619
|
+
// Unchanged
|
|
1620
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1621
|
+
expect(cp3.lsn).toEqual('3/1');
|
|
1198
1622
|
});
|
|
1199
1623
|
|
|
1200
|
-
|
|
1624
|
+
test.runIf(storageVersion >= 3)('deleting while streaming', async () => {
|
|
1625
|
+
await using factory = await generateStorageFactory();
|
|
1626
|
+
const syncRules = await factory.updateSyncRules(
|
|
1627
|
+
updateSyncRulesFromYaml(
|
|
1628
|
+
`
|
|
1629
|
+
bucket_definitions:
|
|
1630
|
+
global:
|
|
1631
|
+
data:
|
|
1632
|
+
- SELECT id, description FROM "%"
|
|
1633
|
+
`,
|
|
1634
|
+
{
|
|
1635
|
+
storageVersion
|
|
1636
|
+
}
|
|
1637
|
+
)
|
|
1638
|
+
);
|
|
1639
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1640
|
+
await using snapshotWriter = await bucketStorage.createWriter({
|
|
1641
|
+
...test_utils.BATCH_OPTIONS,
|
|
1642
|
+
skipExistingRows: true
|
|
1643
|
+
});
|
|
1644
|
+
await using streamingWriter = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1645
|
+
const snapshotTable = await test_utils.resolveTestTable(snapshotWriter, 'test', ['id'], config, 1);
|
|
1646
|
+
const streamingTable = await test_utils.resolveTestTable(streamingWriter, 'test', ['id'], config, 1);
|
|
1647
|
+
|
|
1648
|
+
// We simulate two concurrent batches; separate writers are enough for this test.
|
|
1649
|
+
// For this test, we assume that we start with a row "test1", which is picked up by a snapshot
|
|
1650
|
+
// query, right before the delete is streamed. But the snapshot query is only persisted _after_
|
|
1651
|
+
// the delete is streamed, and we need to ensure that the streamed delete takes precedence.
|
|
1652
|
+
await streamingWriter.save({
|
|
1653
|
+
sourceTable: streamingTable,
|
|
1654
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
1655
|
+
before: {
|
|
1656
|
+
id: 'test1'
|
|
1657
|
+
},
|
|
1658
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
1659
|
+
});
|
|
1660
|
+
await streamingWriter.commit('2/1');
|
|
1661
|
+
|
|
1662
|
+
await snapshotWriter.save({
|
|
1663
|
+
sourceTable: snapshotTable,
|
|
1664
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1665
|
+
after: {
|
|
1666
|
+
id: 'test1',
|
|
1667
|
+
description: 'test1a'
|
|
1668
|
+
},
|
|
1669
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1670
|
+
});
|
|
1671
|
+
await snapshotWriter.markAllSnapshotDone('3/1');
|
|
1672
|
+
await snapshotWriter.commit('1/1');
|
|
1673
|
+
|
|
1674
|
+
await streamingWriter.keepalive('3/1');
|
|
1675
|
+
|
|
1676
|
+
const cp = await bucketStorage.getCheckpoint();
|
|
1677
|
+
expect(cp.lsn).toEqual('3/1');
|
|
1678
|
+
const data = await test_utils.fromAsync(
|
|
1679
|
+
bucketStorage.getBucketDataBatch(cp.checkpoint, [bucketRequest(syncRules, 'global[]')])
|
|
1680
|
+
);
|
|
1681
|
+
|
|
1682
|
+
expect(data).toEqual([]);
|
|
1683
|
+
});
|
|
1201
1684
|
}
|
|
1202
1685
|
|
|
1203
1686
|
/**
|
|
@@ -1205,50 +1688,58 @@ bucket_definitions:
|
|
|
1205
1688
|
*
|
|
1206
1689
|
* Exposed as a separate test so we can test with more storage parameters.
|
|
1207
1690
|
*/
|
|
1208
|
-
export function testChecksumBatching(
|
|
1691
|
+
export function testChecksumBatching(config: storage.TestStorageConfig) {
|
|
1692
|
+
const storageVersion = config.storageVersion ?? CURRENT_STORAGE_VERSION;
|
|
1209
1693
|
test('checksums for multiple buckets', async () => {
|
|
1210
|
-
await using factory = await
|
|
1694
|
+
await using factory = await config.factory();
|
|
1211
1695
|
const syncRules = await factory.updateSyncRules(
|
|
1212
|
-
updateSyncRulesFromYaml(
|
|
1696
|
+
updateSyncRulesFromYaml(
|
|
1697
|
+
`
|
|
1213
1698
|
bucket_definitions:
|
|
1214
1699
|
user:
|
|
1215
1700
|
parameters: select request.user_id() as user_id
|
|
1216
1701
|
data:
|
|
1217
1702
|
- select id, description from test where user_id = bucket.user_id
|
|
1218
|
-
|
|
1703
|
+
`,
|
|
1704
|
+
{
|
|
1705
|
+
storageVersion
|
|
1706
|
+
}
|
|
1707
|
+
)
|
|
1219
1708
|
);
|
|
1220
1709
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1221
|
-
|
|
1222
|
-
const sourceTable =
|
|
1223
|
-
await
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
}
|
|
1710
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1711
|
+
const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
1712
|
+
await writer.markAllSnapshotDone('1/1');
|
|
1713
|
+
for (let u of ['u1', 'u2', 'u3', 'u4']) {
|
|
1714
|
+
for (let t of ['t1', 't2', 't3', 't4']) {
|
|
1715
|
+
const id = `${t}_${u}`;
|
|
1716
|
+
await writer.save({
|
|
1717
|
+
sourceTable,
|
|
1718
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1719
|
+
after: {
|
|
1720
|
+
id,
|
|
1721
|
+
description: `${t} description`,
|
|
1722
|
+
user_id: u
|
|
1723
|
+
},
|
|
1724
|
+
afterReplicaId: test_utils.rid(id)
|
|
1725
|
+
});
|
|
1238
1726
|
}
|
|
1239
|
-
|
|
1240
|
-
|
|
1727
|
+
}
|
|
1728
|
+
await writer.commit('1/1');
|
|
1241
1729
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
1242
1730
|
|
|
1243
1731
|
bucketStorage.clearChecksumCache();
|
|
1244
|
-
const
|
|
1245
|
-
const
|
|
1732
|
+
const users = ['u1', 'u2', 'u3', 'u4'];
|
|
1733
|
+
const expectedChecksums = [346204588, 5261081, 134760718, -302639724];
|
|
1734
|
+
const bucketRequests = users.map((user) => bucketRequest(syncRules, `user["${user}"]`));
|
|
1735
|
+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, bucketRequests)).values()];
|
|
1246
1736
|
checksums.sort((a, b) => a.bucket.localeCompare(b.bucket));
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1737
|
+
const expected = bucketRequests.map((request, index) => ({
|
|
1738
|
+
bucket: request.bucket,
|
|
1739
|
+
count: 4,
|
|
1740
|
+
checksum: expectedChecksums[index]
|
|
1741
|
+
}));
|
|
1742
|
+
expected.sort((a, b) => a.bucket.localeCompare(b.bucket));
|
|
1743
|
+
expect(checksums).toEqual(expected);
|
|
1253
1744
|
});
|
|
1254
1745
|
}
|