I’m trying to build a query builder that takes as input a bunch of JSON and parses it out
into a valid Ecto query. I’ve got most of the actual parsing working, but I can’t quite grok
the process of actually turning a “rule” from the JSON into a query.
Essentially what I’m doing now is this:
def json_to_query(json) do
query = from students in "students",
select: students.id,
distinct: true,
limit: 10
parse(query, json)
end
defp parse(source_query, node) do
res = node
|> Map.get("rules")
|> Enum.reduce(source_query, fn(r, q) -> parse_node(q, r) end)
res
end
defp parse_node(source_query, node) do
if (is_top_level_node(node)), do: parse(source_query, node), else: sql_from_rule(source_query, node)
end
def sql_from_rule(source_query, rule) do
field = Map.get(rule, "field")
value = Map.get(rule, "values") |> List.first |> Map.get("value") |> get_value
operator = Map.get(rule, "operator") |> get_operator
# IEx.pry
source_query
|> join_clause(field)
|> where_clause(field, value, operator)
end
defp join_clause(query, field) do
table = String.split(field, ".") |> List.first
case table do
"students" -> query
_ -> query |> join(:inner, [s], t in ^table, s.id == t.student_id)
end
end
defp where_clause(query, field, value, operator) do
column = String.split(field, ".") |> List.last
# The obvious problem here is that it's going to end up generating the initial
# query just fine, and the joins are fine as far as I can tell, but this
# is not referencing the correct table when it generates the where. Instead of
# `where grades.term == "T1"` I end up with `where "grades.term" == "T1"` which is
# just comparing the strings.
query
|> where(^column == ^value)
end
I’ve found a few related forum posts, but can’t quite figure out how to make the code work
for my situation. Here’s an example of a rule that I’ll be parsing:
{
"rules":[
{
"id":"9a9b899a-0123-4456-b89a-b15f9237a0de",
"field":"students.gifted",
"type":"boolean",
"input":"boolean",
"operator":"equal",
"values":[
{
"type":"boolean",
"value":true
}
]
},
{
"rules":[
{
"id":"aa98baaa-89ab-4cde-b012-315f9238650b",
"field":"grades.term",
"type":"select",
"input":"select",
"operator":"select_equals",
"values":[
{
"type":"select",
"value":"T1"
}
]
},
{
"id":"a9bb8a8b-4567-489a-bcde-f15f92389859",
"field":"students.school_id",
"type":"select",
"input":"select",
"operator":"select_any_in",
"values":[
{
"type":"multiselect",
"value":[
"234siteid",
"456siteid"
]
}
]
}
],
"condition":"AND"
}
The above should generate something like:
select
id,
students.gifted,
grades.term,
students.school_id
from
students s
join grades g on s.id == g.student_id
where
s.gifted == true and
(grades.term == "T1" and s.school_id in ("234siteid", "456siteid"))
I’m looking into dynamic queries but I
don’t quite understand how I could use them in this context. I’d appreciate any help.