Adapter module for Apache Cassandra and ScyllaDB.
Uses Xandra
for communication with the
underlying database.
Uses Ecto
for database interfacing,
running schema migrations, and querying operations.
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 theEcto.Repo
documentation. -
Any of the options supported by
Xandra.Cluster.start_link/1
.
#{Exandra.Connection.start_opts_docs()}
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
end
You can configure your database connection in config/dev.exs
. Here's an example dev configuration
:
# Configure your database
config :my_app, MyApp.Repo,
migration_primary_key: [name: :id, type: :uuid], # Overrides the default type `bigserial` used for version attribute in schema migration
nodes: ["127.0.0.1"], # List of database connection endpoints
keyspace: "my_app_dev", # Name of your keyspace
sync_connect: 5000, # Waiting time in milliseconds for the database connection
log: :info,
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 10
Note: The bigserial
data type is specific to PostgreSQL databases and is not present in Scylla/Cassandra.
You can regularly use Ecto.Schema
with Exandra. For example:
defmodule User do
use Ecto.Schema
@primary_key {:id, Ecto.UUID, 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).
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, :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.
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_number
There is no validation with
Exandra.UDT
and the keys must be strings.
Alternatively, you can use the Exandra.EmbeddedType
for Ecto.Schema
-backed UDTs. For example, if you have a
phone_number
UDT, you can use:
field :home_phone, Exandra.EmbeddedType, using: MyApp.PhoneSchema
Finally, if you have a column of frozen UDTs list<frozen<phone_number>>
, you can still use the
Exandra.EmbeddedType
just as before with cardinality: :many
like so:
field :home_phone, Exandra.EmbeddedType, cardinality: :many, using: MyApp.PhoneSchema
Cassandra/Scylla has a native inet
type which represents either an ipv4 or an ipv6 address.
Exandra provides the Exandra.Inet
type for these fields.
field :last_ip, Exandra.Inet
Tuples can be declared using the Exandra.Tuple
type.
field :version, Exandra.Tuple, types: [:integer, :integer, :integer]
Access to native Cassandra/Scylla collections is available using the appropriate data types in the field definition.
@primary_key false
schema "hotels" do
field :checkins, {:array, :utc_datetime} # list<timestamp>
field :room_to_customer, Exandra.Map, key: :integer, value: :string # map<int, string>
field :available_rooms, Exandra.Set, type: :integer # set<int>
end
It's possible to create composite collections using the following syntax:
field :complex_type, {:array, Exandra.Map}, key: :string, value: Exandra.Set, type: :integerThis creates a
list<map<tuple<integer>, set<string>>>
.However, please note that due to the limited expressivity of this representation, each collection at the same level will have the same typing information:
field :valid, Exandra.Set, type: Exandra.Set, type: :integer # set<set<int>> field :invalid, Exandra.Map, key: Exandra.Set, type: :integer, value: Exandra.Set, type: :string # map<set<int>, set<int>> not map<set<int>, set<string>>
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)
You can run batch queries through Exandra. Batch queries are supported by
Cassandra/Scylla, and allow you to run multiple queries in a single request.
See Exandra.Batch
for more information and examples.
You can use query prefixes to query different keyspaces using the same schemas. As pointed out in the Ecto docs, migrations must be run for each prefix in this case.
You can run batch queries through Exandra. Batch queries are supported by
Cassandra/Scylla, and allow you to run multiple queries in a single request.
See Exandra.Batch
for more information and examples.
You can use query prefixes to query different keyspaces using the same schemas. As pointed out in the Ecto docs, migrations must be run for each prefix in this case.
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
end
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 thetext
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 calledfull_name
would be specified as the type:full_name
.:naive_datetime
,:naive_datetime_usec
,:utc_datetime
,:utc_datetime_usec
- these are all represented as thetimestamp
type.
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:
def change do
execute(
_up_query = "CREATE TYPE full_name (first_name text, last_name text))",
_down_query = "DROP TYPE full_name"
)
end