From a6ef459e15c281018c0d54840bbde971a256b6fa Mon Sep 17 00:00:00 2001 From: cabol Date: Fri, 31 Mar 2023 16:12:28 +0200 Subject: [PATCH] [#189] All commands optionally support a dynamic cache as the first argument --- .formatter.exs | 6 +- guides/creating-new-adapter.md | 4 +- lib/nebulex/adapter/persistence.ex | 2 +- lib/nebulex/adapter/stats.ex | 2 +- lib/nebulex/adapter/transaction.ex | 51 +- lib/nebulex/adapters/nil.ex | 2 +- lib/nebulex/cache.ex | 1099 +++++++++++++++++------- lib/nebulex/cache/kv.ex | 4 +- lib/nebulex/cache/options.ex | 12 - lib/nebulex/cache/persistence.ex | 2 +- lib/nebulex/cache/queryable.ex | 2 +- lib/nebulex/cache/registry.ex | 2 +- lib/nebulex/cache/stats.ex | 2 +- lib/nebulex/cache/supervisor.ex | 2 +- lib/nebulex/cache/transaction.ex | 2 +- lib/nebulex/cache/utils.ex | 33 + lib/nebulex/caching/decorators.ex | 2 +- lib/nebulex/{helpers.ex => utils.ex} | 29 +- test/nebulex/cache/supervisor_test.exs | 4 +- test/nebulex/helpers_test.exs | 14 +- test/shared/cache/kv_test.exs | 12 +- test/shared/cache/transaction_test.exs | 63 +- test/support/test_adapter.exs | 8 +- 23 files changed, 916 insertions(+), 443 deletions(-) create mode 100644 lib/nebulex/cache/utils.ex rename lib/nebulex/{helpers.ex => utils.ex} (78%) diff --git a/.formatter.exs b/.formatter.exs index 9f1a7935..51b98387 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,12 +1,12 @@ locals_without_parens = [ - # Nebulex.Helpers + # Nebulex.Utils unwrap_or_raise: 1, wrap_ok: 1, wrap_error: 1, wrap_error: 2, - # Nebulex.Cache - dynamic_cache: 2, + # Nebulex.Cache.Utils + defcacheapi: 2, # Nebulex.Caching cache_ref: 1, diff --git a/guides/creating-new-adapter.md b/guides/creating-new-adapter.md index 09679a39..13980cf2 100644 --- a/guides/creating-new-adapter.md +++ b/guides/creating-new-adapter.md @@ -256,7 +256,7 @@ defmodule NebulexMemoryAdapter do @behaviour Nebulex.Adapter @behaviour Nebulex.Adapter.Queryable - import Nebulex.Helpers + import Nebulex.Utils @impl Nebulex.Adapter defmacro __before_compile__(_env), do: :ok @@ -308,7 +308,7 @@ defmodule NebulexMemoryAdapter do @behaviour Nebulex.Adapter.KV @behaviour Nebulex.Adapter.Queryable - import Nebulex.Helpers + import Nebulex.Utils @impl Nebulex.Adapter defmacro __before_compile__(_env), do: :ok diff --git a/lib/nebulex/adapter/persistence.ex b/lib/nebulex/adapter/persistence.ex index 4dfef65b..7e61e426 100644 --- a/lib/nebulex/adapter/persistence.ex +++ b/lib/nebulex/adapter/persistence.ex @@ -51,7 +51,7 @@ defmodule Nebulex.Adapter.Persistence do quote do @behaviour Nebulex.Adapter.Persistence - import Nebulex.Helpers + import Nebulex.Utils, only: [wrap_error: 2] @impl true def dump(%{cache: cache}, path, opts) do diff --git a/lib/nebulex/adapter/stats.ex b/lib/nebulex/adapter/stats.ex index f802bd2d..033d6f36 100644 --- a/lib/nebulex/adapter/stats.ex +++ b/lib/nebulex/adapter/stats.ex @@ -40,7 +40,7 @@ defmodule Nebulex.Adapter.Stats do @behaviour Nebulex.Adapter.Stats import Nebulex.Adapter.Stats, only: [defspan: 2, defspan: 3] - import Nebulex.Helpers + import Nebulex.Utils, only: [wrap_error: 2] @impl true def stats(adapter_meta, _opts) do diff --git a/lib/nebulex/adapter/transaction.ex b/lib/nebulex/adapter/transaction.ex index 9a926689..864ceec6 100644 --- a/lib/nebulex/adapter/transaction.ex +++ b/lib/nebulex/adapter/transaction.ex @@ -35,39 +35,40 @@ defmodule Nebulex.Adapter.Transaction do Locking only the involved key (recommended): - MyCache.transaction [keys: [:counter]], fn -> - counter = MyCache.get(:counter) - MyCache.set(:counter, counter + 1) - end - - MyCache.transaction [keys: [:alice, :bob]], fn -> - alice = MyCache.get(:alice) - bob = MyCache.get(:bob) - MyCache.set(:alice, %{alice | balance: alice.balance + 100}) - MyCache.set(:bob, %{bob | balance: bob.balance + 100}) - end + MyCache.transaction( + fn -> + counter = MyCache.get(:counter) + MyCache.set(:counter, counter + 1) + end, + [keys: [:counter]] + ) + + MyCache.transaction( + fn -> + alice = MyCache.get(:alice) + bob = MyCache.get(:bob) + MyCache.set(:alice, %{alice | balance: alice.balance + 100}) + MyCache.set(:bob, %{bob | balance: bob.balance + 100}) + end, + [keys: [:alice, :bob]] + ) """ @doc """ Runs the given function inside a transaction. - A successful transaction returns the value returned by the function wrapped - in a tuple as `{:ok, value}`. - - In case the transaction cannot be executed, then `{:error, reason}` is - returned. + If an Elixir exception occurs, the exception will bubble up from the + transaction function. If the transaction is aborted, + `{:error, Nebulex.Error.t()}` is returned. - If an unhandled error/exception occurs, the error will bubble up from the - transaction function. - - If `transaction/2` is called inside another transaction, the function is - simply executed without wrapping the new transaction call in any way. + A successful transaction returns the value returned by the function wrapped + in a tuple as `{:ok, any()}`. See `c:Nebulex.Cache.transaction/2`. """ - @callback transaction(Nebulex.Adapter.adapter_meta(), Nebulex.Cache.opts(), (() -> any())) :: - any() + @callback transaction(Nebulex.Adapter.adapter_meta(), fun(), Nebulex.Cache.opts()) :: + Nebulex.Cache.ok_error_tuple(any()) @doc """ Returns `{:ok, true}` if the current process is inside a transaction, @@ -85,10 +86,10 @@ defmodule Nebulex.Adapter.Transaction do quote do @behaviour Nebulex.Adapter.Transaction - import Nebulex.Helpers + import Nebulex.Utils, only: [wrap_ok: 1, wrap_error: 2] @impl true - def transaction(%{cache: cache, pid: pid} = adapter_meta, opts, fun) do + def transaction(%{cache: cache, pid: pid} = adapter_meta, fun, opts) do adapter_meta |> do_in_transaction?() |> do_transaction( diff --git a/lib/nebulex/adapters/nil.ex b/lib/nebulex/adapters/nil.ex index 531f0178..9295c367 100644 --- a/lib/nebulex/adapters/nil.ex +++ b/lib/nebulex/adapters/nil.ex @@ -70,7 +70,7 @@ defmodule Nebulex.Adapters.Nil do # Inherit default transaction implementation use Nebulex.Adapter.Transaction - import Nebulex.Helpers + import Nebulex.Utils, only: [wrap_error: 2] ## Nebulex.Adapter diff --git a/lib/nebulex/cache.ex b/lib/nebulex/cache.ex index d945b37e..16c34f7a 100644 --- a/lib/nebulex/cache.ex +++ b/lib/nebulex/cache.ex @@ -269,6 +269,9 @@ defmodule Nebulex.Cache do @typedoc "Cache entry value" @type value() :: any() + @typedoc "Dynamic cache value" + @type dynamic_cache() :: atom() | pid() + @typedoc "Cache entries" @type entries() :: map() | [{key(), value()}] @@ -293,12 +296,16 @@ defmodule Nebulex.Cache do @typedoc "Ok/Error type" @type ok_error_tuple(ok, error) :: {:ok, ok} | {:error, error} + ## API + + import __MODULE__.Utils + @doc false defmacro __using__(opts) do quote do unquote(prelude(opts)) unquote(base_defs()) - unquote(entry_defs()) + unquote(kv_defs()) if Nebulex.Adapter.Queryable in behaviours do unquote(queryable_defs()) @@ -335,26 +342,6 @@ defmodule Nebulex.Cache do defp base_defs do quote do - ## Helpers - - import Nebulex.Helpers, only: [kw_pop_first_lazy: 3] - - @doc """ - Helper macro to resolve the dynamic cache. - """ - defmacro dynamic_cache(opts, do: block) do - quote do - {dynamic_cache, opts} = - kw_pop_first_lazy( - unquote(opts), - :dynamic_cache, - fn -> get_dynamic_cache() end - ) - - unquote(block) - end - end - ## Config and metadata @impl true @@ -388,9 +375,12 @@ defmodule Nebulex.Cache do @impl true def stop(opts \\ []) do - dynamic_cache opts do - Supervisor.stop(dynamic_cache, :normal, Keyword.get(opts, :timeout, 5000)) - end + stop(get_dynamic_cache(), opts) + end + + @impl true + def stop(name, opts) do + Supervisor.stop(name, :normal, Keyword.get(opts, :timeout, 5000)) end # Iniline common instructions @@ -421,184 +411,79 @@ defmodule Nebulex.Cache do end end - defp entry_defs do + defp kv_defs do quote do alias Nebulex.Cache.KV - @impl true - def fetch(key, opts \\ []) do - dynamic_cache opts, do: KV.fetch(dynamic_cache, key, opts) - end + defcacheapi fetch(key, opts \\ []), to: KV - @impl true - def fetch!(key, opts \\ []) do - dynamic_cache opts, do: KV.fetch!(dynamic_cache, key, opts) - end + defcacheapi fetch!(key, opts \\ []), to: KV - @impl true - def get(key, default \\ nil, opts \\ []) do - dynamic_cache opts, do: KV.get(dynamic_cache, key, default, opts) - end + defcacheapi get(key, default \\ nil, opts \\ []), to: KV - @impl true - def get!(key, default \\ nil, opts \\ []) do - dynamic_cache opts, do: KV.get!(dynamic_cache, key, default, opts) - end + defcacheapi get!(key, default \\ nil, opts \\ []), to: KV - @impl true - def get_all(keys, opts \\ []) do - dynamic_cache opts, do: KV.get_all(dynamic_cache, keys, opts) - end + defcacheapi get_all(keys, opts \\ []), to: KV - @impl true - def get_all!(keys, opts \\ []) do - dynamic_cache opts, do: KV.get_all!(dynamic_cache, keys, opts) - end + defcacheapi get_all!(keys, opts \\ []), to: KV - @impl true - def put(key, value, opts \\ []) do - dynamic_cache opts, do: KV.put(dynamic_cache, key, value, opts) - end + defcacheapi put(key, value, opts \\ []), to: KV - @impl true - def put!(key, value, opts \\ []) do - dynamic_cache opts, do: KV.put!(dynamic_cache, key, value, opts) - end + defcacheapi put!(key, value, opts \\ []), to: KV - @impl true - def put_new(key, value, opts \\ []) do - dynamic_cache opts, do: KV.put_new(dynamic_cache, key, value, opts) - end + defcacheapi put_new(key, value, opts \\ []), to: KV - @impl true - def put_new!(key, value, opts \\ []) do - dynamic_cache opts, do: KV.put_new!(dynamic_cache, key, value, opts) - end + defcacheapi put_new!(key, value, opts \\ []), to: KV - @impl true - def replace(key, value, opts \\ []) do - dynamic_cache opts, do: KV.replace(dynamic_cache, key, value, opts) - end + defcacheapi replace(key, value, opts \\ []), to: KV - @impl true - def replace!(key, value, opts \\ []) do - dynamic_cache opts, do: KV.replace!(dynamic_cache, key, value, opts) - end + defcacheapi replace!(key, value, opts \\ []), to: KV - @impl true - def put_all(entries, opts \\ []) do - dynamic_cache opts, do: KV.put_all(dynamic_cache, entries, opts) - end + defcacheapi put_all(entries, opts \\ []), to: KV - @impl true - def put_all!(entries, opts \\ []) do - dynamic_cache opts, do: KV.put_all!(dynamic_cache, entries, opts) - end + defcacheapi put_all!(entries, opts \\ []), to: KV - @impl true - def put_new_all(entries, opts \\ []) do - dynamic_cache opts, do: KV.put_new_all(dynamic_cache, entries, opts) - end + defcacheapi put_new_all(entries, opts \\ []), to: KV - @impl true - def put_new_all!(entries, opts \\ []) do - dynamic_cache opts, do: KV.put_new_all!(dynamic_cache, entries, opts) - end + defcacheapi put_new_all!(entries, opts \\ []), to: KV - @impl true - def delete(key, opts \\ []) do - dynamic_cache opts, do: KV.delete(dynamic_cache, key, opts) - end + defcacheapi delete(key, opts \\ []), to: KV - @impl true - def delete!(key, opts \\ []) do - dynamic_cache opts, do: KV.delete!(dynamic_cache, key, opts) - end + defcacheapi delete!(key, opts \\ []), to: KV - @impl true - def take(key, opts \\ []) do - dynamic_cache opts, do: KV.take(dynamic_cache, key, opts) - end + defcacheapi take(key, opts \\ []), to: KV - @impl true - def take!(key, opts \\ []) do - dynamic_cache opts, do: KV.take!(dynamic_cache, key, opts) - end + defcacheapi take!(key, opts \\ []), to: KV - @impl true - def has_key?(key, opts \\ []) do - dynamic_cache opts, do: KV.has_key?(dynamic_cache, key, opts) - end + defcacheapi has_key?(key, opts \\ []), to: KV - @impl true - def get_and_update(key, fun, opts \\ []) do - dynamic_cache opts, do: KV.get_and_update(dynamic_cache, key, fun, opts) - end + defcacheapi get_and_update(key, fun, opts \\ []), to: KV - @impl true - def get_and_update!(key, fun, opts \\ []) do - dynamic_cache opts, do: KV.get_and_update!(dynamic_cache, key, fun, opts) - end + defcacheapi get_and_update!(key, fun, opts \\ []), to: KV - @impl true - def update(key, initial, fun, opts \\ []) do - dynamic_cache opts, do: KV.update(dynamic_cache, key, initial, fun, opts) - end + defcacheapi update(key, initial, fun, opts \\ []), to: KV - @impl true - def update!(key, initial, fun, opts \\ []) do - dynamic_cache opts, do: KV.update!(get_dynamic_cache(), key, initial, fun, opts) - end + defcacheapi update!(key, initial, fun, opts \\ []), to: KV - @impl true - def incr(key, amount \\ 1, opts \\ []) do - dynamic_cache opts, do: KV.incr(dynamic_cache, key, amount, opts) - end + defcacheapi incr(key, amount \\ 1, opts \\ []), to: KV - @impl true - def incr!(key, amount \\ 1, opts \\ []) do - dynamic_cache opts, do: KV.incr!(dynamic_cache, key, amount, opts) - end + defcacheapi incr!(key, amount \\ 1, opts \\ []), to: KV - @impl true - def decr(key, amount \\ 1, opts \\ []) do - dynamic_cache opts, do: KV.decr(dynamic_cache, key, amount, opts) - end + defcacheapi decr(key, amount \\ 1, opts \\ []), to: KV - @impl true - def decr!(key, amount \\ 1, opts \\ []) do - dynamic_cache opts, do: KV.decr!(dynamic_cache, key, amount, opts) - end + defcacheapi decr!(key, amount \\ 1, opts \\ []), to: KV - @impl true - def ttl(key, opts \\ []) do - dynamic_cache opts, do: KV.ttl(dynamic_cache, key, opts) - end + defcacheapi ttl(key, opts \\ []), to: KV - @impl true - def ttl!(key, opts \\ []) do - dynamic_cache opts, do: KV.ttl!(dynamic_cache, key, opts) - end + defcacheapi ttl!(key, opts \\ []), to: KV - @impl true - def expire(key, ttl, opts \\ []) do - dynamic_cache opts, do: KV.expire(dynamic_cache, key, ttl, opts) - end + defcacheapi expire(key, ttl, opts \\ []), to: KV - @impl true - def expire!(key, ttl, opts \\ []) do - dynamic_cache opts, do: KV.expire!(dynamic_cache, key, ttl, opts) - end + defcacheapi expire!(key, ttl, opts \\ []), to: KV - @impl true - def touch(key, opts \\ []) do - dynamic_cache opts, do: KV.touch(dynamic_cache, key, opts) - end + defcacheapi touch(key, opts \\ []), to: KV - @impl true - def touch!(key, opts \\ []) do - dynamic_cache opts, do: KV.touch!(dynamic_cache, key, opts) - end + defcacheapi touch!(key, opts \\ []), to: KV end end @@ -606,45 +491,21 @@ defmodule Nebulex.Cache do quote do alias Nebulex.Cache.Queryable - @impl true - def all(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.all(dynamic_cache, query, opts) - end + defcacheapi all(query \\ nil, opts \\ []), to: Queryable - @impl true - def all!(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.all!(dynamic_cache, query, opts) - end + defcacheapi all!(query \\ nil, opts \\ []), to: Queryable - @impl true - def count_all(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.count_all(dynamic_cache, query, opts) - end + defcacheapi count_all(query \\ nil, opts \\ []), to: Queryable - @impl true - def count_all!(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.count_all!(dynamic_cache, query, opts) - end + defcacheapi count_all!(query \\ nil, opts \\ []), to: Queryable - @impl true - def delete_all(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.delete_all(dynamic_cache, query, opts) - end + defcacheapi delete_all(query \\ nil, opts \\ []), to: Queryable - @impl true - def delete_all!(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.delete_all!(dynamic_cache, query, opts) - end + defcacheapi delete_all!(query \\ nil, opts \\ []), to: Queryable - @impl true - def stream(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.stream(dynamic_cache, query, opts) - end + defcacheapi stream(query \\ nil, opts \\ []), to: Queryable - @impl true - def stream!(query \\ nil, opts \\ []) do - dynamic_cache opts, do: Queryable.stream!(dynamic_cache, query, opts) - end + defcacheapi stream!(query \\ nil, opts \\ []), to: Queryable end end @@ -652,25 +513,13 @@ defmodule Nebulex.Cache do quote do alias Nebulex.Cache.Persistence - @impl true - def dump(path, opts \\ []) do - dynamic_cache opts, do: Persistence.dump(dynamic_cache, path, opts) - end + defcacheapi dump(path, opts \\ []), to: Persistence - @impl true - def dump!(path, opts \\ []) do - dynamic_cache opts, do: Persistence.dump!(dynamic_cache, path, opts) - end + defcacheapi dump!(path, opts \\ []), to: Persistence - @impl true - def load(path, opts \\ []) do - dynamic_cache opts, do: Persistence.load(dynamic_cache, path, opts) - end + defcacheapi load(path, opts \\ []), to: Persistence - @impl true - def load!(path, opts \\ []) do - dynamic_cache opts, do: Persistence.load!(dynamic_cache, path, opts) - end + defcacheapi load!(path, opts \\ []), to: Persistence end end @@ -678,15 +527,9 @@ defmodule Nebulex.Cache do quote do alias Nebulex.Cache.Transaction - @impl true - def transaction(opts \\ [], fun) do - dynamic_cache opts, do: Transaction.transaction(dynamic_cache, opts, fun) - end + defcacheapi transaction(fun, opts \\ []), to: Transaction - @impl true - def in_transaction?(opts \\ []) do - dynamic_cache opts, do: Transaction.in_transaction?(dynamic_cache, opts) - end + defcacheapi in_transaction?(opts \\ []), to: Transaction end end @@ -694,20 +537,11 @@ defmodule Nebulex.Cache do quote do alias Nebulex.Cache.Stats - @impl true - def stats(opts \\ []) do - dynamic_cache opts, do: Stats.stats(dynamic_cache, opts) - end + defcacheapi stats(opts \\ []), to: Stats - @impl true - def stats!(opts \\ []) do - dynamic_cache opts, do: Stats.stats!(dynamic_cache, opts) - end + defcacheapi stats!(opts \\ []), to: Stats - @impl true - def dispatch_stats(opts \\ []) do - dynamic_cache opts, do: Stats.dispatch_stats(dynamic_cache, opts) - end + defcacheapi dispatch_stats(opts \\ []), to: Stats end end @@ -787,6 +621,16 @@ defmodule Nebulex.Cache do @doc group: "Runtime API" @callback stop(opts()) :: :ok + @doc """ + Same as `c:stop/1` but stops the cache instance given in the first argument + `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + """ + @doc group: "Runtime API" + @callback stop(dynamic_cache(), opts()) :: :ok + @doc """ Returns the atom name or pid of the current cache (based on Ecto dynamic repo). @@ -794,34 +638,32 @@ defmodule Nebulex.Cache do See also `c:put_dynamic_cache/1`. """ @doc group: "Runtime API" - @callback get_dynamic_cache() :: atom() | pid() + @callback get_dynamic_cache() :: dynamic_cache() @doc """ Sets the dynamic cache to be used in further commands (based on Ecto dynamic repo). - There might be cases where we want to have different cache instances but + There are cases where you may want to have different cache instances but access them through the same cache module. By default, when you call `MyApp.Cache.start_link/1`, it will start a cache with the name `MyApp.Cache`. But it is also possible to start multiple caches by using a different name for each of them: MyApp.Cache.start_link(name: :cache1) - MyApp.Cache.start_link(name: :cache2, backend: :shards) + MyApp.Cache.start_link(name: :cache2) You can also start caches without names by explicitly setting the name to `nil`: - MyApp.Cache.start_link(name: nil, backend: :shards) + MyApp.Cache.start_link(name: nil) > **NOTE:** There may be adapters requiring the `:name` option anyway, therefore, it is highly recommended to see the adapter's documentation you want to use. - However, once the cache is started, it is not possible to interact directly - with it, since all operations through `MyApp.Cache` are sent by default to - the cache named `MyApp.Cache`. But you can change the default cache at - compile-time: + All operations through `MyApp.Cache` are sent by default to the cache named + `MyApp.Cache`. But you can change the default cache at compile-time: use Nebulex.Cache, default_dynamic_cache: :cache_name @@ -831,9 +673,15 @@ defmodule Nebulex.Cache do From this moment on, all future commands performed by the current process will run on `:another_cache_name`. + + Additionally, all cache commands optionally support passing the wanted + dynamic cache (name or PID) as the first argument so you can o directly + interact with a cache instance. See the + ["Dynamic caches"](#module-dynamic-caches) section at the module + documentation for more information. """ @doc group: "Runtime API" - @callback put_dynamic_cache(atom() | pid()) :: atom() | pid() + @callback put_dynamic_cache(dynamic_cache()) :: dynamic_cache() @doc """ Invokes the given function `fun` for the dynamic cache `name_or_pid`. @@ -847,7 +695,7 @@ defmodule Nebulex.Cache do See `c:get_dynamic_cache/0` and `c:put_dynamic_cache/1`. """ @doc group: "Runtime API" - @callback with_dynamic_cache(name_or_pid :: atom() | pid(), (() -> any())) :: any() + @callback with_dynamic_cache(dynamic_cache(), fun()) :: any() ## Nebulex.Adapter.KV @@ -872,7 +720,6 @@ defmodule Nebulex.Cache do iex> MyCache.put("foo", "bar") :ok - iex> MyCache.fetch("foo") {:ok, "bar"} @@ -883,6 +730,21 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback fetch(key(), opts()) :: ok_error_tuple(value(), fetch_error_reason()) + @doc """ + Same as `c:fetch/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.fetch(MyCache1, "key", []) + + """ + @doc group: "KV API" + @callback fetch(dynamic_cache(), key(), opts()) :: ok_error_tuple(value(), fetch_error_reason()) + @doc """ Same as `c:fetch/2` but raises `Nebulex.KeyError` if the cache doesn't contain `key`, or `Nebulex.Error` if any other error occurs while executing @@ -891,6 +753,14 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback fetch!(key(), opts()) :: value() + @doc """ + Same as `c:fetch/3` but raises `Nebulex.KeyError` if the cache doesn't + contain `key`, or `Nebulex.Error` if any other error occurs while executing + the command. + """ + @doc group: "KV API" + @callback fetch!(dynamic_cache(), key(), opts()) :: value() + @doc """ Gets a value from cache where the key matches the given `key`. @@ -911,7 +781,6 @@ defmodule Nebulex.Cache do iex> MyCache.put("foo", "bar") :ok - iex> MyCache.get("foo") {:ok, "bar"} @@ -925,18 +794,40 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback get(key(), default :: value(), opts()) :: ok_error_tuple(value()) + @doc """ + Same as `c:get/3`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.get(MyCache1, "key", nil, []) + + """ + @doc group: "KV API" + @callback get(dynamic_cache(), key(), default :: value(), opts()) :: ok_error_tuple(value()) + @doc """ Same as `c:get/3` but raises an exception if an error occurs. """ @doc group: "KV API" @callback get!(key(), default :: value(), opts()) :: value() + @doc """ + Same as `c:get!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback get!(dynamic_cache(), key(), default :: value(), opts()) :: value() + @doc """ Returns a map in the shape of `{:ok, map}` with the key-value pairs of all specified `keys`. For every key that does not hold a value or does not exist, it is ignored and not added into the returned map. - Returns `{:error, reason}` if an error occurs while executing the command. + Returns `{:error, Nebulex.Error.t()}` if an error occurs while executing + the command. ## Options @@ -947,7 +838,6 @@ defmodule Nebulex.Cache do iex> MyCache.put_all([a: 1, c: 3]) :ok - iex> MyCache.get_all([:a, :b, :c]) {:ok, %{a: 1, c: 3}} @@ -955,12 +845,33 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback get_all(keys :: [key()], opts()) :: ok_error_tuple(map()) + @doc """ + Same as `c:get_all/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.get_all(MyCache1, [:a, :b, :c], []) + + """ + @doc group: "KV API" + @callback get_all(dynamic_cache(), keys :: [key()], opts()) :: ok_error_tuple(map()) + @doc """ Same as `c:get_all/2` but raises an exception if an error occurs. """ @doc group: "KV API" @callback get_all!(keys :: [key()], opts()) :: map() + @doc """ + Same as `c:get_all!/3` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback get_all!(dynamic_cache(), keys :: [key()], opts()) :: map() + @doc """ Puts the given `value` under `key` into the Cache. @@ -968,7 +879,8 @@ defmodule Nebulex.Cache do time to live associated with the key is discarded on successful `put` operation. - Returns `:ok` if successful, or `{:error, reason}` if an error occurs. + Returns `:ok` if successful, or `{:error, Nebulex.Error.t()}` + if an error occurs. ## Options @@ -995,24 +907,48 @@ defmodule Nebulex.Cache do iex> MyCache.put("foo", "bar", ttl: :timer.minutes(1)) :ok - iex> MyCache.put("foo", "bar", ttl: :timer.seconds(1)) + iex> MyCache.put("foo", "bar", ttl: :timer.seconds(30)) :ok """ @doc group: "KV API" @callback put(key(), value(), opts()) :: :ok | error_tuple() + @doc """ + Same as `c:put/3`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.put(MyCache1, "foo", "bar", []) + + MyCache.put(MyCache2, "foo", "bar", ttl: :timer.hours(1)) + + """ + @doc group: "KV API" + @callback put(dynamic_cache(), key(), value(), opts()) :: :ok | error_tuple() + @doc """ Same as `c:put/3` but raises an exception if an error occurs. """ @doc group: "KV API" @callback put!(key(), value(), opts()) :: :ok + @doc """ + Same as `c:put!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback put!(dynamic_cache(), key(), value(), opts()) :: :ok + @doc """ Puts the given `entries` (key/value pairs) into the cache. It replaces existing values with new values (just as regular `put`). - Returns `:ok` if successful, or `{:error, reason}` if an error occurs. + Returns `:ok` if successful, or `{:error, Nebulex.Error.t()}` + if an error occurs. ## Options @@ -1028,7 +964,7 @@ defmodule Nebulex.Cache do iex> MyCache.put_all(apples: 3, bananas: 1) :ok - iex> MyCache.put_all(%{apples: 2, oranges: 1}, ttl: 10_000) + iex> MyCache.put_all(%{apples: 2, oranges: 1}, ttl: :timer.hours(1)) :ok **NOTE:** Ideally, this operation should be atomic, so all given keys are @@ -1039,12 +975,35 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback put_all(entries(), opts()) :: :ok | error_tuple() + @doc """ + Same as `c:put_all/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.put_all(MyCache1, [apples: 3, bananas: 1], []) + + MyCache.put_all(MyCache1, %{apples: 2, oranges: 1}, ttl: :timer.hours(1)) + + """ + @doc group: "KV API" + @callback put_all(dynamic_cache(), entries(), opts()) :: :ok | error_tuple() + @doc """ Same as `c:put_all/2` but raises an exception if an error occurs. """ @doc group: "KV API" @callback put_all!(entries(), opts()) :: :ok + @doc """ + Same as `c:put_all!/3` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback put_all!(dynamic_cache(), entries(), opts()) :: :ok + @doc """ Puts the given `value` under `key` into the cache, only if it does not already exist. @@ -1052,7 +1011,7 @@ defmodule Nebulex.Cache do Returns `{:ok, true}` if a value was set, otherwise, `{:ok, false}` is returned. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1067,20 +1026,42 @@ defmodule Nebulex.Cache do iex> MyCache.put_new("foo", "bar") {:ok, true} - - iex> MyCache.put_new("foo", "bar") + iex> MyCache.put_new("foo", "bar", ttt: :timer.hours(1)) {:ok, false} """ @doc group: "KV API" @callback put_new(key(), value(), opts()) :: ok_error_tuple(boolean()) + @doc """ + Same as `c:put_new/3`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.put_new(MyCache1, "foo", "bar", []) + + MyCache.put_new(MyCache1, "foo", "bar", ttt: :timer.hours(1)) + + """ + @doc group: "KV API" + @callback put_new(dynamic_cache(), key(), value(), opts()) :: ok_error_tuple(boolean()) + @doc """ Same as `c:put_new/3` but raises an exception if an error occurs. """ @doc group: "KV API" @callback put_new!(key(), value(), opts()) :: boolean() + @doc """ + Same as `c:put_new!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback put_new!(dynamic_cache(), key(), value(), opts()) :: boolean() + @doc """ Puts the given `entries` (key/value pairs) into the `cache`. It will not perform any operation at all even if just a single key already exists. @@ -1088,7 +1069,7 @@ defmodule Nebulex.Cache do Returns `{:ok, true}` if all entries were successfully set, or `{:ok, false}` if no key was set (at least one key already existed). - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1103,8 +1084,7 @@ defmodule Nebulex.Cache do iex> MyCache.put_new_all(apples: 3, bananas: 1) {:ok, true} - - iex> MyCache.put_new_all(%{apples: 3, oranges: 1}, ttl: 10_000) + iex> MyCache.put_new_all(%{apples: 3, oranges: 1}, ttl: :timer.hours(1)) {:ok, false} **NOTE:** Ideally, this operation should be atomic, so all given keys are @@ -1115,12 +1095,35 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback put_new_all(entries(), opts()) :: ok_error_tuple(boolean()) + @doc """ + Same as `c:put_new_all/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.put_new_all(MyCache1, [apples: 3, bananas: 1], []) + + MyCache.put_new_all(MyCache1, %{apples: 3, oranges: 1}, ttl: 10_000) + + """ + @doc group: "KV API" + @callback put_new_all(dynamic_cache(), entries(), opts()) :: ok_error_tuple(boolean()) + @doc """ Same as `c:put_new_all/2` but raises an exception if an error occurs. """ @doc group: "KV API" @callback put_new_all!(entries(), opts()) :: boolean() + @doc """ + Same as `c:put_new_all!/3` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback put_new_all!(dynamic_cache(), entries(), opts()) :: boolean() + @doc """ Alters the entry stored under `key`, but only if the entry already exists into the Cache. @@ -1128,7 +1131,7 @@ defmodule Nebulex.Cache do Returns `{:ok, true}` if a value was set, otherwise, `{:ok, false}` is returned. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1143,10 +1146,8 @@ defmodule Nebulex.Cache do iex> MyCache.replace("foo", "bar") {:ok, false} - iex> MyCache.put_new("foo", "bar") {:ok, true} - iex> MyCache.replace("foo", "bar2") {:ok, true} @@ -1159,16 +1160,39 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback replace(key(), value(), opts()) :: ok_error_tuple(boolean()) + @doc """ + Same as `c:replace/3`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + MyCache.replace(MyCache1, "foo", "bar", []) + + MyCache.replace(MyCache1, "foo", "bar", ttl: :timer.hours(1)) + + """ + @doc group: "KV API" + @callback replace(dynamic_cache(), key(), value(), opts()) :: ok_error_tuple(boolean()) + @doc """ Same as `c:replace/3` but raises an exception if an error occurs. """ @doc group: "KV API" @callback replace!(key(), value(), opts()) :: boolean() + @doc """ + Same as `c:replace!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback replace!(dynamic_cache(), key(), value(), opts()) :: boolean() + @doc """ Deletes the entry in cache for a specific `key`. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1179,10 +1203,8 @@ defmodule Nebulex.Cache do iex> MyCache.put(:a, 1) :ok - iex> MyCache.delete(:a) :ok - iex> MyCache.get!(:a) nil @@ -1193,12 +1215,33 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback delete(key(), opts()) :: :ok | error_tuple() + @doc """ + Same as `c:delete/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Example + + iex> MyCache.delete(MyCache1, :a, []) + + """ + @doc group: "KV API" + @callback delete(dynamic_cache(), key(), opts()) :: :ok | error_tuple() + @doc """ Same as `c:delete/2` but raises an exception if an error occurs. """ @doc group: "KV API" @callback delete!(key(), opts()) :: :ok + @doc """ + Same as `c:delete!/3` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback delete!(dynamic_cache(), key(), opts()) :: :ok + @doc """ Removes and returns the value associated with `key` in the cache. @@ -1220,16 +1263,30 @@ defmodule Nebulex.Cache do iex> MyCache.put(:a, 1) :ok - iex> MyCache.take(:a) {:ok, 1} - iex> {:error, %Nebulex.KeyError{key: :a}} = MyCache.take(:a) - _error + iex> {:error, %Nebulex.KeyError{key: :a}} = MyCache.take(:a) + _error + + """ + @doc group: "KV API" + @callback take(key(), opts()) :: ok_error_tuple(value(), fetch_error_reason()) + + @doc """ + Same as `c:take/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.take(MyCache1, :a, []) """ @doc group: "KV API" - @callback take(key(), opts()) :: ok_error_tuple(value(), fetch_error_reason()) + @callback take(dynamic_cache(), key(), opts()) :: ok_error_tuple(value(), fetch_error_reason()) @doc """ Same as `c:take/2` but raises an exception if an error occurs. @@ -1237,13 +1294,19 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback take!(key(), opts()) :: value() + @doc """ + Same as `c:take!/3` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback take!(dynamic_cache(), key(), opts()) :: value() + @doc """ Determines if the cache contains an entry for the specified `key`. More formally, returns `{:ok, true}` if the cache contains the given `key`. If the cache doesn't contain `key`, `{:ok, :false}` is returned. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1254,7 +1317,6 @@ defmodule Nebulex.Cache do iex> MyCache.put(:a, 1) :ok - iex> MyCache.has_key?(:a) {:ok, true} @@ -1265,6 +1327,21 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback has_key?(key(), opts()) :: ok_error_tuple(boolean()) + @doc """ + Same as `c:has_key?/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.has_key?(MyCache1, :a, []) + + """ + @doc group: "KV API" + @callback has_key?(dynamic_cache(), key(), opts()) :: ok_error_tuple(boolean()) + @doc """ Increments the counter stored at `key` by the given `amount`, and returns the current count in the shape of `{:ok, count}`. @@ -1272,7 +1349,7 @@ defmodule Nebulex.Cache do If `amount < 0` (negative), the value is decremented by that `amount` instead. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1291,10 +1368,8 @@ defmodule Nebulex.Cache do iex> MyCache.incr(:a) {:ok, 1} - iex> MyCache.incr(:a, 2) {:ok, 3} - iex> MyCache.incr(:a, -1) {:ok, 2} @@ -1305,12 +1380,33 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback incr(key(), amount :: integer(), opts()) :: ok_error_tuple(integer()) + @doc """ + Same as `c:incr/3`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.incr(MyCache1, :a, 1, []) + + """ + @doc group: "KV API" + @callback incr(dynamic_cache(), key(), amount :: integer(), opts()) :: ok_error_tuple(integer()) + @doc """ Same as `c:incr/3` but raises an exception if an error occurs. """ @doc group: "KV API" @callback incr!(key(), amount :: integer(), opts()) :: integer() + @doc """ + Same as `c:incr!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback incr!(dynamic_cache(), key(), amount :: integer(), opts()) :: integer() + @doc """ Decrements the counter stored at `key` by the given `amount`, and returns the current count in the shape of `{:ok, count}`. @@ -1318,7 +1414,7 @@ defmodule Nebulex.Cache do If `amount < 0` (negative), the value is incremented by that `amount` instead (opposite to `incr/3`). - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1337,10 +1433,8 @@ defmodule Nebulex.Cache do iex> MyCache.decr(:a) {:ok, -1} - iex> MyCache.decr(:a, 2) {:ok, -3} - iex> MyCache.decr(:a, -1) {:ok, -2} @@ -1351,12 +1445,33 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback decr(key(), amount :: integer(), opts()) :: ok_error_tuple(integer()) + @doc """ + Same as `c:decr/3`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.decr(MyCache1, :a, 1, []) + + """ + @doc group: "KV API" + @callback decr(dynamic_cache(), key(), amount :: integer(), opts()) :: ok_error_tuple(integer()) + @doc """ Same as `c:decr/3` but raises an exception if an error occurs. """ @doc group: "KV API" @callback decr!(key(), amount :: integer(), opts()) :: integer() + @doc """ + Same as `c:decr!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback decr!(dynamic_cache(), key(), amount :: integer(), opts()) :: integer() + @doc """ Returns the remaining time-to-live for the given `key`. @@ -1378,13 +1493,10 @@ defmodule Nebulex.Cache do iex> MyCache.put(:a, 1, ttl: 5000) :ok - iex> MyCache.put(:b, 2) :ok - iex> MyCache.ttl(:a) {:ok, _remaining_ttl} - iex> MyCache.ttl(:b) {:ok, :infinity} @@ -1395,17 +1507,38 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback ttl(key(), opts()) :: ok_error_tuple(timeout(), fetch_error_reason()) + @doc """ + Same as `c:ttl/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.ttl(MyCache1, :a, []) + + """ + @doc group: "KV API" + @callback ttl(dynamic_cache(), key(), opts()) :: ok_error_tuple(timeout(), fetch_error_reason()) + @doc """ Same as `c:ttl/2` but raises an exception if an error occurs. """ @doc group: "KV API" @callback ttl!(key(), opts()) :: timeout() + @doc """ + Same as `c:ttl!/3` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback ttl!(dynamic_cache(), key(), opts()) :: timeout() + @doc """ Returns `{:ok, true}` if the given `key` exists and the new `ttl` was successfully updated, otherwise, `{:ok, false}` is returned. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1416,10 +1549,8 @@ defmodule Nebulex.Cache do iex> MyCache.put(:a, 1) :ok - - iex> MyCache.expire(:a, 5) + iex> MyCache.expire(:a, :timer.hours(1)) {:ok, true} - iex> MyCache.expire(:a, :infinity) {:ok, true} @@ -1430,17 +1561,38 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback expire(key(), ttl :: timeout(), opts()) :: ok_error_tuple(boolean()) + @doc """ + Same as `c:expire/3`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.expire(MyCache1, :a, :timer.hours(1), []) + + """ + @doc group: "KV API" + @callback expire(dynamic_cache(), key(), ttl :: timeout(), opts()) :: ok_error_tuple(boolean()) + @doc """ Same as `c:expire/3` but raises an exception if an error occurs. """ @doc group: "KV API" @callback expire!(key(), ttl :: timeout(), opts()) :: boolean() + @doc """ + Same as `c:expire!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback expire!(dynamic_cache(), key(), ttl :: timeout(), opts()) :: boolean() + @doc """ Returns `{:ok, true}` if the given `key` exists and the last access time was successfully updated, otherwise, `{:ok, false}` is returned. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -1451,7 +1603,6 @@ defmodule Nebulex.Cache do iex> MyCache.put(:a, 1) :ok - iex> MyCache.touch(:a) {:ok, true} @@ -1462,12 +1613,33 @@ defmodule Nebulex.Cache do @doc group: "KV API" @callback touch(key(), opts()) :: ok_error_tuple(boolean()) + @doc """ + Same as `c:touch/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.touch(MyCache1, :a, []) + + """ + @doc group: "KV API" + @callback touch(dynamic_cache(), key(), opts()) :: ok_error_tuple(boolean()) + @doc """ Same as `c:touch/2` but raises an exception if an error occurs. """ @doc group: "KV API" @callback touch!(key(), opts()) :: boolean() + @doc """ + Same as `c:touch!/3` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback touch!(dynamic_cache(), key(), opts()) :: boolean() + @doc """ Gets the value from `key` and updates it, all in one pass. @@ -1482,7 +1654,8 @@ defmodule Nebulex.Cache do * `{:ok, {current_value, new_value}}` - The `current_value` is the current cached value and `new_value` the updated one returned by `fun`. - * `{:error, reason}` - an error occurred while executing the command. + * `{:error, Nebulex.Error.t()}` - An error occurred while executing + the command. ## Options @@ -1525,6 +1698,27 @@ defmodule Nebulex.Cache do ok_error_tuple({current_value, new_value}) when current_value: value(), new_value: value() + @doc """ + Same as `c:get_and_update/3`, but the command is executed on the cache + instance given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.get_and_update(MyCache1, :a, &{&1, "value!"}, []) + + """ + @doc group: "KV API" + @callback get_and_update( + dynamic_cache(), + key(), + (value() -> {current_value, new_value} | :pop), + opts() + ) :: ok_error_tuple({current_value, new_value}) + when current_value: value(), new_value: value() + @doc """ Same as `c:get_and_update/3` but raises an exception if an error occurs. """ @@ -1533,6 +1727,18 @@ defmodule Nebulex.Cache do {current_value, new_value} when current_value: value(), new_value: value() + @doc """ + Same as `c:get_and_update!/4` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback get_and_update!( + dynamic_cache(), + key(), + (value() -> {current_value, new_value} | :pop), + opts() + ) :: {current_value, new_value} + when current_value: value(), new_value: value() + @doc """ Updates the cached `key` with the given function. @@ -1545,7 +1751,8 @@ defmodule Nebulex.Cache do * `{:ok, value}` - The value associated to the given `key` has been updated. - * `{:error, reason}` - an error occurred while executing the command. + * `{:error, Nebulex.Error.t()}` - An error occurred while executing the + command. ## Options @@ -1560,7 +1767,6 @@ defmodule Nebulex.Cache do iex> MyCache.update(:a, 1, &(&1 * 2)) {:ok, 1} - iex> MyCache.update(:a, 1, &(&1 * 2)) {:ok, 2} @@ -1569,32 +1775,64 @@ defmodule Nebulex.Cache do @callback update(key(), initial :: value(), (value() -> value()), opts()) :: ok_error_tuple(value()) + @doc """ + Same as `c:update/4`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.update(MyCache1, :a, 1, &(&1 * 2), []) + + """ + @doc group: "KV API" + @callback update(dynamic_cache(), key(), initial :: value(), (value() -> value()), opts()) :: + ok_error_tuple(value()) + @doc """ Same as `c:update/4` but raises an exception if an error occurs. """ @doc group: "KV API" @callback update!(key(), initial :: value(), (value() -> value()), opts()) :: value() + @doc """ + Same as `c:update!/5` but raises an exception if an error occurs. + """ + @doc group: "KV API" + @callback update!(dynamic_cache(), key(), initial :: value(), (value() -> value()), opts()) :: + value() + ## Nebulex.Adapter.Queryable @optional_callbacks all: 2, + all: 3, all!: 2, + all!: 3, count_all: 2, + count_all: 3, count_all!: 2, + count_all!: 3, delete_all: 2, + delete_all: 3, delete_all!: 2, + delete_all!: 3, stream: 2, - stream!: 2 + stream: 3, + stream!: 2, + stream!: 3 @doc """ Fetches all entries from cache matching the given `query`. This function returns: - * `{:ok, matched_entries}` - the query is valid, then it is executed - and the matched entries are returned. + * `{:ok, matched_entries}` - The query was successfully executed, + the matched entries are returned. - * `{:error, reason}` - an error occurred while executing the command. + * `{:error, Nebulex.Error.t()}` - An error occurred while executing + the command. ## Query values @@ -1710,21 +1948,43 @@ defmodule Nebulex.Cache do @doc group: "Query API" @callback all(query :: any(), opts()) :: ok_error_tuple([any()]) + @doc """ + Same as `c:all/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.all(MyCache1, nil, []) + + """ + @doc group: "Query API" + @callback all(dynamic_cache(), query :: any(), opts()) :: ok_error_tuple([any()]) + @doc """ Same as `c:all/2` but raises an exception if an error occurs. """ @doc group: "Query API" @callback all!(query :: any(), opts()) :: [any()] + @doc """ + Same as `c:all!/3` but raises an exception if an error occurs. + """ + @doc group: "Query API" + @callback all!(dynamic_cache(), query :: any(), opts()) :: [any()] + @doc """ Similar to `c:all/2` but returns a lazy enumerable that emits all entries from the cache matching the given `query`. This function returns: - * `{:ok, Enum.t()}` - the query is valid, then the stream is returned. + * `{:ok, Enum.t()}` - The query is valid, then the stream is returned. - * `{:error, reason}` - an error occurred while executing the command. + * `{:error, Nebulex.Error.t()}` - An error occurred while executing + the command. ## Query values @@ -1813,22 +2073,44 @@ defmodule Nebulex.Cache do @doc group: "Query API" @callback stream(query :: any(), opts()) :: ok_error_tuple(Enum.t()) + @doc """ + Same as `c:stream/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.stream(MyCache1, nil, []) + + """ + @doc group: "Query API" + @callback stream(dynamic_cache(), query :: any(), opts()) :: ok_error_tuple(Enum.t()) + @doc """ Same as `c:stream/2` but raises an exception if an error occurs. """ @doc group: "Query API" @callback stream!(query :: any(), opts()) :: Enum.t() + @doc """ + Same as `c:stream!/3` but raises an exception if an error occurs. + """ + @doc group: "Query API" + @callback stream!(dynamic_cache(), query :: any(), opts()) :: Enum.t() + @doc """ Deletes all entries matching the given `query`. If `query` is `nil`, then all entries in the cache are deleted. This function returns: - * `{:ok, deleted_count}` - the query is valid, then the matched entries - are deleted and the `deleted_count` is returned. + * `{:ok, deleted_count}` - The query was successfully executed, + the deleted entries count is returned. - * `{:error, reason}` - an error occurred while executing the command. + * `{:error, Nebulex.Error.t()}` - An error occurred while executing + the command. ## Query values @@ -1861,12 +2143,33 @@ defmodule Nebulex.Cache do @doc group: "Query API" @callback delete_all(query :: any(), opts()) :: ok_error_tuple(non_neg_integer()) + @doc """ + Same as `c:delete_all/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.delete_all(MyCache1, nil, []) + + """ + @doc group: "Query API" + @callback delete_all(dynamic_cache(), query :: any(), opts()) :: ok_error_tuple(non_neg_integer()) + @doc """ Same as `c:delete_all/2` but raises an exception if an error occurs. """ @doc group: "Query API" @callback delete_all!(query :: any(), opts()) :: integer() + @doc """ + Same as `c:delete_all!/3` but raises an exception if an error occurs. + """ + @doc group: "Query API" + @callback delete_all!(dynamic_cache(), query :: any(), opts()) :: integer() + @doc """ Counts all entries in cache matching the given `query`. @@ -1875,10 +2178,11 @@ defmodule Nebulex.Cache do This function returns: - * `{:ok, count}` - the query is valid, then the `count` of the - matched entries returned. + * `{:ok, count}` - The query was successfully executed, + the `count` of the matched entries is returned. - * `{:error, reason}` - an error occurred while executing the command. + * `{:error, Nebulex.Error.t()}` - An error occurred while executing + the command. ## Query values @@ -1911,20 +2215,42 @@ defmodule Nebulex.Cache do @doc group: "Query API" @callback count_all(query :: any(), opts()) :: ok_error_tuple(non_neg_integer()) + @doc """ + Same as `c:count_all/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.count_all(MyCache1, nil, []) + + """ + @doc group: "Query API" + @callback count_all(dynamic_cache(), query :: any(), opts()) :: ok_error_tuple(non_neg_integer()) + @doc """ Same as `c:count_all/2` but raises an exception if an error occurs. """ @doc group: "Query API" @callback count_all!(query :: any(), opts()) :: non_neg_integer() + @doc """ + Same as `c:count_all!/3` but raises an exception if an error occurs. + """ + @doc group: "Query API" + @callback count_all!(dynamic_cache(), query :: any(), opts()) :: non_neg_integer() + ## Nebulex.Adapter.Persistence - @optional_callbacks dump: 2, dump!: 2, load: 2, load!: 2 + @optional_callbacks dump: 2, dump: 3, dump!: 2, dump!: 3, load: 2, load: 3, load!: 2, load!: 3 @doc """ Dumps a cache to the given file `path`. - Returns `:ok` if successful, or `{:error, reason}` if an error occurs. + Returns `:ok` if successful, or `{:error, Nebulex.Error.t()}` + if an error occurs. ## Options @@ -1954,16 +2280,38 @@ defmodule Nebulex.Cache do @doc group: "Persistence API" @callback dump(path :: Path.t(), opts()) :: :ok | error_tuple() + @doc """ + Same as `c:dump/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.dump(MyCache1, "my_cache", []) + + """ + @doc group: "Persistence API" + @callback dump(dynamic_cache(), path :: Path.t(), opts()) :: :ok | error_tuple() + @doc """ Same as `c:dump/2` but raises an exception if an error occurs. """ @doc group: "Persistence API" @callback dump!(path :: Path.t(), opts()) :: :ok + @doc """ + Same as `c:dump!/3` but raises an exception if an error occurs. + """ + @doc group: "Persistence API" + @callback dump!(dynamic_cache(), path :: Path.t(), opts()) :: :ok + @doc """ Loads a dumped cache from the given `path`. - Returns `:ok` if successful, or `{:error, reason}` if an error occurs. + Returns `:ok` if successful, or `{:error, Nebulex.Error.t()}` + if an error occurs. ## Options @@ -1998,27 +2346,48 @@ defmodule Nebulex.Cache do @doc group: "Persistence API" @callback load(path :: Path.t(), opts()) :: :ok | error_tuple() + @doc """ + Same as `c:load/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.load(MyCache1, "my_cache", []) + + """ + @doc group: "Persistence API" + @callback load(dynamic_cache(), path :: Path.t(), opts()) :: :ok | error_tuple() + @doc """ Same as `c:load/2` but raises an exception if an error occurs. """ @doc group: "Persistence API" @callback load!(path :: Path.t(), opts()) :: :ok + @doc """ + Same as `c:load!/3` but raises an exception if an error occurs. + """ + @doc group: "Persistence API" + @callback load!(dynamic_cache(), path :: Path.t(), opts()) :: :ok + ## Nebulex.Adapter.Transaction - @optional_callbacks transaction: 2, in_transaction?: 1 + @optional_callbacks transaction: 2, transaction: 3, in_transaction?: 1, in_transaction?: 2 @doc """ Runs the given function inside a transaction. - A successful transaction returns the value returned by the function wrapped - in a tuple as `{:ok, value}`. + If an Elixir exception occurs, the exception will bubble up from the + transaction function. If the transaction is aborted, + `{:error, Nebulex.Error.t()}` is returned. - In case the transaction cannot be executed, then `{:error, reason}` is - returned. + A successful transaction returns the value returned by the function wrapped + in a tuple as `{:ok, any()}`. - If an unhandled error/exception occurs, the error will bubble up from the - transaction function. + ### Nested transactions If `transaction/2` is called inside another transaction, the function is simply executed without wrapping the new transaction call in any way. @@ -2030,31 +2399,58 @@ defmodule Nebulex.Cache do ## Examples - MyCache.transaction fn -> + MyCache.transaction(fn -> alice = MyCache.get(:alice) bob = MyCache.get(:bob) MyCache.put(:alice, %{alice | balance: alice.balance + 100}) MyCache.put(:bob, %{bob | balance: bob.balance + 100}) - end + end) Locking only the involved key (recommended): - MyCache.transaction [keys: [:alice, :bob]], fn -> - alice = MyCache.get(:alice) - bob = MyCache.get(:bob) - MyCache.put(:alice, %{alice | balance: alice.balance + 100}) - MyCache.put(:bob, %{bob | balance: bob.balance + 100}) - end + MyCache.transaction( + fn -> + alice = MyCache.get(:alice) + bob = MyCache.get(:bob) + MyCache.put(:alice, %{alice | balance: alice.balance + 100}) + MyCache.put(:bob, %{bob | balance: bob.balance + 100}) + end, + [keys: [:alice, :bob]] + ) + + """ + @doc group: "Transaction API" + @callback transaction(fun(), opts()) :: ok_error_tuple(any()) + + @doc """ + Same as `c:transaction/2`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.transaction( + MyCache1, + fn -> + alice = MyCache.get(:alice) + bob = MyCache.get(:bob) + MyCache.put(:alice, %{alice | balance: alice.balance + 100}) + MyCache.put(:bob, %{bob | balance: bob.balance + 100}) + end, + [keys: [:alice, :bob]] + ) """ @doc group: "Transaction API" - @callback transaction(opts(), (() -> any())) :: ok_error_tuple(any()) + @callback transaction(dynamic_cache(), fun(), opts()) :: ok_error_tuple(any()) @doc """ Returns `{:ok, true}` if the current process is inside a transaction, otherwise, `{:ok, false}` is returned. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. ## Options @@ -2074,9 +2470,29 @@ defmodule Nebulex.Cache do @doc group: "Transaction API" @callback in_transaction?(opts()) :: ok_error_tuple(boolean()) + @doc """ + Same as `c:in_transaction?/1`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.in_transaction?(MyCache1, []) + + """ + @doc group: "Transaction API" + @callback in_transaction?(dynamic_cache(), opts()) :: ok_error_tuple(boolean()) + ## Nebulex.Adapter.Stats - @optional_callbacks stats: 1, stats!: 1, dispatch_stats: 1 + @optional_callbacks stats: 1, + stats: 2, + stats!: 1, + stats!: 2, + dispatch_stats: 1, + dispatch_stats: 2 @doc """ Returns current stats values. @@ -2086,14 +2502,15 @@ defmodule Nebulex.Cache do * `{:ok, Nebulex.Stats.t()}` - stats are enabled and available for the cache. - * `{:error, reason}` - an error occurred while executing the command. + * `{:error, Nebulex.Error.t()}` - An error occurred while executing + the command. ## Options See the ["Shared options"](#module-shared-options) section at the module documentation for more options. - ## Example + ## Examples iex> MyCache.stats() {:ok, @@ -2112,16 +2529,37 @@ defmodule Nebulex.Cache do @doc group: "Stats API" @callback stats(opts()) :: ok_error_tuple(Nebulex.Stats.t()) + @doc """ + Same as `c:stats/1`, but the command is executed on the cache instance + given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.stats(MyCache1, []) + + """ + @doc group: "Stats API" + @callback stats(dynamic_cache(), opts()) :: ok_error_tuple(Nebulex.Stats.t()) + @doc """ Same as `c:stats/1` but raises an exception if an error occurs. """ @doc group: "Stats API" @callback stats!(opts()) :: Nebulex.Stats.t() + @doc """ + Same as `c:stats!/2` but raises an exception if an error occurs. + """ + @doc group: "Stats API" + @callback stats!(dynamic_cache(), opts()) :: Nebulex.Stats.t() + @doc """ Emits a telemetry event when called with the current stats count. - Returns `{:error, reason}` if an error occurs. + Returns `{:error, Nebulex.Error.t()}` if an error occurs. The telemetry `:measurements` map will include the same as `Nebulex.Stats.t()`'s measurements. For example: @@ -2168,4 +2606,19 @@ defmodule Nebulex.Cache do """ @doc group: "Stats API" @callback dispatch_stats(opts()) :: :ok | error_tuple() + + @doc """ + Same as `c:dispatch_stats/1`, but the command is executed on the cache + instance given at the first argument `dynamic_cache`. + + See the ["Dynamic caches"](#module-dynamic-caches) section at the + module documentation for more information. + + ## Examples + + MyCache.dispatch_stats(MyCache1, []) + + """ + @doc group: "Stats API" + @callback dispatch_stats(dynamic_cache(), opts()) :: :ok | error_tuple() end diff --git a/lib/nebulex/cache/kv.ex b/lib/nebulex/cache/kv.ex index 0a2eb557..1c7acfd2 100644 --- a/lib/nebulex/cache/kv.ex +++ b/lib/nebulex/cache/kv.ex @@ -1,7 +1,7 @@ defmodule Nebulex.Cache.KV do @moduledoc false - import Nebulex.Helpers + import Nebulex.Utils, only: [unwrap_or_raise: 1] alias Nebulex.{Adapter, Time} @@ -251,7 +251,7 @@ defmodule Nebulex.Cache.KV do @doc """ Implementation for `c:Nebulex.Cache.update/4`. """ - def update(name, key, initial, fun, opts) do + def update(name, key, initial, fun, opts) when is_function(fun, 1) do Adapter.with_meta(name, fn %{adapter: adapter} = adapter_meta -> value = case adapter.fetch(adapter_meta, key, opts) do diff --git a/lib/nebulex/cache/options.ex b/lib/nebulex/cache/options.ex index 2d5ee250..038e5580 100644 --- a/lib/nebulex/cache/options.ex +++ b/lib/nebulex/cache/options.ex @@ -108,18 +108,6 @@ defmodule Nebulex.Cache.Options do `:extra_metadata` metadata key of these events. See the "Telemetry events" section for more information. """ - ], - dynamic_cache: [ - type: {:or, [:atom, :pid]}, - required: false, - doc: """ - The name or PID of the cache supervisor process to use for the invoked - cache command. Defaults to `c:get_dynamic_cache/0`. - - There are cases where we want to have different cache instances but access - them through the same cache module. This option tells the executed cache - command what cache instance to use dynamically in runtime. - """ ] ] diff --git a/lib/nebulex/cache/persistence.ex b/lib/nebulex/cache/persistence.ex index aedda6df..cb8be4c6 100644 --- a/lib/nebulex/cache/persistence.ex +++ b/lib/nebulex/cache/persistence.ex @@ -1,7 +1,7 @@ defmodule Nebulex.Cache.Persistence do @moduledoc false - import Nebulex.Helpers + import Nebulex.Utils, only: [unwrap_or_raise: 1] alias Nebulex.Adapter diff --git a/lib/nebulex/cache/queryable.ex b/lib/nebulex/cache/queryable.ex index 90324895..1cfef6f3 100644 --- a/lib/nebulex/cache/queryable.ex +++ b/lib/nebulex/cache/queryable.ex @@ -1,7 +1,7 @@ defmodule Nebulex.Cache.Queryable do @moduledoc false - import Nebulex.Helpers + import Nebulex.Utils, only: [unwrap_or_raise: 1] alias Nebulex.Adapter diff --git a/lib/nebulex/cache/registry.ex b/lib/nebulex/cache/registry.ex index 34900d1d..e80a394a 100644 --- a/lib/nebulex/cache/registry.ex +++ b/lib/nebulex/cache/registry.ex @@ -3,7 +3,7 @@ defmodule Nebulex.Cache.Registry do use GenServer - import Nebulex.Helpers + import Nebulex.Utils, only: [wrap_error: 2] ## API diff --git a/lib/nebulex/cache/stats.ex b/lib/nebulex/cache/stats.ex index 2bcd8a78..f6b4fc68 100644 --- a/lib/nebulex/cache/stats.ex +++ b/lib/nebulex/cache/stats.ex @@ -3,7 +3,7 @@ defmodule Nebulex.Cache.Stats do # use Nebulex.Cache.Options - import Nebulex.Helpers + import Nebulex.Utils, only: [unwrap_or_raise: 1] import Nebulex.Cache.Options, only: [validate_runtime_shared_opts!: 1] alias Nebulex.{Adapter, Telemetry} diff --git a/lib/nebulex/cache/supervisor.ex b/lib/nebulex/cache/supervisor.ex index 093e4ff7..457ed494 100644 --- a/lib/nebulex/cache/supervisor.ex +++ b/lib/nebulex/cache/supervisor.ex @@ -4,7 +4,7 @@ defmodule Nebulex.Cache.Supervisor do use Supervisor import Nebulex.Cache.Options - import Nebulex.Helpers + import Nebulex.Utils alias Nebulex.Telemetry diff --git a/lib/nebulex/cache/transaction.ex b/lib/nebulex/cache/transaction.ex index 846d9c33..b9563a35 100644 --- a/lib/nebulex/cache/transaction.ex +++ b/lib/nebulex/cache/transaction.ex @@ -6,7 +6,7 @@ defmodule Nebulex.Cache.Transaction do @doc """ Implementation for `c:Nebulex.Cache.transaction/2`. """ - def transaction(name, fun, opts) do + def transaction(name, fun, opts) when is_function(fun, 0) do Adapter.with_meta(name, & &1.adapter.transaction(&1, fun, opts)) end diff --git a/lib/nebulex/cache/utils.ex b/lib/nebulex/cache/utils.ex new file mode 100644 index 00000000..66d18de8 --- /dev/null +++ b/lib/nebulex/cache/utils.ex @@ -0,0 +1,33 @@ +defmodule Nebulex.Cache.Utils do + @moduledoc false + + @doc """ + Helper macro for defining the functions implementing the Cache API. + """ + defmacro defcacheapi(fun, to: target) do + {name, args} = Macro.decompose_call(fun) + all_args = defcacheapi_all_args(args) + + quote do + @impl true + def unquote(name)(unquote_splicing(args)) do + unquote(name)( + get_dynamic_cache(), + unquote_splicing(all_args) + ) + end + + @impl true + def unquote(name)(dynamic_cache, unquote_splicing(all_args)) do + unquote(target).unquote(name)(dynamic_cache, unquote_splicing(all_args)) + end + end + end + + defp defcacheapi_all_args(args) do + Enum.map(args, fn + {:\\, _, [arg, _]} -> arg + arg -> arg + end) + end +end diff --git a/lib/nebulex/caching/decorators.ex b/lib/nebulex/caching/decorators.ex index dfed9492..cd221740 100644 --- a/lib/nebulex/caching/decorators.ex +++ b/lib/nebulex/caching/decorators.ex @@ -407,7 +407,7 @@ if Code.ensure_loaded?(Decorator.Define) do use Decorator.Define, cacheable: 1, cache_evict: 1, cache_put: 1 - import Nebulex.Helpers + import Nebulex.Utils, only: [get_option: 4, get_option: 5] import Record ## Types diff --git a/lib/nebulex/helpers.ex b/lib/nebulex/utils.ex similarity index 78% rename from lib/nebulex/helpers.ex rename to lib/nebulex/utils.ex index 95e0085c..dd678fb4 100644 --- a/lib/nebulex/helpers.ex +++ b/lib/nebulex/utils.ex @@ -1,5 +1,5 @@ -defmodule Nebulex.Helpers do - # Module for general purpose helpers. +defmodule Nebulex.Utils do + # Module for general purpose utilities. @moduledoc false ## API @@ -12,7 +12,7 @@ defmodule Nebulex.Helpers do ## Examples - iex> Nebulex.Helpers.get_option( + iex> Nebulex.Utils.get_option( ...> [keys: [1, 2, 3]], ...> :keys, ...> "a list with at least one element", @@ -20,7 +20,7 @@ defmodule Nebulex.Helpers do ...> ) [1, 2, 3] - iex> Nebulex.Helpers.get_option( + iex> Nebulex.Utils.get_option( ...> [], ...> :keys, ...> "a list with at least one element", @@ -28,7 +28,7 @@ defmodule Nebulex.Helpers do ...> ) nil - iex> Nebulex.Helpers.get_option( + iex> Nebulex.Utils.get_option( ...> [keys: 123], ...> :keys, ...> "a list with at least one element", @@ -85,13 +85,13 @@ defmodule Nebulex.Helpers do ## Examples - iex> Nebulex.Helpers.camelize_and_concat([Foo, :bar]) + iex> Nebulex.Utils.camelize_and_concat([Foo, :bar]) Foo.Bar - iex> Nebulex.Helpers.camelize_and_concat([Foo, "bar"]) + iex> Nebulex.Utils.camelize_and_concat([Foo, "bar"]) Foo.Bar - iex> Nebulex.Helpers.camelize_and_concat([Foo, "Bar", 1]) + iex> Nebulex.Utils.camelize_and_concat([Foo, "Bar", 1]) :"Elixir.Foo.Bar.1" """ @@ -102,19 +102,6 @@ defmodule Nebulex.Helpers do |> Module.concat() end - @doc """ - Similar to `Keyword.pop_first/3`, but lazily returns and removes the first - value associated with key in the keyword list. - """ - @spec kw_pop_first_lazy(keyword, term, (() -> term)) :: {term, keyword} - @compile {:inline, kw_pop_first_lazy: 3} - def kw_pop_first_lazy(keywords, key, fun) when is_list(keywords) and is_atom(key) do - case :lists.keytake(key, 1, keywords) do - {:value, {^key, value}, rest} -> {value, rest} - false -> {fun.(), keywords} - end - end - ## Macros @doc false diff --git a/test/nebulex/cache/supervisor_test.exs b/test/nebulex/cache/supervisor_test.exs index 7dc4fbc1..161c9df0 100644 --- a/test/nebulex/cache/supervisor_test.exs +++ b/test/nebulex/cache/supervisor_test.exs @@ -61,7 +61,7 @@ defmodule Nebulex.Cache.SupervisorTest do assert {:ok, pid} = Cache.start_link(name: nil) assert Process.alive?(pid) - assert Cache.stop(dynamic_cache: pid) == :ok + assert Cache.stop(pid, []) == :ok refute Process.alive?(pid) end @@ -74,7 +74,7 @@ defmodule Nebulex.Cache.SupervisorTest do assert [{^pid, _}] = Registry.lookup(Registry.ViaTest, "test") - assert Cache.stop(dynamic_cache: pid) == :ok + assert Cache.stop(pid, []) == :ok refute Process.alive?(pid) end diff --git a/test/nebulex/helpers_test.exs b/test/nebulex/helpers_test.exs index 1e337d44..e25a8511 100644 --- a/test/nebulex/helpers_test.exs +++ b/test/nebulex/helpers_test.exs @@ -1,12 +1,12 @@ -defmodule Nebulex.HelpersTest do +defmodule Nebulex.UtilsTest do use ExUnit.Case, async: true - doctest Nebulex.Helpers + doctest Nebulex.Utils - alias Nebulex.Helpers + alias Nebulex.Utils describe "module_behaviours/2" do test "ok: returns implemented modules" do - assert Helpers.module_behaviours(Nebulex.TestAdapter, "module") == [ + assert Utils.module_behaviours(Nebulex.TestAdapter, "module") == [ Nebulex.Adapter, Nebulex.Adapter.KV, Nebulex.Adapter.Queryable, @@ -18,20 +18,20 @@ defmodule Nebulex.HelpersTest do test "error: invalid module" do assert_raise ArgumentError, fn -> - Helpers.module_behaviours(InvalidModule, "module") + Utils.module_behaviours(InvalidModule, "module") end end end describe "assert_behaviour/3" do test "ok: returns implemented modules" do - assert Helpers.assert_behaviour(Nebulex.TestAdapter, Nebulex.Adapter) == Nebulex.TestAdapter + assert Utils.assert_behaviour(Nebulex.TestAdapter, Nebulex.Adapter) == Nebulex.TestAdapter end end test "error: behaviour not implemented" do assert_raise ArgumentError, fn -> - Helpers.assert_behaviour(Nebulex.TestAdapter, XYZ) + Utils.assert_behaviour(Nebulex.TestAdapter, XYZ) end end end diff --git a/test/shared/cache/kv_test.exs b/test/shared/cache/kv_test.exs index a6f660d8..d7805114 100644 --- a/test/shared/cache/kv_test.exs +++ b/test/shared/cache/kv_test.exs @@ -34,17 +34,17 @@ defmodule Nebulex.Cache.KVTest do end end - test "with :dynamic_cache option", %{cache: cache} = ctx do + test "with dynamic_cache", %{cache: cache} = ctx do if name = Map.get(ctx, :name) do - assert cache.put("foo", "bar", dynamic_cache: name) == :ok - assert cache.fetch!("foo", dynamic_cache: name) == "bar" - assert cache.delete("foo", dynamic_cache: name) == :ok + assert cache.put(name, "foo", "bar", []) == :ok + assert cache.fetch!(name, "foo", []) == "bar" + assert cache.delete(name, "foo", []) == :ok end end - test "with :dynamic_cache option raise and exception", %{cache: cache} do + test "with dynamic_cache raises an exception", %{cache: cache} do assert_raise Nebulex.Error, ~r"could not lookup", fn -> - cache.put!("foo", "bar", dynamic_cache: :invalid) + cache.put!(:invalid, "foo", "bar", []) end end end diff --git a/test/shared/cache/transaction_test.exs b/test/shared/cache/transaction_test.exs index b1b7c090..9566badf 100644 --- a/test/shared/cache/transaction_test.exs +++ b/test/shared/cache/transaction_test.exs @@ -5,29 +5,33 @@ defmodule Nebulex.Cache.TransactionTest do describe "transaction" do test "ok: single transaction", %{cache: cache} do assert cache.transaction(fn -> - with :ok <- cache.put(1, 11), - 11 <- cache.fetch!(1), - :ok <- cache.delete(1) do - cache.get!(1) - end + :ok = cache.put!(1, 11) + + 11 = cache.fetch!(1) + + :ok = cache.delete!(1) + + cache.get!(1) end) == {:ok, nil} end test "ok: nested transaction", %{cache: cache} do assert cache.transaction( - [keys: [1]], fn -> cache.transaction( - [keys: [2]], fn -> - with :ok <- cache.put(1, 11), - 11 <- cache.fetch!(1), - :ok <- cache.delete(1) do - cache.get!(1) - end - end + :ok = cache.put!(1, 11) + + 11 = cache.fetch!(1) + + :ok = cache.delete!(1) + + cache.get!(1) + end, + keys: [2] ) - end + end, + keys: [1] ) == {:ok, {:ok, nil}} end @@ -36,12 +40,14 @@ defmodule Nebulex.Cache.TransactionTest do assert cache.fetch!(:test) == ["old value"] assert cache.transaction( - [keys: [:test]], fn -> ["old value"] = value = cache.fetch!(:test) - :ok = cache.put(:test, ["new value" | value]) + + :ok = cache.put!(:test, ["new value" | value]) + cache.fetch!(:test) - end + end, + keys: [:test] ) == {:ok, ["new value", "old value"]} assert cache.fetch!(:test) == ["new value", "old value"] @@ -50,11 +56,13 @@ defmodule Nebulex.Cache.TransactionTest do test "error: exception is raised", %{cache: cache} do assert_raise MatchError, fn -> cache.transaction(fn -> - with :ok <- cache.put(1, 11), - 11 <- cache.fetch!(1), - :ok <- cache.delete(1) do - :ok = cache.get(1) - end + :ok = cache.put!(1, 11) + + 11 = cache.fetch!(1) + + :ok = cache.delete!(1) + + :ok = cache.get(1) end) end end @@ -66,12 +74,13 @@ defmodule Nebulex.Cache.TransactionTest do _ = cache.put_dynamic_cache(name) cache.transaction( - [keys: [key], retries: 1], fn -> :ok = cache.put(key, true) Process.sleep(1100) - end + end, + keys: [key], + retries: 1 ) end) @@ -80,8 +89,9 @@ defmodule Nebulex.Cache.TransactionTest do assert_raise Nebulex.Error, ~r"cache #{inspect(name)} has aborted a transaction", fn -> {:error, %Nebulex.Error{} = reason} = cache.transaction( - [keys: [key], retries: 1], - fn -> cache.get(key) end + fn -> cache.get(key) end, + keys: [key], + retries: 1 ) raise reason @@ -95,6 +105,7 @@ defmodule Nebulex.Cache.TransactionTest do cache.transaction(fn -> :ok = cache.put(1, 11, return: :key) + {:ok, true} = cache.in_transaction?() end) end diff --git a/test/support/test_adapter.exs b/test/support/test_adapter.exs index f4e4aad2..bab5d4e8 100644 --- a/test/support/test_adapter.exs +++ b/test/support/test_adapter.exs @@ -40,7 +40,7 @@ defmodule Nebulex.TestAdapter do # Inherit default stats implementation use Nebulex.Adapter.Stats - import Nebulex.Helpers + import Nebulex.Utils alias Nebulex.Adapter.Stats alias __MODULE__.{Entry, KV} @@ -243,8 +243,8 @@ defmodule Nebulex.TestAdapter do ## Nebulex.Adapter.Transaction @impl true - defspan transaction(adapter_meta, opts, fun) do - super(adapter_meta, opts, fun) + defspan transaction(adapter_meta, fun, opts) do + super(adapter_meta, fun, opts) end @impl true @@ -272,7 +272,7 @@ defmodule Nebulex.TestAdapter.KV do use GenServer - import Nebulex.Helpers, only: [wrap_error: 2] + import Nebulex.Utils, only: [wrap_error: 2] alias Nebulex.Telemetry alias Nebulex.Telemetry.StatsHandler