An Ash data layer for ScyllaDB using Exandra (Ecto adapter for Cassandra/ScyllaDB).
This data layer implements the Ash.DataLayer behaviour to allow Ash resources
to be backed by ScyllaDB/Cassandra.
Configuration
Configure your resource to use this data layer:
defmodule MyApp.MyResource do
use Ash.Resource,
data_layer: AshScylla.DataLayer
attributes do
uuid_primary_key :id
attribute :name, :string
end
relationships do
# Define relationships as needed
end
endFeatures Supported
:create- Create records:read- Read records with filtering:update- Update records:destroy- Delete records:filter- Filter queries:limit- Limit results:select- Select specific fields:multitenancy- Keyspace-based multitenancy:upsert- Upsert records (INSERT IF NOT EXISTS with LWT):update_query- Bulk update via filtered queries:destroy_query- Bulk delete via filtered queries:keyset- Token-based keyset pagination (the default pagination mode):distinct- DISTINCT on partition key columns{:aggregate, :count}- Per-partition COUNT aggregates{:atomic, :update}- Atomic updates via LWT (IF clauses){:atomic, :upsert}- Atomic upserts via LWT:boolean_filter- OR filter rewriting to IN where possible
Limitations
Since ScyllaDB/Cassandra is a wide-column store, not all SQL features are supported:
- No JOINs (use denormalization or multiple queries)
- Expression calculations are done in Elixir post-processing (not in-database)
- DISTINCT only works on partition key columns
- Limited aggregation support
- Combination queries (UNION/INTERSECT) are not supported
- No transactions across partitions (lightweight transactions only)
- Locking is a no-op (use LWT for conditional operations)
- No complex WHERE clauses on non-primary key columns without secondary indexes
- Cross-partition aggregates require materialized views
- CQL ORDER BY only works on clustering columns within a partition
- OFFSET is not natively supported in ScyllaDB
Summary
Types
@type t() :: %AshScylla.DataLayer{ atomic: atom() | nil, context: map(), filters: list(), keyset: term(), limit: pos_integer() | nil, offset: pos_integer() | nil, repo: module() | nil, resource: Ash.Resource.t(), select: [atom()] | nil, sorts: list(), table: String.t() | nil, tenant: term(), upsert?: boolean(), upsert_fields: [atom()], upsert_identity: atom() | nil }
Functions
@spec upsert(Ash.Resource.t(), Ash.Changeset.t()) :: {:ok, Ash.Resource.t()} | {:error, term()}