AshScylla.DataLayer.QueryBuilder (AshScylla v0.8.0)

Copy Markdown View Source

Query building functions for AshScylla data layer.

Provides optimized query building with filter-to-CQL conversion, prepared statement support, token-based pagination, aggregate queries, and support for Ash 3.0+ features including base_filter, select, distinct, keyset pagination, group by, CONTAINS/CONTAINS KEY, and TOKEN() functions.

Secondary Index Support

When filtering on non-primary key columns, this module checks if a secondary index exists and generates appropriate CQL. ScyllaDB/Cassandra can use secondary indexes for equality checks (=) but not for range queries.

Aggregate Queries

Supports COUNT, SUM, AVG, MIN, MAX aggregate functions with optional GROUP BY clauses for per-partition aggregation.

Keyset Pagination

Token-based pagination using the CQL TOKEN() function on partition keys, enabling efficient pagination without OFFSET overhead.

Summary

Functions

Converts aggregate type and field to CQL aggregate expression.

Applies the base_filter from the resource DSL to the query filters.

Builds a CONTAINS clause for collection type filtering.

Builds GROUP BY clause for aggregate queries.

Builds keyset pagination clause using TOKEN() function.

Builds an optimized CQL query from the data layer query struct.

Builds ORDER BY clause from sort items.

Builds a TOKEN() function clause for partition key queries.

Builds WHERE clause from Ash filters.

Checks if a filter can use secondary indexes.

Converts Ash filter expressions to CQL (safe version).

Converts Ash filter expressions to CQL (bang version).

Functions

aggregate_to_cql(kind, field)

@spec aggregate_to_cql(atom(), atom() | nil) :: String.t()

Converts aggregate type and field to CQL aggregate expression.

apply_base_filter(filters, base_filter)

@spec apply_base_filter(list(), term()) :: list()

Applies the base_filter from the resource DSL to the query filters.

The base_filter is prepended to the query filters so it is always applied.

build_aggregate_query(table, agg_expression, where_clause, params)

@spec build_aggregate_query(String.t(), String.t(), String.t(), list()) ::
  {String.t(), list()}

Builds an aggregate CQL query.

Supports COUNT, SUM, AVG, MIN, MAX with optional GROUP BY.

Examples

build_aggregate_query("users", "COUNT(*) AS total", "WHERE status = ?", ["active"])
# => {"SELECT COUNT(*) AS total FROM users WHERE status = ?", ["active"]}

build_contains_clause(column, value, atom)

@spec build_contains_clause(String.t(), term(), :contains | :contains_key) ::
  {String.t(), list()}

Builds a CONTAINS clause for collection type filtering.

In CQL, CONTAINS is used to check if a collection column contains a value. CONTAINS KEY is used to check if a map column contains a key.

build_group_by(columns)

@spec build_group_by(list() | nil) :: {String.t(), list()}

Builds GROUP BY clause for aggregate queries.

build_keyset_clause(map)

@spec build_keyset_clause(map()) :: {String.t(), list()}

Builds keyset pagination clause using TOKEN() function.

Keyset pagination is more efficient than OFFSET for large datasets. It uses the CQL TOKEN() function on partition keys to fetch pages.

Examples

# For a single partition key column:
build_keyset_clause(%{partition_keys: [:id], values: [last_id], direction: :after})
# => {"WHERE TOKEN(id) > TOKEN(?)", [last_id]}

# For composite partition keys:
build_keyset_clause(%{partition_keys: [:org_id, :id], values: [last_org, last_id], direction: :after})
# => {"WHERE TOKEN(org_id, id) > TOKEN(?, ?)", [last_org, last_id]}

build_optimized_query(data_layer)

@spec build_optimized_query(AshScylla.DataLayer.t()) :: {String.t(), list()}

Builds an optimized CQL query from the data layer query struct.

Supports:

  • Column selection (:select)
  • DISTINCT on partition key columns
  • Keyset pagination (:keyset)
  • Aggregate queries (:aggregates)
  • GROUP BY for aggregate queries
  • Base filter from resource DSL
  • Multiple ORDER BY columns
  • IN queries with multiple values
  • CONTAINS / CONTAINS KEY for collection types
  • TOKEN() function for partition key queries

build_order_by(sorts)

@spec build_order_by(list()) :: {String.t(), list()}

Builds ORDER BY clause from sort items.

Sort items can be:

  • Maps with :field and :direction keys
  • Tuples like {field, direction} (Ash standard format)
  • Bare atoms (default to ASC)
  • Maps with only :field key (default to ASC)

Supports multiple columns for compound ordering.

build_token_clause(keys, values)

@spec build_token_clause(list(), list()) :: {String.t(), list()}

Builds a TOKEN() function clause for partition key queries.

TOKEN() is used in CQL to query by the token of partition keys, enabling efficient range queries across the ring.

Examples

build_token_clause([:id], [uuid_value])
# => {"TOKEN(id) = TOKEN(?)", [uuid_value]}

build_token_clause([:org_id, :id], [org_val, id_val])
# => {"TOKEN(org_id, id) = TOKEN(?, ?)", [org_val, id_val]}

build_where_clause(filters)

@spec build_where_clause(list()) :: {String.t(), list()}

Builds WHERE clause from Ash filters.

can_use_secondary_index?(resource, filters)

@spec can_use_secondary_index?(term(), list()) :: {:ok, list()} | {:error, term()}

Checks if a filter can use secondary indexes.

Returns {:ok, indexed_columns} if the filter can use indexes, or {:error, reason} if it cannot.

filter_to_cql(unknown)

@spec filter_to_cql(term()) ::
  {String.t(), list()} | {:error, {:unknown_filter, term()}}

Converts Ash filter expressions to CQL (safe version).

Returns {cql, params} on success or {:error, {:unknown_filter, term()}} on failure.

Supports:

  • Standard comparison operators: eq, not_eq, gt, gte, lt, lte
  • IN with list values
  • CONTAINS for collection type filtering
  • CONTAINS KEY for map key filtering
  • TOKEN() function for partition key queries
  • EXISTS for existence checks
  • AND/OR boolean combinations
  • Nested expressions

filter_to_cql!(filter)

@spec filter_to_cql!(term()) :: {String.t(), list()}

Converts Ash filter expressions to CQL (bang version).

Raises ArgumentError on unknown filter expressions.