Current section
Files
Jump to
Current section
Files
lib/dsl/dsl.ex
defmodule Durango.Dsl do
alias Durango.{Query, Dsl}
alias Durango.Dsl.{
BoundVar,
DotAccess,
Object,
Function,
# Subquery,
}
alias Durango.AQL.{
ReservedWord,
Operators,
Limit,
Sort,
Options,
Remove,
Update,
Replace,
Insert,
For,
Graph,
Return,
Filter,
Let,
With,
Upsert,
Collect,
}
require ReservedWord
require Operators
require DotAccess
require BoundVar
require Object
require Limit
require Function
require Sort
require Options
require Remove
require Update
require Replace
require Insert
require For
require Graph
require Return
require Filter
require Let
require With
require Upsert
require Collect
BoundVar.inject_parser()
DotAccess.inject_parser()
Limit.inject_parser()
Object.inject_parser()
Function.inject_parser()
Sort.inject_parser()
Remove.inject_parser()
Options.inject_parser()
Update.inject_parser()
Replace.inject_parser()
Insert.inject_parser()
For.inject_parser()
Filter.inject_parser()
Graph.inject_parser()
Return.inject_parser()
Let.inject_parser()
With.inject_parser()
Upsert.inject_parser()
Collect.inject_parser()
def preprocess_ast(ast) do
{ast, bound} = BoundVar.from_quoted(ast)
ast = DotAccess.from_quoted(ast)
{ast, bound}
end
def parse_query(%Query{} = q, []) do
# this function must come after all parsers.
# this function is a finalizer for compilation.
all_tokens =
q.tokens
|> Enum.map(&to_string/1)
|> Enum.map(&String.trim/1)
|> Enum.filter(fn
"" -> false
nil -> false
item -> item
end)
|> Enum.join(" ")
|> String.trim
|> List.wrap
%{ q | tokens: all_tokens }
end
def parse_query(%Query{} = q, [ next | rest ]) do
q
|> Dsl.parse_expr(next)
|> Dsl.parse_query(rest)
end
def parse_expr(%Query{} = q, {:__aliases__, _, [:OLD]}) do
if :OLD in q.local_variables do
q
|> Query.append_tokens("OLD")
else
msg = [
"Durango.Query.query parsing error -",
"OLD variable is only in scope with using",
"UPDATE, REPLACE, or REMOVE.",
]
|> Enum.join(" ")
raise CompileError, description: msg
end
end
def parse_expr(%Query{} = q, {:__aliases__, _, [:NEW]}) do
if :NEW in q.local_variables do
q
|> Query.append_tokens("NEW")
else
msg = [
"Durango.Query.query parsing error -",
"NEW variable is only in scope with using",
"UPDATE, REPLACE, or INSERT.",
]
|> Enum.join(" ")
raise CompileError, description: msg
end
end
def parse_expr(%Query{} = q, {:subquery, _, [ast]}) when is_list(ast) do
Durango.Dsl.Subquery.parse_query(q, ast)
end
def parse_expr(%Query{} = q, :in) do
Query.append_tokens(q, "IN")
end
def parse_expr(%Query{} = q, :and) do
Query.append_tokens(q, "AND")
end
def parse_expr(%Query{} = q, {key, expr}) when is_atom(key) do
q
|> Query.append_tokens([to_string(key)])
|> parse_expr(expr)
end
def parse_expr(%Query{} = q, bool) when is_boolean(bool) do
q
|> Query.append_tokens(to_string(bool))
end
def parse_expr(%Query{} = q, {:inline, _, [%DotAccess{} = dot, sub_query]}) do
# https://docs.arangodb.com/3.3/AQL/Advanced/ArrayOperators.html#inline-expressions
token = DotAccess.to_aql(dot)
q
|> Query.put_local_var(:CURRENT)
|> Query.append_tokens(token<>"[*")
|> parse_query(sub_query)
|> Query.append_tokens("]")
end
def parse_expr(%Query{} = q, atom) when is_atom(atom) do
q
|> Query.append_tokens(to_string(atom))
end
@reserved_words ReservedWord.list_macro()
@operators Operators.list_macro()
@op_or_reserved @reserved_words ++ @operators
def parse_expr(%Query{} = q, {comp, _, [left, right]}) when comp in @op_or_reserved do
q
|> parse_expr(left)
|> parse_expr(comp)
|> parse_expr(right)
end
def parse_expr(%Query{} = q, {:if, _, [conditional, [{:do, do_expr}, {:else, else_expr}]]}) do
q
|> Dsl.parse_expr(conditional)
|> Query.append_tokens("?")
|> Dsl.parse_expr(do_expr)
|> Query.append_tokens(":")
|> Dsl.parse_expr(else_expr)
end
def parse_expr(%Query{} = q, {:=, _, [left, right]}) do
local_variable = Dsl.Helpers.extract_labels(left)
q
|> Query.put_local_var(local_variable)
|> Dsl.parse_expr(left)
|> Query.append_tokens("=")
|> Dsl.parse_expr(right)
end
def parse_expr(%Query{} = q, {{:., _, [{base, _, nil}, attr]}, _, []}) do
Query.ensure_in_locals!(q, base)
Query.append_tokens(q, Dsl.Helpers.stringify(base) <> "." <> Dsl.Helpers.stringify(attr))
end
# @operators Operators.list_macro()
def parse_expr(%Query{} = q, op) when op in Operators.list_macro() do
Query.append_tokens(q, to_string(op))
end
def parse_expr(%Query{} = q, item) when is_binary(item) when is_number(item) do
Query.append_tokens(q, inspect(item))
end
def parse_expr(%Query{} = q, {item, _, nil}) do
Query.append_tokens(q, Dsl.Helpers.stringify(item))
end
def parse_expr(%Query{} = q, items) when is_list(items) do
Dsl.parse_query(q, items)
end
def reduce_assignments(%Query{} = q, expressions, sep) when is_list(expressions) and is_binary(sep) do
{%Query{local_variables: updated_local_vars, bound_variables: updated_bound_vars }, all_tokens} =
expressions
|> Enum.reduce({q, []} , fn expr, {q_acc, tokens} ->
item_query =
%Query{
local_variables: q_acc.local_variables,
bound_variables: q_acc.bound_variables,
}
|> Dsl.parse_expr(expr)
{ item_query, tokens ++ [Enum.join(item_query.tokens, " ")] }
end)
joined_tokens =
all_tokens
|> Enum.map(&String.trim/1)
|> Enum.filter(fn
"" -> false
_ -> true
end)
|> Enum.join(sep)
|> String.trim
%{ q |
bound_variables: Map.merge(q.bound_variables, updated_bound_vars),
local_variables: q.local_variables ++ updated_local_vars,
}
|> Query.append_tokens(joined_tokens)
end
def parse_collection_name(%Query{} = q, {collection, _, nil}) do
parse_collection_name(q, collection)
end
def parse_collection_name(%Query{} = q, collection) when is_atom(collection) do
q
|> Query.append_tokens(to_string(collection))
end
def parse_collection_name(%Query{} = q, %Durango.Dsl.BoundVar{} = bv) do
bv = bv |> BoundVar.put_keytype(:collection)
token = BoundVar.to_aql(bv)
q
|> Query.append_tokens(token)
|> Query.put_bound_var(bv)
end
end