Skip to content

vptheron/epiphany

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Epiphany

Build Status Coverage Status hex.pm version

An Elixir driver for Apache Cassandra.

Important Disclaimer

Epiphany is a pet project for now. I use it to practice and learn Elixir.

Do not use in production. The API will probably change a lot. Furthermore, I will not put a lot of effort into tests and documentation until I have a better idea of where this is going.

However, if you would like to help improve the driver and provide feedback, you are very welcome to do so :)

Usage

Epiphany implements version 3 of the CQL binary protocol used by Cassandra.
This means that it should work with versions 2.x and 3.x of Cassandra. It is currently tested against Apache Cassandra 3.3.

Installation

Add epiphany to your list of dependencies in mix.exs:

def deps do
  [{:epiphany, "~> 0.1.0-dev"}]
end

Note that the version published on Hex may not have all the features listed in this README. To use the latest version of the code, use a Github dependency in mix.exs.

Examples

For now, Epiphany supports opening/closing a connection to one Cassandra node, and running simple queries.

Opening a connection:

# Open a connection to default (localhost:9042)
iex(1)> {:ok, conn} = Epiphany.new()
{:ok, #PID<0.124.0>}

# Open a connection to 10.5.5.5 on 9043
iex(1)> {:ok, conn} = Epiphany.new({'10.5.5.5', 9043})
{:ok, #PID<0.124.0>}

# Closing a connection
iex(2)> Epiphany.close(conn)
:ok

The connection can (and should) be shared among several clients. Note that Epiphany uses the Connection behaviour to handle automatic reconnection. If the Cassandra node you are trying to reach goes down, the client will try to reconnect every few seconds. In the mean time, requests are rejected (i.e. there is no queuing of the requests when in disconnected mode).

Running simple queries:

iex(2)> Epiphany.query(conn, "use excelsior")
{:result, {:set_keyspace, "excelsior"}}

iex(3)> Epiphany.query(conn, "INSERT INTO users(user_name, birth_year) VALUES ('alice', 1993)")          
{:result, :void}

iex(4)> {:result, result} = Epiphany.query(conn, "SELECT * FROM users")
{:result, %Epiphany.Result{... omitted ...}}

iex(4)> {:result, result} = Epiphany.query_with_values(
                              conn, 
                              "SELECT * FROM users WHERE user_name = ?",
                              [Epiphany.DataTypes.to_text("peter")])
{:result, %Epiphany.Result{... omitted ...}}

Using %Epiphany.Result:

iex(5)> result.row_count
3

iex(6)> Enum.map(result.rows, &(Epiphany.Result.Row.as_text(&1,0)))
["bob", "peter", "alice"]

iex(7)> Enum.map(result.rows, &(Epiphany.Result.Row.as_bigint(&1,1)))
[1902, 1967, 1993]

iex(8)> Enum.map(result.rows, &(Epiphany.Result.Row.as_text(&1,"user_name")))   
["bob", "peter", "alice"]

Note that the last example (using the name of the field to access the value) only works if the query was using skip_metadata: false, which is the default value (see next section). Disabling result metadata reduces the size of the response, at the cost of forcing column access by index only.

Complex queries:

iex(8)> Epiphany.query(
          conn,
          "SELECT * FROM users WHERE user_name = ?",
          %Epiphany.Query.Parameters{
            values: [Epiphany.DataTypes.to_text("peter")],
            consistency: :one,
            page_size: 1,
            paging_state: result.paging_state,
            serial_consistency: :local_serial,
            skip_metadata: true})

Query statements can include value placeholders (?). values is a list of bytestrings used with the placeholders. Epiphany.DataTypes contains functions to_XXX to be used to encode various types into bytestrings.

consistency can be set to the following values:

  • :one
  • :two
  • :three
  • :quorum
  • :all
  • :local_quorum
  • :each_quorum
  • :serial
  • :local_serial
  • :local_one

Note that :serial and :local_serial are the only supported values for serial_consistency.

page_size is used to control the size of the result set for each query. The result set will contain at most page_size rows. If there are more rows available, paging_state will be not-nil in the returned %Epiphany.Result, and can be used to run the exact same query with the paging_state. Example:

iex(9)> {:result, result} = Epiphany.query(
                              conn,
                              "SELECT * FROM users",
                              %Epiphany.Query.Parameters{page_size: 1})
{:result,
 %Epiphany.Result{
    row_count: 1,
    rows: [%Epiphany.Result.Row{
             col_count: 2, 
             columns: ["bob", <<0, 0, 0, 0, 0, 0, 7, 110>>]}]}}

iex(10)> {:result, result} = Epiphany.query(
                               conn,
                               "SELECT * FROM users",
                               %Epiphany.Query.Parameters{
                                 page_size: 1, 
                                 paging_state:  result.paging_state})
{:result,
 %Epiphany.Result{
    row_count: 1,
    rows: [%Epiphany.Result.Row{
              col_count: 2,
              columns: ["peter", <<0, 0, 0, 0, 0, 0, 7, 175>>]}]}}

Note that it will likely be detrimental to performance to pick a page_size value too low. A value below 100 is probably too low for most use cases.

Using prepared queries:

iex(5)> {:result, {:prepared, id}} = Epiphany.prepare(conn, "SELECT * FROM users")
{:result,
 {:prepared,
  <<101, 144, 20, 44, 208, 131, 139, 221, 194, 118, 95, 142, 46, 35, 223, 228>>}}
  
iex(6)> Epiphany.execute(conn, id)
{:result, %Epiphany.Result{... omitted ...}}

Note that execute can be given a %Epiphany.Query.Parameters to parameterized the query similarly to query.

Roadmap

A lot of things left to do. My main goal is to improve the quality of the code, the tests and the documentation. I use this project to learn the "Elixir Way". Other than that, here is a non-exhaustive list of what I have in mind:

  • Support missing data types (decimal, inet, uuid, varint and timeuuid)
  • Access to metadata in query result to be able to access fields by name instead of by indices (almost there, need to support complex types)
  • Automatic detection of types when querying (avoid using DataTypes)
  • Automatic detection of types when accessing values if metadata is present
  • Handle reconnection to a node (already done, but need more testing)
  • Support authentication and SSL
  • Support batch statements
  • Introduce Cluster type to support connection to an entire cluster with automatic discovery of the nodes, reconnection, session management, etc

License

Copyright 2016 Vincent Theron

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

About

Cassandra driver for Elixir

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages