Raw SQL & RLS Gate
When the builder can’t express what you need, selectExpr and whereExpr let
you drop to raw SQL. These hatches are guarded against injection.
selectExpr
Section titled “selectExpr”Add a raw expression to the SELECT list:
const rows = await db .from(users) .selectExpr("COUNT(*)", [], "cnt") .selectExpr("MAX(age)", [], "max_age") .select();The third argument is the output alias.
whereExpr
Section titled “whereExpr”Add a raw fragment to the WHERE clause. Bind values as params — never interpolate them:
const rows = await db .from(users) .whereExpr("EXISTS (SELECT 1 FROM posts WHERE posts.user_id = users.id)") .select();The guard
Section titled “The guard”selectExpr / whereExpr reject any expression containing ;, --, or /*
at build time. Those are the patterns that enable SQL injection (statement
termination, line comment, block comment). A rejected expression throws
MountError("VALIDATION", ...).
RLS enforcement gate
Section titled “RLS enforcement gate”When a Driver is in enforceRls mode with a registry protecting a table, the
builder throws MountError("FORBIDDEN") if you query that table without
applying a policy.
Apply a policy
Section titled “Apply a policy”import { applyPolicy, allowOwner } from "@mountsqli/auth";
const scoped = applyPolicy(db.from(files), allowOwner("ownerId"), { userId });await scoped.select(); // safe — policy injected as WHEREOpt out with .unsafe()
Section titled “Opt out with .unsafe()”.unsafe() tells the gate you accept the risk:
await db.from(files).unsafe().select(); // bypasses RLS enforcementInject filters directly
Section titled “Inject filters directly”withFilters(FilterNode[]) injects compiled policy filters without going
through applyPolicy:
const scoped = db.from(files).withFilters(applyPolicy(policy, ctx));Real-world example
Section titled “Real-world example”A guarded report query with a raw aggregate, under RLS:
export async function userReport(userId: string) { return applyPolicy(db.from(files), allowOwner("ownerId"), { userId }) .selectExpr("COUNT(*)", [], "fileCount") .select();}Best practices
Section titled “Best practices”- Prefer builder methods; use raw SQL only when necessary.
- Bind every dynamic value as a param in
whereExpr. - Apply RLS policies at the edge, not per handler.
Common mistakes
Section titled “Common mistakes”- Putting
;in a raw expression — the guard rejects it. - Querying a protected table in enforce mode without
applyPolicyor.unsafe().
Related
Section titled “Related”- Filtering — prefer
where()overwhereExpr. - RLS Policy DSL — build the policies injected here.
- Error Handling —
MountErrorcodes.
