# `AshScylla.DataLayer.Dsl`
[🔗](https://github.com/ohhi-vn/ash_scylla/blob/main/lib/ash_scylla/data_layer/dsl.ex#L15)

DSL extensions for configuring ScyllaDB-specific options on Ash resources.

Supports Ash Framework 3.0+ features including base_filter, identities,
aggregates, calculations, preparations, changes, validations, pipelines,
multitenancy, code_interface, and extended action options.

## Usage

    defmodule MyApp.MyResource do
      use Ash.Resource,
        data_layer: AshScylla.DataLayer

      scylla do
        table "my_table"
        keyspace "my_keyspace"
        consistency :quorum
        ttl 3600
        lwt true

        base_filter [status: "active"]
        default_context %{tenant: "org_123"}
        description "My resource description"

        secondary_index :email
        secondary_index [:name, :age]
        secondary_index :status, name: "idx_user_status"

        materialized_view :users_by_email,
          primary_key: [:email, :id],
          include_columns: [:name, :age]

        pagination :token
        per_action_consistency read: :one, create: :quorum
      end

      attributes do
        uuid_primary_key :id
        uuid_v7_primary_key :vid
        integer_primary_key :seq_id
        create_timestamp :inserted_at
        update_timestamp :updated_at
        attribute :name, :string, public?: true, writable?: true
        attribute :email, :string, sensitive?: false
        attribute :status, :string
        attribute :age, :integer
      end

      identities do
        identity :unique_email, [:email]
      end

      aggregates do
        count :total_count
        count :active_count, filter: [status: "active"]
      end

      calculations do
        calculate :display_name, :string, expr(name)
      end

      preparations do
        prepare build(:load, [:email])
      end

      changes do
        change fn changeset, _context -> changeset end
      end

      validations do
        validate attribute_equals(:status, "active")
      end

      pipelines do
        pipe_through :read
      end

      multitenancy do
        strategy :attribute
        attribute :org_id
      end

      code_interface do
        define :create_user
        define_calculation :active_count
      end

      relationships do
        belongs_to :organization, MyApp.Organization
        has_one :profile, MyApp.Profile
        has_many :posts, MyApp.Post
        many_to_many :tags, MyApp.Tag
      end

      actions do
        create :create do
          accept [:name, :email, :status]
          argument :organization_id, :uuid
          change fn changeset, _context -> changeset end
          validate present([:name])
        end

        read :read do
          prepare build(:load, [:email])
          pagination offset?: true, max_page_size: 100
          metadata :total_count, :integer
          filter [status: "active"]
        end

        update :update do
          accept [:name, :status]
          change fn changeset, _context -> changeset end
          validate present([:name])
        end

        destroy :destroy do
          soft? true
          change fn changeset, _context -> changeset end
        end
      end
    end

## Options

- `:table` - The table name in ScyllaDB (overrides default)
- `:keyspace` - The keyspace to use (overrides repo default)
- `:consistency` - The consistency level for reads/writes
- `:ttl` - Default TTL for inserted records (in seconds)
- `:lwt` - Enable Lightweight Transactions (LWT) for atomic upserts using `INSERT ... IF NOT EXISTS` (default: `false`)

- `:secondary_index` - Define secondary indexes for non-primary key columns
- `:materialized_view` - Define materialized views with different primary key structure
- `:pagination` - Pagination mode: `:token` (default) or `:offset` for offset-based pagination
- `:per_action_consistency` - Per-action consistency overrides as a keyword list, e.g. `[read: :one, create: :quorum]`
- `:base_filter` - A filter expression applied to all queries on this resource (Ash 3.0)
- `:default_context` - Default context map merged into every query/changeset (Ash 3.0)
- `:description` - Human-readable description of the resource (Ash 3.0)
- `:identity` - Define unique identity constraints for upsert operations
- `:aggregate` - Define aggregate queries (count, sum, avg, min, max)
- `:calculation` - Define expression-based calculations
- `:preparation` - Define query preparations
- `:change` - Define changes applied to changesets
- `:validation` - Define attribute validations
- `:pipeline` - Define action pipelines via pipe_through
- `:multitenancy` - Configure multitenancy strategy (:context or :attribute)
- `:code_interface` - Define code interface functions
- `:relationship` - Define relationships (belongs_to, has_one, has_many, many_to_many)
- `:action` - Define actions with extended options (accept, argument, change, validate, prepare, pagination, metadata, filter, soft?)

## Features

The data layer supports:
- **Upsert** (`:upsert`) - Insert-or-update semantics with optional LWT
- **Atomic updates** (`{:atomic, :update}`) - LWT-based conditional updates
- **Atomic upserts** (`{:atomic, :upsert}`) - LWT-based insert-or-update
- **Bulk update/destroy** - `update_query` and `destroy_query` for filtered operations
- **Distinct** - On partition-key columns only
- **Aggregates** - COUNT via `:count` aggregate
- **Expression calculations** - In-memory post-processing
- **Boolean filter** - With OR-to-IN rewriting
- **Base filter** - Automatic filter applied to all queries (Ash 3.0)
- **Default context** - Context merged into all queries (Ash 3.0)
- **Identities** - Unique constraints for upsert operations (Ash 3.0)
- **Multitenancy** - Context-based and attribute-based strategies (Ash 3.0)

# `action_configs`

```elixir
@spec action_configs(module()) :: [map()]
```

Gets the action configurations defined for a resource.

Returns a list of maps with keys:
- `:type` - the action type (:create, :read, :update, :destroy)
- `:name` - the action name (atom)
- `:options` - action options (accept, argument, change, validate, etc.)

# `aggregates`

```elixir
@spec aggregates(module()) :: [map()]
```

Gets the aggregates defined for a resource.

Returns a list of maps with keys:
- `:type` - the aggregate type (:count, :sum, :avg, :min, :max)
- `:name` - the aggregate name (atom)
- `:field` - the field to aggregate on (optional)
- `:options` - additional options (filter, etc.)

# `base_filter`

```elixir
@spec base_filter(module()) :: term() | nil
```

Gets the base_filter configured for a resource.

The base_filter is a filter expression that is automatically applied
to all queries on this resource (Ash 3.0 feature).

# `calculations`

```elixir
@spec calculations(module()) :: [map()]
```

Gets the calculations defined for a resource.

Returns a list of maps with keys:
- `:name` - the calculation name (atom)
- `:type` - the calculation type (atom)
- `:expression` - the expression to evaluate
- `:options` - additional options

# `changes`

```elixir
@spec changes(module()) :: [map()]
```

Gets the changes defined for a resource.

# `consistency`

```elixir
@spec consistency(module()) :: atom() | nil
```

Gets the configured consistency level for a resource.

# `default_context`

```elixir
@spec default_context(module()) :: map() | nil
```

Gets the default_context configured for a resource.

The default_context is a map that is merged into every query and
changeset context for this resource (Ash 3.0 feature).

# `description`

```elixir
@spec description(module()) :: String.t() | nil
```

Gets the description configured for a resource.

# `has_secondary_index?`

```elixir
@spec has_secondary_index?(module(), atom()) :: boolean()
```

Checks if a column has a secondary index defined.

# `identities`

```elixir
@spec identities(module()) :: [map()]
```

Gets the identities defined for a resource.

Returns a list of maps with keys:
- `:name` - the identity name (atom)
- `:columns` - list of column names (atoms)
- `:options` - additional options

# `keyspace`

```elixir
@spec keyspace(module()) :: String.t() | nil
```

Gets the configured keyspace for a resource.

# `lwt`

```elixir
@spec lwt(module()) :: boolean()
```

# `materialized_views`

```elixir
@spec materialized_views(module()) :: [map()]
```

Gets the materialized views defined for a resource.

Returns a list of maps with keys:
- `:name` - the view name (atom)
- `:config` - the view configuration keyword list

# `migrate?`

```elixir
@spec migrate?(module()) :: boolean()
```

Returns whether this resource should be included in migrations.

Defaults to `true` for all resources using `AshScylla.DataLayer`.
Can be overridden in the DSL with `migrate false`.

# `multitenancy`

```elixir
@spec multitenancy(module()) :: map() | nil
```

Gets the multitenancy configuration for a resource.

Returns a map with keys:
- `:strategy` - :context or :attribute
- `:attribute` - the attribute name for :attribute strategy (optional)

# `pagination`

```elixir
@spec pagination(module()) :: :token | :offset
```

Gets the pagination mode for a resource.

Returns `:offset` or `:token`.

# `per_action_consistency`

```elixir
@spec per_action_consistency(module()) :: map()
```

Gets the per-action consistency configuration for a resource.

Returns a map of action_name => consistency_level.

# `pipelines`

```elixir
@spec pipelines(module()) :: [map()]
```

Gets the pipelines defined for a resource.

# `preparations`

```elixir
@spec preparations(module()) :: [map()]
```

Gets the preparations defined for a resource.

# `relationships`

```elixir
@spec relationships(module()) :: [map()]
```

Gets the relationships defined for a resource.

Returns a list of maps with keys:
- `:type` - :belongs_to, :has_one, :has_many, or :many_to_many
- `:name` - the relationship name (atom)
- `:target` - the target resource module
- `:options` - additional options

# `repo`

```elixir
@spec repo(module()) :: module() | nil
```

Gets the configured repo for a resource.

# `scylla`
*macro* 

```elixir
@spec scylla(keyword()) :: Macro.t()
```

Macro for configuring ScyllaDB options in Ash resources.

## Examples

    scylla do
      table "users"
      keyspace "my_keyspace"
      consistency :quorum
      ttl 3600

      base_filter [status: "active"]
      default_context %{tenant: "org_123"}
      description "User accounts"

      secondary_index :email
      secondary_index [:name, :age]

      materialized_view :users_by_email,
        primary_key: [:email, :id],
        include_columns: [:name, :age]

      pagination :token
      per_action_consistency read: :one, create: :quorum

      identity :unique_email, [:email]

      aggregate :count, :total_users
      aggregate :count, :active_users, filter: [status: "active"]

      calculation :display_name, :string, expr(name)

      preparation build(:load, [:email])

      change fn changeset, _context -> changeset end

      validation present([:name])

      pipeline :read

      multitenancy do
        strategy :attribute
        attribute :org_id
      end

      code_interface do
        define :create_user
        define_calculation :active_users
      end

      relationship :belongs_to, :organization, MyApp.Organization
      relationship :has_one, :profile, MyApp.Profile
      relationship :has_many, :posts, MyApp.Post
      relationship :many_to_many, :tags, MyApp.Tag

      action :create, :create_user do
        accept [:name, :email]
        argument :organization_id, :uuid
        change fn changeset, _context -> changeset end
        validate present([:name])
      end

      action :read, :list_users do
        pagination offset?: true, max_page_size: 100
        metadata :total_count, :integer
        filter [status: "active"]
      end

      action :update, :update_user do
        accept [:name, :status]
        change fn changeset, _context -> changeset end
      end

      action :destroy, :delete_user do
        soft? true
      end
    end

# `scylla_code_interface`

```elixir
@spec scylla_code_interface(module()) :: map() | nil
```

Gets the code_interface configuration for a resource.

# `secondary_indexes`

```elixir
@spec secondary_indexes(module()) :: [map()]
```

Gets the secondary indexes defined for a resource.

Returns a list of maps with keys:
- `:columns` - list of column names (atoms)
- `:name` - optional custom index name
- `:options` - additional options

# `table`

```elixir
@spec table(module()) :: String.t() | nil
```

Gets the configured table name for a resource.

# `ttl`

```elixir
@spec ttl(module()) :: pos_integer() | nil
```

Gets the configured TTL for a resource.

# `validations`

```elixir
@spec validations(module()) :: [map()]
```

Gets the validations defined for a resource.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
