s7/scale7-pelops — Issue #48: Using Bytes.CompositeBuilder results in "Not enough bytes to read value of component 0" exception
Author: toddfast
Created: 2011-10-28 00:43:51+00:00
State: open

I have a column family that uses Cassandra's CompositeType as the comparator for column names. My column family declaration (in CQL) is basically the following:

create columnfamily Foo ('id' uuid PRIMARY KEY)
with comparator = 'CompositeType(UTF8Type,UTF8Type)'
and default_validation = text;

When I try to insert a column using Pelop's Bytes.CompositeBuilder to construct the column name, I receive the following exception:

Caused by: InvalidRequestException(why:Not enough bytes to read value of component 0)
at org.apache.cassandra.thrift.Cassandra$batch_mutate_result.read(Cassandra.java:19479)
at org.apache.cassandra.thrift.Cassandra$Client.recv_batch_mutate(Cassandra.java:1035)
at org.apache.cassandra.thrift.Cassandra$Client.batch_mutate(Cassandra.java:1009)
at org.scale7.cassandra.pelops.Mutator$1.execute(Mutator.java:88)
at org.scale7.cassandra.pelops.Mutator$1.execute(Mutator.java:84)
at org.scale7.cassandra.pelops.Operand.tryOperation(Operand.java:86)

I believe the exception above is caused by Bytes.CompositeBuilder generating a byte array incompatible with CompositeType. Looking at the source for Bytes (lines 1124-1131), I can see that CompositeBuilder is simply concatenating the byte buffers of its individual components. However, the documentation for CompositeType describes a very specific format for composite values:

(From https://github.com/apache/cassandra/blob/trunk/src/java/org/apache/cassandra/db/marshal/CompositeType.java)
...
* The encoding of a CompositeType column name should be:
* ...
* where is:
* <'end-of-component' byte>
* where is a 2 bytes unsigned short the and the
* 'end-of-component' byte should always be 0 for actual column name.
* However, it can set to 1 for query bounds. This allows to query for the
* equivalent of 'give me the full super-column'. That is, if during a slice
* query uses:
* start = <3><"foo".getBytes()><0>
* end = <3><"foo".getBytes()><1>
* then he will be sure to get *all* the columns whose first component is "foo".
* If for a component, the 'end-of-component' is != 0, there should not be any
* following component. The end-of-component can also be -1 to allow
* non-inclusive query. For instance:
* start = <3><"foo".getBytes()><-1>
* allows to query everything that is greater than <3><"foo".getBytes()>, but
* not <3><"foo".getBytes()> itself.
...

Cassandra is trying to compare the column name according to the CompositeKey format, and when it does, it reads the first two bytes of the data as the length of the first component value. It then fails when it finds the buffer is not long enough. If I change the comparator declaration to "blob", there is no error.

This problem affects both Pelops 1.2 and 1.3.

It's not clear whether CompositeBuilder was even intended to be compatible with the CompositeType format or if this is just an oversight. At minimum, the documentation for CompositeBuilder needs to clarify how it relates to CompositeType.