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}- Per-partition COUNT aggregates{: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{:aggregate_relationship, _}- Aggregate relationships not supported{:query_aggregate, _}- Query aggregates not supported{:aggregate, :sum}/{:aggregate, :avg}/ etc. - Only COUNT is 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
Summary
Functions
Computes the meta map for a list of resources.
Filters a list of resources to only those whose schema has changed compared to the previous meta map.
Loads the codegen meta file from disk.
Merges previous meta with updated entries for changed resources.
Returns the qualified table name (with keyspace prefix if configured).
Saves the codegen meta map to disk.
Types
@type t() :: AshScylla.Query.t()
Functions
Computes the meta map for a list of resources.
Returns a map of resource_key => schema_hash that can be used to detect changes between runs.
Filters a list of resources to only those whose schema has changed compared to the previous meta map.
Returns the list of changed resources.
Loads the codegen meta file from disk.
Returns an empty map if the file doesn't exist or can't be parsed.
Merges previous meta with updated entries for changed resources.
Returns the qualified table name (with keyspace prefix if configured).
Examples
iex> AshScylla.DataLayer.qualified_table(MyResource)
"test_ks.my_table"
Saves the codegen meta map to disk.
@spec upsert(Ash.Resource.t(), Ash.Changeset.t()) :: {:ok, Ash.Resource.t()} | {:error, term()}