Skip to content

Commit 3ab6a20

Browse files
Allow fragment sources mapped to schemas (#4664)
1 parent d715d59 commit 3ab6a20

File tree

5 files changed

+47
-2
lines changed

5 files changed

+47
-2
lines changed

lib/ecto/query/builder/from.ex

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ defmodule Ecto.Query.Builder.From do
5959

6060
^query ->
6161
case query do
62-
{left, right} -> {left, Macro.expand(right, env)}
62+
{left, right} -> {escape_source(left, env), Macro.expand(right, env)}
6363
_ -> query
6464
end
6565

@@ -118,6 +118,10 @@ defmodule Ecto.Query.Builder.From do
118118
{:ok, prefix} = prefix || {:ok, nil}
119119
{query(prefix, fragment, params, as, hints, env.file, env.line), binds, 1}
120120

121+
{{{:{}, _, [:fragment, _, _]} = fragment, params}, schema} when is_atom(schema) ->
122+
{:ok, prefix} = prefix || {:ok, nil}
123+
{query(prefix, {fragment, schema}, params, as, hints, env.file, env.line), binds, 1}
124+
121125
{{:{}, _, [:values, _, _]} = values, prelude, params} ->
122126
{:ok, prefix} = prefix || {:ok, nil}
123127
query = query(prefix, values, params, as, hints, env.file, env.line)

lib/ecto/query/inspect.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ defimpl Inspect, for: Ecto.Query do
165165
"values (#{Enum.join(fields, ", ")})"
166166
end
167167

168-
defp inspect_source(%{source: {source, schema}}, _names) do
168+
defp inspect_source(%{source: {source, schema}} = part, names) do
169+
source = if is_binary(source), do: source, else: "#{expr(source, names, part)}"
169170
inspect(if source == schema.__schema__(:source), do: schema, else: {source, schema})
170171
end
171172

lib/ecto/query/planner.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,10 +323,18 @@ defmodule Ecto.Query.Planner do
323323
when kind in [:fragment, :values],
324324
do: {expr, source}
325325

326+
defp plan_source(_query, %{source: {{:fragment, _, _} = source, schema}, prefix: nil} = expr, _adapter, _cte_names)
327+
when is_atom(schema) do
328+
{expr, {source, schema, nil}}
329+
end
330+
326331
defp plan_source(query, %{source: {kind, _, _}, prefix: prefix} = expr, _adapter, _cte_names)
327332
when kind in [:fragment, :values],
328333
do: error!(query, expr, "cannot set prefix: #{inspect(prefix)} option for #{kind} sources")
329334

335+
defp plan_source(query, %{source: {{:fragment, _, _}, _schema}, prefix: prefix} = expr, _adapter, _cte_names),
336+
do: error!(query, expr, "cannot set prefix: #{inspect(prefix)} option for fragment sources")
337+
330338
defp plan_subquery(subquery, query, prefix, adapter, source?, cte_names) do
331339
%{query: inner_query} = subquery
332340

test/ecto/query/inspect_test.exs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ defmodule Ecto.Query.InspectTest do
8181

8282
assert i(from(subquery(Post), [])) ==
8383
~s{from p0 in subquery(from p0 in Inspect.Post)}
84+
85+
assert i(from(x in {fragment("select generate_series(?::integer, ?::integer) as num", ^0, ^2), Inspect.Comment}, [])) ==
86+
~s[from c0 in {"fragment(\\"select generate_series(?::integer, ?::integer) as num\\", ^0, ^2)", Inspect.Comment}]
8487
end
8588

8689
test "CTE" do

test/ecto/query/planner_test.exs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ defmodule Ecto.Query.PlannerTest do
143143
end
144144
end
145145

146+
defmodule Barebone do
147+
use Ecto.Schema
148+
149+
@primary_key false
150+
schema "barebone" do
151+
field :num, :integer
152+
end
153+
end
154+
146155
defp plan(query, operation \\ :all) do
147156
{query, params, key} = Planner.plan(query, operation, Ecto.TestAdapter)
148157
{cast_params, dump_params} = Enum.unzip(params)
@@ -939,6 +948,16 @@ defmodule Ecto.Query.PlannerTest do
939948
]
940949
end
941950

951+
test "plan: tuple source with fragment" do
952+
{query, cast_params, dump_params, cache_key} =
953+
plan(from {fragment("? as num", ^0), Barebone})
954+
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
959+
end
960+
942961
describe "plan: CTEs" do
943962
test "with uncacheable queries are uncacheable" do
944963
{_, _, _, cache} =
@@ -2572,6 +2591,16 @@ defmodule Ecto.Query.PlannerTest do
25722591
end
25732592
end
25742593

2594+
test "normalize: tuple source with fragment" do
2595+
{query, _, _, select} =
2596+
normalize_with_params(from {fragment("? as num", ^0), Barebone})
2597+
2598+
%{from: {_, {:source, {{:fragment, _, _}, Barebone}, nil, types}}} = select
2599+
assert types == [num: :integer]
2600+
assert {{:fragment, _, _}, Barebone} = query.from.source
2601+
assert query.select.fields == [{{:., [writable: :always], [{:&, [], [0]}, :num]}, [], []}]
2602+
end
2603+
25752604
describe "normalize: subqueries in boolean expressions" do
25762605
test "replaces {:subquery, index} with an Ecto.SubQuery struct" do
25772606
subquery = from(p in Post, select: p.visits)

0 commit comments

Comments
 (0)