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