diff --git a/include/mysql/impl/binary_deserialization.hpp b/include/mysql/impl/binary_deserialization.hpp new file mode 100644 index 00000000..333e380a --- /dev/null +++ b/include/mysql/impl/binary_deserialization.hpp @@ -0,0 +1,32 @@ +#ifndef INCLUDE_MYSQL_IMPL_BINARY_DESERIALIZATION_HPP_ +#define INCLUDE_MYSQL_IMPL_BINARY_DESERIALIZATION_HPP_ + +#include "mysql/impl/serialization.hpp" +#include "mysql/error.hpp" +#include "mysql/value.hpp" +#include "mysql/metadata.hpp" +#include + +namespace mysql +{ +namespace detail +{ + +inline Error deserialize_binary_value( + DeserializationContext& ctx, + const field_metadata& meta, + value& output +); + +inline error_code deserialize_binary_row( + DeserializationContext& ctx, + const std::vector& meta, + std::vector& output +); + +} +} + +#include "mysql/impl/binary_deserialization.ipp" + +#endif /* INCLUDE_MYSQL_IMPL_BINARY_DESERIALIZATION_HPP_ */ diff --git a/include/mysql/impl/binary_deserialization.ipp b/include/mysql/impl/binary_deserialization.ipp new file mode 100644 index 00000000..f4c4c078 --- /dev/null +++ b/include/mysql/impl/binary_deserialization.ipp @@ -0,0 +1,164 @@ +#ifndef INCLUDE_MYSQL_IMPL_BINARY_DESERIALIZATION_IPP_ +#define INCLUDE_MYSQL_IMPL_BINARY_DESERIALIZATION_IPP_ + +#include +#include "mysql/impl/null_bitmap_traits.hpp" +#include "mysql/impl/tmp.hpp" + +namespace mysql +{ +namespace detail +{ + +using binary_protocol_value = std::variant< + int1, + int2, + int4, + int8, + int1_signed, + int2_signed, + int4_signed, + int8_signed, + string_lenenc, + value_holder, + value_holder, + date, + datetime, + time +>; + +template +binary_protocol_value get_int_deserializable_type( + const field_metadata& meta +) +{ + return meta.is_unsigned() ? binary_protocol_value(UnsignedType()) : + binary_protocol_value(SignedType()); +} + +inline binary_protocol_value get_deserializable_type( + const field_metadata& meta +) +{ + switch (meta.protocol_type()) + { + case protocol_field_type::tiny: + return get_int_deserializable_type(meta); + case protocol_field_type::short_: + case protocol_field_type::year: + return get_int_deserializable_type(meta); + case protocol_field_type::int24: + case protocol_field_type::long_: + return get_int_deserializable_type(meta); + case protocol_field_type::longlong: + return get_int_deserializable_type(meta); + case protocol_field_type::float_: + return value_holder(); + case protocol_field_type::double_: + return value_holder(); + case protocol_field_type::timestamp: + case protocol_field_type::datetime: + return datetime(); + case protocol_field_type::date: + return date(); + case protocol_field_type::time: + return time(); + // True string types + case protocol_field_type::varchar: + case protocol_field_type::var_string: + case protocol_field_type::string: + case protocol_field_type::tiny_blob: + case protocol_field_type::medium_blob: + case protocol_field_type::long_blob: + case protocol_field_type::blob: + case protocol_field_type::enum_: + case protocol_field_type::set: + // Anything else that we do not know how to interpret, we return as a binary string + case protocol_field_type::decimal: + case protocol_field_type::bit: + case protocol_field_type::newdecimal: + case protocol_field_type::geometry: + default: + return string_lenenc(); + } +} + +inline Error deserialize_binary_row_impl( + DeserializationContext& ctx, + const std::vector& meta, + std::vector& output +) +{ + // Packet header is already read + // Number of fields + auto num_fields = meta.size(); + output.resize(num_fields); + + // Null bitmap + null_bitmap_traits null_bitmap (binary_row_null_bitmap_offset, num_fields); + const std::uint8_t* null_bitmap_begin = ctx.first(); + ctx.advance(null_bitmap.byte_count()); + + // Actual values + for (std::vector::size_type i = 0; i < output.size(); ++i) + { + if (null_bitmap.is_null(null_bitmap_begin, i)) + { + output[i] = nullptr; + } + else + { + auto err = deserialize(output[i], ctx); + if (err != Error::ok) return err; + } + } + + // Check for remaining bytes + return ctx.empty() ? Error::extra_bytes : Error::ok; +} + +} +} + +inline mysql::Error mysql::detail::deserialize_binary_value( + DeserializationContext& ctx, + const field_metadata& meta, + value& output +) +{ + auto protocol_value = get_deserializable_type(meta); + return std::visit([&output, &ctx](auto typed_protocol_value) { + using type = decltype(typed_protocol_value); + auto err = deserialize(typed_protocol_value, ctx); + if (err == Error::ok) + { + if constexpr (std::is_constructible_v) // not a value holder + { + output = typed_protocol_value; + } + else if constexpr (is_one_of_v) + { + // regular promotion would make this int32_t. Force it be uint32_t + output = std::uint32_t(typed_protocol_value.value); + } + else + { + output = typed_protocol_value.value; + } + } + return err; + }, protocol_value); +} + +inline mysql::error_code mysql::detail::deserialize_binary_row( + DeserializationContext& ctx, + const std::vector& meta, + std::vector& output +) +{ + return make_error_code(deserialize_binary_row_impl(ctx, meta, output)); +} + + + +#endif /* INCLUDE_MYSQL_IMPL_BINARY_DESERIALIZATION_IPP_ */ diff --git a/include/mysql/impl/tmp.hpp b/include/mysql/impl/tmp.hpp new file mode 100644 index 00000000..31e5e5fd --- /dev/null +++ b/include/mysql/impl/tmp.hpp @@ -0,0 +1,32 @@ +#ifndef INCLUDE_MYSQL_IMPL_TMP_HPP_ +#define INCLUDE_MYSQL_IMPL_TMP_HPP_ + +#include + +namespace mysql +{ +namespace detail +{ + +template +struct is_one_of +{ + static constexpr bool value = std::is_same_v || is_one_of::value; +}; + +template +struct is_one_of +{ + static constexpr bool value = std::is_same_v; +}; + + +template +constexpr bool is_one_of_v = is_one_of::value; + +} +} + + + +#endif /* INCLUDE_MYSQL_IMPL_TMP_HPP_ */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1e751ed9..ac6faab2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable( unit/auth.cpp unit/metadata.cpp unit/text_deserialization.cpp + unit/binary_deserialization.cpp unit/value.cpp unit/row.cpp unit/error.cpp diff --git a/test/unit/binary_deserialization.cpp b/test/unit/binary_deserialization.cpp new file mode 100644 index 00000000..1f71fd7c --- /dev/null +++ b/test/unit/binary_deserialization.cpp @@ -0,0 +1,250 @@ +/* + * deserialize_row.cpp + * + * Created on: Nov 8, 2019 + * Author: ruben + */ + +#include +#include "mysql/impl/binary_deserialization.hpp" +#include "test_common.hpp" + +using namespace mysql::detail; +using namespace mysql::test; +using namespace testing; +using namespace date::literals; +using mysql::value; +using mysql::collation; +using mysql::error_code; +using mysql::Error; + +namespace +{ + +using mysql::operator<<; + +// for deserializa_binary_value +struct BinaryValueParam +{ + std::string name; + std::vector from; + value expected; + protocol_field_type type; + std::uint16_t flags; + + template + BinaryValueParam( + std::string name, + std::vector from, + T&& expected_value, + protocol_field_type type, + std::uint16_t flags=0 + ): + name(std::move(name)), + from(std::move(from)), + expected(std::forward(expected_value)), + type(type), + flags(flags) + { + } +}; + +std::ostream& operator<<(std::ostream& os, const BinaryValueParam& value) { return os << value.name; } + +struct DeserializeBinaryValueTest : public TestWithParam +{ +}; + +TEST_P(DeserializeBinaryValueTest, CorrectFormat_SetsOutputValueReturnsTrue) +{ + column_definition_packet coldef; + coldef.type = GetParam().type; + coldef.flags.value = GetParam().flags; + mysql::field_metadata meta (coldef); + value actual_value; + const auto& buffer = GetParam().from; + DeserializationContext ctx (buffer.data(), buffer.data() + buffer.size(), capabilities()); + auto err = deserialize_binary_value(ctx, meta, actual_value); + EXPECT_EQ(err, Error::ok); + EXPECT_EQ(actual_value, GetParam().expected); +} + +INSTANTIATE_TEST_SUITE_P(StringTypes, DeserializeBinaryValueTest, Values( + BinaryValueParam("varchar", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::var_string), + BinaryValueParam("char", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::string), + BinaryValueParam("varbinary", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", + protocol_field_type::var_string, column_flags::binary), + BinaryValueParam("binary", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", + protocol_field_type::string, column_flags::binary), + BinaryValueParam("text_blob", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", + protocol_field_type::blob, column_flags::blob), + BinaryValueParam("enum", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", + protocol_field_type::string, column_flags::enum_), + BinaryValueParam("set", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", + protocol_field_type::string, column_flags::set), + + BinaryValueParam("bit", {0x02, 0x02, 0x01}, "\2\1", protocol_field_type::bit), + BinaryValueParam("decimal", {0x02, 0x31, 0x30}, "10", protocol_field_type::newdecimal), + BinaryValueParam("geomtry", {0x04, 0x74, 0x65, 0x73, 0x74}, "test", protocol_field_type::geometry) +)); + +INSTANTIATE_TEST_SUITE_P(IntTypes, DeserializeBinaryValueTest, Values( + BinaryValueParam("tinyint_unsigned", {0x14}, std::uint32_t(20), + protocol_field_type::tiny, column_flags::unsigned_), + BinaryValueParam("tinyint_signed", {0xec}, std::int32_t(-20), protocol_field_type::tiny), + + BinaryValueParam("smallint_unsigned", {0x14, 0x00}, std::uint32_t(20), + protocol_field_type::short_, column_flags::unsigned_), + BinaryValueParam("smallint_signed", {0xec, 0xff}, std::int32_t(-20), protocol_field_type::short_), + + BinaryValueParam("mediumint_unsigned", {0x14, 0x00, 0x00, 0x00}, std::uint32_t(20), + protocol_field_type::int24, column_flags::unsigned_), + BinaryValueParam("mediumint_signed", {0xec, 0xff, 0xff, 0xff}, std::int32_t(-20), protocol_field_type::int24), + + BinaryValueParam("int_unsigned", {0x14, 0x00, 0x00, 0x00}, std::uint32_t(20), + protocol_field_type::long_, column_flags::unsigned_), + BinaryValueParam("int_signed", {0xec, 0xff, 0xff, 0xff}, std::int32_t(-20), protocol_field_type::long_), + + BinaryValueParam("bigint_unsigned", {0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, std::uint64_t(20), + protocol_field_type::longlong, column_flags::unsigned_), + BinaryValueParam("bigint_signed", {0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, std::int64_t(-20), + protocol_field_type::longlong) +)); + +INSTANTIATE_TEST_SUITE_P(FloatingPointTypes, DeserializeBinaryValueTest, Values( + BinaryValueParam("float", {0x66, 0x66, 0x86, 0xc0}, -4.2f, protocol_field_type::float_), + BinaryValueParam("double", {0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0xc0}, -4.2, protocol_field_type::double_) +)); + +INSTANTIATE_TEST_SUITE_P(TimeTypes, DeserializeBinaryValueTest, Values( + BinaryValueParam("date", {0x04, 0xda, 0x07, 0x03, 0x1c}, makedate(2010, 3, 28), protocol_field_type::date), + BinaryValueParam("datetime", {0x0b, 0xda, 0x07, 0x05, 0x02, 0x17, 0x01, 0x32, 0xa0, 0x86, 0x01, 0x00}, + makedt(2010, 5, 2, 23, 1, 50, 100000), protocol_field_type::datetime), + BinaryValueParam("timestamp", {0x0b, 0xda, 0x07, 0x05, 0x02, 0x17, 0x01, 0x32, 0xa0, 0x86, 0x01, 0x00}, + makedt(2010, 5, 2, 23, 1, 50, 100000), protocol_field_type::timestamp), + BinaryValueParam("time", { 0x0c, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0xa0, 0x86, 0x01, 0x00}, + maket(120, 2, 3, 100000), protocol_field_type::time), + BinaryValueParam("year", {0xe3, 0x07}, std::uint32_t(2019), protocol_field_type::year, column_flags::unsigned_) +)); + +/*struct DeserializeTextRowTest : public Test +{ + std::vector meta { + column_definition_packet { + string_lenenc("def"), + string_lenenc("awesome"), + string_lenenc("test_table"), + string_lenenc("test_table"), + string_lenenc("f0"), + string_lenenc("f0"), + collation::utf8_general_ci, + int4(300), + protocol_field_type::var_string, + int2(0), + int1(0) + }, + column_definition_packet { + string_lenenc("def"), + string_lenenc("awesome"), + string_lenenc("test_table"), + string_lenenc("test_table"), + string_lenenc("f1"), + string_lenenc("f1"), + collation::binary, + int4(11), + protocol_field_type::long_, + int2(0), + int1(0) + }, + column_definition_packet { + string_lenenc("def"), + string_lenenc("awesome"), + string_lenenc("test_table"), + string_lenenc("test_table"), + string_lenenc("f2"), + string_lenenc("f2"), + collation::binary, + int4(22), + protocol_field_type::datetime, + int2(column_flags::binary), + int1(2) + } + }; + std::vector values; + + error_code deserialize(const std::vector& buffer) + { + DeserializationContext ctx (buffer.data(), buffer.data() + buffer.size(), capabilities()); + return deserialize_text_row(ctx, meta, values); + } +}; + +TEST_F(DeserializeTextRowTest, SameNumberOfValuesAsFieldsNonNulls_DeserializesReturnsOk) +{ + std::vector expected_values {value("val"), value(std::int32_t(21)), value(makedt(2010, 10, 1))}; + std::vector buffer { + 0x03, 0x76, 0x61, 0x6c, 0x02, 0x32, 0x31, 0x16, + 0x32, 0x30, 0x31, 0x30, 0x2d, 0x31, 0x30, 0x2d, + 0x30, 0x31, 0x20, 0x30, 0x30, 0x3a, 0x30, 0x30, + 0x3a, 0x30, 0x30, 0x2e, 0x30, 0x30 + }; + auto err = deserialize(buffer); + EXPECT_EQ(err, error_code()); + EXPECT_EQ(values, expected_values); +} + +TEST_F(DeserializeTextRowTest, SameNumberOfValuesAsFieldsOneNull_DeserializesReturnsOk) +{ + std::vector expected_values {value("val"), value(nullptr), value(makedt(2010, 10, 1))}; + std::vector buffer { + 0x03, 0x76, 0x61, 0x6c, 0xfb, 0x16, 0x32, 0x30, + 0x31, 0x30, 0x2d, 0x31, 0x30, 0x2d, 0x30, 0x31, + 0x20, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, + 0x30, 0x2e, 0x30, 0x30 + }; + auto err = deserialize(buffer); + EXPECT_EQ(err, error_code()); + EXPECT_EQ(values, expected_values); +} + +TEST_F(DeserializeTextRowTest, SameNumberOfValuesAsFieldsAllNull_DeserializesReturnsOk) +{ + std::vector expected_values {value(nullptr), value(nullptr), value(nullptr)}; + auto err = deserialize({0xfb, 0xfb, 0xfb}); + EXPECT_EQ(err, error_code()); + EXPECT_EQ(values, expected_values); +} + +TEST_F(DeserializeTextRowTest, TooFewValues_ReturnsError) +{ + auto err = deserialize({0xfb, 0xfb}); + EXPECT_EQ(err, make_error_code(Error::incomplete_message)); +} + +TEST_F(DeserializeTextRowTest, TooManyValues_ReturnsError) +{ + auto err = deserialize({0xfb, 0xfb, 0xfb, 0xfb}); + EXPECT_EQ(err, make_error_code(Error::extra_bytes)); +} + +TEST_F(DeserializeTextRowTest, ErrorDeserializingContainerStringValue_ReturnsError) +{ + auto err = deserialize({0x03, 0xaa, 0xab, 0xfb, 0xfb}); + EXPECT_EQ(err, make_error_code(Error::incomplete_message)); +} + +TEST_F(DeserializeTextRowTest, ErrorDeserializingContainerValue_ReturnsError) +{ + std::vector buffer { + 0x03, 0x76, 0x61, 0x6c, 0xfb, 0x16, 0x32, 0x30, + 0x31, 0x30, 0x2d, 0x31, 0x30, 0x2d, 0x30, 0x31, + 0x20, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, + 0x30, 0x2f, 0x30, 0x30 + }; + auto err = deserialize(buffer); + EXPECT_EQ(err, make_error_code(Error::protocol_value_error)); +}*/ + +} + + diff --git a/test/unit/text_deserialization.cpp b/test/unit/text_deserialization.cpp index 09ad8bee..fcf7aacf 100644 --- a/test/unit/text_deserialization.cpp +++ b/test/unit/text_deserialization.cpp @@ -6,7 +6,6 @@ */ #include -#include #include "mysql/impl/text_deserialization.hpp" #include "test_common.hpp" @@ -82,7 +81,7 @@ INSTANTIATE_TEST_SUITE_P(StringTypes, DeserializeTextValueTest, Values( TextValueParam("SET", "value1,value2", "value1,value2", protocol_field_type::string, column_flags::set), TextValueParam("BIT", "\1", "\1", protocol_field_type::bit), - TextValueParam("DECIMAL", "\1", "\1", protocol_field_type::bit), + TextValueParam("DECIMAL", "\1", "\1", protocol_field_type::newdecimal), TextValueParam("GEOMTRY", "\1", "\1", protocol_field_type::geometry, column_flags::binary | column_flags::blob) ));