Begin meta.* search

pull/1164/head
Rick Carlino 2020-02-27 10:39:01 -06:00
parent 2a62a0bbeb
commit 0fab548533
2 changed files with 111 additions and 37 deletions

View File

@ -1,10 +1,6 @@
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
@ -29,18 +25,54 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
point IDs (int) that match
the group's criteria.
"""
def run(%PointGroup{} = pg) do
# = = = Handle AND criteria
results = find_matching_point_ids(pg)
List.flatten(results)
def run(%PointGroup{point_ids: static_ids} = pg) do
# = = = Handle point_id criteria
always_ok = Repo.all(from(p in Point, where: p.id in ^static_ids, select: p))
# = = = Handle AND criteria
dynamic_ids = find_matching_point_ids(pg)
dynamic_query = from(p in Point, where: p.id in ^dynamic_ids, select: p)
needs_meta_filter = Repo.all(dynamic_query)
# = = = Handle meta.* criteria
search_matches = search_meta_fields(pg, needs_meta_filter)
search_matches ++ always_ok
end
# Map/Reduce operations to convert a %PointGroup{}
# to a list of SQL queries.
def search_meta_fields(%PointGroup{} = pg, points) do
meta = "meta."
meta_len = String.length(meta)
pg.criteria["string_eq"]
|> Map.to_list()
|> Enum.filter(fn {k, _v} ->
String.starts_with?(k, meta)
end)
|> Enum.map(fn {k, value} ->
clean_key = String.slice(k, ((meta_len)..-1))
{clean_key, value}
end)
|> Enum.reduce(%{}, fn {key, value}, all ->
all_values = all[key] || []
Map.merge(all, %{key => value ++ all_values})
end)
|> Map.to_list()
|> Enum.reduce(points, fn {key, values}, finalists ->
finalists
|> Enum.filter(fn point -> point.meta[key] end)
|> Enum.filter(fn point -> point.meta[key] in values end)
end)
end
def matching_ids_no_meta(%PointGroup{} = pg) do
find_matching_point_ids(pg)
end
# Find all point IDs that are matched by a PointGroup
# This is not including any meta.* search terms,
# since `meta` is a JSON coolumn that must be
# manually searched in memory.
defp find_matching_point_ids(%PointGroup{} = pg) do
{pg, []}
results = {pg, []}
|> stage_1("string_eq", @string_fields, "IN")
|> stage_1("number_eq", @numberic_fields, "IN")
|> stage_1("number_gt", @numberic_fields, ">")
@ -52,15 +84,16 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
|> Enum.reduce({[], [], 0}, &stage_3/2)
|> unwrap_stage_3()
|> finalize()
end
results
end
def finalize({fragments, criteria}) do
x = Enum.join(fragments, " AND ")
sql = "SELECT id FROM points WHERE #{x}"
escapes = List.flatten(criteria)
{:ok, query} = Repo.query(sql, escapes)
{:ok, query} = Repo.query(sql, List.flatten(criteria))
%Sqlite.DbConnection.Result{ rows: rows } = query
rows
List.flatten(rows)
end
defp unwrap_stage_1({_, right}), do: right

View File

@ -32,21 +32,26 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
def rand, do: Enum.random(0..@n) / 1
def point!(%{id: id} = params) do
point = Map.merge(%Point{
id: id,
name: "point #{id}",
meta: %{},
plant_stage: "planted",
created_at: @now,
pointer_type: "Plant",
radius: 10.0,
tool_id: nil,
discarded_at: nil,
gantry_mounted: false,
x: 0.0,
y: 0.0,
z: 0.0
}, params)
point =
Map.merge(
%Point{
id: id,
name: "point #{id}",
meta: %{},
plant_stage: "planted",
created_at: @now,
pointer_type: "Plant",
radius: 10.0,
tool_id: nil,
discarded_at: nil,
gantry_mounted: false,
x: 0.0,
y: 0.0,
z: 0.0
},
params
)
Repo.insert!(point)
point
end
@ -85,20 +90,56 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
pg
end
test "query" do
test "matching_ids_no_meta - Finds point group critera (excluding meta attrs)" do
expect(Timex, :now, fn -> @now end)
pg = point_group_with_fake_points()
perfect_match = point!(%{
id: 999,
perfect_match =
point!(%{
id: 999,
created_at: @five_days_ago,
openfarm_slug: "five",
meta: %{"created_by" => "plant-detection"},
radius: 10.0,
x: 6.0,
z: 9.0
})
expected = [perfect_match.id]
results = CriteriaRetriever.matching_ids_no_meta(pg)
assert Enum.count(expected) == Enum.count(results)
Enum.map(expected, fn id -> assert Enum.member?(results, id) end)
end
test "run/1" do
expect(Timex, :now, fn -> @now end)
pg = point_group_with_fake_points()
# This one is _almost_ a perfect match,
# but the meta field is a miss.
point!(%{
id: 888,
created_at: @five_days_ago,
openfarm_slug: "five",
meta: %{ "created_by" => "plant-detection" },
meta: %{"created_by" => "not-plant-detection"},
radius: 10.0,
x: 6.0,
z: 9.0
})
assert [perfect_match.id] == CriteriaRetriever.run(pg)
perfect_match = point!(%{
id: 999,
created_at: @five_days_ago,
openfarm_slug: "five",
meta: %{"created_by" => "plant-detection"},
radius: 10.0,
x: 6.0,
z: 9.0
})
expected = [perfect_match.id, 1, 2, 3]
results = Enum.map(CriteriaRetriever.run(pg), fn p -> p.id end)
assert Enum.count(expected) == Enum.count(results)
Enum.map(expected, fn id -> assert Enum.member?(results, id) end)
end
end