Skip to content

Conversation

@nadilas
Copy link
Contributor

@nadilas nadilas commented Jan 27, 2026

Problem

When Ecto generates an IN clause with a subquery (e.g., WHERE id IN (SELECT ...)), the adapter's catch-all IN handler wraps it in JSON_EACH(), producing invalid SQL:

WHERE (s0."id" IN (SELECT value FROM JSON_EACH(?)))

This causes a malformed JSON error at runtime because the subquery parameters (strings, integers, etc.) are not valid JSON arrays.

Affected use case: Libraries like Oban with the Oban.Engines.Lite engine use UPDATE ... WHERE id IN (subquery) patterns in fetch_jobs/3, which triggers this bug.

Root Cause

The defp expr({:in, _, [left, right]}, sources, query) catch-all clause handles all non-list IN expressions by wrapping the right side in JSON_EACH(). This is correct for tagged arrays (e.g., ~w() sigils) but incorrect for %Ecto.SubQuery{} structs, which should generate inline SQL subqueries.

The Postgres adapter in ecto_sql has a specific pattern match for %Ecto.SubQuery{} (source) that was missing from this adapter.

Before (broken)

WHERE (s0."id" IN (SELECT value FROM JSON_EACH(?)))
-- Parameters: ["asdf-..."] (not a JSON array!)
-- Result: malformed JSON error

After (fixed)

WHERE (s0."id" IN (SELECT ss0s0."id" FROM "oban_jobs" AS ss0s0 WHERE ...))
-- Proper inline subquery with correct aliases

Summary by CodeRabbit

  • Bug Fixes
    • Fixed IN clause handling with subqueries to generate proper SQL queries, eliminating unnecessary JSON processing and improving query reliability.
    • Enhanced subquery expression handling to correctly render inline SQL subqueries within nested query operations.
    • Improved overall database query efficiency by replacing fallback JSON-based processing with dedicated subquery support.

✏️ Tip: You can customize this high-level summary in your review settings.

The catch-all IN clause handler was wrapping SubQuery expressions in
JSON_EACH(), producing invalid SQL like:
  WHERE id IN (SELECT value FROM JSON_EACH(?))

This adds a specific pattern match for %Ecto.SubQuery{} before the
catch-all, generating proper inline subqueries:
  WHERE id IN (SELECT s0.id FROM table AS s0 WHERE ...)

Also adds a general SubQuery expression handler that properly resolves
parent query aliases and combination queries.

This fixes compatibility with libraries like Oban that use subqueries
in UPDATE...WHERE id IN (subquery) patterns.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

Walkthrough

Adds support for IN clauses with subquery operands in the LibSQL adapter by introducing dedicated SubQuery expression handling that generates proper SQL subqueries instead of relying on JSON_EACH for complex right-hand sides.

Changes

Cohort / File(s) Summary
LibSQL IN Clause Subquery Support
lib/ecto/adapters/libsql/connection.ex
Introduces SubQuery expression path for rendering inline SQL subqueries in IN clauses. Adds context propagation via subquery_as_prefix(sources) to handle nested subqueries. Replaces prior fallback behaviour for Ecto.SubQuery inputs. Two instances added indicating duplication pattern in the implementation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • Datatype fixes for lists in IN clauses #65: Also modifies IN-clause rendering in the same file, with changes to unwrapping Tagged list values and IN fallback adjustments that directly interact with this subquery enhancement.

Poem

🐰 A hop, skip, and query refined,
Subqueries nested, perfectly aligned,
No JSON_EACH, just SQL so clean,
The finest IN clauses you've ever seen! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main change: adding proper support for Ecto.SubQuery in IN expressions instead of the previous JSON_EACH fallback behaviour.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

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