Cross-source joins¶
One SELECT joins a table in a SQLite file with a table in a DuckDB file. Sluice checks access for each table.
Example¶
Start the server with examples/cross-source-join/serve.sh. The script needs Go to write the DuckDB file.
Each data source is one catalog. Its name is the first part of catalog.schema.table.
# Transactional data in a SQLite file.
apiVersion: sluice.bino.bi/v1alpha1
kind: DataSource
metadata:
name: shop
spec:
type: sqlite
path: ./data/shop.db
# Reference data in a DuckDB file.
apiVersion: sluice.bino.bi/v1alpha1
kind: DataSource
metadata:
name: ref
spec:
type: duckdb_file
path: ./data/ref.duckdb
The allow covers one table in each catalog.
# One clause per catalog. A join needs an allow for every table it reads.
apiVersion: sluice.bino.bi/v1alpha1
kind: SqlAccessPolicy
metadata:
name: allow-analytics
priority: 100
spec:
effect: allow
match:
any:
- subjects:
groups: ["analytics"]
resources:
catalogs: ["shop"]
schemas: ["main"]
tables: ["orders"]
- subjects:
groups: ["analytics"]
resources:
catalogs: ["ref"]
schemas: ["main"]
tables: ["countries"]
The analyst key joins both tables.
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_analyst.supersecret" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT o.id, o.amount_cents, c.name AS country, c.region FROM shop.main.orders o JOIN ref.main.countries c ON c.code = o.country_code ORDER BY o.id"}'
{
"query_id": "<query-id>",
"columns": ["id", "amount_cents", "country", "region"],
"rows": [
[1, 1999, "United States", "Americas"],
[2, 4599, "Germany", "EMEA"],
[3, 7800, "France", "EMEA"],
[4, 250, "United States", "Americas"],
[5, 32000, "Japan", "APAC"]
],
"row_count": 5,
"truncated": false
}
What happened: Sluice attached both files and ran one query over them. The allow covers both tables, so the join passes.
More examples¶
The intern key may read ref.main.countries only. The same join is denied.
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_intern.supersecret" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT o.id, o.amount_cents, c.name AS country, c.region FROM shop.main.orders o JOIN ref.main.countries c ON c.code = o.country_code ORDER BY o.id"}'
{
"code": "ACL_DENIED",
"message": "no SqlAccessPolicy allows shop.main.orders",
"query_id": "<query-id>"
}
A mask on orders.customer_id also applies inside the join.
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_analyst.supersecret" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT o.id, o.customer_id, c.name AS country FROM shop.main.orders o JOIN ref.main.countries c ON c.code = o.country_code ORDER BY o.id"}'
{
"query_id": "<query-id>",
"columns": ["id", "customer_id", "country"],
"rows": [
[1, null, "United States"],
[2, null, "Germany"],
[3, null, "France"],
[4, null, "United States"],
[5, null, "Japan"]
],
"row_count": 5,
"truncated": false
}
This suite case shows the rewritten SQL. The mask stays on the orders side.
- name: analyst joins both catalogs, the mask stays on the orders side
identity: { subject: analyst, groups: [analytics] }
sql: >-
SELECT o.id, o.customer_id, c.name AS country
FROM shop.main.orders o JOIN ref.main.countries c ON c.code = o.country_code
expect:
outcome: allow
applied: ["ColumnMaskPolicy/mask-customer-id", "SqlAccessPolicy/allow-analytics"]
masks: ["shop.main.orders.customer_id=null"]
rewrittenSql: >-
SELECT o.id, o.customer_id, c.name AS country
FROM (SELECT sluice_src.id, NULL AS customer_id, sluice_src.country_code, sluice_src.amount_cents
FROM shop.main.orders sluice_src) o
JOIN ref.main.countries c ON c.code = o.country_code
A QueryRejectPolicy rejects a query on more than one catalog for selected subjects.
# Contractors may read one catalog per query.
apiVersion: sluice.bino.bi/v1alpha1
kind: QueryRejectPolicy
metadata:
name: one-catalog
priority: 200
spec:
match:
any:
- subjects:
groups: ["contractors"]
reject:
rules:
- name: no-second-catalog
expression: "size(query.catalogs) > 1"
message: "read one data source per query"
This suite case proves it.
- name: a contractor cannot join two catalogs
identity: { subject: carl, groups: [analytics, contractors] }
sql: >-
SELECT o.id, c.name AS country
FROM shop.main.orders o JOIN ref.main.countries c ON c.code = o.country_code
expect:
outcome: reject
rejections: ["one-catalog/no-second-catalog"]
The same join on Postgres and S3 is in examples/cross-source-join/manual/postgres-s3/. make docs-check only validates its policies.
Limits¶
limits.disableCrossCatalog: truein the server config rejects every query that names more than one catalog:ACL_REJECTED.