Current section
Files
Jump to
Current section
Files
lib/squirrelix/inference.ex
defmodule Squirrelix.Inference.Inferrer do
@moduledoc """
Behaviour for modules that infer SQL query parameters and returned columns.
Part of the supported public API for custom query sources. Prefer
`Squirrelix.Postgres.inferrer/1` when connecting to a live database.
Callbacks receive a `Squirrelix.Query` and must return either:
{:ok, [params: types, returns: columns]}
{:error, structured_error}
where `types` is a list of type atoms (see the Types guide) and `columns` is a
list of maps with `:name`, `:type`, and `:nullable?` keys — the same shape as
metadata-file entries.
The Mix task uses `Squirrelix.Postgres.inferrer/1` when `--infer` is passed.
See the [Configuration guide](configuration.html) for connection options.
"""
@callback infer(Squirrelix.Query.t()) :: {:ok, keyword()} | {:error, struct()}
end
defmodule Squirrelix.Inference do
@moduledoc false
alias Squirrelix.Error
alias Squirrelix.Query
alias Squirrelix.QueryDirectory
alias Squirrelix.TypedQuery
alias Squirrelix.TypedQueryDirectory
@type inferrer :: (Query.t() -> {:ok, keyword()} | {:error, struct()}) | module()
@spec from_query_directories([QueryDirectory.t()], inferrer()) :: [TypedQueryDirectory.t()]
def from_query_directories(query_directories, inferrer) when is_list(query_directories) do
query_directories
|> Enum.sort_by(& &1.directory)
|> Enum.map(&from_query_directory(&1, inferrer))
end
defp from_query_directory(%QueryDirectory{} = query_directory, inferrer) do
{typed_queries, errors} =
Enum.reduce(query_directory.queries, {[], query_directory.errors}, fn query, acc ->
infer_query(query, inferrer, acc)
end)
%TypedQueryDirectory{
directory: query_directory.directory,
queries: Enum.reverse(typed_queries),
errors: Enum.map(errors, &Error.normalize/1)
}
end
defp infer_query(query, inferrer, {typed_queries, errors}) do
case call_inferrer(inferrer, query) do
{:ok, metadata} ->
typed_query_from_metadata(query, metadata, typed_queries, errors)
{:error, error} ->
{typed_queries, errors ++ [error |> Error.attach_query(query) |> Error.normalize()]}
end
end
defp call_inferrer(inferrer, query) when is_function(inferrer, 1) do
inferrer.(query)
end
defp call_inferrer(inferrer, query) when is_atom(inferrer) do
inferrer.infer(query)
end
defp typed_query_from_metadata(query, metadata, typed_queries, errors) do
case TypedQuery.from_query(query, metadata) do
{:ok, typed_query} -> {[typed_query | typed_queries], errors}
{:error, error} -> {typed_queries, errors ++ [Error.attach_query(error, query)]}
end
end
end