2023-09-23 14:52:22 +12:00
|
|
|
defmodule AshSqlite.Repo do
|
|
|
|
@moduledoc """
|
|
|
|
Resources that use `AshSqlite.DataLayer` use a `Repo` to access the database.
|
|
|
|
|
|
|
|
This repo is a thin wrapper around an `Ecto.Repo`.
|
|
|
|
|
|
|
|
You can use `Ecto.Repo`'s `init/2` to configure your repo like normal, but
|
|
|
|
instead of returning `{:ok, config}`, use `super(config)` to pass the
|
|
|
|
configuration to the `AshSqlite.Repo` implementation.
|
|
|
|
"""
|
|
|
|
|
|
|
|
@doc "Use this to inform the data layer about what extensions are installed"
|
|
|
|
@callback installed_extensions() :: [String.t()]
|
|
|
|
|
|
|
|
@doc """
|
|
|
|
Use this to inform the data layer about the oldest potential sqlite version it will be run on.
|
|
|
|
|
|
|
|
Must be an integer greater than or equal to 13.
|
|
|
|
"""
|
|
|
|
@callback min_pg_version() :: integer()
|
|
|
|
|
|
|
|
@doc "The path where your migrations are stored"
|
|
|
|
@callback migrations_path() :: String.t() | nil
|
|
|
|
@doc "Allows overriding a given migration type for *all* fields, for example if you wanted to always use :timestamptz for :utc_datetime fields"
|
|
|
|
@callback override_migration_type(atom) :: atom
|
|
|
|
|
|
|
|
defmacro __using__(opts) do
|
|
|
|
quote bind_quoted: [opts: opts] do
|
|
|
|
otp_app = opts[:otp_app] || raise("Must configure OTP app")
|
|
|
|
|
|
|
|
use Ecto.Repo,
|
|
|
|
adapter: Ecto.Adapters.SQLite3,
|
|
|
|
otp_app: otp_app
|
|
|
|
|
|
|
|
@behaviour AshSqlite.Repo
|
|
|
|
|
|
|
|
defoverridable insert: 2, insert: 1, insert!: 2, insert!: 1
|
|
|
|
|
|
|
|
def installed_extensions, do: []
|
|
|
|
def migrations_path, do: nil
|
|
|
|
def override_migration_type(type), do: type
|
|
|
|
def min_pg_version, do: 10
|
|
|
|
|
|
|
|
def init(_, config) do
|
|
|
|
new_config =
|
|
|
|
config
|
|
|
|
|> Keyword.put(:installed_extensions, installed_extensions())
|
|
|
|
|> Keyword.put(:migrations_path, migrations_path())
|
2023-10-11 08:42:51 +13:00
|
|
|
|> Keyword.put(:case_sensitive_like, :on)
|
2023-09-23 14:52:22 +12:00
|
|
|
|
|
|
|
{:ok, new_config}
|
|
|
|
end
|
|
|
|
|
|
|
|
def insert(struct_or_changeset, opts \\ []) do
|
|
|
|
struct_or_changeset
|
|
|
|
|> to_ecto()
|
|
|
|
|> then(fn value ->
|
|
|
|
repo = get_dynamic_repo()
|
|
|
|
|
|
|
|
Ecto.Repo.Schema.insert(
|
|
|
|
__MODULE__,
|
|
|
|
repo,
|
|
|
|
value,
|
|
|
|
Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:insert, opts))
|
|
|
|
)
|
|
|
|
end)
|
|
|
|
|> from_ecto()
|
|
|
|
end
|
|
|
|
|
|
|
|
def insert!(struct_or_changeset, opts \\ []) do
|
|
|
|
struct_or_changeset
|
|
|
|
|> to_ecto()
|
|
|
|
|> then(fn value ->
|
|
|
|
repo = get_dynamic_repo()
|
|
|
|
|
|
|
|
Ecto.Repo.Schema.insert!(
|
|
|
|
__MODULE__,
|
|
|
|
repo,
|
|
|
|
value,
|
|
|
|
Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:insert, opts))
|
|
|
|
)
|
|
|
|
end)
|
|
|
|
|> from_ecto()
|
|
|
|
end
|
|
|
|
|
|
|
|
def from_ecto({:ok, result}), do: {:ok, from_ecto(result)}
|
|
|
|
def from_ecto({:error, _} = other), do: other
|
|
|
|
|
|
|
|
def from_ecto(nil), do: nil
|
|
|
|
|
|
|
|
def from_ecto(value) when is_list(value) do
|
|
|
|
Enum.map(value, &from_ecto/1)
|
|
|
|
end
|
|
|
|
|
|
|
|
def from_ecto(%resource{} = record) do
|
|
|
|
if Spark.Dsl.is?(resource, Ash.Resource) do
|
|
|
|
empty = struct(resource)
|
|
|
|
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.relationships()
|
|
|
|
|> Enum.reduce(record, fn relationship, record ->
|
|
|
|
case Map.get(record, relationship.name) do
|
|
|
|
%Ecto.Association.NotLoaded{} ->
|
|
|
|
Map.put(record, relationship.name, Map.get(empty, relationship.name))
|
|
|
|
|
|
|
|
value ->
|
|
|
|
Map.put(record, relationship.name, from_ecto(value))
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
else
|
|
|
|
record
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def from_ecto(other), do: other
|
|
|
|
|
|
|
|
def to_ecto(nil), do: nil
|
|
|
|
|
|
|
|
def to_ecto(value) when is_list(value) do
|
|
|
|
Enum.map(value, &to_ecto/1)
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_ecto(%resource{} = record) do
|
|
|
|
if Spark.Dsl.is?(resource, Ash.Resource) do
|
|
|
|
resource
|
|
|
|
|> Ash.Resource.Info.relationships()
|
|
|
|
|> Enum.reduce(record, fn relationship, record ->
|
|
|
|
value =
|
|
|
|
case Map.get(record, relationship.name) do
|
|
|
|
%Ash.NotLoaded{} ->
|
|
|
|
%Ecto.Association.NotLoaded{
|
|
|
|
__field__: relationship.name,
|
|
|
|
__cardinality__: relationship.cardinality
|
|
|
|
}
|
|
|
|
|
|
|
|
value ->
|
|
|
|
to_ecto(value)
|
|
|
|
end
|
|
|
|
|
|
|
|
Map.put(record, relationship.name, value)
|
|
|
|
end)
|
|
|
|
else
|
|
|
|
record
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_ecto(other), do: other
|
|
|
|
|
|
|
|
defoverridable init: 2,
|
|
|
|
installed_extensions: 0,
|
|
|
|
override_migration_type: 1,
|
|
|
|
min_pg_version: 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|