README.md

# Exandra

[![Hex.pm](https://img.shields.io/hexpm/v/exandra)](https://hex.pm/packages/exandra)
![GitHub](https://img.shields.io/github/license/vinniefranco/exandra)
[![CI](https://github.com/vinniefranco/exandra/actions/workflows/main.yml/badge.svg)](https://github.com/vinniefranco/exandra/actions/workflows/main.yml)
[![Coverage Status](https://coveralls.io/repos/github/vinniefranco/exandra/badge.svg?branch=main)](https://coveralls.io/github/vinniefranco/exandra?branch=main)
![Libraries.io dependency status for latest release](https://img.shields.io/librariesio/release/hex/exandra)

Adapter module for [Apache Cassandra](https://cassandra.apache.org/_/index.html)
and [ScyllaDB](https://www.scylladb.com/).

Uses [`Xandra`](https://github.com/lexhide/xandra) for communication with the
underlying database.

## Configuration

To configure an `Ecto.Repo` that uses `Exandra` as its adapter, you can use
the application configuration or pass the options when starting the repo.

You can use the following options:

- Any of the options supported by `Ecto.Repo` itself, which you can see
      in the `Ecto.Repo` documentation.

- Any of the option supported by `Xandra.Cluster.start_link/1`.

To configure your Ecto repository to use this adapter, you can use the
  `:adapter` option. For example, when defining the repo:

```elixir
defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app, adapter: Exandra
end
```

## Schemas

You can regularly use `Ecto.Schema` with Exandra. For example:

```elixir
defmodule User do
  use Ecto.Schema
  
  @primary_key {:id, :binary_id, autogenerate: true}
  schema "users" do
    field :email, :string
    field :meta, Exandra.Map, key: :string, value: :string
  end
end
```

You can use all the usual types (`:string`, `Ecto.UUID`, and so on).

### Maps

The `:map` type gets stored in Cassandra/Scylla as a blob of text with the map encoded as JSON. For example, if you have a schema with

```elixir
field :features, :map
```

you can pass the field as an Elixir map when setting it, and Exandra will convert it to a map
on the way from the database. Because Exandra uses JSON for this, you'll have to pay attention
to things such as atom keys (which can be used when writing, but will be strings when reading)
and such.

### User-Defined Types (UDTs)

If one of your fields is a UDT, you can use the `Exandra.UDT` type for it. For example, if you
 have a `phone_number` UDT, you can declare fields with that type as:

```elixir
field :home_phone, Exandra.UDT, type: :phone_number
field :office_phone, Exandra.UDT, type: :phone_number
```
### Arrays

You can use arrays with the Ecto `{:array, <type>}` type. This gets translated to the
`list<_>` native Cassandra/Scylla type. For example, you can declare a field as

```elixir
field :checkins, {:array, :utc_datetime}
```

This field will use the native type `list<timestamp>`.

  > #### Exandra Types {: .tip}
  >
  > If you want to use actual Cassandra/Scylla types such as `map<_, _>` or
  > `set<_>`, you can use the corresponding Exandra types `Exandra.Map` and `Exandra.Set`.

### Counter Tables

You can use the `Exandra.Counter` type to create counter fields (in counter tables). For
example:

```elixir
@primary_key false
schema "page_views" do
  field :route, :string, primary_key: true
  field :total, Exandra.Counter
end
```

You can only *update* counter fields. You'll have to use `c:Ecto.Repo.update_all/2`
to insert or update counters. For example, in the table above, you'd update the
  `:total` counter field with:

```elixir
  query =
    from page_view in "page_views",
    where: page_view.route == "/browse",
    update: [set: [total: 1]]

  MyApp.Repo.update_all(query)
```

## Migrations

You can use Exandra to run migrations as well, as it supports most of the DDL-related
  commands from `Ecto.Migration`. For example:

```elixir
defmodule AddUsers do
  use Ecto.Migration

  def change do
    create table("users", primary_key: false) do
      add :email, :string, primary_key: true
      add :age, :int
    end
  end
end
```

  > #### Cassandra and Scylla Types {: .info}
  >
  > When writing migrations, remember that you must use the **actual types** from Cassandra or
  > Scylla, which you must pass in as an *atom*.
  >
  > For example, to add a column with the type of
  > a map of integer keys to boolean values, you need to declare its type as
  > `:"map<int, boolean>"`.

This is a non-comprehensive list of types you can use:

- `:"map<key_type, value_type>"` - maps (such as `:"map<int, boolean>"`).
- `:"list<type>"` - lists (such as `:"list<uuid>"`).
- `:string` - gets translated to the `text` type.
- `:map` - maps get stored as text, and Exandra dumps and loads them automatically.
- `<udt>` - User-Defined Types (UDTs) should be specified as their name, expressed as an
      atom. For example, a UDT called `full_name` would be specified as the type `:full_name`.
- `:naive_datetime`, `:naive_datetime_usec`, `:utc_datetime`, `:utc_datetime_usec` -
      these get all represented as the `timestamp` type.

  ### User-Defined Types (UDTs)

  `Ecto.Migration` doesn't support creating, altering, or dropping Cassandra/Scylla **UDTs**.
  To do those operations in a migration, use `Ecto.Migration.execute/1`
  or `Ecto.Migration.execute/2`. For example, in your migration module:

```elixir
def change do
  execute(
    _up_query = "CREATE TYPE full_name (first_name text, last_name text))",
    _down_query = "DROP TYPE full_name"
  )
end
```