Current section
Files
Jump to
Current section
Files
lib/query/exists.ex
defmodule QueryBuilder.Query.Exists do
@moduledoc false
require Ecto.Query
def apply_exists(
ecto_query,
source_schema,
assoc_fields,
scope_filters,
filters,
or_filters,
negate?
)
when is_atom(source_schema) do
subquery =
source_schema._query()
|> correlate_to_parent!(source_schema)
|> apply_joins_and_filters(source_schema, assoc_fields, scope_filters, filters, or_filters)
|> Ecto.Query.select([], 1)
exists_dynamic =
if negate? do
Ecto.Query.dynamic([], not exists(subquery))
else
Ecto.Query.dynamic([], exists(subquery))
end
Ecto.Query.where(ecto_query, ^exists_dynamic)
end
defp apply_joins_and_filters(
ecto_query,
source_schema,
assoc_fields,
scope_filters,
filters,
or_filters
) do
assoc_list =
QueryBuilder.AssocList.build(
source_schema,
QueryBuilder.AssocList.new(source_schema),
assoc_fields,
join: :inner
)
ecto_query = QueryBuilder.JoinMaker.make_joins(ecto_query, assoc_list)
ecto_query =
ecto_query
|> maybe_where(assoc_list, scope_filters, [])
|> maybe_where(assoc_list, filters, or_filters)
ecto_query
end
defp maybe_where(ecto_query, _assoc_list, [], []), do: ecto_query
defp maybe_where(ecto_query, assoc_list, filters, or_filters) do
QueryBuilder.Query.Where.where(ecto_query, assoc_list, filters, or_filters)
end
defp correlate_to_parent!(ecto_query, source_schema) do
primary_keys = source_schema.__schema__(:primary_key)
if primary_keys == [] do
raise ArgumentError,
"where_exists_subquery/where_not_exists_subquery require the root schema to have a primary key " <>
"so the EXISTS subquery can be correlated via parent_as/1; " <>
"got schema with no primary key: #{inspect(source_schema)}"
end
correlation_dynamic =
primary_keys
|> Enum.map(fn primary_key ->
Ecto.Query.dynamic(
[{^source_schema, x}],
field(x, ^primary_key) == field(parent_as(^source_schema), ^primary_key)
)
end)
|> Enum.reduce(fn left, right -> Ecto.Query.dynamic(^left and ^right) end)
Ecto.Query.where(ecto_query, ^correlation_dynamic)
end
end