Row Extraction
Cassandra’s Row
holds the response from a statement. Using the driver, conversion into a useful Scala data type is
cumbersome both in extracting a value from the Row, and converting it from the Java type. Scala-Cass handles all of that
under the hood.
As an example, start with a Row
retrieved from Cassandra table. Let’s say this table has a definition of
scala> case class MyRow(s: String, i: Int, l: List[Long]) // s varchar, i int, l bigint
defined class MyRow
Then we select a row using a ScalaSession
(see more of select
here)
scala> case class Select(s: String)
defined class Select
scala> val rowRes = sSession.selectOneStar("mytable", Select("a str")).execute()
rowRes: com.weather.scalacass.Result[Option[com.datastax.driver.core.Row]] = Right(Some(Row[a str, 1234, [5678]]))
scala> val row: Row = rowRes.toOption.flatten.get
row: com.datastax.driver.core.Row = Row[a str, 1234, [5678]]
First, let’s extract into MyRow
using the regular driver
scala> import scala.collection.JavaConverters._
import scala.collection.JavaConverters._
scala> val driverDerivedRow = MyRow(row.getString("s"), row.getInt("i"), row.getList("l", classOf[java.lang.Long]).asScala.toList.map(Long.unbox))
driverDerivedRow: MyRow = MyRow(a str,1234,List(5678))
Especially for the List[Long]
, it is unnecessarily verbose, since we already have the necessary type information. In
addition, we don’t know if any of these values came back as null
, so in truth, we would need null checks as well
We can hide all of this boilerplate using the ScalaCass library. First, we need to import the syntax,
com.weather.scalacass.syntax
. Then let’s mirror that extraction from above
scala> import com.weather.scalacass.syntax._
import com.weather.scalacass.syntax._
scala> val scalaCassDerivedRow = MyRow(row.as[String]("s"), row.as[Int]("i"), row.as[List[Long]]("l"))
scalaCassDerivedRow: MyRow = MyRow(a str,1234,List(5678))
All we need to specify is the type that we want*, and the library handles the rest. If one of these values came back null, the driver will throw an exception since we do not want to introduce null values into our code.
If you do need to handle null types, use the Option
type to extract values, as this will return None
instead of
null
scala> row.as[Option[String]]("s")
res3: Option[String] = Some(a str)
There are 2 convenience functions around this, getAs
and getOrElse
, that retrieves an Optional response of the type,
and, in the case of getOrElse
, provides a default in the case it gets back a None
scala> row.getAs[Int]("i")
res5: Option[Int] = Some(1234)
scala> row.getOrElse[Int]("i", -12345)
res6: Int = 1234
If you want to handle the exception yourself, there is a way to simply attempt the extraction and return either the success or the failure
scala> row.attemptAs[Int]("i")
res8: com.weather.scalacass.Result[Int] = Right(1234)
Case Classes
We already have a conveniently defined MyRow
with all of the type information we want, so the Scala-Cass library (with
the help of the Shapeless library) can automatically use the case class
directly for extraction
scala> row.as[MyRow]
res9: MyRow = MyRow(a str,1234,List(5678))
scala> row.getAs[MyRow]
res10: Option[MyRow] = Some(MyRow(a str,1234,List(5678)))
scala> row.getOrElse[MyRow](MyRow("default row", -12345, List(-5678L)))
res11: MyRow = MyRow(a str,1234,List(5678))
scala> row.attemptAs[MyRow]
res12: com.weather.scalacass.Result[MyRow] = Right(MyRow(a str,1234,List(5678)))
Note that no arguments (aside from the type parameter) are passed when extracting to a case class because you are acting on the entire row.