mirror of
https://github.com/ash-project/ash_postgres.git
synced 2024-09-19 13:03:14 +12:00
improvement: fix significant performance issue in lateral joins
fix: ensure source table is sorted in lateral join
This commit is contained in:
parent
3cbf11df36
commit
487c9f32d6
4 changed files with 114 additions and 64 deletions
2
.github/workflows/elixir.yml
vendored
2
.github/workflows/elixir.yml
vendored
|
@ -18,7 +18,7 @@ jobs:
|
|||
matrix:
|
||||
otp: ["23"]
|
||||
elixir: ["1.11.0"]
|
||||
ash: ["master", "1.46.3"]
|
||||
ash: ["master", "1.46.11"]
|
||||
pg_version: ["9.6", "11"]
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -559,13 +559,24 @@ defmodule AshPostgres.DataLayer do
|
|||
source_query = Ash.Query.new(source_query)
|
||||
|
||||
subquery =
|
||||
subquery(
|
||||
from(destination in query,
|
||||
where:
|
||||
field(destination, ^destination_field) ==
|
||||
field(parent_as(:source_record), ^source_field)
|
||||
if query.windows[:order] do
|
||||
subquery(
|
||||
from(destination in query,
|
||||
select_merge: %{__order__: over(row_number(), :order)},
|
||||
where:
|
||||
field(destination, ^destination_field) ==
|
||||
field(parent_as(:source_record), ^source_field)
|
||||
)
|
||||
)
|
||||
)
|
||||
else
|
||||
subquery(
|
||||
from(destination in query,
|
||||
where:
|
||||
field(destination, ^destination_field) ==
|
||||
field(parent_as(:source_record), ^source_field)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
source_query.resource
|
||||
|> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]})
|
||||
|
@ -580,14 +591,28 @@ defmodule AshPostgres.DataLayer do
|
|||
end
|
||||
|> case do
|
||||
{:ok, data_layer_query} ->
|
||||
{:ok,
|
||||
from(source in data_layer_query,
|
||||
as: :source_record,
|
||||
where: field(source, ^source_field) in ^source_values,
|
||||
inner_lateral_join: destination in ^subquery,
|
||||
on: field(source, ^source_field) == field(destination, ^destination_field),
|
||||
select: destination
|
||||
)}
|
||||
if query.windows[:order] do
|
||||
{:ok,
|
||||
from(source in data_layer_query,
|
||||
as: :source_record,
|
||||
where: field(source, ^source_field) in ^source_values,
|
||||
inner_lateral_join: destination in ^subquery,
|
||||
on: field(source, ^source_field) == field(destination, ^destination_field),
|
||||
order_by: destination.__order__,
|
||||
select: destination,
|
||||
distinct: true
|
||||
)}
|
||||
else
|
||||
{:ok,
|
||||
from(source in data_layer_query,
|
||||
as: :source_record,
|
||||
where: field(source, ^source_field) in ^source_values,
|
||||
inner_lateral_join: destination in ^subquery,
|
||||
on: field(source, ^source_field) == field(destination, ^destination_field),
|
||||
select: destination,
|
||||
distinct: true
|
||||
)}
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
|
@ -628,7 +653,6 @@ defmodule AshPostgres.DataLayer do
|
|||
|> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]})
|
||||
|> set_lateral_join_prefix(query)
|
||||
|> Ash.Query.do_filter(relationship.filter)
|
||||
|> Ash.Query.sort(Map.get(relationship, :sort))
|
||||
|> case do
|
||||
%{valid?: true} = query ->
|
||||
Ash.Query.data_layer_query(query)
|
||||
|
@ -638,26 +662,53 @@ defmodule AshPostgres.DataLayer do
|
|||
end
|
||||
|> case do
|
||||
{:ok, data_layer_query} ->
|
||||
subquery =
|
||||
subquery(
|
||||
from(destination in query,
|
||||
join: through in ^through_query,
|
||||
on:
|
||||
field(through, ^destination_field_on_join_table) ==
|
||||
field(destination, ^destination_field),
|
||||
where:
|
||||
field(through, ^source_field_on_join_table) ==
|
||||
field(parent_as(:source_record), ^source_field)
|
||||
if query.windows[:order] do
|
||||
subquery =
|
||||
subquery(
|
||||
from(destination in query,
|
||||
select_merge: %{__order__: over(row_number(), :order)},
|
||||
join: through in ^through_query,
|
||||
on:
|
||||
field(through, ^destination_field_on_join_table) ==
|
||||
field(destination, ^destination_field),
|
||||
where:
|
||||
field(through, ^source_field_on_join_table) ==
|
||||
field(parent_as(:source_record), ^source_field)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
{:ok,
|
||||
from(source in data_layer_query,
|
||||
as: :source_record,
|
||||
where: field(source, ^source_field) in ^source_values,
|
||||
inner_lateral_join: through in ^subquery,
|
||||
select: through
|
||||
)}
|
||||
{:ok,
|
||||
from(source in data_layer_query,
|
||||
as: :source_record,
|
||||
where: field(source, ^source_field) in ^source_values,
|
||||
inner_lateral_join: destination in ^subquery,
|
||||
select: destination,
|
||||
order_by: destination.__order__,
|
||||
distinct: true
|
||||
)}
|
||||
else
|
||||
subquery =
|
||||
subquery(
|
||||
from(destination in query,
|
||||
join: through in ^through_query,
|
||||
on:
|
||||
field(through, ^destination_field_on_join_table) ==
|
||||
field(destination, ^destination_field),
|
||||
where:
|
||||
field(through, ^source_field_on_join_table) ==
|
||||
field(parent_as(:source_record), ^source_field)
|
||||
)
|
||||
)
|
||||
|
||||
{:ok,
|
||||
from(source in data_layer_query,
|
||||
as: :source_record,
|
||||
where: field(source, ^source_field) in ^source_values,
|
||||
inner_lateral_join: destination in ^subquery,
|
||||
select: destination,
|
||||
distinct: true
|
||||
)}
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
|
@ -957,8 +1008,8 @@ defmodule AshPostgres.DataLayer do
|
|||
|
||||
sort
|
||||
|> sanitize_sort()
|
||||
|> Enum.reduce_while({:ok, query}, fn
|
||||
{order, %Ash.Query.Calculation{} = calc}, {:ok, query} ->
|
||||
|> Enum.reduce_while({:ok, %Ecto.Query.QueryExpr{expr: [], params: []}}, fn
|
||||
{order, %Ash.Query.Calculation{} = calc}, {:ok, query_expr} ->
|
||||
type =
|
||||
if calc.type do
|
||||
Ash.Type.ecto_type(calc.type)
|
||||
|
@ -976,27 +1027,17 @@ defmodule AshPostgres.DataLayer do
|
|||
})
|
||||
|> case do
|
||||
{:ok, expr} ->
|
||||
{params, expr} = do_filter_to_expr(expr, query.__ash_bindings__, [], false, type)
|
||||
{params, expr} =
|
||||
do_filter_to_expr(expr, query.__ash_bindings__, query_expr.params, false, type)
|
||||
|
||||
new_query =
|
||||
Map.update!(query, :order_bys, fn order_bys ->
|
||||
order_bys = order_bys || []
|
||||
|
||||
sort_expr = %Ecto.Query.QueryExpr{
|
||||
expr: [{order, expr}],
|
||||
params: params
|
||||
}
|
||||
|
||||
order_bys ++ [sort_expr]
|
||||
end)
|
||||
|
||||
{:cont, {:ok, new_query}}
|
||||
{:cont,
|
||||
{:ok, %{query_expr | expr: query_expr.expr ++ [{order, expr}], params: params}}}
|
||||
|
||||
{:error, error} ->
|
||||
{:halt, {:error, error}}
|
||||
end
|
||||
|
||||
{order, sort}, {:ok, query} ->
|
||||
{order, sort}, {:ok, query_expr} ->
|
||||
expr =
|
||||
case Map.fetch(query.__ash_bindings__.aggregates, sort) do
|
||||
{:ok, binding} ->
|
||||
|
@ -1047,21 +1088,30 @@ defmodule AshPostgres.DataLayer do
|
|||
{{:., [], [{:&, [], [0]}, sort]}, [], []}
|
||||
end
|
||||
|
||||
new_query =
|
||||
Map.update!(query, :order_bys, fn order_bys ->
|
||||
order_bys = order_bys || []
|
||||
{:cont, {:ok, %{query_expr | expr: [query_expr.expr ++ {order, expr}]}}}
|
||||
end)
|
||||
|> case do
|
||||
{:ok, %{expr: []}} ->
|
||||
{:ok, query}
|
||||
|
||||
sort_expr = %Ecto.Query.QueryExpr{
|
||||
expr: [
|
||||
{order, expr}
|
||||
]
|
||||
}
|
||||
{:ok, sort_expr} ->
|
||||
new_query =
|
||||
query
|
||||
|> Map.update!(:order_bys, fn order_bys ->
|
||||
order_bys = order_bys || []
|
||||
|
||||
order_bys ++ [sort_expr]
|
||||
end)
|
||||
|> Map.update!(:windows, fn windows ->
|
||||
order_by_expr = %{sort_expr | expr: [order_by: sort_expr.expr]}
|
||||
Keyword.put(windows, :order, order_by_expr)
|
||||
end)
|
||||
|
||||
{:cont, {:ok, new_query}}
|
||||
end)
|
||||
{:ok, new_query}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -95,7 +95,7 @@ defmodule AshPostgres.MixProject do
|
|||
{:ecto_sql, "~> 3.5"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:postgrex, ">= 0.0.0"},
|
||||
{:ash, ash_version("~> 1.46 and >= 1.46.3")},
|
||||
{:ash, ash_version("~> 1.46 and >= 1.46.11")},
|
||||
{:git_ops, "~> 2.4.3", only: :dev},
|
||||
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
|
||||
{:ex_check, "~> 0.11.0", only: :dev},
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -1,5 +1,5 @@
|
|||
%{
|
||||
"ash": {:hex, :ash, "1.46.3", "61f4513810fa6ae401f2290ee45bc93db1fa7a289b5d5b71d70d8a2c6a83a5b6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [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]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "bdc4c22e4770204b712301f9f18f9b9f687931cc499a0516f1974cbee00cf48c"},
|
||||
"ash": {:hex, :ash, "1.46.11", "bc404875dad27ed5a9e4fc2ad5369287342da1eeb571ec1b85b21c024515b9f7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [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]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "685814bb094dbc2cc4c9dd460778670f6d248c006fcd02e8a75fe173350bb0b1"},
|
||||
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
|
||||
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
|
||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||
|
@ -28,7 +28,7 @@
|
|||
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.3.5", "a4f6820cdcb4ee444afd78635f323e58e8a5ddf2fbbe9b9d283a99f972034bae", [:mix], [], "hexpm", "f5507cc90033a8d12769522009c80aa9164af6bab245dbd4ad421d008455f1e1"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.3.6", "365d03c05d43483d3eacf820671dafce5b49d692667b3bb8cae28447fd2414ef", [:mix], [], "hexpm", "1c1d3536c4aee1be2c8f3c691bf27c62dbd88d9bb3a0b1a011913453932e8c15"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
||||
"picosat_elixir": {:hex, :picosat_elixir, "0.1.5", "23673bd3080a4489401e25b4896aff1f1138d47b2f650eab724aad1506188ebb", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b30b3c3abd1f4281902d3b5bc9b67e716509092d6243b010c29d8be4a526e8c8"},
|
||||
|
|
Loading…
Reference in a new issue