CompanyJune 11, 2015

DataStax Java Driver: 2.2.0-rc1 released!

DataStax Java Driver: 2.2.0-rc1 released!

The Java driver team is pleased to announce the release of version 2.2.0-rc1, which brings parity with Cassandra 2.2. Here's what the new Cassandra features change for driver users:

  • New CQL data types
    • SMALLINT and TINYINT
    • DATE and TIME
  • New metadata for user-defined functions
  • Unbound variables
  • Query warnings

 

New CQL data types

Four new types were introduced:

SMALLINT and TINYINT

These two integer types match Java's short and byte, so their usage should be quite straightforward:

1

2

3

4

5

6

7

session.execute("CREATE TABLE IF NOT EXISTS small_ints(s smallint PRIMARY KEY, t tinyint)");

PreparedStatement pst = session.prepare("INSERT INTO small_ints (s, t) VALUES (:s, :t)");

session.execute(pst.bind(Short.MIN_VALUE, Byte.MAX_VALUE));

 

Row row = session.execute("SELECT * FROM small_ints").one();

short s = row.getShort("s");

byte t = row.getByte("t");

There is one minor catch: Java's integer literals default to int, which the driver serializes as CQLINTs. So the following will fail:

1

2

3

session.execute(pst.bind(1, 1));

// InvalidTypeException: Invalid type for value 0 of CQL type smallint,

// expecting class java.lang.Short but class java.lang.Integer provided

The workaround is simply to coerce your arguments to the correct type:

1

session.execute(pst.bind((short)1, (byte)1));

DATE and TIME

In previous versions, Cassandra only had TIMESTAMP to represent date and time. The two new types add support for date-only and time-only values:

1

2

3

4

5

6

7

session.execute("CREATE TABLE IF NOT EXISTS dates(ts timestamp PRIMARY KEY, d date, t time)");

session.execute("INSERT INTO dates (ts, d, t) VALUES ('2015-01-28 11:47:58', '2015-01-28', '11:47:58')");

 

Row row = session.execute("SELECT * FROM dates").one();

Date ts = row.getTimestamp("ts");

DateWithoutTime d = row.getDate("d");

long t = row.getTime("t");

The getter for TIMESTAMP used to be called getDate, we renamed it to getTimestampgetDate is now used for the DATE type. This is obviously a breaking change when migrating from a previous driver version, but we think the consistency was worth it.

DATE is modeled with a custom Java type called DateWithoutTime (but the name is likely to change before 2.2 goes GA). This was not an easy decision to make, but we ruled out other possibilities:

  • The new java.time types from Java 8 were not an option, because we want to keep compatibility with older JDKs.
  • We didn't want to bring in an additional dependency to Joda Time. And if we had, the logical thing would have been to migrate all time types to Joda, but its LocalTime type does not have nanosecond resolution.

We hope to introduce custom serializers soon (JAVA-721), so that users can use their preferred Java representations.

TIME is modeled with a long, which represents the number of nanoseconds since midnight.

New metadata for user-defined functions

Cassandra 2.2 introduces user-defined functions. Normal functions operate on individual rows:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

cqlsh:test> CREATE TABLE ints(i int PRIMARY KEY);

cqlsh:test> INSERT INTO ints(i) VALUES (1);

cqlsh:test> INSERT INTO ints(i) VALUES (2);

cqlsh:test> INSERT INTO ints(i) VALUES (3);

 

cqlsh:test> CREATE FUNCTION inc(i int)

            RETURNS NULL ON NULL INPUT

            RETURNS int LANGUAGE java

            AS 'return i+1;';

cqlsh:test> select inc(i) from ints;

 

 test.inc(i)

-------------

           2

           3

           4

Aggregates combine rows to compute a compound value:

1

2

3

4

5

6

7

8

9

cqlsh:test> CREATE FUNCTION plus(s int, v int)

            RETURNS NULL ON NULL INPUT

            RETURNS int LANGUAGE java AS 'return s+v;';

cqlsh:test> CREATE AGGREGATE sum(int) SFUNC plus STYPE int INITCOND 0;

cqlsh:test> SELECT test.sum(i) FROM ints;

 

 test.sum(i)

-------------

           6

From the client side, calling a user-defined function is not particularly different from a built-in one:

1

2

3

4

5

session.execute("SELECT test.sum(i) FROM ints");

 

// With the query builder:

import static com.datastax.driver.core.querybuilder.QueryBuilder.*;

session.execute(select().fcall("test.sum", raw("i")).from("ints"));

The most important addition to the driver is that functions are now exposed as metadata:

1

2

3

4

5

6

7

8

9

AggregateMetadata sum = cluster.getMetadata()

    .getKeyspace("test")

    .getAggregate("sum", DataType.cint());

System.out.printf("%s is an aggregate that computes a result of type %s%n",

    sum.getSimpleName(), sum.getReturnType());

 

FunctionMetadata plus = sum.getStateFunc();

System.out.printf("%s is a function that operates on %s%n",

    plus.getSimpleName(), plus.getArguments());

Note that, in order to retrieve a function or aggregate from a keyspace, you need to specify the argument types, to distinguish overloaded versions.

Unbound variables

In previous driver versions, you had to provide all variables in a bound statement, or it would get rejected. This is no longer the case:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

session.execute("CREATE TABLE IF NOT EXISTS unbound(k int PRIMARY KEY, v int)");

PreparedStatement pst = session.prepare("INSERT INTO unbound (k, v) VALUES (?, ?)");

 

// Normal execution: all values bound

session.execute(pst.bind(1, 1));

int v = session.execute("SELECT v FROM unbound WHERE k = 1")

    .one().getInt("v");

assert v == 1;

 

// New in 2.2: v is left unbound

session.execute(pst.bind(1));

v = session.execute("SELECT v FROM unbound WHERE k = 1")

    .one().getInt("v");

assert v == 1;

As you can see, unset values do not overwrite previously inserted ones.

Unset values are not always legal. Cassandra will throw an error if the resulting query is invalid:

1

2

3

PreparedStatement pst = session.prepare("SELECT v FROM unbound WHERE k = ?");

session.execute(pst.bind());

// InvalidQueryException: Invalid unset value for column k

Query warnings

Cassandra now sends warnings back with the response instead of just logging them server-side. The driver exposes them through ExecutionInfo. The easiest way to observe this is to simulate a large batch:

1

2

3

4

5

6

7

8

9

10

11

session.execute("CREATE TABLE IF NOT EXISTS example(k int primary key, v text)");

 

BatchStatement batch = new BatchStatement();

batch.add(new SimpleStatement("INSERT INTO example (k, v) VALUES (1, ?)",

    Strings.repeat("1", 5 * 1024)));

ResultSet rs = session.execute(batch);

 

List<String> warnings = rs.getExecutionInfo().getWarnings();

assert warnings.size() == 1;

// Batch of prepared statements for [test.example] is of size 5137,

// exceeding specified threshold of 5120 by 17.

Getting the driver

As always, the driver is available from Maven and from our downloads server.

Please note that this is a release candidate and is not meant to be used in production. The public API is subject to change until the final release. If you are planning to upgrade soon, be sure to read our upgrade guide.

We're also running a platform and runtime survey to improve our testing infrastructure. Your feedback would be most appreciated.

Discover more
ReleasesJava
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.