Current section
Files
Jump to
Current section
Files
lib/parser.ex
defmodule Yax.Parser do
@moduledoc false
@operators ~w(eq gt gte lt lte neq like ilike in is fts plfts phfts wfts cs cd ov sl sr nxr nxl adj not)a
def parse_params(params) do
Enum.reduce(params, [], &parse_param/2)
end
defp parse_param({"select", value}, agg) do
{:ok, matches, _, _, _, _} = QueryParser.select(value)
Enum.reduce(matches, agg, &parse_select/2)
end
defp parse_param({"order", value}, agg) do
default =
case String.split(value, ".") do
[field] ->
[asc: String.to_existing_atom(field)]
[field, direction] ->
[{String.to_existing_atom(direction), String.to_existing_atom(field)}]
end
Keyword.update(agg, :order_by, default, &Keyword.merge(&1, default))
end
defp parse_param({other, value}, agg) do
[field | assocs] = other |> String.split(".") |> Enum.reverse()
assocs = Enum.reverse(assocs)
field = String.to_existing_atom(field)
value =
case String.split(value, ".") do
[one] -> one
many -> Enum.map(many, &to_operator_atom/1)
end
default = [{field, value}]
case assocs do
[] ->
Keyword.update(
agg,
:where,
default,
&Keyword.put(&1, field, value)
)
other ->
update_assoc(agg, other, fn v ->
Keyword.update(v, :where, default, &Keyword.put(&1, field, value))
end)
end
end
defp parse_select([assoc, fields], agg)
when is_binary(assoc) and is_list(fields) do
value = Enum.map(fields, &String.to_existing_atom/1)
path = String.split(assoc, ".")
update_assoc(agg, path, fn v ->
Keyword.update(
v,
:select,
value,
&(&1 ++ value)
)
end)
end
defp parse_select(fields, agg)
when is_list(fields) do
value = Enum.map(fields, &String.to_existing_atom/1)
Keyword.update(
agg,
:select,
value,
&(&1 ++ value)
)
end
@spec update_assoc(keyword, [binary, ...], (keyword -> keyword)) :: keyword
defp update_assoc(agg, [assoc], updater) do
assoc = String.to_existing_atom(assoc)
default = updater.([])
Keyword.update(
agg,
:assoc,
%{assoc => default},
&Map.update(&1, assoc, default, updater)
)
end
defp update_assoc(agg, [assoc | rest], updater) do
assoc = String.to_existing_atom(assoc)
default = update_assoc([], rest, updater)
Keyword.update(
agg,
:assoc,
%{assoc => default},
fn x -> Map.update(x, assoc, default, &update_assoc(&1, rest, updater)) end
)
end
defp to_operator_atom(str),
do:
if(@operators |> Enum.map(&to_string/1) |> Enum.member?(str),
do: String.to_existing_atom(str),
else: str
)
end
defmodule QueryParser do
@moduledoc false
import NimbleParsec
field = ascii_string([?a..?z, ?_, ?*, ?.], min: 1)
separator = optional(string(","))
fields = repeat(field |> ignore(separator))
group =
ignore(string("("))
|> wrap(fields)
|> ignore(string(")"))
token =
field
|> optional(group)
defparsec(
:select,
repeat(
wrap(token)
|> ignore(separator)
)
)
end