This commit is contained in:
Zach Daniel 2020-04-03 23:42:15 -04:00
parent 9c2c11bb56
commit 00c8ae8941
No known key found for this signature in database
GPG key ID: C377365383138D4B
3 changed files with 226 additions and 37 deletions

View file

@ -1,5 +1,5 @@
# Used by "mix format" # Used by "mix format"
[ [
locals_without_parens: [] locals_without_parens: [],
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
] ]

View file

@ -94,10 +94,11 @@ defmodule AshPostgres do
@impl true @impl true
def create(resource, changeset) do def create(resource, changeset) do
changeset = Map.update!(changeset, :action, fn changeset =
:create -> :insert Map.update!(changeset, :action, fn
action -> action :create -> :insert
end) action -> action
end)
repo(resource).insert(changeset) repo(resource).insert(changeset)
rescue rescue
@ -128,25 +129,212 @@ defmodule AshPostgres do
# make queries perform well. For now, I'm just choosing the most naive approach # make queries perform well. For now, I'm just choosing the most naive approach
# possible: left join to relationships that appear in `or` conditions, inner # possible: left join to relationships that appear in `or` conditions, inner
# join to conditions in the mainline query. # join to conditions in the mainline query.
def filter(query, filter, resource) do
IO.inspect(filter)
{:ok, query}
# filter
# |> join_relationships()
# |> Enum.flat_map(fn {key, filter} ->
# Enum.map(filter, fn {filter_type, value} ->
# {key, filter_type, value}
# end)
# end)
# |> Enum.reduce({:ok, query}, fn
# _, {:error, error} ->
# {:error, error}
# {key, filter_type, value}, {:ok, query} -> def filter(query, filter, resource) do
# do_filter(query, key, filter_type, value) new_query =
# end) query
|> Map.put(:bindings, %{})
|> join_all_relationships(filter)
|> add_filter_expression(filter)
{:ok, new_query}
end end
defp join_all_relationships(query, filter, path \\ [])
defp join_all_relationships(query, %{relationships: relationships}, _path)
when relationships == %{} do
query
end
defp join_all_relationships(query, filter, path) do
query =
Map.put_new(query, :__ash_bindings__, %{current: Enum.count(query.joins) + 1, bindings: %{}})
Enum.reduce(filter.relationships, query, fn {name, relationship_filter}, query ->
# TODO: This can be smarter. If the same relationship exists in all `ors`,
# we can inner join it, (unless the filter is only for fields being null)
join_type =
if Enum.empty?(filter.ors) && filter.not == nil do
:inner
else
:left
end
current_path = [Ash.relationship(filter.resource, name) | path]
joined_query = join_relationship(query, current_path, join_type)
join_all_relationships(joined_query, relationship_filter, current_path)
end)
end
defp join_relationship(query, path, join_type) do
path_names = Enum.map(path, & &1.name)
case Map.get(query.__ash_bindings__.bindings, path_names) do
%{type: existing_join_type} when join_type != existing_join_type ->
raise "unreachable?"
nil ->
do_join_relationship(query, path, join_type)
_ ->
query
end
end
defp do_join_relationship(query, [%{type: :many_to_many} = relationship], :inner) do
new_query =
from(row in query,
join: through in ^relationship.through,
on:
field(row, ^relationship.source_field) ==
field(through, ^relationship.source_field_on_join_table),
join: destination in ^relationship.destination,
on:
field(destination, ^relationship.destination_field) ==
field(through, ^relationship.destination_field_on_join_table)
)
join_path = [String.to_existing_atom(to_string(relationship.name) <> "_join_assoc")]
full_path = [relationship.name]
new_query
|> add_binding(join_path, :inner)
|> add_binding(full_path, :inner)
end
defp do_join_relationship(query, [relationship], :inner) do
new_query =
from(row in query,
join: destination in ^relationship.destination,
on: field(row, ^relationship.source_field) == field(row, ^relationship.destination_field)
)
add_binding(new_query, [relationship.name], :inner)
end
defp add_filter_expression(query, filter) do
{params, not_expr} =
case filter.not do
nil ->
{[], nil}
not_filter ->
filter_to_expr(not_filter)
end
{params, expr} = filter_to_expr(filter, query.__ash_bindings__.bindings, params)
expr = join_exprs(not_expr, expr, :and)
{params, expr} =
Enum.reduce(filter.ors, {params, expr}, fn or_filter, {params, existing_expr} ->
{params, expr} = filter_to_expr(or_filter, params)
{params, join_exprs(existing_expr, expr, :or)}
end)
if expr do
query
else
boolean_expr = %Ecto.Query.BooleanExpr{
expr: expr,
op: :and,
params: params
}
%{query | wheres: [boolean_expr | query.wheres]}
end
end
defp join_exprs(nil, nil, _op), do: nil
defp join_exprs(expr, nil, _op), do: expr
defp join_exprs(nil, expr, _op), do: expr
defp join_exprs(expr, expr, op), do: {op, expr, expr}
defp filter_to_expr(filter, bindings, current_binding \\ 0, params \\ [], path \\ []) do
param_count = Enum.count(params)
{params, existing_expr, _param_count} =
Enum.reduce(filter.attributes, {params, nil, param_count}, fn {attribute, filter},
{params, existing_expr,
param_count} ->
case filter_value_to_expr(attribute, filter, current_binding) do
{param, expr} ->
{params ++ [param], join_exprs(existing_expr, expr, :and), param_count + 1}
expr ->
{params, join_exprs(existing_expr, expr, :and), param_count}
end
end)
Enum.reduce(filter.relationships, {params, existing_expr}, fn {relationship,
relationship_filter},
{params, existing_expr} ->
full_path = path ++ [relationship]
binding = Map.get(bindings, full_path) || raise "unbound relationship referenced!"
{params, expr} = filter_to_expr(relationship_filter, bindings, binding, params, full_path)
{params, join_exprs(expr, existing_expr)}
end)
end
defp add_binding(query, path, type) do
current = query.__ash_bindings__.current
bindings = query.__ash_bindings__.bindings
new_ash_bindings = %{
query.__ash_bindings__
| bindings: do_add_binding(bindings, path, current, type),
current: current + 1
}
%{query | __ash_bindings__: new_ash_bindings}
end
defp do_add_binding(bindings, path, current, type) do
Map.put(bindings, path, %{binding: current, type: type})
end
# defp join_all_relationships(query, filter, kind \\ :inner) do
# case filter.ors do
# [] ->
# Enum.reduce(filter.relationships, query, fn {relationship_name, filter}, query ->
# relationship = Ash.relationship(filter.resource, relationship_name)
# join_relationship(query, relationship, filter, kind)
# end)
# ors ->
# Enum.reduce([filter | ors], query, fn filter, query ->
# join_all_relationships(query, Map.put(filter, :ors, []), :left)
# end)
# end
# end
# defp join_relationships(_query, _relationship, _filter, :left), do: raise "unimplemented"
# defp join_relationship(query, %{type: :many_to_many} = relationship, filter, _type) do
# filtered_destination = filter(Ecto.Queryable.to_query(relationship.destination), filter, relationship.destination)
# from row in query,
# left_join: through in ^relationship.through,
# on: field(row, ^relationship.source_field) == field(through, ^relationship.source_field_on_join_table),
# left_join: destination in ^filtered_destination,
# on: field(destination, ^relationship.destination_field) == field(through, ^relationship.destination_field_on_join_table)
# end
# defp join_relationship(query, relationship, filter, join_kind) do
# filtered_destination = filter(Ecto.Queryable.to_query(relationship.destination), filter, relationship.destination)
# from row in query,
# join: destination in ^filtered_destination,
# on: field(row, ^relationship.source_field) == field(destination, ^relationship.destination_field)
# query
# end
defp do_filter(query, key, :equals, value) do defp do_filter(query, key, :equals, value) do
from(row in query, from(row in query,
where: field(row, ^key) == ^value where: field(row, ^key) == ^value

View file

@ -1,24 +1,25 @@
%{ %{
"ash": {:hex, :ash, "0.1.1", "2db9c647db445e57e9f789c7c0b2fe0ab45bed51d8004a5069baea974f4cc7f4", [:mix], [{:ashton, "~> 0.4.1", [hex: :ashton, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}], "hexpm"}, "ash": {:hex, :ash, "0.1.1", "2db9c647db445e57e9f789c7c0b2fe0ab45bed51d8004a5069baea974f4cc7f4", [:mix], [{:ashton, "~> 0.4.1", [hex: :ashton, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}], "hexpm"},
"ashton": {:hex, :ashton, "0.4.1", "d0f7782ac44fa22da7ce544028ee3d2078592a834d8adf3e5b4b6aeb94413a55", [:mix], [], "hexpm"}, "ashton": {:hex, :ashton, "0.4.1", "d0f7782ac44fa22da7ce544028ee3d2078592a834d8adf3e5b4b6aeb94413a55", [:mix], [], "hexpm", "24db667932517fdbc3f2dae777f28b8d87629271387d4490bc4ae8d9c46ff3d3"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"dataloader": {:hex, :dataloader, "1.0.6", "fb724d6d3fb6acb87d27e3b32dea3a307936ad2d245faf9cf5221d1323d6a4ba", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "dataloader": {:hex, :dataloader, "1.0.6", "fb724d6d3fb6acb87d27e3b32dea3a307936ad2d245faf9cf5221d1323d6a4ba", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, "db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "5a0e8c1c722dbcd31c0cbd1906b1d1074c863d335c295e4b994849b65a1fbe47"},
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
"ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "01251d9b28081b7e0af02a1875f9b809b057f064754ca3b274949d5216ea6f5f"},
"ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a2e23cf761668126252418cae07eff7967ad0152fbc5e2d0dc3de487a5ec774c"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"ets": {:hex, :ets, "0.8.0", "90153faafd289bb0801a537d5b05661f46d5e70b2bb55cccf5ab7f0d41d07832", [:mix], [], "hexpm"}, "ets": {:hex, :ets, "0.8.0", "90153faafd289bb0801a537d5b05661f46d5e70b2bb55cccf5ab7f0d41d07832", [:mix], [], "hexpm", "bda4e05b16eada36798cfda16db551dc5243c0adc9a6dfe655b1bc1279b99cb8"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "machinery": {:hex, :machinery, "1.0.0", "df6968d84c651b9971a33871c78c10157b6e13e4f3390b0bee5b0e8bdea8c781", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "4f6eb4185a48e7245360bedf653af4acc6fa6ae8ff4690619395543fa1a8395f"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.2", "1d71150d5293d703a9c38d4329da57d3935faed2031d64bc19e77b654ef2d177", [:mix], [], "hexpm", "51aa192e0941313c394956718bdb1e59325874f88f45871cff90345b97f60bba"},
"picosat_elixir": {:hex, :picosat_elixir, "0.1.1", "5d7dc32f4a896d588d73a9bd987a291e4217d6f687b2091bf4115cf3ea1cbac9", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "picosat_elixir": {:hex, :picosat_elixir, "0.1.1", "5d7dc32f4a896d588d73a9bd987a291e4217d6f687b2091bf4115cf3ea1cbac9", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "099ef22e9b86dcba88cd3ca5f3c40c8026005ba93a129429234e275a56f54043"},
"plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, "plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12cd418e207b8ed787dfe0f520fccd6c001f58d9108233feae7df36462593d1f"},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm", "e9e3cacfd37c1531c0ca70ca7c0c30ce2dbb02998a4f7719de180fe63f8d41e4"},
} }