Packages

Convert Abacus expression into Ecto DSL expressions, and use them for where, selects, order_by, group_by or having clauses.

Current section

Files

Jump to
abacus_sql lib completion completion.ex
Raw

lib/completion/completion.ex

defmodule AbacusSql.Completion do
@dot_autocomplete ~r/([a-z0-9\._]*)\.$/
@filtered_with_dot ~r/([a-z0-9\._]*)\.([a-z0-9_]+)$/
@filtered_without_dot ~r/([a-z0-9_]*)$/
@type context :: [{atom, any}]
@type schema :: module | {module, context}
@spec autocomplete(binary, module, any) :: {:ok, [AbacusSql.Completion.Item.t]} | {:error, any}
def autocomplete(code, root_schema, context \\ []) do
with {:ok, {expression, filter}} <- filter_code(code),
{:ok, deepest_schema} <- find_deepest_schema(expression, root_schema, context),
{deepest_schema, context} <- extract_schema_context(deepest_schema, context),
{:ok, results, _context} <- AbacusSql.Completable.children(deepest_schema, context),
{:ok, filtered_results} <- filter_results(results, filter) do
{:ok, filtered_results}
else
e ->
{:error, e}
end
end
@spec filter_code(code :: binary) :: {:ok, {expression :: binary, filter :: binary}} | {:error, any}
defp filter_code(code) do
with :error <- dot_autocomplete(code),
:error <- filtered_with_dot(code),
:error <- filtered_without_dot(code) do
{:error, :not_completable}
else
{:ok, _result} = ok -> ok
end
end
defp dot_autocomplete(code) do
case Regex.run(@dot_autocomplete, code) do
[_, ""] -> :error
nil -> :error
[_, expression] -> {:ok, {expression, nil}}
end
end
defp filtered_with_dot(code) do
case Regex.run(@filtered_with_dot, code) do
nil -> :error
[_, "", _] -> :error
[_, _, ""] -> :error
[_, expression, filter] -> {:ok, {expression, filter}}
end
end
defp filtered_without_dot(code) do
case Regex.run(@filtered_without_dot, code) do
[_, ""] -> {:ok, {nil, nil}}
nil -> :error
[_, filter] -> {:ok, {nil, filter}}
end
end
@spec find_deepest_schema(expr :: binary | tuple, module, term) :: {:ok, module} | {:error, term}
defp find_deepest_schema(nothing, root_schema, _context) when is_nil(nothing) or nothing == "", do: {:ok, root_schema}
defp find_deepest_schema(expr, root_schema, context) when is_binary(expr) do
with {:ok, ast} <- AbacusSql.Term.parse(expr) do
find_deepest_schema(ast, root_schema, context)
else
_ -> {:error, :compile_error}
end
end
defp find_deepest_schema({variable, _, nil}, root_schema, context) do
# single variable was used
with {:ok, result, context} <- find_single_result_in_schema(root_schema, variable, context),
{schema, context} <- extract_schema_context(result.type, context) do
{:ok, {schema, context}}
end
end
defp find_deepest_schema({:., _, [up, {:variable, down}]}, root_schema, context) do
with {:ok, up_schema} <- find_deepest_schema(up, root_schema, context) do
find_deepest_schema({down, [], nil}, up_schema, context)
end
end
defp find_deepest_schema(_, _, _) do
{:error, :invalid_path}
end
@spec find_single_result_in_schema(schema, any(), context) ::
{:error, any} | {:ok, module, context}
def find_single_result_in_schema(schema, variable, context) do
{schema, context} = extract_schema_context(schema, context)
with {:ok, results, context} <- AbacusSql.Completable.children(schema, context),
[result] <- Enum.filter(results, &(&1.code == variable)) do
{:ok, result, context}
else
_ ->
{:error, :invalid_path}
end
end
@spec extract_schema_context(schema, context) :: {module, context}
def extract_schema_context(schema, context) when is_atom(schema) and is_list(context) do
{schema, context}
end
def extract_schema_context({schema, schema_context}, context) when is_atom(schema) and is_list(schema_context) do
{schema, Keyword.merge(context, schema_context)}
end
defp filter_results(results, nothing) when is_nil(nothing) or nothing == "" do
results = Enum.map(results, &Map.put(&1, :filtered_code, &1.code))
{:ok, results}
end
defp filter_results(results, filter) do
results =
results
|> Enum.filter(&String.starts_with?(&1.code, filter))
|> Enum.map(fn item ->
filtered_code = String.replace_leading(item.code, filter, "")
Map.put(item, :filtered_code, filtered_code)
end)
{:ok, results}
end
end