| → / Space | Next slide |
| ← | Previous slide |
| Home | First slide |
| End | Last slide |
| Swipe | Touch navigation |
Your AI platform answers questions from live data, but not every user should see every answer. Sales reps should only see their own region's pipeline. Partners should see aggregated metrics but never individual end-user records. Auditors need read access to compliance tables but nothing else.
These rules must apply consistently whether the question comes from a Genie Agent, a Vector Search query, an agent workflow, or a direct SQL call. Unity Catalog is the single enforcement layer that makes this possible: row filters, column masks, attribute-based access control, all resolved from the caller's identity.
This deck shows you the four layers of data access control and how they compose.
Every securable object lives in a three-level namespace. Permissions propagate downward.
catalog.schema.table -- Example demo_governance.sales.deals
Tables, views, functions, volumes, models, and connections all live inside this hierarchy.
| GRANT on catalog | Applies to all schemas and tables within |
| GRANT on schema | Applies to all tables within |
| GRANT on table | Applies to that table only |
Grants are additive. A GRANT at the catalog level gives access to everything below. There is no implicit access: every identity starts with zero privileges.
Explicit-only model: No access without a GRANT. Owners have full control. Metastore admins can manage all objects.
UC evaluates all four layers in sequence before returning any data. Each answers a different question.
WHERE can they access?
Workspace bindings limit which workspaces see specific catalogs. Isolates dev/staging/prod.
WHO can access WHAT?
GRANTs on securable objects. Hierarchical inheritance. Owners have full control.
WHAT data based on tags?
Governed tags + policy functions enforce access dynamically at scale. Define once, apply to thousands.
WHAT rows/columns?
Row filters, column masks, and dynamic views control visibility at query time per identity.
All four layers evaluate in sequence. The user receives fully governed data: workspace verified, privileges checked, ABAC policies enforced, rows filtered, columns masked, all actions audited.
Control which workspaces can see specific catalogs, external locations, and storage credentials.
| Use case | Restrict production data to production workspaces only |
| Scope | Catalogs, external locations, storage credentials |
| Override | Takes precedence over user-level permissions |
A user with SELECT on a table still cannot access it from a workspace that is not bound to the catalog.
Explicit permission model. No implicit access.
-- Grant read access GRANT SELECT ON TABLE catalog.schema.deals TO `sales_east`; -- Grant at schema level GRANT USE SCHEMA ON SCHEMA catalog.sales TO `all_sales`;
Hierarchical: catalog → schema → table. Owners have full control over their objects.
Row filters automatically restrict which rows a user sees. Applied at query time by the SQL engine.
A SQL function is bound to a table. UC injects the function as a WHERE clause on every query. The user never sees filtered rows and does not know they exist.
-- Create the filter function CREATE FUNCTION sales_filter(region STRING) RETURN is_member('admin') OR region = 'WEST' AND is_member('sales_west'); -- Apply to table ALTER TABLE deals SET ROW FILTER sales_filter ON (region);
| Silent | Filtered rows are invisible. No error, no indication they exist. |
| Automatic | Enforced by the SQL engine on every query. No app code changes. |
| Identity-aware | current_user() and is_member() resolve at query time. |
| Same query | Different users run the same SQL, see different rows. |
One filter per table. If you need multiple filter conditions, combine them in a single function using OR/AND logic.
Column masks transform sensitive values at query time. Users see the row but with masked column values.
A SQL function is bound to a specific column. UC replaces the raw value with the function's output on every query.
-- Create the mask function CREATE FUNCTION mask_ssn(ssn STRING) RETURN CASE WHEN is_member('admins') THEN ssn ELSE '***-**-' || RIGHT(ssn, 4) END; -- Apply to column ALTER TABLE customers ALTER COLUMN ssn SET MASK mask_ssn;
| Use SQL | SQL masks faster than Python UDFs. Prefer deterministic expressions. |
| Minimize args | Fewer arguments = better optimization. Max args: referenced columns still unconstrained. |
| Simple logic | Minimize distinct mask expressions. CASE WHEN over nested function calls. |
| Lookup tables | One per query (optimized out). Keep them small and cached. |
Limitation: Cannot mask generated columns or reference time-travel/clone snapshots. Iceberg REST reads do not apply masks.
Attribute-Based Access Control uses tags to enforce dynamic, scalable policies without per-object permissions.
Account-level tags with enforced naming rules. Applied to catalogs, schemas, tables, columns, volumes, and service objects (model services, MCP services). Tags inherit downward. Reserved tag families include databricks:, class.*, sap.*, system.*, and ai.* (for foundation models like ai.model_creator).
-- Tag a table ALTER TABLE customer_data SET TAGS ('sensitivity' = 'high', 'region' = 'EMEA'); -- Tag a foundation model service ALTER SERVICE my_model SET TAGS ('ai.model_creator' = 'data-science', 'sensitivity' = 'restricted');
Policies reference tags to dynamically control access. One policy can govern thousands of objects without manual per-object configuration. GRANT policies (Beta) dynamically grant privileges like EXECUTE on models based on tag attributes, while DENY policies (Beta) explicitly block access and take precedence over grants.
-- Query tags at runtime
SELECT * FROM system.information_schema
.column_tags
WHERE tag_name = 'sensitivity'
AND tag_value = 'pii';
Tags classify. Masks/Filters enforce. Apply a mask to any column tagged PII; it fires automatically on all queries, all tools.
| 1,000 tables | 1,000 GRANT statements per role. Manual, error-prone. |
| New table | Must remember to add GRANTs. Miss one and data leaks or access breaks. |
| Policy change | Update hundreds of GRANTs. Hope you got them all. |
| Audit | Piecing together who has access requires scanning every object. |
| 1,000 tables | 1 policy + tags. Applied automatically. |
| New table | Tag it. Policy applies instantly. |
| Policy change | Update one policy definition. Instant propagation. |
| Audit | Query tags and policies. Full visibility. |
One policy covers thousands of tables.
Re-tag a table to change access instantly.
Tags + policies = full governance lineage.
Not application code. Not middleware. The SQL engine itself enforces every layer.
Applications run the same SQL. Governance is invisible to the app layer. Add a row filter and every query automatically respects it.
Row filters and column masks are injected by the engine. No SQL trick, no API shortcut, no SDK shortcut can skip them. The only override is ownership or admin privilege.
Every query is logged with the identity that executed it, the governance policies that fired, and the data that was returned. System tables provide the full lineage.
Same table. Same query. Different identity. Different data.
This is the core principle of UC governance. It works identically for Genie Agents, Agent Bricks, MCP servers, notebooks, dashboards, and any tool that executes SQL through a warehouse.
Two identity functions, different propagation behavior. Choose based on your token pattern.
Returns the calling user's email from the active SQL token.
-- Filter: only see your own rows
WHERE owner = current_user()
| OBO | Returns the human's email. Propagates through Genie Agents, Agent Bricks, MCP chains. |
| M2M | Returns the SP's application ID. |
| Best for | Per-user row ownership, individual audit trails. |
Returns TRUE if the executing identity belongs to a workspace group.
-- Filter: role-based access
WHERE is_member('sales_west')
AND region = 'WEST'
| M2M | Evaluates the SP's group membership. Works well when you control the SP. |
| OBO | Evaluates the human's groups. Works as expected. |
| Best for | Role-based filtering, federation patterns. |
OBO pattern: prefer current_user(), which propagates the human identity end-to-end. Federation pattern: use is_member(), where role-based SPs map to workspace groups.
A critical distinction when column masks use is_member() in different token patterns.
When a service principal executes the SQL directly, is_member() evaluates the SP's workspace group membership. You control group assignment explicitly.
-- SP is in 'admins' group is_member('admins') => TRUE -- Full SSN returned
In Genie Agents or Agent Bricks OBO flows, is_member() checks the service layer's groups, not the human's. Use a lookup table instead:
-- Pattern: current_user() + lookup
CASE
WHEN EXISTS (
SELECT 1 FROM access_control.user_roles
WHERE email = current_user()
AND role IN ('admin', 'compliance')
) THEN ssn
ELSE '***-**-' || RIGHT(ssn, 4)
END
Rule: In OBO chains, use current_user() + UC-governed lookup table, never is_member() alone for role-based masking.
Both can apply to the same table. The row filter runs first, then column masks apply to surviving rows.
-- Row filter: region-based access CREATE FUNCTION deals_row_filter(region STRING) RETURN is_member('admin') OR (region = 'WEST' AND is_member('sales_west')) OR (region = 'EAST' AND is_member('sales_east')); ALTER TABLE deals SET ROW FILTER deals_row_filter ON (region); -- Column mask: hide margin from non-finance CREATE FUNCTION mask_margin(val DOUBLE) RETURN CASE WHEN is_member('finance') THEN val ELSE NULL END; ALTER TABLE deals ALTER COLUMN margin_pct SET MASK mask_margin;
| Deal | Region | Amount | Margin |
|---|---|---|---|
| Acme Corp | WEST | $50K | NULL |
| Beta Inc | WEST | $30K | NULL |
EAST rows filtered out. Margin masked.
| Deal | Region | Amount | Margin |
|---|---|---|---|
| Acme Corp | WEST | $50K | 42% |
| Beta Inc | WEST | $30K | 38% |
| East Co | EAST | $45K | 51% |
All rows visible. Real margins shown.
Automated classification tags sensitive data. Your masks and filters decide what to do with those tags.
Agentic AI scans columns and detects PII (email, SSN, credit card, etc). Results written as databricks:classifier:* tags. The results view shows User Access exposure metrics for the last 7 days, revealing how many distinct users accessed masked versus unmasked data by classification type.
-- See classified columns
SELECT catalog_name, table_name,
column_name, tag_value
FROM system.information_schema
.column_tags
WHERE tag_name =
'databricks:classifier:pii_type';
Scans new tables within 24h. Can be automatic or manual review only. Detection exclusions (Beta) let you dismiss false positives, which removes the tag and feeds back to improve future scans.
Classification does not auto-apply masks. After reviewing, you apply masks to sensitive columns.
-- For each classified column
ALTER TABLE my_catalog.hr.employees
ALTER COLUMN ssn
SET MASK my_catalog.masks.pii_mask;
Use ABAC: one mask covers all PII-tagged columns.
Workflow: Classification finds PII → review results → apply masks/row filters → all queries automatically governed.
current_user()is_member()is_member()Most production deployments combine multiple layers. Workspace bindings for environment isolation. GRANTs for baseline access. ABAC for scale. Row filters + column masks for fine-grained control.