CompanyJuly 7, 2015

DataStax C/C++ Driver: 2.1 Beta released!

Michael Penick
Michael PenickDataStax
DataStax C/C++ Driver: 2.1 Beta released!

We are pleased to announce the 2.1 beta release of the C/C++ driver for Apache Cassandra and DataStax Enterprise. This release includes a majority of client-side features required to take advantage of Apache Cassandra 2.1 and DataStax Enterprise 4.7. These features include support for user defined types, tuples, nested (frozen) collections and named parameters. The final 2.1 release will also include client-side timestamps, support for the full range of stream IDs available in CQL native protocol version 3 and retry polices.

What’s new

User defined types

User defined types (UDTs for short), introduced in Apache Cassandra 2.1, allow for creating composite data types with multiple fields in a single column. This can be useful for simplifying schema by grouping related fields into a single UDT instead of using multiple columns. More information about using UDTs can be found in this post.

A UDT constructed from a data type definition found in schema metadata

int rc;

const
CassSchema* schema;
const
CassDataType* data_type1;

CassStatement* statement;
CassUserType* user_type1;
CassFuture* future;

const
char* query = "INSERT INTO keyspace1.table1 (key, value) VALUES (?, ?)";

/* Schema and type information is usually pretty static, therefore these
 * objects should be retreived sporadically and cached */
schema = cass_session_get_schema(session);
data_type1 = cass_schema_get_udt(schema, "keyspace1"
, "type1");

statement = cass_statement_new(query, 2);

/* The user type is created using the data type description found in the
 * Cassandra schema metadata */
user_type1 = cass_user_type_new_from_data_type(data_type1);

/* Values can be bound to a UDT by name (as well as by position) */
cass_user_type_set_string_by_name(user_type1, "field1"
, "abc");
cass_user_type_set_int32_by_name(user_type1, "field2"
, 123);

/* Bind the parameters to the query */
cass_statement_bind_string(statement, 0, "key1"
);
cass_statement_bind_user_type(statement, 1, user_type1);

future = cass_session_execute(session, statement);

rc = cass_future_error_code(future);
if
(rc != CASS_OK) {
  /* Handle error */
}

/* Clean up */
cass_schema_free(schema);
cass_statement_free(statement);
cass_user_type_free(user_type1);
cass_future_free(future);

 

A UDT constructed from a manually created data type definition 

int rc;

CassDataType* data_type1; /* Notice: not const */
CassStatement* statement;
CassUserType* user_type1;
CassFuture* future;

const
char* query = "INSERT INTO keyspace1.table1 (key, value) VALUES (?, ?)";

/* Manually create a data type that describes the UDT.
 * This should be created once and cached. */
data_type1 = cass_data_type_new(CASS_VALUE_TYPE_UDT);
cass_data_type_add_sub_value_type_by_name(data_type1, "field1"
, CASS_VALUE_TYPE_TEXT);
cass_data_type_add_sub_value_type_by_name(data_type1, "field2"
, CASS_VALUE_TYPE_INT);

statement = cass_statement_new(query, 2);
/* The user type is created using the data type description built previously */
user_type1 = cass_user_type_new_from_data_type(data_type1);

/* Values can be bound to a UDT by name (as well as by position) */
cass_user_type_set_string_by_name(user_type1, "field1"
, "def");
cass_user_type_set_int32_by_name(user_type1, "field2"
, 456);

/* Bind the parameters to the query */
cass_statement_bind_string(statement, 0, "key2"
);
cass_statement_bind_user_type(statement, 1, user_type1);

future = cass_session_execute(session, statement);

rc = cass_future_error_code(future);
if
(rc != CASS_OK) {
  /* Handle error */
}

/* Clean up */
cass_data_type_free(data_type1);
cass_statement_free(statement);
cass_user_type_free(user_type1);
cass_future_free(future);

Selecting a UDT value and iterating over its fields

CassStatement* statement;
CassFuture* future;
const
CassResult* result;

const
char* query = "SELECT value FROM keyspace1.table1";
statement = cass_statement_new(query, 0);
future = cass_session_execute(session, statement);

result = cass_future_get_result(future);
if
(result != NULL && cass_result_row_count(result) > 0) {
  const
CassRow* row = cass_result_first_row(result);

  /* Create an iterator to iterate over the UDT's fields */
  CassIterator* fields_iterator =
    cass_iterator_from_user_type(cass_row_get_column_by_name(row,
"value"));

  while
(cass_iterator_next(fields_iterator)) {
    const
char* field_name;
    size_t
field_name_size;

    /* Get the field's name */
    cass_iterator_get_user_type_field_name(fields_iterator,
                                           &field_name, &field_name_size);

    /* Get the field's value */
    if
(strncmp(field_name, "field1", field_name_size) == 0) {
      const
char* field1_value;
      size_t
field1_value_size;
      cass_value_get_string(cass_iterator_get_user_type_field_value(fields_iterator),
                            &field1_value, &field1_value_size);
      /* Use field1's value */
      printf
("%.*s %.*s\n", (int)field_name_size, field_name,
                            (
int)field1_value_size, field1_value);
    }
else if (strncmp(field_name, "field2", field_name_size) == 0) {
      cass_int32_t field2_value;
      cass_value_get_int32(cass_iterator_get_user_type_field_value(fields_iterator),
                           &field2_value);
      /* Use field2's value */
      printf
("%.*s %d\n", (int)field_name_size, field_name,
                          field2_value);
    }
  }

  cass_iterator_free(fields_iterator);
} else
{
  /* Handle error */
}

cass_statement_free(statement);
cass_future_free(future);

 

UDT schema

CREATE KEYSPACE keyspace1
WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': '3' };

CREATE TYPE keyspace1.type1 (field1 text, field2 int);

CREATE TABLE keyspace1.table1 (key text PRIMARY KEY, value frozen<type1>)

Tuples
Tuples, also introduced in Apache Cassandra 2.1, are useful for creating positional, fixed length sets of multiple types. They're similar to UDTs in that they are arbitrary composite types, however; tuple fields are unnamed, therefore its fields can only be referenced by position. This also means that it is not possible to add new fields to a tuple.

Inserting a tuple value

int rc;

CassStatement* statement;
CassTuple* tuple;
CassFuture* future;

const
char* query = "INSERT INTO keyspace1.table2 (key, value) VALUES (?, ?)";

statement = cass_statement_new(query, 2);

tuple = cass_tuple_new(2);

/* Values are bound to a tuple by position */
cass_tuple_set_string(tuple, 0, "abc"
);
cass_tuple_set_int32(tuple, 1, 123);

/* Bind the parameters to the query */
cass_statement_bind_string(statement, 0, "key1"
);
cass_statement_bind_tuple(statement, 1, tuple);

future = cass_session_execute(session, statement);

rc = cass_future_error_code(future);
if
(rc != CASS_OK) {
  /* Handle error */
}

/* Clean up */
cass_statement_free(statement);
cass_tuple_free(tuple);
cass_future_free(future);

Selecting a tuple value and iterating over its fields

CassStatement* statement;
CassFuture* future;
const
CassResult* result;

const
char* query = "SELECT value FROM keyspace1.table2";

statement = cass_statement_new(query, 0);
future = cass_session_execute(session, statement);


result = cass_future_get_result(future);
if
(result != NULL && cass_result_row_count(result) > 0) {
  const
CassRow* row = cass_result_first_row(result);

  /* Create an iterator to iterate over the tuple's fields */
  CassIterator* tuple_iterator =
    cass_iterator_from_tuple(cass_row_get_column_by_name(row,
"value"));

  while
(cass_iterator_next(tuple_iterator)) {
    const
CassValue* value = cass_iterator_get_value(tuple_iterator);

    /* Get the tuple field's value */
    if
(cass_value_type(value) == CASS_VALUE_TYPE_TEXT) {
      const
char* text_value;
      size_t
text_value_size;
      cass_value_get_string(value,
                            &text_value, &text_value_size);
      /* Use value */
      printf
("%.*s\n", (int)text_value_size, text_value);
    }
else if (cass_value_type(value) == CASS_VALUE_TYPE_INT) {
      cass_int32_t int_value;
      cass_value_get_int32(value, &int_value);
      /* Use value */
      printf
("%d\n", int_value);
    }
  }

 cass_iterator_free(tuple_iterator);
} else
{
  /* Handle error */
}

cass_statement_free(statement);
cass_future_free(future);

 

Tuple schema

CREATE TABLE keyspace1.table2 (key text PRIMARY KEY, value frozen<tuple<text, int>>);

Nested collections
With the release of Apache Cassandra 2.1 it is now possible to nest immutable (known as "frozen") collections within other collections. To support this feature the driver added functions and internal serialization logic to allow appending collections inside other collections.

A new method has been added for nesting collections

/* A nested collection can be appended to another collection */
cass_collection_append_collection(collection, nested_collection);

Collection values can now be recursively iterated

/* A nested collection can be retreived from another collection */
const
CassValue* nested_collection = cass_iterator_get_value(collection);
CassIterator* nested_collection_iterator =
cass_iterator_from_collection(nested_collection);

/* Iterate over nested collection */

Named parameters

It is possible to name parameters inside a query string. In previous releases only positional parameters were supported for non-prepared queries, that is, parameters denoted with "?" needed to be bound to a query in the same order as they appeared in the query string. This version of the driver allows parameters to be named using the ":<name>" syntax. Named parameters can also be used in conjunction with prepared queries, but are most useful for non-prepared queries where metadata for the parameters' names are not available.

int rc;

CassStatement* statement;
CassTuple* tuple;
CassFuture* future;

/* The query string uses the form ":&lt;name&gt;" of parameters instead of "?" */
const
char* query = "INSERT INTO keyspace1.table2 (key, value) VALUES (:k, :v)";

statement = cass_statement_new(query, 2);

tuple = cass_tuple_new(2);

/* Values are bound to a tuple by position */
cass_tuple_set_string(tuple, 0, "def"
);
cass_tuple_set_int32(tuple, 1, 456);

/* Bind the parameters to the query using names */cass_statement_bind_string_by_name(statement, "k"
, "key2");
cass_statement_bind_tuple_by_name(statement, "v"
, tuple);

future = cass_session_execute(session, statement);

rc = cass_future_error_code(future);
if
(rc != CASS_OK) {
  /* Handle error */
}

/* Clean up */
cass_statement_free(statement);
cass_tuple_free(tuple);
cass_future_free(future);

This release includes a couple internal improvements:Internal improvements

  • The driver now supports version 3 of the CQL native protocol allowing the driver to support new client-side features as well as supporting larger range of stream IDs. The larger range of stream IDs will allow the driver to achieve higher query throughput with less connections. The next driver release will fully capitalize on this potential increase in performance.
  • Read buffers are now cached and reused inside a connection. This is a big performance win on Windows and has the potential to improve performance on other platforms too. More information can be found in theJIRA issue.

What's next

In the next release we plan to finish Apache Cassandra 2.1 support and address any feedback or issues introduced by this beta release. Let us know what you think of the new features and API. To provide feedback use the following:

 

Discover more
DriversC++
Share

One-stop Data API for Production GenAI

Astra DB gives JavaScript developers a complete data API and out-of-the-box integrations that make it easier to build production RAG apps with high relevancy and low latency.