DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Using AUTHID Parameter in Oracle PL/SQL
  • Enhancing SQL Server Security With AI-Driven Anomaly Detection
  • Data Privacy and Security: A Developer's Guide to Handling Sensitive Data With DuckDB
  • Strengthening Cloud Environments Through Python and SQL Integration

Trending

  • The Self-Hosting Math Changed While We Were Still Arguing About Sovereignty
  • A Non-Expiring AKS Deployment Token Could Read Secrets Across Three Namespaces
  • Guess the Speed: Building a Family Internet Speed Guessing Game
  • After the AI Commit: What Eight Retrospectives Taught Me About Working With Coding Agents

Draft This article hasn't been published, so readers can't see it.

In moderation This article is waiting for review, so readers can't see it yet.

Deleted This article has been deleted, so readers can't see it.

  1. DZone
  2. Data Engineering
  3. Databases
  4. Scaling Row-Level Security With ABAC on Databricks Unity Catalog

Scaling Row-Level Security With ABAC on Databricks Unity Catalog

Learn how to scale row-level security in Databricks Unity Catalog using a tag-driven ABAC pattern that reduces maintenance and simplifies onboarding.

By 
Sriram Vadlamani
·
Jul. 20, 26 · Analysis
Post on X (opens in a new tab) Share on LinkedIn (opens in a new tab)
2.8K Views

Join the DZone community Save articles, join the discussion, and download Refcards and Trend Reports with a free account.

Join for free Already a member? Log in

Onboarding a new table into row-level security should be four lines of metadata. Not two new objects, a code review, and a platform-team ticket. This post describes a tag-driven attribute-based access control (ABAC) pattern built on Databricks Unity Catalog primitives that achieves the objective of one UDF per filter shape, one policy per shape, and a single control table that drives all per-group authorization logic.

I work as a solutions architect with large enterprises running hundreds of tables across multiple regions, product lines, and source systems, where row-level security follows a pattern: Group A sees records from System X. Group B sees the regions China and India. Group C sees plant key 333. Group D combines two source systems. Group E sees everything except specific values. The domain (finance, healthcare, etc.) doesn't matter. The pattern remains the same.

A traditional implementation looks like this:

  1. One row-filter UDF per table
  2. One row-filter policy per (table and group) combination
  3. A SQL query layer that joins every table to an identity mapping table

Every new table required writing two new objects (UDF + policy) and updating every existing group definition. Every new group required touching every UDF. Onboarding a new product line meant rebuilding the whole machine. Hundreds of tables × dozens of groups = a maintenance nightmare.

Instead, what they needed was a pattern where:

  • A new table joins the RLS scheme with only metadata changes — no new UDFs, no new policies
  • A new business group is just a data write — no DDL, no code review
  • Misconfigured rules fail closed, not open

This post describes the four-layer pattern we landed on. It's built entirely on Unity Catalog ABAC primitives (governed tags, row-filter policies, attribute-based binding) and a single control table that drives per-group filter logic.

ABAC-based row-level security workflow in Databricks Unity Catalog

The Four Layers

Each layer does exactly one thing and one thing only:

  • Layer 1: Tags are declarative metadata. A table-level tag declares which shape a table is. A column-level tag tells the row filter which physical column corresponds to which logical attribute. All tags do is describe the data. They facilitate the action for the next layers.
  • Layer 2: UDF is the decision logic. Given a row's attribute values, it returns a boolean answer of TRUE or FALSE for current_user(). It doesn't know anything about which table it's filtering; it just answers "is this row visible?"
  • Layer 3: Policy is the binding. It says, "for tables tagged with shape X, call UDF Y with these columns." It uses tag-matching expressions so it auto-attaches to new tables as they're tagged. This is what enables us to avoid per-table DDL. 
  • Layer 4: Row-Level Security is what the customer experiences. Their SQL doesn't change; filtered rows just come back.

Layer 1 + 2: Tags Describe, UDFs Decide

Two governed tags do all the work. A rls_tag on the table says "this is a table_1_filter shape." An rls_attr tag on each column says "this column is the src_sys_cd attribute" or "this column is the region attribute." The column tag is the load-bearing piece — it lets you have a column literally named ws_region_cd and still have the policy treat it as the logical region attribute. Physical naming is decoupled from policy semantics.

One UDF per table shape. A "shape" is a set of filterable columns. In this customer's setup, there are two shapes:

  • Table 1 shape: (src_sys_cd, plant_key, region) — three attributes. Maybe it's best to call this shape a combination of the keys. For example, all the tables that have src_sys_cd + plant_key+region fall under this shape.
  • Table 2 shape: (src_sys_cd, order_key) — two attributes

Each shape has its own UDF (rf_table_1, rf_table_2). The UDF signature takes one parameter per filterable column. The body joins a user_group_control_table (one row per group rule) to a user_group_membership mapping (or, in production, is_account_group_member()) and applies BOOL_OR across the rules — a union grant.

The key property of this UDF design: it doesn't know which table is calling it. It just answers, given attribute values, "does current_user() get this row?" That's what lets the same UDF serve many tables of the same shape.

Layer 3: The Policy that ties it all together

The policy is the only object in the system that knows about both tags and UDFs. Its four clauses each answer a separate question:

clause question it answers

`ON SCHEMA …`

Where does this policy live? (Schema-scoped — broad reach.)

`WHEN has_tag_value('rls_tag', '…')`

Which tables should it attach to? Anything tagged with the right shape.

`MATCH COLUMNS … has_tag_value('rls_attr', '…')`

Inside each table, which physical column maps to which logical attribute?

`ROW FILTER … USING COLUMNS (…)`

Which UDF to call, and in what argument order?


The auto-attachment behavior is what makes the pattern scale. The policy doesn't enumerate tables — it matches them by tag. Tag a new table tomorrow, and the policy applies to it on the next query. Zero policy edits.

Query Time: What Actually Happens

When a user runs SELECT * FROM table_1, here's what Unity Catalog does behind the scenes:

What Unity Catalog does when user runs SELECT * FROM table_1


  1. The planner sees the table and looks up policies attached to its schema. It finds rls_policy_t1.
  2. The policy's `WHEN` clause checks the table tag. Does table_1 have rls_tag=table_1_filter? Yes → the policy attaches. (If no, the policy is skipped for this table.)
  3. The policy's `MATCH COLUMNS` resolves attributes. For each logical attribute name, it scans column tags to find the physical column with that role: src_sys_cd → physical column src_sys_cd; plant_key → physical column plant_key; region → physical column region.
  4. The query is rewritten to append WHERE rf_table_1(src_sys_cd, plant_key, region) = TRUE. The customer's original SQL is unchanged.
  5. The UDF runs per row. It joins the control table to membership for current_user(), evaluates each rule, and BOOL_ORs the results — TRUE if any rule grants the row, FALSE otherwise.
  6. The engine emits only the `TRUE` rows. The customer sees only their authorized subset. They never see the UDF call or the policy mechanics.

The whole thing is transparent to the application — same SQL, filtered result.

The Scale Payoff

The reason to build the pattern this way only becomes obvious when you onboard the second, third, and hundredth table.

Adding a New Table to an Existing Shape

A new dimension table arrives that fits the Table 1 shape. The work to bring it under RLS:

Step effort shape

Create the table (customer's normal DDL)

—

Tag the table with the shape

1 line of DDL

Tag the columns with their logical roles

3 lines of DDL

Update the UDF?

None

Update the policy?

None

Update the control table?

**None** (existing groups apply automatically through their existing rules)


Four ALTER lines. That's the whole onboarding cost.

Adding a New Business Group

A new business group needs access to a specific slice of the data:

step effort

`INSERT` one row into `user_group_control_table`

1 INSERT

Add users to the AD group (outside Databricks)

—

Update the UDF?

None

Update the policy?

None

Update any table tags?

None


One INSERT. Adding a new group is a data write, not DDL — which means operations teams can self-serve through their normal change-management process, without code review or platform-team involvement.

GitHub repo: https://github.com/vbablue/databricks-abac-rls-demo/tree/main

Data definition language security sql

Opinions expressed by DZone contributors are their own.

Related

  • Using AUTHID Parameter in Oracle PL/SQL
  • Enhancing SQL Server Security With AI-Driven Anomaly Detection
  • Data Privacy and Security: A Developer's Guide to Handling Sensitive Data With DuckDB
  • Strengthening Cloud Environments Through Python and SQL Integration

Partner Resources

×

Comments

Loading comments

The comments didn't load

Please try again in a moment.

Likes

Loading

The next likes didn't load. Please try again.

The likes didn't load

Please try again in a moment.

See who liked this

Log in or join DZone for free to see who liked this article.

Log in Join for free
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook