Skip to content

Commit

Permalink
Merge pull request #17 from valyukov/nested_relationships
Browse files Browse the repository at this point in the history
Nested relationships
  • Loading branch information
albertored committed May 25, 2020
2 parents 20f2fc0 + cc4d292 commit 0162c70
Show file tree
Hide file tree
Showing 16 changed files with 298 additions and 151 deletions.
66 changes: 33 additions & 33 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
name: CI
on: [pull_request, push]
jobs:
mix_test:
name: mix test (Elixir ${{ matrix.elixir }} OTP ${{ matrix.otp }})
strategy:
matrix:
elixir: ['1.7.4', '1.10.1']
include:
- elixir: '1.7.4'
otp: '19.x'
- elixir: '1.10.1'
otp: '22.x'
runs-on: ubuntu-16.04
steps:
- name: Setup PostgreSQL
uses: Harmon758/postgresql-action@v1.0.0
with:
postgresql db: ex_sieve_test
postgresql user: ex_sieve_user
postgresql password: ex_sieve_password
- uses: actions/checkout@v1
- uses: actions/setup-elixir@v1
with:
otp-version: ${{ matrix.otp }}
elixir-version: ${{ matrix.elixir }}
- name: Install Dependencies
run: mix deps.get
env:
MIX_ENV: test
- name: Run Tests
run: mix ecto.migrate && mix test
env:
MIX_ENV: test
DB_USER: ex_sieve_user
DB_PASSWORD: ex_sieve_password
mix_test:
name: mix test (Elixir ${{ matrix.elixir }} OTP ${{ matrix.otp }})
strategy:
matrix:
elixir: ["1.7.4", "1.10.1"]
include:
- elixir: "1.7.4"
otp: "19.x"
- elixir: "1.10.1"
otp: "22.x"
runs-on: ubuntu-latest
env:
MIX_ENV: test
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- name: Setup PostgreSQL
uses: Harmon758/postgresql-action@v1.0.0
with:
postgresql db: ex_sieve_test
postgresql user: ex_sieve_user
postgresql password: ex_sieve_password
- uses: actions/checkout@v1
- uses: actions/setup-elixir@v1
with:
otp-version: ${{ matrix.otp }}
elixir-version: ${{ matrix.elixir }}
- name: Install Dependencies
run: mix deps.get
- name: Run Tests
run: mix ecto.migrate && mix coveralls.github
env:
DB_USER: ex_sieve_user
DB_PASSWORD: ex_sieve_password
8 changes: 8 additions & 0 deletions coveralls.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"skip_files": [
"test"
],
"coverage_options": {
"treat_no_relevant_lines_as_covered": true
}
}
29 changes: 1 addition & 28 deletions lib/ex_sieve/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,9 @@ defmodule ExSieve.Builder do

@spec call(Ecto.Queryable.t(), Grouping.t(), list(Sort.t())) :: Ecto.Query.t()
def call(query, grouping, sorts) do
relations = build_relations(grouping, sorts)

query
|> Join.build(relations)
|> Join.build(grouping, sorts)
|> Where.build(grouping)
|> OrderBy.build(sorts)
end

defp build_relations(grouping, sorts) do
sorts_parents = Enum.map(sorts, & &1.attribute.parent)

grouping
|> List.wrap()
|> get_grouping_conditions()
|> Enum.flat_map(& &1.attributes)
|> Enum.map(& &1.parent)
|> Enum.concat(sorts_parents)
|> Enum.uniq()
|> List.delete(:query)
end

defp get_grouping_conditions(groupings, acc \\ [])

defp get_grouping_conditions([%Grouping{conditions: conditions, groupings: []} | t], acc) do
get_grouping_conditions(t, acc ++ conditions)
end

defp get_grouping_conditions([%Grouping{conditions: conditions, groupings: groupings} | t], acc) do
get_grouping_conditions(t ++ groupings, acc ++ conditions)
end

defp get_grouping_conditions([], acc), do: acc
end
70 changes: 65 additions & 5 deletions lib/ex_sieve/builder/join.ex
Original file line number Diff line number Diff line change
@@ -1,22 +1,82 @@
defmodule ExSieve.Builder.Join do
@moduledoc false
alias Ecto.Query.Builder.Join
alias ExSieve.Node.{Grouping, Sort}

@spec build(Ecto.Queryable.t(), Macro.t()) :: Ecto.Query.t()
def build(query, relations) do
@spec build(Ecto.Queryable.t(), Grouping.t(), list(Sort.t())) :: Ecto.Query.t()
def build(query, grouping, sorts) do
relations = build_relations(grouping, sorts)
Enum.reduce(relations, query, &apply_join/2)
end

@spec apply_join(Macro.t(), Ecto.Queryable.t()) :: Ecto.Query.t() | no_return
defp apply_join(relation, query) do
defp build_relations(grouping, sorts) do
sorts_parents = Enum.map(sorts, & &1.attribute.parent)

grouping
|> get_grouping_conditions()
|> Enum.flat_map(& &1.attributes)
|> Enum.map(& &1.parent)
|> Enum.concat(sorts_parents)
|> all_possible_relations()
|> Enum.concat()
|> Enum.uniq()
|> Enum.sort(&(length(&1) <= length(&2)))
|> to_parent_relation_tuple()
end

defp get_grouping_conditions(groupings, acc \\ [])

defp get_grouping_conditions(%Grouping{} = grouping, acc), do: get_grouping_conditions([grouping], acc)

defp get_grouping_conditions([%Grouping{conditions: conditions, groupings: []} | t], acc) do
get_grouping_conditions(t, acc ++ conditions)
end

defp get_grouping_conditions([%Grouping{conditions: conditions, groupings: groupings} | t], acc) do
get_grouping_conditions(t ++ groupings, acc ++ conditions)
end

defp get_grouping_conditions([], acc), do: acc

defp all_possible_relations(parents) do
Enum.map(parents, fn parent_list ->
{relations, _} =
Enum.map_reduce(parent_list, [], fn el, acc ->
acc = [el | acc]
{acc, acc}
end)

relations
end)
end

defp to_parent_relation_tuple(parents) do
Enum.map(parents, fn parent_list ->
case parent_list do
[] -> {nil, :query}
[head] -> {nil, head}
[head | tail] -> {tail |> Enum.reverse() |> Enum.join("_"), head}
end
end)
end

defp apply_join({parent, relation} = pr, query) do
query
|> Macro.escape()
|> Join.build(:inner, [Macro.var(:query, __MODULE__)], expr(relation), nil, nil, relation, nil, nil, __ENV__)
|> Join.build(:inner, join_binding(parent), expr(relation), nil, nil, join_as(pr), nil, nil, __ENV__)
|> elem(0)
|> Code.eval_quoted()
|> elem(0)
end

defp join_binding(nil), do: [Macro.var(:query, __MODULE__)]

defp join_binding(parent), do: [{String.to_atom(parent), Macro.var(:query, __MODULE__)}]

defp join_as({nil, relation}), do: relation

defp join_as({parent, relation}), do: :"#{parent}_#{relation}"

defp expr(relation) do
quote do
unquote(Macro.var(relation, __MODULE__)) in assoc(query, unquote(relation))
Expand Down
9 changes: 7 additions & 2 deletions lib/ex_sieve/builder/order_by.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ defmodule ExSieve.Builder.OrderBy do
end)
end

defp dynamic_sort(:query, name), do: dynamic([p], field(p, ^name))
defp dynamic_sort(parent, name), do: dynamic([{^parent, p}], field(p, ^name))
defp dynamic_sort([], name), do: dynamic([p], field(p, ^name))
defp dynamic_sort([parent], name), do: dynamic([{^parent, p}], field(p, ^name))

defp dynamic_sort(parents, name) do
parent = parents |> Enum.join("_") |> String.to_atom()
dynamic([{^parent, p}], field(p, ^name))
end
end
Loading

0 comments on commit 0162c70

Please sign in to comment.