@powersync/service-core-tests 0.15.4 → 0.16.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 +53 -0
- package/dist/test-utils/AbstractStreamTestContext.d.ts +51 -0
- package/dist/test-utils/AbstractStreamTestContext.js +143 -0
- package/dist/test-utils/AbstractStreamTestContext.js.map +1 -0
- package/dist/test-utils/StorageDataHelpers.d.ts +8 -0
- package/dist/test-utils/StorageDataHelpers.js +33 -0
- package/dist/test-utils/StorageDataHelpers.js.map +1 -0
- package/dist/test-utils/general-utils.d.ts +3 -8
- package/dist/test-utils/general-utils.js +28 -26
- package/dist/test-utils/general-utils.js.map +1 -1
- package/dist/test-utils/test-utils-index.d.ts +2 -0
- package/dist/test-utils/test-utils-index.js +2 -0
- package/dist/test-utils/test-utils-index.js.map +1 -1
- package/dist/tests/register-data-storage-data-tests.js +3 -3
- package/dist/tests/register-data-storage-data-tests.js.map +1 -1
- package/dist/tests/register-data-storage-parameter-tests.js +436 -65
- package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
- package/dist/tests/register-parameter-compacting-tests.js +8 -8
- package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
- package/dist/tests/util.d.ts +1 -2
- package/dist/tests/util.js +8 -5
- package/dist/tests/util.js.map +1 -1
- package/package.json +7 -6
- package/src/test-utils/AbstractStreamTestContext.ts +179 -0
- package/src/test-utils/StorageDataHelpers.ts +44 -0
- package/src/test-utils/general-utils.ts +31 -31
- package/src/test-utils/test-utils-index.ts +2 -0
- package/src/tests/register-data-storage-data-tests.ts +3 -3
- package/src/tests/register-data-storage-parameter-tests.ts +519 -67
- package/src/tests/register-parameter-compacting-tests.ts +8 -8
- package/src/tests/util.ts +12 -9
- package/tsconfig.json +3 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { CURRENT_STORAGE_VERSION, JwtPayload, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ParameterIndexLookupCreator,
|
|
4
|
+
RequestParameters,
|
|
5
|
+
ScopedParameterLookup,
|
|
6
|
+
SqliteJsonRow,
|
|
7
|
+
UnscopedParameterLookup
|
|
8
|
+
} from '@powersync/service-sync-rules';
|
|
3
9
|
import { expect, test } from 'vitest';
|
|
4
10
|
import * as test_utils from '../test-utils/test-utils-index.js';
|
|
5
11
|
import { bucketRequest } from '../test-utils/test-utils-index.js';
|
|
6
|
-
import { parameterLookupScope } from './util.js';
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* @example
|
|
@@ -18,7 +23,6 @@ import { parameterLookupScope } from './util.js';
|
|
|
18
23
|
export function registerDataStorageParameterTests(config: storage.TestStorageConfig) {
|
|
19
24
|
const generateStorageFactory = config.factory;
|
|
20
25
|
const storageVersion = config.storageVersion ?? CURRENT_STORAGE_VERSION;
|
|
21
|
-
const MYBUCKET_1 = parameterLookupScope('mybucket', '1');
|
|
22
26
|
|
|
23
27
|
test('save and load parameters', async () => {
|
|
24
28
|
await using factory = await generateStorageFactory();
|
|
@@ -37,6 +41,7 @@ bucket_definitions:
|
|
|
37
41
|
)
|
|
38
42
|
);
|
|
39
43
|
const bucketStorage = factory.getInstance(syncRules);
|
|
44
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
40
45
|
|
|
41
46
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
42
47
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -69,12 +74,20 @@ bucket_definitions:
|
|
|
69
74
|
await writer.commit('1/1');
|
|
70
75
|
|
|
71
76
|
const checkpoint = await bucketStorage.getCheckpoint();
|
|
72
|
-
const parameters =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
78
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
79
|
+
|
|
80
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
81
|
+
async getParameterSets(lookups) {
|
|
82
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
83
|
+
|
|
84
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
85
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group1a' }] }]);
|
|
86
|
+
return parameter_sets;
|
|
76
87
|
}
|
|
77
|
-
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(buckets.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1a"]').bucket]);
|
|
78
91
|
});
|
|
79
92
|
|
|
80
93
|
test('it should use the latest version', async () => {
|
|
@@ -94,6 +107,7 @@ bucket_definitions:
|
|
|
94
107
|
)
|
|
95
108
|
);
|
|
96
109
|
const bucketStorage = factory.getInstance(syncRules);
|
|
110
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
97
111
|
|
|
98
112
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
99
113
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -121,20 +135,30 @@ bucket_definitions:
|
|
|
121
135
|
await writer.commit('1/2');
|
|
122
136
|
const checkpoint2 = await bucketStorage.getCheckpoint();
|
|
123
137
|
|
|
124
|
-
const parameters =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
138
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
139
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
140
|
+
|
|
141
|
+
const buckets1 = await querier.queryDynamicBucketDescriptions({
|
|
142
|
+
async getParameterSets(lookups) {
|
|
143
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
144
|
+
|
|
145
|
+
const parameter_sets = await checkpoint1.getParameterSets(lookups, 1000);
|
|
146
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group1' }] }]);
|
|
147
|
+
return parameter_sets;
|
|
128
148
|
}
|
|
129
|
-
|
|
149
|
+
});
|
|
150
|
+
expect(buckets1.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1"]').bucket]);
|
|
130
151
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
const buckets2 = await querier.queryDynamicBucketDescriptions({
|
|
153
|
+
async getParameterSets(lookups) {
|
|
154
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
155
|
+
|
|
156
|
+
const parameter_sets = await checkpoint2.getParameterSets(lookups, 1000);
|
|
157
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group2' }] }]);
|
|
158
|
+
return parameter_sets;
|
|
136
159
|
}
|
|
137
|
-
|
|
160
|
+
});
|
|
161
|
+
expect(buckets2.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group2"]').bucket]);
|
|
138
162
|
});
|
|
139
163
|
|
|
140
164
|
test('it should use the latest version after updates', async () => {
|
|
@@ -154,6 +178,7 @@ bucket_definitions:
|
|
|
154
178
|
)
|
|
155
179
|
);
|
|
156
180
|
const bucketStorage = factory.getInstance(syncRules);
|
|
181
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
157
182
|
|
|
158
183
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
159
184
|
const table = await test_utils.resolveTestTable(writer, 'todos', ['id', 'list_id'], config);
|
|
@@ -197,18 +222,29 @@ bucket_definitions:
|
|
|
197
222
|
// There removal operation for the association of `list2`::`todo2` should not interfere with the new
|
|
198
223
|
// association of `list1`::`todo2`
|
|
199
224
|
const checkpoint = await bucketStorage.getCheckpoint();
|
|
200
|
-
const parameters =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
225
|
+
const parameters = new RequestParameters(
|
|
226
|
+
new JwtPayload({ sub: 'u1', parameters: { list_id: ['list1', 'list2'] } }),
|
|
227
|
+
{}
|
|
228
|
+
);
|
|
229
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
204
230
|
|
|
205
|
-
|
|
206
|
-
{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
231
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
232
|
+
async getParameterSets(lookups) {
|
|
233
|
+
expect(lookups.map((l) => JSON.stringify(l.indexKey)).sort()).toEqual(['["list1"]', '["list2"]']);
|
|
234
|
+
|
|
235
|
+
const results = await checkpoint.getParameterSets(lookups, 1000);
|
|
236
|
+
const allRows = results.flatMap(({ rows }) => rows);
|
|
237
|
+
expect(allRows.sort((a, b) => (a.todo_id as string).localeCompare(b.todo_id as string))).toEqual([
|
|
238
|
+
{ todo_id: 'todo1' },
|
|
239
|
+
{ todo_id: 'todo2' }
|
|
240
|
+
]);
|
|
241
|
+
return results;
|
|
211
242
|
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(buckets.map((b) => b.bucket).sort()).toEqual([
|
|
246
|
+
bucketRequest(syncRules, 'mybucket["todo1"]').bucket,
|
|
247
|
+
bucketRequest(syncRules, 'mybucket["todo2"]').bucket
|
|
212
248
|
]);
|
|
213
249
|
});
|
|
214
250
|
|
|
@@ -229,6 +265,7 @@ bucket_definitions:
|
|
|
229
265
|
)
|
|
230
266
|
);
|
|
231
267
|
const bucketStorage = factory.getInstance(syncRules);
|
|
268
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
232
269
|
|
|
233
270
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
234
271
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -248,20 +285,31 @@ bucket_definitions:
|
|
|
248
285
|
|
|
249
286
|
await writer.commit('1/1');
|
|
250
287
|
|
|
251
|
-
const TEST_PARAMS = { group_id: 'group1' };
|
|
252
|
-
|
|
253
288
|
const checkpoint = await bucketStorage.getCheckpoint();
|
|
289
|
+
const testQuery = async (jwtParameters: Record<string, any>, expectedParameterSets: SqliteJsonRow[]) => {
|
|
290
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: jwtParameters }), {});
|
|
291
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
254
292
|
|
|
255
|
-
|
|
256
|
-
|
|
293
|
+
return await querier.queryDynamicBucketDescriptions({
|
|
294
|
+
async getParameterSets(lookups) {
|
|
295
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
296
|
+
if (expectedParameterSets.length == 0) {
|
|
297
|
+
expect(parameter_sets).toEqual([]);
|
|
298
|
+
} else {
|
|
299
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: expectedParameterSets }]);
|
|
300
|
+
}
|
|
301
|
+
return parameter_sets;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
expect(await testQuery({ n1: 314n, f2: 314, f3: 3.14 }, [{ group_id: 'group1' }])).toMatchObject([
|
|
307
|
+
{ bucket: bucketRequest(syncRules, 'mybucket["group1"]').bucket }
|
|
257
308
|
]);
|
|
258
|
-
expect(
|
|
259
|
-
|
|
260
|
-
ScopedParameterLookup.direct(MYBUCKET_1, [314, 314n, 3.14])
|
|
309
|
+
expect(await testQuery({ n1: 314, f2: 314n, f3: 3.14 }, [{ group_id: 'group1' }])).toMatchObject([
|
|
310
|
+
{ bucket: bucketRequest(syncRules, 'mybucket["group1"]').bucket }
|
|
261
311
|
]);
|
|
262
|
-
expect(
|
|
263
|
-
const parameters3 = await checkpoint.getParameterSets([ScopedParameterLookup.direct(MYBUCKET_1, [314n, 314, 3])]);
|
|
264
|
-
expect(parameters3).toEqual([]);
|
|
312
|
+
expect(await testQuery({ n1: 314n, f2: 314, f3: 3 }, [])).toEqual([]);
|
|
265
313
|
});
|
|
266
314
|
|
|
267
315
|
test('save and load parameters with large numbers', async () => {
|
|
@@ -285,6 +333,7 @@ bucket_definitions:
|
|
|
285
333
|
)
|
|
286
334
|
);
|
|
287
335
|
const bucketStorage = factory.getInstance(syncRules);
|
|
336
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
288
337
|
|
|
289
338
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
290
339
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -315,14 +364,23 @@ bucket_definitions:
|
|
|
315
364
|
|
|
316
365
|
await writer.commit('1/1');
|
|
317
366
|
|
|
318
|
-
const TEST_PARAMS = { group_id: 'group1' };
|
|
319
|
-
|
|
320
367
|
const checkpoint = await bucketStorage.getCheckpoint();
|
|
321
368
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
369
|
+
const n1 = 1152921504606846976n;
|
|
370
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: { n1 } }), {});
|
|
371
|
+
|
|
372
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
373
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
374
|
+
getParameterSets: async (lookups) => {
|
|
375
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([[n1]]);
|
|
376
|
+
|
|
377
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
378
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group1' }] }]);
|
|
379
|
+
return parameter_sets;
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
expect(buckets.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1"]').bucket]);
|
|
326
384
|
});
|
|
327
385
|
|
|
328
386
|
test('save and load parameters with workspaceId', async () => {
|
|
@@ -342,7 +400,7 @@ bucket_definitions:
|
|
|
342
400
|
}
|
|
343
401
|
)
|
|
344
402
|
);
|
|
345
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).
|
|
403
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
346
404
|
const bucketStorage = factory.getInstance(syncRules);
|
|
347
405
|
|
|
348
406
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
@@ -366,10 +424,10 @@ bucket_definitions:
|
|
|
366
424
|
|
|
367
425
|
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
368
426
|
async getParameterSets(lookups) {
|
|
369
|
-
expect(lookups
|
|
427
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['u1']]);
|
|
370
428
|
|
|
371
|
-
const parameter_sets = await checkpoint.getParameterSets(lookups);
|
|
372
|
-
expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }]);
|
|
429
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
430
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ workspace_id: 'workspace1' }] }]);
|
|
373
431
|
return parameter_sets;
|
|
374
432
|
}
|
|
375
433
|
});
|
|
@@ -400,7 +458,7 @@ bucket_definitions:
|
|
|
400
458
|
}
|
|
401
459
|
)
|
|
402
460
|
);
|
|
403
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).
|
|
461
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
404
462
|
const bucketStorage = factory.getInstance(syncRules);
|
|
405
463
|
|
|
406
464
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
@@ -446,11 +504,15 @@ bucket_definitions:
|
|
|
446
504
|
|
|
447
505
|
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
448
506
|
async getParameterSets(lookups) {
|
|
449
|
-
expect(lookups
|
|
507
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([[]]);
|
|
450
508
|
|
|
451
|
-
const parameter_sets = await checkpoint.getParameterSets(lookups);
|
|
452
|
-
parameter_sets
|
|
453
|
-
|
|
509
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
510
|
+
expect(parameter_sets).toHaveLength(1);
|
|
511
|
+
|
|
512
|
+
const [{ lookup, rows }] = parameter_sets;
|
|
513
|
+
expect(lookup).toEqual(lookups[0]);
|
|
514
|
+
rows.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
515
|
+
expect(rows).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
|
|
454
516
|
return parameter_sets;
|
|
455
517
|
}
|
|
456
518
|
});
|
|
@@ -490,7 +552,7 @@ bucket_definitions:
|
|
|
490
552
|
}
|
|
491
553
|
)
|
|
492
554
|
);
|
|
493
|
-
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).
|
|
555
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
494
556
|
const bucketStorage = factory.getInstance(syncRules);
|
|
495
557
|
|
|
496
558
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
@@ -554,16 +616,17 @@ bucket_definitions:
|
|
|
554
616
|
await querier.queryDynamicBucketDescriptions({
|
|
555
617
|
async getParameterSets(lookups) {
|
|
556
618
|
foundLookups.push(...lookups);
|
|
557
|
-
const output = await checkpoint.getParameterSets(lookups);
|
|
558
|
-
|
|
619
|
+
const output = await checkpoint.getParameterSets(lookups, 1000);
|
|
620
|
+
for (const { rows } of output) {
|
|
621
|
+
parameter_sets.push(...rows);
|
|
622
|
+
}
|
|
623
|
+
|
|
559
624
|
return output;
|
|
560
625
|
}
|
|
561
626
|
})
|
|
562
627
|
).map((e) => e.bucket);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
ScopedParameterLookup.direct(parameterLookupScope('by_workspace', '2'), ['u1'])
|
|
566
|
-
]);
|
|
628
|
+
// Not testing the scope anymore - the exact format depends on storage version
|
|
629
|
+
expect(foundLookups.map((l) => l.indexKey)).toEqual([[], ['u1']]);
|
|
567
630
|
parameter_sets.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
568
631
|
expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
|
|
569
632
|
|
|
@@ -591,6 +654,7 @@ bucket_definitions:
|
|
|
591
654
|
)
|
|
592
655
|
);
|
|
593
656
|
const bucketStorage = factory.getInstance(syncRules);
|
|
657
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
594
658
|
|
|
595
659
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
596
660
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -611,9 +675,19 @@ bucket_definitions:
|
|
|
611
675
|
await writer.flush();
|
|
612
676
|
|
|
613
677
|
const checkpoint = await bucketStorage.getCheckpoint();
|
|
678
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
679
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
614
680
|
|
|
615
|
-
const
|
|
616
|
-
|
|
681
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
682
|
+
async getParameterSets(lookups) {
|
|
683
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
684
|
+
|
|
685
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
686
|
+
expect(parameter_sets).toEqual([]);
|
|
687
|
+
return parameter_sets;
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
expect(buckets).toEqual([]);
|
|
617
691
|
});
|
|
618
692
|
|
|
619
693
|
test('invalidate cached parsed sync rules', async () => {
|
|
@@ -671,6 +745,7 @@ streams:
|
|
|
671
745
|
`)
|
|
672
746
|
);
|
|
673
747
|
const bucketStorage = factory.getInstance(syncRules);
|
|
748
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
674
749
|
|
|
675
750
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
676
751
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -688,13 +763,390 @@ streams:
|
|
|
688
763
|
await writer.commit('1/1');
|
|
689
764
|
|
|
690
765
|
const checkpoint = await bucketStorage.getCheckpoint();
|
|
691
|
-
const parameters =
|
|
692
|
-
|
|
766
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'baz' }), {});
|
|
767
|
+
const querier = sync_rules.getBucketParameterQuerier({
|
|
768
|
+
...test_utils.querierOptions(parameters),
|
|
769
|
+
streams: {
|
|
770
|
+
stream: [
|
|
771
|
+
{
|
|
772
|
+
priorityOverride: null,
|
|
773
|
+
parameters: null,
|
|
774
|
+
opaque_id: 123
|
|
775
|
+
}
|
|
776
|
+
]
|
|
777
|
+
}
|
|
778
|
+
}).querier;
|
|
779
|
+
|
|
780
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
781
|
+
async getParameterSets(lookups) {
|
|
782
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['baz']]);
|
|
783
|
+
|
|
784
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
785
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ '0': 'bar' }] }]);
|
|
786
|
+
return parameter_sets;
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
expect(buckets).toHaveLength(1);
|
|
790
|
+
expect(buckets).toMatchObject([
|
|
791
|
+
{
|
|
792
|
+
bucket: expect.stringMatching(/stream.*\["bar"\]$/),
|
|
793
|
+
definition: 'stream',
|
|
794
|
+
inclusion_reasons: [{ subscription: 123 }],
|
|
795
|
+
priority: 3
|
|
796
|
+
}
|
|
693
797
|
]);
|
|
694
|
-
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
test('respects parameter limit', async () => {
|
|
801
|
+
await using factory = await generateStorageFactory();
|
|
802
|
+
const syncRules = await factory.updateSyncRules(
|
|
803
|
+
updateSyncRulesFromYaml(
|
|
804
|
+
`
|
|
805
|
+
config:
|
|
806
|
+
edition: 3
|
|
807
|
+
|
|
808
|
+
streams:
|
|
809
|
+
a:
|
|
810
|
+
auto_subscribe: true
|
|
811
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM b)
|
|
812
|
+
`,
|
|
813
|
+
{
|
|
814
|
+
storageVersion
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
);
|
|
818
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
819
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
820
|
+
|
|
821
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
822
|
+
const testTable = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
|
|
823
|
+
await writer.markAllSnapshotDone('1/1');
|
|
824
|
+
|
|
825
|
+
for (let i = 0; i < 10; i++) {
|
|
826
|
+
await writer.save({
|
|
827
|
+
sourceTable: testTable,
|
|
828
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
829
|
+
after: {
|
|
830
|
+
id: `t${i}`
|
|
831
|
+
},
|
|
832
|
+
afterReplicaId: test_utils.rid(`t${i}`)
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
await writer.commit('1/1');
|
|
837
|
+
|
|
838
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
839
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u' }), {});
|
|
840
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
841
|
+
|
|
842
|
+
await expect(
|
|
843
|
+
querier.queryDynamicBucketDescriptions({
|
|
844
|
+
async getParameterSets(lookups) {
|
|
845
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 5);
|
|
846
|
+
return parameter_sets;
|
|
847
|
+
}
|
|
848
|
+
})
|
|
849
|
+
).rejects.toThrow('Too many parameter results (limit was 5)');
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
test('sync streams store multiple parameter outputs for a single source row and lookup', async () => {
|
|
853
|
+
await using factory = await generateStorageFactory();
|
|
854
|
+
const syncRules = await factory.updateSyncRules(
|
|
855
|
+
updateSyncRulesFromYaml(`
|
|
856
|
+
config:
|
|
857
|
+
edition: 3
|
|
858
|
+
streams:
|
|
859
|
+
chat:
|
|
860
|
+
auto_subscribe: true
|
|
861
|
+
query: |
|
|
862
|
+
SELECT a.*
|
|
863
|
+
FROM a, b, json_each(b.x) x, json_each(b.y) y
|
|
864
|
+
WHERE a.x = x.value AND y.value = auth.user_id()
|
|
865
|
+
`)
|
|
866
|
+
);
|
|
867
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
868
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
869
|
+
|
|
870
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
871
|
+
const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
|
|
872
|
+
const firstState = {
|
|
873
|
+
id: 'id0',
|
|
874
|
+
x: JSON.stringify(['x1', 'x2']),
|
|
875
|
+
y: JSON.stringify(['y1', 'y2'])
|
|
876
|
+
};
|
|
877
|
+
const secondState = {
|
|
878
|
+
id: 'id0',
|
|
879
|
+
x: JSON.stringify(['x2']),
|
|
880
|
+
y: JSON.stringify(['y1', 'y2'])
|
|
881
|
+
};
|
|
882
|
+
const thirdState = {
|
|
883
|
+
id: 'id0',
|
|
884
|
+
x: JSON.stringify(['x2']),
|
|
885
|
+
y: JSON.stringify(['y2'])
|
|
886
|
+
};
|
|
887
|
+
const replicaId = test_utils.rid('id0');
|
|
888
|
+
|
|
889
|
+
await writer.markAllSnapshotDone('1/1');
|
|
890
|
+
await writer.save({
|
|
891
|
+
sourceTable: tableB,
|
|
892
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
893
|
+
after: firstState,
|
|
894
|
+
afterReplicaId: replicaId
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
await writer.commit('1/1');
|
|
898
|
+
|
|
899
|
+
let checkpoint = await bucketStorage.getCheckpoint();
|
|
900
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'y1' }), {});
|
|
901
|
+
const querier = sync_rules.getBucketParameterQuerier({
|
|
902
|
+
...test_utils.querierOptions(parameters)
|
|
903
|
+
}).querier;
|
|
904
|
+
|
|
905
|
+
let buckets = await querier.queryDynamicBucketDescriptions({
|
|
906
|
+
async getParameterSets(lookups) {
|
|
907
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
|
|
908
|
+
|
|
909
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
910
|
+
|
|
911
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ '0': 'x1' }, { '0': 'x2' }] }]);
|
|
912
|
+
return parameter_sets;
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
expect(buckets).toMatchObject([
|
|
695
917
|
{
|
|
696
|
-
|
|
918
|
+
bucket: expect.stringMatching(/chat.*\["x1"\]$/),
|
|
919
|
+
definition: 'chat',
|
|
920
|
+
inclusion_reasons: ['default'],
|
|
921
|
+
priority: 3
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
bucket: expect.stringMatching(/chat.*\["x2"\]$/),
|
|
925
|
+
definition: 'chat',
|
|
926
|
+
inclusion_reasons: ['default'],
|
|
927
|
+
priority: 3
|
|
928
|
+
}
|
|
929
|
+
]);
|
|
930
|
+
|
|
931
|
+
// Make the x2 bucket inaccessible
|
|
932
|
+
await writer.save({
|
|
933
|
+
sourceTable: tableB,
|
|
934
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
935
|
+
before: firstState,
|
|
936
|
+
after: secondState,
|
|
937
|
+
beforeReplicaId: replicaId,
|
|
938
|
+
afterReplicaId: replicaId
|
|
939
|
+
});
|
|
940
|
+
await writer.commit('1/2');
|
|
941
|
+
|
|
942
|
+
checkpoint = await bucketStorage.getCheckpoint();
|
|
943
|
+
buckets = await querier.queryDynamicBucketDescriptions({
|
|
944
|
+
async getParameterSets(lookups) {
|
|
945
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
|
|
946
|
+
|
|
947
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
948
|
+
|
|
949
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ '0': 'x2' }] }]);
|
|
950
|
+
return parameter_sets;
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
expect(buckets.map((bkt) => bkt.bucket)).toStrictEqual([expect.stringContaining('x2')]);
|
|
954
|
+
|
|
955
|
+
// Second update, remove user from inputs
|
|
956
|
+
await writer.save({
|
|
957
|
+
sourceTable: tableB,
|
|
958
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
959
|
+
before: secondState,
|
|
960
|
+
after: thirdState,
|
|
961
|
+
beforeReplicaId: replicaId,
|
|
962
|
+
afterReplicaId: replicaId
|
|
963
|
+
});
|
|
964
|
+
await writer.commit('1/3');
|
|
965
|
+
|
|
966
|
+
checkpoint = await bucketStorage.getCheckpoint();
|
|
967
|
+
buckets = await querier.queryDynamicBucketDescriptions({
|
|
968
|
+
async getParameterSets(lookups) {
|
|
969
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
|
|
970
|
+
|
|
971
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
972
|
+
|
|
973
|
+
expect(parameter_sets).toHaveLength(0);
|
|
974
|
+
return parameter_sets;
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
expect(buckets).toHaveLength(0);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
test('can request multiple lookups at once', async () => {
|
|
981
|
+
await using factory = await generateStorageFactory();
|
|
982
|
+
const syncRules = await factory.updateSyncRules(
|
|
983
|
+
updateSyncRulesFromYaml(`
|
|
984
|
+
config:
|
|
985
|
+
edition: 3
|
|
986
|
+
streams:
|
|
987
|
+
a:
|
|
988
|
+
auto_subscribe: true
|
|
989
|
+
query: SELECT * FROM a WHERE p IN (SELECT id FROM param_a WHERE u = auth.user_id())
|
|
990
|
+
b:
|
|
991
|
+
auto_subscribe: true
|
|
992
|
+
query: SELECT * FROM b WHERE p IN (SELECT id FROM param_b WHERE u = auth.user_id())
|
|
993
|
+
`)
|
|
994
|
+
);
|
|
995
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
996
|
+
const parsedSyncRules = syncRules.parsed(test_utils.PARSE_OPTIONS);
|
|
997
|
+
const hydrationState = parsedSyncRules.hydrationState;
|
|
998
|
+
const syncConfig = parsedSyncRules.syncConfigWithErrors.config;
|
|
999
|
+
|
|
1000
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1001
|
+
const paramATable = await test_utils.resolveTestTable(writer, 'param_a', ['id'], config, 1);
|
|
1002
|
+
const paramBTable = await test_utils.resolveTestTable(writer, 'param_b', ['id'], config, 2);
|
|
1003
|
+
const replicaId = test_utils.rid('id');
|
|
1004
|
+
|
|
1005
|
+
// Insert the same row into param_a and param_b
|
|
1006
|
+
await writer.markAllSnapshotDone('1/1');
|
|
1007
|
+
await writer.save({
|
|
1008
|
+
sourceTable: paramATable,
|
|
1009
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1010
|
+
after: {
|
|
1011
|
+
id: 'id',
|
|
1012
|
+
u: 'user'
|
|
1013
|
+
},
|
|
1014
|
+
afterReplicaId: replicaId
|
|
1015
|
+
});
|
|
1016
|
+
await writer.save({
|
|
1017
|
+
sourceTable: paramBTable,
|
|
1018
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1019
|
+
after: {
|
|
1020
|
+
id: 'id',
|
|
1021
|
+
u: 'user'
|
|
1022
|
+
},
|
|
1023
|
+
afterReplicaId: replicaId
|
|
1024
|
+
});
|
|
1025
|
+
await writer.commit('1/1');
|
|
1026
|
+
|
|
1027
|
+
function findParameterOnTable(name: string): ParameterIndexLookupCreator {
|
|
1028
|
+
for (const source of syncConfig.bucketParameterLookupSources) {
|
|
1029
|
+
for (const param of source.getSourceTables()) {
|
|
1030
|
+
if (param.name == name) return source;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
throw new Error(`Expected parameter index on ${name}`);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Run two lookups on separate parameter indexes.
|
|
1038
|
+
const lookupA = ScopedParameterLookup.normalized(
|
|
1039
|
+
hydrationState.getParameterIndexLookupScope(findParameterOnTable('param_a')),
|
|
1040
|
+
UnscopedParameterLookup.normalized(['user'])
|
|
1041
|
+
);
|
|
1042
|
+
const lookupB = ScopedParameterLookup.normalized(
|
|
1043
|
+
hydrationState.getParameterIndexLookupScope(findParameterOnTable('param_b')),
|
|
1044
|
+
UnscopedParameterLookup.normalized(['user'])
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
1048
|
+
const parameterSets = await checkpoint.getParameterSets([lookupA, lookupB], 1000);
|
|
1049
|
+
const expectedRow = { '0': 'id' };
|
|
1050
|
+
let foundLookupA = false,
|
|
1051
|
+
foundLookupB = false;
|
|
1052
|
+
|
|
1053
|
+
// We should get the same row on both, with information on which lookup contributed which row.
|
|
1054
|
+
for (const { lookup, rows } of parameterSets) {
|
|
1055
|
+
if (lookup === lookupA) {
|
|
1056
|
+
foundLookupA = true;
|
|
1057
|
+
expect(rows).toStrictEqual([expectedRow]);
|
|
1058
|
+
} else if (lookup === lookupB) {
|
|
1059
|
+
foundLookupB = true;
|
|
1060
|
+
expect(rows).toStrictEqual([expectedRow]);
|
|
1061
|
+
} else {
|
|
1062
|
+
throw new Error('unexpected lookup in results');
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
expect(foundLookupA).toBeTruthy();
|
|
1067
|
+
expect(foundLookupB).toBeTruthy();
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
test('sync streams preserve duplicate downstream lookups with different provenance', async () => {
|
|
1071
|
+
await using factory = await generateStorageFactory();
|
|
1072
|
+
const syncRules = await factory.updateSyncRules(
|
|
1073
|
+
updateSyncRulesFromYaml(
|
|
1074
|
+
`
|
|
1075
|
+
config:
|
|
1076
|
+
edition: 3
|
|
1077
|
+
streams:
|
|
1078
|
+
stream:
|
|
1079
|
+
auto_subscribe: true
|
|
1080
|
+
query: |
|
|
1081
|
+
SELECT a.*
|
|
1082
|
+
FROM a, b, c
|
|
1083
|
+
WHERE a.x = b.x
|
|
1084
|
+
AND a.z = c.z
|
|
1085
|
+
AND b.y = c.y
|
|
1086
|
+
AND c.u = auth.user_id()
|
|
1087
|
+
`,
|
|
1088
|
+
{
|
|
1089
|
+
storageVersion
|
|
1090
|
+
}
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1093
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
1094
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
1095
|
+
|
|
1096
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1097
|
+
const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config, 1);
|
|
1098
|
+
const tableC = await test_utils.resolveTestTable(writer, 'c', ['id'], config, 2);
|
|
1099
|
+
|
|
1100
|
+
await writer.markAllSnapshotDone('1/1');
|
|
1101
|
+
await writer.save({
|
|
1102
|
+
sourceTable: tableB,
|
|
1103
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1104
|
+
after: {
|
|
1105
|
+
id: 'b1',
|
|
1106
|
+
y: 'shared-y',
|
|
1107
|
+
x: 'x-from-shared-y'
|
|
1108
|
+
},
|
|
1109
|
+
afterReplicaId: test_utils.rid('b1')
|
|
1110
|
+
});
|
|
1111
|
+
await writer.save({
|
|
1112
|
+
sourceTable: tableC,
|
|
1113
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1114
|
+
after: {
|
|
1115
|
+
id: 'c1',
|
|
1116
|
+
u: 'user1',
|
|
1117
|
+
y: 'shared-y',
|
|
1118
|
+
z: 'z1'
|
|
1119
|
+
},
|
|
1120
|
+
afterReplicaId: test_utils.rid('c1')
|
|
1121
|
+
});
|
|
1122
|
+
await writer.save({
|
|
1123
|
+
sourceTable: tableC,
|
|
1124
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
1125
|
+
after: {
|
|
1126
|
+
id: 'c2',
|
|
1127
|
+
u: 'user1',
|
|
1128
|
+
y: 'shared-y',
|
|
1129
|
+
z: 'z2'
|
|
1130
|
+
},
|
|
1131
|
+
afterReplicaId: test_utils.rid('c2')
|
|
1132
|
+
});
|
|
1133
|
+
await writer.commit('1/1');
|
|
1134
|
+
|
|
1135
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
1136
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
1137
|
+
const querier = sync_rules.getBucketParameterQuerier({
|
|
1138
|
+
...test_utils.querierOptions(parameters)
|
|
1139
|
+
}).querier;
|
|
1140
|
+
|
|
1141
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
1142
|
+
async getParameterSets(lookups) {
|
|
1143
|
+
return checkpoint.getParameterSets(lookups, 1000);
|
|
697
1144
|
}
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
expect(buckets.map((bucket) => bucket.bucket).sort()).toStrictEqual([
|
|
1148
|
+
expect.stringMatching(/stream.*\["x-from-shared-y","z1"\]$/),
|
|
1149
|
+
expect.stringMatching(/stream.*\["x-from-shared-y","z2"\]$/)
|
|
698
1150
|
]);
|
|
699
1151
|
});
|
|
700
1152
|
}
|