Skip to content

Instantly share code, notes, and snippets.

@WolfDan
Last active January 9, 2019 17:59
Show Gist options
  • Save WolfDan/7505734b755de33a7e61c7d12230f575 to your computer and use it in GitHub Desktop.
Save WolfDan/7505734b755de33a7e61c7d12230f575 to your computer and use it in GitHub Desktop.
Instruction Parser
%{
where: %{
OR: [
%{
description: "description",
OR: [
%{
test: "hmmm"
},
%{
test: nil
}
]
},
%{
description: nil,
date: "2018"
}
],
date: "bla",
createdAt: "2016",
createdAt_gt: "2017",
createdAt_lt: "2019"
}
}
%{
instruction: %{
"createdAt" => %{"eq" => "2016", "gt" => "2017", "lt" => "2019"}
},
on_fail: :fail,
on_sucess: %{
instruction: %{"date" => "bla"},
on_fail: %{
instruction: %{"description" => "description"},
on_fail: %{
instruction: %{"test" => "hmmm"},
on_fail: %{
instruction: %{"test" => nil},
on_fail: %{
instruction: %{"date" => "2018"},
on_fail: :fail,
on_sucess: %{
instruction: %{"description" => nil},
on_fail: :fail,
on_sucess: :success
}
},
on_sucess: :success
},
on_sucess: :success
},
on_sucess: :success
},
on_sucess: :success
}
}
defmodule Nomure.Schema.Query.Plan.Parser do
@moduledoc """
Parse the `where` given query into a behaviour tree like structure
"""
@instructions [
"_gt",
"_lt",
"_starts_with",
"_ends_with"
]
def parse(%{where: statement}) do
sorted_keys = get_clean_key_names(statement)
get_instructions(sorted_keys, statement)
end
def get_instructions([name | new_keys], statements, fail_instruction \\ :fail) do
statements = for {key, val} <- statements, into: %{}, do: {to_string(key), val}
similar_instructions =
Enum.filter(statements, fn {x, _} -> String.replace(x, @instructions, "") == name end)
|> Map.new()
|> Map.keys()
instructions =
Enum.map(similar_instructions, fn
^name -> Map.get(statements, name)
x -> {x |> String.replace(name <> "_", ""), Map.get(statements, x)}
end)
instructions =
if Enum.any?(instructions, fn x -> is_tuple(x) end) do
# if atribute has the property name then is an `eq` instruction
instructions
|> Enum.map(fn
{x, v} -> {x, v}
value -> {"eq", value}
end)
|> Map.new()
else
instructions
|> List.first()
end
instruc_values = Map.new([{name, instructions}])
case new_keys do
# No more instructions, if sucess all right, if fail all operation fails
[] ->
%{
instruction: instruc_values,
on_sucess: :success,
on_fail: fail_instruction
}
# OR instruction, decople list and process
["OR"] ->
%{
instruction: instruc_values,
on_sucess: :success,
on_fail: resolve_or_instruction(Map.get(statements, "OR"), fail_instruction)
}
# more AND instructions, continue the process
_ ->
%{
instruction: instruc_values,
on_sucess: get_instructions(new_keys, statements),
on_fail: fail_instruction
}
end
end
def resolve_or_instruction([], _) do
:fail
end
def resolve_or_instruction(instruction_list, fail_instruction) do
# We reverse the list so the last item is the first to be processed
instruction_list =
instruction_list
|> Enum.reverse()
last_instruction =
instruction_list
|> List.first()
last_instruction =
get_instructions(get_clean_key_names(last_instruction), last_instruction, fail_instruction)
{_, instruction_list} =
instruction_list
|> List.pop_at(0)
Enum.reduce(instruction_list, last_instruction, fn
x, acc ->
get_instructions(get_clean_key_names(x), x, acc)
end)
end
def get_clean_key_names(map) do
map
|> get_keys()
|> Enum.map(&String.replace(&1, @instructions, ""))
# ["createdAt_gt", "createdAt_lt", "OR"] -> ["OR", "createdAt"]
|> Enum.uniq()
# ["OR", "createdAt"] -> ["createdAt", "OR"]
|> Enum.sort_by(&(&1 == "OR"))
end
# by priority `AND` then `OR`
defp get_keys(map) do
map |> Map.keys() |> Enum.map(&to_string/1)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment