@powersync/service-core 0.2.2 → 0.4.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 +31 -0
- package/dist/api/diagnostics.js +2 -2
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/schema.js.map +1 -1
- package/dist/auth/CachedKeyCollector.js.map +1 -1
- package/dist/auth/JwtPayload.d.ts +6 -2
- package/dist/auth/KeySpec.js.map +1 -1
- package/dist/auth/KeyStore.js +3 -9
- package/dist/auth/KeyStore.js.map +1 -1
- package/dist/auth/LeakyBucket.js.map +1 -1
- package/dist/auth/RemoteJWKSCollector.js.map +1 -1
- package/dist/auth/SupabaseKeyCollector.js.map +1 -1
- package/dist/db/mongo.js.map +1 -1
- package/dist/entry/cli-entry.js +2 -2
- package/dist/entry/cli-entry.js.map +1 -1
- package/dist/entry/commands/config-command.js.map +1 -1
- package/dist/entry/commands/migrate-action.js +12 -4
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/entry/commands/start-action.js.map +1 -1
- package/dist/entry/commands/teardown-action.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/locks/LockManager.d.ts +10 -0
- package/dist/locks/LockManager.js +7 -0
- package/dist/locks/LockManager.js.map +1 -0
- package/dist/locks/MongoLocks.d.ts +36 -0
- package/dist/locks/MongoLocks.js +81 -0
- package/dist/locks/MongoLocks.js.map +1 -0
- package/dist/locks/locks-index.d.ts +2 -0
- package/dist/locks/locks-index.js +3 -0
- package/dist/locks/locks-index.js.map +1 -0
- package/dist/metrics/Metrics.js +6 -6
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
- package/dist/migrations/definitions.d.ts +18 -0
- package/dist/migrations/definitions.js +6 -0
- package/dist/migrations/definitions.js.map +1 -0
- package/dist/migrations/executor.d.ts +16 -0
- package/dist/migrations/executor.js +64 -0
- package/dist/migrations/executor.js.map +1 -0
- package/dist/migrations/migrations-index.d.ts +3 -0
- package/dist/migrations/migrations-index.js +4 -0
- package/dist/migrations/migrations-index.js.map +1 -0
- package/dist/migrations/migrations.d.ts +1 -1
- package/dist/migrations/migrations.js +12 -8
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/migrations/store/migration-store.d.ts +11 -0
- package/dist/migrations/store/migration-store.js +46 -0
- package/dist/migrations/store/migration-store.js.map +1 -0
- package/dist/replication/ErrorRateLimiter.js.map +1 -1
- package/dist/replication/PgRelation.js.map +1 -1
- package/dist/replication/WalConnection.js.map +1 -1
- package/dist/replication/WalStream.d.ts +0 -1
- package/dist/replication/WalStream.js +21 -25
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/WalStreamManager.js +12 -13
- package/dist/replication/WalStreamManager.js.map +1 -1
- package/dist/replication/WalStreamRunner.js +8 -8
- package/dist/replication/WalStreamRunner.js.map +1 -1
- package/dist/replication/util.js.map +1 -1
- package/dist/routes/auth.d.ts +8 -10
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/endpoints/admin.d.ts +1011 -0
- package/dist/routes/{admin.js → endpoints/admin.js} +33 -18
- package/dist/routes/endpoints/admin.js.map +1 -0
- package/dist/routes/endpoints/checkpointing.d.ts +76 -0
- package/dist/routes/endpoints/checkpointing.js +36 -0
- package/dist/routes/endpoints/checkpointing.js.map +1 -0
- package/dist/routes/endpoints/dev.d.ts +312 -0
- package/dist/routes/{dev.js → endpoints/dev.js} +25 -16
- package/dist/routes/endpoints/dev.js.map +1 -0
- package/dist/routes/endpoints/route-endpoints-index.d.ts +6 -0
- package/dist/routes/endpoints/route-endpoints-index.js +7 -0
- package/dist/routes/endpoints/route-endpoints-index.js.map +1 -0
- package/dist/routes/endpoints/socket-route.d.ts +2 -0
- package/dist/routes/{socket-route.js → endpoints/socket-route.js} +12 -12
- package/dist/routes/endpoints/socket-route.js.map +1 -0
- package/dist/routes/endpoints/sync-rules.d.ts +174 -0
- package/dist/routes/{sync-rules.js → endpoints/sync-rules.js} +44 -24
- package/dist/routes/endpoints/sync-rules.js.map +1 -0
- package/dist/routes/endpoints/sync-stream.d.ts +132 -0
- package/dist/routes/{sync-stream.js → endpoints/sync-stream.js} +28 -19
- package/dist/routes/endpoints/sync-stream.js.map +1 -0
- package/dist/routes/hooks.d.ts +10 -0
- package/dist/routes/hooks.js +31 -0
- package/dist/routes/hooks.js.map +1 -0
- package/dist/routes/route-register.d.ts +10 -0
- package/dist/routes/route-register.js +87 -0
- package/dist/routes/route-register.js.map +1 -0
- package/dist/routes/router.d.ts +16 -4
- package/dist/routes/router.js +6 -1
- package/dist/routes/router.js.map +1 -1
- package/dist/routes/routes-index.d.ts +5 -3
- package/dist/routes/routes-index.js +5 -3
- package/dist/routes/routes-index.js.map +1 -1
- package/dist/runner/teardown.js +9 -9
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +3 -0
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/ChecksumCache.js.map +1 -1
- package/dist/storage/MongoBucketStorage.js +5 -5
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/mongo/MongoBucketBatch.js +23 -18
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
- package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoSyncRulesLock.js +3 -3
- package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/mongo/OperationBatch.js.map +1 -1
- package/dist/storage/mongo/PersistedBatch.js +2 -2
- package/dist/storage/mongo/PersistedBatch.js.map +1 -1
- package/dist/storage/mongo/db.d.ts +2 -2
- package/dist/storage/mongo/db.js.map +1 -1
- package/dist/storage/mongo/util.js.map +1 -1
- package/dist/sync/BroadcastIterable.js.map +1 -1
- package/dist/sync/LastValueSink.js.map +1 -1
- package/dist/sync/merge.js.map +1 -1
- package/dist/sync/safeRace.js.map +1 -1
- package/dist/sync/sync.d.ts +2 -2
- package/dist/sync/sync.js +5 -5
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.js.map +1 -1
- package/dist/system/CorePowerSyncSystem.d.ts +12 -7
- package/dist/system/CorePowerSyncSystem.js +26 -2
- package/dist/system/CorePowerSyncSystem.js.map +1 -1
- package/dist/system/system-index.d.ts +1 -0
- package/dist/system/system-index.js +2 -0
- package/dist/system/system-index.js.map +1 -0
- package/dist/util/Mutex.js.map +1 -1
- package/dist/util/PgManager.js.map +1 -1
- package/dist/util/alerting.d.ts +0 -2
- package/dist/util/alerting.js +0 -6
- package/dist/util/alerting.js.map +1 -1
- package/dist/util/config/collectors/config-collector.js +3 -3
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/filesystem-config-collector.js +7 -5
- package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
- package/dist/util/config/compound-config-collector.js +4 -4
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
- package/dist/util/config.js.map +1 -1
- package/dist/util/env.d.ts +1 -2
- package/dist/util/env.js +3 -2
- package/dist/util/env.js.map +1 -1
- package/dist/util/memory-tracking.js +2 -2
- package/dist/util/memory-tracking.js.map +1 -1
- package/dist/util/migration_lib.js.map +1 -1
- package/dist/util/pgwire_utils.js +2 -2
- package/dist/util/pgwire_utils.js.map +1 -1
- package/dist/util/populate_test_data.js.map +1 -1
- package/dist/util/secs.js.map +1 -1
- package/dist/util/utils.js +4 -4
- package/dist/util/utils.js.map +1 -1
- package/package.json +13 -10
- package/src/api/diagnostics.ts +5 -5
- package/src/api/schema.ts +1 -1
- package/src/auth/JwtPayload.ts +6 -2
- package/src/auth/KeyStore.ts +3 -9
- package/src/entry/cli-entry.ts +3 -4
- package/src/entry/commands/config-command.ts +1 -1
- package/src/entry/commands/migrate-action.ts +14 -6
- package/src/entry/commands/start-action.ts +1 -1
- package/src/entry/commands/teardown-action.ts +1 -1
- package/src/index.ts +5 -2
- package/src/locks/LockManager.ts +16 -0
- package/src/locks/MongoLocks.ts +142 -0
- package/src/locks/locks-index.ts +2 -0
- package/src/metrics/Metrics.ts +8 -8
- package/src/migrations/db/migrations/1684951997326-init.ts +3 -3
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +3 -3
- package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +2 -2
- package/src/migrations/definitions.ts +21 -0
- package/src/migrations/executor.ts +87 -0
- package/src/migrations/migrations-index.ts +3 -0
- package/src/migrations/migrations.ts +15 -11
- package/src/migrations/store/migration-store.ts +63 -0
- package/src/replication/WalConnection.ts +2 -2
- package/src/replication/WalStream.ts +24 -29
- package/src/replication/WalStreamManager.ts +14 -15
- package/src/replication/WalStreamRunner.ts +10 -10
- package/src/replication/util.ts +1 -1
- package/src/routes/auth.ts +22 -16
- package/src/routes/endpoints/admin.ts +237 -0
- package/src/routes/endpoints/checkpointing.ts +41 -0
- package/src/routes/endpoints/dev.ts +199 -0
- package/src/routes/endpoints/route-endpoints-index.ts +6 -0
- package/src/routes/{socket-route.ts → endpoints/socket-route.ts} +13 -16
- package/src/routes/endpoints/sync-rules.ts +227 -0
- package/src/routes/endpoints/sync-stream.ts +98 -0
- package/src/routes/hooks.ts +45 -0
- package/src/routes/route-register.ts +104 -0
- package/src/routes/router.ts +34 -6
- package/src/routes/routes-index.ts +5 -4
- package/src/runner/teardown.ts +9 -9
- package/src/storage/BucketStorage.ts +7 -2
- package/src/storage/ChecksumCache.ts +2 -2
- package/src/storage/MongoBucketStorage.ts +8 -8
- package/src/storage/SourceTable.ts +2 -2
- package/src/storage/mongo/MongoBucketBatch.ts +29 -22
- package/src/storage/mongo/MongoSyncBucketStorage.ts +3 -3
- package/src/storage/mongo/MongoSyncRulesLock.ts +3 -3
- package/src/storage/mongo/OperationBatch.ts +1 -1
- package/src/storage/mongo/PersistedBatch.ts +3 -3
- package/src/storage/mongo/db.ts +3 -4
- package/src/sync/sync.ts +11 -11
- package/src/sync/util.ts +2 -2
- package/src/system/CorePowerSyncSystem.ts +31 -10
- package/src/system/system-index.ts +1 -0
- package/src/util/alerting.ts +0 -8
- package/src/util/config/collectors/config-collector.ts +5 -3
- package/src/util/config/collectors/impl/filesystem-config-collector.ts +8 -6
- package/src/util/config/compound-config-collector.ts +4 -4
- package/src/util/env.ts +4 -2
- package/src/util/memory-tracking.ts +2 -2
- package/src/util/pgwire_utils.ts +3 -3
- package/src/util/utils.ts +5 -5
- package/test/src/auth.test.ts +4 -2
- package/test/src/data_storage.test.ts +181 -19
- package/test/src/env.ts +6 -6
- package/test/src/setup.ts +7 -0
- package/test/src/slow_tests.test.ts +45 -6
- package/test/src/sync.test.ts +6 -5
- package/test/tsconfig.json +1 -1
- package/tsconfig.json +5 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +1 -3
- package/dist/migrations/db/store.d.ts +0 -3
- package/dist/migrations/db/store.js +0 -10
- package/dist/migrations/db/store.js.map +0 -1
- package/dist/routes/admin.d.ts +0 -7
- package/dist/routes/admin.js.map +0 -1
- package/dist/routes/checkpointing.d.ts +0 -3
- package/dist/routes/checkpointing.js +0 -30
- package/dist/routes/checkpointing.js.map +0 -1
- package/dist/routes/dev.d.ts +0 -6
- package/dist/routes/dev.js.map +0 -1
- package/dist/routes/route-generators.d.ts +0 -15
- package/dist/routes/route-generators.js +0 -32
- package/dist/routes/route-generators.js.map +0 -1
- package/dist/routes/socket-route.d.ts +0 -2
- package/dist/routes/socket-route.js.map +0 -1
- package/dist/routes/sync-rules.d.ts +0 -6
- package/dist/routes/sync-rules.js.map +0 -1
- package/dist/routes/sync-stream.d.ts +0 -5
- package/dist/routes/sync-stream.js.map +0 -1
- package/src/migrations/db/store.ts +0 -11
- package/src/routes/admin.ts +0 -229
- package/src/routes/checkpointing.ts +0 -38
- package/src/routes/dev.ts +0 -194
- package/src/routes/route-generators.ts +0 -39
- package/src/routes/sync-rules.ts +0 -210
- package/src/routes/sync-stream.ts +0 -95
- package/test/src/sql_functions.test.ts +0 -254
- package/test/src/sql_operators.test.ts +0 -132
- package/test/src/sync_rules.test.ts +0 -1053
|
@@ -1,1053 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_SCHEMA,
|
|
3
|
-
DEFAULT_TAG,
|
|
4
|
-
DartSchemaGenerator,
|
|
5
|
-
ExpressionType,
|
|
6
|
-
JsSchemaGenerator,
|
|
7
|
-
SourceTableInterface,
|
|
8
|
-
SqlDataQuery,
|
|
9
|
-
SqlParameterQuery,
|
|
10
|
-
SqlSyncRules,
|
|
11
|
-
StaticSchema,
|
|
12
|
-
normalizeTokenParameters
|
|
13
|
-
} from '@powersync/service-sync-rules';
|
|
14
|
-
import { describe, expect, test } from 'vitest';
|
|
15
|
-
import { SourceTable } from '../../src/storage/SourceTable.js';
|
|
16
|
-
|
|
17
|
-
class TestSourceTable implements SourceTableInterface {
|
|
18
|
-
readonly connectionTag = DEFAULT_TAG;
|
|
19
|
-
readonly schema = DEFAULT_SCHEMA;
|
|
20
|
-
|
|
21
|
-
constructor(public readonly table: string) {}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const ASSETS = new TestSourceTable('assets');
|
|
25
|
-
const USERS = new TestSourceTable('users');
|
|
26
|
-
|
|
27
|
-
describe('sync rules', () => {
|
|
28
|
-
test('parse empty sync rules', () => {
|
|
29
|
-
const rules = SqlSyncRules.fromYaml('bucket_definitions: {}');
|
|
30
|
-
expect(rules.bucket_descriptors).toEqual([]);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('parse global sync rules', () => {
|
|
34
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
35
|
-
bucket_definitions:
|
|
36
|
-
mybucket:
|
|
37
|
-
data:
|
|
38
|
-
- SELECT id, description FROM assets
|
|
39
|
-
`);
|
|
40
|
-
const bucket = rules.bucket_descriptors[0];
|
|
41
|
-
expect(bucket.name).toEqual('mybucket');
|
|
42
|
-
expect(bucket.bucket_parameters).toEqual([]);
|
|
43
|
-
const dataQuery = bucket.data_queries[0];
|
|
44
|
-
expect(dataQuery.bucket_parameters).toEqual([]);
|
|
45
|
-
expect(dataQuery.columnOutputNames()).toEqual(['id', 'description']);
|
|
46
|
-
expect(rules.evaluateRow({ sourceTable: ASSETS, record: { id: 'asset1', description: 'test' } })).toEqual([
|
|
47
|
-
{
|
|
48
|
-
ruleId: '1',
|
|
49
|
-
table: 'assets',
|
|
50
|
-
id: 'asset1',
|
|
51
|
-
data: {
|
|
52
|
-
id: 'asset1',
|
|
53
|
-
description: 'test'
|
|
54
|
-
},
|
|
55
|
-
bucket: 'mybucket[]'
|
|
56
|
-
}
|
|
57
|
-
]);
|
|
58
|
-
expect(rules.getStaticBucketIds({ token_parameters: {}, user_parameters: {} })).toEqual(['mybucket[]']);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('parse global sync rules with filter', () => {
|
|
62
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
63
|
-
bucket_definitions:
|
|
64
|
-
mybucket:
|
|
65
|
-
parameters: SELECT WHERE token_parameters.is_admin
|
|
66
|
-
data: []
|
|
67
|
-
`);
|
|
68
|
-
const bucket = rules.bucket_descriptors[0];
|
|
69
|
-
expect(bucket.bucket_parameters).toEqual([]);
|
|
70
|
-
const param_query = bucket.global_parameter_queries[0];
|
|
71
|
-
|
|
72
|
-
expect(param_query.filter!.filter({ token_parameters: { is_admin: 1n } })).toEqual([{}]);
|
|
73
|
-
expect(param_query.filter!.filter({ token_parameters: { is_admin: 0n } })).toEqual([]);
|
|
74
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ is_admin: true }))).toEqual(['mybucket[]']);
|
|
75
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ is_admin: false }))).toEqual([]);
|
|
76
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({}))).toEqual([]);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test('parse global sync rules with table filter', () => {
|
|
80
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
81
|
-
bucket_definitions:
|
|
82
|
-
mybucket:
|
|
83
|
-
parameters: SELECT FROM users WHERE users.id = token_parameters.user_id AND users.is_admin
|
|
84
|
-
data: []
|
|
85
|
-
`);
|
|
86
|
-
const bucket = rules.bucket_descriptors[0];
|
|
87
|
-
expect(bucket.bucket_parameters).toEqual([]);
|
|
88
|
-
const param_query = bucket.parameter_queries[0];
|
|
89
|
-
expect(param_query.bucket_parameters).toEqual([]);
|
|
90
|
-
expect(rules.evaluateParameterRow(USERS, { id: 'user1', is_admin: 1 })).toEqual([
|
|
91
|
-
{
|
|
92
|
-
bucket_parameters: [{}],
|
|
93
|
-
lookup: ['mybucket', '1', 'user1']
|
|
94
|
-
}
|
|
95
|
-
]);
|
|
96
|
-
expect(rules.evaluateParameterRow(USERS, { id: 'user1', is_admin: 0 })).toEqual([]);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('parse bucket with parameters', () => {
|
|
100
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
101
|
-
bucket_definitions:
|
|
102
|
-
mybucket:
|
|
103
|
-
parameters: SELECT token_parameters.user_id, user_parameters.device_id
|
|
104
|
-
data:
|
|
105
|
-
- SELECT id, description FROM assets WHERE assets.user_id = bucket.user_id AND assets.device_id = bucket.device_id AND NOT assets.archived
|
|
106
|
-
`);
|
|
107
|
-
const bucket = rules.bucket_descriptors[0];
|
|
108
|
-
expect(bucket.bucket_parameters).toEqual(['user_id', 'device_id']);
|
|
109
|
-
const param_query = bucket.global_parameter_queries[0];
|
|
110
|
-
expect(param_query.bucket_parameters).toEqual(['user_id', 'device_id']);
|
|
111
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ user_id: 'user1' }, { device_id: 'device1' }))).toEqual([
|
|
112
|
-
'mybucket["user1","device1"]'
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
const data_query = bucket.data_queries[0];
|
|
116
|
-
expect(data_query.bucket_parameters).toEqual(['user_id', 'device_id']);
|
|
117
|
-
expect(
|
|
118
|
-
rules.evaluateRow({
|
|
119
|
-
sourceTable: ASSETS,
|
|
120
|
-
record: { id: 'asset1', description: 'test', user_id: 'user1', device_id: 'device1' }
|
|
121
|
-
})
|
|
122
|
-
).toEqual([
|
|
123
|
-
{
|
|
124
|
-
ruleId: '1',
|
|
125
|
-
bucket: 'mybucket["user1","device1"]',
|
|
126
|
-
id: 'asset1',
|
|
127
|
-
data: {
|
|
128
|
-
id: 'asset1',
|
|
129
|
-
description: 'test'
|
|
130
|
-
},
|
|
131
|
-
table: 'assets'
|
|
132
|
-
}
|
|
133
|
-
]);
|
|
134
|
-
expect(
|
|
135
|
-
rules.evaluateRow({
|
|
136
|
-
sourceTable: ASSETS,
|
|
137
|
-
record: { id: 'asset1', description: 'test', user_id: 'user1', archived: 1, device_id: 'device1' }
|
|
138
|
-
})
|
|
139
|
-
).toEqual([]);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('parse bucket with parameters and OR condition', () => {
|
|
143
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
144
|
-
bucket_definitions:
|
|
145
|
-
mybucket:
|
|
146
|
-
parameters: SELECT token_parameters.user_id
|
|
147
|
-
data:
|
|
148
|
-
- SELECT id, description FROM assets WHERE assets.user_id = bucket.user_id OR assets.owner_id = bucket.user_id
|
|
149
|
-
`);
|
|
150
|
-
const bucket = rules.bucket_descriptors[0];
|
|
151
|
-
expect(bucket.bucket_parameters).toEqual(['user_id']);
|
|
152
|
-
const param_query = bucket.global_parameter_queries[0];
|
|
153
|
-
expect(param_query.bucket_parameters).toEqual(['user_id']);
|
|
154
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ user_id: 'user1' }))).toEqual(['mybucket["user1"]']);
|
|
155
|
-
|
|
156
|
-
const data_query = bucket.data_queries[0];
|
|
157
|
-
expect(data_query.bucket_parameters).toEqual(['user_id']);
|
|
158
|
-
expect(
|
|
159
|
-
rules.evaluateRow({
|
|
160
|
-
sourceTable: ASSETS,
|
|
161
|
-
record: { id: 'asset1', description: 'test', user_id: 'user1' }
|
|
162
|
-
})
|
|
163
|
-
).toEqual([
|
|
164
|
-
{
|
|
165
|
-
ruleId: '1',
|
|
166
|
-
bucket: 'mybucket["user1"]',
|
|
167
|
-
id: 'asset1',
|
|
168
|
-
data: {
|
|
169
|
-
id: 'asset1',
|
|
170
|
-
description: 'test'
|
|
171
|
-
},
|
|
172
|
-
table: 'assets'
|
|
173
|
-
}
|
|
174
|
-
]);
|
|
175
|
-
expect(
|
|
176
|
-
rules.evaluateRow({
|
|
177
|
-
sourceTable: ASSETS,
|
|
178
|
-
record: { id: 'asset1', description: 'test', owner_id: 'user1' }
|
|
179
|
-
})
|
|
180
|
-
).toEqual([
|
|
181
|
-
{
|
|
182
|
-
ruleId: '1',
|
|
183
|
-
bucket: 'mybucket["user1"]',
|
|
184
|
-
id: 'asset1',
|
|
185
|
-
data: {
|
|
186
|
-
id: 'asset1',
|
|
187
|
-
description: 'test'
|
|
188
|
-
},
|
|
189
|
-
table: 'assets'
|
|
190
|
-
}
|
|
191
|
-
]);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test('parse bucket with parameters and invalid OR condition', () => {
|
|
195
|
-
expect(() => {
|
|
196
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
197
|
-
bucket_definitions:
|
|
198
|
-
mybucket:
|
|
199
|
-
parameters: SELECT token_parameters.user_id
|
|
200
|
-
data:
|
|
201
|
-
- SELECT id, description FROM assets WHERE assets.user_id = bucket.user_id AND (assets.user_id = bucket.foo OR assets.other_id = bucket.bar)
|
|
202
|
-
`);
|
|
203
|
-
}).toThrowError(/must use the same parameters/);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test('reject unsupported queries', () => {
|
|
207
|
-
expect(
|
|
208
|
-
SqlSyncRules.validate(`
|
|
209
|
-
bucket_definitions:
|
|
210
|
-
mybucket:
|
|
211
|
-
parameters: SELECT token_parameters.user_id LIMIT 1
|
|
212
|
-
data: []
|
|
213
|
-
`)
|
|
214
|
-
).toMatchObject([{ message: 'LIMIT is not supported' }]);
|
|
215
|
-
|
|
216
|
-
expect(
|
|
217
|
-
SqlSyncRules.validate(`
|
|
218
|
-
bucket_definitions:
|
|
219
|
-
mybucket:
|
|
220
|
-
data:
|
|
221
|
-
- SELECT DISTINCT id, description FROM assets
|
|
222
|
-
`)
|
|
223
|
-
).toMatchObject([{ message: 'DISTINCT is not supported' }]);
|
|
224
|
-
|
|
225
|
-
expect(
|
|
226
|
-
SqlSyncRules.validate(`
|
|
227
|
-
bucket_definitions:
|
|
228
|
-
mybucket:
|
|
229
|
-
parameters: SELECT token_parameters.user_id OFFSET 10
|
|
230
|
-
data: []
|
|
231
|
-
`)
|
|
232
|
-
).toMatchObject([{ message: 'LIMIT is not supported' }]);
|
|
233
|
-
|
|
234
|
-
expect(() => {
|
|
235
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
236
|
-
bucket_definitions:
|
|
237
|
-
mybucket:
|
|
238
|
-
parameters: SELECT token_parameters.user_id FOR UPDATE SKIP LOCKED
|
|
239
|
-
data: []
|
|
240
|
-
`);
|
|
241
|
-
}).toThrowError(/SKIP is not supported/);
|
|
242
|
-
|
|
243
|
-
expect(() => {
|
|
244
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
245
|
-
bucket_definitions:
|
|
246
|
-
mybucket:
|
|
247
|
-
parameters: SELECT token_parameters.user_id FOR UPDATE
|
|
248
|
-
data: []
|
|
249
|
-
`);
|
|
250
|
-
}).toThrowError(/FOR is not supported/);
|
|
251
|
-
|
|
252
|
-
expect(() => {
|
|
253
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
254
|
-
bucket_definitions:
|
|
255
|
-
mybucket:
|
|
256
|
-
data:
|
|
257
|
-
- SELECT id, description FROM assets ORDER BY id
|
|
258
|
-
`);
|
|
259
|
-
}).toThrowError(/ORDER BY is not supported/);
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
test('transforming things', () => {
|
|
263
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
264
|
-
bucket_definitions:
|
|
265
|
-
mybucket:
|
|
266
|
-
parameters: SELECT upper(token_parameters.user_id) AS user_id
|
|
267
|
-
data:
|
|
268
|
-
- SELECT id, upper(description) AS description_upper FROM assets WHERE upper(assets.user_id) = bucket.user_id AND NOT assets.archived
|
|
269
|
-
`);
|
|
270
|
-
const bucket = rules.bucket_descriptors[0];
|
|
271
|
-
expect(bucket.bucket_parameters).toEqual(['user_id']);
|
|
272
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ user_id: 'user1' }))).toEqual(['mybucket["USER1"]']);
|
|
273
|
-
|
|
274
|
-
expect(
|
|
275
|
-
rules.evaluateRow({
|
|
276
|
-
sourceTable: ASSETS,
|
|
277
|
-
record: { id: 'asset1', description: 'test', user_id: 'user1' }
|
|
278
|
-
})
|
|
279
|
-
).toEqual([
|
|
280
|
-
{
|
|
281
|
-
ruleId: '1',
|
|
282
|
-
bucket: 'mybucket["USER1"]',
|
|
283
|
-
id: 'asset1',
|
|
284
|
-
data: {
|
|
285
|
-
id: 'asset1',
|
|
286
|
-
description_upper: 'TEST'
|
|
287
|
-
},
|
|
288
|
-
table: 'assets'
|
|
289
|
-
}
|
|
290
|
-
]);
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test('transforming things with upper-case functions', () => {
|
|
294
|
-
// Testing that we can use different case for the function names
|
|
295
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
296
|
-
bucket_definitions:
|
|
297
|
-
mybucket:
|
|
298
|
-
parameters: SELECT UPPER(token_parameters.user_id) AS user_id
|
|
299
|
-
data:
|
|
300
|
-
- SELECT id, UPPER(description) AS description_upper FROM assets WHERE UPPER(assets.user_id) = bucket.user_id AND NOT assets.archived
|
|
301
|
-
`);
|
|
302
|
-
const bucket = rules.bucket_descriptors[0];
|
|
303
|
-
expect(bucket.bucket_parameters).toEqual(['user_id']);
|
|
304
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ user_id: 'user1' }))).toEqual(['mybucket["USER1"]']);
|
|
305
|
-
|
|
306
|
-
expect(
|
|
307
|
-
rules.evaluateRow({
|
|
308
|
-
sourceTable: ASSETS,
|
|
309
|
-
record: { id: 'asset1', description: 'test', user_id: 'user1' }
|
|
310
|
-
})
|
|
311
|
-
).toEqual([
|
|
312
|
-
{
|
|
313
|
-
ruleId: '1',
|
|
314
|
-
bucket: 'mybucket["USER1"]',
|
|
315
|
-
id: 'asset1',
|
|
316
|
-
data: {
|
|
317
|
-
id: 'asset1',
|
|
318
|
-
description_upper: 'TEST'
|
|
319
|
-
},
|
|
320
|
-
table: 'assets'
|
|
321
|
-
}
|
|
322
|
-
]);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
test('transforming json', () => {
|
|
326
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
327
|
-
bucket_definitions:
|
|
328
|
-
mybucket:
|
|
329
|
-
data:
|
|
330
|
-
- SELECT id, data ->> 'count' AS count, data -> 'bool' AS bool1, data ->> 'bool' AS bool2, 'true' ->> '$' as bool3, json_extract(data, '$.bool') AS bool4 FROM assets
|
|
331
|
-
`);
|
|
332
|
-
expect(
|
|
333
|
-
rules.evaluateRow({
|
|
334
|
-
sourceTable: ASSETS,
|
|
335
|
-
record: { id: 'asset1', data: JSON.stringify({ count: 5, bool: true }) }
|
|
336
|
-
})
|
|
337
|
-
).toEqual([
|
|
338
|
-
{
|
|
339
|
-
ruleId: '1',
|
|
340
|
-
bucket: 'mybucket[]',
|
|
341
|
-
id: 'asset1',
|
|
342
|
-
data: {
|
|
343
|
-
id: 'asset1',
|
|
344
|
-
count: 5n,
|
|
345
|
-
bool1: 'true',
|
|
346
|
-
bool2: 1n,
|
|
347
|
-
bool3: 1n,
|
|
348
|
-
bool4: 1n
|
|
349
|
-
},
|
|
350
|
-
table: 'assets'
|
|
351
|
-
}
|
|
352
|
-
]);
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
test('IN json', () => {
|
|
356
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
357
|
-
bucket_definitions:
|
|
358
|
-
mybucket:
|
|
359
|
-
parameters: SELECT token_parameters.region_id
|
|
360
|
-
data:
|
|
361
|
-
- SELECT id, description FROM assets WHERE bucket.region_id IN assets.region_ids
|
|
362
|
-
`);
|
|
363
|
-
|
|
364
|
-
expect(
|
|
365
|
-
rules.evaluateRow({
|
|
366
|
-
sourceTable: ASSETS,
|
|
367
|
-
record: {
|
|
368
|
-
id: 'asset1',
|
|
369
|
-
description: 'test',
|
|
370
|
-
region_ids: JSON.stringify(['region1', 'region2'])
|
|
371
|
-
}
|
|
372
|
-
})
|
|
373
|
-
).toEqual([
|
|
374
|
-
{
|
|
375
|
-
ruleId: '1',
|
|
376
|
-
bucket: 'mybucket["region1"]',
|
|
377
|
-
id: 'asset1',
|
|
378
|
-
data: {
|
|
379
|
-
id: 'asset1',
|
|
380
|
-
description: 'test'
|
|
381
|
-
},
|
|
382
|
-
table: 'assets'
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
ruleId: '1',
|
|
386
|
-
bucket: 'mybucket["region2"]',
|
|
387
|
-
id: 'asset1',
|
|
388
|
-
data: {
|
|
389
|
-
id: 'asset1',
|
|
390
|
-
description: 'test'
|
|
391
|
-
},
|
|
392
|
-
table: 'assets'
|
|
393
|
-
}
|
|
394
|
-
]);
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
test('direct boolean param', () => {
|
|
398
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
399
|
-
bucket_definitions:
|
|
400
|
-
mybucket:
|
|
401
|
-
parameters: SELECT token_parameters.is_admin
|
|
402
|
-
data:
|
|
403
|
-
- SELECT id, description, role, 'admin' as rule FROM assets WHERE bucket.is_admin
|
|
404
|
-
- SELECT id, description, role, 'normal' as rule FROM assets WHERE (bucket.is_admin OR bucket.is_admin = false) AND assets.role != 'admin'
|
|
405
|
-
`);
|
|
406
|
-
|
|
407
|
-
expect(
|
|
408
|
-
rules.evaluateRow({ sourceTable: ASSETS, record: { id: 'asset1', description: 'test', role: 'admin' } })
|
|
409
|
-
).toEqual([
|
|
410
|
-
{
|
|
411
|
-
ruleId: '1',
|
|
412
|
-
bucket: 'mybucket[1]',
|
|
413
|
-
id: 'asset1',
|
|
414
|
-
data: {
|
|
415
|
-
id: 'asset1',
|
|
416
|
-
description: 'test',
|
|
417
|
-
role: 'admin',
|
|
418
|
-
rule: 'admin'
|
|
419
|
-
},
|
|
420
|
-
table: 'assets'
|
|
421
|
-
}
|
|
422
|
-
]);
|
|
423
|
-
|
|
424
|
-
// TODO: Deduplicate somewhere
|
|
425
|
-
expect(
|
|
426
|
-
rules.evaluateRow({ sourceTable: ASSETS, record: { id: 'asset2', description: 'test', role: 'normal' } })
|
|
427
|
-
).toEqual([
|
|
428
|
-
{
|
|
429
|
-
ruleId: '1',
|
|
430
|
-
bucket: 'mybucket[1]',
|
|
431
|
-
id: 'asset2',
|
|
432
|
-
data: {
|
|
433
|
-
id: 'asset2',
|
|
434
|
-
description: 'test',
|
|
435
|
-
role: 'normal',
|
|
436
|
-
rule: 'admin'
|
|
437
|
-
},
|
|
438
|
-
table: 'assets'
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
ruleId: '2',
|
|
442
|
-
bucket: 'mybucket[1]',
|
|
443
|
-
id: 'asset2',
|
|
444
|
-
data: {
|
|
445
|
-
id: 'asset2',
|
|
446
|
-
description: 'test',
|
|
447
|
-
role: 'normal',
|
|
448
|
-
rule: 'normal'
|
|
449
|
-
},
|
|
450
|
-
table: 'assets'
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
ruleId: '2',
|
|
454
|
-
bucket: 'mybucket[0]',
|
|
455
|
-
id: 'asset2',
|
|
456
|
-
data: {
|
|
457
|
-
id: 'asset2',
|
|
458
|
-
description: 'test',
|
|
459
|
-
role: 'normal',
|
|
460
|
-
rule: 'normal'
|
|
461
|
-
},
|
|
462
|
-
table: 'assets'
|
|
463
|
-
}
|
|
464
|
-
]);
|
|
465
|
-
|
|
466
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ is_admin: true }))).toEqual(['mybucket[1]']);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test('token_parameters IN query', function () {
|
|
470
|
-
const sql = 'SELECT id as group_id FROM groups WHERE token_parameters.user_id IN groups.user_ids';
|
|
471
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
472
|
-
query.id = '1';
|
|
473
|
-
expect(query.evaluateParameterRow({ id: 'group1', user_ids: JSON.stringify(['user1', 'user2']) })).toEqual([
|
|
474
|
-
{
|
|
475
|
-
lookup: ['mybucket', '1', 'user1'],
|
|
476
|
-
bucket_parameters: [
|
|
477
|
-
{
|
|
478
|
-
group_id: 'group1'
|
|
479
|
-
}
|
|
480
|
-
]
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
lookup: ['mybucket', '1', 'user2'],
|
|
484
|
-
bucket_parameters: [
|
|
485
|
-
{
|
|
486
|
-
group_id: 'group1'
|
|
487
|
-
}
|
|
488
|
-
]
|
|
489
|
-
}
|
|
490
|
-
]);
|
|
491
|
-
expect(
|
|
492
|
-
query.getLookups(
|
|
493
|
-
normalizeTokenParameters({
|
|
494
|
-
user_id: 'user1'
|
|
495
|
-
})
|
|
496
|
-
)
|
|
497
|
-
).toEqual([['mybucket', '1', 'user1']]);
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
test('IN token_parameters query', function () {
|
|
501
|
-
const sql = 'SELECT id as region_id FROM regions WHERE name IN token_parameters.region_names';
|
|
502
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
503
|
-
query.id = '1';
|
|
504
|
-
expect(query.evaluateParameterRow({ id: 'region1', name: 'colorado' })).toEqual([
|
|
505
|
-
{
|
|
506
|
-
lookup: ['mybucket', '1', 'colorado'],
|
|
507
|
-
bucket_parameters: [
|
|
508
|
-
{
|
|
509
|
-
region_id: 'region1'
|
|
510
|
-
}
|
|
511
|
-
]
|
|
512
|
-
}
|
|
513
|
-
]);
|
|
514
|
-
expect(
|
|
515
|
-
query.getLookups(
|
|
516
|
-
normalizeTokenParameters({
|
|
517
|
-
region_names: JSON.stringify(['colorado', 'texas'])
|
|
518
|
-
})
|
|
519
|
-
)
|
|
520
|
-
).toEqual([
|
|
521
|
-
['mybucket', '1', 'colorado'],
|
|
522
|
-
['mybucket', '1', 'texas']
|
|
523
|
-
]);
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
test('some math', () => {
|
|
527
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
528
|
-
bucket_definitions:
|
|
529
|
-
mybucket:
|
|
530
|
-
data:
|
|
531
|
-
- SELECT id, (5 / 2) AS int, (5 / 2.0) AS float, (CAST(5 AS real) / 2) AS float2 FROM assets
|
|
532
|
-
`);
|
|
533
|
-
|
|
534
|
-
expect(rules.evaluateRow({ sourceTable: ASSETS, record: { id: 'asset1' } })).toEqual([
|
|
535
|
-
{
|
|
536
|
-
ruleId: '1',
|
|
537
|
-
bucket: 'mybucket[]',
|
|
538
|
-
id: 'asset1',
|
|
539
|
-
data: {
|
|
540
|
-
id: 'asset1',
|
|
541
|
-
int: 2n,
|
|
542
|
-
float: 2.5,
|
|
543
|
-
float2: 2.5
|
|
544
|
-
},
|
|
545
|
-
table: 'assets'
|
|
546
|
-
}
|
|
547
|
-
]);
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
test('bucket with static numeric parameters', () => {
|
|
551
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
552
|
-
bucket_definitions:
|
|
553
|
-
mybucket:
|
|
554
|
-
parameters: SELECT token_parameters.int1, token_parameters.float1, token_parameters.float2
|
|
555
|
-
data:
|
|
556
|
-
- SELECT id FROM assets WHERE assets.int1 = bucket.int1 AND assets.float1 = bucket.float1 AND assets.float2 = bucket.float2
|
|
557
|
-
`);
|
|
558
|
-
expect(rules.getStaticBucketIds(normalizeTokenParameters({ int1: 314, float1: 3.14, float2: 314 }))).toEqual([
|
|
559
|
-
'mybucket[314,3.14,314]'
|
|
560
|
-
]);
|
|
561
|
-
|
|
562
|
-
expect(
|
|
563
|
-
rules.evaluateRow({ sourceTable: ASSETS, record: { id: 'asset1', int1: 314n, float1: 3.14, float2: 314 } })
|
|
564
|
-
).toEqual([
|
|
565
|
-
{
|
|
566
|
-
ruleId: '1',
|
|
567
|
-
bucket: 'mybucket[314,3.14,314]',
|
|
568
|
-
id: 'asset1',
|
|
569
|
-
data: {
|
|
570
|
-
id: 'asset1'
|
|
571
|
-
},
|
|
572
|
-
table: 'assets'
|
|
573
|
-
}
|
|
574
|
-
]);
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
test('bucket with queried numeric parameters', () => {
|
|
578
|
-
const sql =
|
|
579
|
-
'SELECT users.int1, users.float1, users.float2 FROM users WHERE users.int1 = token_parameters.int1 AND users.float1 = token_parameters.float1 AND users.float2 = token_parameters.float2';
|
|
580
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
581
|
-
query.id = '1';
|
|
582
|
-
// Note: We don't need to worry about numeric vs decimal types in the lookup - JSONB handles normalization for us.
|
|
583
|
-
expect(query.evaluateParameterRow({ int1: 314n, float1: 3.14, float2: 314 })).toEqual([
|
|
584
|
-
{
|
|
585
|
-
lookup: ['mybucket', '1', 314n, 3.14, 314],
|
|
586
|
-
|
|
587
|
-
bucket_parameters: [{ int1: 314n, float1: 3.14, float2: 314 }]
|
|
588
|
-
}
|
|
589
|
-
]);
|
|
590
|
-
|
|
591
|
-
// Similarly, we don't need to worry about the types here.
|
|
592
|
-
// This test just checks the current behavior.
|
|
593
|
-
expect(query.getLookups(normalizeTokenParameters({ int1: 314n, float1: 3.14, float2: 314 }))).toEqual([
|
|
594
|
-
['mybucket', '1', 314n, 3.14, 314n]
|
|
595
|
-
]);
|
|
596
|
-
|
|
597
|
-
// We _do_ need to care about the bucket string representation.
|
|
598
|
-
expect(query.resolveBucketIds([{ int1: 314, float1: 3.14, float2: 314 }], normalizeTokenParameters({}))).toEqual([
|
|
599
|
-
'mybucket[314,3.14,314]'
|
|
600
|
-
]);
|
|
601
|
-
|
|
602
|
-
expect(query.resolveBucketIds([{ int1: 314n, float1: 3.14, float2: 314 }], normalizeTokenParameters({}))).toEqual([
|
|
603
|
-
'mybucket[314,3.14,314]'
|
|
604
|
-
]);
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
test('parameter query with token filter (1)', () => {
|
|
608
|
-
// Also supported: token_parameters.is_admin = true
|
|
609
|
-
// Not supported: token_parameters.is_admin != false
|
|
610
|
-
// Support could be added later.
|
|
611
|
-
const sql = 'SELECT FROM users WHERE users.id = token_parameters.user_id AND token_parameters.is_admin';
|
|
612
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
613
|
-
query.id = '1';
|
|
614
|
-
|
|
615
|
-
expect(query.evaluateParameterRow({ id: 'user1' })).toEqual([
|
|
616
|
-
{
|
|
617
|
-
lookup: ['mybucket', '1', 'user1', 1n],
|
|
618
|
-
bucket_parameters: [{}]
|
|
619
|
-
}
|
|
620
|
-
]);
|
|
621
|
-
|
|
622
|
-
expect(query.getLookups(normalizeTokenParameters({ user_id: 'user1', is_admin: true }))).toEqual([
|
|
623
|
-
['mybucket', '1', 'user1', 1n]
|
|
624
|
-
]);
|
|
625
|
-
// Would not match any actual lookups
|
|
626
|
-
expect(query.getLookups(normalizeTokenParameters({ user_id: 'user1', is_admin: false }))).toEqual([
|
|
627
|
-
['mybucket', '1', 'user1', 0n]
|
|
628
|
-
]);
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
test('parameter query with token filter (2)', () => {
|
|
632
|
-
const sql =
|
|
633
|
-
'SELECT users.id AS user_id, token_parameters.is_admin as is_admin FROM users WHERE users.id = token_parameters.user_id AND token_parameters.is_admin';
|
|
634
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
635
|
-
query.id = '1';
|
|
636
|
-
|
|
637
|
-
expect(query.evaluateParameterRow({ id: 'user1' })).toEqual([
|
|
638
|
-
{
|
|
639
|
-
lookup: ['mybucket', '1', 'user1', 1n],
|
|
640
|
-
|
|
641
|
-
bucket_parameters: [{ user_id: 'user1' }]
|
|
642
|
-
}
|
|
643
|
-
]);
|
|
644
|
-
|
|
645
|
-
expect(query.getLookups(normalizeTokenParameters({ user_id: 'user1', is_admin: true }))).toEqual([
|
|
646
|
-
['mybucket', '1', 'user1', 1n]
|
|
647
|
-
]);
|
|
648
|
-
|
|
649
|
-
expect(
|
|
650
|
-
query.resolveBucketIds([{ user_id: 'user1' }], normalizeTokenParameters({ user_id: 'user1', is_admin: true }))
|
|
651
|
-
).toEqual(['mybucket["user1",1]']);
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
test('custom table and id', () => {
|
|
655
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
656
|
-
bucket_definitions:
|
|
657
|
-
mybucket:
|
|
658
|
-
data:
|
|
659
|
-
- SELECT client_id AS id, description FROM assets_123 as assets WHERE assets.archived = false
|
|
660
|
-
- SELECT other_id AS id, description FROM assets_123 as assets
|
|
661
|
-
`);
|
|
662
|
-
|
|
663
|
-
expect(
|
|
664
|
-
rules.evaluateRow({
|
|
665
|
-
sourceTable: new TestSourceTable('assets_123'),
|
|
666
|
-
record: { client_id: 'asset1', description: 'test', archived: 0n, other_id: 'other1' }
|
|
667
|
-
})
|
|
668
|
-
).toEqual([
|
|
669
|
-
{
|
|
670
|
-
ruleId: '1',
|
|
671
|
-
bucket: 'mybucket[]',
|
|
672
|
-
id: 'asset1',
|
|
673
|
-
data: {
|
|
674
|
-
id: 'asset1',
|
|
675
|
-
description: 'test'
|
|
676
|
-
},
|
|
677
|
-
table: 'assets'
|
|
678
|
-
},
|
|
679
|
-
{
|
|
680
|
-
ruleId: '2',
|
|
681
|
-
bucket: 'mybucket[]',
|
|
682
|
-
id: 'other1',
|
|
683
|
-
data: {
|
|
684
|
-
id: 'other1',
|
|
685
|
-
description: 'test'
|
|
686
|
-
},
|
|
687
|
-
table: 'assets'
|
|
688
|
-
}
|
|
689
|
-
]);
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
test('wildcard table', () => {
|
|
693
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
694
|
-
bucket_definitions:
|
|
695
|
-
mybucket:
|
|
696
|
-
data:
|
|
697
|
-
- SELECT client_id AS id, description, _table_suffix as suffix, * FROM "assets_%" as assets WHERE assets.archived = false AND _table_suffix > '100'
|
|
698
|
-
`);
|
|
699
|
-
|
|
700
|
-
expect(
|
|
701
|
-
rules.evaluateRow({
|
|
702
|
-
sourceTable: new TestSourceTable('assets_123'),
|
|
703
|
-
record: { client_id: 'asset1', description: 'test', archived: 0n, other_id: 'other1' }
|
|
704
|
-
})
|
|
705
|
-
).toEqual([
|
|
706
|
-
{
|
|
707
|
-
ruleId: '1',
|
|
708
|
-
bucket: 'mybucket[]',
|
|
709
|
-
id: 'asset1',
|
|
710
|
-
data: {
|
|
711
|
-
id: 'asset1',
|
|
712
|
-
description: 'test',
|
|
713
|
-
suffix: '123',
|
|
714
|
-
archived: 0n,
|
|
715
|
-
client_id: 'asset1',
|
|
716
|
-
other_id: 'other1'
|
|
717
|
-
},
|
|
718
|
-
table: 'assets'
|
|
719
|
-
}
|
|
720
|
-
]);
|
|
721
|
-
});
|
|
722
|
-
|
|
723
|
-
test('wildcard without alias', () => {
|
|
724
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
725
|
-
bucket_definitions:
|
|
726
|
-
mybucket:
|
|
727
|
-
data:
|
|
728
|
-
- SELECT *, _table_suffix as suffix, * FROM "%" WHERE archived = false
|
|
729
|
-
`);
|
|
730
|
-
|
|
731
|
-
expect(
|
|
732
|
-
rules.evaluateRow({
|
|
733
|
-
sourceTable: ASSETS,
|
|
734
|
-
record: { id: 'asset1', description: 'test', archived: 0n }
|
|
735
|
-
})
|
|
736
|
-
).toEqual([
|
|
737
|
-
{
|
|
738
|
-
ruleId: '1',
|
|
739
|
-
bucket: 'mybucket[]',
|
|
740
|
-
id: 'asset1',
|
|
741
|
-
data: {
|
|
742
|
-
id: 'asset1',
|
|
743
|
-
description: 'test',
|
|
744
|
-
suffix: 'assets',
|
|
745
|
-
archived: 0n
|
|
746
|
-
},
|
|
747
|
-
table: 'assets'
|
|
748
|
-
}
|
|
749
|
-
]);
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
test('should filter schemas', () => {
|
|
753
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
754
|
-
bucket_definitions:
|
|
755
|
-
mybucket:
|
|
756
|
-
data:
|
|
757
|
-
- SELECT id FROM "assets" # Yes
|
|
758
|
-
- SELECT id FROM "public"."assets" # yes
|
|
759
|
-
- SELECT id FROM "default.public"."assets" # yes
|
|
760
|
-
- SELECT id FROM "other"."assets" # no
|
|
761
|
-
- SELECT id FROM "other.public"."assets" # no
|
|
762
|
-
`);
|
|
763
|
-
|
|
764
|
-
expect(
|
|
765
|
-
rules.evaluateRow({
|
|
766
|
-
sourceTable: ASSETS,
|
|
767
|
-
record: { id: 'asset1' }
|
|
768
|
-
})
|
|
769
|
-
).toEqual([
|
|
770
|
-
{
|
|
771
|
-
ruleId: '1',
|
|
772
|
-
bucket: 'mybucket[]',
|
|
773
|
-
id: 'asset1',
|
|
774
|
-
data: {
|
|
775
|
-
id: 'asset1'
|
|
776
|
-
},
|
|
777
|
-
table: 'assets'
|
|
778
|
-
},
|
|
779
|
-
{
|
|
780
|
-
ruleId: '2',
|
|
781
|
-
bucket: 'mybucket[]',
|
|
782
|
-
id: 'asset1',
|
|
783
|
-
data: {
|
|
784
|
-
id: 'asset1'
|
|
785
|
-
},
|
|
786
|
-
table: 'assets'
|
|
787
|
-
},
|
|
788
|
-
{
|
|
789
|
-
ruleId: '3',
|
|
790
|
-
bucket: 'mybucket[]',
|
|
791
|
-
id: 'asset1',
|
|
792
|
-
data: {
|
|
793
|
-
id: 'asset1'
|
|
794
|
-
},
|
|
795
|
-
table: 'assets'
|
|
796
|
-
}
|
|
797
|
-
]);
|
|
798
|
-
});
|
|
799
|
-
|
|
800
|
-
test('case-sensitive parameter queries (1)', () => {
|
|
801
|
-
const sql = 'SELECT users."userId" AS user_id FROM users WHERE users."userId" = token_parameters.user_id';
|
|
802
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
803
|
-
query.id = '1';
|
|
804
|
-
|
|
805
|
-
expect(query.evaluateParameterRow({ userId: 'user1' })).toEqual([
|
|
806
|
-
{
|
|
807
|
-
lookup: ['mybucket', '1', 'user1'],
|
|
808
|
-
|
|
809
|
-
bucket_parameters: [{ user_id: 'user1' }]
|
|
810
|
-
}
|
|
811
|
-
]);
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
test('case-sensitive parameter queries (2)', () => {
|
|
815
|
-
// Note: This documents current behavior.
|
|
816
|
-
// This may change in the future - we should check against expected behavior for
|
|
817
|
-
// Postgres and/or SQLite.
|
|
818
|
-
const sql = 'SELECT users.userId AS user_id FROM users WHERE users.userId = token_parameters.user_id';
|
|
819
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
820
|
-
query.id = '1';
|
|
821
|
-
|
|
822
|
-
expect(query.evaluateParameterRow({ userId: 'user1' })).toEqual([]);
|
|
823
|
-
expect(query.evaluateParameterRow({ userid: 'user1' })).toEqual([
|
|
824
|
-
{
|
|
825
|
-
lookup: ['mybucket', '1', 'user1'],
|
|
826
|
-
|
|
827
|
-
bucket_parameters: [{ user_id: 'user1' }]
|
|
828
|
-
}
|
|
829
|
-
]);
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
test('dynamic global parameter query', () => {
|
|
833
|
-
const sql = "SELECT workspaces.id AS workspace_id FROM workspaces WHERE visibility = 'public'";
|
|
834
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
835
|
-
query.id = '1';
|
|
836
|
-
|
|
837
|
-
expect(query.evaluateParameterRow({ id: 'workspace1', visibility: 'public' })).toEqual([
|
|
838
|
-
{
|
|
839
|
-
lookup: ['mybucket', '1'],
|
|
840
|
-
|
|
841
|
-
bucket_parameters: [{ workspace_id: 'workspace1' }]
|
|
842
|
-
}
|
|
843
|
-
]);
|
|
844
|
-
|
|
845
|
-
expect(query.evaluateParameterRow({ id: 'workspace1', visibility: 'private' })).toEqual([]);
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
test('invalid OR in parameter queries', () => {
|
|
849
|
-
// Supporting this case is more tricky. We can do this by effectively denormalizing the OR clause
|
|
850
|
-
// into separate queries, but it's a significant change. For now, developers should do that manually.
|
|
851
|
-
const sql =
|
|
852
|
-
"SELECT workspaces.id AS workspace_id FROM workspaces WHERE workspaces.user_id = token_parameters.user_id OR visibility = 'public'";
|
|
853
|
-
const query = SqlParameterQuery.fromSql('mybucket', sql) as SqlParameterQuery;
|
|
854
|
-
expect(query.errors[0].message).toMatch(/must use the same parameters/);
|
|
855
|
-
});
|
|
856
|
-
|
|
857
|
-
test('types', () => {
|
|
858
|
-
const schema = new StaticSchema([
|
|
859
|
-
{
|
|
860
|
-
tag: SourceTable.DEFAULT_TAG,
|
|
861
|
-
schemas: [
|
|
862
|
-
{
|
|
863
|
-
name: SourceTable.DEFAULT_SCHEMA,
|
|
864
|
-
tables: [
|
|
865
|
-
{
|
|
866
|
-
name: 'assets',
|
|
867
|
-
columns: [
|
|
868
|
-
{ name: 'id', pg_type: 'uuid' },
|
|
869
|
-
{ name: 'name', pg_type: 'text' },
|
|
870
|
-
{ name: 'count', pg_type: 'int4' },
|
|
871
|
-
{ name: 'owner_id', pg_type: 'uuid' }
|
|
872
|
-
]
|
|
873
|
-
}
|
|
874
|
-
]
|
|
875
|
-
}
|
|
876
|
-
]
|
|
877
|
-
}
|
|
878
|
-
]);
|
|
879
|
-
|
|
880
|
-
const q1 = SqlDataQuery.fromSql('q1', ['user_id'], `SELECT * FROM assets WHERE owner_id = bucket.user_id`);
|
|
881
|
-
expect(q1.getColumnOutputs(schema)).toEqual([
|
|
882
|
-
{
|
|
883
|
-
name: 'assets',
|
|
884
|
-
columns: [
|
|
885
|
-
{ name: 'id', type: ExpressionType.TEXT },
|
|
886
|
-
{ name: 'name', type: ExpressionType.TEXT },
|
|
887
|
-
{ name: 'count', type: ExpressionType.INTEGER },
|
|
888
|
-
{ name: 'owner_id', type: ExpressionType.TEXT }
|
|
889
|
-
]
|
|
890
|
-
}
|
|
891
|
-
]);
|
|
892
|
-
|
|
893
|
-
const q2 = SqlDataQuery.fromSql(
|
|
894
|
-
'q1',
|
|
895
|
-
['user_id'],
|
|
896
|
-
`
|
|
897
|
-
SELECT id :: integer as id,
|
|
898
|
-
upper(name) as name_upper,
|
|
899
|
-
hex('test') as hex,
|
|
900
|
-
count + 2 as count2,
|
|
901
|
-
count * 3.0 as count3,
|
|
902
|
-
count * '4' as count4,
|
|
903
|
-
name ->> '$.attr' as json_value,
|
|
904
|
-
ifnull(name, 2.0) as maybe_name
|
|
905
|
-
FROM assets WHERE owner_id = bucket.user_id`
|
|
906
|
-
);
|
|
907
|
-
expect(q2.getColumnOutputs(schema)).toEqual([
|
|
908
|
-
{
|
|
909
|
-
name: 'assets',
|
|
910
|
-
columns: [
|
|
911
|
-
{ name: 'id', type: ExpressionType.INTEGER },
|
|
912
|
-
{ name: 'name_upper', type: ExpressionType.TEXT },
|
|
913
|
-
{ name: 'hex', type: ExpressionType.TEXT },
|
|
914
|
-
{ name: 'count2', type: ExpressionType.INTEGER },
|
|
915
|
-
{ name: 'count3', type: ExpressionType.REAL },
|
|
916
|
-
{ name: 'count4', type: ExpressionType.NUMERIC },
|
|
917
|
-
{ name: 'json_value', type: ExpressionType.ANY_JSON },
|
|
918
|
-
{ name: 'maybe_name', type: ExpressionType.TEXT.or(ExpressionType.REAL) }
|
|
919
|
-
]
|
|
920
|
-
}
|
|
921
|
-
]);
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
test('validate columns', () => {
|
|
925
|
-
const schema = new StaticSchema([
|
|
926
|
-
{
|
|
927
|
-
tag: SourceTable.DEFAULT_TAG,
|
|
928
|
-
schemas: [
|
|
929
|
-
{
|
|
930
|
-
name: SourceTable.DEFAULT_SCHEMA,
|
|
931
|
-
tables: [
|
|
932
|
-
{
|
|
933
|
-
name: 'assets',
|
|
934
|
-
columns: [
|
|
935
|
-
{ name: 'id', pg_type: 'uuid' },
|
|
936
|
-
{ name: 'name', pg_type: 'text' },
|
|
937
|
-
{ name: 'count', pg_type: 'int4' },
|
|
938
|
-
{ name: 'owner_id', pg_type: 'uuid' }
|
|
939
|
-
]
|
|
940
|
-
}
|
|
941
|
-
]
|
|
942
|
-
}
|
|
943
|
-
]
|
|
944
|
-
}
|
|
945
|
-
]);
|
|
946
|
-
const q1 = SqlDataQuery.fromSql(
|
|
947
|
-
'q1',
|
|
948
|
-
['user_id'],
|
|
949
|
-
'SELECT id, name, count FROM assets WHERE owner_id = bucket.user_id',
|
|
950
|
-
schema
|
|
951
|
-
);
|
|
952
|
-
expect(q1.errors).toEqual([]);
|
|
953
|
-
|
|
954
|
-
const q2 = SqlDataQuery.fromSql(
|
|
955
|
-
'q2',
|
|
956
|
-
['user_id'],
|
|
957
|
-
'SELECT id, upper(description) as d FROM assets WHERE other_id = bucket.user_id',
|
|
958
|
-
schema
|
|
959
|
-
);
|
|
960
|
-
expect(q2.errors).toMatchObject([
|
|
961
|
-
{
|
|
962
|
-
message: `Column not found: other_id`,
|
|
963
|
-
type: 'warning'
|
|
964
|
-
},
|
|
965
|
-
{
|
|
966
|
-
message: `Column not found: description`,
|
|
967
|
-
type: 'warning'
|
|
968
|
-
}
|
|
969
|
-
]);
|
|
970
|
-
|
|
971
|
-
const q3 = SqlDataQuery.fromSql(
|
|
972
|
-
'q3',
|
|
973
|
-
['user_id'],
|
|
974
|
-
'SELECT id, description, * FROM nope WHERE other_id = bucket.user_id',
|
|
975
|
-
schema
|
|
976
|
-
);
|
|
977
|
-
expect(q3.errors).toMatchObject([
|
|
978
|
-
{
|
|
979
|
-
message: `Table public.nope not found`,
|
|
980
|
-
type: 'warning'
|
|
981
|
-
}
|
|
982
|
-
]);
|
|
983
|
-
});
|
|
984
|
-
|
|
985
|
-
test('schema generation', () => {
|
|
986
|
-
const schema = new StaticSchema([
|
|
987
|
-
{
|
|
988
|
-
tag: SourceTable.DEFAULT_TAG,
|
|
989
|
-
schemas: [
|
|
990
|
-
{
|
|
991
|
-
name: SourceTable.DEFAULT_SCHEMA,
|
|
992
|
-
tables: [
|
|
993
|
-
{
|
|
994
|
-
name: 'assets',
|
|
995
|
-
columns: [
|
|
996
|
-
{ name: 'id', pg_type: 'uuid' },
|
|
997
|
-
{ name: 'name', pg_type: 'text' },
|
|
998
|
-
{ name: 'count', pg_type: 'int4' },
|
|
999
|
-
{ name: 'owner_id', pg_type: 'uuid' }
|
|
1000
|
-
]
|
|
1001
|
-
}
|
|
1002
|
-
]
|
|
1003
|
-
}
|
|
1004
|
-
]
|
|
1005
|
-
}
|
|
1006
|
-
]);
|
|
1007
|
-
|
|
1008
|
-
const rules = SqlSyncRules.fromYaml(`
|
|
1009
|
-
bucket_definitions:
|
|
1010
|
-
mybucket:
|
|
1011
|
-
data:
|
|
1012
|
-
- SELECT * FROM assets as assets1
|
|
1013
|
-
- SELECT id, name, count FROM assets as assets2
|
|
1014
|
-
- SELECT id, owner_id as other_id, foo FROM assets as ASSETS2
|
|
1015
|
-
`);
|
|
1016
|
-
|
|
1017
|
-
expect(new DartSchemaGenerator().generate(rules, schema)).toEqual(`Schema([
|
|
1018
|
-
Table('assets1', [
|
|
1019
|
-
Column.text('name'),
|
|
1020
|
-
Column.integer('count'),
|
|
1021
|
-
Column.text('owner_id')
|
|
1022
|
-
]),
|
|
1023
|
-
Table('assets2', [
|
|
1024
|
-
Column.text('name'),
|
|
1025
|
-
Column.integer('count'),
|
|
1026
|
-
Column.text('other_id'),
|
|
1027
|
-
Column.text('foo')
|
|
1028
|
-
])
|
|
1029
|
-
]);
|
|
1030
|
-
`);
|
|
1031
|
-
|
|
1032
|
-
expect(new JsSchemaGenerator().generate(rules, schema)).toEqual(`new Schema([
|
|
1033
|
-
new Table({
|
|
1034
|
-
name: 'assets1',
|
|
1035
|
-
columns: [
|
|
1036
|
-
new Column({ name: 'name', type: ColumnType.TEXT }),
|
|
1037
|
-
new Column({ name: 'count', type: ColumnType.INTEGER }),
|
|
1038
|
-
new Column({ name: 'owner_id', type: ColumnType.TEXT })
|
|
1039
|
-
]
|
|
1040
|
-
}),
|
|
1041
|
-
new Table({
|
|
1042
|
-
name: 'assets2',
|
|
1043
|
-
columns: [
|
|
1044
|
-
new Column({ name: 'name', type: ColumnType.TEXT }),
|
|
1045
|
-
new Column({ name: 'count', type: ColumnType.INTEGER }),
|
|
1046
|
-
new Column({ name: 'other_id', type: ColumnType.TEXT }),
|
|
1047
|
-
new Column({ name: 'foo', type: ColumnType.TEXT })
|
|
1048
|
-
]
|
|
1049
|
-
})
|
|
1050
|
-
])
|
|
1051
|
-
`);
|
|
1052
|
-
});
|
|
1053
|
-
});
|