ash_postgres/lib/ash_postgres.ex

176 lines
4.3 KiB
Elixir
Raw Normal View History

2019-12-05 03:58:20 +13:00
defmodule AshPostgres do
2019-12-06 07:45:42 +13:00
@using_opts_schema Ashton.schema(
opts: [
repo: :atom
],
required: [:repo],
describe: [
repo:
"The repo that will be used to fetch your data. See the `Ecto.Repo` documentation for more"
],
constraints: [
repo:
{&AshPostgres.postgres_repo?/1, "must be using the postgres adapter"}
]
)
@moduledoc """
A postgres data layer that levereges Ecto's postgres tools.
To use it, add `use AshPostgres, repo: MyRepo` to your resource, after `use Ash.Resource`
#{Ashton.document(@using_opts_schema)}
"""
2019-11-27 11:33:52 +13:00
@behaviour Ash.DataLayer
2019-10-31 04:13:22 +13:00
defmacro __using__(opts) do
2019-12-06 07:45:42 +13:00
quote bind_quoted: [opts: opts] do
opts = AshPostgres.validate_using_opts(__MODULE__, opts)
2019-11-27 11:33:52 +13:00
2019-12-06 07:45:42 +13:00
@data_layer AshPostgres
@repo opts[:repo]
2019-11-27 11:33:52 +13:00
def repo() do
@repo
end
2019-10-31 04:13:22 +13:00
end
end
2019-12-06 07:45:42 +13:00
def validate_using_opts(mod, opts) do
case Ashton.validate(opts, @using_opts_schema) do
{:ok, opts} ->
opts
{:error, [{key, message} | _]} ->
raise Ash.Error.ResourceDslError,
resource: mod,
using: __MODULE__,
option: key,
message: message
end
end
def postgres_repo?(repo) do
repo.__adapter__() == Ecto.Adapters.Postgres
end
2019-11-27 11:33:52 +13:00
def repo(resource) do
resource.repo()
end
import Ecto.Query, only: [from: 2]
2019-12-09 08:02:30 +13:00
@impl true
2020-01-18 06:24:07 +13:00
def can?(:query_async), do: true
def can?(:transact), do: true
def can?(:composite_primary_key), do: true
def can?({:filter, :in}), do: true
def can?({:filter, :not_in}), do: true
def can?({:filter, :not_eq}), do: true
def can?({:filter, :eq}), do: true
def can?({:filter, :and}), do: true
def can?({:filter, :or}), do: true
def can?({:filter, :not}), do: true
def can?({:filter_related, _}), do: true
def can?(_), do: false
2019-12-09 08:02:30 +13:00
2019-11-27 11:33:52 +13:00
@impl true
def limit(query, limit, _resource) do
{:ok, from(row in query, limit: ^limit)}
end
@impl true
def offset(query, offset, _resource) do
{:ok, from(row in query, offset: ^offset)}
end
@impl true
def run_query(query, resource) do
{:ok, repo(resource).all(query)}
end
@impl true
def resource_to_query(resource), do: Ecto.Queryable.to_query(resource)
2019-11-28 18:27:12 +13:00
@impl true
2019-12-09 08:02:30 +13:00
def create(resource, changeset) do
2020-01-18 06:24:07 +13:00
changeset = Map.update!(changeset, :action, fn
:create -> :insert
action -> action
end)
2019-12-03 19:48:04 +13:00
2020-01-18 06:24:07 +13:00
repo(resource).insert(changeset)
rescue
e ->
{:error, e}
end
2019-12-03 19:48:04 +13:00
2020-01-18 06:24:07 +13:00
@impl true
def update(resource, changeset) do
repo(resource).update(changeset)
rescue
e ->
{:error, e}
2019-11-28 18:27:12 +13:00
end
2019-11-30 05:38:14 +13:00
@impl true
def sort(query, sort, _resource) do
{:ok,
from(row in query,
order_by: ^sort
)}
end
2019-11-27 11:33:52 +13:00
@impl true
2020-01-18 06:24:07 +13:00
# TODO: I have learned from experience that no single approach here
# will be a one-size-fits-all. We need to either use complexity metrics,
# hints from the interface, or some other heuristic to do our best to
# make queries perform well. For now, I'm just choosing the most naive approach
# possible: left join to relationships that appear in `or` conditions, inner
# join to conditions in the mainline query.
2019-11-29 19:54:57 +13:00
def filter(query, filter, resource) do
2020-01-18 06:24:07 +13:00
IO.inspect(filter)
{:ok, query}
# filter
# |> join_relationships()
# |> Enum.flat_map(fn {key, filter} ->
# Enum.map(filter, fn {filter_type, value} ->
# {key, filter_type, value}
# end)
# end)
# |> Enum.reduce({:ok, query}, fn
# _, {:error, error} ->
# {:error, error}
2019-11-28 10:36:49 +13:00
2020-01-18 06:24:07 +13:00
# {key, filter_type, value}, {:ok, query} ->
# do_filter(query, key, filter_type, value)
# end)
2019-11-27 11:33:52 +13:00
end
2020-01-18 06:24:07 +13:00
defp do_filter(query, key, :equals, value) do
from(row in query,
where: field(row, ^key) == ^value
)
2019-11-27 11:33:52 +13:00
end
2020-01-18 06:24:07 +13:00
defp do_filter(query, key, :in, value) do
from(row in query,
where: field(row, ^key) == ^value
)
2019-11-28 10:36:49 +13:00
end
2020-01-18 06:24:07 +13:00
defp do_filter(_, key, type, value) do
{:error, "Invalid filter #{key} #{type} #{inspect(value)}"}
2019-11-27 11:33:52 +13:00
end
@impl true
def can_query_async?(resource) do
repo(resource).in_transaction?()
end
2019-12-03 19:48:04 +13:00
2020-01-18 06:24:07 +13:00
@impl true
def transaction(resource, func) do
repo(resource).transaction(func)
end
2019-10-31 04:13:22 +13:00
end