Exandra
Adapter module for Apache Cassandra and ScyllaDB.
Uses 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.Repoitself, which you can seein 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:
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :my_app, adapter: Exandra
endSchemas
You can regularly use Ecto.Schema with Exandra. For example:
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
field :features, :mapyou 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:
field :home_phone, Exandra.UDT, type: :phone_number
field :office_phone, Exandra.UDT, type: :phone_numberArrays
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
field :checkins, {:array, :utc_datetime}
This field will use the native type list<timestamp>.
Exandra Types
If you want to use actual Cassandra/Scylla types such as
map<_, _>orset<_>, you can use the corresponding Exandra typesExandra.MapandExandra.Set.
Counter Tables
You can use the Exandra.Counter type to create counter fields (in counter tables). For
example:
@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:
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:
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
endCassandra and Scylla Types
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 thetexttype.: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 anatom. 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.Migrationdoesn't support creating, altering, or dropping Cassandra/Scylla UDTs. To do those operations in a migration, useEcto.Migration.execute/1orEcto.Migration.execute/2. For example, in your migration module:
def change do
execute(
_up_query = "CREATE TYPE full_name (first_name text, last_name text))",
_down_query = "DROP TYPE full_name"
)
end