An Ash data layer for ScyllaDB using Xandra (direct CQL driver).
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:sort/{:sort, _}- ORDER BY on clustering columns:boolean_filter- OR filter rewriting to IN where possible:nested_expressions- Nested filter expressions{:filter_expr, _}- Filter expression support:changeset_filter- Changeset-based filtering:calculate- In-memory calculations:action_select- Action-specific select:async_engine- Async engine support:bulk_create- Batch INSERT operations:transact- Transaction wrapper:composite_primary_key- Composite primary key support{:aggregate, :count}/{:aggregate, :sum}/{:aggregate, :avg}/{:aggregate, :min}/{:aggregate, :max}- Per-partition aggregate functions{:query_aggregate, :count}/{:query_aggregate, :sum}/{:query_aggregate, :avg}/{:query_aggregate, :min}/{:query_aggregate, :max}- Query-level aggregate functions (Ash.count/2,Ash.sum/2, etc.){:aggregate_relationship, _}- Relationship aggregates (belongs_to via per-record subqueries){:atomic, :update}- Atomic updates via LWT (IF clauses){:atomic, :upsert}- Atomic upserts via LWT{:atomic, :create}- Atomic creates
Features NOT Supported
:offset- ScyllaDB has no OFFSET; use keyset pagination:expr_error- Expression error handling not implemented:expression_calculation_sort- Not supported:aggregate_filter- Aggregate filtering not supported:aggregate_sort- Aggregate sorting not supported:bulk_create_with_partial_success- Bulk create is all-or-nothing:update_many- Update-many not implemented:composite_type- Composite types not supported:through_relationship- Through relationships not supported:bulk_upsert_return_skipped- Not supported:distinct_sort- Not supported{:combine, :union}/{:combine, :union_all}/{:combine, :intersection}- No combination queries{:lock, :for_update}- Locking is a no-op{:join, _}- No JOINs (use denormalization){:lateral_join, _}- No lateral joins{:filter_relationship, _}- Relationship filtering not supported{:exists, :unrelated}- Exists queries not supported{:aggregate, :unrelated}- Unrelated aggregates not supported{:query_aggregate, :list}/{:query_aggregate, :first}/{:query_aggregate, :exists}/{:query_aggregate, :custom}- Only COUNT, SUM, AVG, MIN, MAX are supported{:aggregate, :list}/{:aggregate, :first}/{:aggregate, :exists}/{:aggregate, :custom}- Only COUNT, SUM, AVG, MIN, MAX are supported{:aggregate, :unrelated}- Unrelated aggregates not supported
Ash Query Extensions
The following Ash 3.0+ query features are supported via Xandra:
fragment/1+— raw CQL injection:fragment("col = ?", value)passes through to Xandra directlynow(),today(),ago(...),from_now(...)— evaluated client-side by Ash before reaching the data layerhas(collection_col, value)— maps to CQLCONTAINSon collection/set/list columnsoverlaps(collection_col, [a, b])— maps tocol CONTAINS a OR col CONTAINS bwith ALLOW FILTERING- Arithmetic operators (
+,-,*,/) — evaluated client-side by Ash - String functions (
concat,contains,starts_with,ends_with,string_length, etc.) — maps to CQLLIKEwhere applicable if/3,is_nil/1,length/1,round/1,string_downcase/1,string_trim/1— evaluated client-side by Ash
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
Filtering with LIKE (contains / starts_with / ends_with)
Ash's contains/2, starts_with/2, and ends_with/2 are translated to CQL
LIKE. In ScyllaDB, LIKE is only available on columns indexed with a
SASI index (or a similar text index). A LIKE filter against an unindexed
column will fail at query time rather than silently returning wrong results.
If you rely on substring/prefix/suffix matching, declare a SASI index on the
relevant column (e.g. via secondary_index with the appropriate index type)
or expect the query to error.
Summary
Functions
Returns the qualified table name (with keyspace prefix if configured).
Types
@type t() :: AshScylla.Query.t()
Functions
Returns the qualified table name (with keyspace prefix if configured).
Examples
iex> AshScylla.DataLayer.qualified_table(MyResource)
"test_ks.my_table"
@spec upsert(Ash.Resource.t(), Ash.Changeset.t()) :: {:ok, Ash.Resource.t()} | {:error, term()}