Skip to content

Commit 4cd04cc

Browse files
psarnajul-stas
authored andcommitted
decoder: handle empty non-string types
For legacy reasons, non-string types support "empty" values, e.g. you can have an empty int, distinct from NULL. This case used to trigger deserialization errors, so now it's simply a fallback to NULL. Another option is to introduce a new "empty" state that a value can be in, but it's semantically no different from NULL, even though it's serialized in a different way. Ref: https://github.com/apache/cassandra/blob/7b58b79fe24c612f8bbf7984c325e88496f482d6/doc/native_protocol_v4.spec#L861-L863
1 parent 4bf44c7 commit 4cd04cc

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

src/decoder.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ Value Decoder::decode_value(const DataType::ConstPtr& data_type) {
149149

150150
int32_t count = 0;
151151
if (!data_type->is_collection()) {
152+
if (!is_string_type(data_type->value_type()) && !is_bytes_type(data_type->value_type())
153+
&& size == 0) {
154+
// For legacy reasons, non-string types support "empty" values, e.g. you can have
155+
// an empty int, distinct from NULL. This case used to trigger deserialization errors,
156+
// so now it's simply a fallback to NULL. Another option is to introduce a new "empty"
157+
// state that a value can be in, but it's semantically no different from NULL,
158+
// even though it's serialized in a different way.
159+
// Ref: https://github.com/apache/cassandra/blob/7b58b79fe24c612f8bbf7984c325e88496f482d6/doc/native_protocol_v4.spec#L861-L863
160+
return Value(data_type);
161+
}
152162
return Value(data_type, decoder);
153163
} else if (decoder.decode_int32(count)) {
154164
return Value(data_type, count, decoder);

tests/src/unit/tests/test_decoder.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <gtest/gtest.h>
1818

1919
#include "decoder.hpp"
20+
#include "value.hpp"
2021
#include "logger.hpp"
2122

2223
using namespace datastax;
@@ -1002,3 +1003,48 @@ TEST_F(DecoderUnitTest, DecodeWarnings) {
10021003
ASSERT_FALSE(decoder.decode_warnings(value));
10031004
ASSERT_TRUE(failure_logged_);
10041005
}
1006+
1007+
TEST_F(DecoderUnitTest, DecodeEmpty) {
1008+
CassValueType nonzero_length_scalars[] = {
1009+
CASS_VALUE_TYPE_BIGINT,
1010+
CASS_VALUE_TYPE_BOOLEAN,
1011+
CASS_VALUE_TYPE_COUNTER,
1012+
CASS_VALUE_TYPE_DECIMAL,
1013+
CASS_VALUE_TYPE_DOUBLE,
1014+
CASS_VALUE_TYPE_FLOAT,
1015+
CASS_VALUE_TYPE_INT,
1016+
CASS_VALUE_TYPE_TIMESTAMP,
1017+
CASS_VALUE_TYPE_UUID,
1018+
CASS_VALUE_TYPE_TIMEUUID,
1019+
CASS_VALUE_TYPE_INET,
1020+
CASS_VALUE_TYPE_DATE,
1021+
CASS_VALUE_TYPE_TIME,
1022+
CASS_VALUE_TYPE_SMALL_INT,
1023+
CASS_VALUE_TYPE_TINY_INT,
1024+
CASS_VALUE_TYPE_DURATION,
1025+
};
1026+
CassValueType variable_length_scalars[] = {
1027+
CASS_VALUE_TYPE_CUSTOM,
1028+
CASS_VALUE_TYPE_ASCII,
1029+
CASS_VALUE_TYPE_BLOB,
1030+
CASS_VALUE_TYPE_TEXT,
1031+
CASS_VALUE_TYPE_VARCHAR,
1032+
CASS_VALUE_TYPE_VARINT,
1033+
};
1034+
for (CassValueType t : nonzero_length_scalars) {
1035+
const char input[4] = { 0, 0, 0, 0 };
1036+
TestDecoder decoder((const char*)input, 4);
1037+
DataType::ConstPtr type_ptr(new DataType(t));
1038+
Value val = decoder.decode_value(type_ptr);
1039+
// Empty types are deserialized without errors and treated as null
1040+
ASSERT_TRUE(val.is_null());
1041+
}
1042+
for (CassValueType t : variable_length_scalars) {
1043+
const char input[4] = { 0, 0, 0, 0 };
1044+
TestDecoder decoder((const char*)input, 4);
1045+
DataType::ConstPtr type_ptr(new DataType(t));
1046+
Value val = decoder.decode_value(type_ptr);
1047+
// Empty types with variable size are simply empty, not null
1048+
ASSERT_FALSE(val.is_null());
1049+
}
1050+
}

0 commit comments

Comments
 (0)