AshScylla.DataLayer (AshScylla v1.0.5)

Copy Markdown View Source

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
end

Features 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

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

t()

@type t() :: AshScylla.Query.t()

Functions

codegen(action, opts)

@spec codegen(
  atom(),
  keyword()
) :: {:ok, term()} | {:error, term()}

compute_codegen_meta(resources)

@spec compute_codegen_meta([module()]) :: %{required(String.t()) => integer()}

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.

filter_changed_resources(resources, previous_meta)

@spec filter_changed_resources([module()], map()) :: [module()]

Filters a list of resources to only those whose schema has changed compared to the previous meta map.

Returns the list of changed resources.

load_codegen_meta(meta_file)

@spec load_codegen_meta(String.t()) :: map()

Loads the codegen meta file from disk.

Returns an empty map if the file doesn't exist or can't be parsed.

merge_codegen_meta(previous_meta, changed_resources, current_meta)

@spec merge_codegen_meta(map(), [module()], map()) :: map()

Merges previous meta with updated entries for changed resources.

qualified_table(resource)

@spec qualified_table(module()) :: String.t()

Returns the qualified table name (with keyspace prefix if configured).

Examples

iex> AshScylla.DataLayer.qualified_table(MyResource)
"test_ks.my_table"

save_codegen_meta(meta_file, meta)

@spec save_codegen_meta(String.t(), map()) :: :ok

Saves the codegen meta map to disk.

upsert(resource, changeset)

@spec upsert(Ash.Resource.t(), Ash.Changeset.t()) ::
  {:ok, Ash.Resource.t()} | {:error, term()}