Back to changelog
New
3 minute read

ClickHouse: Row Policies & Full-Text Search Index

Atlas now supports ClickHouse row policies as a first-class resource and the text index type for full-text search, covering declarative row-level access control and token-based search indexes side by side with existing table, role, and grant management.

Row Policies

Row policies filter which rows a role or user can see on a given table or across a whole database. In ClickHouse, you might declare them like this:

CREATE ROW POLICY analysts_only ON example_db.orders USING region = 'us' TO analyst, report_user;
CREATE ROW POLICY recent_only ON example_db.orders USING created_at > now() - INTERVAL 90 DAY TO ALL;
CREATE ROW POLICY exclude_deleted ON example_db.orders AS RESTRICTIVE USING is_deleted = 0 TO ALL;
CREATE ROW POLICY db_tenant_isolation ON example_db.* USING tenant_id = currentUser() TO ALL;
CREATE ROW POLICY hide_pii ON example_db.orders USING sensitivity < 3 TO ALL EXCEPT intern;

Atlas now represents each row policy as a policy block. Inspecting the schema above produces the following HCL, covering permissive and restrictive modes, to_all, to (roles and users), to_all_except, and a database-wide target (example_db.*):

policy "analysts_only" {
on = table.orders
using = "region = 'us'"
to = [role.analyst, user.report_user]
}
policy "recent_only" {
on = table.orders
using = "created_at > now() - INTERVAL 90 DAY"
to_all = true
}
policy "exclude_deleted" {
on = table.orders
restrictive = true
using = "is_deleted = 0"
to_all = true
}
policy "db_tenant_isolation" {
on = "example_db.*"
using = "tenant_id = currentUser()"
to_all = true
}
policy "hide_pii" {
on = table.orders
using = "sensitivity < 3"
to_all_except = [role.intern]
}

Row policies are diffed individually. Changing the using expression, flipping between permissive and restrictive, retargeting to different roles, adding new policies, or dropping existing ones all produce minimal migrations:

-- Modify row policy "analysts_only" on "example_db"."orders"
ALTER ROW POLICY `analysts_only` ON `example_db`.`orders` USING region IN ('us','eu') TO `analyst`, `report_user`;
-- Modify row policy "recent_only" on "example_db"."orders"
ALTER ROW POLICY `recent_only` ON `example_db`.`orders` USING created_at > now() - INTERVAL 180 DAY TO ALL;
-- Create row policy "exclude_archived" on "example_db"."orders"
CREATE ROW POLICY `exclude_archived` ON `example_db`.`orders` AS RESTRICTIVE FOR SELECT USING status != 'archived' TO ALL;
-- Drop row policy "hide_pii" on "example_db"."orders"
DROP ROW POLICY `hide_pii` ON `example_db`.`orders`;

Full-Text Search Index

ClickHouse provides a text index type that tokenizes string columns for full-text search. Declare it on a table via the index block, with the tokenizer passed through the type expression:

table "tbl" {
schema = schema.script_text_index
engine = MergeTree
column "id" {
null = false
type = UInt64
}
column "name" {
null = false
type = String
}
index "text" {
type = sql("text(tokenizer = splitByNonAlpha)")
on {
expr = "name"
}
}
primary_key {
columns = [column.id]
}
}

Atlas generates the expected table definition:

CREATE TABLE `tbl` (
`id` UInt64,
`name` String,
INDEX `text` ((name)) TYPE text(tokenizer = splitByNonAlpha)
) ENGINE = MergeTree
PRIMARY KEY (`id`) ORDER BY (`id`) SETTINGS index_granularity = 8192;

Tokenizer changes are detected and applied as a drop-and-add on the index. For example, switching from splitByNonAlpha to splitByString([',']) produces:

ALTER TABLE `tbl` DROP INDEX `text`;
ALTER TABLE `tbl` ADD INDEX `text` ((name)) TYPE text(tokenizer = splitByString([',']));
featureclickhouserow-policytext-indexfull-text-searchsecurity