Skip to content

fix(orm): pass the model alias to $expr filters - #2864

Open
Natansal-weme wants to merge 1 commit into
zenstackhq:devfrom
Natansal-weme:fix/expr-relation-filter-alias
Open

Natansal-weme wants to merge 1 commit into
zenstackhq:devfrom
Natansal-weme:fix/expr-relation-filter-alias

Conversation

@Natansal-weme

@Natansal-weme Natansal-weme commented Oct 1, 2026 •

Copy link
Copy Markdown

Fixes #2863

Relation filters select the related model under a generated alias (e.g. "User" as "$$_Post$author"), but $expr only receives the expression builder, so it can't qualify references to the filtered model. A qualified reference like 'User.email' type-checks and then fails with missing FROM-clause entry for table "User".

$expr now gets a second context argument with modelAlias, mirroring the context computed field implementations already receive:

await db.post.findMany({
    where: {
        author: {
            $expr: (eb, { modelAlias }) => eb(eb.ref(`${modelAlias}.email`), 'like', '%@zenstack.dev'),
        },
    },
});
  • ExprFilterContext<Schema, Model> types modelAlias as the model name, matching the expression builder's scope, so eb.ref(`${modelAlias}.field`) type-checks without casts. At runtime it holds the real alias.
  • The change is additive: existing (eb) => ... callbacks keep working, including unqualified references.

Tests

  • tests/regression/test/issue-2863.test.ts: top-level, to-one and to-many relation filters using modelAlias, plus unqualified references in a relation filter. Passes on SQLite and PostgreSQL.
  • tests/e2e/orm/client-api/find.test.ts: the same qualified references with a typed client, to cover the typing.
  • tests/e2e/orm/client-api on SQLite: everything passes except the two MySQL timezone tests, which need a MySQL server I don't have locally.

Summary by CodeRabbit

  • New Features
    • $expr filters now provide the current model alias as a second callback argument, enabling qualified field references in top-level and relation filters.
  • Tests
    • Added coverage for alias-qualified and unqualified expression filters across top-level and related records.

Relation filters select the related model under a generated alias, so a
`$expr` that qualifies references with the model name fails with a
missing FROM-clause entry. `$expr` now receives a context with
`modelAlias`, typed as the model name like the expression builder scope.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
CLAUDE.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: zenstackhq/zenstack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: f5268008-fea3-46e5-96f8-d5530aaa7ce7

📥 Commits

Reviewing files that changed from the base of the PR and between e7ba2fa and 5c90ef5.

📒 Files selected for processing (4)
  • packages/orm/src/client/crud-types.ts
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • tests/e2e/orm/client-api/find.test.ts
  • tests/regression/test/issue-2863.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

$expr callbacks now receive the filtered model’s alias as a typed context argument. The filter implementation passes the alias to callbacks. Tests cover qualified references in top-level and relation filters, and unqualified references in a relation filter.

Changes

$expr model alias context

Layer / File(s) Summary
Typed context, callback invocation, and filter coverage
packages/orm/src/client/crud-types.ts, packages/orm/src/client/crud/dialects/base-dialect.ts, tests/e2e/orm/client-api/find.test.ts, tests/regression/test/issue-2863.test.ts
ExprFilterContext types the modelAlias provided to $expr callbacks. buildFilter passes the current alias. Tests cover top-level, to-one, and to-many relation filters, including unqualified references.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: ymc9

Merge Risk: ⚪ Minimal · up to 5c90e

The alias context enables qualified expressions in relation filters while retaining existing one-argument callbacks. No actionable merge-blocking issue was identified; the change is mergeable subject to normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 5c90e

The change exposes the current query alias without adding database privileges. The inspected relation filters retain their correlation constraints. No introduced security issue was identified in these paths, but broader authorization coverage remains incomplete.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is query-predicate construction for callers already able to supply executable $expr callbacks. The change enables qualification of the active relation scope; the inspected code does not introduce a separate service, credential, or database-access capability.

Trust Boundaries and Controls

  • inferred — Exposing the active alias does not itself remove relation containment: to-one and to-many paths retain their join or primary-key/foreign-key correlation and EXISTS wrappers. These query constraints are not evidence of complete tenant or authorization enforcement outside the inspected dialect.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: passing the model alias to $expr filters.
Linked Issues check ✅ Passed Issue [#2863] requires a second $expr callback context with modelAlias, support for qualified references in relation filters, and preservation of existing one-argument callbacks. crud-types.ts a…
Out of Scope Changes check ✅ Passed The reported changes are limited to the $expr context type, the runtime callback invocation, and tests for issue [#2863]. The type and test changes directly support the requested alias behavior. No …
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/orm/src/client/crud-types.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

packages/orm/src/client/crud/dialects/base-dialect.ts

ESLint skipped: the matched ESLint configuration already failed (missing-dependency).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

$expr inside relation filters can't reference the filtered model (missing FROM-clause entry)

1 participant