mirror of
https://github.com/ash-project/ash_postgres.git
synced 2024-09-19 13:03:14 +12:00
improvement: support new distinct
features from ash core
This commit is contained in:
parent
e5fd47164d
commit
c920b09277
8 changed files with 146 additions and 64 deletions
|
@ -1,2 +1,2 @@
|
|||
erlang 25.3
|
||||
elixir 1.15.2
|
||||
erlang 26.0.2
|
||||
elixir 1.15.4
|
||||
|
|
|
@ -17,6 +17,7 @@ if Mix.env() == :dev do
|
|||
end
|
||||
|
||||
if Mix.env() == :test do
|
||||
config :ash, :validate_api_resource_inclusion?, false
|
||||
config :ash, :validate_api_config_inclusion?, false
|
||||
|
||||
config :ash_postgres, AshPostgres.TestRepo,
|
||||
|
|
|
@ -408,7 +408,7 @@ defmodule AshPostgres.DataLayer do
|
|||
}
|
||||
|
||||
alias Ash.Filter
|
||||
alias Ash.Query.{BooleanExpression, Not}
|
||||
alias Ash.Query.{BooleanExpression, Not, Ref}
|
||||
|
||||
@behaviour Ash.DataLayer
|
||||
|
||||
|
@ -1839,6 +1839,7 @@ defmodule AshPostgres.DataLayer do
|
|||
{:error, distinct_statement} ->
|
||||
distinct_query =
|
||||
query
|
||||
|> Ecto.Query.exclude(:order_by)
|
||||
|> default_bindings(resource)
|
||||
|> Map.put(:distinct, distinct_statement)
|
||||
|
||||
|
@ -1854,8 +1855,28 @@ defmodule AshPostgres.DataLayer do
|
|||
end
|
||||
end)
|
||||
|
||||
joined_query_source =
|
||||
Enum.reduce(
|
||||
[
|
||||
:join,
|
||||
:group_by,
|
||||
:having,
|
||||
:distinct,
|
||||
:select,
|
||||
:combinations,
|
||||
:with_ctes,
|
||||
:limit,
|
||||
:offset,
|
||||
:lock,
|
||||
:preload,
|
||||
:update
|
||||
],
|
||||
query,
|
||||
&Ecto.Query.exclude(&2, &1)
|
||||
)
|
||||
|
||||
joined_query =
|
||||
from(row in query.from.source,
|
||||
from(row in joined_query_source,
|
||||
join: distinct in subquery(distinct_query),
|
||||
on: ^on
|
||||
)
|
||||
|
@ -1878,23 +1899,28 @@ defmodule AshPostgres.DataLayer do
|
|||
defp get_distinct_statement(query, distinct_on) do
|
||||
sort = query.__ash_bindings__.sort || []
|
||||
|
||||
distinct =
|
||||
query.distinct ||
|
||||
%Ecto.Query.QueryExpr{
|
||||
expr: [],
|
||||
params: []
|
||||
}
|
||||
|
||||
if sort == [] do
|
||||
{:ok, default_distinct_statement(query, distinct_on)}
|
||||
else
|
||||
distinct_on
|
||||
|> Enum.reduce_while({sort, []}, fn
|
||||
_, {[], _distinct_statement} ->
|
||||
|> Enum.reduce_while({sort, [], [], Enum.count(distinct.params)}, fn
|
||||
_, {[], _distinct_statement, _, _count} ->
|
||||
{:halt, :error}
|
||||
|
||||
distinct_on, {[order_by | rest_order_by], distinct_statement} ->
|
||||
distinct_on, {[order_by | rest_order_by], distinct_statement, params, count} ->
|
||||
case order_by do
|
||||
{^distinct_on, order} ->
|
||||
{distinct_expr, params, count} = distinct_on_expr(query, distinct_on, params, count)
|
||||
|
||||
{:cont,
|
||||
{rest_order_by,
|
||||
[
|
||||
{order, {{:., [], [{:&, [], [0]}, distinct_on]}, [], []}}
|
||||
| distinct_statement
|
||||
]}}
|
||||
{rest_order_by, [{order, distinct_expr} | distinct_statement], params, count}}
|
||||
|
||||
_ ->
|
||||
{:halt, :error}
|
||||
|
@ -1904,14 +1930,13 @@ defmodule AshPostgres.DataLayer do
|
|||
:error ->
|
||||
{:error, default_distinct_statement(query, distinct_on)}
|
||||
|
||||
{_, result} ->
|
||||
distinct =
|
||||
query.distinct ||
|
||||
%Ecto.Query.QueryExpr{
|
||||
expr: []
|
||||
}
|
||||
|
||||
{:ok, %{distinct | expr: distinct.expr ++ Enum.reverse(result)}}
|
||||
{_, result, params, _} ->
|
||||
{:ok,
|
||||
%{
|
||||
distinct
|
||||
| expr: distinct.expr ++ Enum.reverse(result),
|
||||
params: distinct.params ++ Enum.reverse(params)
|
||||
}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1923,12 +1948,60 @@ defmodule AshPostgres.DataLayer do
|
|||
expr: []
|
||||
}
|
||||
|
||||
expr =
|
||||
Enum.map(distinct_on, fn distinct_on_field ->
|
||||
{:asc, {{:., [], [{:&, [], [0]}, distinct_on_field]}, [], []}}
|
||||
{expr, params, _} =
|
||||
Enum.reduce(distinct_on, {[], [], Enum.count(distinct.params)}, fn
|
||||
{distinct_on_field, order}, {expr, params, count} ->
|
||||
{distinct_expr, params, count} =
|
||||
distinct_on_expr(query, distinct_on_field, params, count)
|
||||
|
||||
{[{order, distinct_expr} | expr], params, count}
|
||||
|
||||
distinct_on_field, {expr, params, count} ->
|
||||
{distinct_expr, params, count} =
|
||||
distinct_on_expr(query, distinct_on_field, params, count)
|
||||
|
||||
{[{:asc, distinct_expr} | expr], params, count}
|
||||
end)
|
||||
|
||||
%{distinct | expr: distinct.expr ++ expr}
|
||||
%{
|
||||
distinct
|
||||
| expr: distinct.expr ++ Enum.reverse(expr),
|
||||
params: distinct.params ++ Enum.reverse(params)
|
||||
}
|
||||
end
|
||||
|
||||
defp distinct_on_expr(query, field, params, count) do
|
||||
resource = query.__ash_bindings__.resource
|
||||
|
||||
ref =
|
||||
case field do
|
||||
%Ash.Query.Calculation{} = calc ->
|
||||
%Ref{attribute: calc, relationship_path: [], resource: resource}
|
||||
|
||||
field ->
|
||||
%Ref{
|
||||
attribute: Ash.Resource.Info.field(resource, field),
|
||||
relationship_path: [],
|
||||
resource: resource
|
||||
}
|
||||
end
|
||||
|
||||
dynamic = AshPostgres.Expr.dynamic_expr(query, ref, query.__ash_bindings__)
|
||||
|
||||
result =
|
||||
Ecto.Query.Builder.Dynamic.partially_expand(
|
||||
:distinct,
|
||||
query,
|
||||
dynamic,
|
||||
params,
|
||||
count
|
||||
)
|
||||
|
||||
expr = elem(result, 0)
|
||||
new_params = elem(result, 1)
|
||||
new_count = result |> Tuple.to_list() |> List.last()
|
||||
|
||||
{expr, new_params, new_count}
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
34
lib/expr.ex
34
lib/expr.ex
|
@ -787,40 +787,6 @@ defmodule AshPostgres.Expr do
|
|||
end
|
||||
end
|
||||
|
||||
defp do_dynamic_expr(
|
||||
query,
|
||||
%Ref{
|
||||
attribute: %Ash.Resource.Calculation{calculation: {module, opts}} = calculation
|
||||
} = ref,
|
||||
bindings,
|
||||
embedded?,
|
||||
type
|
||||
) do
|
||||
calc_type =
|
||||
AshPostgres.Types.parameterized_type(
|
||||
calculation.type,
|
||||
Map.get(calculation, :constraints, [])
|
||||
)
|
||||
|
||||
validate_type!(query, calc_type, ref)
|
||||
|
||||
{:ok, query_calc} =
|
||||
Ash.Query.Calculation.new(
|
||||
calculation.name,
|
||||
module,
|
||||
opts,
|
||||
calculation.type
|
||||
)
|
||||
|
||||
expr = do_dynamic_expr(query, %{ref | attribute: query_calc}, bindings, embedded?, type)
|
||||
|
||||
if calc_type do
|
||||
Ecto.Query.dynamic(type(^expr, ^calc_type))
|
||||
else
|
||||
expr
|
||||
end
|
||||
end
|
||||
|
||||
defp do_dynamic_expr(
|
||||
query,
|
||||
%Ref{
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -161,7 +161,7 @@ defmodule AshPostgres.MixProject do
|
|||
{:ecto, "~> 3.9"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:ash, ash_version("~> 2.11 and >= 2.11.9")},
|
||||
{:ash, ash_version("~> 2.11 and >= 2.11.11")},
|
||||
{:git_ops, "~> 2.5", only: [:dev, :test]},
|
||||
{:ex_doc, "~> 0.22", only: [:dev, :test], runtime: false},
|
||||
{:ex_check, "~> 0.14", only: [:dev, :test]},
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -1,5 +1,5 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "2.11.9", "8f692fc38895d7eafbad981f3ef2b6dc22af3cabe18a847db10faa750cec0537", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e57e54083ead9eb4915b0755d15e335b610e595f1030eb338a3c9287b2241764"},
|
||||
"ash": {:hex, :ash, "2.11.11", "84bdbc62f984ea10a9c0e30749ed1c015375657b4f1da32a85a7465d24ec45c3", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: true]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:spark, ">= 1.1.20 and < 2.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:stream_data, "~> 0.5.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d7c9399ebe0322e93c785e6f9df8b66006326ea5745834ad3e830e5d4a0549e9"},
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
|
||||
"comparable": {:hex, :comparable, "1.0.0", "bb669e91cedd14ae9937053e5bcbc3c52bb2f22422611f43b6e38367d94a495f", [:mix], [{:typable, "~> 0.1", [hex: :typable, repo: "hexpm", optional: false]}], "hexpm", "277c11eeb1cd726e7cd41c6c199e7e52fa16ee6830b45ad4cdc62e51f62eb60c"},
|
||||
|
|
|
@ -7,19 +7,19 @@ defmodule AshPostgres.DistinctTest do
|
|||
|
||||
setup do
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "title"})
|
||||
|> Ash.Changeset.new(%{title: "title", score: 1})
|
||||
|> Api.create!()
|
||||
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "title"})
|
||||
|> Ash.Changeset.new(%{title: "title", score: 1})
|
||||
|> Api.create!()
|
||||
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "foo"})
|
||||
|> Ash.Changeset.new(%{title: "foo", score: 2})
|
||||
|> Api.create!()
|
||||
|
||||
Post
|
||||
|> Ash.Changeset.new(%{title: "foo"})
|
||||
|> Ash.Changeset.new(%{title: "foo", score: 2})
|
||||
|> Api.create!()
|
||||
|
||||
:ok
|
||||
|
@ -77,4 +77,42 @@ defmodule AshPostgres.DistinctTest do
|
|||
|
||||
assert [_] = results
|
||||
end
|
||||
|
||||
test "distinct can use calculations sort that does not match the distinct using a limit #2" do
|
||||
results =
|
||||
Post
|
||||
|> Ash.Query.distinct(:negative_score)
|
||||
|> Ash.Query.sort(:negative_score)
|
||||
|> Ash.Query.load(:negative_score)
|
||||
|> Api.read!()
|
||||
|
||||
assert [
|
||||
%{title: "foo", negative_score: -2},
|
||||
%{title: "title", negative_score: -1}
|
||||
] = results
|
||||
|
||||
results =
|
||||
Post
|
||||
|> Ash.Query.distinct(:negative_score)
|
||||
|> Ash.Query.sort(negative_score: :desc)
|
||||
|> Ash.Query.load(:negative_score)
|
||||
|> Api.read!()
|
||||
|
||||
assert [
|
||||
%{title: "title", negative_score: -1},
|
||||
%{title: "foo", negative_score: -2}
|
||||
] = results
|
||||
|
||||
results =
|
||||
Post
|
||||
|> Ash.Query.distinct(:negative_score)
|
||||
|> Ash.Query.sort(:title)
|
||||
|> Ash.Query.load(:negative_score)
|
||||
|> Api.read!()
|
||||
|
||||
assert [
|
||||
%{title: "foo", negative_score: -2},
|
||||
%{title: "title", negative_score: -1}
|
||||
] = results
|
||||
end
|
||||
end
|
||||
|
|
|
@ -102,6 +102,10 @@ defmodule AshPostgres.Test.Post do
|
|||
|
||||
has_many(:comments, AshPostgres.Test.Comment, destination_attribute: :post_id)
|
||||
|
||||
has_many :comments_matching_post_title, AshPostgres.Test.Comment do
|
||||
filter(expr(title == parent_expr(title)))
|
||||
end
|
||||
|
||||
has_many :popular_comments, AshPostgres.Test.Comment do
|
||||
destination_attribute(:post_id)
|
||||
filter(expr(likes > 10))
|
||||
|
|
Loading…
Reference in a new issue