Overview of the Cassandra CQL API

Class/Interface Description

CassandraCQLProvider

Resource adaptor provider interface used to:

  1. make synchronous queries

  2. create prepared statements

  3. create batch statements

  4. create new CassandraSession activities

CassandraSession

SLEE activity type used to:

  1. make an asynchrous query (result delivered as an event)

  2. get additional results in a result-set (result delivered as an event)

  3. test if a session is valid and has any outstanding queries

  4. end a session

There are also convenience operations to:

  1. make synchronous queries

  2. create prepared statements

CassandraStatement

Represents an executable cassandra query. This is the super-type for PreparedCassandraStatement and BoundCassandraStatement and as such defines operations that are common to both.

PreparedCassandraStatement

A statement that has been 'prepared' by the cassandra database. Such statements have been pre-parsed and validated by the database. A prepared statement can be executed once concrete values have been provided for the bind variables (to make a bound statement). A prepared statement also allows you to define default values for properties such as the Consistency level or tracing. Such default values are used in any bound statement created from the prepared statement.

BoundCassandraStatement

A prepared statement for which all bind variables have been bound to values.

BatchCassandraStatement

A group of statements that will be executed together as a 'batch' by the cassandra database.

CassandraResultSet

The result of a query against the cassandra database. Used to:

  1. test if a result set is exhausted, or there are more rows available

  2. access the rows in the result set

  3. fetch more rows in the rowset. This is an asynchronous request, with the result being delivered as an event.

CassandraRow

A row in a result set. Used to get values in the row by position, or name.

Examples

Getting the Cassandra CQL provider

You get the Cassandra CQL provider object from JNDI. For example, consider a service with the following @RATypeBinding:

@RATypeBinding(
    raType = @ComponentId(name = "cassandra-cql-ratype",
                        vendor = "OpenCloud",
                       version = "1.0.0"),
    activityContextInterfaceFactoryName =
                "cassandra-cql-ra/activitycontextinterfacefactory",
    resourceAdaptorEntityLink = "cassandra-cql-ra",
    resourceAdaptorObjectName = "cassandra-cql-ra/provider"
)

Then an SBB might include code such as the following in setSbbContext().

@Override
public void setSbbContext(SbbContext context) {
    try {
        final Context env = (Context) new InitialContext().lookup("java:comp/env");
        this.cassandraCQLProvider =
                (CassandraCQLProvider) env.lookup("cassandra-cql-ra/provider");
        this.cassandraCQLACIFactory =
                (CassandraCQLActivityContextInterfaceFactory)
                    env.lookup("cassandra-cql-ra/activitycontextinterfacefactory");
    }
    catch (NamingException e) {
        throw new RuntimeException("Failed Cassandra provider + ACI factory lookup", e);
    }
}

Preparing statements

A prepared statement is a cassandra query that has been pre-parsed and validated by the cassandra database. Ideally you prepare a query once, and then use it many times by binding values to binding variables in the query. The cassandra CQL RA manages a cache of prepared statements so you do not need to implement any type of caching yourself. This saves compute resources on the database, avoids needless network interaction with the database and minimises the time the application will block and wait during prepare.

final String query = "SELECT * FROM users WHERE lastname='Page'";
final PreparedCassandraStatement preparedQuery = cassandraCQLProvider.prepare(query);
Tip

You may also prepare statements via the prepare operation of the CassandraSession.

Binding values to variables

A bound statement is a prepared statement with values bound to the bind variables. The values of a bound statement can be set by either index or name. If multiple bind variables have the same name, setting that name will set all the variables for that name.

All the variables of the statement must be bound before it can be executed. If you don’t explicitly set a value for a variable, a CassandraException will be thrown when submitting the statement. If you want to set a variable to null, use the setToNull operation.

There are two options for creating a bound statement from a prepared statement:

  1. Use bind()

    final BoundCassandraStatement boundStatement = preparedStatement.bind();
  2. Use bind(Object…​ values)

    final BoundCassandraStatement boundStatement = preparedStatement.bind(firstValue,
                                                                          secondValue,
                                                                          thirdValue);

Use the set operations of BoundCassandraStatement to bind any remaining variables to values. There are two variants of each set operation, one which takes an index, and one which takes a name.

boundStatement.setString("firstname", "David").setString("lastname", "Page");
Tip

Use the isSet(int i) or isSet(String name) to check if a variable has been bound.

Using a batch statement

A batch statement is a group of queries that you wish to execute together in the cassandra database. Typically these queries are related, from a business logic perspective.

There are two steps to using a batch statement:

  1. Create a batch statement with the createBatchStatement operation of the provider.

    // create a new batch statement
    final BatchCassandraStatement batchStatement = cassandraCQLProvider.createBatchStatement()
  2. Add queries to the batch statement with the add and addAll operations.

    // add statements to the batch statement
    batchStatement.add(firstBoundStatement);
    batchStatement.addAll(boundStatementList);
    Note

    The options of the added statement such as consistency level, fetch size, tracing, will be ignored for the purpose of the execution of the Batch. Instead, the options used are the ones defined by the batch statement.

Once the batch statement is built, you can then execute it synchronously or asynchronously.

Executing synchronous queries

Queries are executed synchronously by using operations on the provider. There are three variants:

  1. Execute a query

    CassandraResultSet execute(String query) throws CassandraException;
  2. Execute a query, with arguments

    CassandraResultSet execute(String query, Object... values) throws CassandraException;
  3. Execute a statement

    CassandraResultSet execute(CassandraStatement st) throws CassandraException;

There is an overloaded version of each operation that take timeout arguments as well. The resource adaptor will throw a CassandraException if execute blocks longer than the timeout. For example:

CassandraResultSet execute(long timeout, TimeUnit units, String query) throws CassandraException;
Note

All of the non-timeout variants use the policy.query.defaulttimeout from Configuration options related to the Cassandra cluster as the timeout value.

Creating a CassandraSession for asynchronous queries

The result of an asynchronous query, or a request for additional results in a result set, are received as events that are associated with an activity called the CassandraSession. You use the Cassandra CQL provider to create a new session. You use an ActivityContextInterface factory to get the ActivityContextInterface associated with the activity so you can attach to it, and thereafter receive any events associated with the activity. See the following example.

final CassandraSession session = cassandraCQLProvider.newSession();
final ActivityContextInterface sessionACI =
        cassandraCQLACIFactory.getActivityContextInterface(session);
sessionACI.attach(context.getSbbLocalObject());

The Cassandra CQL provider will always return a CassandraSession object. In an overload or an error situation the CassaandraSession returned may not be valid and will fail any requests you make by throwing a CassandraException. You can test if the CassandraSession is valid with the isValid() operation.

final CassandraSession session = cassandraCQLProvider.newSession();
if (session.isValid()) {
    // if this is a valid 'session' then attach to the ACI.
    ActivityContextInterface sessionACI =
        cassandraCQLACIFactory.getActivityContextInterface(session);
    sessionACI.attach(context.getSbbLocalObject());
}
Tip Consider incrementing a statistic to record such an error. You could also define a threshold alarm that is a function of such a statistic.

Executing asynchronous queries

Queries are executed asynchronously by using operations on CassandraSession. There are three variants:

  1. Execute a query

    void execute(String query) throws CassandraException;
  2. Execute a query, with arguments

    void execute(String query, Object... values) throws CassandraException;
  3. Execute a statement

    void execute(CassandraStatement st) throws CassandraException;

The result (a CassandraResultSet) will be received in a future SLEE transaction as a QueryResultEvent.

There is an overloaded version of each operation that take timeout arguments as well. The resource adaptor will throw a CassandraException if future does not return a result within the timeout. For example:

CassandraResultSet execute(long timeout, TimeUnit units, String query) throws CassandraException;
Note

All of the non-timeout variants use the responseExecutor.guardtimer.commoninterval from Processing asynchronous responses as the timeout value.

Getting all the results in a result set

If a query will result in a large number of rows in the result set, then the results will be received as a number of pages.

Tip You can control the fetch size on a per query basis by using the setFetchSize() operation on CassandraStatement

You request the next page of results in a result set by using the fetchMoreResults() operation of CassandraResultSet. The next page of results will be received in a subsequent SLEE transaction as a QueryResultEvent (event if the initial query was executed synchronously).

public interface CassandraResultSet extends Iterable<CassandraRow> {
    // ...

    /**
     * Fetch the next page of results in the result set.
     * @param session session upon which subsequent results will be delivered
     * @return true if more results will be fetched,
     *         false if all results have already been fetched
     * @throws CassandraException if there is a failure requesting more results
     */
    boolean fetchMoreResults(CassandraSession session) throws CassandraException;

    // ...
}
Tip Use the isFullyFetched() operation of CassandraResultSet to test if all results in a result set have been fetched.
Previous page Next page