# `Nebulex.Adapter.CompositeKV`
[🔗](https://github.com/elixir-nebulex/nebulex/blob/v3.1.0/lib/nebulex/adapter/composite_kv.ex#L1)

Specifies the adapter Composite KV API.

Composite operations combine a read and a write on the same key in one
call and receive a function as an argument. This behaviour covers
`c:get_and_update/6`, `c:update/7`, `c:fetch_or_store/6`, and
`c:get_or_store/6`.

By default, Nebulex builds these operations on top of the
`Nebulex.Adapter.KV` primitives (`fetch`, `put`, and `delete`), and the
given function runs in the calling process, on the local node. This holds
even when the adapter performs the underlying read and write commands on
remote nodes.

Because the read and the write are separate commands, the default
implementation is **not atomic**:

  * For `c:get_and_update/6` and `c:update/7`, concurrent calls on the
    same key can overwrite each other's changes.

  * For `c:fetch_or_store/6` and `c:get_or_store/6`, concurrent cache
    misses on the same key can evaluate the function more than once, and
    the last write wins.

If atomicity is required and the adapter supports transactions, wrap the
call in `c:Nebulex.Cache.transaction/2` locking the key with the `:keys`
option. The lock coordinates only writers that use transactions on the same
keys; plain writes are not affected.

This behaviour is required: `Nebulex.Cache` validates at compile time that
the adapter implements it, alongside `Nebulex.Adapter` and
`Nebulex.Adapter.KV`. Most adapters get it with
`use Nebulex.Adapter.CompositeKV`, which provides the default implementation
in this module. An adapter implements the callbacks itself to change where
the function runs (e.g., on the node owning the key) or to provide different
atomicity guarantees.

## Default implementation

`use Nebulex.Adapter.CompositeKV` declares the behaviour and provides the
default implementation for every callback. The callbacks are overridable,
so an adapter can override only some of them:

    defmodule MyAdapter do
      @behaviour Nebulex.Adapter
      @behaviour Nebulex.Adapter.KV

      use Nebulex.Adapter.CompositeKV

      # Override `get_and_update/6` and `update/7`; the other callbacks
      # fall back to the default implementation.
      @impl true
      def get_and_update(adapter_meta, key, fun, ttl, keep_ttl?, opts) do
        # Adapter-specific implementation ...
      end

      @impl true
      def update(adapter_meta, key, initial, fun, ttl, keep_ttl?, opts) do
        # Adapter-specific implementation ...
      end

      ...
    end

## Telemetry

Each composite operation is a cache command itself: a Telemetry span with
`command: :get_and_update`, `command: :update`, `command: :fetch_or_store`,
or `command: :get_or_store` is emitted. The default implementation runs the
primitive commands through `Nebulex.Adapter.run_command/4`, so each of them
emits its own command span as well, and the cache entry events and stats
built on those spans are still produced. The shared `:telemetry`,
`:telemetry_event`, and `:telemetry_metadata` options apply to the composite
command span and are forwarded to the primitive commands.

> #### Overrides and cache entry events {: .warning}
>
> The built-in cache entry (`Nebulex.Event.CacheEntryEvent`) and stats
> handlers are driven by the primitive command events, not by the composite
> one. If an override bypasses those commands, or runs them under another
> cache's metadata, those handlers will not account for the operation on
> the original cache. Preserve equivalent events for the affected cache
> when implementing an override.

# `adapter_meta`

```elixir
@type adapter_meta() :: Nebulex.Adapter.adapter_meta()
```

Proxy type to the adapter meta

# `fetch_or_store`

```elixir
@callback fetch_or_store(
  adapter_meta(),
  Nebulex.Cache.key(),
  Nebulex.Cache.fetch_or_store_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) :: Nebulex.Cache.ok_error_tuple(Nebulex.Cache.value())
```

Fetches the value for `key` or, on a cache miss, evaluates `fun` and
stores its result.

`fun` must return `{:ok, value}`, in which case `value` is stored under
`key` and returned, or `{:error, reason}`, in which case nothing is
stored and the error is returned.

The `ttl` and `keep_ttl` arguments apply to the write, as in
`c:Nebulex.Adapter.KV.put/7`.

Returns `{:ok, value}` if successful; `{:error, reason}` otherwise.

See `c:Nebulex.Cache.fetch_or_store/3`.

# `get_and_update`

```elixir
@callback get_and_update(
  adapter_meta(),
  Nebulex.Cache.key(),
  Nebulex.Cache.get_and_update_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) ::
  Nebulex.Cache.ok_error_tuple({Nebulex.Cache.value(), Nebulex.Cache.value()})
```

Gets the value for `key` and updates it using the given function.

`fun` is called with the current cached value under `key` (or `nil` if
`key` hasn't been cached) and must return a two-element tuple: the value to
return as the current value, which may differ from the cached one, and the
new value to store under `key`. `fun` may also return `:pop` to remove the
entry and return the current value.

The `ttl` and `keep_ttl` arguments apply to the write, as in
`c:Nebulex.Adapter.KV.put/7`.

Returns `{:ok, {current_value, new_value}}` if successful;
`{:error, reason}` otherwise.

See `c:Nebulex.Cache.get_and_update/3`.

# `get_or_store`

```elixir
@callback get_or_store(
  adapter_meta(),
  Nebulex.Cache.key(),
  Nebulex.Cache.get_or_store_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) :: Nebulex.Cache.ok_error_tuple(Nebulex.Cache.value())
```

Gets the value for `key` or, on a cache miss, evaluates `fun` and stores
whatever it returns.

The `ttl` and `keep_ttl` arguments apply to the write, as in
`c:Nebulex.Adapter.KV.put/7`.

Returns `{:ok, value}` if successful; `{:error, reason}` otherwise.

See `c:Nebulex.Cache.get_or_store/3`.

# `update`

```elixir
@callback update(
  adapter_meta(),
  Nebulex.Cache.key(),
  initial :: Nebulex.Cache.value(),
  Nebulex.Cache.update_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) :: Nebulex.Cache.ok_error_tuple(Nebulex.Cache.value())
```

Updates the cached `key` with the given function.

If `key` is present in the cache, `fun` is invoked with the current value
and its result is stored under `key`. If `key` is not present, `initial`
is stored under `key` and `fun` is not invoked.

The `ttl` and `keep_ttl` arguments apply to the write, as in
`c:Nebulex.Adapter.KV.put/7`.

Returns `{:ok, value}` with the stored value if successful;
`{:error, reason}` otherwise.

See `c:Nebulex.Cache.update/4`.

# `fetch_or_store`

```elixir
@spec fetch_or_store(
  adapter_meta(),
  Nebulex.Cache.key(),
  Nebulex.Cache.fetch_or_store_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) :: Nebulex.Cache.ok_error_tuple(Nebulex.Cache.value())
```

Default implementation for `c:fetch_or_store/6`.

# `get_and_update`

```elixir
@spec get_and_update(
  adapter_meta(),
  Nebulex.Cache.key(),
  Nebulex.Cache.get_and_update_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) ::
  Nebulex.Cache.ok_error_tuple({Nebulex.Cache.value(), Nebulex.Cache.value()})
```

Default implementation for `c:get_and_update/6`.

# `get_or_store`

```elixir
@spec get_or_store(
  adapter_meta(),
  Nebulex.Cache.key(),
  Nebulex.Cache.get_or_store_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) :: Nebulex.Cache.ok_error_tuple(Nebulex.Cache.value())
```

Default implementation for `c:get_or_store/6`.

# `update`

```elixir
@spec update(
  adapter_meta(),
  Nebulex.Cache.key(),
  Nebulex.Cache.value(),
  Nebulex.Cache.update_fun(),
  Nebulex.Cache.ttl(),
  Nebulex.Cache.keep_ttl(),
  Nebulex.Cache.opts()
) :: Nebulex.Cache.ok_error_tuple(Nebulex.Cache.value())
```

Default implementation for `c:update/7`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
