Otto Chrons bio photo

Otto Chrons

Jack of all trades, master of some.

Twitter LinkedIn Github

Cassandra is a powerful NoSQL database written in Java. I’ve been using Cassandra since version 0.6 with a variety of clients from Hector to Astyanax and finally the DataStax driver for Java.

As we moved from Java to Scala, I tried to find an appropriate Cassandra client for Scala. Unfortunately most of them were either dead or too complicated, trying to build an object model on top of basic CQL functionality. Being used to the “raw” Cassandra style since olden days, I just needed something to make the client a bit easier to use, supporting nice Scala features.

Most of the Cassandra code involves writing various queries and executing them (asynchronously) in the current session, so I came up with this super-thin layer on top of the existing DataStax client.

This piece of code adds two simple functionalities to the existing DataStax client interface. First has to do with asynchronous processing and facilitates converting Java ResultSetFuture into a standard Scala Future[ResultSet] . This allows you to use all the usual Future practices and write code like this:

val query = ...
session.executeAsync(query).map { resultSet =>
  // process the result asynchronously
} recover {
  case e : Exception =>
    // something went wrong in the query or processing it
}

Second decoration concerns building queries. With the Java driver you’re bound to Java rules and cannot take advantage of Scala language features. With a couple of implicits you can go from

val query = QueryBuilder.select("id", "col1").from("table").where(QueryBuilder.eq("id",id)).and(QueryBuilder.eq("id2",id2)).orderBy(desc("col1"))

to

val query = select("id", "col1") from "table" where ("id" === id) and ("id2" === id2) orderBy("col1" desc)

Note that eq is reserved in Scala, so a simple import QueryBuilder._ is not going to expose it to your code but you must use the full name, use `eq` or rename it in the import. With implicit decorations, all these problems go away and you’re left with clear and concise code.

Updates and inserts are also greatly simplified as shown by examples below:

update(T_TABLE) `with` (COL_OBJECTDATA := json) where (KEY_VERSION === fcall("now")) and (KEY_ID === obj.compositeId) and (KEY_CLASS === obj.partClass)
// or
insertInto(T_TABLE) values(KEY_VERSION -> fcall("now"), COL_OBJECTDATA -> json, KEY_ID -> obj.compositeId, KEY_CLASS -> obj.partClass)

Using sets is also simplified:

update(T_OBJECTINDEX) `with` (COL_OBJECT_IDS += objectId) where (KEY_INDEX_ID === index)

The wrapper is by no means complete and only addresses part of the CQL functionality (for example lists and maps are not enhanced), but it is quite easy to add new functionality in the same fashion.