diff options
author | pavook <pavook@yandex-team.com> | 2024-07-30 21:40:33 +0300 |
---|---|---|
committer | pavook <pavook@yandex-team.com> | 2024-07-30 21:52:37 +0300 |
commit | f7ca71b582e2347ec55857b493d6bccf55bbc3df (patch) | |
tree | 3a136dbb465847d633e3764636b3d5f09d2e1f4c | |
parent | 79da6309e6e03ecbc7eb6f5083e2350c65496f68 (diff) | |
download | ydb-f7ca71b582e2347ec55857b493d6bccf55bbc3df.tar.gz |
Add a safe class for managing statistic paths
2b600be732d64131e213852536c78b387535213f
-rw-r--r-- | yt/yt/core/misc/public.h | 8 | ||||
-rw-r--r-- | yt/yt/core/misc/statistic_path-inl.h | 29 | ||||
-rw-r--r-- | yt/yt/core/misc/statistic_path.cpp | 323 | ||||
-rw-r--r-- | yt/yt/core/misc/statistic_path.h | 258 | ||||
-rw-r--r-- | yt/yt/core/misc/unittests/statistic_path_ut.cpp | 216 | ||||
-rw-r--r-- | yt/yt/core/misc/unittests/ya.make | 1 | ||||
-rw-r--r-- | yt/yt/core/ya.make | 1 | ||||
-rw-r--r-- | yt/yt/core/ytree/serialize.cpp | 12 | ||||
-rw-r--r-- | yt/yt/core/ytree/serialize.h | 5 |
9 files changed, 853 insertions, 0 deletions
diff --git a/yt/yt/core/misc/public.h b/yt/yt/core/misc/public.h index 7efd15b738..9cff9ba2cd 100644 --- a/yt/yt/core/misc/public.h +++ b/yt/yt/core/misc/public.h @@ -140,6 +140,14 @@ using TInternedObjectDataPtr = TIntrusivePtr<TInternedObjectData<T>>; template <class T> class TInternedObject; +namespace NStatisticPath { + +class TStatisticPathLiteral; +class TStatisticPath; +struct TStatisticPathSerializer; + +} // namespace NStatisticPath + class TStatistics; class TSummary; diff --git a/yt/yt/core/misc/statistic_path-inl.h b/yt/yt/core/misc/statistic_path-inl.h new file mode 100644 index 0000000000..d9821066a3 --- /dev/null +++ b/yt/yt/core/misc/statistic_path-inl.h @@ -0,0 +1,29 @@ +#ifndef STATISTIC_PATH_INL_H_ + #error "Direct inclusion of this file is not allowed, include format.h" + // For the sake of sane code completion. + #include "statistic_path.h" +#endif + +#include "serialize.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <typename C> +void NStatisticPath::TStatisticPathSerializer::Save(C& context, const NStatisticPath::TStatisticPath& path) +{ + NYT::Save(context, path.Path()); +} + +template <typename C> +void NStatisticPath::TStatisticPathSerializer::Load(C& context, NStatisticPath::TStatisticPath& path) +{ + NStatisticPath::TStatisticPathType tmp; + NYT::Load(context, tmp); + path = TStatisticPath(std::move(tmp)); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/yt/core/misc/statistic_path.cpp b/yt/yt/core/misc/statistic_path.cpp new file mode 100644 index 0000000000..19f478d9f9 --- /dev/null +++ b/yt/yt/core/misc/statistic_path.cpp @@ -0,0 +1,323 @@ +#include "statistic_path.h" + +#include "error.h" + +#include <yt/yt/core/yson/format.h> + +namespace NYT::NStatisticPath { + +//////////////////////////////////////////////////////////////////////////////// + +TError CheckStatisticPathLiteral(const TStatisticPathType& literal) +{ + if (literal.Empty()) { + return TError("Empty statistic path literal"); + } + constexpr static TChar invalidCharacters[2]{Delimiter, 0}; + constexpr static TBasicStringBuf<TChar> invalidCharacterBuf(invalidCharacters, 2); + if (auto invalidPos = literal.find_first_of(invalidCharacterBuf); + invalidPos != TStatisticPathType::npos) { + return TError("Invalid character found in a statistic path literal") + << TErrorAttribute("literal", literal) + << TErrorAttribute("invalid_character", static_cast<ui8>(literal[invalidPos])); + } + return {}; +} + +TErrorOr<TStatisticPathLiteral> ParseStatisticPathLiteral(const TStatisticPathType& literal) +{ + if (auto error = CheckStatisticPathLiteral(literal); !error.IsOK()) { + return error; + } + return TStatisticPathLiteral(literal, false); +} + +//////////////////////////////////////////////////////////////////////////////// + +TStatisticPathLiteral::TStatisticPathLiteral(const TStatisticPathType& literal, bool /*tag*/) noexcept + : Literal_(literal) +{ } + +TStatisticPathLiteral::TStatisticPathLiteral(const TStatisticPathType& literal) + : TStatisticPathLiteral(ParseStatisticPathLiteral(literal).ValueOrThrow()) +{ } + +const TStatisticPathType& TStatisticPathLiteral::Literal() const noexcept +{ + return Literal_; +} + +//////////////////////////////////////////////////////////////////////////////// + +TStatisticPath::TStatisticPath(const TStatisticPathType& path) + : Path_(path) +{ } + +TStatisticPath::TStatisticPath(TStatisticPathType&& path) noexcept + : Path_(std::move(path)) +{ } + +TStatisticPath::TStatisticPath(const TStatisticPathLiteral& literal) + : Path_(TStatisticPathType(Delimiter) + literal.Literal()) +{ } + +//////////////////////////////////////////////////////////////////////////////// + +TError CheckStatisticPath(const TStatisticPathType& path) +{ + constexpr static TChar twoDelimiters[2]{Delimiter, Delimiter}; + constexpr static TBasicStringBuf<TChar> adjacentDelimiters(twoDelimiters, 2); + + TError error; + if (path.empty()) { + return {}; + } + + if (path.front() != Delimiter) { + error = TError("Statistic path must start with a delimiter"); + } else if (path.back() == Delimiter) { + error = TError("Statistic path must not end with a delimiter"); + } else if (path.Contains(0)) { + error = TError("Statistic path must not contain a null character"); + } else if (path.Contains(adjacentDelimiters)) { + error = TError("Statistic path must not contain adjacent delimiters"); + } + + if (!error.IsOK()) { + return error + << TErrorAttribute("path", path); + } + return {}; +} + +TErrorOr<TStatisticPath> ParseStatisticPath(const TStatisticPathType& path) +{ + if (auto error = CheckStatisticPath(path); !error.IsOK()) { + return error; + } + return TStatisticPath(path); +} + +//////////////////////////////////////////////////////////////////////////////// + +const TStatisticPathType& TStatisticPath::Path() const noexcept +{ + return Path_; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool TStatisticPath::Empty() const noexcept +{ + return Path_.empty(); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TStatisticPath::Swap(TStatisticPath& other) noexcept +{ + Path_.swap(other.Path_); +} + +//////////////////////////////////////////////////////////////////////////////// + +TStatisticPath& TStatisticPath::Append(const TStatisticPathLiteral& literal) +{ + return Append(TStatisticPath(literal)); +} + +TStatisticPath& TStatisticPath::Append(const TStatisticPath& other) +{ + Path_.append(other.Path_); + return *this; +} + +//////////////////////////////////////////////////////////////////////////////// + +TStatisticPath::const_iterator::value_type TStatisticPath::Front() const noexcept +{ + YT_VERIFY(!Empty()); + return *begin(); +} + +TStatisticPath::const_iterator::value_type TStatisticPath::Back() const noexcept +{ + YT_VERIFY(!Empty()); + return *(--end()); +} + +//////////////////////////////////////////////////////////////////////////////// + +void TStatisticPath::PopBack() +{ + YT_VERIFY(!Empty()); + Path_.resize(Path_.size() - Back().Size() - 1); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool TStatisticPath::StartsWith(const TStatisticPath& prefix) const +{ + // TODO(pavook) std::ranges::starts_with(*this, prefix) when C++23 arrives. + auto [selfIt, prefixIt] = std::ranges::mismatch(*this, prefix); + return prefixIt == prefix.end(); +} + +bool TStatisticPath::EndsWith(const TStatisticPath& suffix) const +{ + // TODO(pavook) std::ranges::ends_with(*this, suffix) when C++23 arrives. + auto [selfIt, suffixIt] = std::mismatch(rbegin(), rend(), suffix.rbegin(), suffix.rend()); + return suffixIt == suffix.rend(); +} + +TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPathLiteral& rhs) +{ + return TStatisticPath(lhs).Append(rhs); +} + +TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPath& rhs) +{ + return TStatisticPath(lhs).Append(rhs); +} + +TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPathLiteral& rhs) +{ + return TStatisticPath(std::move(lhs)).Append(rhs); +} + +TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPath& rhs) +{ + return TStatisticPath(std::move(lhs)).Append(rhs); +} + +TStatisticPath operator/(const TStatisticPathLiteral& lhs, const TStatisticPathLiteral& rhs) +{ + return TStatisticPath(lhs).Append(rhs); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool operator==(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept +{ + return lhs.Path() == rhs.Path(); +} + +std::strong_ordering operator<=>(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept +{ + return lhs.Path().compare(rhs.Path()) <=> 0; +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace NStatisticPathLiterals { + +TStatisticPathLiteral operator""_L(const char* str, size_t len) +{ + return TStatisticPathLiteral(TStatisticPathType(str, len)); +} + +} // namespace NStatisticPathLiterals + +//////////////////////////////////////////////////////////////////////////////// + +TStatisticPath::TIterator::TIterator(const TStatisticPath& path, TStatisticPath::TIterator::TBeginIteratorTag) noexcept + : Path_(path.Path()) + , Entry_(Path_.begin(), Path_.begin()) +{ + Advance(); +} + +TStatisticPath::TIterator::TIterator(const TStatisticPath& path, TStatisticPath::TIterator::TEndIteratorTag) noexcept + : Path_(path.Path()) + , Entry_() +{ } + +//////////////////////////////////////////////////////////////////////////////// + +TStatisticPath::TIterator::reference TStatisticPath::TIterator::operator*() const noexcept +{ + return Entry_; +} + +//////////////////////////////////////////////////////////////////////////////// + +TStatisticPath::TIterator& TStatisticPath::TIterator::operator++() noexcept +{ + Advance(); + return *this; +} + +TStatisticPath::TIterator TStatisticPath::TIterator::operator++(int) noexcept +{ + TIterator result = *this; + Advance(); + return result; +} + +TStatisticPath::TIterator& TStatisticPath::TIterator::operator--() noexcept +{ + Retreat(); + return *this; +} + +TStatisticPath::TIterator TStatisticPath::TIterator::operator--(int) noexcept +{ + TIterator result = *this; + Retreat(); + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +void TStatisticPath::TIterator::Advance() noexcept +{ + if (Entry_.end() == Path_.end()) { + Entry_ = value_type(); + } else { + Entry_ = value_type(std::next(Entry_.end()), Path_.end()).NextTok(Delimiter); + } +} + +void TStatisticPath::TIterator::Retreat() noexcept +{ + if (!Entry_) { + Entry_ = value_type(Path_.begin(), Path_.end()).RNextTok(Delimiter); + } else { + Entry_ = value_type(Path_.begin(), std::prev(Entry_.begin())).RNextTok(Delimiter); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +bool operator==(const TStatisticPath::TIterator& lhs, const TStatisticPath::TIterator& rhs) noexcept +{ + // TODO(pavook) should we compare sizes? + return lhs.Entry_.data() == rhs.Entry_.data(); +} + +//////////////////////////////////////////////////////////////////////////////// + +// TODO(pavook) make it constant-time to comply with ranges::range semantic requirements? +TStatisticPath::const_iterator TStatisticPath::begin() const noexcept +{ + return {*this, TStatisticPath::TIterator::TBeginIteratorTag{}}; +} + +TStatisticPath::const_iterator TStatisticPath::end() const noexcept +{ + return {*this, TStatisticPath::TIterator::TEndIteratorTag{}}; +} + +TStatisticPath::const_reverse_iterator TStatisticPath::rbegin() const noexcept +{ + return std::make_reverse_iterator(end()); +} + +TStatisticPath::const_reverse_iterator TStatisticPath::rend() const noexcept +{ + return std::make_reverse_iterator(begin()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NStatisticPath diff --git a/yt/yt/core/misc/statistic_path.h b/yt/yt/core/misc/statistic_path.h new file mode 100644 index 0000000000..8908e13089 --- /dev/null +++ b/yt/yt/core/misc/statistic_path.h @@ -0,0 +1,258 @@ +#pragma once + +#include "public.h" + +#include <yt/yt/core/yson/public.h> +#include <yt/yt/core/ytree/public.h> + +#include <util/generic/strbuf.h> +#include <util/generic/string.h> + +namespace NYT { + +namespace NStatisticPath { + +//////////////////////////////////////////////////////////////////////////////// + +//! A type to store paths and literals as. +using TStatisticPathType = TString; + +using TChar = TStatisticPathType::TChar; + +//! Delimiter character to use instead of `/`. We want it to compare "less" than any other character. +//! See YT-22118 for motivation. +static constexpr TChar Delimiter = '\x01'; + +//////////////////////////////////////////////////////////////////////////////// + +//! Checks that `literal` is a valid statistic path literal, i.e. `literal` isn't empty, +//! doesn't contain `Delimiter` and doesn't contain `\0`. +TError CheckStatisticPathLiteral(const TStatisticPathType& literal); + +class TStatisticPathLiteral +{ +private: + TStatisticPathLiteral(const TStatisticPathType& literal, bool /*tag*/) noexcept; + + friend TErrorOr<TStatisticPathLiteral> ParseStatisticPathLiteral(const TStatisticPathType& literal); + +public: + //! Can throw an exception in case `literal` is invalid (see `CheckStatisticPathLiteral`). + explicit TStatisticPathLiteral(const TStatisticPathType& literal); + + friend class TStatisticPath; + + [[nodiscard]] bool operator==(const TStatisticPathLiteral& other) const noexcept = default; + + //! Returns the underlying string. + [[nodiscard]] const TStatisticPathType& Literal() const noexcept; + +private: + TStatisticPathType Literal_; +}; + +//! Checks that `literal` is a valid statistic path literal (see `CheckStatisticPathLiteral`) and +//! returns TStatisticPathLiteral if it is. Returns an error with explanation otherwise. +TErrorOr<TStatisticPathLiteral> ParseStatisticPathLiteral(const TStatisticPathType& literal); + +//////////////////////////////////////////////////////////////////////////////// + +//! Checks that `path` is a valid statistic path (it can be constructed as a sequence of literals). +//! Returns an error with explanation otherwise. +TError CheckStatisticPath(const TStatisticPathType& path); + +//! Checks that `path` is a valid statistic path (it can be constructed as a sequence of literals), +//! and returns a `TStatisticPath` built from it if it is. Returns an error with explanation otherwise. +TErrorOr<TStatisticPath> ParseStatisticPath(const TStatisticPathType& path); + +// TODO(pavook) constexpr when constexpr std::string arrives. +class TStatisticPath +{ +private: + class TIterator + { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = TBasicStringBuf<TChar>; + using difference_type = value_type::difference_type; + using pointer = void; + + // This is needed for iterator sanity as we store the value inside the iterator. + // This is allowed, as since C++20 ForwardIterator::reference doesn't have to be an actual reference. + using reference = const value_type; + + reference operator*() const noexcept; + + TIterator& operator++() noexcept; + + TIterator operator++(int) noexcept; + + TIterator& operator--() noexcept; + + TIterator operator--(int) noexcept; + + friend bool operator==(const TIterator& lhs, const TIterator& rhs) noexcept; + + TIterator() noexcept = default; + + TIterator(const TIterator& other) noexcept = default; + + TIterator& operator=(const TIterator& other) noexcept = default; + + private: + value_type Path_; + value_type Entry_; + + struct TBeginIteratorTag + { }; + + struct TEndIteratorTag + { }; + + TIterator(const TStatisticPath& path, TBeginIteratorTag) noexcept; + + TIterator(const TStatisticPath& path, TEndIteratorTag) noexcept; + + void Advance() noexcept; + + void Retreat() noexcept; + + friend class TStatisticPath; + }; + + friend bool operator==(const TIterator& lhs, const TIterator& rhs) noexcept; + +public: + using iterator = TIterator; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator<TIterator>; + using const_reverse_iterator = reverse_iterator; + + //! Constructs an empty path. The underlying string will be empty. + TStatisticPath() noexcept = default; + + //! Constructs a path from an existing literal. The literal will be prepended with a Delimiter. + TStatisticPath(const TStatisticPathLiteral& literal); + + TStatisticPath(const TStatisticPath& other) = default; + + TStatisticPath(TStatisticPath&& other) noexcept = default; + + TStatisticPath& operator=(const TStatisticPath& other) = default; + + TStatisticPath& operator=(TStatisticPath&& other) noexcept = default; + + ~TStatisticPath() noexcept = default; + + void Swap(TStatisticPath& other) noexcept; + + //! Returns `true` if the path is empty. + [[nodiscard]] bool Empty() const noexcept; + + //! Returns the underlying path string. + [[nodiscard]] const TStatisticPathType& Path() const noexcept; + + //! Appends a literal to the end of the path, adding a Delimiter between them. + TStatisticPath& Append(const TStatisticPathLiteral& literal); + + //! Appends another path to the end of the current path. Essentially concatenates the underlying strings. + TStatisticPath& Append(const TStatisticPath& other); + + [[nodiscard]] const_iterator begin() const noexcept; + + [[nodiscard]] const_iterator end() const noexcept; + + [[nodiscard]] const_reverse_iterator rbegin() const noexcept; + + [[nodiscard]] const_reverse_iterator rend() const noexcept; + + //! Returns the first literal of the path. Verifies that the path is not empty. + [[nodiscard]] const_iterator::value_type Front() const noexcept; + + //! Returns the last literal of the path. Verifies that the path is not empty. + [[nodiscard]] const_iterator::value_type Back() const noexcept; + + //! Removes the last literal from the path. Verifies that the path is not empty. + void PopBack(); + + //! Returns `true` if `path` is a prefix of the current path. + [[nodiscard]] bool StartsWith(const TStatisticPath& path) const; + + //! Returns `true` if `path` is a suffix of the current path. + [[nodiscard]] bool EndsWith(const TStatisticPath& path) const; + +private: + TStatisticPathType Path_; + + TStatisticPath& AppendStr(const TStatisticPathType& path); + + //! Private, build the path from literals instead. + explicit TStatisticPath(const TStatisticPathType& path); + + //! Private, build the path from literals instead. + explicit TStatisticPath(TStatisticPathType&& path) noexcept; + + friend struct TStatisticPathSerializer; + + friend TErrorOr<TStatisticPath> ParseStatisticPath(const TStatisticPathType& path); +}; + +// TODO(pavook) `= default` when it stops crashing the compiler. +[[nodiscard]] bool operator==(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept; + +[[nodiscard]] std::strong_ordering operator<=>(const TStatisticPath& lhs, const TStatisticPath& rhs) noexcept; + +//////////////////////////////////////////////////////////////////////////////// + +//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`. +[[nodiscard]] TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPathLiteral& rhs); + +//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`. +[[nodiscard]] TStatisticPath operator/(const TStatisticPath& lhs, const TStatisticPath& rhs); + +//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`. +[[nodiscard]] TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPathLiteral& rhs); + +//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`. +[[nodiscard]] TStatisticPath operator/(TStatisticPath&& lhs, const TStatisticPath& rhs); + +//! Appends `rhs` to `lhs`. See `TStatisticPath::Append`. +[[nodiscard]] TStatisticPath operator/(const TStatisticPathLiteral& lhs, const TStatisticPathLiteral& rhs); + +//////////////////////////////////////////////////////////////////////////////// + +namespace NStatisticPathLiterals { + +[[nodiscard]] TStatisticPathLiteral operator""_L(const char* str, size_t len); + +} // namespace NStatisticPathLiterals + +//////////////////////////////////////////////////////////////////////////////// + +struct TStatisticPathSerializer +{ + template <class C> + static void Save(C& context, const NStatisticPath::TStatisticPath& path); + + template <class C> + static void Load(C& context, NStatisticPath::TStatisticPath& path); +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NStatisticPath + +template <class C> +struct TSerializerTraits<NStatisticPath::TStatisticPath, C, void> +{ + using TSerializer = NStatisticPath::TStatisticPathSerializer; + using TComparer = TValueBoundComparer; +}; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define STATISTIC_PATH_INL_H_ +#include "statistic_path-inl.h" +#undef STATISTIC_PATH_INL_H_ diff --git a/yt/yt/core/misc/unittests/statistic_path_ut.cpp b/yt/yt/core/misc/unittests/statistic_path_ut.cpp new file mode 100644 index 0000000000..e92d73c452 --- /dev/null +++ b/yt/yt/core/misc/unittests/statistic_path_ut.cpp @@ -0,0 +1,216 @@ +#include <yt/yt/core/test_framework/framework.h> + +#include <yt/yt/core/misc/error.h> +#include <yt/yt/core/misc/statistic_path.h> + +namespace NYT { +namespace { + +using namespace NStatisticPath; +using namespace NStatisticPathLiterals; + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, Literal) +{ + EXPECT_EQ("ABC"_L.Literal(), "ABC"); + + EXPECT_TRUE(CheckStatisticPathLiteral("$a/b.c").IsOK()); + EXPECT_FALSE(CheckStatisticPathLiteral(TString(Delimiter)).IsOK()); + EXPECT_FALSE(CheckStatisticPathLiteral("\0").IsOK()); + EXPECT_FALSE(CheckStatisticPathLiteral("").IsOK()); + + EXPECT_THROW(TStatisticPathLiteral("\0"), TErrorException); + EXPECT_THROW(TStatisticPathLiteral(TString(Delimiter)), TErrorException); + EXPECT_THROW(TStatisticPathLiteral(""), TErrorException); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, Append) +{ + EXPECT_EQ(("A"_L / "BB"_L / "CCC"_L).Path(), + TString(Delimiter) + "A" + Delimiter + "BB" + Delimiter + "CCC"); + + EXPECT_EQ(("A"_L / "B"_L) / "C"_L, "A"_L / ("B"_L / "C"_L)); + + EXPECT_EQ("A"_L / TStatisticPath(), "A"_L); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, Iterator) +{ + EXPECT_TRUE(std::bidirectional_iterator<TStatisticPath::const_iterator>); + EXPECT_TRUE(std::bidirectional_iterator<TStatisticPath::const_reverse_iterator>); + EXPECT_TRUE(std::ranges::range<TStatisticPath>); + + TStatisticPath path = "A"_L / "BB"_L / "CCC"_L; + + std::vector<TString> expected{"A", "BB", "CCC"}; + + { + std::vector<TString> actual(path.begin(), path.end()); + EXPECT_EQ(actual, expected); + } + + { + std::reverse(expected.begin(), expected.end()); + std::vector<TString> actual(path.rbegin(), path.rend()); + EXPECT_EQ(actual, expected); + } + + { + // Post-increment, post-decrement validity test. + auto it = path.begin(); + EXPECT_EQ(*(it++), "A"); + EXPECT_EQ(*it, "BB"); + EXPECT_EQ(*(it--), "BB"); + EXPECT_EQ(*it, "A"); + } + + // Sanity check. + auto it = path.begin(); + const auto& value = *it; + ++it; + EXPECT_NE(value, *it); + + TStatisticPath emptyPath; + EXPECT_EQ(emptyPath.begin(), emptyPath.end()); + EXPECT_TRUE(emptyPath.Empty()); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, Compare) +{ + EXPECT_EQ("A"_L / "B"_L, "A"_L / "B"_L); + + EXPECT_LT("A"_L / "B"_L, "A"_L / "B"_L / "C"_L); + + EXPECT_LT("A"_L / "B"_L, "A"_L / "C"_L); + EXPECT_LT("A"_L / "B"_L, "B"_L); +} + +//////////////////////////////////////////////////////////////////////////////// + +// We want a statistic path and it's "path prefix" to be adjacent according to +// path comparison. See YT-22118 for motivation. +TEST(TStatisticPathTest, Adjacent) +{ + for (TChar c = std::numeric_limits<TChar>::min();; ++c) { + auto literal = ParseStatisticPathLiteral(TString("A") + c + "B"); + if (literal.IsOK()) { + EXPECT_LT("A"_L / "B"_L, TStatisticPath(literal.Value())); + } + if (c == std::numeric_limits<TChar>::max()) { + break; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, Parse) +{ + EXPECT_EQ(ParseStatisticPath(TString(Delimiter) + "ABC" + Delimiter + "DEF").ValueOrThrow(), "ABC"_L / "DEF"_L); + EXPECT_EQ(ParseStatisticPath("").ValueOrThrow(), TStatisticPath()); + + EXPECT_FALSE(ParseStatisticPath(TString(Delimiter) + "A\0B"_sb).IsOK()); + EXPECT_FALSE(ParseStatisticPath(TString(Delimiter) + "AB" + Delimiter).IsOK()); + EXPECT_FALSE(ParseStatisticPath(TString(Delimiter) + "A" + Delimiter + Delimiter + "B").IsOK()); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, Constructor) +{ + EXPECT_EQ(TStatisticPath("abc"_L).Path(), TString(Delimiter) + "abc"); + + TStatisticPath defaultConstructed; + EXPECT_EQ(defaultConstructed.Path(), TString()); + EXPECT_TRUE(defaultConstructed.Empty()); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, StartsWith) +{ + // Simple prefix. + EXPECT_TRUE(("A"_L / "BB"_L / "CCC"_L).StartsWith("A"_L / "BB"_L)); + // Equal paths. + EXPECT_TRUE(("A"_L / "BB"_L).StartsWith("A"_L / "BB"_L)); + // Empty prefix. + EXPECT_TRUE(("A"_L / "B"_L).StartsWith({})); + + // Reverse prefix. + EXPECT_FALSE(("A"_L / "BB"_L).StartsWith("A"_L / "BB"_L / "CCC"_L)); + // There are string prefixes that are not path prefixes. + EXPECT_FALSE(("A"_L / "BB"_L / "CCC"_L).StartsWith("A"_L / "BB"_L / "C"_L)); + // Sanity check. + EXPECT_FALSE(("A"_L / "B"_L).StartsWith("B"_L)); +} + +TEST(TStatisticPathTest, EndsWith) +{ + // Simple suffix. + EXPECT_TRUE(("A"_L / "BB"_L / "CCC"_L).EndsWith("BB"_L / "CCC"_L)); + // Equal paths. + EXPECT_TRUE(("A"_L / "BB"_L).EndsWith("A"_L / "BB"_L)); + // Empty prefix. + EXPECT_TRUE(("A"_L / "B"_L).EndsWith({})); + + // Reverse suffix. + EXPECT_FALSE(("A"_L / "BB"_L).EndsWith("CCC"_L / "A"_L / "BB"_L)); + // There are string suffixes that are not path suffixes. + EXPECT_FALSE(("CCC"_L / "A"_L / "BB"_L).EndsWith("C"_L / "A"_L / "BB"_L)); + // Sanity check. + EXPECT_FALSE(("A"_L / "B"_L).EndsWith("A"_L)); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, FrontBack) +{ + EXPECT_EQ(("A"_L / "BB"_L / "CCC"_L).Front(), "A"); + EXPECT_EQ(("A"_L / "BB"_L / "CCC"_L).Back(), "CCC"); + EXPECT_EQ(TStatisticPath("A"_L).Front(), "A"); + EXPECT_EQ(TStatisticPath("A"_L).Back(), "A"); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, PopBack) +{ + TStatisticPath path = "A"_L / "BB"_L / "CCC"_L; + EXPECT_EQ(path.Back(), "CCC"); + path.PopBack(); + EXPECT_EQ(path.Back(), "BB"); + path.PopBack(); + EXPECT_EQ(path.Back(), "A"); + path.PopBack(); + EXPECT_TRUE(path.Empty()); +} + +//////////////////////////////////////////////////////////////////////////////// + +TEST(TStatisticPathTest, Swap) +{ + TStatisticPath a = "A"_L / "BB"_L; + TStatisticPath b = "B"_L / "AA"_L; + TStatisticPath originalA = a; + TStatisticPath originalB = b; + + a.Swap(b); + EXPECT_EQ(a, originalB); + EXPECT_EQ(b, originalA); + + a.Swap(b); + EXPECT_EQ(a, originalA); + EXPECT_EQ(b, originalB); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace +} // namespace NYT diff --git a/yt/yt/core/misc/unittests/ya.make b/yt/yt/core/misc/unittests/ya.make index 7f8396c752..25a32157ec 100644 --- a/yt/yt/core/misc/unittests/ya.make +++ b/yt/yt/core/misc/unittests/ya.make @@ -65,6 +65,7 @@ SRCS( sliding_window_ut.cpp sync_cache_ut.cpp spsc_queue_ut.cpp + statistic_path_ut.cpp statistics_ut.cpp string_ut.cpp sync_expiring_cache_ut.cpp diff --git a/yt/yt/core/ya.make b/yt/yt/core/ya.make index 9ce00aca5b..d4a54b2ad7 100644 --- a/yt/yt/core/ya.make +++ b/yt/yt/core/ya.make @@ -152,6 +152,7 @@ SRCS( misc/shutdown.cpp misc/signal_registry.cpp misc/slab_allocator.cpp + misc/statistic_path.cpp misc/statistics.cpp misc/string_helpers.cpp misc/cache_config.cpp diff --git a/yt/yt/core/ytree/serialize.cpp b/yt/yt/core/ytree/serialize.cpp index f6a1b23ca6..8476a92b7e 100644 --- a/yt/yt/core/ytree/serialize.cpp +++ b/yt/yt/core/ytree/serialize.cpp @@ -167,6 +167,12 @@ void Serialize(IInputStream& input, IYsonConsumer* consumer) Serialize(TYsonInput(&input), consumer); } +// TStatisticPath. +void Serialize(const NStatisticPath::TStatisticPath& path, IYsonConsumer* consumer) +{ + consumer->OnStringScalar(path.Path()); +} + // Subtypes of google::protobuf::Message void SerializeProtobufMessage( const Message& message, @@ -342,6 +348,12 @@ void Deserialize(TGuid& value, INodePtr node) value = TGuid::FromString(node->AsString()->GetValue()); } +// TStatisticPath. +void Deserialize(NStatisticPath::TStatisticPath& value, INodePtr node) +{ + value = NStatisticPath::ParseStatisticPath(node->AsString()->GetValue()).ValueOrThrow(); +} + // Subtypes of google::protobuf::Message void DeserializeProtobufMessage( Message& message, diff --git a/yt/yt/core/ytree/serialize.h b/yt/yt/core/ytree/serialize.h index 2a9c0875ee..633507dce5 100644 --- a/yt/yt/core/ytree/serialize.h +++ b/yt/yt/core/ytree/serialize.h @@ -6,6 +6,7 @@ #include <yt/yt/core/misc/guid.h> #include <yt/yt/core/misc/mpl.h> +#include <yt/yt/core/misc/statistic_path.h> #include <yt/yt/core/yson/writer.h> @@ -161,6 +162,8 @@ void Serialize( template <class T, class TTag> void Serialize(const TStrongTypedef<T, TTag>& value, NYson::IYsonConsumer* consumer); +void Serialize(const NStatisticPath::TStatisticPath& path, NYson::IYsonConsumer* consumer); + //////////////////////////////////////////////////////////////////////////////// template <class T> @@ -260,6 +263,8 @@ void Deserialize( template <class T, class TTag> void Deserialize(TStrongTypedef<T, TTag>& value, INodePtr node); +void Deserialize(NStatisticPath::TStatisticPath& path, INodePtr node); + //////////////////////////////////////////////////////////////////////////////// template <class T, class... TExtraArgs> |