Skip to content

Commit 5dc886a

Browse files
Allow joins with {fragment, Schema} source (#4668)
1 parent 0bbddd7 commit 5dc886a

File tree

3 files changed

+57
-11
lines changed

3 files changed

+57
-11
lines changed

integration_test/cases/joins.exs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Ecto.Integration.JoinsTest do
55
import Ecto.Query
66

77
alias Ecto.Integration.Post
8+
alias Ecto.Integration.Barebone
89
alias Ecto.Integration.Comment
910
alias Ecto.Integration.Permalink
1011
alias Ecto.Integration.User
@@ -162,6 +163,16 @@ defmodule Ecto.Integration.JoinsTest do
162163
assert p2.permalink.id == plid1
163164
end
164165

166+
test "joins with fragment source mapped to schema" do
167+
query =
168+
from f1 in {fragment("select 1 as num"), Barebone},
169+
join: f2 in {fragment("select 1 as visits"), Post},
170+
on: f1.num == f2.visits,
171+
select: {f1, struct(f2, [:visits])}
172+
173+
assert {%Barebone{num: 1} = b1, %Post{visits: 1} = b2} = TestRepo.one(query)
174+
end
175+
165176
## Associations joins
166177

167178
test "has_many association join" do

lib/ecto/query/builder/join.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,18 @@ defmodule Ecto.Query.Builder.Join do
8181
end
8282
end
8383

84+
def escape({{:fragment, _, _} = fragment, schema} = join, vars, env) do
85+
{_, expr, _, _, params} = escape(fragment, vars, env)
86+
87+
case Macro.expand(schema, env) do
88+
schema when is_atom(schema) ->
89+
{:_, {expr, schema}, nil, nil, params}
90+
91+
_ ->
92+
Builder.error!("malformed join `#{Macro.to_string(join)}` in query expression")
93+
end
94+
end
95+
8496
def escape({:assoc, _, [{var, _, context}, field]}, vars, _env)
8597
when is_atom(var) and is_atom(context) do
8698
ensure_field!(field)

test/ecto/query/planner_test.exs

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -949,13 +949,26 @@ defmodule Ecto.Query.PlannerTest do
949949
end
950950

951951
test "plan: tuple source with fragment" do
952-
{query, cast_params, dump_params, cache_key} =
953-
plan(from {fragment("? as num", ^0), Barebone})
952+
query =
953+
from f1 in {fragment("? as num", ^0), Barebone},
954+
join: f2 in {fragment("? as visits", ^0), Post},
955+
on: f1.num == f2.visits,
956+
select: {f1, f2}
957+
958+
{query, cast_params, dump_params, cache_key} = plan(query)
954959

955-
assert {{{:fragment, [], _}, Barebone, nil}} = query.sources
956-
assert cast_params == [0]
957-
assert dump_params == [0]
958-
assert [:all, {:from, {{:fragment, _, _}, Barebone, _, _}, []}] = cache_key
960+
assert {from_source, join_source} = query.sources
961+
assert {{:fragment, [], _}, Barebone, nil} = from_source
962+
assert {{:fragment, [], _}, Post, nil} = join_source
963+
assert cast_params == [0, 0]
964+
assert dump_params == [0, 0]
965+
966+
assert [
967+
:all,
968+
{:join, [{:inner, {{:fragment, _, _}, Post, _, _}, {:==, _, _}, []}]},
969+
{:from, {{:fragment, _, _}, Barebone, _, _}, []},
970+
{:select, {:{}, [], [{:&, [], [0]}, {:&, [], [1]}]}}
971+
] = cache_key
959972
end
960973

961974
test "plan: tuple source with fragment and take" do
@@ -2609,13 +2622,23 @@ defmodule Ecto.Query.PlannerTest do
26092622
end
26102623

26112624
test "normalize: tuple source with fragment" do
2612-
{query, _, _, select} =
2613-
normalize_with_params(from {fragment("? as num", ^0), Barebone})
2625+
query =
2626+
from f1 in {fragment("? as num", ^0), Barebone},
2627+
join: f2 in {fragment("? as num", ^0), Barebone},
2628+
on: f1.num == f2.num,
2629+
select: {f1, f2}
26142630

2615-
%{from: {_, {:source, {{:fragment, _, _}, Barebone}, nil, types}}} = select
2616-
assert types == [num: :integer]
2631+
{query, _, _, select} = normalize_with_params(query)
2632+
2633+
%{from: {_, {:source, {{:fragment, _, _}, Barebone}, nil, from_types}}} = select
2634+
assert from_types == [num: :integer]
26172635
assert {{:fragment, _, _}, Barebone} = query.from.source
2618-
assert query.select.fields == [{{:., [writable: :always], [{:&, [], [0]}, :num]}, [], []}]
2636+
assert [%{source: {{:fragment, _, _}, Barebone}}] = query.joins
2637+
2638+
assert query.select.fields == [
2639+
{{:., [writable: :always], [{:&, [], [0]}, :num]}, [], []},
2640+
{{:., [writable: :always], [{:&, [], [1]}, :num]}, [], []}
2641+
]
26192642
end
26202643

26212644
test "normalize: tuple source with fragment and take" do

0 commit comments

Comments
 (0)