@powersync/service-core-tests 0.0.0-dev-20260313100403 → 0.0.0-dev-20260601140105
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 +91 -5
- 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/MetricsHelper.js +1 -1
- package/dist/test-utils/MetricsHelper.js.map +1 -1
- 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/stream_utils.js +2 -2
- package/dist/test-utils/stream_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-compacting-tests.js +491 -627
- package/dist/tests/register-compacting-tests.js.map +1 -1
- package/dist/tests/register-data-storage-checkpoint-tests.js +192 -304
- package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
- package/dist/tests/register-data-storage-data-tests.js +1125 -1446
- package/dist/tests/register-data-storage-data-tests.js.map +1 -1
- package/dist/tests/register-data-storage-parameter-tests.js +816 -629
- package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
- package/dist/tests/register-migration-tests.js +63 -139
- package/dist/tests/register-migration-tests.js.map +1 -1
- package/dist/tests/register-parameter-compacting-tests.js +95 -171
- package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
- package/dist/tests/register-report-tests.js +22 -22
- package/dist/tests/register-report-tests.js.map +1 -1
- package/dist/tests/register-sync-tests.js +965 -1155
- package/dist/tests/register-sync-tests.js.map +1 -1
- package/dist/tests/tests-index.d.ts +4 -4
- package/dist/tests/tests-index.js +4 -4
- package/dist/tests/tests-index.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/MetricsHelper.ts +1 -1
- package/src/test-utils/StorageDataHelpers.ts +44 -0
- package/src/test-utils/general-utils.ts +31 -31
- package/src/test-utils/stream_utils.ts +2 -2
- package/src/test-utils/test-utils-index.ts +2 -0
- package/src/tests/register-data-storage-data-tests.ts +24 -2
- package/src/tests/register-data-storage-parameter-tests.ts +519 -67
- package/src/tests/register-parameter-compacting-tests.ts +8 -9
- package/src/tests/register-report-tests.ts +22 -22
- package/src/tests/register-sync-tests.ts +74 -0
- package/src/tests/tests-index.ts +4 -4
- package/src/tests/util.ts +12 -9
- package/tsconfig.json +3 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,61 +1,8 @@
|
|
|
1
|
-
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
-
if (value !== null && value !== void 0) {
|
|
3
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
-
var dispose, inner;
|
|
5
|
-
if (async) {
|
|
6
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
-
dispose = value[Symbol.asyncDispose];
|
|
8
|
-
}
|
|
9
|
-
if (dispose === void 0) {
|
|
10
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
-
dispose = value[Symbol.dispose];
|
|
12
|
-
if (async) inner = dispose;
|
|
13
|
-
}
|
|
14
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
-
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
-
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
-
}
|
|
18
|
-
else if (async) {
|
|
19
|
-
env.stack.push({ async: true });
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
};
|
|
23
|
-
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
-
return function (env) {
|
|
25
|
-
function fail(e) {
|
|
26
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
-
env.hasError = true;
|
|
28
|
-
}
|
|
29
|
-
var r, s = 0;
|
|
30
|
-
function next() {
|
|
31
|
-
while (r = env.stack.pop()) {
|
|
32
|
-
try {
|
|
33
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
-
if (r.dispose) {
|
|
35
|
-
var result = r.dispose.call(r.value);
|
|
36
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
-
}
|
|
38
|
-
else s |= 1;
|
|
39
|
-
}
|
|
40
|
-
catch (e) {
|
|
41
|
-
fail(e);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
-
if (env.hasError) throw env.error;
|
|
46
|
-
}
|
|
47
|
-
return next();
|
|
48
|
-
};
|
|
49
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
-
var e = new Error(message);
|
|
51
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
-
});
|
|
53
1
|
import { CURRENT_STORAGE_VERSION, JwtPayload, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
54
|
-
import { RequestParameters, ScopedParameterLookup } from '@powersync/service-sync-rules';
|
|
2
|
+
import { RequestParameters, ScopedParameterLookup, UnscopedParameterLookup } from '@powersync/service-sync-rules';
|
|
55
3
|
import { expect, test } from 'vitest';
|
|
56
4
|
import * as test_utils from '../test-utils/test-utils-index.js';
|
|
57
5
|
import { bucketRequest } from '../test-utils/test-utils-index.js';
|
|
58
|
-
import { parameterLookupScope } from './util.js';
|
|
59
6
|
/**
|
|
60
7
|
* @example
|
|
61
8
|
* ```TypeScript
|
|
@@ -69,133 +16,120 @@ import { parameterLookupScope } from './util.js';
|
|
|
69
16
|
export function registerDataStorageParameterTests(config) {
|
|
70
17
|
const generateStorageFactory = config.factory;
|
|
71
18
|
const storageVersion = config.storageVersion ?? CURRENT_STORAGE_VERSION;
|
|
72
|
-
const MYBUCKET_1 = parameterLookupScope('mybucket', '1');
|
|
73
19
|
test('save and load parameters', async () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const factory = __addDisposableResource(env_1, await generateStorageFactory(), true);
|
|
77
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
20
|
+
await using factory = await generateStorageFactory();
|
|
21
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
78
22
|
bucket_definitions:
|
|
79
23
|
mybucket:
|
|
80
24
|
parameters:
|
|
81
25
|
- SELECT group_id FROM test WHERE id1 = token_parameters.user_id OR id2 = token_parameters.user_id
|
|
82
26
|
data: []
|
|
83
27
|
`, {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const result_1 = __disposeResources(env_1);
|
|
127
|
-
if (result_1)
|
|
128
|
-
await result_1;
|
|
129
|
-
}
|
|
28
|
+
storageVersion
|
|
29
|
+
}));
|
|
30
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
31
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
32
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
33
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
34
|
+
await writer.markAllSnapshotDone('1/1');
|
|
35
|
+
await writer.save({
|
|
36
|
+
sourceTable: testTable,
|
|
37
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
38
|
+
after: {
|
|
39
|
+
id: 't2',
|
|
40
|
+
id1: 'user3',
|
|
41
|
+
id2: 'user4',
|
|
42
|
+
group_id: 'group2a'
|
|
43
|
+
},
|
|
44
|
+
afterReplicaId: test_utils.rid('t2')
|
|
45
|
+
});
|
|
46
|
+
await writer.save({
|
|
47
|
+
sourceTable: testTable,
|
|
48
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
49
|
+
after: {
|
|
50
|
+
id: 't1',
|
|
51
|
+
id1: 'user1',
|
|
52
|
+
id2: 'user2',
|
|
53
|
+
group_id: 'group1a'
|
|
54
|
+
},
|
|
55
|
+
afterReplicaId: test_utils.rid('t1')
|
|
56
|
+
});
|
|
57
|
+
await writer.commit('1/1');
|
|
58
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
59
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
60
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
61
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
62
|
+
async getParameterSets(lookups) {
|
|
63
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
64
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
65
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group1a' }] }]);
|
|
66
|
+
return parameter_sets;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
expect(buckets.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1a"]').bucket]);
|
|
130
70
|
});
|
|
131
71
|
test('it should use the latest version', async () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const factory = __addDisposableResource(env_2, await generateStorageFactory(), true);
|
|
135
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
72
|
+
await using factory = await generateStorageFactory();
|
|
73
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
136
74
|
bucket_definitions:
|
|
137
75
|
mybucket:
|
|
138
76
|
parameters:
|
|
139
77
|
- SELECT group_id FROM test WHERE id = token_parameters.user_id
|
|
140
78
|
data: []
|
|
141
79
|
`, {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
await result_2;
|
|
192
|
-
}
|
|
80
|
+
storageVersion
|
|
81
|
+
}));
|
|
82
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
83
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
84
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
85
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
86
|
+
await writer.markAllSnapshotDone('1/1');
|
|
87
|
+
await writer.save({
|
|
88
|
+
sourceTable: testTable,
|
|
89
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
90
|
+
after: {
|
|
91
|
+
id: 'user1',
|
|
92
|
+
group_id: 'group1'
|
|
93
|
+
},
|
|
94
|
+
afterReplicaId: test_utils.rid('user1')
|
|
95
|
+
});
|
|
96
|
+
await writer.commit('1/1');
|
|
97
|
+
const checkpoint1 = await bucketStorage.getCheckpoint();
|
|
98
|
+
await writer.save({
|
|
99
|
+
sourceTable: testTable,
|
|
100
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
101
|
+
after: {
|
|
102
|
+
id: 'user1',
|
|
103
|
+
group_id: 'group2'
|
|
104
|
+
},
|
|
105
|
+
afterReplicaId: test_utils.rid('user1')
|
|
106
|
+
});
|
|
107
|
+
await writer.commit('1/2');
|
|
108
|
+
const checkpoint2 = await bucketStorage.getCheckpoint();
|
|
109
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
110
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
111
|
+
const buckets1 = await querier.queryDynamicBucketDescriptions({
|
|
112
|
+
async getParameterSets(lookups) {
|
|
113
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
114
|
+
const parameter_sets = await checkpoint1.getParameterSets(lookups, 1000);
|
|
115
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group1' }] }]);
|
|
116
|
+
return parameter_sets;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
expect(buckets1.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1"]').bucket]);
|
|
120
|
+
const buckets2 = await querier.queryDynamicBucketDescriptions({
|
|
121
|
+
async getParameterSets(lookups) {
|
|
122
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
123
|
+
const parameter_sets = await checkpoint2.getParameterSets(lookups, 1000);
|
|
124
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group2' }] }]);
|
|
125
|
+
return parameter_sets;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
expect(buckets2.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group2"]').bucket]);
|
|
193
129
|
});
|
|
194
130
|
test('it should use the latest version after updates', async () => {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const factory = __addDisposableResource(env_3, await generateStorageFactory(), true);
|
|
198
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
131
|
+
await using factory = await generateStorageFactory();
|
|
132
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
199
133
|
bucket_definitions:
|
|
200
134
|
mybucket:
|
|
201
135
|
parameters:
|
|
@@ -204,186 +138,178 @@ bucket_definitions:
|
|
|
204
138
|
WHERE list_id IN token_parameters.list_id
|
|
205
139
|
data: []
|
|
206
140
|
`, { storageVersion }));
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
todo_id: '
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (result_3)
|
|
266
|
-
await result_3;
|
|
267
|
-
}
|
|
141
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
142
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
143
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
144
|
+
const table = await test_utils.resolveTestTable(writer, 'todos', ['id', 'list_id'], config);
|
|
145
|
+
await writer.markAllSnapshotDone('1/1');
|
|
146
|
+
// Create two todos which initially belong to different lists
|
|
147
|
+
await writer.save({
|
|
148
|
+
sourceTable: table,
|
|
149
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
150
|
+
after: {
|
|
151
|
+
id: 'todo1',
|
|
152
|
+
list_id: 'list1'
|
|
153
|
+
},
|
|
154
|
+
afterReplicaId: test_utils.rid('todo1')
|
|
155
|
+
});
|
|
156
|
+
await writer.save({
|
|
157
|
+
sourceTable: table,
|
|
158
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
159
|
+
after: {
|
|
160
|
+
id: 'todo2',
|
|
161
|
+
list_id: 'list2'
|
|
162
|
+
},
|
|
163
|
+
afterReplicaId: test_utils.rid('todo2')
|
|
164
|
+
});
|
|
165
|
+
await writer.commit('1/1');
|
|
166
|
+
// Update the second todo item to now belong to list 1
|
|
167
|
+
await writer.save({
|
|
168
|
+
sourceTable: table,
|
|
169
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
170
|
+
after: {
|
|
171
|
+
id: 'todo2',
|
|
172
|
+
list_id: 'list1'
|
|
173
|
+
},
|
|
174
|
+
afterReplicaId: test_utils.rid('todo2')
|
|
175
|
+
});
|
|
176
|
+
await writer.commit('1/1');
|
|
177
|
+
// We specifically request the todo_ids for both lists.
|
|
178
|
+
// There removal operation for the association of `list2`::`todo2` should not interfere with the new
|
|
179
|
+
// association of `list1`::`todo2`
|
|
180
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
181
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: { list_id: ['list1', 'list2'] } }), {});
|
|
182
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
183
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
184
|
+
async getParameterSets(lookups) {
|
|
185
|
+
expect(lookups.map((l) => JSON.stringify(l.indexKey)).sort()).toEqual(['["list1"]', '["list2"]']);
|
|
186
|
+
const results = await checkpoint.getParameterSets(lookups, 1000);
|
|
187
|
+
const allRows = results.flatMap(({ rows }) => rows);
|
|
188
|
+
expect(allRows.sort((a, b) => a.todo_id.localeCompare(b.todo_id))).toEqual([
|
|
189
|
+
{ todo_id: 'todo1' },
|
|
190
|
+
{ todo_id: 'todo2' }
|
|
191
|
+
]);
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
expect(buckets.map((b) => b.bucket).sort()).toEqual([
|
|
196
|
+
bucketRequest(syncRules, 'mybucket["todo1"]').bucket,
|
|
197
|
+
bucketRequest(syncRules, 'mybucket["todo2"]').bucket
|
|
198
|
+
]);
|
|
268
199
|
});
|
|
269
200
|
test('save and load parameters with different number types', async () => {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const factory = __addDisposableResource(env_4, await generateStorageFactory(), true);
|
|
273
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
201
|
+
await using factory = await generateStorageFactory();
|
|
202
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
274
203
|
bucket_definitions:
|
|
275
204
|
mybucket:
|
|
276
205
|
parameters:
|
|
277
206
|
- SELECT group_id FROM test WHERE n1 = token_parameters.n1 and f2 = token_parameters.f2 and f3 = token_parameters.f3
|
|
278
207
|
data: []
|
|
279
208
|
`, {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
209
|
+
storageVersion
|
|
210
|
+
}));
|
|
211
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
212
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
213
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
214
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
215
|
+
await writer.markAllSnapshotDone('1/1');
|
|
216
|
+
await writer.save({
|
|
217
|
+
sourceTable: testTable,
|
|
218
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
219
|
+
after: {
|
|
220
|
+
id: 't1',
|
|
221
|
+
group_id: 'group1',
|
|
222
|
+
n1: 314n,
|
|
223
|
+
f2: 314,
|
|
224
|
+
f3: 3.14
|
|
225
|
+
},
|
|
226
|
+
afterReplicaId: test_utils.rid('t1')
|
|
227
|
+
});
|
|
228
|
+
await writer.commit('1/1');
|
|
229
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
230
|
+
const testQuery = async (jwtParameters, expectedParameterSets) => {
|
|
231
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: jwtParameters }), {});
|
|
232
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
233
|
+
return await querier.queryDynamicBucketDescriptions({
|
|
234
|
+
async getParameterSets(lookups) {
|
|
235
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
236
|
+
if (expectedParameterSets.length == 0) {
|
|
237
|
+
expect(parameter_sets).toEqual([]);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: expectedParameterSets }]);
|
|
241
|
+
}
|
|
242
|
+
return parameter_sets;
|
|
243
|
+
}
|
|
297
244
|
});
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
])
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
ScopedParameterLookup.direct(MYBUCKET_1, [314, 314n, 3.14])
|
|
307
|
-
]);
|
|
308
|
-
expect(parameters2).toEqual([TEST_PARAMS]);
|
|
309
|
-
const parameters3 = await checkpoint.getParameterSets([ScopedParameterLookup.direct(MYBUCKET_1, [314n, 314, 3])]);
|
|
310
|
-
expect(parameters3).toEqual([]);
|
|
311
|
-
}
|
|
312
|
-
catch (e_4) {
|
|
313
|
-
env_4.error = e_4;
|
|
314
|
-
env_4.hasError = true;
|
|
315
|
-
}
|
|
316
|
-
finally {
|
|
317
|
-
const result_4 = __disposeResources(env_4);
|
|
318
|
-
if (result_4)
|
|
319
|
-
await result_4;
|
|
320
|
-
}
|
|
245
|
+
};
|
|
246
|
+
expect(await testQuery({ n1: 314n, f2: 314, f3: 3.14 }, [{ group_id: 'group1' }])).toMatchObject([
|
|
247
|
+
{ bucket: bucketRequest(syncRules, 'mybucket["group1"]').bucket }
|
|
248
|
+
]);
|
|
249
|
+
expect(await testQuery({ n1: 314, f2: 314n, f3: 3.14 }, [{ group_id: 'group1' }])).toMatchObject([
|
|
250
|
+
{ bucket: bucketRequest(syncRules, 'mybucket["group1"]').bucket }
|
|
251
|
+
]);
|
|
252
|
+
expect(await testQuery({ n1: 314n, f2: 314, f3: 3 }, [])).toEqual([]);
|
|
321
253
|
});
|
|
322
254
|
test('save and load parameters with large numbers', async () => {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const factory = __addDisposableResource(env_5, await generateStorageFactory(), true);
|
|
329
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
255
|
+
// This ensures serialization / deserialization of "current_data" is done correctly.
|
|
256
|
+
// This specific case tested here cannot happen with postgres in practice, but we still
|
|
257
|
+
// test this to ensure correct deserialization.
|
|
258
|
+
await using factory = await generateStorageFactory();
|
|
259
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
330
260
|
bucket_definitions:
|
|
331
261
|
mybucket:
|
|
332
262
|
parameters:
|
|
333
263
|
- SELECT group_id FROM test WHERE n1 = token_parameters.n1
|
|
334
264
|
data: []
|
|
335
265
|
`, {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
await result_5;
|
|
380
|
-
}
|
|
266
|
+
storageVersion
|
|
267
|
+
}));
|
|
268
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
269
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
270
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
271
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
272
|
+
await writer.markAllSnapshotDone('1/1');
|
|
273
|
+
await writer.save({
|
|
274
|
+
sourceTable: testTable,
|
|
275
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
276
|
+
after: {
|
|
277
|
+
id: 't1',
|
|
278
|
+
group_id: 'group1',
|
|
279
|
+
n1: 1152921504606846976n // 2^60
|
|
280
|
+
},
|
|
281
|
+
afterReplicaId: test_utils.rid('t1')
|
|
282
|
+
});
|
|
283
|
+
await writer.save({
|
|
284
|
+
sourceTable: testTable,
|
|
285
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
286
|
+
after: {
|
|
287
|
+
id: 't1',
|
|
288
|
+
group_id: 'group1',
|
|
289
|
+
// Simulate a TOAST value, even though it can't happen for values like this
|
|
290
|
+
// in practice.
|
|
291
|
+
n1: undefined
|
|
292
|
+
},
|
|
293
|
+
afterReplicaId: test_utils.rid('t1')
|
|
294
|
+
});
|
|
295
|
+
await writer.commit('1/1');
|
|
296
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
297
|
+
const n1 = 1152921504606846976n;
|
|
298
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: { n1 } }), {});
|
|
299
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
300
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
301
|
+
getParameterSets: async (lookups) => {
|
|
302
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([[n1]]);
|
|
303
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
304
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ group_id: 'group1' }] }]);
|
|
305
|
+
return parameter_sets;
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
expect(buckets.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'mybucket["group1"]').bucket]);
|
|
381
309
|
});
|
|
382
310
|
test('save and load parameters with workspaceId', async () => {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const factory = __addDisposableResource(env_6, await generateStorageFactory(), true);
|
|
386
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
311
|
+
await using factory = await generateStorageFactory();
|
|
312
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
387
313
|
bucket_definitions:
|
|
388
314
|
by_workspace:
|
|
389
315
|
parameters:
|
|
@@ -391,58 +317,46 @@ bucket_definitions:
|
|
|
391
317
|
workspace."userId" = token_parameters.user_id
|
|
392
318
|
data: []
|
|
393
319
|
`, {
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
catch (e_6) {
|
|
432
|
-
env_6.error = e_6;
|
|
433
|
-
env_6.hasError = true;
|
|
434
|
-
}
|
|
435
|
-
finally {
|
|
436
|
-
const result_6 = __disposeResources(env_6);
|
|
437
|
-
if (result_6)
|
|
438
|
-
await result_6;
|
|
439
|
-
}
|
|
320
|
+
storageVersion
|
|
321
|
+
}));
|
|
322
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
323
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
324
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
325
|
+
const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', ['id'], config);
|
|
326
|
+
await writer.markAllSnapshotDone('1/1');
|
|
327
|
+
await writer.save({
|
|
328
|
+
sourceTable: workspaceTable,
|
|
329
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
330
|
+
after: {
|
|
331
|
+
id: 'workspace1',
|
|
332
|
+
userId: 'u1'
|
|
333
|
+
},
|
|
334
|
+
afterReplicaId: test_utils.rid('workspace1')
|
|
335
|
+
});
|
|
336
|
+
await writer.commit('1/1');
|
|
337
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
338
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u1' }), {});
|
|
339
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
340
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
341
|
+
async getParameterSets(lookups) {
|
|
342
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['u1']]);
|
|
343
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
344
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ workspace_id: 'workspace1' }] }]);
|
|
345
|
+
return parameter_sets;
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
expect(buckets).toEqual([
|
|
349
|
+
{
|
|
350
|
+
bucket: bucketRequest(syncRules, 'by_workspace["workspace1"]').bucket,
|
|
351
|
+
priority: 3,
|
|
352
|
+
definition: 'by_workspace',
|
|
353
|
+
inclusion_reasons: ['default']
|
|
354
|
+
}
|
|
355
|
+
]);
|
|
440
356
|
});
|
|
441
357
|
test('save and load parameters with dynamic global buckets', async () => {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const factory = __addDisposableResource(env_7, await generateStorageFactory(), true);
|
|
445
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
358
|
+
await using factory = await generateStorageFactory();
|
|
359
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
446
360
|
bucket_definitions:
|
|
447
361
|
by_public_workspace:
|
|
448
362
|
parameters:
|
|
@@ -450,84 +364,75 @@ bucket_definitions:
|
|
|
450
364
|
workspace.visibility = 'public'
|
|
451
365
|
data: []
|
|
452
366
|
`, {
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
env_7.hasError = true;
|
|
519
|
-
}
|
|
520
|
-
finally {
|
|
521
|
-
const result_7 = __disposeResources(env_7);
|
|
522
|
-
if (result_7)
|
|
523
|
-
await result_7;
|
|
524
|
-
}
|
|
367
|
+
storageVersion
|
|
368
|
+
}));
|
|
369
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
370
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
371
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
372
|
+
const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
|
|
373
|
+
await writer.markAllSnapshotDone('1/1');
|
|
374
|
+
await writer.save({
|
|
375
|
+
sourceTable: workspaceTable,
|
|
376
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
377
|
+
after: {
|
|
378
|
+
id: 'workspace1',
|
|
379
|
+
visibility: 'public'
|
|
380
|
+
},
|
|
381
|
+
afterReplicaId: test_utils.rid('workspace1')
|
|
382
|
+
});
|
|
383
|
+
await writer.save({
|
|
384
|
+
sourceTable: workspaceTable,
|
|
385
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
386
|
+
after: {
|
|
387
|
+
id: 'workspace2',
|
|
388
|
+
visibility: 'private'
|
|
389
|
+
},
|
|
390
|
+
afterReplicaId: test_utils.rid('workspace2')
|
|
391
|
+
});
|
|
392
|
+
await writer.save({
|
|
393
|
+
sourceTable: workspaceTable,
|
|
394
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
395
|
+
after: {
|
|
396
|
+
id: 'workspace3',
|
|
397
|
+
visibility: 'public'
|
|
398
|
+
},
|
|
399
|
+
afterReplicaId: test_utils.rid('workspace3')
|
|
400
|
+
});
|
|
401
|
+
await writer.commit('1/1');
|
|
402
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
403
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'unknown' }), {});
|
|
404
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
405
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
406
|
+
async getParameterSets(lookups) {
|
|
407
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([[]]);
|
|
408
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
409
|
+
expect(parameter_sets).toHaveLength(1);
|
|
410
|
+
const [{ lookup, rows }] = parameter_sets;
|
|
411
|
+
expect(lookup).toEqual(lookups[0]);
|
|
412
|
+
rows.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
413
|
+
expect(rows).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
|
|
414
|
+
return parameter_sets;
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
buckets.sort((a, b) => a.bucket.localeCompare(b.bucket));
|
|
418
|
+
expect(buckets).toEqual([
|
|
419
|
+
{
|
|
420
|
+
bucket: bucketRequest(syncRules, 'by_public_workspace["workspace1"]').bucket,
|
|
421
|
+
priority: 3,
|
|
422
|
+
definition: 'by_public_workspace',
|
|
423
|
+
inclusion_reasons: ['default']
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
bucket: bucketRequest(syncRules, 'by_public_workspace["workspace3"]').bucket,
|
|
427
|
+
priority: 3,
|
|
428
|
+
definition: 'by_public_workspace',
|
|
429
|
+
inclusion_reasons: ['default']
|
|
430
|
+
}
|
|
431
|
+
]);
|
|
525
432
|
});
|
|
526
433
|
test('multiple parameter queries', async () => {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const factory = __addDisposableResource(env_8, await generateStorageFactory(), true);
|
|
530
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
434
|
+
await using factory = await generateStorageFactory();
|
|
435
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
531
436
|
bucket_definitions:
|
|
532
437
|
by_workspace:
|
|
533
438
|
parameters:
|
|
@@ -537,138 +442,124 @@ bucket_definitions:
|
|
|
537
442
|
workspace.user_id = token_parameters.user_id
|
|
538
443
|
data: []
|
|
539
444
|
`, {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
445
|
+
storageVersion
|
|
446
|
+
}));
|
|
447
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
448
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
449
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
450
|
+
const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
|
|
451
|
+
await writer.markAllSnapshotDone('1/1');
|
|
452
|
+
await writer.save({
|
|
453
|
+
sourceTable: workspaceTable,
|
|
454
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
455
|
+
after: {
|
|
456
|
+
id: 'workspace1',
|
|
457
|
+
visibility: 'public'
|
|
458
|
+
},
|
|
459
|
+
afterReplicaId: test_utils.rid('workspace1')
|
|
460
|
+
});
|
|
461
|
+
await writer.save({
|
|
462
|
+
sourceTable: workspaceTable,
|
|
463
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
464
|
+
after: {
|
|
465
|
+
id: 'workspace2',
|
|
466
|
+
visibility: 'private'
|
|
467
|
+
},
|
|
468
|
+
afterReplicaId: test_utils.rid('workspace2')
|
|
469
|
+
});
|
|
470
|
+
await writer.save({
|
|
471
|
+
sourceTable: workspaceTable,
|
|
472
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
473
|
+
after: {
|
|
474
|
+
id: 'workspace3',
|
|
475
|
+
user_id: 'u1',
|
|
476
|
+
visibility: 'private'
|
|
477
|
+
},
|
|
478
|
+
afterReplicaId: test_utils.rid('workspace3')
|
|
479
|
+
});
|
|
480
|
+
await writer.save({
|
|
481
|
+
sourceTable: workspaceTable,
|
|
482
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
483
|
+
after: {
|
|
484
|
+
id: 'workspace4',
|
|
485
|
+
user_id: 'u2',
|
|
486
|
+
visibility: 'private'
|
|
487
|
+
},
|
|
488
|
+
afterReplicaId: test_utils.rid('workspace4')
|
|
489
|
+
});
|
|
490
|
+
await writer.commit('1/1');
|
|
491
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
492
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u1' }), {});
|
|
493
|
+
// Test intermediate values - could be moved to sync_rules.test.ts
|
|
494
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
495
|
+
// Test final values - the important part
|
|
496
|
+
const foundLookups = [];
|
|
497
|
+
const parameter_sets = [];
|
|
498
|
+
const buckets = (await querier.queryDynamicBucketDescriptions({
|
|
499
|
+
async getParameterSets(lookups) {
|
|
500
|
+
foundLookups.push(...lookups);
|
|
501
|
+
const output = await checkpoint.getParameterSets(lookups, 1000);
|
|
502
|
+
for (const { rows } of output) {
|
|
503
|
+
parameter_sets.push(...rows);
|
|
599
504
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
catch (e_8) {
|
|
614
|
-
env_8.error = e_8;
|
|
615
|
-
env_8.hasError = true;
|
|
616
|
-
}
|
|
617
|
-
finally {
|
|
618
|
-
const result_8 = __disposeResources(env_8);
|
|
619
|
-
if (result_8)
|
|
620
|
-
await result_8;
|
|
621
|
-
}
|
|
505
|
+
return output;
|
|
506
|
+
}
|
|
507
|
+
})).map((e) => e.bucket);
|
|
508
|
+
// Not testing the scope anymore - the exact format depends on storage version
|
|
509
|
+
expect(foundLookups.map((l) => l.indexKey)).toEqual([[], ['u1']]);
|
|
510
|
+
parameter_sets.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
|
|
511
|
+
expect(parameter_sets).toEqual([{ workspace_id: 'workspace1' }, { workspace_id: 'workspace3' }]);
|
|
512
|
+
buckets.sort();
|
|
513
|
+
expect(buckets).toEqual([
|
|
514
|
+
bucketRequest(syncRules, 'by_workspace["workspace1"]').bucket,
|
|
515
|
+
bucketRequest(syncRules, 'by_workspace["workspace3"]').bucket
|
|
516
|
+
]);
|
|
622
517
|
});
|
|
623
518
|
test('truncate parameters', async () => {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const factory = __addDisposableResource(env_9, await generateStorageFactory(), true);
|
|
627
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
519
|
+
await using factory = await generateStorageFactory();
|
|
520
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
628
521
|
bucket_definitions:
|
|
629
522
|
mybucket:
|
|
630
523
|
parameters:
|
|
631
524
|
- SELECT group_id FROM test WHERE id1 = token_parameters.user_id OR id2 = token_parameters.user_id
|
|
632
525
|
data: []
|
|
633
526
|
`, {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
527
|
+
storageVersion
|
|
528
|
+
}));
|
|
529
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
530
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
531
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
532
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
533
|
+
await writer.markAllSnapshotDone('1/1');
|
|
534
|
+
await writer.save({
|
|
535
|
+
sourceTable: testTable,
|
|
536
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
537
|
+
after: {
|
|
538
|
+
id: 't2',
|
|
539
|
+
id1: 'user3',
|
|
540
|
+
id2: 'user4',
|
|
541
|
+
group_id: 'group2a'
|
|
542
|
+
},
|
|
543
|
+
afterReplicaId: test_utils.rid('t2')
|
|
544
|
+
});
|
|
545
|
+
await writer.truncate([testTable]);
|
|
546
|
+
await writer.flush();
|
|
547
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
548
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
549
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
550
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
551
|
+
async getParameterSets(lookups) {
|
|
552
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['user1']]);
|
|
553
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
554
|
+
expect(parameter_sets).toEqual([]);
|
|
555
|
+
return parameter_sets;
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
expect(buckets).toEqual([]);
|
|
666
559
|
});
|
|
667
560
|
test('invalidate cached parsed sync rules', async () => {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
const bucketStorageFactory = __addDisposableResource(env_10, await generateStorageFactory(), true);
|
|
671
|
-
const syncRules = await bucketStorageFactory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
561
|
+
await using bucketStorageFactory = await generateStorageFactory();
|
|
562
|
+
const syncRules = await bucketStorageFactory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
672
563
|
bucket_definitions:
|
|
673
564
|
by_workspace:
|
|
674
565
|
parameters:
|
|
@@ -676,40 +567,28 @@ bucket_definitions:
|
|
|
676
567
|
workspace."userId" = token_parameters.user_id
|
|
677
568
|
data: []
|
|
678
569
|
`, {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
catch (e_10) {
|
|
699
|
-
env_10.error = e_10;
|
|
700
|
-
env_10.hasError = true;
|
|
701
|
-
}
|
|
702
|
-
finally {
|
|
703
|
-
const result_10 = __disposeResources(env_10);
|
|
704
|
-
if (result_10)
|
|
705
|
-
await result_10;
|
|
706
|
-
}
|
|
570
|
+
storageVersion
|
|
571
|
+
}));
|
|
572
|
+
const syncBucketStorage = bucketStorageFactory.getInstance(syncRules);
|
|
573
|
+
const parsedSchema1 = syncBucketStorage.getParsedSyncRules({
|
|
574
|
+
defaultSchema: 'public'
|
|
575
|
+
});
|
|
576
|
+
const parsedSchema2 = syncBucketStorage.getParsedSyncRules({
|
|
577
|
+
defaultSchema: 'public'
|
|
578
|
+
});
|
|
579
|
+
// These should be cached, this will be the same instance
|
|
580
|
+
expect(parsedSchema2).equals(parsedSchema1);
|
|
581
|
+
expect(parsedSchema1.getSourceTables()[0].schema).equals('public');
|
|
582
|
+
const parsedSchema3 = syncBucketStorage.getParsedSyncRules({
|
|
583
|
+
defaultSchema: 'databasename'
|
|
584
|
+
});
|
|
585
|
+
// The cache should not be used
|
|
586
|
+
expect(parsedSchema3).not.equals(parsedSchema2);
|
|
587
|
+
expect(parsedSchema3.getSourceTables()[0].schema).equals('databasename');
|
|
707
588
|
});
|
|
708
589
|
test('sync streams smoke test', async () => {
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const factory = __addDisposableResource(env_11, await generateStorageFactory(), true);
|
|
712
|
-
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
590
|
+
await using factory = await generateStorageFactory();
|
|
591
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
713
592
|
config:
|
|
714
593
|
edition: 3
|
|
715
594
|
|
|
@@ -719,39 +598,347 @@ streams:
|
|
|
719
598
|
SELECT data.* FROM test AS data, test AS param
|
|
720
599
|
WHERE data.foo = param.bar AND param.baz = auth.user_id()
|
|
721
600
|
`));
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
601
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
602
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
603
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
604
|
+
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
605
|
+
await writer.markAllSnapshotDone('1/1');
|
|
606
|
+
await writer.save({
|
|
607
|
+
sourceTable: testTable,
|
|
608
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
609
|
+
after: {
|
|
610
|
+
baz: 'baz',
|
|
611
|
+
bar: 'bar'
|
|
612
|
+
},
|
|
613
|
+
afterReplicaId: test_utils.rid('t1')
|
|
614
|
+
});
|
|
615
|
+
await writer.commit('1/1');
|
|
616
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
617
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'baz' }), {});
|
|
618
|
+
const querier = sync_rules.getBucketParameterQuerier({
|
|
619
|
+
...test_utils.querierOptions(parameters),
|
|
620
|
+
streams: {
|
|
621
|
+
stream: [
|
|
622
|
+
{
|
|
623
|
+
priorityOverride: null,
|
|
624
|
+
parameters: null,
|
|
625
|
+
opaque_id: 123
|
|
626
|
+
}
|
|
627
|
+
]
|
|
628
|
+
}
|
|
629
|
+
}).querier;
|
|
630
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
631
|
+
async getParameterSets(lookups) {
|
|
632
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['baz']]);
|
|
633
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
634
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ '0': 'bar' }] }]);
|
|
635
|
+
return parameter_sets;
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
expect(buckets).toHaveLength(1);
|
|
639
|
+
expect(buckets).toMatchObject([
|
|
640
|
+
{
|
|
641
|
+
bucket: expect.stringMatching(/stream.*\["bar"\]$/),
|
|
642
|
+
definition: 'stream',
|
|
643
|
+
inclusion_reasons: [{ subscription: 123 }],
|
|
644
|
+
priority: 3
|
|
645
|
+
}
|
|
646
|
+
]);
|
|
647
|
+
});
|
|
648
|
+
test('respects parameter limit', async () => {
|
|
649
|
+
await using factory = await generateStorageFactory();
|
|
650
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
651
|
+
config:
|
|
652
|
+
edition: 3
|
|
653
|
+
|
|
654
|
+
streams:
|
|
655
|
+
a:
|
|
656
|
+
auto_subscribe: true
|
|
657
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM b)
|
|
658
|
+
`, {
|
|
659
|
+
storageVersion
|
|
660
|
+
}));
|
|
661
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
662
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
663
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
664
|
+
const testTable = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
|
|
665
|
+
await writer.markAllSnapshotDone('1/1');
|
|
666
|
+
for (let i = 0; i < 10; i++) {
|
|
726
667
|
await writer.save({
|
|
727
668
|
sourceTable: testTable,
|
|
728
669
|
tag: storage.SaveOperationTag.INSERT,
|
|
729
670
|
after: {
|
|
730
|
-
|
|
731
|
-
bar: 'bar'
|
|
671
|
+
id: `t${i}`
|
|
732
672
|
},
|
|
733
|
-
afterReplicaId: test_utils.rid(
|
|
673
|
+
afterReplicaId: test_utils.rid(`t${i}`)
|
|
734
674
|
});
|
|
735
|
-
await writer.commit('1/1');
|
|
736
|
-
const checkpoint = await bucketStorage.getCheckpoint();
|
|
737
|
-
const parameters = await checkpoint.getParameterSets([
|
|
738
|
-
ScopedParameterLookup.direct(parameterLookupScope('lookup', '0'), ['baz'])
|
|
739
|
-
]);
|
|
740
|
-
expect(parameters).toEqual([
|
|
741
|
-
{
|
|
742
|
-
'0': 'bar'
|
|
743
|
-
}
|
|
744
|
-
]);
|
|
745
675
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
676
|
+
await writer.commit('1/1');
|
|
677
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
678
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'u' }), {});
|
|
679
|
+
const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
|
|
680
|
+
await expect(querier.queryDynamicBucketDescriptions({
|
|
681
|
+
async getParameterSets(lookups) {
|
|
682
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 5);
|
|
683
|
+
return parameter_sets;
|
|
684
|
+
}
|
|
685
|
+
})).rejects.toThrow('Too many parameter results (limit was 5)');
|
|
686
|
+
});
|
|
687
|
+
test('sync streams store multiple parameter outputs for a single source row and lookup', async () => {
|
|
688
|
+
await using factory = await generateStorageFactory();
|
|
689
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
690
|
+
config:
|
|
691
|
+
edition: 3
|
|
692
|
+
streams:
|
|
693
|
+
chat:
|
|
694
|
+
auto_subscribe: true
|
|
695
|
+
query: |
|
|
696
|
+
SELECT a.*
|
|
697
|
+
FROM a, b, json_each(b.x) x, json_each(b.y) y
|
|
698
|
+
WHERE a.x = x.value AND y.value = auth.user_id()
|
|
699
|
+
`));
|
|
700
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
701
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
702
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
703
|
+
const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
|
|
704
|
+
const firstState = {
|
|
705
|
+
id: 'id0',
|
|
706
|
+
x: JSON.stringify(['x1', 'x2']),
|
|
707
|
+
y: JSON.stringify(['y1', 'y2'])
|
|
708
|
+
};
|
|
709
|
+
const secondState = {
|
|
710
|
+
id: 'id0',
|
|
711
|
+
x: JSON.stringify(['x2']),
|
|
712
|
+
y: JSON.stringify(['y1', 'y2'])
|
|
713
|
+
};
|
|
714
|
+
const thirdState = {
|
|
715
|
+
id: 'id0',
|
|
716
|
+
x: JSON.stringify(['x2']),
|
|
717
|
+
y: JSON.stringify(['y2'])
|
|
718
|
+
};
|
|
719
|
+
const replicaId = test_utils.rid('id0');
|
|
720
|
+
await writer.markAllSnapshotDone('1/1');
|
|
721
|
+
await writer.save({
|
|
722
|
+
sourceTable: tableB,
|
|
723
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
724
|
+
after: firstState,
|
|
725
|
+
afterReplicaId: replicaId
|
|
726
|
+
});
|
|
727
|
+
await writer.commit('1/1');
|
|
728
|
+
let checkpoint = await bucketStorage.getCheckpoint();
|
|
729
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'y1' }), {});
|
|
730
|
+
const querier = sync_rules.getBucketParameterQuerier({
|
|
731
|
+
...test_utils.querierOptions(parameters)
|
|
732
|
+
}).querier;
|
|
733
|
+
let buckets = await querier.queryDynamicBucketDescriptions({
|
|
734
|
+
async getParameterSets(lookups) {
|
|
735
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
|
|
736
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
737
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ '0': 'x1' }, { '0': 'x2' }] }]);
|
|
738
|
+
return parameter_sets;
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
expect(buckets).toMatchObject([
|
|
742
|
+
{
|
|
743
|
+
bucket: expect.stringMatching(/chat.*\["x1"\]$/),
|
|
744
|
+
definition: 'chat',
|
|
745
|
+
inclusion_reasons: ['default'],
|
|
746
|
+
priority: 3
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
bucket: expect.stringMatching(/chat.*\["x2"\]$/),
|
|
750
|
+
definition: 'chat',
|
|
751
|
+
inclusion_reasons: ['default'],
|
|
752
|
+
priority: 3
|
|
753
|
+
}
|
|
754
|
+
]);
|
|
755
|
+
// Make the x2 bucket inaccessible
|
|
756
|
+
await writer.save({
|
|
757
|
+
sourceTable: tableB,
|
|
758
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
759
|
+
before: firstState,
|
|
760
|
+
after: secondState,
|
|
761
|
+
beforeReplicaId: replicaId,
|
|
762
|
+
afterReplicaId: replicaId
|
|
763
|
+
});
|
|
764
|
+
await writer.commit('1/2');
|
|
765
|
+
checkpoint = await bucketStorage.getCheckpoint();
|
|
766
|
+
buckets = await querier.queryDynamicBucketDescriptions({
|
|
767
|
+
async getParameterSets(lookups) {
|
|
768
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
|
|
769
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
770
|
+
expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ '0': 'x2' }] }]);
|
|
771
|
+
return parameter_sets;
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
expect(buckets.map((bkt) => bkt.bucket)).toStrictEqual([expect.stringContaining('x2')]);
|
|
775
|
+
// Second update, remove user from inputs
|
|
776
|
+
await writer.save({
|
|
777
|
+
sourceTable: tableB,
|
|
778
|
+
tag: storage.SaveOperationTag.UPDATE,
|
|
779
|
+
before: secondState,
|
|
780
|
+
after: thirdState,
|
|
781
|
+
beforeReplicaId: replicaId,
|
|
782
|
+
afterReplicaId: replicaId
|
|
783
|
+
});
|
|
784
|
+
await writer.commit('1/3');
|
|
785
|
+
checkpoint = await bucketStorage.getCheckpoint();
|
|
786
|
+
buckets = await querier.queryDynamicBucketDescriptions({
|
|
787
|
+
async getParameterSets(lookups) {
|
|
788
|
+
expect(lookups.map((l) => l.indexKey)).toEqual([['y1']]);
|
|
789
|
+
const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
|
|
790
|
+
expect(parameter_sets).toHaveLength(0);
|
|
791
|
+
return parameter_sets;
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
expect(buckets).toHaveLength(0);
|
|
795
|
+
});
|
|
796
|
+
test('can request multiple lookups at once', async () => {
|
|
797
|
+
await using factory = await generateStorageFactory();
|
|
798
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
799
|
+
config:
|
|
800
|
+
edition: 3
|
|
801
|
+
streams:
|
|
802
|
+
a:
|
|
803
|
+
auto_subscribe: true
|
|
804
|
+
query: SELECT * FROM a WHERE p IN (SELECT id FROM param_a WHERE u = auth.user_id())
|
|
805
|
+
b:
|
|
806
|
+
auto_subscribe: true
|
|
807
|
+
query: SELECT * FROM b WHERE p IN (SELECT id FROM param_b WHERE u = auth.user_id())
|
|
808
|
+
`));
|
|
809
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
810
|
+
const parsedSyncRules = syncRules.parsed(test_utils.PARSE_OPTIONS);
|
|
811
|
+
const hydrationState = parsedSyncRules.hydrationState;
|
|
812
|
+
const syncConfig = parsedSyncRules.syncConfigWithErrors.config;
|
|
813
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
814
|
+
const paramATable = await test_utils.resolveTestTable(writer, 'param_a', ['id'], config, 1);
|
|
815
|
+
const paramBTable = await test_utils.resolveTestTable(writer, 'param_b', ['id'], config, 2);
|
|
816
|
+
const replicaId = test_utils.rid('id');
|
|
817
|
+
// Insert the same row into param_a and param_b
|
|
818
|
+
await writer.markAllSnapshotDone('1/1');
|
|
819
|
+
await writer.save({
|
|
820
|
+
sourceTable: paramATable,
|
|
821
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
822
|
+
after: {
|
|
823
|
+
id: 'id',
|
|
824
|
+
u: 'user'
|
|
825
|
+
},
|
|
826
|
+
afterReplicaId: replicaId
|
|
827
|
+
});
|
|
828
|
+
await writer.save({
|
|
829
|
+
sourceTable: paramBTable,
|
|
830
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
831
|
+
after: {
|
|
832
|
+
id: 'id',
|
|
833
|
+
u: 'user'
|
|
834
|
+
},
|
|
835
|
+
afterReplicaId: replicaId
|
|
836
|
+
});
|
|
837
|
+
await writer.commit('1/1');
|
|
838
|
+
function findParameterOnTable(name) {
|
|
839
|
+
for (const source of syncConfig.bucketParameterLookupSources) {
|
|
840
|
+
for (const param of source.getSourceTables()) {
|
|
841
|
+
if (param.name == name)
|
|
842
|
+
return source;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
throw new Error(`Expected parameter index on ${name}`);
|
|
846
|
+
}
|
|
847
|
+
// Run two lookups on separate parameter indexes.
|
|
848
|
+
const lookupA = ScopedParameterLookup.normalized(hydrationState.getParameterIndexLookupScope(findParameterOnTable('param_a')), UnscopedParameterLookup.normalized(['user']));
|
|
849
|
+
const lookupB = ScopedParameterLookup.normalized(hydrationState.getParameterIndexLookupScope(findParameterOnTable('param_b')), UnscopedParameterLookup.normalized(['user']));
|
|
850
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
851
|
+
const parameterSets = await checkpoint.getParameterSets([lookupA, lookupB], 1000);
|
|
852
|
+
const expectedRow = { '0': 'id' };
|
|
853
|
+
let foundLookupA = false, foundLookupB = false;
|
|
854
|
+
// We should get the same row on both, with information on which lookup contributed which row.
|
|
855
|
+
for (const { lookup, rows } of parameterSets) {
|
|
856
|
+
if (lookup === lookupA) {
|
|
857
|
+
foundLookupA = true;
|
|
858
|
+
expect(rows).toStrictEqual([expectedRow]);
|
|
859
|
+
}
|
|
860
|
+
else if (lookup === lookupB) {
|
|
861
|
+
foundLookupB = true;
|
|
862
|
+
expect(rows).toStrictEqual([expectedRow]);
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
throw new Error('unexpected lookup in results');
|
|
866
|
+
}
|
|
754
867
|
}
|
|
868
|
+
expect(foundLookupA).toBeTruthy();
|
|
869
|
+
expect(foundLookupB).toBeTruthy();
|
|
870
|
+
});
|
|
871
|
+
test('sync streams preserve duplicate downstream lookups with different provenance', async () => {
|
|
872
|
+
await using factory = await generateStorageFactory();
|
|
873
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(`
|
|
874
|
+
config:
|
|
875
|
+
edition: 3
|
|
876
|
+
streams:
|
|
877
|
+
stream:
|
|
878
|
+
auto_subscribe: true
|
|
879
|
+
query: |
|
|
880
|
+
SELECT a.*
|
|
881
|
+
FROM a, b, c
|
|
882
|
+
WHERE a.x = b.x
|
|
883
|
+
AND a.z = c.z
|
|
884
|
+
AND b.y = c.y
|
|
885
|
+
AND c.u = auth.user_id()
|
|
886
|
+
`, {
|
|
887
|
+
storageVersion
|
|
888
|
+
}));
|
|
889
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
890
|
+
const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
|
|
891
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
892
|
+
const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config, 1);
|
|
893
|
+
const tableC = await test_utils.resolveTestTable(writer, 'c', ['id'], config, 2);
|
|
894
|
+
await writer.markAllSnapshotDone('1/1');
|
|
895
|
+
await writer.save({
|
|
896
|
+
sourceTable: tableB,
|
|
897
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
898
|
+
after: {
|
|
899
|
+
id: 'b1',
|
|
900
|
+
y: 'shared-y',
|
|
901
|
+
x: 'x-from-shared-y'
|
|
902
|
+
},
|
|
903
|
+
afterReplicaId: test_utils.rid('b1')
|
|
904
|
+
});
|
|
905
|
+
await writer.save({
|
|
906
|
+
sourceTable: tableC,
|
|
907
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
908
|
+
after: {
|
|
909
|
+
id: 'c1',
|
|
910
|
+
u: 'user1',
|
|
911
|
+
y: 'shared-y',
|
|
912
|
+
z: 'z1'
|
|
913
|
+
},
|
|
914
|
+
afterReplicaId: test_utils.rid('c1')
|
|
915
|
+
});
|
|
916
|
+
await writer.save({
|
|
917
|
+
sourceTable: tableC,
|
|
918
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
919
|
+
after: {
|
|
920
|
+
id: 'c2',
|
|
921
|
+
u: 'user1',
|
|
922
|
+
y: 'shared-y',
|
|
923
|
+
z: 'z2'
|
|
924
|
+
},
|
|
925
|
+
afterReplicaId: test_utils.rid('c2')
|
|
926
|
+
});
|
|
927
|
+
await writer.commit('1/1');
|
|
928
|
+
const checkpoint = await bucketStorage.getCheckpoint();
|
|
929
|
+
const parameters = new RequestParameters(new JwtPayload({ sub: 'user1' }), {});
|
|
930
|
+
const querier = sync_rules.getBucketParameterQuerier({
|
|
931
|
+
...test_utils.querierOptions(parameters)
|
|
932
|
+
}).querier;
|
|
933
|
+
const buckets = await querier.queryDynamicBucketDescriptions({
|
|
934
|
+
async getParameterSets(lookups) {
|
|
935
|
+
return checkpoint.getParameterSets(lookups, 1000);
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
expect(buckets.map((bucket) => bucket.bucket).sort()).toStrictEqual([
|
|
939
|
+
expect.stringMatching(/stream.*\["x-from-shared-y","z1"\]$/),
|
|
940
|
+
expect.stringMatching(/stream.*\["x-from-shared-y","z2"\]$/)
|
|
941
|
+
]);
|
|
755
942
|
});
|
|
756
943
|
}
|
|
757
944
|
//# sourceMappingURL=register-data-storage-parameter-tests.js.map
|