Current section
Files
Jump to
Current section
Files
lib/aerospike/repo.ex
defmodule Aerospike.Repo do
@moduledoc """
Generates an application-owned facade for one supervised Aerospike cluster.
`use Aerospike.Repo` binds a Repo module to one cluster name and exposes
current `Aerospike` facade functions without requiring callers to pass that
name on every operation.
Define a Repo in your application:
defmodule MyApp.Repo do
use Aerospike.Repo, otp_app: :my_app
end
Then configure and supervise it:
config :my_app, MyApp.Repo,
transport: Aerospike.Transport.Tcp,
hosts: ["127.0.0.1:3000"],
namespaces: ["test"],
pool_size: 2
children = [
MyApp.Repo
]
The generated module delegates to the canonical `Aerospike` API with
`conn/0` injected as the cluster name:
key = MyApp.Repo.key("test", "users", "user:1")
{:ok, _metadata} = MyApp.Repo.put(key, %{"name" => "Ada"})
{:ok, record} = MyApp.Repo.get(key)
A Repo is a thin module facade over one cluster. It does not perform schema
mapping, changeset validation, query translation, or object reflection.
"""
@typedoc "Application-owned Repo module generated with `use Aerospike.Repo`."
@type t :: module()
@typedoc "Option accepted by `use Aerospike.Repo`."
@type option ::
{:otp_app, atom()}
| {:adapter, module()}
| {:name, atom()}
@typedoc """
Options accepted by `use Aerospike.Repo`.
`:otp_app` is required and names the application environment used by
`config/0`. `:adapter` defaults to `Aerospike` and is mainly useful for
tests. `:name` defaults to the generated Repo module and becomes the cluster
identity injected into delegated calls.
"""
@type opts :: [option()]
@repo_delegates [
{:get, [:key, {:bins, :all}, {:opts, []}]},
{:get_header, [:key, {:opts, []}]},
{:put, [:key, :bins, {:opts, []}]},
{:put_payload, [:key, :payload, {:opts, []}]},
{:put_payload!, [:key, :payload, {:opts, []}]},
{:add, [:key, :bins, {:opts, []}]},
{:append, [:key, :bins, {:opts, []}]},
{:prepend, [:key, :bins, {:opts, []}]},
{:exists, [:key, {:opts, []}]},
{:touch, [:key, {:opts, []}]},
{:delete, [:key, {:opts, []}]},
{:operate, [:key, :operations, {:opts, []}]},
{:apply_udf, [:key, :package, :function, :args, {:opts, []}]},
{:batch_get, [:keys, {:bins, :all}, {:opts, []}]},
{:batch_get_header, [:keys, {:opts, []}]},
{:batch_exists, [:keys, {:opts, []}]},
{:batch_get_operate, [:keys, :operations, {:opts, []}]},
{:batch_delete, [:keys, {:opts, []}]},
{:batch_udf, [:keys, :package, :function, :args, {:opts, []}]},
{:batch_operate, [:entries, {:opts, []}]},
{:scan_stream, [:scan, {:opts, []}]},
{:scan_stream!, [:scan, {:opts, []}]},
{:scan_all, [:scan, {:opts, []}]},
{:scan_all!, [:scan, {:opts, []}]},
{:scan_page, [:scan, {:opts, []}]},
{:scan_page!, [:scan, {:opts, []}]},
{:scan_count, [:scan, {:opts, []}]},
{:scan_count!, [:scan, {:opts, []}]},
{:query_stream, [:query, {:opts, []}]},
{:query_stream!, [:query, {:opts, []}]},
{:query_all, [:query, {:opts, []}]},
{:query_all!, [:query, {:opts, []}]},
{:query_count, [:query, {:opts, []}]},
{:query_count!, [:query, {:opts, []}]},
{:query_page, [:query, {:opts, []}]},
{:query_page!, [:query, {:opts, []}]},
{:query_aggregate, [:query, :package, :function, :args, {:opts, []}]},
{:query_aggregate_result, [:query, :package, :function, :args, {:opts, []}]},
{:query_aggregate_result!, [:query, :package, :function, :args, {:opts, []}]},
{:query_execute, [:query, :operations, {:opts, []}]},
{:query_udf, [:query, :package, :function, :args, {:opts, []}]},
{:info, [:command, {:opts, []}]},
{:info_node, [:node_name, :command, {:opts, []}]},
{:nodes, []},
{:node_names, []},
{:metrics_enabled?, []},
{:enable_metrics, [{:opts, []}]},
{:disable_metrics, []},
{:stats, []},
{:warm_up, [{:opts, []}]},
{:create_index, [:namespace, :set, {:opts, []}]},
{:create_expression_index, [:namespace, :set, :expression, {:opts, []}]},
{:drop_index, [:namespace, :index_name, {:opts, []}]},
{:set_xdr_filter, [:datacenter, :namespace, :filter]},
{:list_udfs, [{:opts, []}]},
{:register_udf, [:path_or_content, :server_name, {:opts, []}]},
{:remove_udf, [:server_name, {:opts, []}]},
{:truncate, [:namespace, {:set_or_opts, []}]},
{:truncate, [:namespace, :set, :opts]},
{:transaction, [:fun]},
{:transaction, [:txn_or_opts, :fun]},
{:commit, [:txn]},
{:abort, [:txn]},
{:txn_status, [:txn]}
]
@admin_delegates [
{:create_user, [:user_name, :password, :roles, {:opts, []}]},
{:create_pki_user, [:user_name, :roles, {:opts, []}]},
{:drop_user, [:user_name, {:opts, []}]},
{:change_password, [:user_name, :password, {:opts, []}]},
{:grant_roles, [:user_name, :roles, {:opts, []}]},
{:revoke_roles, [:user_name, :roles, {:opts, []}]},
{:query_user, [:user_name, {:opts, []}]},
{:query_users, [{:opts, []}]},
{:create_role, [:role_name, :privileges, {:opts, []}]},
{:drop_role, [:role_name, {:opts, []}]},
{:set_whitelist, [:role_name, :whitelist, {:opts, []}]},
{:set_quotas, [:role_name, :read_quota, :write_quota, {:opts, []}]},
{:grant_privileges, [:role_name, :privileges, {:opts, []}]},
{:revoke_privileges, [:role_name, :privileges, {:opts, []}]},
{:query_role, [:role_name, {:opts, []}]},
{:query_roles, [{:opts, []}]}
]
@doc false
defmacro __using__(opts) do
otp_app = Keyword.fetch!(opts, :otp_app)
adapter = Keyword.get(opts, :adapter, Aerospike)
conn = Keyword.get(opts, :name, __CALLER__.module)
repo_wrappers = wrapper_defs(adapter, @repo_delegates)
admin_wrappers = wrapper_defs(adapter, @admin_delegates)
quote do
@doc """
Returns the cluster name injected into generated Repo calls.
"""
@spec conn() :: atom()
def conn, do: unquote(conn)
@doc """
Returns the runtime configuration for this Repo.
Configuration is read from the `:otp_app` passed to `use Aerospike.Repo`
under the generated Repo module key. Runtime options passed to
`child_spec/1` or `start_link/1` are merged over these values. The
configured cluster `:name` defaults to `conn/0`.
"""
@spec config() :: keyword()
def config do
unquote(otp_app)
|> Application.get_env(__MODULE__, [])
|> Keyword.put_new(:name, conn())
end
@doc """
Builds a child spec for supervising this Repo's Aerospike cluster.
`opts` are cluster supervisor options merged over `config/0`.
"""
@spec child_spec(keyword()) :: Supervisor.child_spec()
def child_spec(opts) when is_list(opts) do
unquote(adapter).child_spec(Keyword.merge(config(), opts))
end
@doc """
Starts this Repo's Aerospike cluster.
`opts` are cluster supervisor options merged over `config/0`.
"""
@spec start_link(keyword()) :: Supervisor.on_start()
def start_link(opts) when is_list(opts) do
unquote(adapter).start_link(Keyword.merge(config(), opts))
end
@doc """
Stops this Repo's Aerospike cluster using the adapter default timeout.
"""
@spec close() :: :ok
def close do
unquote(adapter).close(conn())
end
@doc """
Stops this Repo's Aerospike cluster with an explicit timeout in milliseconds.
"""
@spec close(non_neg_integer()) :: :ok
def close(timeout) when is_integer(timeout) and timeout >= 0 do
unquote(adapter).close(conn(), timeout)
end
@doc """
Builds an `Aerospike.Key` for this Repo.
This is a convenience delegate to `Aerospike.key/3`; it does not attach
the Repo name to the key.
"""
@spec key(String.t(), String.t(), String.t() | integer()) :: Aerospike.Key.t()
def key(namespace, set, user_key) do
unquote(adapter).key(namespace, set, user_key)
end
@doc """
Builds an `Aerospike.Key` from an existing 20-byte digest.
"""
@spec key_digest(String.t(), String.t(), <<_::160>>) :: Aerospike.Key.t()
def key_digest(namespace, set, digest) do
unquote(adapter).key_digest(namespace, set, digest)
end
unquote_splicing(repo_wrappers)
defmodule Admin do
@moduledoc false
def conn, do: unquote(conn)
unquote_splicing(admin_wrappers)
end
end
end
defp wrapper_defs(adapter, delegates) do
Enum.map(delegates, fn {fun_name, args} ->
build_wrapper(adapter, fun_name, args)
end)
end
defp build_wrapper(adapter, fun_name, args) do
arg_defs = Enum.map(args, &arg_definition/1)
arg_values = Enum.map(args, &arg_value/1)
spec_args = Enum.map(args, fn _arg -> quote(do: term()) end)
facade_arity = length(args) + 1
doc =
"Delegates to the configured adapter's `#{fun_name}/#{facade_arity}` with `conn/0` as the first argument."
quote do
@doc unquote(doc)
@spec unquote(fun_name)(unquote_splicing(spec_args)) :: term()
def unquote(fun_name)(unquote_splicing(arg_defs)) do
unquote(adapter).unquote(fun_name)(conn(), unquote_splicing(arg_values))
end
end
end
defp arg_definition({name, default}), do: {:\\, [], [{name, [], nil}, default]}
defp arg_definition(name), do: {name, [], nil}
defp arg_value({name, _default}), do: {name, [], nil}
defp arg_value(name), do: {name, [], nil}
end