Skip to content

fix(orm): parenthesize an inlined computed field expression - #2796

Merged
ymc9 merged 1 commit into
zenstackhq:devfrom
evgenovalov:fix/parenthesize-inlined-computed-field
Aug 14, 2026
Merged

fix(orm): parenthesize an inlined computed field expression#2796
ymc9 merged 1 commit into
zenstackhq:devfrom
evgenovalov:fix/parenthesize-inlined-computed-field

Conversation

@evgenovalov

@evgenovalov evgenovalov commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #2795

Motivation

A computed field is inlined into larger expressions — a boolean filter renders it as <expr> = $n. If the implementation's top-level node is a bare comparison, the operator precedence leaks into the surrounding query:

computedFields: {
    post: {
        isMine: (eb) => eb('authorId', '=', 1),
    },
},
await db.post.findMany({ where: { isMine: true } });
error: syntax error at or near "="
-- before
select "Post"."id" as "id", "authorId" = $1 as "isMine" from "public"."Post" where "authorId" = $2 = $3
-- after
select "Post"."id" as "id", ("authorId" = $1) as "isMine" from "public"."Post" where ("authorId" = $2) = $3

Postgres rejects the chained = outright. SQLite and MySQL parse it left-associatively as (a = $2) = $3, which happens to be the intended semantics, so the bug is invisible there — it surfaced only on the Postgres CI matrix of #2789.

Changes

  • BaseCrudDialect.fieldRef() wraps the implementation's expression in eb.parens() when inlining a computed field.
  • e2e test in computed-fields.test.ts (runs on every provider in the matrix): a bare-comparison field asserted through where: { isMine: true } / { isMine: false } / NOT, plus an eb.or-based field as a no-regression guard.

The wrap is unconditional because Kysely skips it for an expression that is already parenthesized, so eb.or / eb.and and subquery implementations compile byte-for-byte as before; only bare comparisons, unary operations and raw fragments change. Verified against the compiled SQL for each shape.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved filtering for computed fields that use comparisons or combined logical expressions.
    • Ensured computed-field expressions are grouped correctly when used in larger queries.
    • Preserved support for true, false, NOT, and OR filtering behavior.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e19e607e-15de-4b9b-9fc5-53141392c015

📥 Commits

Reviewing files that changed from the base of the PR and between c67c8a2 and 318018f.

📒 Files selected for processing (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • tests/e2e/orm/client-api/computed-fields.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/orm/src/client/crud/dialects/base-dialect.ts
  • tests/e2e/orm/client-api/computed-fields.test.ts

📝 Walkthrough

Walkthrough

The ORM now parenthesizes computed-field expressions before embedding them in larger SQL expressions. End-to-end tests cover direct, negated, and logical boolean filters while confirming computed-field reads.

Changes

Computed field filtering

Layer / File(s) Summary
Parenthesized computed expressions and boolean filter coverage
packages/orm/src/client/crud/dialects/base-dialect.ts, tests/e2e/orm/client-api/computed-fields.test.ts
fieldRef now wraps computed-field expressions in parentheses and continues forwarding query-time arguments. End-to-end tests cover direct, NOT, and OR boolean filters and computed-field reads.

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

Mergeability Score: ⚪ Minimal · up to 31801

The PR makes a localized SQL-expression parenthesization change and adds coverage for the affected computed-field cases; no actionable merge-blocking risk remains beyond normal checks and review.

Possibly related PRs

  • zenstackhq/zenstack#2744: Both PRs modify BaseCrudDialect.fieldRef; this PR adds parenthesization while the related PR adds argument forwarding.
  • zenstackhq/zenstack#2762: This PR builds on related computed-field handling in fieldRef.
  • zenstackhq/zenstack#2789: Both PRs modify computed-field handling in base-dialect.ts; this PR addresses the SQL parenthesization issue.

Suggested reviewers: ymc9

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: parenthesizing inlined computed-field expressions.
Linked Issues check ✅ Passed The implementation and tests satisfy issue #2795 by parenthesizing computed expressions and covering boolean, NOT, OR, and AND filtering.
Out of Scope Changes check ✅ Passed All changes directly support issue #2795 and add focused regression coverage without unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 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/dialects/base-dialect.ts

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


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.

A computed field is inlined into larger expressions — a boolean filter renders it as
`<expr> = $n` — so an implementation whose top-level node is a bare comparison produced
a chained `"authorId" = $2 = $3`. Postgres rejects that as a syntax error; sqlite and
mysql only parse it correctly by accident of left associativity.

Wrap the implementation's expression in parens so its operator precedence stays
contained. Kysely doesn't double-wrap an already parenthesized expression, so `eb.or`
/`eb.and`-based and subquery implementations compile unchanged.

Fixes zenstackhq#2795

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@evgenovalov
evgenovalov force-pushed the fix/parenthesize-inlined-computed-field branch from c67c8a2 to 318018f Compare August 13, 2026 09:37
@ymc9
ymc9 merged commit 1390aa0 into zenstackhq:dev Aug 14, 2026
8 checks passed
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.

Computed field expression is not parenthesized when inlined, breaking boolean filters on Postgres

2 participants