Current section

Files

Jump to
ecto_foundationdb lib ecto_foundationdb tenant backend.ex
Raw

lib/ecto_foundationdb/tenant/backend.ex

defmodule EctoFoundationDB.Tenant.Backend do
@moduledoc false
alias EctoFoundationDB.Database
alias EctoFoundationDB.Layer.Pack
alias EctoFoundationDB.Options
alias EctoFoundationDB.Tenant
@type txobj() :: :erlfdb.database() | :erlfdb.tenant()
@type ref() :: any()
@type db_object() :: any()
@type opened() :: any()
@type meta() :: any()
@type tenant_name() :: :erlfdb.tenant_name()
@callback txobj(db :: :erlfdb.database(), opened :: opened(), meta :: meta()) :: txobj()
@callback ref(opened :: opened(), meta :: meta()) :: ref()
@callback make_meta(opened :: opened()) :: meta()
@callback list(db :: :erlfdb.database(), options :: Options.t()) :: list(db_object())
@callback create(db :: :erlfdb.database(), tenant_name :: tenant_name(), options :: Options.t()) ::
:ok | {:error, :tenant_already_exists}
@callback delete(db :: :erlfdb.database(), tenant_name :: tenant_name(), options :: Options.t()) ::
:ok
@callback get(db :: :erlfdb.database(), tenant_name :: tenant_name(), options :: Options.t()) ::
{:ok, db_object()} | {:error, :tenant_does_not_exist}
@callback open(db :: :erlfdb.database(), tenant_name :: tenant_name(), options :: Options.t()) ::
opened()
@callback all_data_ranges(meta :: meta()) :: list(tuple())
@callback get_name(id :: String.t(), options :: Options.t()) :: String.t()
@callback get_id(db_object :: db_object(), options :: Options.t()) :: String.t()
@callback extend_tuple(tuple :: tuple(), meta :: meta()) :: tuple()
@callback extract_tuple(tuple :: tuple(), meta :: meta()) :: tuple()
@spec db_open!(Database.t(), Ecto.Repo.t(), Tenant.id(), Options.t()) :: Tenant.t()
def db_open!(db, repo, id, options) do
:ok = ensure_created(db, id, options)
db_open(db, repo, id, options)
end
@doc """
If the tenant doesn't exist, create it. Otherwise, no-op.
"""
@spec ensure_created(Database.t(), Tenant.id(), Options.t()) :: :ok
def ensure_created(db, id, options) do
with false <- exists?(db, id, options),
:ok <- create(db, id, options) do
:ok
else
true -> :ok
{:error, :tenant_already_exists} -> :ok
end
end
@doc """
Returns true if the tenant exists in the database. False otherwise.
"""
@spec exists?(Database.t(), Tenant.id(), Options.t()) :: boolean()
def exists?(db, id, options) do
case get(db, id, options) do
{:ok, _} -> true
{:error, :tenant_does_not_exist} -> false
end
end
@spec db_open(Database.t(), Ecto.Repo.t(), Tenant.id(), Options.t()) :: Tenant.t()
def db_open(db, repo, id, options) do
module = get_module(options)
tenant_name = module.get_name(id, options)
opened = module.open(db, tenant_name, options)
meta = module.make_meta(opened)
ref = module.ref(opened, meta)
%Tenant{
id: id,
repo: repo,
backend: meta.__struct__,
ref: ref,
txobj: module.txobj(db, opened, meta),
meta: meta,
options: [
metadata_cache: Options.get(options, :metadata_cache)
]
}
end
@spec list(Database.t(), Options.t()) :: [Tenant.id()]
def list(db, options) do
module = get_module(options)
list = module.list(db, options)
for db_object <- list do
module.get_id(db_object, options)
end
end
@spec create(Database.t(), Tenant.id(), Options.t()) :: :ok | {:error, :tenant_already_exists}
def create(db, id, options) do
module = get_module(options)
tenant_name = module.get_name(id, options)
module.create(db, tenant_name, options)
end
@spec clear(Database.t(), Tenant.id(), Options.t()) :: :ok
def clear(db, id, options) do
tenant = db_open(db, nil, id, options)
ranges =
get_module(options).all_data_ranges(tenant.meta)
:erlfdb.transactional(Tenant.txobj(tenant), fn tx ->
for {start_key, end_key} <- ranges, do: :erlfdb.clear_range(tx, start_key, end_key)
end)
:ok
end
@spec empty(Database.t(), Tenant.id(), Options.t()) :: :ok
def empty(db, id, options) do
tenant = db_open(db, nil, id, options)
{start_key, end_key} =
Pack.adapter_repo_range(tenant)
:erlfdb.transactional(Tenant.txobj(tenant), fn tx ->
:erlfdb.clear_range(tx, start_key, end_key)
end)
:ok
end
@spec delete(Database.t(), Tenant.id(), Options.t()) :: :ok | {:error, atom()}
def delete(db, id, options) do
module = get_module(options)
tenant_name = module.get_name(id, options)
module.delete(db, tenant_name, options)
end
def get(db, id, options) do
module = get_module(options)
tenant_name = module.get_name(id, options)
module.get(db, tenant_name, options)
end
defp get_module(options) do
Options.get(options, :tenant_backend)
end
def set_option(tenant, key, value) do
%Tenant{options: options} = tenant
%Tenant{tenant | options: Keyword.merge(options, [{key, value}])}
end
end