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 acluster
or a connection and aname
.Cassandrax.Schema
- schemas are used to map data fetched from Cassandra into an Elixir struct. They useEcto.Schema
under the hood and should require a@primary_key
to be defined before the table definition.Cassandrax.Query
- followingEcto
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.