Cassandrax (Cassandrax v0.4.0) View Source

Cassandrax is a Cassandra ORM built on top of Xandra and Ecto. Xandra provides the driver for communication with the Database, and Ecto provides the data mapping functionality as well as changesets to control data mutations.

Cassandrax is heavily inspired by Triton and Ecto projects, if you have used any of those projects, you'll get up to speed in no time to use Cassandrax.

Cassandrax is split into 3 components following the design choices made on Ecto:

  • Cassandrax.Keyspace - keyspaces are the touchpoints with your Cassandra. Keyspaces provide the API for inserting, updating and deleting data from your tables, as well as querying the data already stored. Keyspaces need a cluster or a connection and a name.

  • Cassandrax.Schema - schemas are used to map data fetched from Cassandra into an Elixir struct. They use Ecto.Schema under the hood and should require a @primary_key to be defined before the table definition.

  • Cassandrax.Query - following Ecto design, they're written in Elixir syntax and are used to retrieve data from a Cassandra table. Queries are composable, even though they should remain as straight forward as possible. Unlike with relational databases, where you first model the data and their relationships and later you define how you should query that data, on Cassandra query design decisions are made when modeling the tables. For more information, please refer to Cassandra Docs

Next we'll provide an overview of these components and how you'll use them to insert/change/fetch from/to Cassandra. Please check their corresponding module documentation for more in-depth description of the features available and options.

Keyspaces

Cassandrax.Keyspace is a wrapper around the keyspace. Each keyspace contains one or multiple tables and belongs to a cluster. A keyspace can be defined like so:

defmodule SomeKeyspace do
  use Cassandrax.Keyspace, cluster: SomeCluster, name: "some_keyspace"
end

And the configuration for SomeCluster must be in your application environment, usually defined in your config/config.exs:

config :cassandrax, clusters: [SomeCluster]

config :cassandrax, SomeCluster,
    protocol_version: :v4,
    nodes: ["127.0.0.1:9042"]
    username: "cassandra",
    password: "cassandra",
    # cassandrax accepts all options you'd use in xandra

Cassandrax automatically picks up these configs to start the pool of connections for each cluster. Keep in mind that all keyspaces that belong to the same cluster will share the same pool of connections. If you need your keyspace to have its own connection pool, please refer to the Cassandrax.Keyspace specific documentation.

Schemas

Schemas are used for table definition. Here's an example:

defmodule UserByEmail do
  use Cassandrax.Schema

  # needs to be defined *before* defining the schema
  @primary_key [:email]

  # table name is users_by_email. Notice we don't need to set the keyspace here
  table "users_by_email" do
    field :email, :string
    field :id, :integer
    field :username, :string
  end
end

Cassandrax uses Ecto.Schema to define a struct with the schema fields:

iex> user_by_email = %UserByEmail{email: "user@example.com"}
iex> user_by_email.email
"user@example.com"

Just like with Ecto, this schema allows us to interact with keyspaces, like so:

iex> user_by_email = %UserByEmail{email: "user@example.com", id: 123_456, username: "user"}
iex> SomeKeyspace.insert!(user_by_email)
%UserByEmail{...}

Unlike relational databases which come with the autoincrement ID as default primary key, Cassandra requires you to define your own primary key. Therefore calling Keyspace.insert/2 always returns the struct itself with updated metadata, but no changed fields.

Also, bear in mind that Cassandra doesn't provice consistency guarantees the same way relational databases do, so the returned values of, for instance, deleting a record that doesn't exist anymore is the same as deleting an existing one:

iex> user_by_email = %UserByEmail{email: "user@example.com", id: 123_456, username: "user"}

# Store the result in a variable
iex> result = SomeKeyspace.insert!(user_by_email)
%UserByEmail{...}

# Now delete the recently inserted record...
iex> SomeKeyspace.delete!(result)
%UserByEmail{...}

# And if you try to delete a record that doesn't exist, no error is returned
iex> SomeKeyspace.delete!(result)
%UserByEmail{...}

Queries

Cassandrax provides you with a DSL so you can write queries in Elixir, lowering the chances of writing invalid CQL statements. In some occasions, Cassandrax.Query will validate your query at compile time and fail as soon as possible if your query is invalid:

import Ecto.Query

query = UserByEmail |> where(:email == "user@example.com")

# Returns a List of %UserByEmail{} structs matching the query
result = SomeKeyspace.all(query)

Specific examples and detailed documentation for all available keywords are available in Cassandrax.Query module docs, but the supported keywords are:

  • :allow_filtering
  • :distinct
  • :group_by
  • :limit
  • :order_by
  • :per_partition_limit
  • :select
  • :where

Cassandrax.Keyspace provides the same API as Ecto: You have Keyspace.all/1 which returns all records matching a query, Keyspace.one/1 which returns a single entry or raises and Keyspace.get/2 which fetches an entry by its primary key.

Link to this section Summary

Link to this section Functions

Link to this function

cql(conn, statement, values \\ [], opts \\ [])

View Source
Link to this function

ensure_cluster_config!(empty, cluster)

View Source

Callback implementation for Application.start/2.

Link to this function

stream_cql(conn, statement, values \\ [], opts \\ [])

View Source