Skip to content

Queries and Statements as Functions

Gustavo De Micheli edited this page Aug 30, 2024 · 2 revisions

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

Usage

First we need to mark a CqlSession implicit:

import net.nmoncho.helenus.*

given 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: ScalaPreparedStatement1[String, Row] = net.nmoncho.helenus.internal.cql.ScalaPreparedStatement1@2e63d4f8

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

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

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@4e05ea7f

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

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: Future[ScalaPreparedStatement1[String, Row]] = Future(Success(net.nmoncho.helenus.internal.cql.ScalaPreparedStatement1@37569062))

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

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.