DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk7 min

Your Search Query Is a Program: Composing Role-Based SQL With the Strategy Pattern

An unparenthesized OR in a search filter can bypass role-based visibility. Here is how to compose visibility rules and optional filters as separate Strategy families in Java and Spring JDBC.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Search code that serves different roles and also accepts optional filters often concatenates everything into one WHERE clause, and that is where visibility rules leak. The design in Paolo’s DEV Community article, posted September 26, 2026, separates two questions: what a user is allowed to see, and what the user asked to find. One visibility strategy, chosen by role, is required for every search. Optional filter contributors add their own predicates. A builder combines everything with bound parameters and refuses to produce a query that has no visibility decision. The example is written in Java with Spring JDBC against SQL Server.

Why an OR in a filter can undo role-based visibility

Start with the failure the design is meant to prevent. Suppose a local officer’s visibility rule is unit.id = :unitId, and a region filter is appended as a plain string: unit.id = :regionId OR unit.parent_id = :regionId. Simplified to show the mechanism, the combined WHERE clause becomes:

WHERE unit.id = :unitId AND unit.id = :regionId OR unit.parent_id = :regionId

SQL evaluates AND before OR, so this reads as (unit.id = :unitId AND unit.id = :regionId) OR unit.parent_id = :regionId. The second branch carries no visibility check at all. The article reports that its local-officer case returned documents from another region. Read on its own, the filter looks harmless, which is why the protection has to live in the builder rather than in code review.

Two decisions, two families of strategies

The design treats access and search as separate axes. The article puts each one into its own family of strategies:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Visibility strategy. Exactly one per search, selected by the user’s role. It is required, so the search cannot be built without one.
  • Filter contributor. Zero or more per search, one per optional criterion. Each contributes its own predicate, its parameters and, where needed, joins or CTEs. No contributor owns the whole query.

The author puts the pattern this way: “The pattern is Strategy, used twice: one family of strategies decides what a user may see, the other what the user asked for, and neither writes the whole query.”

The article also frames the problem as a question of invariants: “A search with optional filters and role-based visibility is application logic, and one of its invariants is a security boundary.” Treating visibility as an invariant rather than as one more optional filter is what keeps it from being dropped.

The visibility policy by role

The example defines five roles. Each role’s rule becomes one visibility strategy.

Role What the visibility strategy allows
LOCAL_OFFICER Their own unit.
REGIONAL_SUPERVISOR The region and its local offices, plus chartered units only during an active, explicit delegation.
NATIONAL_ADMIN All documents.
AUDITOR Approved or archived documents across units.
DELEGATE Only units with an active delegation.

These role names belong to the example. Your own policy will have different roles, but the same split applies: a rule per role for what may be seen, and nothing else in that rule.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The ten optional filters

The example adds ten filters that a user can activate on top of the visibility scope:

  • Region
  • Unit
  • Type
  • Status
  • Date range
  • Attachments
  • Author
  • Title
  • Tag
  • Overdue

How a search is assembled

A search runs through the same sequence each time, whatever filters are active:

  1. Resolve the user’s scope from their role and any delegations.
  2. Create one search context. It holds a single resolved date, so every date-dependent rule in the request reads the same value.
  3. Apply exactly one visibility strategy for the user’s role.
  4. Apply each filter contributor whose criterion is active in the request.
  5. Let the builder compose the joins, CTEs, predicates, parameters, selected columns and ordering into one statement.

Because the active filters change from request to request, each combination of filters produces its own SQL text. The article contrasts this with a single fixed statement that tries to handle every optional criterion at once.

Failing closed for roles without a visibility rule

The registry that maps roles to visibility strategies rejects any role that has no strategy, and the builder refuses to produce a query when no visibility strategy has made a decision. The article tests this with an unhandled EXTERNAL_REVIEWER role. The composed approach threw an error instead of returning every document. A missing rule becomes a visible failure in development, not a silent widening of access in production.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Security details in the builder

Bind values, and treat the fragment check as a tripwire

Values travel as bound parameters and never as concatenated text. The builder also rejects certain characters inside fragments. The article describes this check as a tripwire that catches accidental mistakes, not as proof against unsafe SQL. Bound parameters are the protection; the character check only surfaces mistakes earlier.

Map sort names through a whitelist

SQL identifiers such as column names cannot be bound as parameters. The sort field is therefore chosen from a fixed list of names, each mapped to a known column, and a user-supplied string is never placed into the ORDER BY clause.

Parenthesize every fragment

Each fragment, especially one containing OR, is wrapped in parentheses before it is ANDed with the others. The leak at the top of this article came from operator precedence, so this is the direct fix.

Reject conflicting parameter names

Two contributors may use the same parameter name only when they pass the same value. The builder rejects a duplicate name whose new value differs, which stops one contributor from silently overwriting a value another contributor depends on.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Resolve “today” once

The search context stores one resolved date. The visibility scope and the overdue filter both read that value, so a request evaluated just before and just after midnight cannot apply two different dates.

Escape LIKE wildcards

Binding a value does not change how LIKE reads wildcard characters. A user who types % or _ into a title search widens the match unless the pattern is escaped. The article’s SQL Server example escapes %, _ and [ in patterns.

Select sensitive columns only where they are needed

The article selects author email only in the national-admin scope, rather than fetching it for every user and hiding it later. A value that is never fetched for other roles cannot be exposed by a later mistake in the display layer.

Performance: measure rather than assume

Generating a separate SQL text for each filter combination can look like a cost. The article does not claim this design is faster. It notes that SQL Server 2025’s Optional Parameter Plan Optimization handles optional predicates through plan variants, and it says performance with ten optional predicates should be measured rather than assumed. Plan caching depends on your server version and configuration, so check the plans your own workload produces.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Alternatives compared

The article compares the options on five axes: whether predicates form a structured tree that handles precedence, control over SQL and database-specific features, ORM and entity requirements, where authorization is enforced and how visible that is during review, and operational or license cost.

Approach Predicates form a structured tree SQL and dialect control Entity requirement Where authorization lives Cost noted by the article
Parenthesized direct SQL (the example’s approach) Only if each fragment is parenthesized by hand or by the builder Full None; Spring JDBC with records In the query builder, visible in the generated SQL Not stated
Spring Data Specifications / JPA Criteria API Yes; predicates compose structurally, which prevents this precedence leak Standard Criteria has limitations for the example’s CTE needs JPA entities required Not stated Not stated
jOOQ Yes; conditions are rendered from an AST CTEs, window functions and SQL Server dialect features supported Code generation step added Not stated Commercial license required for SQL Server use
SQL Server Row-Level Security Not stated; a filter predicate applies to every query Enforced in the database, including for ad-hoc reports Not stated In the database; session context must be set on connection checkout Not stated

The article says jOOQ would be the first option it evaluates for a new project. It treats Row-Level Security as a second line of defense, because a database-side rule makes visibility harder to see in application SQL and harder to test.

When parent and child conditions stop being enough

The example’s hierarchy check, unit.parent_id = :regionId, assumes a three-level hierarchy. If units can nest deeper than that, a single parent comparison misses descendants. The article names a closure table or a recursive CTE as the ways to handle descendant lookup in deeper trees. Either one moves the hierarchy logic out of the simple parent comparison and into a structure built for it.

Choosing the abstraction for your problem

The article’s guidance is conditional, and the conditions are the useful part:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • One role, a few filters and a small internal audience: a straightforward parenthesized query with tests can be enough. The tests should check absence as well as presence, meaning which documents a user must not see, not only the ones they should.
  • Many visibility cases, filters that keep arriving, or a leak with serious consequences: the composed design earns the extra structure it adds.

What the article establishes, and what it does not

Paolo’s article presents a demo and a design proposal. It does not prove that this architecture is always the safest or the fastest option. Its test figures are the author’s own reported results, not independently reproduced numbers:

  • The authorization matrix covers 21 documents and 7 users, run against both implementations for 294 cases.
  • Characterization testing compares both implementations for 20 criteria combinations for every user.

The demo’s stated environment is Java 21, Spring Boot 4.1.1, Spring Framework 7.0.9, Flyway 12.4.0, Testcontainers 2.0.5, Microsoft JDBC Driver for SQL Server 13.4.0 and SQL Server 2025 CU9. These are the versions the article names for its example, not a statement about the latest releases of each component.

The demo uses Spring JDBC with NamedParameterJdbcTemplate and records, with no JPA.

|

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.