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:
Avi Kivity
2022-12-27 20:33:32 +02:00
parent 563998b69a
commit da4abccf89
4 changed files with 21 additions and 5 deletions

View File

@@ -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();
};

View File

@@ -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<>();
}

View File

@@ -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 {

View File

@@ -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>);