@powersync/service-core-tests 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/test-utils/general-utils.d.ts +9 -3
- package/dist/test-utils/general-utils.js +26 -26
- package/dist/test-utils/general-utils.js.map +1 -1
- package/dist/tests/register-compacting-tests.d.ts +1 -1
- package/dist/tests/register-compacting-tests.js +136 -93
- 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 +44 -27
- 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 +715 -207
- 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 +123 -58
- 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 +13 -13
- package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
- package/dist/tests/register-sync-tests.d.ts +4 -1
- package/dist/tests/register-sync-tests.js +63 -34
- package/dist/tests/register-sync-tests.js.map +1 -1
- package/dist/tests/util.d.ts +6 -1
- package/dist/tests/util.js +31 -2
- package/dist/tests/util.js.map +1 -1
- package/package.json +3 -3
- package/src/test-utils/general-utils.ts +42 -28
- package/src/tests/register-compacting-tests.ts +153 -103
- package/src/tests/register-data-storage-checkpoint-tests.ts +70 -22
- package/src/tests/register-data-storage-data-tests.ts +732 -110
- package/src/tests/register-data-storage-parameter-tests.ts +168 -59
- package/src/tests/register-parameter-compacting-tests.ts +18 -13
- package/src/tests/register-sync-tests.ts +71 -35
- package/src/tests/util.ts +52 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -50,10 +50,11 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
-
import { getUuidReplicaIdentityBson, storage } from '@powersync/service-core';
|
|
53
|
+
import { CURRENT_STORAGE_VERSION, getUuidReplicaIdentityBson, reduceBucket, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
54
54
|
import { describe, expect, test } from 'vitest';
|
|
55
55
|
import * as test_utils from '../test-utils/test-utils-index.js';
|
|
56
|
-
import {
|
|
56
|
+
import { bucketRequest } from '../test-utils/test-utils-index.js';
|
|
57
|
+
import { bucketRequestMap, bucketRequests } from './util.js';
|
|
57
58
|
/**
|
|
58
59
|
* Normalize data from OplogEntries for comparison in tests.
|
|
59
60
|
* Tests typically expect the stringified result
|
|
@@ -74,22 +75,24 @@ const normalizeOplogData = (data) => {
|
|
|
74
75
|
*
|
|
75
76
|
* ```
|
|
76
77
|
*/
|
|
77
|
-
export function registerDataStorageDataTests(
|
|
78
|
+
export function registerDataStorageDataTests(config) {
|
|
79
|
+
const generateStorageFactory = config.factory;
|
|
80
|
+
const storageVersion = config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
|
|
81
|
+
const TEST_TABLE = test_utils.makeTestTable('test', ['id'], config);
|
|
78
82
|
test('removing row', async () => {
|
|
79
83
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
80
84
|
try {
|
|
81
85
|
const factory = __addDisposableResource(env_1, await generateStorageFactory(), true);
|
|
82
|
-
const syncRules = await factory.updateSyncRules(
|
|
83
|
-
content: `
|
|
86
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
84
87
|
bucket_definitions:
|
|
85
88
|
global:
|
|
86
89
|
data:
|
|
87
90
|
- SELECT id, description FROM "%"
|
|
88
|
-
|
|
89
|
-
});
|
|
91
|
+
`, { storageVersion }));
|
|
90
92
|
const bucketStorage = factory.getInstance(syncRules);
|
|
91
93
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
92
94
|
const sourceTable = TEST_TABLE;
|
|
95
|
+
await batch.markAllSnapshotDone('1/1');
|
|
93
96
|
await batch.save({
|
|
94
97
|
sourceTable,
|
|
95
98
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -107,7 +110,7 @@ bucket_definitions:
|
|
|
107
110
|
await batch.commit('1/1');
|
|
108
111
|
});
|
|
109
112
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
110
|
-
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
113
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
111
114
|
const data = batch[0].chunkData.data.map((d) => {
|
|
112
115
|
return {
|
|
113
116
|
op: d.op,
|
|
@@ -121,10 +124,12 @@ bucket_definitions:
|
|
|
121
124
|
{ op: 'PUT', object_id: 'test1', checksum: c1 },
|
|
122
125
|
{ op: 'REMOVE', object_id: 'test1', checksum: c2 }
|
|
123
126
|
]);
|
|
124
|
-
const checksums = [
|
|
127
|
+
const checksums = [
|
|
128
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
129
|
+
];
|
|
125
130
|
expect(checksums).toEqual([
|
|
126
131
|
{
|
|
127
|
-
bucket: 'global[]',
|
|
132
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
128
133
|
checksum: (c1 + c2) & 0xffffffff,
|
|
129
134
|
count: 2
|
|
130
135
|
}
|
|
@@ -140,21 +145,296 @@ bucket_definitions:
|
|
|
140
145
|
await result_1;
|
|
141
146
|
}
|
|
142
147
|
});
|
|
143
|
-
test('
|
|
148
|
+
test('insert after delete in new batch', async () => {
|
|
144
149
|
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
145
150
|
try {
|
|
146
151
|
const factory = __addDisposableResource(env_2, await generateStorageFactory(), true);
|
|
147
|
-
const syncRules = await factory.updateSyncRules(
|
|
148
|
-
content: `
|
|
152
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
149
153
|
bucket_definitions:
|
|
150
154
|
global:
|
|
151
155
|
data:
|
|
152
|
-
- SELECT
|
|
153
|
-
|
|
156
|
+
- SELECT id, description FROM "%"
|
|
157
|
+
`, { storageVersion }));
|
|
158
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
159
|
+
const sourceTable = TEST_TABLE;
|
|
160
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
161
|
+
await batch.markAllSnapshotDone('1/1');
|
|
162
|
+
await batch.save({
|
|
163
|
+
sourceTable,
|
|
164
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
165
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
166
|
+
});
|
|
167
|
+
await batch.commit('0/1');
|
|
168
|
+
});
|
|
169
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
170
|
+
const sourceTable = TEST_TABLE;
|
|
171
|
+
await batch.save({
|
|
172
|
+
sourceTable,
|
|
173
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
174
|
+
after: {
|
|
175
|
+
id: 'test1',
|
|
176
|
+
description: 'test1'
|
|
177
|
+
},
|
|
178
|
+
afterReplicaId: test_utils.rid('test1')
|
|
179
|
+
});
|
|
180
|
+
await batch.commit('2/1');
|
|
181
|
+
});
|
|
182
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
183
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
184
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
185
|
+
return {
|
|
186
|
+
op: d.op,
|
|
187
|
+
object_id: d.object_id,
|
|
188
|
+
checksum: d.checksum
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
const c1 = 2871785649;
|
|
192
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
193
|
+
const checksums = [
|
|
194
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
195
|
+
];
|
|
196
|
+
expect(checksums).toEqual([
|
|
197
|
+
{
|
|
198
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
199
|
+
checksum: c1 & 0xffffffff,
|
|
200
|
+
count: 1
|
|
201
|
+
}
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
catch (e_2) {
|
|
205
|
+
env_2.error = e_2;
|
|
206
|
+
env_2.hasError = true;
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
const result_2 = __disposeResources(env_2);
|
|
210
|
+
if (result_2)
|
|
211
|
+
await result_2;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
test('update after delete in new batch', async () => {
|
|
215
|
+
const env_3 = { stack: [], error: void 0, hasError: false };
|
|
216
|
+
try {
|
|
217
|
+
// Update after delete may not be common, but the storage layer should handle it in an eventually-consistent way.
|
|
218
|
+
const factory = __addDisposableResource(env_3, await generateStorageFactory(), true);
|
|
219
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
220
|
+
bucket_definitions:
|
|
221
|
+
global:
|
|
222
|
+
data:
|
|
223
|
+
- SELECT id, description FROM "%"
|
|
224
|
+
`, { storageVersion }));
|
|
225
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
226
|
+
const sourceTable = TEST_TABLE;
|
|
227
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
228
|
+
await batch.markAllSnapshotDone('1/1');
|
|
229
|
+
await batch.save({
|
|
230
|
+
sourceTable,
|
|
231
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
232
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
233
|
+
});
|
|
234
|
+
await batch.commit('0/1');
|
|
235
|
+
});
|
|
236
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
237
|
+
const sourceTable = TEST_TABLE;
|
|
238
|
+
await batch.save({
|
|
239
|
+
sourceTable,
|
|
240
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
241
|
+
before: {
|
|
242
|
+
id: 'test1'
|
|
243
|
+
},
|
|
244
|
+
after: {
|
|
245
|
+
id: 'test1',
|
|
246
|
+
description: 'test1'
|
|
247
|
+
},
|
|
248
|
+
beforeReplicaId: test_utils.rid('test1'),
|
|
249
|
+
afterReplicaId: test_utils.rid('test1')
|
|
250
|
+
});
|
|
251
|
+
await batch.commit('2/1');
|
|
252
|
+
});
|
|
253
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
254
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
255
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
256
|
+
return {
|
|
257
|
+
op: d.op,
|
|
258
|
+
object_id: d.object_id,
|
|
259
|
+
checksum: d.checksum
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
const c1 = 2871785649;
|
|
263
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
264
|
+
const checksums = [
|
|
265
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
266
|
+
];
|
|
267
|
+
expect(checksums).toEqual([
|
|
268
|
+
{
|
|
269
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
270
|
+
checksum: c1 & 0xffffffff,
|
|
271
|
+
count: 1
|
|
272
|
+
}
|
|
273
|
+
]);
|
|
274
|
+
}
|
|
275
|
+
catch (e_3) {
|
|
276
|
+
env_3.error = e_3;
|
|
277
|
+
env_3.hasError = true;
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
const result_3 = __disposeResources(env_3);
|
|
281
|
+
if (result_3)
|
|
282
|
+
await result_3;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
test('insert after delete in same batch', async () => {
|
|
286
|
+
const env_4 = { stack: [], error: void 0, hasError: false };
|
|
287
|
+
try {
|
|
288
|
+
const factory = __addDisposableResource(env_4, await generateStorageFactory(), true);
|
|
289
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
290
|
+
bucket_definitions:
|
|
291
|
+
global:
|
|
292
|
+
data:
|
|
293
|
+
- SELECT id, description FROM "%"
|
|
294
|
+
`, {
|
|
295
|
+
storageVersion
|
|
296
|
+
}));
|
|
297
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
298
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
299
|
+
const sourceTable = TEST_TABLE;
|
|
300
|
+
await batch.markAllSnapshotDone('1/1');
|
|
301
|
+
await batch.save({
|
|
302
|
+
sourceTable,
|
|
303
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
304
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
305
|
+
});
|
|
306
|
+
await batch.save({
|
|
307
|
+
sourceTable,
|
|
308
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
309
|
+
after: {
|
|
310
|
+
id: 'test1',
|
|
311
|
+
description: 'test1'
|
|
312
|
+
},
|
|
313
|
+
afterReplicaId: test_utils.rid('test1')
|
|
314
|
+
});
|
|
315
|
+
await batch.commit('1/1');
|
|
154
316
|
});
|
|
317
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
318
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
319
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
320
|
+
return {
|
|
321
|
+
op: d.op,
|
|
322
|
+
object_id: d.object_id,
|
|
323
|
+
checksum: d.checksum
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
const c1 = 2871785649;
|
|
327
|
+
expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
|
|
328
|
+
const checksums = [
|
|
329
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
330
|
+
];
|
|
331
|
+
expect(checksums).toEqual([
|
|
332
|
+
{
|
|
333
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
334
|
+
checksum: c1 & 0xffffffff,
|
|
335
|
+
count: 1
|
|
336
|
+
}
|
|
337
|
+
]);
|
|
338
|
+
}
|
|
339
|
+
catch (e_4) {
|
|
340
|
+
env_4.error = e_4;
|
|
341
|
+
env_4.hasError = true;
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
const result_4 = __disposeResources(env_4);
|
|
345
|
+
if (result_4)
|
|
346
|
+
await result_4;
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
test('(insert, delete, insert), (delete)', async () => {
|
|
350
|
+
const env_5 = { stack: [], error: void 0, hasError: false };
|
|
351
|
+
try {
|
|
352
|
+
const factory = __addDisposableResource(env_5, await generateStorageFactory(), true);
|
|
353
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
354
|
+
bucket_definitions:
|
|
355
|
+
global:
|
|
356
|
+
data:
|
|
357
|
+
- SELECT id, description FROM "%"
|
|
358
|
+
`, {
|
|
359
|
+
storageVersion
|
|
360
|
+
}));
|
|
361
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
362
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
363
|
+
const sourceTable = TEST_TABLE;
|
|
364
|
+
await batch.markAllSnapshotDone('1/1');
|
|
365
|
+
await batch.save({
|
|
366
|
+
sourceTable,
|
|
367
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
368
|
+
after: {
|
|
369
|
+
id: 'test1',
|
|
370
|
+
description: 'test1'
|
|
371
|
+
},
|
|
372
|
+
afterReplicaId: test_utils.rid('test1')
|
|
373
|
+
});
|
|
374
|
+
await batch.save({
|
|
375
|
+
sourceTable,
|
|
376
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
377
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
378
|
+
});
|
|
379
|
+
await batch.save({
|
|
380
|
+
sourceTable,
|
|
381
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
382
|
+
after: {
|
|
383
|
+
id: 'test1',
|
|
384
|
+
description: 'test1'
|
|
385
|
+
},
|
|
386
|
+
afterReplicaId: test_utils.rid('test1')
|
|
387
|
+
});
|
|
388
|
+
await batch.commit('1/1');
|
|
389
|
+
});
|
|
390
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
391
|
+
const sourceTable = TEST_TABLE;
|
|
392
|
+
await batch.markAllSnapshotDone('1/1');
|
|
393
|
+
await batch.save({
|
|
394
|
+
sourceTable,
|
|
395
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
396
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
397
|
+
});
|
|
398
|
+
await batch.commit('2/1');
|
|
399
|
+
});
|
|
400
|
+
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
401
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
402
|
+
expect(reduceBucket(batch[0].chunkData.data).slice(1)).toEqual([]);
|
|
403
|
+
const data = batch[0].chunkData.data.map((d) => {
|
|
404
|
+
return {
|
|
405
|
+
op: d.op,
|
|
406
|
+
object_id: d.object_id,
|
|
407
|
+
checksum: d.checksum
|
|
408
|
+
};
|
|
409
|
+
});
|
|
410
|
+
expect(data).toMatchSnapshot();
|
|
411
|
+
}
|
|
412
|
+
catch (e_5) {
|
|
413
|
+
env_5.error = e_5;
|
|
414
|
+
env_5.hasError = true;
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
const result_5 = __disposeResources(env_5);
|
|
418
|
+
if (result_5)
|
|
419
|
+
await result_5;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
test('changing client ids', async () => {
|
|
423
|
+
const env_6 = { stack: [], error: void 0, hasError: false };
|
|
424
|
+
try {
|
|
425
|
+
const factory = __addDisposableResource(env_6, await generateStorageFactory(), true);
|
|
426
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
427
|
+
bucket_definitions:
|
|
428
|
+
global:
|
|
429
|
+
data:
|
|
430
|
+
- SELECT client_id as id, description FROM "%"
|
|
431
|
+
`, {
|
|
432
|
+
storageVersion
|
|
433
|
+
}));
|
|
155
434
|
const bucketStorage = factory.getInstance(syncRules);
|
|
156
435
|
const sourceTable = TEST_TABLE;
|
|
157
436
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
437
|
+
await batch.markAllSnapshotDone('1/1');
|
|
158
438
|
await batch.save({
|
|
159
439
|
sourceTable,
|
|
160
440
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -188,7 +468,7 @@ bucket_definitions:
|
|
|
188
468
|
await batch.commit('1/1');
|
|
189
469
|
});
|
|
190
470
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
191
|
-
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
471
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
192
472
|
const data = batch[0].chunkData.data.map((d) => {
|
|
193
473
|
return {
|
|
194
474
|
op: d.op,
|
|
@@ -202,31 +482,32 @@ bucket_definitions:
|
|
|
202
482
|
{ op: 'PUT', object_id: 'client2' }
|
|
203
483
|
]);
|
|
204
484
|
}
|
|
205
|
-
catch (
|
|
206
|
-
|
|
207
|
-
|
|
485
|
+
catch (e_6) {
|
|
486
|
+
env_6.error = e_6;
|
|
487
|
+
env_6.hasError = true;
|
|
208
488
|
}
|
|
209
489
|
finally {
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
212
|
-
await
|
|
490
|
+
const result_6 = __disposeResources(env_6);
|
|
491
|
+
if (result_6)
|
|
492
|
+
await result_6;
|
|
213
493
|
}
|
|
214
494
|
});
|
|
215
495
|
test('re-apply delete', async () => {
|
|
216
|
-
const
|
|
496
|
+
const env_7 = { stack: [], error: void 0, hasError: false };
|
|
217
497
|
try {
|
|
218
|
-
const factory = __addDisposableResource(
|
|
219
|
-
const syncRules = await factory.updateSyncRules(
|
|
220
|
-
content: `
|
|
498
|
+
const factory = __addDisposableResource(env_7, await generateStorageFactory(), true);
|
|
499
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
221
500
|
bucket_definitions:
|
|
222
501
|
global:
|
|
223
502
|
data:
|
|
224
503
|
- SELECT id, description FROM "%"
|
|
225
|
-
|
|
226
|
-
|
|
504
|
+
`, {
|
|
505
|
+
storageVersion
|
|
506
|
+
}));
|
|
227
507
|
const bucketStorage = factory.getInstance(syncRules);
|
|
228
508
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
229
509
|
const sourceTable = TEST_TABLE;
|
|
510
|
+
await batch.markAllSnapshotDone('1/1');
|
|
230
511
|
await batch.save({
|
|
231
512
|
sourceTable,
|
|
232
513
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -255,7 +536,7 @@ bucket_definitions:
|
|
|
255
536
|
});
|
|
256
537
|
});
|
|
257
538
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
258
|
-
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
539
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
259
540
|
const data = batch[0].chunkData.data.map((d) => {
|
|
260
541
|
return {
|
|
261
542
|
op: d.op,
|
|
@@ -269,39 +550,40 @@ bucket_definitions:
|
|
|
269
550
|
{ op: 'PUT', object_id: 'test1', checksum: c1 },
|
|
270
551
|
{ op: 'REMOVE', object_id: 'test1', checksum: c2 }
|
|
271
552
|
]);
|
|
272
|
-
const checksums = [
|
|
553
|
+
const checksums = [
|
|
554
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
555
|
+
];
|
|
273
556
|
expect(checksums).toEqual([
|
|
274
557
|
{
|
|
275
|
-
bucket: 'global[]',
|
|
558
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
276
559
|
checksum: (c1 + c2) & 0xffffffff,
|
|
277
560
|
count: 2
|
|
278
561
|
}
|
|
279
562
|
]);
|
|
280
563
|
}
|
|
281
|
-
catch (
|
|
282
|
-
|
|
283
|
-
|
|
564
|
+
catch (e_7) {
|
|
565
|
+
env_7.error = e_7;
|
|
566
|
+
env_7.hasError = true;
|
|
284
567
|
}
|
|
285
568
|
finally {
|
|
286
|
-
const
|
|
287
|
-
if (
|
|
288
|
-
await
|
|
569
|
+
const result_7 = __disposeResources(env_7);
|
|
570
|
+
if (result_7)
|
|
571
|
+
await result_7;
|
|
289
572
|
}
|
|
290
573
|
});
|
|
291
574
|
test('re-apply update + delete', async () => {
|
|
292
|
-
const
|
|
575
|
+
const env_8 = { stack: [], error: void 0, hasError: false };
|
|
293
576
|
try {
|
|
294
|
-
const factory = __addDisposableResource(
|
|
295
|
-
const syncRules = await factory.updateSyncRules(
|
|
296
|
-
content: `
|
|
577
|
+
const factory = __addDisposableResource(env_8, await generateStorageFactory(), true);
|
|
578
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
297
579
|
bucket_definitions:
|
|
298
580
|
global:
|
|
299
581
|
data:
|
|
300
582
|
- SELECT id, description FROM "%"
|
|
301
|
-
|
|
302
|
-
});
|
|
583
|
+
`, { storageVersion }));
|
|
303
584
|
const bucketStorage = factory.getInstance(syncRules);
|
|
304
585
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
586
|
+
await batch.markAllSnapshotDone('1/1');
|
|
305
587
|
const sourceTable = TEST_TABLE;
|
|
306
588
|
await batch.save({
|
|
307
589
|
sourceTable,
|
|
@@ -314,6 +596,7 @@ bucket_definitions:
|
|
|
314
596
|
});
|
|
315
597
|
});
|
|
316
598
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
599
|
+
await batch.markAllSnapshotDone('1/1');
|
|
317
600
|
const sourceTable = TEST_TABLE;
|
|
318
601
|
await batch.save({
|
|
319
602
|
sourceTable,
|
|
@@ -341,6 +624,7 @@ bucket_definitions:
|
|
|
341
624
|
await batch.commit('1/1');
|
|
342
625
|
});
|
|
343
626
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
627
|
+
await batch.markAllSnapshotDone('1/1');
|
|
344
628
|
const sourceTable = TEST_TABLE;
|
|
345
629
|
await batch.save({
|
|
346
630
|
sourceTable,
|
|
@@ -368,7 +652,7 @@ bucket_definitions:
|
|
|
368
652
|
await batch.commit('2/1');
|
|
369
653
|
});
|
|
370
654
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
371
|
-
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
655
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
372
656
|
const data = batch[0].chunkData.data.map((d) => {
|
|
373
657
|
return {
|
|
374
658
|
op: d.op,
|
|
@@ -384,27 +668,29 @@ bucket_definitions:
|
|
|
384
668
|
{ op: 'PUT', object_id: 'test1', checksum: c1 },
|
|
385
669
|
{ op: 'REMOVE', object_id: 'test1', checksum: c2 }
|
|
386
670
|
]);
|
|
387
|
-
const checksums = [
|
|
671
|
+
const checksums = [
|
|
672
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
673
|
+
];
|
|
388
674
|
expect(checksums).toEqual([
|
|
389
675
|
{
|
|
390
|
-
bucket: 'global[]',
|
|
676
|
+
bucket: bucketRequest(syncRules, 'global[]').bucket,
|
|
391
677
|
checksum: (c1 + c1 + c1 + c2) & 0xffffffff,
|
|
392
678
|
count: 4
|
|
393
679
|
}
|
|
394
680
|
]);
|
|
395
681
|
}
|
|
396
|
-
catch (
|
|
397
|
-
|
|
398
|
-
|
|
682
|
+
catch (e_8) {
|
|
683
|
+
env_8.error = e_8;
|
|
684
|
+
env_8.hasError = true;
|
|
399
685
|
}
|
|
400
686
|
finally {
|
|
401
|
-
const
|
|
402
|
-
if (
|
|
403
|
-
await
|
|
687
|
+
const result_8 = __disposeResources(env_8);
|
|
688
|
+
if (result_8)
|
|
689
|
+
await result_8;
|
|
404
690
|
}
|
|
405
691
|
});
|
|
406
692
|
test('batch with overlapping replica ids', async () => {
|
|
407
|
-
const
|
|
693
|
+
const env_9 = { stack: [], error: void 0, hasError: false };
|
|
408
694
|
try {
|
|
409
695
|
// This test checks that we get the correct output when processing rows with:
|
|
410
696
|
// 1. changing replica ids
|
|
@@ -413,18 +699,17 @@ bucket_definitions:
|
|
|
413
699
|
// It can break at two places:
|
|
414
700
|
// 1. Not getting the correct "current_data" state for each operation.
|
|
415
701
|
// 2. Output order not being correct.
|
|
416
|
-
const factory = __addDisposableResource(
|
|
417
|
-
const syncRules = await factory.updateSyncRules(
|
|
418
|
-
content: `
|
|
702
|
+
const factory = __addDisposableResource(env_9, await generateStorageFactory(), true);
|
|
703
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
419
704
|
bucket_definitions:
|
|
420
705
|
global:
|
|
421
706
|
data:
|
|
422
707
|
- SELECT id, description FROM "test"
|
|
423
|
-
|
|
424
|
-
});
|
|
708
|
+
`, { storageVersion }));
|
|
425
709
|
const bucketStorage = factory.getInstance(syncRules);
|
|
426
710
|
// Pre-setup
|
|
427
711
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
712
|
+
await batch.markAllSnapshotDone('1/1');
|
|
428
713
|
const sourceTable = TEST_TABLE;
|
|
429
714
|
await batch.save({
|
|
430
715
|
sourceTable,
|
|
@@ -520,7 +805,7 @@ bucket_definitions:
|
|
|
520
805
|
});
|
|
521
806
|
});
|
|
522
807
|
const checkpoint2 = result2.flushed_op;
|
|
523
|
-
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint2,
|
|
808
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint2, bucketRequestMap(syncRules, [['global[]', checkpoint1]])));
|
|
524
809
|
const data = batch[0].chunkData.data.map((d) => {
|
|
525
810
|
return {
|
|
526
811
|
op: d.op,
|
|
@@ -544,18 +829,18 @@ bucket_definitions:
|
|
|
544
829
|
{ op: 'PUT', object_id: 'test5', data: JSON.stringify({ id: 'test5', description: 'test5d' }) }
|
|
545
830
|
]);
|
|
546
831
|
}
|
|
547
|
-
catch (
|
|
548
|
-
|
|
549
|
-
|
|
832
|
+
catch (e_9) {
|
|
833
|
+
env_9.error = e_9;
|
|
834
|
+
env_9.hasError = true;
|
|
550
835
|
}
|
|
551
836
|
finally {
|
|
552
|
-
const
|
|
553
|
-
if (
|
|
554
|
-
await
|
|
837
|
+
const result_9 = __disposeResources(env_9);
|
|
838
|
+
if (result_9)
|
|
839
|
+
await result_9;
|
|
555
840
|
}
|
|
556
841
|
});
|
|
557
842
|
test('changed data with replica identity full', async () => {
|
|
558
|
-
const
|
|
843
|
+
const env_10 = { stack: [], error: void 0, hasError: false };
|
|
559
844
|
try {
|
|
560
845
|
function rid2(id, description) {
|
|
561
846
|
return getUuidReplicaIdentityBson({ id, description }, [
|
|
@@ -563,19 +848,20 @@ bucket_definitions:
|
|
|
563
848
|
{ name: 'description', type: 'VARCHAR', typeId: 25 }
|
|
564
849
|
]);
|
|
565
850
|
}
|
|
566
|
-
const factory = __addDisposableResource(
|
|
567
|
-
const syncRules = await factory.updateSyncRules(
|
|
568
|
-
content: `
|
|
851
|
+
const factory = __addDisposableResource(env_10, await generateStorageFactory(), true);
|
|
852
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
569
853
|
bucket_definitions:
|
|
570
854
|
global:
|
|
571
855
|
data:
|
|
572
856
|
- SELECT id, description FROM "test"
|
|
573
|
-
|
|
574
|
-
|
|
857
|
+
`, {
|
|
858
|
+
storageVersion
|
|
859
|
+
}));
|
|
575
860
|
const bucketStorage = factory.getInstance(syncRules);
|
|
576
|
-
const sourceTable = test_utils.makeTestTable('test', ['id', 'description']);
|
|
861
|
+
const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
|
|
577
862
|
// Pre-setup
|
|
578
863
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
864
|
+
await batch.markAllSnapshotDone('1/1');
|
|
579
865
|
await batch.save({
|
|
580
866
|
sourceTable,
|
|
581
867
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -618,7 +904,7 @@ bucket_definitions:
|
|
|
618
904
|
});
|
|
619
905
|
});
|
|
620
906
|
const checkpoint3 = result3.flushed_op;
|
|
621
|
-
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3,
|
|
907
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3, bucketRequestMap(syncRules, [['global[]', checkpoint1]])));
|
|
622
908
|
const data = batch[0].chunkData.data.map((d) => {
|
|
623
909
|
return {
|
|
624
910
|
op: d.op,
|
|
@@ -652,18 +938,18 @@ bucket_definitions:
|
|
|
652
938
|
}
|
|
653
939
|
]);
|
|
654
940
|
}
|
|
655
|
-
catch (
|
|
656
|
-
|
|
657
|
-
|
|
941
|
+
catch (e_10) {
|
|
942
|
+
env_10.error = e_10;
|
|
943
|
+
env_10.hasError = true;
|
|
658
944
|
}
|
|
659
945
|
finally {
|
|
660
|
-
const
|
|
661
|
-
if (
|
|
662
|
-
await
|
|
946
|
+
const result_10 = __disposeResources(env_10);
|
|
947
|
+
if (result_10)
|
|
948
|
+
await result_10;
|
|
663
949
|
}
|
|
664
950
|
});
|
|
665
951
|
test('unchanged data with replica identity full', async () => {
|
|
666
|
-
const
|
|
952
|
+
const env_11 = { stack: [], error: void 0, hasError: false };
|
|
667
953
|
try {
|
|
668
954
|
function rid2(id, description) {
|
|
669
955
|
return getUuidReplicaIdentityBson({ id, description }, [
|
|
@@ -671,19 +957,20 @@ bucket_definitions:
|
|
|
671
957
|
{ name: 'description', type: 'VARCHAR', typeId: 25 }
|
|
672
958
|
]);
|
|
673
959
|
}
|
|
674
|
-
const factory = __addDisposableResource(
|
|
675
|
-
const syncRules = await factory.updateSyncRules(
|
|
676
|
-
content: `
|
|
960
|
+
const factory = __addDisposableResource(env_11, await generateStorageFactory(), true);
|
|
961
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
677
962
|
bucket_definitions:
|
|
678
963
|
global:
|
|
679
964
|
data:
|
|
680
965
|
- SELECT id, description FROM "test"
|
|
681
|
-
|
|
682
|
-
|
|
966
|
+
`, {
|
|
967
|
+
storageVersion
|
|
968
|
+
}));
|
|
683
969
|
const bucketStorage = factory.getInstance(syncRules);
|
|
684
|
-
const sourceTable = test_utils.makeTestTable('test', ['id', 'description']);
|
|
970
|
+
const sourceTable = test_utils.makeTestTable('test', ['id', 'description'], config);
|
|
685
971
|
// Pre-setup
|
|
686
972
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
973
|
+
await batch.markAllSnapshotDone('1/1');
|
|
687
974
|
await batch.save({
|
|
688
975
|
sourceTable,
|
|
689
976
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -726,7 +1013,7 @@ bucket_definitions:
|
|
|
726
1013
|
});
|
|
727
1014
|
});
|
|
728
1015
|
const checkpoint3 = result3.flushed_op;
|
|
729
|
-
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3,
|
|
1016
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint3, bucketRequestMap(syncRules, [['global[]', checkpoint1]])));
|
|
730
1017
|
const data = batch[0].chunkData.data.map((d) => {
|
|
731
1018
|
return {
|
|
732
1019
|
op: d.op,
|
|
@@ -753,34 +1040,35 @@ bucket_definitions:
|
|
|
753
1040
|
}
|
|
754
1041
|
]);
|
|
755
1042
|
}
|
|
756
|
-
catch (
|
|
757
|
-
|
|
758
|
-
|
|
1043
|
+
catch (e_11) {
|
|
1044
|
+
env_11.error = e_11;
|
|
1045
|
+
env_11.hasError = true;
|
|
759
1046
|
}
|
|
760
1047
|
finally {
|
|
761
|
-
const
|
|
762
|
-
if (
|
|
763
|
-
await
|
|
1048
|
+
const result_11 = __disposeResources(env_11);
|
|
1049
|
+
if (result_11)
|
|
1050
|
+
await result_11;
|
|
764
1051
|
}
|
|
765
1052
|
});
|
|
766
1053
|
test('large batch', async () => {
|
|
767
|
-
const
|
|
1054
|
+
const env_12 = { stack: [], error: void 0, hasError: false };
|
|
768
1055
|
try {
|
|
769
1056
|
// Test syncing a batch of data that is small in count,
|
|
770
1057
|
// but large enough in size to be split over multiple returned batches.
|
|
771
1058
|
// The specific batch splits is an implementation detail of the storage driver,
|
|
772
1059
|
// and the test will have to updated when other implementations are added.
|
|
773
|
-
const factory = __addDisposableResource(
|
|
774
|
-
const syncRules = await factory.updateSyncRules(
|
|
775
|
-
content: `
|
|
1060
|
+
const factory = __addDisposableResource(env_12, await generateStorageFactory(), true);
|
|
1061
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
776
1062
|
bucket_definitions:
|
|
777
1063
|
global:
|
|
778
1064
|
data:
|
|
779
1065
|
- SELECT id, description FROM "%"
|
|
780
|
-
|
|
781
|
-
|
|
1066
|
+
`, {
|
|
1067
|
+
storageVersion
|
|
1068
|
+
}));
|
|
782
1069
|
const bucketStorage = factory.getInstance(syncRules);
|
|
783
1070
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1071
|
+
await batch.markAllSnapshotDone('1/1');
|
|
784
1072
|
const sourceTable = TEST_TABLE;
|
|
785
1073
|
const largeDescription = '0123456789'.repeat(12_000_00);
|
|
786
1074
|
await batch.save({
|
|
@@ -826,7 +1114,7 @@ bucket_definitions:
|
|
|
826
1114
|
const options = {
|
|
827
1115
|
chunkLimitBytes: 16 * 1024 * 1024
|
|
828
1116
|
};
|
|
829
|
-
const batch1 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
1117
|
+
const batch1 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]), options));
|
|
830
1118
|
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
831
1119
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
|
|
832
1120
|
{ op_id: '2', op: 'PUT', object_id: 'large1', checksum: 454746904 }
|
|
@@ -836,7 +1124,7 @@ bucket_definitions:
|
|
|
836
1124
|
has_more: true,
|
|
837
1125
|
next_after: '2'
|
|
838
1126
|
});
|
|
839
|
-
const batch2 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
1127
|
+
const batch2 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch1[0].chunkData.next_after)]]), options));
|
|
840
1128
|
expect(test_utils.getBatchData(batch2)).toEqual([
|
|
841
1129
|
{ op_id: '3', op: 'PUT', object_id: 'large2', checksum: 1795508474 },
|
|
842
1130
|
{ op_id: '4', op: 'PUT', object_id: 'test3', checksum: 1359888332 }
|
|
@@ -846,35 +1134,36 @@ bucket_definitions:
|
|
|
846
1134
|
has_more: false,
|
|
847
1135
|
next_after: '4'
|
|
848
1136
|
});
|
|
849
|
-
const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
1137
|
+
const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch2[0].chunkData.next_after)]]), options));
|
|
850
1138
|
expect(test_utils.getBatchData(batch3)).toEqual([]);
|
|
851
1139
|
expect(test_utils.getBatchMeta(batch3)).toEqual(null);
|
|
852
1140
|
}
|
|
853
|
-
catch (
|
|
854
|
-
|
|
855
|
-
|
|
1141
|
+
catch (e_12) {
|
|
1142
|
+
env_12.error = e_12;
|
|
1143
|
+
env_12.hasError = true;
|
|
856
1144
|
}
|
|
857
1145
|
finally {
|
|
858
|
-
const
|
|
859
|
-
if (
|
|
860
|
-
await
|
|
1146
|
+
const result_12 = __disposeResources(env_12);
|
|
1147
|
+
if (result_12)
|
|
1148
|
+
await result_12;
|
|
861
1149
|
}
|
|
862
1150
|
});
|
|
863
1151
|
test('long batch', async () => {
|
|
864
|
-
const
|
|
1152
|
+
const env_13 = { stack: [], error: void 0, hasError: false };
|
|
865
1153
|
try {
|
|
866
1154
|
// Test syncing a batch of data that is limited by count.
|
|
867
|
-
const factory = __addDisposableResource(
|
|
868
|
-
const syncRules = await factory.updateSyncRules(
|
|
869
|
-
content: `
|
|
1155
|
+
const factory = __addDisposableResource(env_13, await generateStorageFactory(), true);
|
|
1156
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
870
1157
|
bucket_definitions:
|
|
871
1158
|
global:
|
|
872
1159
|
data:
|
|
873
1160
|
- SELECT id, description FROM "%"
|
|
874
|
-
|
|
875
|
-
|
|
1161
|
+
`, {
|
|
1162
|
+
storageVersion
|
|
1163
|
+
}));
|
|
876
1164
|
const bucketStorage = factory.getInstance(syncRules);
|
|
877
1165
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1166
|
+
await batch.markAllSnapshotDone('1/1');
|
|
878
1167
|
const sourceTable = TEST_TABLE;
|
|
879
1168
|
for (let i = 1; i <= 6; i++) {
|
|
880
1169
|
await batch.save({
|
|
@@ -890,7 +1179,7 @@ bucket_definitions:
|
|
|
890
1179
|
await batch.commit('1/1');
|
|
891
1180
|
});
|
|
892
1181
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
893
|
-
const batch1 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
1182
|
+
const batch1 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]]), { limit: 4 }));
|
|
894
1183
|
expect(test_utils.getBatchData(batch1)).toEqual([
|
|
895
1184
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
|
|
896
1185
|
{ op_id: '2', op: 'PUT', object_id: 'test2', checksum: 730027011 },
|
|
@@ -902,7 +1191,7 @@ bucket_definitions:
|
|
|
902
1191
|
has_more: true,
|
|
903
1192
|
next_after: '4'
|
|
904
1193
|
});
|
|
905
|
-
const batch2 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
1194
|
+
const batch2 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch1.chunkData.next_after)]]), {
|
|
906
1195
|
limit: 4
|
|
907
1196
|
}));
|
|
908
1197
|
expect(test_utils.getBatchData(batch2)).toEqual([
|
|
@@ -914,29 +1203,28 @@ bucket_definitions:
|
|
|
914
1203
|
has_more: false,
|
|
915
1204
|
next_after: '6'
|
|
916
1205
|
});
|
|
917
|
-
const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint,
|
|
1206
|
+
const batch3 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [['global[]', BigInt(batch2.chunkData.next_after)]]), {
|
|
918
1207
|
limit: 4
|
|
919
1208
|
}));
|
|
920
1209
|
expect(test_utils.getBatchData(batch3)).toEqual([]);
|
|
921
1210
|
expect(test_utils.getBatchMeta(batch3)).toEqual(null);
|
|
922
1211
|
}
|
|
923
|
-
catch (
|
|
924
|
-
|
|
925
|
-
|
|
1212
|
+
catch (e_13) {
|
|
1213
|
+
env_13.error = e_13;
|
|
1214
|
+
env_13.hasError = true;
|
|
926
1215
|
}
|
|
927
1216
|
finally {
|
|
928
|
-
const
|
|
929
|
-
if (
|
|
930
|
-
await
|
|
1217
|
+
const result_13 = __disposeResources(env_13);
|
|
1218
|
+
if (result_13)
|
|
1219
|
+
await result_13;
|
|
931
1220
|
}
|
|
932
1221
|
});
|
|
933
1222
|
describe('batch has_more', () => {
|
|
934
1223
|
const setup = async (options) => {
|
|
935
|
-
const
|
|
1224
|
+
const env_14 = { stack: [], error: void 0, hasError: false };
|
|
936
1225
|
try {
|
|
937
|
-
const factory = __addDisposableResource(
|
|
938
|
-
const syncRules = await factory.updateSyncRules(
|
|
939
|
-
content: `
|
|
1226
|
+
const factory = __addDisposableResource(env_14, await generateStorageFactory(), true);
|
|
1227
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
940
1228
|
bucket_definitions:
|
|
941
1229
|
global1:
|
|
942
1230
|
data:
|
|
@@ -944,10 +1232,10 @@ bucket_definitions:
|
|
|
944
1232
|
global2:
|
|
945
1233
|
data:
|
|
946
1234
|
- SELECT id, description FROM test WHERE bucket = 'global2'
|
|
947
|
-
|
|
948
|
-
});
|
|
1235
|
+
`, { storageVersion }));
|
|
949
1236
|
const bucketStorage = factory.getInstance(syncRules);
|
|
950
1237
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1238
|
+
await batch.markAllSnapshotDone('1/1');
|
|
951
1239
|
const sourceTable = TEST_TABLE;
|
|
952
1240
|
for (let i = 1; i <= 10; i++) {
|
|
953
1241
|
await batch.save({
|
|
@@ -964,26 +1252,27 @@ bucket_definitions:
|
|
|
964
1252
|
await batch.commit('1/1');
|
|
965
1253
|
});
|
|
966
1254
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
967
|
-
|
|
1255
|
+
const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, bucketRequestMap(syncRules, [
|
|
968
1256
|
['global1[]', 0n],
|
|
969
1257
|
['global2[]', 0n]
|
|
970
1258
|
]), options));
|
|
1259
|
+
return { syncRules, batch };
|
|
971
1260
|
}
|
|
972
|
-
catch (
|
|
973
|
-
|
|
974
|
-
|
|
1261
|
+
catch (e_14) {
|
|
1262
|
+
env_14.error = e_14;
|
|
1263
|
+
env_14.hasError = true;
|
|
975
1264
|
}
|
|
976
1265
|
finally {
|
|
977
|
-
const
|
|
978
|
-
if (
|
|
979
|
-
await
|
|
1266
|
+
const result_14 = __disposeResources(env_14);
|
|
1267
|
+
if (result_14)
|
|
1268
|
+
await result_14;
|
|
980
1269
|
}
|
|
981
1270
|
};
|
|
982
1271
|
test('batch has_more (1)', async () => {
|
|
983
|
-
const batch = await setup({ limit: 5 });
|
|
1272
|
+
const { batch, syncRules } = await setup({ limit: 5 });
|
|
984
1273
|
expect(batch.length).toEqual(2);
|
|
985
|
-
expect(batch[0].chunkData.bucket).toEqual('global1[]');
|
|
986
|
-
expect(batch[1].chunkData.bucket).toEqual('global2[]');
|
|
1274
|
+
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
|
|
1275
|
+
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
987
1276
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
988
1277
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
989
1278
|
]);
|
|
@@ -1005,10 +1294,10 @@ bucket_definitions:
|
|
|
1005
1294
|
});
|
|
1006
1295
|
});
|
|
1007
1296
|
test('batch has_more (2)', async () => {
|
|
1008
|
-
const batch = await setup({ limit: 11 });
|
|
1297
|
+
const { batch, syncRules } = await setup({ limit: 11 });
|
|
1009
1298
|
expect(batch.length).toEqual(2);
|
|
1010
|
-
expect(batch[0].chunkData.bucket).toEqual('global1[]');
|
|
1011
|
-
expect(batch[1].chunkData.bucket).toEqual('global2[]');
|
|
1299
|
+
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
|
|
1300
|
+
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
1012
1301
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
1013
1302
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
1014
1303
|
]);
|
|
@@ -1036,11 +1325,11 @@ bucket_definitions:
|
|
|
1036
1325
|
});
|
|
1037
1326
|
test('batch has_more (3)', async () => {
|
|
1038
1327
|
// 50 bytes is more than 1 row, less than 2 rows
|
|
1039
|
-
const batch = await setup({ limit: 3, chunkLimitBytes: 50 });
|
|
1328
|
+
const { batch, syncRules } = await setup({ limit: 3, chunkLimitBytes: 50 });
|
|
1040
1329
|
expect(batch.length).toEqual(3);
|
|
1041
|
-
expect(batch[0].chunkData.bucket).toEqual('global1[]');
|
|
1042
|
-
expect(batch[1].chunkData.bucket).toEqual('global2[]');
|
|
1043
|
-
expect(batch[2].chunkData.bucket).toEqual('global2[]');
|
|
1330
|
+
expect(batch[0].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global1[]').bucket);
|
|
1331
|
+
expect(batch[1].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
1332
|
+
expect(batch[2].chunkData.bucket).toEqual(bucketRequest(syncRules, 'global2[]').bucket);
|
|
1044
1333
|
expect(test_utils.getBatchData(batch[0])).toEqual([
|
|
1045
1334
|
{ op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 }
|
|
1046
1335
|
]);
|
|
@@ -1068,53 +1357,54 @@ bucket_definitions:
|
|
|
1068
1357
|
});
|
|
1069
1358
|
});
|
|
1070
1359
|
test('empty storage metrics', async () => {
|
|
1071
|
-
const
|
|
1360
|
+
const env_15 = { stack: [], error: void 0, hasError: false };
|
|
1072
1361
|
try {
|
|
1073
|
-
const f = __addDisposableResource(
|
|
1362
|
+
const f = __addDisposableResource(env_15, await generateStorageFactory({ dropAll: true }), true);
|
|
1074
1363
|
const metrics = await f.getStorageMetrics();
|
|
1075
1364
|
expect(metrics).toEqual({
|
|
1076
1365
|
operations_size_bytes: 0,
|
|
1077
1366
|
parameters_size_bytes: 0,
|
|
1078
1367
|
replication_size_bytes: 0
|
|
1079
1368
|
});
|
|
1080
|
-
const r = await f.configureSyncRules(
|
|
1369
|
+
const r = await f.configureSyncRules(updateSyncRulesFromYaml('bucket_definitions: {}'));
|
|
1081
1370
|
const storage = f.getInstance(r.persisted_sync_rules);
|
|
1082
1371
|
await storage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1372
|
+
await batch.markAllSnapshotDone('1/0');
|
|
1083
1373
|
await batch.keepalive('1/0');
|
|
1084
1374
|
});
|
|
1085
|
-
|
|
1086
|
-
expect(metrics2).toMatchSnapshot();
|
|
1375
|
+
await f.getStorageMetrics();
|
|
1087
1376
|
}
|
|
1088
|
-
catch (
|
|
1089
|
-
|
|
1090
|
-
|
|
1377
|
+
catch (e_15) {
|
|
1378
|
+
env_15.error = e_15;
|
|
1379
|
+
env_15.hasError = true;
|
|
1091
1380
|
}
|
|
1092
1381
|
finally {
|
|
1093
|
-
const
|
|
1094
|
-
if (
|
|
1095
|
-
await
|
|
1382
|
+
const result_15 = __disposeResources(env_15);
|
|
1383
|
+
if (result_15)
|
|
1384
|
+
await result_15;
|
|
1096
1385
|
}
|
|
1097
1386
|
});
|
|
1098
1387
|
test('op_id initialization edge case', async () => {
|
|
1099
|
-
const
|
|
1388
|
+
const env_16 = { stack: [], error: void 0, hasError: false };
|
|
1100
1389
|
try {
|
|
1101
1390
|
// Test syncing a batch of data that is small in count,
|
|
1102
1391
|
// but large enough in size to be split over multiple returned chunks.
|
|
1103
1392
|
// Similar to the above test, but splits over 1MB chunks.
|
|
1104
|
-
const factory = __addDisposableResource(
|
|
1105
|
-
const syncRules = await factory.updateSyncRules(
|
|
1106
|
-
content: `
|
|
1393
|
+
const factory = __addDisposableResource(env_16, await generateStorageFactory(), true);
|
|
1394
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
1107
1395
|
bucket_definitions:
|
|
1108
1396
|
global:
|
|
1109
1397
|
data:
|
|
1110
1398
|
- SELECT id FROM test
|
|
1111
1399
|
- SELECT id FROM test_ignore WHERE false
|
|
1112
|
-
|
|
1113
|
-
|
|
1400
|
+
`, {
|
|
1401
|
+
storageVersion
|
|
1402
|
+
}));
|
|
1114
1403
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1115
|
-
const sourceTable = test_utils.makeTestTable('test', ['id']);
|
|
1116
|
-
const sourceTableIgnore = test_utils.makeTestTable('test_ignore', ['id']);
|
|
1404
|
+
const sourceTable = test_utils.makeTestTable('test', ['id'], config);
|
|
1405
|
+
const sourceTableIgnore = test_utils.makeTestTable('test_ignore', ['id'], config);
|
|
1117
1406
|
const result1 = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1407
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1118
1408
|
// This saves a record to current_data, but not bucket_data.
|
|
1119
1409
|
// This causes a checkpoint to be created without increasing the op_id sequence.
|
|
1120
1410
|
await batch.save({
|
|
@@ -1141,31 +1431,32 @@ bucket_definitions:
|
|
|
1141
1431
|
// we expect 0n and 1n, or 1n and 2n.
|
|
1142
1432
|
expect(checkpoint2).toBeGreaterThan(checkpoint1);
|
|
1143
1433
|
}
|
|
1144
|
-
catch (
|
|
1145
|
-
|
|
1146
|
-
|
|
1434
|
+
catch (e_16) {
|
|
1435
|
+
env_16.error = e_16;
|
|
1436
|
+
env_16.hasError = true;
|
|
1147
1437
|
}
|
|
1148
1438
|
finally {
|
|
1149
|
-
const
|
|
1150
|
-
if (
|
|
1151
|
-
await
|
|
1439
|
+
const result_16 = __disposeResources(env_16);
|
|
1440
|
+
if (result_16)
|
|
1441
|
+
await result_16;
|
|
1152
1442
|
}
|
|
1153
1443
|
});
|
|
1154
1444
|
test('unchanged checksums', async () => {
|
|
1155
|
-
const
|
|
1445
|
+
const env_17 = { stack: [], error: void 0, hasError: false };
|
|
1156
1446
|
try {
|
|
1157
|
-
const factory = __addDisposableResource(
|
|
1158
|
-
const syncRules = await factory.updateSyncRules(
|
|
1159
|
-
content: `
|
|
1447
|
+
const factory = __addDisposableResource(env_17, await generateStorageFactory(), true);
|
|
1448
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
1160
1449
|
bucket_definitions:
|
|
1161
1450
|
global:
|
|
1162
1451
|
data:
|
|
1163
1452
|
- SELECT client_id as id, description FROM "%"
|
|
1164
|
-
|
|
1165
|
-
|
|
1453
|
+
`, {
|
|
1454
|
+
storageVersion
|
|
1455
|
+
}));
|
|
1166
1456
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1167
1457
|
const sourceTable = TEST_TABLE;
|
|
1168
1458
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1459
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1169
1460
|
await batch.save({
|
|
1170
1461
|
sourceTable,
|
|
1171
1462
|
tag: storage.SaveOperationTag.INSERT,
|
|
@@ -1178,45 +1469,262 @@ bucket_definitions:
|
|
|
1178
1469
|
await batch.commit('1/1');
|
|
1179
1470
|
});
|
|
1180
1471
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
1181
|
-
const checksums = [
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
expect(
|
|
1472
|
+
const checksums = [
|
|
1473
|
+
...(await bucketStorage.getChecksums(checkpoint, bucketRequests(syncRules, ['global[]']))).values()
|
|
1474
|
+
];
|
|
1475
|
+
expect(checksums).toEqual([
|
|
1476
|
+
{ bucket: bucketRequest(syncRules, 'global[]').bucket, checksum: 1917136889, count: 1 }
|
|
1477
|
+
]);
|
|
1478
|
+
const checksums2 = [
|
|
1479
|
+
...(await bucketStorage.getChecksums(checkpoint + 1n, bucketRequests(syncRules, ['global[]']))).values()
|
|
1480
|
+
];
|
|
1481
|
+
expect(checksums2).toEqual([
|
|
1482
|
+
{ bucket: bucketRequest(syncRules, 'global[]').bucket, checksum: 1917136889, count: 1 }
|
|
1483
|
+
]);
|
|
1185
1484
|
}
|
|
1186
|
-
catch (
|
|
1187
|
-
|
|
1188
|
-
|
|
1485
|
+
catch (e_17) {
|
|
1486
|
+
env_17.error = e_17;
|
|
1487
|
+
env_17.hasError = true;
|
|
1189
1488
|
}
|
|
1190
1489
|
finally {
|
|
1191
|
-
const
|
|
1192
|
-
if (
|
|
1193
|
-
await
|
|
1490
|
+
const result_17 = __disposeResources(env_17);
|
|
1491
|
+
if (result_17)
|
|
1492
|
+
await result_17;
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
testChecksumBatching(config);
|
|
1496
|
+
test('empty checkpoints (1)', async () => {
|
|
1497
|
+
const env_18 = { stack: [], error: void 0, hasError: false };
|
|
1498
|
+
try {
|
|
1499
|
+
const factory = __addDisposableResource(env_18, await generateStorageFactory(), true);
|
|
1500
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
1501
|
+
bucket_definitions:
|
|
1502
|
+
global:
|
|
1503
|
+
data:
|
|
1504
|
+
- SELECT id, description FROM "%"
|
|
1505
|
+
`, { storageVersion }));
|
|
1506
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1507
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1508
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1509
|
+
await batch.commit('1/1');
|
|
1510
|
+
const cp1 = await bucketStorage.getCheckpoint();
|
|
1511
|
+
expect(cp1.lsn).toEqual('1/1');
|
|
1512
|
+
await batch.commit('2/1', { createEmptyCheckpoints: true });
|
|
1513
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1514
|
+
expect(cp2.lsn).toEqual('2/1');
|
|
1515
|
+
await batch.keepalive('3/1');
|
|
1516
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1517
|
+
expect(cp3.lsn).toEqual('3/1');
|
|
1518
|
+
// For the last one, we skip creating empty checkpoints
|
|
1519
|
+
// This means the LSN stays at 3/1.
|
|
1520
|
+
await batch.commit('4/1', { createEmptyCheckpoints: false });
|
|
1521
|
+
const cp4 = await bucketStorage.getCheckpoint();
|
|
1522
|
+
expect(cp4.lsn).toEqual('3/1');
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
catch (e_18) {
|
|
1526
|
+
env_18.error = e_18;
|
|
1527
|
+
env_18.hasError = true;
|
|
1528
|
+
}
|
|
1529
|
+
finally {
|
|
1530
|
+
const result_18 = __disposeResources(env_18);
|
|
1531
|
+
if (result_18)
|
|
1532
|
+
await result_18;
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
test('empty checkpoints (2)', async () => {
|
|
1536
|
+
const env_19 = { stack: [], error: void 0, hasError: false };
|
|
1537
|
+
try {
|
|
1538
|
+
const factory = __addDisposableResource(env_19, await generateStorageFactory(), true);
|
|
1539
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
1540
|
+
bucket_definitions:
|
|
1541
|
+
global:
|
|
1542
|
+
data:
|
|
1543
|
+
- SELECT id, description FROM "%"
|
|
1544
|
+
`, {
|
|
1545
|
+
storageVersion
|
|
1546
|
+
}));
|
|
1547
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1548
|
+
const sourceTable = TEST_TABLE;
|
|
1549
|
+
// We simulate two concurrent batches, but nesting is the easiest way to do this.
|
|
1550
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch1) => {
|
|
1551
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch2) => {
|
|
1552
|
+
await batch1.markAllSnapshotDone('1/1');
|
|
1553
|
+
await batch1.commit('1/1');
|
|
1554
|
+
await batch1.commit('2/1', { createEmptyCheckpoints: false });
|
|
1555
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1556
|
+
expect(cp2.lsn).toEqual('1/1'); // checkpoint 2/1 skipped
|
|
1557
|
+
await batch2.save({
|
|
1558
|
+
sourceTable,
|
|
1559
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1560
|
+
after: {
|
|
1561
|
+
id: 'test1',
|
|
1562
|
+
description: 'test1a'
|
|
1563
|
+
},
|
|
1564
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1565
|
+
});
|
|
1566
|
+
// This simulates what happens on a snapshot processor.
|
|
1567
|
+
// This may later change to a flush() rather than commit().
|
|
1568
|
+
await batch2.commit(test_utils.BATCH_OPTIONS.zeroLSN);
|
|
1569
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1570
|
+
expect(cp3.lsn).toEqual('1/1'); // Still unchanged
|
|
1571
|
+
// This now needs to advance the LSN, despite {createEmptyCheckpoints: false}
|
|
1572
|
+
await batch1.commit('4/1', { createEmptyCheckpoints: false });
|
|
1573
|
+
const cp4 = await bucketStorage.getCheckpoint();
|
|
1574
|
+
expect(cp4.lsn).toEqual('4/1');
|
|
1575
|
+
});
|
|
1576
|
+
});
|
|
1577
|
+
}
|
|
1578
|
+
catch (e_19) {
|
|
1579
|
+
env_19.error = e_19;
|
|
1580
|
+
env_19.hasError = true;
|
|
1581
|
+
}
|
|
1582
|
+
finally {
|
|
1583
|
+
const result_19 = __disposeResources(env_19);
|
|
1584
|
+
if (result_19)
|
|
1585
|
+
await result_19;
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
test('empty checkpoints (sync rule activation)', async () => {
|
|
1589
|
+
const env_20 = { stack: [], error: void 0, hasError: false };
|
|
1590
|
+
try {
|
|
1591
|
+
const factory = __addDisposableResource(env_20, await generateStorageFactory(), true);
|
|
1592
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
1593
|
+
bucket_definitions:
|
|
1594
|
+
global:
|
|
1595
|
+
data:
|
|
1596
|
+
- SELECT id, description FROM "%"
|
|
1597
|
+
`, {
|
|
1598
|
+
storageVersion
|
|
1599
|
+
}));
|
|
1600
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1601
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1602
|
+
const result = await batch.commit('1/1', { createEmptyCheckpoints: false });
|
|
1603
|
+
expect(result).toEqual({ checkpointBlocked: true, checkpointCreated: false });
|
|
1604
|
+
// Snapshot is only valid once we reach 3/1
|
|
1605
|
+
await batch.markAllSnapshotDone('3/1');
|
|
1606
|
+
});
|
|
1607
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1608
|
+
// 2/1 < 3/1 - snapshot not valid yet, block checkpoint
|
|
1609
|
+
const result = await batch.commit('2/1', { createEmptyCheckpoints: false });
|
|
1610
|
+
expect(result).toEqual({ checkpointBlocked: true, checkpointCreated: false });
|
|
1611
|
+
});
|
|
1612
|
+
// No empty checkpoint should be created by the commit above.
|
|
1613
|
+
const cp1 = await bucketStorage.getCheckpoint();
|
|
1614
|
+
expect(cp1.lsn).toEqual(null);
|
|
1615
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1616
|
+
// After this commit, the snapshot should be valid.
|
|
1617
|
+
// We specifically check that this is done even if createEmptyCheckpoints: false.
|
|
1618
|
+
const result = await batch.commit('3/1', { createEmptyCheckpoints: false });
|
|
1619
|
+
expect(result).toEqual({ checkpointBlocked: false, checkpointCreated: true });
|
|
1620
|
+
});
|
|
1621
|
+
// Now, the checkpoint should advance the sync rules active.
|
|
1622
|
+
const cp2 = await bucketStorage.getCheckpoint();
|
|
1623
|
+
expect(cp2.lsn).toEqual('3/1');
|
|
1624
|
+
const activeSyncRules = await factory.getActiveSyncRulesContent();
|
|
1625
|
+
expect(activeSyncRules?.id).toEqual(syncRules.id);
|
|
1626
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1627
|
+
// At this point, it should be a truely empty checkpoint
|
|
1628
|
+
const result = await batch.commit('4/1', { createEmptyCheckpoints: false });
|
|
1629
|
+
expect(result).toEqual({ checkpointBlocked: false, checkpointCreated: false });
|
|
1630
|
+
});
|
|
1631
|
+
// Unchanged
|
|
1632
|
+
const cp3 = await bucketStorage.getCheckpoint();
|
|
1633
|
+
expect(cp3.lsn).toEqual('3/1');
|
|
1634
|
+
}
|
|
1635
|
+
catch (e_20) {
|
|
1636
|
+
env_20.error = e_20;
|
|
1637
|
+
env_20.hasError = true;
|
|
1638
|
+
}
|
|
1639
|
+
finally {
|
|
1640
|
+
const result_20 = __disposeResources(env_20);
|
|
1641
|
+
if (result_20)
|
|
1642
|
+
await result_20;
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
test.runIf(storageVersion >= 3)('deleting while streaming', async () => {
|
|
1646
|
+
const env_21 = { stack: [], error: void 0, hasError: false };
|
|
1647
|
+
try {
|
|
1648
|
+
const factory = __addDisposableResource(env_21, await generateStorageFactory(), true);
|
|
1649
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
1650
|
+
bucket_definitions:
|
|
1651
|
+
global:
|
|
1652
|
+
data:
|
|
1653
|
+
- SELECT id, description FROM "%"
|
|
1654
|
+
`, {
|
|
1655
|
+
storageVersion
|
|
1656
|
+
}));
|
|
1657
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1658
|
+
const sourceTable = TEST_TABLE;
|
|
1659
|
+
// We simulate two concurrent batches, and nesting is the easiest way to do this.
|
|
1660
|
+
// For this test, we assume that we start with a row "test1", which is picked up by a snapshot
|
|
1661
|
+
// query, right before the delete is streamed. But the snapshot query is only persisted _after_
|
|
1662
|
+
// the delete is streamed, and we need to ensure that the streamed delete takes precedence.
|
|
1663
|
+
await bucketStorage.startBatch({ ...test_utils.BATCH_OPTIONS, skipExistingRows: true }, async (snapshotBatch) => {
|
|
1664
|
+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (streamingBatch) => {
|
|
1665
|
+
streamingBatch.save({
|
|
1666
|
+
sourceTable,
|
|
1667
|
+
tag: storage.SaveOperationTag.DELETE,
|
|
1668
|
+
before: {
|
|
1669
|
+
id: 'test1'
|
|
1670
|
+
},
|
|
1671
|
+
beforeReplicaId: test_utils.rid('test1')
|
|
1672
|
+
});
|
|
1673
|
+
await streamingBatch.commit('2/1');
|
|
1674
|
+
await snapshotBatch.save({
|
|
1675
|
+
sourceTable,
|
|
1676
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1677
|
+
after: {
|
|
1678
|
+
id: 'test1',
|
|
1679
|
+
description: 'test1a'
|
|
1680
|
+
},
|
|
1681
|
+
afterReplicaId: test_utils.rid('test1')
|
|
1682
|
+
});
|
|
1683
|
+
await snapshotBatch.markAllSnapshotDone('3/1');
|
|
1684
|
+
await snapshotBatch.commit('1/1');
|
|
1685
|
+
await streamingBatch.keepalive('3/1');
|
|
1686
|
+
});
|
|
1687
|
+
});
|
|
1688
|
+
const cp = await bucketStorage.getCheckpoint();
|
|
1689
|
+
expect(cp.lsn).toEqual('3/1');
|
|
1690
|
+
const data = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(cp.checkpoint, bucketRequestMap(syncRules, [['global[]', 0n]])));
|
|
1691
|
+
expect(data).toEqual([]);
|
|
1692
|
+
}
|
|
1693
|
+
catch (e_21) {
|
|
1694
|
+
env_21.error = e_21;
|
|
1695
|
+
env_21.hasError = true;
|
|
1696
|
+
}
|
|
1697
|
+
finally {
|
|
1698
|
+
const result_21 = __disposeResources(env_21);
|
|
1699
|
+
if (result_21)
|
|
1700
|
+
await result_21;
|
|
1194
1701
|
}
|
|
1195
1702
|
});
|
|
1196
|
-
testChecksumBatching(generateStorageFactory);
|
|
1197
1703
|
}
|
|
1198
1704
|
/**
|
|
1199
1705
|
* This specifically tests an issue we ran into with MongoDB storage.
|
|
1200
1706
|
*
|
|
1201
1707
|
* Exposed as a separate test so we can test with more storage parameters.
|
|
1202
1708
|
*/
|
|
1203
|
-
export function testChecksumBatching(
|
|
1709
|
+
export function testChecksumBatching(config) {
|
|
1710
|
+
const storageVersion = config.storageVersion ?? CURRENT_STORAGE_VERSION;
|
|
1204
1711
|
test('checksums for multiple buckets', async () => {
|
|
1205
|
-
const
|
|
1712
|
+
const env_22 = { stack: [], error: void 0, hasError: false };
|
|
1206
1713
|
try {
|
|
1207
|
-
const factory = __addDisposableResource(
|
|
1208
|
-
const syncRules = await factory.updateSyncRules(
|
|
1209
|
-
content: `
|
|
1714
|
+
const factory = __addDisposableResource(env_22, await config.factory(), true);
|
|
1715
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
1210
1716
|
bucket_definitions:
|
|
1211
1717
|
user:
|
|
1212
1718
|
parameters: select request.user_id() as user_id
|
|
1213
1719
|
data:
|
|
1214
1720
|
- select id, description from test where user_id = bucket.user_id
|
|
1215
|
-
|
|
1216
|
-
|
|
1721
|
+
`, {
|
|
1722
|
+
storageVersion
|
|
1723
|
+
}));
|
|
1217
1724
|
const bucketStorage = factory.getInstance(syncRules);
|
|
1218
|
-
const sourceTable =
|
|
1725
|
+
const sourceTable = test_utils.makeTestTable('test', ['id'], config);
|
|
1219
1726
|
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
|
|
1727
|
+
await batch.markAllSnapshotDone('1/1');
|
|
1220
1728
|
for (let u of ['u1', 'u2', 'u3', 'u4']) {
|
|
1221
1729
|
for (let t of ['t1', 't2', 't3', 't4']) {
|
|
1222
1730
|
const id = `${t}_${u}`;
|
|
@@ -1236,24 +1744,24 @@ bucket_definitions:
|
|
|
1236
1744
|
});
|
|
1237
1745
|
const { checkpoint } = await bucketStorage.getCheckpoint();
|
|
1238
1746
|
bucketStorage.clearChecksumCache();
|
|
1239
|
-
const buckets = ['user["u1"]', 'user["u2"]', 'user["u3"]', 'user["u4"]'];
|
|
1747
|
+
const buckets = bucketRequests(syncRules, ['user["u1"]', 'user["u2"]', 'user["u3"]', 'user["u4"]']);
|
|
1240
1748
|
const checksums = [...(await bucketStorage.getChecksums(checkpoint, buckets)).values()];
|
|
1241
1749
|
checksums.sort((a, b) => a.bucket.localeCompare(b.bucket));
|
|
1242
1750
|
expect(checksums).toEqual([
|
|
1243
|
-
{ bucket: 'user["u1"]', count: 4, checksum: 346204588 },
|
|
1244
|
-
{ bucket: 'user["u2"]', count: 4, checksum: 5261081 },
|
|
1245
|
-
{ bucket: 'user["u3"]', count: 4, checksum: 134760718 },
|
|
1246
|
-
{ bucket: 'user["u4"]', count: 4, checksum: -302639724 }
|
|
1751
|
+
{ bucket: bucketRequest(syncRules, 'user["u1"]').bucket, count: 4, checksum: 346204588 },
|
|
1752
|
+
{ bucket: bucketRequest(syncRules, 'user["u2"]').bucket, count: 4, checksum: 5261081 },
|
|
1753
|
+
{ bucket: bucketRequest(syncRules, 'user["u3"]').bucket, count: 4, checksum: 134760718 },
|
|
1754
|
+
{ bucket: bucketRequest(syncRules, 'user["u4"]').bucket, count: 4, checksum: -302639724 }
|
|
1247
1755
|
]);
|
|
1248
1756
|
}
|
|
1249
|
-
catch (
|
|
1250
|
-
|
|
1251
|
-
|
|
1757
|
+
catch (e_22) {
|
|
1758
|
+
env_22.error = e_22;
|
|
1759
|
+
env_22.hasError = true;
|
|
1252
1760
|
}
|
|
1253
1761
|
finally {
|
|
1254
|
-
const
|
|
1255
|
-
if (
|
|
1256
|
-
await
|
|
1762
|
+
const result_22 = __disposeResources(env_22);
|
|
1763
|
+
if (result_22)
|
|
1764
|
+
await result_22;
|
|
1257
1765
|
}
|
|
1258
1766
|
});
|
|
1259
1767
|
}
|