diff --git a/include/mysql/impl/null_bitmap_traits.hpp b/include/mysql/impl/null_bitmap_traits.hpp new file mode 100644 index 00000000..b1d23f25 --- /dev/null +++ b/include/mysql/impl/null_bitmap_traits.hpp @@ -0,0 +1,45 @@ +#ifndef MYSQL_ASIO_IMPL_NULL_BITMAP_TRAITS_HPP +#define MYSQL_ASIO_IMPL_NULL_BITMAP_TRAITS_HPP + +#include +#include + +namespace mysql +{ +namespace detail +{ + +class null_bitmap_traits +{ + std::size_t offset_; + std::size_t num_fields_; + + constexpr std::size_t byte_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) / 8; } + constexpr std::size_t bit_pos(std::size_t field_pos) const noexcept { return (field_pos + offset_) % 8; } +public: + constexpr null_bitmap_traits(std::size_t offset, std::size_t num_fields) noexcept: + offset_(offset), num_fields_ {num_fields} {}; + constexpr std::size_t offset() const noexcept { return offset_; } + constexpr std::size_t num_fields() const noexcept { return num_fields_; } + + constexpr std::size_t byte_count() const noexcept { return (num_fields_ + 7 + offset_) / 8; } + bool is_null(const std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept + { + assert(field_pos < num_fields_); + return null_bitmap_begin[byte_pos(field_pos)] & (1 << bit_pos(field_pos)); + } + void set_null(std::uint8_t* null_bitmap_begin, std::size_t field_pos) const noexcept + { + assert(field_pos < num_fields_); + null_bitmap_begin[byte_pos(field_pos)] |= (1 << bit_pos(field_pos)); + } +}; + +constexpr std::size_t stmt_execute_null_bitmap_offset = 0; +constexpr std::size_t binary_row_null_bitmap_offset = 2; + +} +} + + +#endif /* INCLUDE_NULL_BITMAP_HPP_ */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 84022253..79c665c7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable( unit/value.cpp unit/row.cpp unit/error.cpp + unit/null_bitmap.cpp ) # A codegen issue in MSVC C++17 makes gmock expectations not work if (NOT MSVC) diff --git a/test/unit/null_bitmap.cpp b/test/unit/null_bitmap.cpp new file mode 100644 index 00000000..668efe8d --- /dev/null +++ b/test/unit/null_bitmap.cpp @@ -0,0 +1,273 @@ + +#include +#include +#include "mysql/impl/null_bitmap_traits.hpp" + +using mysql::detail::null_bitmap_traits; +using mysql::detail::stmt_execute_null_bitmap_offset; +using mysql::detail::binary_row_null_bitmap_offset; + +namespace +{ + +// byte_count() +struct ByteCountParams +{ + std::size_t offset; + std::size_t num_fields; + std::size_t expected_value; +}; +std::ostream& operator<<(std::ostream& os, const ByteCountParams& v) +{ + return os << "(offset=" << v.offset << ", num_fields=" << v.num_fields << ")"; +} + +struct NullBitmapTraitsByteCountTest : testing::TestWithParam {}; + +TEST_P(NullBitmapTraitsByteCountTest, Trivial_ReturnsNumberOfBytes) +{ + null_bitmap_traits traits (GetParam().offset, GetParam().num_fields); + EXPECT_EQ(traits.byte_count(), GetParam().expected_value); +} + +INSTANTIATE_TEST_SUITE_P(StmtExecuteOffset, NullBitmapTraitsByteCountTest, testing::Values( + ByteCountParams{stmt_execute_null_bitmap_offset, 0, 0}, + ByteCountParams{stmt_execute_null_bitmap_offset, 1, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 2, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 3, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 4, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 5, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 6, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 7, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 8, 1}, + ByteCountParams{stmt_execute_null_bitmap_offset, 9, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 10, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 11, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 12, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 13, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 14, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 15, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 16, 2}, + ByteCountParams{stmt_execute_null_bitmap_offset, 17, 3} +)); + +INSTANTIATE_TEST_SUITE_P(BinaryRowOffset, NullBitmapTraitsByteCountTest, testing::Values( + ByteCountParams{binary_row_null_bitmap_offset, 0, 1}, + ByteCountParams{binary_row_null_bitmap_offset, 1, 1}, + ByteCountParams{binary_row_null_bitmap_offset, 2, 1}, + ByteCountParams{binary_row_null_bitmap_offset, 3, 1}, + ByteCountParams{binary_row_null_bitmap_offset, 4, 1}, + ByteCountParams{binary_row_null_bitmap_offset, 5, 1}, + ByteCountParams{binary_row_null_bitmap_offset, 6, 1}, + ByteCountParams{binary_row_null_bitmap_offset, 7, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 8, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 9, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 10, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 11, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 12, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 13, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 14, 2}, + ByteCountParams{binary_row_null_bitmap_offset, 15, 3}, + ByteCountParams{binary_row_null_bitmap_offset, 16, 3}, + ByteCountParams{binary_row_null_bitmap_offset, 17, 3} +)); + +// is_null +struct IsNullParams +{ + std::size_t offset; + std::size_t pos; + bool expected; +}; +std::ostream& operator<<(std::ostream& os, const IsNullParams& v) +{ + return os << "(offset=" << v.offset << ", pos=" << v.pos << ")"; +} +struct NullBitmapTraitsIsNullTest : testing::TestWithParam {}; + +TEST_P(NullBitmapTraitsIsNullTest, Trivial_ReturnsFlagValue) +{ + std::array content { 0xb4, 0xff, 0x00 }; // 0b10110100, 0b11111111, 0b00000000 + null_bitmap_traits traits (GetParam().offset, 17); // 17 fields + bool actual = traits.is_null(content.data(), GetParam().pos); + EXPECT_EQ(actual, GetParam().expected); +} + +INSTANTIATE_TEST_SUITE_P(StmtExecuteOffset, NullBitmapTraitsIsNullTest, testing::Values( + IsNullParams{stmt_execute_null_bitmap_offset, 0, false}, + IsNullParams{stmt_execute_null_bitmap_offset, 1, false}, + IsNullParams{stmt_execute_null_bitmap_offset, 2, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 3, false}, + IsNullParams{stmt_execute_null_bitmap_offset, 4, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 5, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 6, false}, + IsNullParams{stmt_execute_null_bitmap_offset, 7, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 8, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 9, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 10, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 11, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 12, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 13, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 14, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 15, true}, + IsNullParams{stmt_execute_null_bitmap_offset, 16, false} +)); + +INSTANTIATE_TEST_SUITE_P(BinaryRowOffset, NullBitmapTraitsIsNullTest, testing::Values( + IsNullParams{binary_row_null_bitmap_offset, 0, true}, + IsNullParams{binary_row_null_bitmap_offset, 1, false}, + IsNullParams{binary_row_null_bitmap_offset, 2, true}, + IsNullParams{binary_row_null_bitmap_offset, 3, true}, + IsNullParams{binary_row_null_bitmap_offset, 4, false}, + IsNullParams{binary_row_null_bitmap_offset, 5, true}, + IsNullParams{binary_row_null_bitmap_offset, 6, true}, + IsNullParams{binary_row_null_bitmap_offset, 7, true}, + IsNullParams{binary_row_null_bitmap_offset, 8, true}, + IsNullParams{binary_row_null_bitmap_offset, 9, true}, + IsNullParams{binary_row_null_bitmap_offset, 10, true}, + IsNullParams{binary_row_null_bitmap_offset, 11, true}, + IsNullParams{binary_row_null_bitmap_offset, 12, true}, + IsNullParams{binary_row_null_bitmap_offset, 13, true}, + IsNullParams{binary_row_null_bitmap_offset, 14, false}, + IsNullParams{binary_row_null_bitmap_offset, 15, false}, + IsNullParams{binary_row_null_bitmap_offset, 16, false} +)); + +TEST(NullBitmapTraits, IsNull_OneFieldStmtExecuteFirstBitZero_ReturnsFalse) +{ + std::uint8_t value = 0b00000000; + null_bitmap_traits traits (stmt_execute_null_bitmap_offset, 1); + EXPECT_FALSE(traits.is_null(&value, 0)); +} + +TEST(NullBitmapTraits, IsNull_OneFieldStmtExecuteFirstBitOne_ReturnsTrue) +{ + std::uint8_t value = 0b00000001; + null_bitmap_traits traits (stmt_execute_null_bitmap_offset, 1); + EXPECT_TRUE(traits.is_null(&value, 0)); +} + +TEST(NullBitmapTraits, IsNull_OneFieldBinaryRowThirdBitZero_ReturnsFalse) +{ + std::uint8_t value = 0b00000000; + null_bitmap_traits traits (binary_row_null_bitmap_offset, 1); + EXPECT_FALSE(traits.is_null(&value, 0)); +} + +TEST(NullBitmapTraits, IsNull_OneFieldBinaryRowThirdBitOne_ReturnsTrue) +{ + std::uint8_t value = 0b00000100; + null_bitmap_traits traits (binary_row_null_bitmap_offset, 1); + EXPECT_TRUE(traits.is_null(&value, 0)); +} + +// set_null +struct SetNullParams +{ + std::size_t offset; + std::size_t pos; + std::array expected; + + SetNullParams(std::size_t offset, std::size_t pos, const std::array& expected): + offset(offset), pos(pos), expected(expected) {}; +}; +std::ostream& operator<<(std::ostream& os, const SetNullParams& v) +{ + return os << "(offset=" << v.offset << ", pos=" << v.pos << ")"; +} +struct NullBitmapTraitsSetNullTest : testing::TestWithParam {}; + +TEST_P(NullBitmapTraitsSetNullTest, Trivial_SetsAdequateBit) +{ + std::array expected_buffer {}; // help detect buffer overruns + memcpy(expected_buffer.data(), GetParam().expected.data(), 3); + std::array actual_buffer {}; + null_bitmap_traits traits (GetParam().offset, 17); // 17 fields + traits.set_null(actual_buffer.data(), GetParam().pos); + EXPECT_EQ(expected_buffer, actual_buffer); +} + +INSTANTIATE_TEST_SUITE_P(StmtExecuteOffset, NullBitmapTraitsSetNullTest, testing::Values( + SetNullParams(stmt_execute_null_bitmap_offset, 0, {0b00000001, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 1, {0b00000010, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 2, {0b00000100, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 3, {0b00001000, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 4, {0b00010000, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 5, {0b00100000, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 6, {0b01000000, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 7, {0b10000000, 0, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 8, {0, 0b00000001, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 9, {0, 0b00000010, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 10, {0, 0b00000100, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 11, {0, 0b00001000, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 12, {0, 0b00010000, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 13, {0, 0b00100000, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 14, {0, 0b01000000, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 15, {0, 0b10000000, 0}), + SetNullParams(stmt_execute_null_bitmap_offset, 16, {0, 0, 0b00000001}) +)); + +INSTANTIATE_TEST_SUITE_P(BinaryRowOffset, NullBitmapTraitsSetNullTest, testing::Values( + SetNullParams(binary_row_null_bitmap_offset, 0, {0b00000100, 0, 0}), + SetNullParams(binary_row_null_bitmap_offset, 1, {0b00001000, 0, 0}), + SetNullParams(binary_row_null_bitmap_offset, 2, {0b00010000, 0, 0}), + SetNullParams(binary_row_null_bitmap_offset, 3, {0b00100000, 0, 0}), + SetNullParams(binary_row_null_bitmap_offset, 4, {0b01000000, 0, 0}), + SetNullParams(binary_row_null_bitmap_offset, 5, {0b10000000, 0, 0}), + SetNullParams(binary_row_null_bitmap_offset, 6, {0, 0b00000001, 0}), + SetNullParams(binary_row_null_bitmap_offset, 7, {0, 0b00000010, 0}), + SetNullParams(binary_row_null_bitmap_offset, 8, {0, 0b00000100, 0}), + SetNullParams(binary_row_null_bitmap_offset, 9, {0, 0b00001000, 0}), + SetNullParams(binary_row_null_bitmap_offset, 10, {0, 0b00010000, 0}), + SetNullParams(binary_row_null_bitmap_offset, 11, {0, 0b00100000, 0}), + SetNullParams(binary_row_null_bitmap_offset, 12, {0, 0b01000000, 0}), + SetNullParams(binary_row_null_bitmap_offset, 13, {0, 0b10000000, 0}), + SetNullParams(binary_row_null_bitmap_offset, 14, {0, 0, 0b00000001}), + SetNullParams(binary_row_null_bitmap_offset, 15, {0, 0, 0b00000010}), + SetNullParams(binary_row_null_bitmap_offset, 16, {0, 0, 0b00000100}) +)); + +TEST(NullBitmapTraits, SetNull_OneFieldStmtExecute_SetsFirstBitToZero) +{ + std::uint8_t value = 0; + null_bitmap_traits traits (stmt_execute_null_bitmap_offset, 1); + traits.set_null(&value, 0); + EXPECT_EQ(value, 1); +} + +TEST(NullBitmapTraits, SetNull_OneFieldBinaryRow_SetsThirdBitToZero) +{ + std::uint8_t value = 0; + null_bitmap_traits traits (binary_row_null_bitmap_offset, 1); + traits.set_null(&value, 0); + EXPECT_EQ(value, 4); +} + +TEST(NullBitmapTraits, SetNull_MultifiedStmtExecute_SetsAppropriateBits) +{ + std::array expected_buffer { 0xb4, 0xff, 0x00, 0x00 }; + std::array actual_buffer {}; + null_bitmap_traits traits (stmt_execute_null_bitmap_offset, 17); // 17 fields + for (std::size_t pos: {2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + { + traits.set_null(actual_buffer.data(), pos); + } + EXPECT_EQ(expected_buffer, actual_buffer); +} + +TEST(NullBitmapTraits, SetNull_MultifiedBinaryRow_SetsAppropriateBits) +{ + std::array expected_buffer { 0xb4, 0xff, 0x00, 0x00 }; + std::array actual_buffer {}; + null_bitmap_traits traits (binary_row_null_bitmap_offset, 17); // 17 fields + for (std::size_t pos: {0, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13}) + { + traits.set_null(actual_buffer.data(), pos); + } + EXPECT_EQ(expected_buffer, actual_buffer); +} + + + +} +