Skip to content

Queries and Statements as Functions

Gustavo De Micheli edited this page Jun 24, 2024 · 7 revisions

Let's see how we can query Cassandra using PreparedStatement as functions

Usage

First we need to mark a CqlSession implicit:

import net.nmoncho.helenus._

implicit val session: CqlSession = cqlSession 

Queries as Functions

Helenus defines several extension methods to create and prepare queries:

val hotelsByName = "SELECT * FROM hotels WHERE name = ?".toCQL.prepare[String]
// hotelsByName: internal.cql.ScalaPreparedStatement1[String, Row] = net.nmoncho.helenus.internal.cql.ScalaPreparedStatement1@66246f48

val resultSet = hotelsByName("Rotterdam Marriott").execute()
// resultSet: PagingIterable[Row] = com.datastax.oss.driver.internal.core.PagingIterableWrapper@44e96d1e

val result = resultSet.nextOption()
// result: Option[Row] = Some(
//   value = com.datastax.oss.driver.internal.core.cql.DefaultRow@32fe6674
// )

Let's take this step by step:

  • Queries are defined as String literals with a CQL syntax.
  • These have to be extended with the .toCQL method.
  • Then by using prepare we define how many parameters the statement has and what types does those parameters have. Here we obtain a ScalaPreparedStatement, a similar construction to PreparedStatement.
  • When a query is prepared, we can treat it as function that will produce BoundStatement when all parameters are provided.
  • We can decide whenever we want to execute these BoundStatements, which returns a ResultSet.
  • Finally, we also have extension methods on ResultSet to extract Rows from them.

There is also a short-hand syntax for executing a query:

val shortHandRs = hotelsByName.execute("Rotterdam Marriott")
// shortHandRs: PagingIterable[Row] = com.datastax.oss.driver.internal.core.PagingIterableWrapper@25645c81

val shortHandResult = shortHandRs.nextOption()
// shortHandResult: Option[Row] = Some(
//   value = com.datastax.oss.driver.internal.core.cql.DefaultRow@60ff394a
// )

After a statement is prepared, Helenus will check at runtime if the provided parameters types match what the database is expecting. If these don't match, a warning will be logged.

Asynchronous Execution

We can also define and execute statement asynchronously (ie. with Futures):

val hotelsByNameAsync = "SELECT * FROM hotels WHERE name = ?".toCQL.prepareAsync[String]
// hotelsByNameAsync: concurrent.Future[internal.cql.ScalaPreparedStatement1[String, Row]] = Future(Success(net.nmoncho.helenus.internal.cql.ScalaPreparedStatement1@6f7129d6))

val resultAsync = for {
  query <- hotelsByNameAsync
  resultSet <- query.executeAsync("Rotterdam Marriott")
} yield resultSet.currPage.nextOption()
// resultAsync: concurrent.Future[Option[Row]] = Future(Success(Some(com.datastax.oss.driver.internal.core.cql.DefaultRow@2e7d2f8a)))

Let's take this step by step:

  • Queries are again defined as String literals with a CQL syntax, and are extended with .toCQL.
  • Then by using prepareAsync we define how many and what types does those, parameters have. This returns a Future[ScalaPreparedStatement].
  • When a query is prepared, we can execute it whenever we see fit using the short-hand method executeAsync.

Iterating and Extracting Results

Defining and executing queries is one part of the story. The other part is how we make sense of the Rows we get out of these queries.

We can iterate results using some extension methods, and we can map rows to a more meaningful result using RowMappers.

We can also page over results using Pagers.

Clone this wiki locally