Skip to content

fix: translate exists/1 subqueries - #135

Open
l-vincent-l wants to merge 1 commit into
ocean:mainfrom
l-vincent-l:bugfix-exists-subquery
Open

l-vincent-l wants to merge 1 commit into
ocean:mainfrom
l-vincent-l:bugfix-exists-subquery

Conversation

@l-vincent-l

@l-vincent-l l-vincent-l commented Oct 22, 2026 •

Copy link
Copy Markdown

Problem

exists(subquery) has no expr/3 clause and falls through to the catch-all, which renders it as a bare ?:

SELECT s0."name" FROM "test_items" AS s0 WHERE (?)

The next bound parameter then stands in for the whole condition, and SQLite evaluates it as a boolean: depending on its value, the filter matches every row or none, without any error.

I hit this with Ash Framework, whose policies such as exists(memberships, user_id == ^actor(:id)) compile to an exists subquery: they let every actor read every record (or none, depending on the first character of the actor's id). That case also needs correlated parent_as/1 references, fixed separately in #136.

Fix

Translate exists(subquery) to exists(SELECT ...), as ecto_sqlite3 does.

Tests

Added to test/in_expression_comprehensive_test.exs: exists and not exists, each checked both when the subquery matches and when it does not. Both fail on main. mix test --exclude turso_remote: all passing. No Rust changes.

Summary by CodeRabbit

  • Bug Fixes
    • Correlated parent_as/1 references and exists/1 subqueries now generate SQL correctly, avoiding accidental parameter binding and incorrect query results.
    • Positive and negated exists/1 conditions now correctly include or exclude outer query results based on whether the subquery finds a match.
    • Unsupported query expressions now raise an Ecto.QueryError instead of being rendered as placeholders.

exists(subquery) had no expr/3 clause and fell through to the catch-all,
which rendered it as a bare `?`. The next bound parameter then stood in for
the whole condition, so the filter matched every row or none depending on
its value: Ash policies written with exists(...) let every actor through.

It is now translated to exists(SELECT ...), as in ecto_sqlite3.
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 81494d5d-6aed-47a5-88fd-e9cc7d1876eb
📥 Commits

Reviewing files that changed from the base of the PR and between b38f93f and ed619ff.

📒 Files selected for processing: 2
  • CHANGELOG.md
  • test/in_expression_comprehensive_test.exs
🚧 Files skipped from review as they are similar to previous changes: 1
  • CHANGELOG.md

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

Walkthrough

The LibSQL adapter now renders correlated parent_as/1 fields and exists/1 subqueries as SQL. Unsupported expressions raise Ecto.QueryError instead of producing a ? placeholder. Integration tests check positive and negated exists/1 results.

Changes

SQL expression translation

Layer / File(s) Summary
Correlated aliases and exists subqueries
lib/ecto/adapters/libsql/connection.ex, test/in_expression_comprehensive_test.exs, CHANGELOG.md
Parent aliases resolve through parent queries. The adapter renders parent_as/1 fields and exists/1 expressions in SQL. Integration tests check generated SQL and results for matching and non-matching subqueries, including negated exists/1. The changelog describes the exists/1 translation.
Unsupported expression errors
lib/ecto/adapters/libsql/connection.ex
Unsupported expressions now raise Ecto.QueryError with the query and inspected expression instead of producing a ? placeholder.

Priority: ➖ Normal

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

Change: Bug fix

Merge Risk: ⚪ Minimal · up to ed619

The reviewed change addresses ordinary exists subqueries without an identified new merge-blocking issue.

Pre-merge checks: Passed 5
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: translating exists/1 subqueries into SQL exists(SELECT ...) expressions.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

A rabbit checks the SQL at night,
And sees exists rendered right.
Parent aliases find their place,
Unsupported forms now state their case.
Two tests watch the rows appear,
Then the rabbit hops away, clear.

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

@l-vincent-l
l-vincent-l force-pushed the bugfix-exists-subquery branch from b38f93f to ed619ff Compare October 22, 2026 10:20

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.

1 participant