From a9a374ff53426b0085c6be03a95eaad55a379f53 Mon Sep 17 00:00:00 2001 From: Gigitsu Date: Fri, 3 Jul 2026 18:29:21 +0200 Subject: [PATCH 1/2] Fix wrong placeholder numbering when a `from` source is a `{fragment, schema}` tuple --- lib/ecto/query/planner.ex | 5 +++++ test/ecto/query/planner_test.exs | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/lib/ecto/query/planner.ex b/lib/ecto/query/planner.ex index 55a437fbcb..7862c78814 100644 --- a/lib/ecto/query/planner.ex +++ b/lib/ecto/query/planner.ex @@ -1522,6 +1522,11 @@ defmodule Ecto.Query.Planner do {{:fragment, meta, fragments}, acc} end + defp prewalk_source({{:fragment, meta, fragments}, schema}, kind, query, expr, acc, adapter) do + {fragments, acc} = prewalk(fragments, kind, query, expr, acc, adapter) + {{{:fragment, meta, fragments}, schema}, acc} + end + defp prewalk_source({:values, meta, [types, num_rows]}, _kind, _query, _expr, acc, _adapter) do length = num_rows * length(types) # Adapters will use the schema types to cast the values diff --git a/test/ecto/query/planner_test.exs b/test/ecto/query/planner_test.exs index 93be9b9eab..fead56601e 100644 --- a/test/ecto/query/planner_test.exs +++ b/test/ecto/query/planner_test.exs @@ -988,6 +988,26 @@ defmodule Ecto.Query.PlannerTest do ] = cache_key end + test "plan: tuple source with fragment numbers later placeholders after the source" do + good_query = + from(f in fragment("widgets_since(?)", ^"as_of"), + where: f.visits in ^[1, 2], + select: f + ) + |> normalize() + + assert Macro.to_string(hd(good_query.wheres).expr) == "&0.visits() in ^(1, 2)" + + bad_query = + from(f in {fragment("widgets_since(?)", ^"as_of"), Post}, + where: f.visits in ^[1, 2], + select: f + ) + |> normalize() + + assert Macro.to_string(hd(bad_query.wheres).expr) == "&0.visits() in ^(1, 2)" + end + describe "plan: CTEs" do test "with uncacheable queries are uncacheable" do {_, _, _, cache} = From 8be426899b34a6b90d49d632868def68a84a4d5b Mon Sep 17 00:00:00 2001 From: Gigitsu Date: Fri, 3 Jul 2026 18:43:51 +0200 Subject: [PATCH 2/2] reword --- test/ecto/query/planner_test.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/ecto/query/planner_test.exs b/test/ecto/query/planner_test.exs index fead56601e..e6fd4b9179 100644 --- a/test/ecto/query/planner_test.exs +++ b/test/ecto/query/planner_test.exs @@ -990,7 +990,7 @@ defmodule Ecto.Query.PlannerTest do test "plan: tuple source with fragment numbers later placeholders after the source" do good_query = - from(f in fragment("widgets_since(?)", ^"as_of"), + from(f in fragment("some_sql_function(?)", ^"value"), where: f.visits in ^[1, 2], select: f ) @@ -999,7 +999,7 @@ defmodule Ecto.Query.PlannerTest do assert Macro.to_string(hd(good_query.wheres).expr) == "&0.visits() in ^(1, 2)" bad_query = - from(f in {fragment("widgets_since(?)", ^"as_of"), Post}, + from(f in {fragment("some_sql_function(?)", ^"value"), Post}, where: f.visits in ^[1, 2], select: f )