SQLRules pushdown¶
RowGuard defaults to use_sqlrules=True. That is powerful—and the most common
source of “validation isn’t working” confusion.
SQLRules is a separate library that compiles Pydantic field constraints into
SQLAlchemy WHERE expressions. RowGuard depends on sqlrules>=1.0,<2.
What pushdown does¶
When enabled, RowGuard asks SQLRules to compile supported Pydantic
constraints into SQL WHERE clauses.
Example: age: Annotated[int, Field(ge=18)] may become WHERE age >= 18.
Rows that fail those constraints never leave the database, so they never
appear in result.rejected or stream statistics as Python-side rejections.
Pushdown is an optimization, not authorization. Express tenant / ACL filters
as explicit SQL or where= clauses.
Capability matrix (practical)¶
Exact coverage is defined by SQLRules. As a rule of thumb for 0.6.0:
Constraint style |
Typically pushed down? |
Notes |
|---|---|---|
|
Yes |
Common numeric bounds |
|
Often |
Depends on SQLRules + dialect |
|
Sometimes |
Dialect-sensitive |
|
Often |
When mapped to comparable columns |
|
No |
Python-only |
Computed fields / properties |
No |
Python-only |
Cross-field model logic |
No |
Use |
When unsure: run once with use_sqlrules=False and compare rows_read /
rejected, or inspect compile_plan(...).pushdown_plan.
When to use True (default)¶
Production reads where invalid candidates should not waste bandwidth
You only need accepted models
When to use False¶
Debugging validation failures
Auditing / ETL with
on_reject="collect"Tests that assert on
rejectedConstraints SQLRules cannot push
result = rowguard.select(
session=session,
table=users,
model=UserRead,
on_reject="collect",
use_sqlrules=False,
)
print(result.rejected)
Mental model¶
use_sqlrules=True
→ DB filters what it can
→ Pydantic validates remaining rows
→ rejected only for rows that still failed in Python
use_sqlrules=False
→ DB returns candidates without those constraint filters
→ Pydantic validates every returned row
→ rejected reflects Python-side failures