# `AshScylla.DataLayer.Collection`
[🔗](https://github.com/ohhi-vn/ash_scylla/blob/main/lib/ash_scylla/data_layer/collection.ex#L15)

Collection type (LIST, SET, MAP) optimization for ScyllaDB.

Provides encoding/decoding, CQL generation, and query building
for ScyllaDB collection types with support for:

- Frozen collections (immutable, stored as single values)
- Collection literals in CQL
- Collection operations (append, prepend, remove, index access)
- Secondary indexes on collections (for SET/LIST: full collection;
  for MAP: keys, values, or entries)

## Usage

    # Encode a list for Xandra
    AshScylla.DataLayer.Collection.encode([1, 2, 3], :list, element_type: :int)

    # Generate CQL for appending to a collection
    AshScylla.DataLayer.Collection.append_cql(:users, :tags, ["new_tag"])

    # Generate a CONTAINS filter
    AshScylla.DataLayer.Collection.contains_cql(:users, :tags, "search_tag")

## Xandra Encoding

- LIST → Elixir list (preserves order, allows duplicates)
- SET → Elixir MapSet (unordered, unique)
- MAP → Elixir map (key-value pairs)
- Frozen → Tuple (immutable, stored as single blob)

# `append_cql`

```elixir
@spec append_cql(String.t() | atom(), atom(), term()) :: String.t()
```

Generates CQL for appending to a collection.

Uses the `+` operator to append elements.

## Examples

    iex> AshScylla.DataLayer.Collection.append_cql(:users, :tags, ["new_tag"])
    "UPDATE users SET tags = tags + ? WHERE ..."

    iex> AshScylla.DataLayer.Collection.append_cql("users", :tags, ["new_tag"])
    "UPDATE users SET tags = tags + ? WHERE ..."

# `collection_index_cql`

```elixir
@spec collection_index_cql(
  String.t() | atom(),
  atom(),
  :values | :keys | :entries | :full
) ::
  String.t()
```

Generates CQL for a secondary index on a collection column.

## Index Types

- `:full` — Index the entire frozen collection
- `:values` — Index individual values in a set or list
- `:keys` — Index map keys
- `:entries` — Index map entries as key-value pairs

## Examples

    iex> AshScylla.DataLayer.Collection.collection_index_cql(:users, :tags, :values)
    "CREATE INDEX ON users (VALUES(tags))"

    iex> AshScylla.DataLayer.Collection.collection_index_cql(:users, :metadata, :keys)
    "CREATE INDEX ON users (KEYS(metadata))"

    iex> AshScylla.DataLayer.Collection.collection_index_cql(:users, :frozen_data, :full)
    "CREATE INDEX ON users (FULL(frozen_data))"

# `collection_type_to_cql`

```elixir
@spec collection_type_to_cql(
  atom(),
  keyword()
) :: String.t()
```

Returns the CQL type string for a collection type.

## Options

- `:element_type` — Element type for list/set (default: `:text`)
- `:key_type` — Key type for map (default: `:text`)
- `:value_type` — Value type for map (default: `:text`)
- `:frozen` — If true, wrap in `FROZEN<...>`

## Examples

    iex> AshScylla.DataLayer.Collection.collection_type_to_cql(:list, element_type: :text)
    "LIST<TEXT>"

    iex> AshScylla.DataLayer.Collection.collection_type_to_cql(:set, element_type: :int)
    "SET<INT>"

    iex> AshScylla.DataLayer.Collection.collection_type_to_cql(:map, key_type: :text, value_type: :int)
    "MAP<TEXT, INT>"

    iex> AshScylla.DataLayer.Collection.collection_type_to_cql(:list, element_type: :text, frozen: true)
    "FROZEN<LIST<TEXT>>"

# `contains_cql`

```elixir
@spec contains_cql(String.t() | atom(), atom(), term()) :: {String.t(), [term()]}
```

Generates CQL for a CONTAINS filter (for queries).

Returns a `{cql_fragment, params}` tuple.

## Examples

    iex> AshScylla.DataLayer.Collection.contains_cql(:users, :tags, "admin")
    {"tags CONTAINS ?", ["admin"]}

# `contains_key_cql`

```elixir
@spec contains_key_cql(String.t() | atom(), atom(), term()) :: {String.t(), [term()]}
```

Generates CQL for a CONTAINS KEY filter (for map queries).

Returns a `{cql_fragment, params}` tuple.

## Examples

    iex> AshScylla.DataLayer.Collection.contains_key_cql(:users, :metadata, "role")
    {"metadata CONTAINS KEY ?", ["role"]}

# `decode`

```elixir
@spec decode(term(), atom(), keyword()) :: term()
```

Decodes a collection from Xandra to Elixir format.

## Options

- `:frozen` — If true, unwrap from tuple format

## Examples

    iex> AshScylla.DataLayer.Collection.decode(["a", "b"], :list, [])
    ["a", "b"]

    iex> AshScylla.DataLayer.Collection.decode({1, 2, 3}, :set, frozen: true)
    [1, 2, 3]

    iex> AshScylla.DataLayer.Collection.decode({"k", "v"}, :map, frozen: true)
    %{"k" => "v"}

# `encode`

```elixir
@spec encode(term(), atom(), keyword()) :: term()
```

Encodes an Elixir collection to Xandra-compatible format.

## Options

- `:element_type` — The type of elements (`:text`, `:int`, `:uuid`, etc.)
- `:key_type` — For maps, the key type (default: `:text`)
- `:value_type` — For maps, the value type (default: `:text`)
- `:frozen` — If true, wrap in a tuple for frozen collections

## Examples

    iex> AshScylla.DataLayer.Collection.encode(["a", "b"], :list, element_type: :text)
    ["a", "b"]

    iex> AshScylla.DataLayer.Collection.encode([1, 2, 3], :set, element_type: :int)
    MapSet.new([1, 2, 3])

    iex> AshScylla.DataLayer.Collection.encode(%{"k" => "v"}, :map, key_type: :text, value_type: :text)
    %{"k" => "v"}

# `get_at_cql`

```elixir
@spec get_at_cql(String.t() | atom(), atom(), non_neg_integer() | String.t()) ::
  String.t()
```

Generates CQL for accessing a collection element by index/key.

## Examples

    iex> AshScylla.DataLayer.Collection.get_at_cql(:users, :items, 0)
    "SELECT items[?] FROM users WHERE ..."

    iex> AshScylla.DataLayer.Collection.get_at_cql(:users, :metadata, "key")
    "SELECT metadata[?] FROM users WHERE ..."

# `optimize_for_storage`

```elixir
@spec optimize_for_storage(term(), atom(), keyword()) :: term()
```

Optimizes a collection value for storage.

For lists: no change.
For sets: sorts for consistent encoding.
For maps: no change.
For frozen: converts to tuple.

## Examples

    iex> AshScylla.DataLayer.Collection.optimize_for_storage([3, 1, 2], :set, element_type: :int)
    [1, 2, 3]

    iex> AshScylla.DataLayer.Collection.optimize_for_storage(["a", "b"], :list, element_type: :text)
    ["a", "b"]

# `prepend_cql`

```elixir
@spec prepend_cql(String.t() | atom(), atom(), term()) :: String.t()
```

Generates CQL for prepending to a list.

Uses the `+` operator with the value on the left side (list prepend).

## Examples

    iex> AshScylla.DataLayer.Collection.prepend_cql(:users, :items, ["first"])
    "UPDATE users SET items = ? + items WHERE ..."

# `remove_cql`

```elixir
@spec remove_cql(String.t() | atom(), atom(), term()) :: String.t()
```

Generates CQL for removing from a collection.

Uses the `-` operator to remove elements.

## Examples

    iex> AshScylla.DataLayer.Collection.remove_cql(:users, :tags, ["old_tag"])
    "UPDATE users SET tags = tags - ? WHERE ..."

# `set_at_cql`

```elixir
@spec set_at_cql(String.t() | atom(), atom(), non_neg_integer() | String.t(), term()) ::
  String.t()
```

Generates CQL for setting a collection element by index/key.

## Examples

    iex> AshScylla.DataLayer.Collection.set_at_cql(:users, :items, 0, "first")
    "UPDATE users SET items[?] = ? WHERE ..."

    iex> AshScylla.DataLayer.Collection.set_at_cql(:users, :metadata, "key", "val")
    "UPDATE users SET metadata[?] = ? WHERE ..."

# `size_cql`

```elixir
@spec size_cql(String.t() | atom(), atom()) :: String.t()
```

Generates CQL for getting collection size.

## Examples

    iex> AshScylla.DataLayer.Collection.size_cql(:users, :tags)
    "SELECT SIZE(tags) FROM users WHERE ..."

# `validate`

```elixir
@spec validate(term(), atom(), keyword()) :: :ok | {:error, String.t()}
```

Validates a collection value against its declared type.

## Examples

    iex> AshScylla.DataLayer.Collection.validate(["a", "b"], :list, element_type: :text)
    :ok

    iex> AshScylla.DataLayer.Collection.validate("not_a_list", :list, element_type: :text)
    {:error, "Expected a list, got: "not_a_list""}

---

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