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.ordersusing = "region = 'us'"to = [role.analyst, user.report_user]}policy "recent_only" {on = table.ordersusing = "created_at > now() - INTERVAL 90 DAY"to_all = true}policy "exclude_deleted" {on = table.ordersrestrictive = trueusing = "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.ordersusing = "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_indexengine = MergeTreecolumn "id" {null = falsetype = UInt64}column "name" {null = falsetype = 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 = MergeTreePRIMARY 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([',']));