types: make empty type deserialize to non-null value
The empty type is used internally to implement CQL sets on top
of multi-cell maps. The map's key (an atomic cell) represents the
set value, and the map's value is discarded. Since it's unneeded
we use an internal "empty" type.
Currently, it is deserialized into a `data_value` object representing
a NULL. Since it's discarded, it really doesn't matter.
However, with the impending change to change lists to allow NULLs,
it does matter:
1. the coordinator sets the 'collections_as_maps' flag for LWT
requests since it wants list indexes (this affects sets too).
2. the replica responds by serializing a set as a map.
3. since we start allow NULL collection values, we now serialize
those NULLs as NULLs.
4. the coordinator deserializes the map, and complains about NULL
values, since those are not supported.
The solution is simple, deserialize the empty value as a non-NULL
object. We create an empty empty_type_representation and add the
scaffolding needed. Serialization and deserialization is already
coded, it was just never called for NULL values (which were serialized
with size 0, in collections, rather than size -1, luckily).
A unit test is added.
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
#include "utils/big_decimal.hh"
|
||||
|
||||
struct empty_type_impl final : public abstract_type {
|
||||
using native_type = empty_type_representation;
|
||||
empty_type_impl();
|
||||
};
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <string_view>
|
||||
#include <seastar/testing/test_case.hh>
|
||||
#include <seastar/net/inet_address.hh>
|
||||
#include "utils/UUID_gen.hh"
|
||||
@@ -25,6 +26,7 @@
|
||||
#include "test/lib/exception_utils.hh"
|
||||
|
||||
using namespace std::literals::chrono_literals;
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
void test_parsing_fails(const shared_ptr<const abstract_type>& type, sstring str)
|
||||
{
|
||||
@@ -957,3 +959,11 @@ SEASTAR_TEST_CASE(test_simple_type_compatibility) {
|
||||
}
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_empty_type_serialization) {
|
||||
auto v = data_value(empty_type_representation());
|
||||
auto ser = v.serialize();
|
||||
BOOST_REQUIRE(ser.has_value());
|
||||
BOOST_REQUIRE_EQUAL(*ser, to_bytes(sstring(""sv)));
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
11
types.cc
11
types.cc
@@ -2111,7 +2111,7 @@ struct deserialize_visitor {
|
||||
}
|
||||
data_value operator()(const tuple_type_impl& t) { return deserialize_aux(t, v); }
|
||||
data_value operator()(const user_type_impl& t) { return deserialize_aux(t, v); }
|
||||
data_value operator()(const empty_type_impl& t) { return data_value::make_null(t.shared_from_this()); }
|
||||
data_value operator()(const empty_type_impl& t) { return data_value(empty_type_representation()); }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2923,8 +2923,7 @@ struct native_value_clone_visitor {
|
||||
}
|
||||
void* operator()(const counter_type_impl&) { fail(unimplemented::cause::COUNTERS); }
|
||||
void* operator()(const empty_type_impl&) {
|
||||
// Can't happen
|
||||
abort();
|
||||
return new empty_type_representation();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2944,8 +2943,7 @@ struct native_value_delete_visitor {
|
||||
}
|
||||
void operator()(const counter_type_impl&) { fail(unimplemented::cause::COUNTERS); }
|
||||
void operator()(const empty_type_impl&) {
|
||||
// Can't happen
|
||||
abort();
|
||||
delete reinterpret_cast<empty_type_representation*>(object);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -3401,6 +3399,9 @@ data_value::data_value(big_decimal v) : data_value(make_new(decimal_type, v)) {
|
||||
data_value::data_value(cql_duration d) : data_value(make_new(duration_type, d)) {
|
||||
}
|
||||
|
||||
data_value::data_value(empty_type_representation e) : data_value(make_new(empty_type, e)) {
|
||||
}
|
||||
|
||||
sstring data_value::to_parsable_string() const {
|
||||
// For some reason trying to do it using fmt::format refuses to compile
|
||||
// auto to_parsable_str_transform = boost::adaptors::transformed([](const data_value& dv) -> sstring {
|
||||
|
||||
4
types.hh
4
types.hh
@@ -332,6 +332,9 @@ const T& value_cast(const data_value& value);
|
||||
template <typename T>
|
||||
T&& value_cast(data_value&& value);
|
||||
|
||||
struct empty_type_representation {
|
||||
};
|
||||
|
||||
class data_value {
|
||||
void* _value; // FIXME: use "small value optimization" for small types
|
||||
data_type _type;
|
||||
@@ -386,6 +389,7 @@ public:
|
||||
data_value(utils::multiprecision_int);
|
||||
data_value(big_decimal);
|
||||
data_value(cql_duration);
|
||||
data_value(empty_type_representation);
|
||||
explicit data_value(std::optional<bytes>);
|
||||
template <typename NativeType>
|
||||
data_value(std::optional<NativeType>);
|
||||
|
||||
Reference in New Issue
Block a user