Current section

Files

Jump to
nebulex lib nebulex cache kv.ex
Raw

lib/nebulex/cache/kv.ex

defmodule Nebulex.Cache.KV do
@moduledoc false
import Nebulex.Adapter
import Nebulex.Utils, only: [is_timeout: 1, unwrap_or_raise: 1, wrap_error: 2]
alias Nebulex.Cache.Options
# Inline common instructions
@compile inline: [do_put: 7, do_put_all: 5, do_expire: 4, do_incr: 6]
@doc """
Implementation for `c:Nebulex.Cache.fetch/2`.
"""
defcommand fetch(name, key, opts)
@doc """
Implementation for `c:Nebulex.Cache.fetch!/2`.
"""
def fetch!(name, key, opts) do
unwrap_or_raise fetch(name, key, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.get/3`.
"""
def get(name, key, default, opts) do
name
|> lookup_meta()
|> do_get(key, default, opts)
end
defp do_get(adapter_meta, key, default, opts) do
with {:error, %Nebulex.KeyError{key: ^key}} <- run_command(adapter_meta, :fetch, [key], opts) do
{:ok, default}
end
end
@doc """
Implementation for `c:Nebulex.Cache.get!/3`.
"""
def get!(name, key, default, opts) do
unwrap_or_raise get(name, key, default, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.put/3`.
"""
def put(name, key, value, opts) do
with {:ok, _} <- do_put(name, key, value, :put, opts) do
:ok
end
end
@doc """
Implementation for `c:Nebulex.Cache.put!/3`.
"""
def put!(name, key, value, opts) do
_ = unwrap_or_raise do_put(name, key, value, :put, opts)
:ok
end
@doc """
Implementation for `c:Nebulex.Cache.put_new/3`.
"""
def put_new(name, key, value, opts) do
do_put(name, key, value, :put_new, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.put_new!/3`.
"""
def put_new!(name, key, value, opts) do
unwrap_or_raise put_new(name, key, value, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.replace/3`.
"""
def replace(name, key, value, opts) do
do_put(name, key, value, :replace, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.replace!/3`.
"""
def replace!(name, key, value, opts) do
unwrap_or_raise replace(name, key, value, opts)
end
defp do_put(name, key, value, on_write, opts) do
{ttl, opts} = Options.pop_and_validate_timeout!(opts, :ttl)
{keep_ttl?, opts} = Options.pop_and_validate_boolean!(opts, :keep_ttl, on_write == :replace)
do_put(name, key, value, on_write, ttl, keep_ttl?, opts)
end
defcommandp do_put(name, key, value, on_write, ttl, keep_ttl?, opts), command: :put
@doc """
Implementation for `c:Nebulex.Cache.put_all/2`.
"""
def put_all(name, entries, opts) do
with {:ok, _} <- do_put_all(name, entries, :put, opts) do
:ok
end
end
@doc """
Implementation for `c:Nebulex.Cache.put_all!/2`.
"""
def put_all!(name, entries, opts) do
_ = unwrap_or_raise do_put_all(name, entries, :put, opts)
:ok
end
@doc """
Implementation for `c:Nebulex.Cache.put_new_all/2`.
"""
def put_new_all(name, entries, opts) do
do_put_all(name, entries, :put_new, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.put_new_all!/2`.
"""
def put_new_all!(name, entries, opts) do
unwrap_or_raise put_new_all(name, entries, opts)
end
def do_put_all(_name, [], _on_write, _opts) do
{:ok, true}
end
def do_put_all(_name, %{} = entries, _on_write, _opts) when map_size(entries) == 0 do
{:ok, true}
end
def do_put_all(name, entries, on_write, opts) do
{ttl, opts} = Options.pop_and_validate_timeout!(opts, :ttl)
do_put_all(name, entries, on_write, ttl, opts)
end
defcommandp do_put_all(name, entries, on_write, ttl, opts), command: :put_all
@doc """
Implementation for `c:Nebulex.Cache.delete/2`.
"""
defcommand delete(name, key, opts)
@doc """
Implementation for `c:Nebulex.Cache.delete!/2`.
"""
def delete!(name, key, opts) do
unwrap_or_raise delete(name, key, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.take/2`.
"""
defcommand take(name, key, opts)
@doc """
Implementation for `c:Nebulex.Cache.take!/2`.
"""
def take!(name, key, opts) do
unwrap_or_raise take(name, key, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.has_key?/1`.
"""
defcommand has_key?(name, key, opts)
@doc """
Implementation for `c:Nebulex.Cache.ttl/1`.
"""
defcommand ttl(name, key, opts)
@doc """
Implementation for `c:Nebulex.Cache.ttl!/1`.
"""
def ttl!(name, key, opts) do
unwrap_or_raise ttl(name, key, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.expire/2`.
"""
def expire(name, key, ttl, opts) do
ttl =
(is_timeout(ttl) && ttl) ||
raise ArgumentError, "expected ttl to be a valid timeout, got: #{inspect(ttl)}"
do_expire(name, key, ttl, opts)
end
defcommandp do_expire(name, key, ttl, opts), command: :expire
@doc """
Implementation for `c:Nebulex.Cache.expire!/2`.
"""
def expire!(name, key, ttl, opts) do
unwrap_or_raise expire(name, key, ttl, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.touch/1`.
"""
defcommand touch(name, key, opts)
@doc """
Implementation for `c:Nebulex.Cache.touch!/1`.
"""
def touch!(name, key, opts) do
unwrap_or_raise touch(name, key, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.incr/3`.
"""
def incr(name, key, amount, opts)
def incr(name, key, amount, opts) when is_integer(amount) do
{default, opts} = Options.pop_and_validate_integer!(opts, :default)
{ttl, opts} = Options.pop_and_validate_timeout!(opts, :ttl)
do_incr(name, key, amount, default, ttl, opts)
end
def incr(_name, _key, amount, _opts) do
raise ArgumentError,
"invalid value for amount argument: expected integer, got: #{inspect(amount)}"
end
defcommandp do_incr(name, key, amount, default, ttl, opts), command: :update_counter
@doc """
Implementation for `c:Nebulex.Cache.incr!/3`.
"""
def incr!(name, key, amount, opts) do
unwrap_or_raise incr(name, key, amount, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.decr/3`.
"""
def decr(name, key, amount, opts)
def decr(name, key, amount, opts) when is_integer(amount) do
incr(name, key, amount * -1, opts)
end
def decr(_cache, _key, amount, _opts) do
raise ArgumentError,
"invalid value for amount argument: expected integer, got: #{inspect(amount)}"
end
@doc """
Implementation for `c:Nebulex.Cache.decr!/3`.
"""
def decr!(name, key, amount, opts) do
unwrap_or_raise decr(name, key, amount, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.get_and_update/3`.
"""
def get_and_update(name, key, fun, opts) when is_function(fun, 1) do
{ttl, opts} = Options.pop_and_validate_timeout!(opts, :ttl)
{keep_ttl?, opts} = Options.pop_and_validate_boolean!(opts, :keep_ttl)
adapter_meta = lookup_meta(name)
with {:ok, current} <- do_get(adapter_meta, key, nil, opts) do
{:ok, eval_get_and_update_function(current, adapter_meta, key, ttl, keep_ttl?, opts, fun)}
end
end
defp eval_get_and_update_function(current, adapter_meta, key, ttl, keep_ttl?, opts, fun) do
case fun.(current) do
{get, nil} ->
{get, get}
{get, update} ->
{:ok, true} = run_command(adapter_meta, :put, [key, update, :put, ttl, keep_ttl?], opts)
{get, update}
:pop when is_nil(current) ->
{nil, nil}
:pop ->
:ok = run_command(adapter_meta, :delete, [key], opts)
{current, nil}
other ->
raise ArgumentError,
"the given function must return a two-element tuple or :pop," <>
" got: #{inspect(other)}"
end
end
@doc """
Implementation for `c:Nebulex.Cache.get_and_update!/3`.
"""
def get_and_update!(name, key, fun, opts) do
unwrap_or_raise get_and_update(name, key, fun, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.update/4`.
"""
def update(name, key, initial, fun, opts) when is_function(fun, 1) do
{ttl, opts} = Options.pop_and_validate_timeout!(opts, :ttl)
{keep_ttl?, opts} = Options.pop_and_validate_boolean!(opts, :keep_ttl)
adapter_meta = lookup_meta(name)
value =
case run_command(adapter_meta, :fetch, [key], opts) do
{:ok, value} -> fun.(value)
{:error, %Nebulex.KeyError{key: ^key}} -> initial
{:error, _} = error -> throw({:return, error})
end
with {:ok, true} <- run_command(adapter_meta, :put, [key, value, :put, ttl, keep_ttl?], opts) do
{:ok, value}
end
catch
{:return, error} -> error
end
@doc """
Implementation for `c:Nebulex.Cache.update!/4`.
"""
def update!(name, key, initial, fun, opts) do
unwrap_or_raise update(name, key, initial, fun, opts)
end
@doc """
Implementation for `c:Nebulex.Cache.fetch_or_store/3`.
"""
def fetch_or_store(name, key, fun, opts) do
with {:error, %Nebulex.KeyError{key: ^key}} <- fetch(name, key, opts) do
eval_fetch_or_store_fun(name, key, fun, opts)
end
end
@doc """
Implementation for `c:Nebulex.Cache.fetch_or_store!/3`.
"""
def fetch_or_store!(name, key, fun, opts) do
unwrap_or_raise fetch_or_store(name, key, fun, opts)
end
defp eval_fetch_or_store_fun(name, key, fun, opts) do
case fun.() do
{:ok, value} ->
with {:ok, _} <- do_put(name, key, value, :put, opts) do
{:ok, value}
end
{:error, reason} ->
wrap_error Nebulex.Error, reason: reason, command: :fetch_or_store, key: key
other ->
raise "the supplied lambda function must return {:ok, value} " <>
"or {:error, reason}, got: #{inspect(other)}"
end
end
@doc """
Implementation for `c:Nebulex.Cache.get_or_store/3`.
"""
def get_or_store(name, key, fun, opts) do
with {:error, %Nebulex.KeyError{key: ^key}} <- fetch(name, key, opts) do
eval_get_or_store_fun(name, key, fun, opts)
end
end
@doc """
Implementation for `c:Nebulex.Cache.get_or_store!/3`.
"""
def get_or_store!(name, key, fun, opts) do
unwrap_or_raise get_or_store(name, key, fun, opts)
end
defp eval_get_or_store_fun(name, key, fun, opts) do
value = fun.()
with {:ok, _} <- do_put(name, key, value, :put, opts) do
{:ok, value}
end
end
end