Reduce code size of FarmbotCore.Asset.CriteriaRetriever

pull/1164/head
Rick Carlino 2020-02-25 14:00:57 -06:00
parent d4d7cc020b
commit 471269b7d5
2 changed files with 73 additions and 58 deletions

View File

@ -1,6 +1,10 @@
defmodule FarmbotCore.Asset.CriteriaRetriever do
alias FarmbotCore.Asset.{PointGroup, Repo, Point}
import Ecto.Query
alias FarmbotCore.Asset.{
PointGroup,
Repo,
# Point
}
# import Ecto.Query
@moduledoc """
__ _ The PointGroup asset declares a list
@ -20,64 +24,74 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
@string_fields ["name", "openfarm_slug", "plant_stage", "pointer_type"]
def run(%PointGroup{} = pg) do
{_, list} = flatten(pg)
reducer = fn [expr, op, args], results ->
from(p in results, where: fragment("? ? ?", ^expr, ^op, ^args))
end
and_query = Enum.reduce(list, Point, reducer)
# = = = Handle AND criteria
IO.inspect(and_query)
Repo.all(and_query)
{query, criteria} = flatten(pg) |> normalize() |> to_sql()
x = Repo.query(query, criteria)
IO.puts("FIX THIS SYNTAX ERROR. COnvert ? to $123. WILL BE HARD FOR ARRAYS")
IO.inspect(x)
[]
# = = = Handle point_id criteria
# = = = Handle meta.* criteria
end
# Map/Reduce operations to convert a %PointGroup{}
# to a list of SQL queries.
def flatten(%PointGroup{} = pg) do
{pg, []}
|> handle_number_eq_fields()
|> handle_number_gt_fields()
|> handle_number_lt_fields()
|> handle_string_eq_fields()
|> handle_day_field()
{pg, []}
|> stage_1("string_eq", @string_fields, "IN")
|> stage_1("number_eq", @numberic_fields, "IN")
|> stage_1("number_gt", @numberic_fields, ">")
|> stage_1("number_lt", @numberic_fields, "<")
|> stage_1("day")
|> unwrap()
end
def normalize(list) do
list
|> Enum.reduce(%{}, &stage_2/2)
|> Map.to_list()
|> Enum.reduce({[], []}, &stage_3/2)
end
defp handle_number_eq_fields({%PointGroup{} = pg, accum}) do
build(pg, "number_eq", @numberic_fields, "IN", accum)
def to_sql({fragments, criteria}) do
queries = fragments
|> Enum.with_index
|> Enum.map(fn {str, inx} -> String.replace(str, "?", "$#{inx}") end)
|> Enum.join(" AND ")
{"SELECT id FROM points WHERE #{queries}", criteria}
end
defp handle_number_gt_fields({%PointGroup{} = pg, accum}) do
build(pg, "number_gt", @numberic_fields, ">", accum)
end
defp unwrap({_pg, accum}), do: accum
defp handle_number_lt_fields({%PointGroup{} = pg, accum}) do
build(pg, "number_lt", @numberic_fields, "<", accum)
end
defp handle_string_eq_fields({%PointGroup{} = pg, accum}) do
build(pg, "string_eq", @string_fields, "IN", accum)
end
defp handle_day_field({%PointGroup{} = pg, accum}) do
op = pg.criteria["day"]["op"] || "<"
days = pg.criteria["day"]["days_ago"] || 0
now = Timex.now()
time = Timex.shift(now, days: -1 * days)
query = ["created_at", op, time]
{ pg, accum ++ [ query ] }
end
defp build(pg, criteria_kind, criteria_fields, op, accum) do
results = criteria_fields
|> Enum.map(fn field ->
{field, pg.criteria[criteria_kind][field]}
end)
defp stage_1({pg, accum}, kind, fields, op) do
results = fields
|> Enum.map(fn field -> {field, pg.criteria[kind][field]} end)
|> Enum.filter(fn {_k, v} -> v end)
|> Enum.map(fn {k, v} -> [k, op, v] end)
|> Enum.map(fn {k, v} -> {k, op, v} end)
{pg, accum ++ results}
end
defp stage_1({pg, accum}, "day") do
op = pg.criteria["day"]["op"] || "<"
days = pg.criteria["day"]["days_ago"] || 0
time = Timex.shift(Timex.now(), days: -1 * days)
{ pg, accum ++ [{"created_at", op, time}] }
end
defp stage_2({lhs, "IN", rhs}, results) do
query = "(#{lhs} IN ?)"
all_values = results[query] || []
Map.merge(results, %{query => rhs ++ all_values})
end
defp stage_2({lhs, op, rhs}, results) do
query = "(#{lhs} #{op} ?)"
Map.merge(results, %{query => rhs})
end
defp stage_3({next_query, next_fragment}, {query, fragments}) do
{query ++ [next_query], fragments ++ [next_fragment]}
end
end

View File

@ -15,10 +15,10 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
criteria: %{
"day" => %{"op" => "<", "days_ago" => 4},
"string_eq" => %{
"openfarm_slug" => ["five"],
"openfarm_slug" => ["five", "nine"],
"meta.created_by" => ["plant-detection"]
},
"number_eq" => %{"radius" => [6]},
"number_eq" => %{"radius" => [6, 10]},
"number_lt" => %{"x" => 7},
"number_gt" => %{"z" => 8}
}
@ -62,22 +62,23 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
end
test "query" do
pg = point_group_with_fake_points()
CriteriaRetriever.run(pg)
results = CriteriaRetriever.run(point_group_with_fake_points())
IO.puts("===================================")
IO.inspect(results)
end
test "CriteriaRetriever.flatten/1" do
expect(Timex, :now, 1, fn -> ~U[2222-12-12 02:22:22.222222Z] end)
expected = [
["created_at", "<", ~U[2222-12-08 02:22:22.222222Z]],
["openfarm_slug", "IN", ["five"]],
["radius", "IN", [6]],
["x", "<", 7],
["z", ">", 8]
{"created_at", "<", ~U[2222-12-08 02:22:22.222222Z]},
{"openfarm_slug", "IN", ["five", "nine"]},
{"radius", "IN", [6, 10]},
{"x", "<", 7},
{"z", ">", 8}
]
{_pg, results} = CriteriaRetriever.flatten(@fake_point_group)
results = CriteriaRetriever.flatten(@fake_point_group)
assert Enum.count(expected) == Enum.count(results)