From 6f1ccbf1d954bb7eacf74cf10dec4fddcfdc65b1 Mon Sep 17 00:00:00 2001 From: "soichiro.nishizawa" Date: Thu, 18 Mar 2021 14:16:42 +0900 Subject: [PATCH 1/2] add table sorter --- lib/etso/adapter/behaviour/queryable.ex | 8 +++- lib/etso/ets/table_sorter.ex | 61 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 lib/etso/ets/table_sorter.ex diff --git a/lib/etso/adapter/behaviour/queryable.ex b/lib/etso/adapter/behaviour/queryable.ex index 52b2c1d..8a59fd6 100644 --- a/lib/etso/adapter/behaviour/queryable.ex +++ b/lib/etso/adapter/behaviour/queryable.ex @@ -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} @@ -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 diff --git a/lib/etso/ets/table_sorter.ex b/lib/etso/ets/table_sorter.ex new file mode 100644 index 0000000..fb332a1 --- /dev/null +++ b/lib/etso/ets/table_sorter.ex @@ -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 From 592e20cdb3616c5a075937752a2f4f3298faaede Mon Sep 17 00:00:00 2001 From: "soichiro.nishizawa" Date: Tue, 23 Mar 2021 18:36:05 +0900 Subject: [PATCH 2/2] add test --- test/ets/table_sorter_test.exs | 175 +++++++++++++++++++++++++++++++++ test/northwind/repo_test.exs | 22 +++++ 2 files changed, 197 insertions(+) create mode 100644 test/ets/table_sorter_test.exs diff --git a/test/ets/table_sorter_test.exs b/test/ets/table_sorter_test.exs new file mode 100644 index 0000000..b5f05d4 --- /dev/null +++ b/test/ets/table_sorter_test.exs @@ -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 diff --git a/test/northwind/repo_test.exs b/test/northwind/repo_test.exs index e32f70f..89c9753 100644 --- a/test/northwind/repo_test.exs +++ b/test/northwind/repo_test.exs @@ -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