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;
constCassSchema* schema;
constCassDataType* data_type1;
CassStatement* statement;
CassUserType* user_type1;
CassFuture* future;
constchar
* 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;
constchar
* 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;
constCassResult* result;
constchar
* 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) {
constCassRow* 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)) {
constchar
* field_name;
size_tfield_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) {
constchar
* field1_value;
size_tfield1_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;
constchar
* 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;
constCassResult* result;
constchar
* 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) {
constCassRow* 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)) {
constCassValue* value = cass_iterator_get_value(tuple_iterator);
/* Get the tuple field's value */
if(cass_value_type(value) == CASS_VALUE_TYPE_TEXT) {
constchar
* text_value;
size_ttext_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 */
constCassValue* 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 ":<name>" of parameters instead of "?" */
constchar
* 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:
- Mailing List: https://groups.google.com/a/lists.datastax.com/forum/#!forum/cpp-driver-user
- IRC: #datastax-drivers on irc.freenode.net
- Review and contribute source code: https://github.com/datastax/cpp-driver
- Report issues on JIRA: https://datastax-oss.atlassian.net/browse/CPP