Skip to content
Gustavo De Micheli edited this page Aug 30, 2024 · 3 revisions

main status Maven Central 3

Helenus is collection of Scala utilities for Apache Cassandra. Its goal is to make interacting with Cassandra easier, in a type-safe manner, while trying to avoid introducing a complex API.

This wiki covers the Scala 3 version of Helenus. For Scala 2, go here.

Installation

Include the library into you project definition:

libraryDependencies += "net.nmoncho" %% "helenus-core" % "1.0.0"

Motivation

We tried using libraries such as Phantom and Quill, which are great by the way, but they didn't fit entirely our mindset or workflow. We believe the best way to use Cassandra, or any DB for that matter, is to use its Query Language directly.

Helenus takes inspiration from libraries such as Anorm, trying to provide a similar experience by putting CQL first. Our goals are:

  • Give users control over the queries that are actually executed.
  • Keep the library simple with a concise API.

Concepts

With this motivation in mind, Helenus builds on top of the concepts defined by the Cassandra Java Driver:

  • PrepareStatement: The preferred way to define an DML operation (e.g. SELECT, or INSERT). We use a string literal to define an operation. The driver will parse and validate the query.
  • BoundStatement: Once we have a PreparedStatement, we can run it against the database by binding all the defined parameters (e.g. ?)
  • TypeCodec: A TypeCodec tells the driver how to encode, or decode, a particular JVM type.

Helenus builds on top of these concepts:

  • ScalaPreparedStatement: Extends the original PreparedStatement statement by introducing helper methods for execution. A ScalaPreparedStatement can be treated as a function, where its input parameters are the bound parameters defined in the statement, and its output as the rows that come out of executing the statement.
  • ScalaBoundStatement: When a ScalaPreparedStatement is executed it will produce a ScalaBoundStatement which carries along the output type defined by the original statement
  • RowMapper: A RowMapper tells Helenus how to extract desired results out of a Row. RowMappers can be defined explicitly, or derived implicitly (See Extracting Results)
  • Adapter: Statements are defined with a sequence of parameters. An Adapter tells Helenus how to adapt a complex type, such as case class, to the parameters a statement expects.
  • Mapping: A Mapping brings both an Adapter and a RowMapper under the same abstraction. Its goal is to make working with case classes easier. Mappings are specially useful when the statement takes more than 22 parameters (e.g. inserting a case class that has more than 22 fields)
  • ColumnMapper: By default, implicitly derived RowMappers will map columns to fields one-to-one. A ColumnMapper tells Helenus how to convert columns into/from fields in a more controlled fashion.
  • ColumnNamingScheme: By default, field names are mapped one-to-one to column names (e.g. for a field named "hotelId" Helenus will expect a column name "hotelId"). With an implicit ColumnNamingScheme you can control how this mapping is done.
  • Pager: A Pager let's you query results in a paginated fashion, providing a consistent behavior regardless of how you execute such query. Please read Pagination

With these concepts in mind, let's see how we work with the library.

Queries and Statements

We can define queries and statements in two ways: as functions, or as interpolated statements.

The main distinction between these two is that with the former we get a ScalaPreparedStatement which we use as a function to get data in or out of Cassandra, creating a ScalaBoundStatement in the process (ie. parameters have to be bound before executing the statement). With the latter we get directly a ScalaBoundStatement as parameters are defined at the same moment as the statement (the statement is still prepared but everything is done in one step).

The Cassandra Java Driver caches PreparedStatements, so using an Interpolated Statement won't prepare the query multiple times.

Statements can be executed in three ways:

  • Synchronously
  • Asynchronously
  • Reactively (e.g. Akka or Pekko Streams)

Queries and Statements, as Interpolated Statements

To learn how to query with Helenus, see Queries and Statements. And for using the asynchronous API, see Queries and Statements (Async).

Codecs

For documentation on TypeCodec, see Codecs.

Akka

For Akka Streams integration, see Akka.

Flink

For Flink integration, see Flink.

Pekko

For Pekko Streams integration, see Pekko.