#pragma once #include "utils_meta.h" #include // for ipv4-ipv6 platform-specific stuff #include #include #include #include #include namespace clickhouse { class Block; class Column; std::ostream& operator<<(std::ostream& ostr, const ItemView& item_view); } inline bool operator==(const in6_addr& left, const in6_addr& right) { return memcmp(&left, &right, sizeof(left)) == 0; } inline bool operator==(const in_addr& left, const in_addr& right) { return memcmp(&left, &right, sizeof(left)) == 0; } inline bool operator==(const in_addr & left, const uint32_t& right) { return memcmp(&left, &right, sizeof(left)) == 0; } inline bool operator==(const uint32_t& left, const in_addr& right) { return memcmp(&left, &right, sizeof(left)) == 0; } inline bool operator==(const in6_addr & left, const std::string_view & right) { return right.size() == sizeof(left) && memcmp(&left, right.data(), sizeof(left)) == 0; } inline bool operator==(const std::string_view & left, const in6_addr & right) { return left.size() == sizeof(right) && memcmp(left.data(), &right, sizeof(right)) == 0; } inline bool operator!=(const in6_addr& left, const in6_addr& right) { return !(left == right); } inline bool operator!=(const in_addr& left, const in_addr& right) { return !(left == right); } inline bool operator!=(const in_addr & left, const uint32_t& right) { return !(left == right); } inline bool operator!=(const uint32_t& left, const in_addr& right) { return !(left == right); } inline bool operator!=(const in6_addr & left, const std::string_view & right) { return !(left == right); } inline bool operator!=(const std::string_view & left, const in6_addr & right) { return !(left == right); } namespace details { // Make a column a RO stl-like container template struct ColumnAsContainerWrapper { const NestedColumnType& nested_col; struct Iterator { const NestedColumnType& nested_col; size_t i = 0; auto& operator++() { ++i; return *this; } auto operator*() const { return nested_col.At(i); } bool operator==(const Iterator & other) const { return &other.nested_col == &this->nested_col && other.i == this->i; } bool operator!=(const Iterator & other) const { return !(other == *this); } }; size_t size() const { return nested_col.Size(); } auto begin() const { return Iterator{nested_col, 0}; } auto end() const { return Iterator{nested_col, nested_col.Size()}; } }; // Helper to allow comparing values of two instances of clickhouse::Column, when concrete type is unknown. // Comparison is done by comparing result of Column::GetItem(). template <> struct ColumnAsContainerWrapper { const clickhouse::Column& nested_col; struct Iterator { const clickhouse::Column& nested_col; size_t i = 0; auto& operator++() { ++i; return *this; } struct ItemWrapper { const clickhouse::ItemView item_view; bool operator==(const ItemWrapper & other) const { // type-erased comparison, byte-by-byte return item_view.type == other.item_view.type && item_view.data == other.item_view.data; } template bool operator==(const U & other) const { return item_view.get() == other; } template bool operator!=(const U & other) const { return !(*this == other); } friend std::ostream& operator<<(std::ostream& ostr, const ItemWrapper& val) { return ostr << val.item_view; } }; auto operator*() const { return ItemWrapper{nested_col.GetItem(i)}; } bool operator==(const Iterator & other) const { return &other.nested_col == &this->nested_col && other.i == this->i; } bool operator!=(const Iterator & other) const { return !(other == *this); } }; size_t size() const { return nested_col.Size(); } auto begin() const { return Iterator{nested_col, 0}; } auto end() const { return Iterator{nested_col, nested_col.Size()}; } }; } template auto maybeWrapColumnAsContainer(const T & t) { if constexpr (std::is_base_of_v) { return ::details::ColumnAsContainerWrapper{t}; } else { return t; } } // Compare values to each other, if values are container-ish, then recursively deep compare those containers. template ::testing::AssertionResult CompareRecursive(const Left & left, const Right & right); // Compare containers element-wise, if elements are containers themselves - compare recursevely template ::testing::AssertionResult CompareCotainersRecursive(const LeftContainer& left, const RightContainer& right) { if (left.size() != right.size()) return ::testing::AssertionFailure() << "\nMismatching containers size, expected: " << left.size() << " actual: " << right.size(); auto l_i = std::begin(left); auto r_i = std::begin(right); for (size_t i = 0; i < left.size(); ++i, ++l_i, ++r_i) { auto result = CompareRecursive(*l_i, *r_i); if (!result) return result << "\n\nMismatch at pos: " << i + 1; } return ::testing::AssertionSuccess(); } template struct PrintContainer; template ::testing::AssertionResult CompareRecursive(const Left & left, const Right & right) { using L = std::decay_t; using R = std::decay_t; if constexpr (!is_string_v && !is_string_v && (is_container_v || std::is_base_of_v) && (is_container_v || std::is_base_of_v) ) { const auto & l = maybeWrapColumnAsContainer(left); const auto & r = maybeWrapColumnAsContainer(right); if (auto result = CompareCotainersRecursive(l, r)) return result; else return result << "\nExpected container: " << PrintContainer{l} << "\nActual container : " << PrintContainer{r}; } else if constexpr (std::is_same_v || std::is_same_v) { return CompareRecursive(std::string_view(left), right); } else if constexpr (std::is_same_v || std::is_same_v) { return CompareRecursive(left, std::string_view(right)); } else { if (left != right) { // Handle std::optional(nan) // I'm too lazy to code comparison against std::nullopt, but this shouldn't be a problem in real life. // RN comparing against std::nullopt, you'll get a compilation error. if constexpr (is_instantiation_of::value && is_instantiation_of::value) { if (left.has_value() && right.has_value()) return CompareRecursive(*left, *right); } else if constexpr (is_instantiation_of::value) { if (left) return CompareRecursive(*left, right); } else if constexpr (is_instantiation_of::value) { if (right) return CompareRecursive(left, *right); } else if constexpr (std::is_floating_point_v && std::is_floating_point_v) { if (std::isnan(left) && std::isnan(right)) return ::testing::AssertionSuccess(); } return ::testing::AssertionFailure() << "\nExpected value: " << left << "\nActual value : " << right; } return ::testing::AssertionSuccess(); } }