# `Nebulex.Adapters.Local.QueryHelper`
[🔗](https://github.com/elixir-nebulex/nebulex_local/blob/v3.1.0/lib/nebulex/adapters/local/query_helper.ex#L2)

Builds ETS match specifications for `Nebulex.Adapters.Local` from named
fields, so you do not need to write the internal entry tuple
`{:entry, key, value, touched, exp, tag}` yourself.

The `match_spec/1` macro turns the named fields into the entry tuple and
uses `Ex2ms` to generate the match spec. It requires the optional `:ex2ms`
dependency.

## Getting Started

The simplest way to use this module is with `use`:

    defmodule MyCache.Queries do
      use Nebulex.Adapters.Local.QueryHelper

      def by_key(key) do
        match_spec key: k, value: v, where: k == key, select: v
      end

      def by_tag(tag) do
        match_spec tag: t, where: t == tag, select: true
      end
    end

## Examples

    # Simple match on key
    match_spec key: k, value: v, where: k == :foo, select: v

    # Match with tag
    match_spec key: k, value: v, tag: t,
               where: t == :important,
               select: {k, v}

    # Complex guards
    match_spec key: k, value: v,
               where: is_integer(k) and k > 10,
               select: {k, v}

    # Without guards
    match_spec key: k, value: v, select: {k, v}

    # Return entire entry
    match_spec key: k, value: v, where: k > 100, select: :"$_"

    # Match only on specific fields
    match_spec tag: t, where: t == :important, select: true

    # Complex conditions
    match_spec key: k, value: v, tag: t, exp: e,
               where: k > t and e != :infinity,
               select: {k, v, e}

## Supported field bindings

- `:key` - The cache key
- `:value` - The cached value
- `:touched` - Timestamp when entry was last touched
- `:exp` - Expiration timestamp (or `:infinity`)
- `:tag` - Optional tag metadata

Fields not mentioned in the pattern will be wildcarded with `_`.

## Clauses

- Field bindings (`:key`, `:value`, `:tag`, `:touched`, `:exp`) - Bind entry
  fields to variables.
- `:where` - Optional guard conditions (supports all ETS guard functions).
- `:select` - What to return (optional, defaults to `true`).

## Using match specs with cache operations

Use the match specs with `get_all`, `count_all`, `delete_all`, and
`stream`:

    use Nebulex.Adapters.Local.QueryHelper

    # Get all values where the key is an integer greater than 10
    ms = match_spec key: k, value: v, where: is_integer(k) and k > 10, select: v
    MyCache.get_all!(query: ms)

    # Count the entries with a specific tag
    ms = match_spec tag: t, where: t == :user_session
    MyCache.count_all!(query: ms)

    # Delete expired entries (exp is not :infinity and less than now)
    now = System.system_time(:millisecond)
    ms = match_spec exp: e, where: e != :infinity and e < now
    MyCache.delete_all!(query: ms)

    # Stream entries in batches
    ms = match_spec value: v, where: is_binary(v), select: v
    MyCache.stream!(query: ms) |> Enum.take(100)

To reuse queries, put them in a module:

    defmodule MyApp.CacheQueries do
      use Nebulex.Adapters.Local.QueryHelper

      # Find all entries for a specific user
      def user_entries(user_id) do
        match_spec tag: t, where: t == {:user, user_id}, select: :"$_"
      end

      # Find entries expiring within the next hour (not expired yet)
      def expiring_soon do
        now = System.system_time(:millisecond)
        cutoff = now + :timer.hours(1)

        match_spec exp: e, where: e != :infinity and e > now and e < cutoff
      end

      # Filter by tag and the time the entry was last written or touched
      def recent_tagged_entries(tag, min_time) do
        match_spec key: k,
                   value: v,
                   tag: t,
                   touched: ts,
                   where: t == tag and ts > min_time,
                   select: {k, v}
      end
    end

    MyApp.Cache.get_all!(query: MyApp.CacheQueries.user_entries(123))
    MyApp.Cache.delete_all!(query: MyApp.CacheQueries.expiring_soon())

## Tags and caching decorators

The `Nebulex.Caching` decorators can tag the entries they write, and
`cache_evict` can take a QueryHelper match spec to evict entries by tag or
by reference.

### Tagging entries from decorators

Pass the tag through the decorator's `:opts` option:

    defmodule MyApp.UserCache do
      use Nebulex.Caching, cache: MyApp.Cache
      use Nebulex.Adapters.Local.QueryHelper

      # Cache user data, tagged with the user ID
      @decorate cacheable(key: {:user, user_id}, opts: [tag: {:user, user_id}])
      def get_user(user_id) do
        # fetch user from database
        {:ok, user}
      end

      # Cache user permissions under their own key, with the same tag
      @decorate cacheable(key: {:permissions, user_id}, opts: [tag: {:user, user_id}])
      def get_user_permissions(user_id) do
        # fetch permissions from database
        {:ok, permissions}
      end

      # Store session data, tagged as a session
      @decorate cache_put(key: session_id, opts: [tag: :sessions])
      def create_session(session_id, data) do
        data
      end
    end

Tags are metadata, not part of the key. With the default `:set` table
type, writing the same key replaces its entry regardless of the tag, so
give each kind of data its own key.

### Evicting entries by tag

Use `cache_evict` with a function that returns the match spec:

    defmodule MyApp.UserCache do
      use Nebulex.Caching, cache: MyApp.Cache
      use Nebulex.Adapters.Local.QueryHelper

      # ... cacheable functions as above ...

      # Evict all cached data for a specific user
      @decorate cache_evict(query: &evict_user_query/1)
      def invalidate_user(user_id) do
        :ok
      end

      defp evict_user_query(%{args: [user_id]} = _context) do
        match_spec tag: t, where: t == {:user, user_id}
      end
    end

The same approach clears all the sessions of a user, or all sessions:

    defmodule MyApp.Sessions do
      use Nebulex.Caching, cache: MyApp.Cache
      use Nebulex.Adapters.Local.QueryHelper

      @decorate cache_put(key: session_id, opts: [tag: {:session, user_id}])
      def store_session(user_id, session_id, data) do
        data
      end

      # Evict all sessions of a user when they log out
      @decorate cache_evict(query: &evict_user_sessions_query/1)
      def logout_user(user_id) do
        :ok
      end

      # Evict all sessions of all users (e.g., during maintenance)
      @decorate cache_evict(query: &evict_all_sessions_query/1)
      def clear_all_sessions do
        :ok
      end

      defp evict_user_sessions_query(%{args: [user_id]} = _context) do
        match_spec tag: t, where: t == {:session, user_id}
      end

      defp evict_all_sessions_query(_context) do
        match_spec tag: t, where: is_tuple(t) and elem(t, 0) == :session
      end
    end

### Evicting cache references

When a decorator caches a value under several keys with the `:references`
option, `keyref_match_spec/2` finds the reference entries that point to a
key, so `cache_evict` can remove them all at once:

    defmodule MyApp.UserAccounts do
      use Nebulex.Caching, cache: MyApp.Cache
      use Nebulex.Adapters.Local.QueryHelper

      @decorate cacheable(key: user_id)
      def get_user_account(user_id) do
        fetch_user(user_id)
      end

      @decorate cacheable(key: email, references: &(&1 && &1.id))
      def get_user_account_by_email(email) do
        fetch_user_by_email(email)
      end

      @decorate cacheable(key: token, references: &(&1 && &1.id))
      def get_user_account_by_token(token) do
        fetch_user_by_token(token)
      end

      # Evict the user and all the lookups (by email and token) that
      # reference it
      @decorate cache_evict(key: user_id, query: &invalidate_refs/1)
      def update_user_account(user_id, attrs) do
        :ok
      end

      defp invalidate_refs(%{args: [user_id | _]} = _context) do
        keyref_match_spec(user_id)
      end
    end

## Internal entry format

The internal ETS entry is an Erlang record with the structure:
`{:entry, key, value, touched, exp, tag}`

The `match_spec/1` macro builds this tuple for you.

# `__using__`
*macro* 

Imports the query helper macros and `Ex2ms` for building match specifications.

When you `use` this module, it automatically imports:
- `Ex2ms` - For the underlying match spec generation (`fun do ... end`)
- `Nebulex.Adapters.Local.QueryHelper` - For the `match_spec/1` macro

## Usage

    defmodule MyModule do
      use Nebulex.Adapters.Local.QueryHelper

      def my_query do
        match_spec key: k, value: v, where: k == :foo, select: v
      end
    end

## Alternative: Manual Import

If you prefer more control, you can import modules manually:

    defmodule MyModule do
      import Ex2ms
      import Nebulex.Adapters.Local.QueryHelper

      def my_query do
        match_spec key: k, value: v, where: k == :foo, select: v
      end
    end

## Note

The `match_spec/1` macro requires `Ex2ms` to be available. If you don't use
this module or manually import `Ex2ms`, you'll get a compilation error when
trying to use `match_spec/1`.

# `keyref_match_spec`

```elixir
@spec keyref_match_spec(term(), keyword()) :: :ets.match_spec()
```

Builds a match spec for finding cache reference entries (keyrefs).

This is useful for invalidating all reference keys that point to a specific
referenced key. Cache references are created using the `keyref/2` function
in Nebulex caching decorators.

The match spec returns the reference key (the cache key that points to the
referenced key). This works seamlessly with `get_all/1`, `delete_all/1`, and
`count_all/1` operations.

## Parameters

  * `referenced_key` - The key that references point to (required).
  * `opts` - Optional keyword list:
    * `:cache` - Filter references to a specific cache (optional). When not
      provided, matches references in any cache (including `nil` for local
      references).

## Examples

    # Delete all reference entries pointing to a specific key (any cache)
    ms = keyref_match_spec(:user_123)
    MyCache.delete_all!(query: ms)

    # Delete reference entries pointing to a key in a specific cache
    ms = keyref_match_spec(:user_123, cache: MyApp.UserCache)
    MyCache.delete_all!(query: ms)

    # Count how many references point to a key
    ms = keyref_match_spec(:product_456)
    MyCache.count_all!(query: ms)

    # Get all cache keys that are references to a specific key
    ms = keyref_match_spec(:user_123)
    reference_keys = MyCache.get_all!(query: ms)

## Background

When using the `:references` option with caching decorators, Nebulex stores
reference entries as keyref records with the structure:
`{:"$nbx_keyref_spec", cache, key, ttl}`. This function helps you query and
clean up these reference entries.

## See also

  * `Nebulex.Caching` - For information about cache references and the
    `:references` option.

# `match_spec`
*macro* 

Builds an ETS match specification from a SQL-like declarative syntax.

The macro accepts a keyword list with field bindings, optional guards
(`:where`), and a return expression (`:select`).

## Requirements

This macro requires `Ex2ms` to be available. The recommended approach is to
use this module:

    use Nebulex.Adapters.Local.QueryHelper

Alternatively, you can manually import both modules:

    import Ex2ms
    import Nebulex.Adapters.Local.QueryHelper

## Syntax

    match_spec field: var, ..., where: guards, select: return_value

Where:
- Field bindings (`:key`, `:value`, `:tag`, `:touched`, `:exp`) bind entry
  fields to variables.
- `:where` - Optional guard clause with conditions.
- `:select` - Optional return expression (defaults to `true`).

## Examples

    # Match all entries where key equals :foo
    match_spec key: k, value: v, where: k == :foo, select: v

    # Match entries with specific tag
    match_spec key: k, tag: t, where: t == :important, select: k

    # Complex guards and return values
    match_spec key: k, value: v, exp: e,
               where: is_integer(v) and e != :infinity,
               select: {k, v, e}

    # Without guards
    match_spec key: k, value: v, select: {k, v}

    # Return the entire entry
    match_spec value: v, tag: t, where: t == :foo, select: :"$_"

    # Using default select: true (useful for count_all/delete_all)
    match_spec tag: t, where: t == :important
    # Same as: match_spec tag: t, where: t == :important, select: true

## Notes

- Fields not mentioned will be wildcarded (`:_`).
- The order of fields doesn't matter.
- Guards support all ETS guard functions (via Ex2ms).
- The `:select` clause is optional and defaults to `true`.

---

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