FREE STUDY NOTES · DP-800

Row-level security in SQL Server and Azure SQL: filter and block predicates

How to build row-level security with an inline predicate function and a security policy, and how filter and block predicates differ.

From Ultra Transcenders DP-800 by Tony Rough (coming December 2026)

Row-level security (RLS) restricts access to rows based on the caller’s identity or execution context, with the logic held in the database rather than the application. It’s built from an inline table-valued function (the predicate function) bound to tables by a security policy. It’s available in SQL Server, Azure SQL Database, Azure SQL Managed Instance, SQL database in Fabric, and Fabric Warehouse and SQL analytics endpoint. Figure 7.2 traces a read and an insert through the policy built in this section.

An app connected as one shared user sets TenantId 42 with sp_set_session_context. Its reads pass through the filter predicate and its inserts through the AFTER INSERT block predicate. Both predicates call an inline table-valued function that compares SESSION_CONTEXT with each row's TenantId. Tenant 42 rows come back, tenant 7 rows are silently filtered, and an insert of a tenant 7 row is rejected.
Figure 7.2: A row-level security policy for a shared application user

Filter versus block predicates

Predicate Applies to Effect
Filter Reads by SELECT, UPDATE, DELETE Rows silently disappear; the app isn’t told
Block AFTER INSERT New rows Insert fails if the new row violates the predicate
Block AFTER UPDATE Updated rows Prevents updating a row to a value that violates the predicate
Block BEFORE UPDATE Existing rows Prevents updating rows that currently violate it
Block BEFORE DELETE Existing rows Prevents deleting rows that violate it

A filter predicate alone still lets users insert rows they then can’t see, and update a visible row so that it becomes filtered; add block predicates to stop that. The optimiser doesn’t check an AFTER UPDATE block predicate when the columns it uses weren’t changed. Block predicates apply to BULK INSERT too. The RLS page states that Microsoft Fabric and Azure Synapse Analytics support filter predicates only; that note sits beside the Warehouse example, while the Fabric SQL database feature list simply marks RLS as supported, so test block predicates there before relying on them.

Building a policy for a shared application user

When a middle tier connects as one database user, it can store the end user’s identity with sp_set_session_context and the predicate can read it with SESSION_CONTEXT. @read_only = 1 stops the value changing until the connection closes.

CREATE SCHEMA Security;
GO
CREATE FUNCTION Security.fn_TenantPredicate(@TenantId int)
RETURNS TABLE WITH SCHEMABINDING
AS RETURN SELECT 1 AS ok
   WHERE DATABASE_PRINCIPAL_ID() = DATABASE_PRINCIPAL_ID('AppUser')
     AND CAST(SESSION_CONTEXT(N'TenantId') AS int) = @TenantId;
GO
CREATE SECURITY POLICY Security.TenantFilter
    ADD FILTER PREDICATE Security.fn_TenantPredicate(TenantId) ON dbo.Orders,
    ADD BLOCK PREDICATE Security.fn_TenantPredicate(TenantId) ON dbo.Orders AFTER INSERT
    WITH (STATE = ON);
-- In the application, after opening the connection:
EXEC sp_set_session_context @key = N'TenantId', @value = 42, @read_only = 1;

STATE = ON enables the policy; ALTER SECURITY POLICY ... WITH (STATE = OFF) disables it. Data API builder can pass token claims into SESSION_CONTEXT in the same way (see “Secure access, auditing and securing endpoints”).

Permissions and behaviour

Cross-feature effects

RLS and DDM together

RLS decides which rows come back; DDM changes how column values in those rows are displayed. A filtered-out row is never masked because it’s never returned, and a returned row shows masked columns unless the caller holds UNMASK.

Common trap: Adding only a filter predicate and assuming users can’t write other tenants’ rows - filter predicates don’t stop inserts. Add an AFTER INSERT (and, where needed, AFTER UPDATE) block predicate, or deny updates to the tenant column.

Common trap: Expecting db_owner to bypass a security policy - policies filter or block rows for dbo, db_owner and table owners as well; exemptions must be written into the predicate function.

Get the whole book

This note is one section of Ultra Transcenders DP-800: Developing AI-Enabled Database Solutions, an independent study guide that explains every topic the exam covers by technology, with comparison tables, diagrams and the common traps, plus a glossary linked to Microsoft Learn.

Amazon.co.ukKindle: coming soonPaperback: coming soon
Amazon.comKindle: coming soonPaperback: coming soon

Due on Amazon in December 2026, in Kindle and paperback editions.

About the book · DP-800 terms in the glossary · All DP-800 study notes

More DP-800 study notes