Packages

Hierarchy structure for ecto models with PostgreSQL LTree.

Current section

Files

Jump to
hierarch lib query descendants.ex
Raw

lib/query/descendants.ex

defmodule Hierarch.Query.Descendants do
import Ecto.Query
@doc """
Return query expressions for descendants of a query
```elixir
import Ecto.Query
query = from t in Catelog, where: [..]
query
|> Hierarch.Query.Descendants.query()
|> Repo.all
"""
def query(%{from: %{source: {_table_name, schema}}} = queryable) do
path_column = schema.__hierarch__(:path_column)
[pk_column] = schema.__schema__(:primary_key)
uuids_query =
from(t in queryable,
select: %{
__ancestry__:
fragment(
"? || replace(text(?), '-', '_')",
field(t, ^path_column),
field(t, ^pk_column)
)
}
)
uuids_array_query =
from(t in subquery(uuids_query),
select: %{__ltrees__: fragment("array_agg(?)", t.__ancestry__)}
)
schema
|> join(:cross, [], a in subquery(uuids_array_query))
|> where([t, a], fragment("? @> ?", a.__ltrees__, field(t, ^path_column)))
end
@doc """
Return query expressions for descendants
## Options
* `:with_self` - when true to include itself. Defaults to false.
```elixir
%Catelog{
id: "06a84054-8827-42c2-9b75-25ed75e6d5f8",
name: "Top.Hobbies",
path: "a9ae8f40-b016-4bf9-8224-e2755466e699",
}
|> Hierarch.Query.Descendants.query()
|> Repo.all
# or
%Catelog{
id: "06a84054-8827-42c2-9b75-25ed75e6d5f8",
name: "Top.Hobbies",
path: "a9ae8f40-b016-4bf9-8224-e2755466e699",
}
|> Catelog.descendants()
|> Repo.all
```
"""
def query(%schema{} = struct, opts \\ []) do
condition = descendants_condition(struct)
if Keyword.get(opts, :with_self, false) do
from t in schema, where: ^condition, or_where: ^including_self_condition(struct)
else
from t in schema, where: ^condition
end
end
defp descendants_condition(%schema{} = struct) do
path = Hierarch.Util.struct_path(struct)
[{_pk_column, value}] = Ecto.primary_key(struct)
descendants_path = Hierarch.LTree.concat(path, value)
path_column = schema.__hierarch__(:path_column)
path_column_field_type = schema.__schema__(:type, path_column)
dynamic(
[p],
fragment(
"? <@ ?",
field(p, ^path_column),
type(^descendants_path, ^path_column_field_type)
)
)
end
defp including_self_condition(%_schema{} = struct) do
[{pk_column, value}] = Ecto.primary_key(struct)
dynamic([p], field(p, ^pk_column) == ^value)
end
end