Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix for :preload, and support ORDER BY query #12

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/etso/adapter/behaviour/queryable.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Etso.Adapter.Behaviour.Queryable do

alias Etso.Adapter.TableRegistry
alias Etso.ETS.MatchSpecification
alias Etso.ETS.TableSorter

def prepare(:all, query) do
{:nocache, query}
Expand All @@ -12,7 +13,12 @@ defmodule Etso.Adapter.Behaviour.Queryable do
{_, schema} = query.from.source
{:ok, ets_table} = TableRegistry.get_table(repo, schema)
ets_match = MatchSpecification.build(query, params)
ets_objects = :ets.select(ets_table, [ets_match])

ets_objects =
ets_table
|> :ets.select([ets_match])
|> TableSorter.sort(query)

{length(ets_objects), ets_objects}
end

Expand Down
61 changes: 61 additions & 0 deletions lib/etso/ets/table_sorter.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule Etso.ETS.TableSorter do
@moduledoc """
This module is used to sort the list of ETS objects based
on the order by clause and fields in the query.
"""

def sort(ets_objects, query) do
orders =
Enum.flat_map(query.order_bys, fn %{expr: order_bys} ->
Enum.map(order_bys, fn {order, {{:., [], [{:&, [], [0]}, field]}, [], []}} ->
{field, order}
end)
end)

select_fields =
Enum.map(query.select.fields, fn {{:., _, [{:&, [], [0]}, field]}, [], []} -> field end)

ets_objects
|> Enum.map(fn object ->
select_fields
|> Enum.zip(object)
|> Enum.into(%{})
end)
|> Enum.sort_by(& &1, build_sorter(orders))
|> Enum.map(fn object -> Enum.map(select_fields, &Map.fetch!(object, &1)) end)
end

defp build_sorter(sort_keys) do
sort_keys
|> Enum.reverse()
|> Enum.reduce(fn _, _ -> true end, fn {key, order}, acc ->
build_sorter_by_order(order, key, acc)
end)
end

def build_sorter_by_order(:asc, key, next_cond_fun) do
fn lhs, rhs ->
lval = Map.fetch!(lhs, key)
rval = Map.fetch!(rhs, key)

cond do
lval < rval -> true
lval > rval -> false
true -> next_cond_fun.(lhs, rhs)
end
end
end

def build_sorter_by_order(:desc, key, next_cond_fun) do
fn lhs, rhs ->
lval = Map.fetch!(lhs, key)
rval = Map.fetch!(rhs, key)

cond do
lval > rval -> true
lval < rval -> false
true -> next_cond_fun.(lhs, rhs)
end
end
end
end
175 changes: 175 additions & 0 deletions test/ets/table_sorter_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
defmodule ETS.TableSorterTest do
use ExUnit.Case

describe "sort/2" do
test "sort ets_objects" do
query = dummy_query()
ets_objects = dummy_ets_objects()

[object1, object2, object3, object4, object5] = ets_objects

[result1, result2, result3, result4, result5] =
Etso.ETS.TableSorter.sort(ets_objects, query)

assert object1 == result1
assert object2 == result2
assert object3 == result4
assert object4 == result3
assert object5 == result5
end
end

def dummy_query() do
%Ecto.Query{
select: %Ecto.Query.SelectExpr{
fields: [
{{:., [], [{:&, [], [0]}, :order_id]}, [], []},
{{:., [], [{:&, [], [0]}, :customer_id]}, [], []},
{{:., [], [{:&, [], [0]}, :employee_id]}, [], []},
{{:., [], [{:&, [], [0]}, :freight]}, [], []},
{{:., [], [{:&, [], [0]}, :order_date]}, [], []},
{{:., [], [{:&, [], [0]}, :required_date]}, [], []},
{{:., [], [{:&, [], [0]}, :ship_name]}, [], []},
{{:., [], [{:&, [], [0]}, :ship_via]}, [], []},
{{:., [], [{:&, [], [0]}, :shipped_date]}, [], []},
{{:., [], [{:&, [], [0]}, :ship_address]}, [], []},
{{:., [], [{:&, [], [0]}, :details]}, [], []},
{{:., [type: :integer], [{:&, [], [0]}, :ship_via]}, [], []}
]
},
order_bys: [
%Ecto.Query.QueryExpr{
expr: [asc: {{:., [], [{:&, [], [0]}, :ship_via]}, [], []}]
}
]
}
end

def dummy_ets_objects() do
[
[
10309,
"HUNGO",
3,
47.3,
~D[1996-09-19],
~D[1996-10-17],
"Hungry Owl All-Night Grocers",
1,
~D[1996-10-23],
%{
city: "Cork",
country: "Ireland",
phone: nil,
postal_code: nil,
region: "Co. Cork",
street: "8 Johnstown Road"
},
[
%{discount: 0.0, product_id: nil, quantity: 20, unit_price: nil},
%{discount: 0.0, product_id: nil, quantity: 30, unit_price: nil},
%{discount: 0.0, product_id: nil, quantity: 2, unit_price: nil},
%{discount: 0.0, product_id: nil, quantity: 20, unit_price: nil},
%{discount: 0.0, product_id: nil, quantity: 3, unit_price: nil}
],
1
],
[
10269,
"WHITC",
5,
4.56,
~D[1996-07-31],
~D[1996-08-14],
"White Clover Markets",
1,
~D[1996-08-09],
%{
city: "Seattle",
country: "USA",
phone: nil,
postal_code: "98124",
region: "WA",
street: "1029 - 12th Ave. S."
},
[
%{discount: 0.05, product_id: nil, quantity: 60, unit_price: nil},
%{discount: 0.05, product_id: nil, quantity: 20, unit_price: nil}
],
1
],
[
10677,
"ANTON",
1,
4.03,
~D[1997-09-22],
~D[1997-10-20],
"Antonio Moreno Taquería",
3,
~D[1997-09-26],
%{
city: "México D.F.",
country: "Mexico",
phone: nil,
postal_code: "5023",
region: nil,
street: "Mataderos 2312"
},
[
%{discount: 0.15, product_id: nil, quantity: 30, unit_price: nil},
%{discount: 0.15, product_id: nil, quantity: 8, unit_price: nil}
],
3
],
[
10301,
"WANDK",
8,
45.08,
~D[1996-09-09],
~D[1996-10-07],
"Die Wandernde Kuh",
2,
~D[1996-09-17],
%{
city: "Stuttgart",
country: "Germany",
phone: nil,
postal_code: "70563",
region: nil,
street: "Adenauerallee 900"
},
[
%{discount: 0.0, product_id: nil, quantity: 10, unit_price: nil},
%{discount: 0.0, product_id: nil, quantity: 20, unit_price: nil}
],
2
],
[
10542,
"KOENE",
1,
10.95,
~D[1997-05-20],
~D[1997-06-17],
"Königlich Essen",
3,
~D[1997-05-26],
%{
city: "Brandenburg",
country: "Germany",
phone: nil,
postal_code: "14776",
region: nil,
street: "Maubelstr. 90"
},
[
%{discount: 0.05, product_id: nil, quantity: 15, unit_price: nil},
%{discount: 0.05, product_id: nil, quantity: 24, unit_price: nil}
],
3
]
]
end
end
22 changes: 22 additions & 0 deletions test/northwind/repo_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,26 @@ defmodule Northwind.RepoTest do
|> Repo.all()
|> Repo.preload(shipper: :orders)
end

test "Order / Shipper / Orders Preloading before all()" do
Model.Order
|> preload([_x], shipper: :orders)
|> Repo.all()
end

test "Order By Desc company_name, Asc phone" do
unsorted_shippers =
Model.Shipper
|> Repo.all()

sorted_shippers =
Model.Shipper
|> order_by([x], desc: x.company_name, asc: x.phone)
|> Repo.all()

assert sorted_shippers ==
unsorted_shippers
|> Enum.sort_by(& &1.company_name, :desc)
|> Enum.sort_by(& &1.phone)
end
end