Skip to content

Row Filters, Column Masks, and ABAC Policies

Step 29 of 30

Row Filters, Column Masks, and ABAC Policies

Introduction

This lesson completes the Governance and Security section of the official exam outline (about 15% of the scored questions). The previous lesson controlled access at the level of whole objects: a principal can or cannot read a table. Real organizations need finer control. A regional sales manager should see only the rows of their region; a support agent should see the last four digits of a card number but not the whole value; an analyst should see the data but never the email addresses.

Unity Catalog offers three tools for this fine-grained control: row filters, column masks and, at scale, attribute-based access control (ABAC) policies driven by governed tags. Row filters and column masks are defined per table. ABAC applies the same logic automatically to every table that carries the right tags, across a whole catalog or schema. Databricks announced ABAC row filtering and column masking policies, governed tags and data classification as generally available in 2026; its documentation recommends ABAC for consistent rules across many tables, and per-table filters and masks when you need logic specific to one table or have not adopted ABAC yet.

Learning objectives

  • Explain the difference between object-level privileges, row filters, column masks and ABAC policies.
  • Write a row filter function and attach it to a table.
  • Write a column mask function and attach it to a column.
  • Use is_account_group_member() to implement identity-based logic.
  • Explain ABAC: governed tags, policy scope, principals, conditions and the policy functions.
  • Choose between a dynamic view, per-table filters/masks and ABAC.
  • Name the limits and operational points to remember (privileges, performance, and what consumers see).

1. Why object-level privileges are not enough

Imagine the table prod.sales.orders, with columns order_id, region, customer_email and amount.

Requirement Object-level GRANT SELECT What is needed
EU managers see only EU rows Cannot express it Row filter
Everybody sees the table but the email is hidden except for support Cannot express it Column mask
All columns tagged as personal data are masked in 400 tables Would need 400 separate definitions ABAC policy with governed tags

Before these features, engineers created dynamic views per table, with a CASE expression or a WHERE clause using is_account_group_member(). That still works, but the base table remains readable by anyone who finds it, and each table needs its own view. Row filters and masks are attached to the table itself, so any query on the table is protected, whatever the interface (notebook, SQL warehouse, dashboard, job).

2. Row filters

A row filter is a SQL user-defined function (UDF) that returns a BOOLEAN. It is attached to a table and receives the values of one or more columns of each row. Unity Catalog evaluates it for every query on the table; rows for which it returns TRUE are returned, and rows for which it returns FALSE (or NULL) are hidden. The reader does not see the filter in the query: the rows are simply absent.

-- 1. The function
CREATE OR REPLACE FUNCTION prod.security.region_filter(region STRING)
RETURN is_account_group_member('global-managers')
    OR (is_account_group_member('eu-managers') AND region = 'EU')
    OR (is_account_group_member('us-managers') AND region = 'US');

-- 2. Attach it to the table, naming the column(s) passed to the function
ALTER TABLE prod.sales.orders
SET ROW FILTER prod.security.region_filter ON (region);

-- Remove it
ALTER TABLE prod.sales.orders DROP ROW FILTER;

Points to remember:

  • The function is a normal Unity Catalog object, so it can be stored in a dedicated security schema with its own, tightly controlled privileges.
  • is_account_group_member('group') returns true when the current user belongs to the account-level group; it is the preferred check in Unity Catalog. current_user() returns the user's identity and is useful for a mapping table (for example user_regions), but a mapping table must itself be protected.
  • A table can have one row filter; the function may combine several conditions.
  • The filter can also be given other columns, or a constant argument, but it must be deterministic and fast: it runs for every row of every query.
  • Users need access to the table (SELECT and the USE privileges) as usual; the row filter then trims what they receive. Setting a filter requires owning the table (or MANAGE) and EXECUTE on the function.

Concrete example. A bank stores loans(loan_id, branch_id, amount, ...). Branch managers must see only their branch. The security team creates branch_filter(branch_id) that compares branch_id with a lookup in a protected mapping table security.user_branches using current_user(). A manager runs SELECT COUNT(*) FROM loans and gets only the loans of their branch; an auditor in the group audit gets everything.

3. Column masks

A column mask is a SQL UDF attached to a column. It takes the column's value (and optionally other columns of the row) and returns the value to display. The return type must match the column's type, so that queries and tools do not break.

CREATE OR REPLACE FUNCTION prod.security.mask_email(email STRING)
RETURN CASE
  WHEN is_account_group_member('pii-readers') THEN email
  ELSE regexp_replace(email, '^[^@]+', '*****')
END;

ALTER TABLE prod.sales.orders
ALTER COLUMN customer_email SET MASK prod.security.mask_email;

-- Remove it
ALTER TABLE prod.sales.orders
ALTER COLUMN customer_email DROP MASK;

Members of pii-readers see maria@example.com; everybody else sees *****@example.com.

A mask can use additional columns of the same row through USING COLUMNS:

ALTER TABLE prod.sales.orders
ALTER COLUMN card_number SET MASK prod.security.mask_card USING COLUMNS (country);

Properties:

Aspect Row filter Column mask
Attached to A table A column
Function returns BOOLEAN Same type as the column
Effect Hides rows Replaces values
Number per object One per table One per column
Removing ALTER TABLE ... DROP ROW FILTER ALTER COLUMN ... DROP MASK

Masks are not encryption. They control what a query returns. Anyone with higher privileges (for example the owner of the function logic through group membership) can see the raw value, and the stored files are unchanged.

4. Dynamic views versus filters and masks

Criterion Dynamic view Row filter / column mask ABAC policy
Protection point A view, base table still readable by privileged users The table itself The table itself, by tags
Reuse One view per table One function reused by many tables, attached manually One policy for many tables automatically
Effort for 400 tables Very high High (attach to each) Low (tag once, policy applies)
Typical use today Legacy or ad hoc Table-specific logic Consistent enterprise rules

5. Attribute-based access control (ABAC)

ABAC decides access from attributes of the data (tags) and, in policies, of the principals (their groups), rather than from a list of grants per object. In Unity Catalog the building blocks are:

  • Governed tags: account-level tags whose name, allowed values and the permission to assign them are controlled centrally. Unlike free tags, a governed tag cannot be created or applied by just anyone; a data steward decides who may assign pii = email, for example.
  • Policies: rules that say "for the objects matching this tag condition, apply this row filter or column mask function, to these principals, except those principals".
  • Policy functions: the same kind of SQL UDFs as in sections 2 and 3, now referenced by a policy.

A policy is defined on a scope: a catalog, a schema or a table. It is inherited downward, so a policy at catalog level applies to all current and future matching tables below it. The condition uses the tags of the tables and columns, and the principals can be users, service principals or groups, with an EXCEPT list for those not concerned.

An illustrative policy that masks every column tagged as an email for everyone except the privacy team (check the current documentation for the exact syntax):

CREATE POLICY mask_emails
ON CATALOG prod
COLUMN MASK prod.security.mask_email
TO `account users`
EXCEPT `pii-readers`
FOR TABLES
MATCH COLUMNS hasTagValue('pii', 'email') AS email_col
ON COLUMN email_col;

A row filter policy follows the same pattern, with ROW FILTER and a condition on the table, for example tables tagged domain = sales that have a column tagged region.

Operational facts:

  • Creating a policy requires the right to manage it on the securable (the MANAGE privilege on the catalog, schema or table, or ownership) and EXECUTE on the function used.
  • Tagging the data is the key step: a column without the tag is not affected. Governance teams often combine ABAC with data classification, which automatically detects and tags sensitive columns.
  • Policies are evaluated at query time, and the policy applies whatever the interface used.
  • Some ABAC capabilities, such as DENY policies and the identity attribute functions, were still listed in beta in the documentation reviewed for this course. They are not part of the core exam concepts.

Concrete example. A pharmaceutical group has 1,200 tables across 30 schemas. Compliance requires that every column holding a patient identifier is masked for everybody except the clinical-data team. Without ABAC, an engineer would attach a mask to each column, then forget new tables. With ABAC, the data steward tags the identifier columns with pii = patient_id (or data classification does it), one catalog-level policy applies mask_patient_id to every tagged column, now and in the future, and the clinical-data group is the exception.

6. Operational points

  • Compute: tables protected by filters, masks or policies are only readable on supported compute (serverless and recent Databricks Runtime versions on standard or dedicated access mode). Older runtimes and unsupported modes are denied rather than leaking data.
  • Performance: the function runs on the query path. Keep it simple: no heavy subqueries or external calls. A mapping-table lookup should be small.
  • Downstream objects: views built on a protected table respect the protection for the user querying them; materialized views and streaming tables have specific rules that the documentation lists, so check them before relying on them.
  • Testing: test with accounts in each group, and check the result of SELECT *, COUNT(*), joins and aggregations, because a mask applied before an aggregation changes the aggregate.
  • Auditing: access to protected tables appears in the audit logs and system tables, and lineage keeps working.

Worked example: protecting customer data in a shared orders table

Scenario. prod.sales.orders has order_id, region, customer_email, card_last4 and amount. Requirements:

  1. Regional managers see only their region's rows; the group global-finance sees everything.
  2. Everybody except support-tier2 sees customer_email masked.
  3. The same email rule must apply to 80 other tables in the catalog prod.

Step 1: Row filter on this table. Table-specific logic fits a per-table row filter.

CREATE OR REPLACE FUNCTION prod.security.region_filter(region STRING)
RETURN is_account_group_member('global-finance')
    OR (is_account_group_member('eu-managers') AND region = 'EU')
    OR (is_account_group_member('us-managers') AND region = 'US');

ALTER TABLE prod.sales.orders SET ROW FILTER prod.security.region_filter ON (region);

Step 2: Email masking for 80 tables. Create the mask function mask_email, have the steward tag every email column with pii = email, then create one catalog-level ABAC column-mask policy for all principals except support-tier2.

Step 3: Grants. Make sure the groups hold USE CATALOG, USE SCHEMA and SELECT, because filters and masks restrict what is returned and do not grant access.

Step 4: Test. A user in eu-managers runs SELECT region, COUNT(*) FROM prod.sales.orders GROUP BY region: only EU appears and customer_email is masked. A user in support-tier2 sees the real email but only the rows allowed by the filter.

Why the alternatives are worse. A dynamic view per table protects only the view and requires 80 definitions. Copying a masked version of the table creates stale duplicates. Revoking SELECT from analysts blocks the legitimate use of non-sensitive columns.

Common pitfalls and exam tips

  • Filters and masks restrict results; they do not replace GRANT SELECT. The user still needs USE CATALOG, USE SCHEMA and SELECT.
  • A row filter function returns BOOLEAN; a column mask function returns the column's type.
  • ALTER TABLE ... SET ROW FILTER ... ON (cols) and ALTER COLUMN ... SET MASK attach them; DROP ROW FILTER and DROP MASK remove them.
  • Use is_account_group_member() for group checks in Unity Catalog, not workspace-local group checks.
  • One row filter per table, one mask per column.
  • ABAC = governed tags + policies; a policy at a catalog or schema is inherited by matching objects below it and applies to future tables too.
  • For many tables with the same rule, choose ABAC; for one-off logic, a table filter or mask; dynamic views are the legacy approach.
  • Masking is not encryption and does not change stored files.

Exam tip: If a question says "hide the values of a sensitive column from most users while keeping the table readable", the answer is a column mask. If it says "each manager must see only their own region's rows", it is a row filter. If it says "the same protection for every column holding personal data across many tables, including future ones", it is an ABAC policy with governed tags.

Mini case study

Situation. A retailer's table prod.crm.customers contains customer_id, country, email and phone. Data engineers complain that marketing analysts can read emails and phone numbers. The compliance officer wants: (a) analysts see only customers of their own country; (b) email and phone hidden for everybody but the group crm-admins; (c) the phone rule to extend automatically to new tables with a phone column.

Task. Propose the design.

Solution.

  1. Row filter country_filter(country) attached with ALTER TABLE ... SET ROW FILTER ... ON (country); analysts are in groups per country, crm-admins sees all.
  2. For email, a column mask attached with ALTER COLUMN email SET MASK mask_email, as this table is the only one concerned.
  3. For phone, the steward tags phone columns with a governed tag such as pii = phone, and a catalog-level ABAC column-mask policy applies mask_phone to tagged columns for all principals except crm-admins. New tables with a tagged phone column are covered automatically.
  4. Check that the analysts hold USE CATALOG, USE SCHEMA and SELECT, and test with an analyst account, an admin account and an account in no group.

Key terms

  • Row filter: SQL UDF returning BOOLEAN that decides which rows a user sees.
  • Column mask: SQL UDF that returns the displayed value of a column.
  • Dynamic view: view that changes its result depending on the reader's identity.
  • is_account_group_member(): function testing membership of the current user in an account-level group.
  • ABAC: access control driven by tags and policies instead of per-object grants.
  • Governed tag: centrally controlled tag with allowed values and assignment permissions.
  • Policy scope: catalog, schema or table at which an ABAC policy is defined and inherited.
  • Data classification: automatic detection and tagging of sensitive data.

Summary checklist

  • I can write and attach a row filter, and remove it.
  • I can write and attach a column mask, and remove it.
  • I can explain why filters and masks do not replace GRANT.
  • I can describe governed tags, ABAC policies, their scope and inheritance.
  • I can pick between a dynamic view, a table-level filter or mask, and ABAC.
  • I know the main operational points: supported compute, function performance, testing.

Official sources