From 5a08eaf742342b181a270db121495fbec5e02407 Mon Sep 17 00:00:00 2001 From: nadilas Date: Tue, 27 Jan 2026 20:17:49 +0100 Subject: [PATCH] fix: handle Ecto.SubQuery in IN expressions 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. --- lib/ecto/adapters/libsql/connection.ex | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/ecto/adapters/libsql/connection.ex b/lib/ecto/adapters/libsql/connection.ex index 1d296ee..52bd8db 100644 --- a/lib/ecto/adapters/libsql/connection.ex +++ b/lib/ecto/adapters/libsql/connection.ex @@ -1249,6 +1249,11 @@ defmodule Ecto.Adapters.LibSql.Connection do [expr(left, sources, query), " IN (", args, ?)] end + # IN with subquery - generate proper SQL subquery instead of JSON_EACH + defp expr({:in, _, [left, %Ecto.SubQuery{} = subquery]}, sources, query) do + [expr(left, sources, query), " IN ", expr(subquery, sources, query)] + end + # Catch-all for IN with non-list right side (e.g. Ecto.Query.Tagged from ~w() sigil, JSON-encoded arrays) # This handles cases where the right side has been pre-processed or wrapped by Ecto defp expr({:in, _, [left, right]}, sources, query) do @@ -1317,6 +1322,18 @@ defmodule Ecto.Adapters.LibSql.Connection do expr(arg, sources, query) end + # SubQuery expression - generates inline SQL subquery + defp expr(%Ecto.SubQuery{query: query}, sources, parent_query) do + combinations = + Enum.map(query.combinations, fn {type, combination_query} -> + {type, put_in(combination_query.aliases[@parent_as], {parent_query, sources})} + end) + + query = put_in(query.combinations, combinations) + query = put_in(query.aliases[@parent_as], {parent_query, sources}) + [?(, all(query, subquery_as_prefix(sources)), ?)] + end + # Literal values (numbers, strings, etc.) defp expr(literal, _sources, _query) when is_number(literal) do to_string(literal)