AshScylla.DataLayer.Collection (AshScylla v0.11.1)

Copy Markdown View Source

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")

Summary

Functions

Generates CQL for appending to a collection.

Generates CQL for a secondary index on a collection column.

Returns the CQL type string for a collection type.

Generates CQL for a CONTAINS filter (for queries).

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

Decodes a collection from Xandra to Elixir format.

Encodes an Elixir collection to Xandra-compatible format.

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

Optimizes a collection value for storage.

Generates CQL for prepending to a list.

Generates CQL for removing from a collection.

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

Generates CQL for getting collection size.

Validates a collection value against its declared type.

Functions

append_cql(table, column, value)

@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(table, column, index_type)

@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(atom, opts)

@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(table, column, value)

@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(table, column, value)

@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(value, atom, opts)

@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(value, atom, opts)

@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(table, column, index)

@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(value, atom, opts)

@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(table, column, value)

@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(table, column, value)

@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(table, column, index, value)

@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(table, column)

@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(value, atom, opts)

@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""}