View Source Exandra (exandra v0.10.2)

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.

configuration

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:

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: :binary_id], # Overrides the default type `bigserial` used for version attribute in schema migration
  contact_points: ["127.0.0.1"],  # List of database connection endpoints
  keyspace: "my_app_dev", # Name of your keyspace
  port: 9042,                     # Default port
  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.

schemas

Schemas

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

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, :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

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_number

String Keys

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

arrays

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

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<_, _> or set<_>, you can use the corresponding Exandra types Exandra.Map and Exandra.Set.

counter-tables

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)

batch-queries

Batch Queries

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.

multiple-keyspaces-using-prefixes

Multiple keyspaces using prefixes

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.

migrations

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
end

Cassandra 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 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 are all represented as the timestamp type.

user-defined-types-udts-1

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:

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

Link to this section Summary

Functions

Streams the results of a simple query or a prepared query.

Link to this section Functions

Link to this function

execute_batch(repo, batch, options \\ [])

View Source
@spec execute_batch(Ecto.Repo.t(), Exandra.Batch.t(), keyword()) ::
  :ok | {:error, Exception.t()}

Executes a batch query.

See Exandra.Batch.

examples

Examples

queries = [
  {"INSERT INTO users (email) VALUES (?)", ["jeff@example.com"]},
  {"INSERT INTO users (email) VALUES (?)", ["britta@example.com"]}
]

Exandra.execute_batch(MyApp.Repo, %Exandra.Batch{queries: queries})
Link to this function

stream!(repo, sql, values, opts \\ [])

View Source
@spec stream!(Ecto.Repo.t(), String.t(), [term()], Keyword.t()) ::
  Xandra.PageStream.t()

Streams the results of a simple query or a prepared query.

See Xandra.prepare!/4 and Xandra.stream_pages!/4 for more information including supported options.

examples

Examples

stream =
  Exandra.stream!(
    "SELECT * FROM USERS WHERE can_contact = ?",
    [true],
    MyRepo,
  )

Enum.each(
  stream,
  fn page ->
    Enum.each(page, fn user -> send_email(user.email, "Hello!") end)
  end
)