Browse Source

YQ Connector: move tests from yql to ydb (OSS)

Перенос папки с тестами на Коннектор из папки yql в папку ydb (синхронизируется с github).
vitalyisaev 1 year ago
parent
commit
c2b2dfd982

+ 158 - 0
contrib/clickhouse/base/base/BorrowedObjectPool.h

@@ -0,0 +1,158 @@
+#pragma once
+
+#include <cstdint>
+#include <vector>
+#include <chrono>
+#include <mutex>
+#include <condition_variable>
+
+#include <base/defines.h>
+#include <base/MoveOrCopyIfThrow.h>
+
+/** Pool for limited size objects that cannot be used from different threads simultaneously.
+  * The main use case is to have fixed size of objects that can be reused in different threads during their lifetime
+  * and have to be initialized on demand.
+  * Two main properties of pool are allocated objects size and borrowed objects size.
+  * Allocated objects size is size of objects that are currently allocated by the pool.
+  * Borrowed objects size is size of objects that are borrowed by clients.
+  * If max_size == 0 then pool has unlimited size and objects will be allocated without limit.
+  *
+  * Pool provides following strategy for borrowing object:
+  * If max_size == 0 then pool has unlimited size and objects will be allocated without limit.
+  * 1. If pool has objects that can be borrowed increase borrowed objects size and return it.
+  * 2. If pool allocatedObjectsSize is lower than max objects size or pool has unlimited size
+  * allocate new object, increase borrowed objects size and return it.
+  * 3. If pool is full wait on condition variable with or without timeout until some object
+  * will be returned to the pool.
+  */
+template <typename T>
+class BorrowedObjectPool final
+{
+public:
+    explicit BorrowedObjectPool(size_t max_size_) : max_size(max_size_) {}
+
+    /// Borrow object from pool. If pull is full and all objects were borrowed
+    /// then calling thread will wait until some object will be returned into pool.
+    template <typename FactoryFunc>
+    void borrowObject(T & dest, FactoryFunc && func)
+    {
+        std::unique_lock<std::mutex> lock(objects_mutex);
+
+        if (!objects.empty())
+        {
+            dest = borrowFromObjects(lock);
+            return;
+        }
+
+        bool has_unlimited_size = (max_size == 0);
+
+        if (unlikely(has_unlimited_size) || allocated_objects_size < max_size)
+        {
+            dest = allocateObjectForBorrowing(lock, std::forward<FactoryFunc>(func));
+            return;
+        }
+
+        condition_variable.wait(lock, [this] { return !objects.empty(); });
+        dest = borrowFromObjects(lock);
+    }
+
+    /// Same as borrowObject function, but wait with timeout.
+    /// Returns true if object was borrowed during timeout.
+    template <typename FactoryFunc>
+    bool tryBorrowObject(T & dest, FactoryFunc && func, size_t timeout_in_milliseconds = 0)
+    {
+        std::unique_lock<std::mutex> lock(objects_mutex);
+
+        if (!objects.empty())
+        {
+            dest = borrowFromObjects(lock);
+            return true;
+        }
+
+        bool has_unlimited_size = (max_size == 0);
+
+        if (unlikely(has_unlimited_size) || allocated_objects_size < max_size)
+        {
+            dest = allocateObjectForBorrowing(lock, std::forward<FactoryFunc>(func));
+            return true;
+        }
+
+        bool wait_result = condition_variable.wait_for(lock, std::chrono::milliseconds(timeout_in_milliseconds), [this] { return !objects.empty(); });
+
+        if (wait_result)
+            dest = borrowFromObjects(lock);
+
+        return wait_result;
+    }
+
+    /// Return object into pool. Client must return same object that was borrowed.
+    inline void returnObject(T && object_to_return)
+    {
+        {
+            std::lock_guard lock(objects_mutex);
+
+            objects.emplace_back(std::move(object_to_return));
+            --borrowed_objects_size;
+        }
+
+        condition_variable.notify_one();
+    }
+
+    /// Max pool size
+    inline size_t maxSize() const
+    {
+        return max_size;
+    }
+
+    /// Allocated objects size by the pool. If allocatedObjectsSize == maxSize then pool is full.
+    inline size_t allocatedObjectsSize() const
+    {
+        std::lock_guard lock(objects_mutex);
+        return allocated_objects_size;
+    }
+
+    /// Returns allocatedObjectsSize == maxSize
+    inline bool isFull() const
+    {
+        std::lock_guard lock(objects_mutex);
+        return allocated_objects_size == max_size;
+    }
+
+    /// Borrowed objects size. If borrowedObjectsSize == allocatedObjectsSize and pool is full.
+    /// Then client will wait during borrowObject function call.
+    inline size_t borrowedObjectsSize() const
+    {
+        std::lock_guard lock(objects_mutex);
+        return borrowed_objects_size;
+    }
+
+private:
+
+    template <typename FactoryFunc>
+    inline T allocateObjectForBorrowing(const std::unique_lock<std::mutex> &, FactoryFunc && func)
+    {
+        ++allocated_objects_size;
+        ++borrowed_objects_size;
+
+        return std::forward<FactoryFunc>(func)();
+    }
+
+    inline T borrowFromObjects(const std::unique_lock<std::mutex> &)
+    {
+        T dst;
+        detail::moveOrCopyIfThrow(std::move(objects.back()), dst);
+        objects.pop_back();
+
+        ++borrowed_objects_size;
+
+        return dst;
+    }
+
+    size_t max_size;
+
+    mutable std::mutex objects_mutex;
+    std::condition_variable condition_variable;
+    size_t allocated_objects_size = 0;
+    size_t borrowed_objects_size = 0;
+    std::vector<T> objects;
+};

+ 14 - 0
contrib/clickhouse/base/base/DayNum.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <base/types.h>
+#include <base/strong_typedef.h>
+
+/** Represents number of days since 1970-01-01.
+  * See DateLUTImpl for usage examples.
+  */
+STRONG_TYPEDEF(UInt16, DayNum)
+
+/** Represent number of days since 1970-01-01 but in extended range,
+ * for dates before 1970-01-01 and after 2105
+ */
+STRONG_TYPEDEF(Int32, ExtendedDayNum)

+ 141 - 0
contrib/clickhouse/base/base/Decimal.h

@@ -0,0 +1,141 @@
+#pragma once
+#include <base/extended_types.h>
+#include <base/Decimal_fwd.h>
+
+#if !defined(NO_SANITIZE_UNDEFINED)
+#if defined(__clang__)
+    #define NO_SANITIZE_UNDEFINED __attribute__((__no_sanitize__("undefined")))
+#else
+    #define NO_SANITIZE_UNDEFINED
+#endif
+#endif
+
+namespace DB
+{
+template <class> struct Decimal;
+class DateTime64;
+
+using Decimal32 = Decimal<Int32>;
+using Decimal64 = Decimal<Int64>;
+using Decimal128 = Decimal<Int128>;
+using Decimal256 = Decimal<Int256>;
+
+template <class T> struct NativeTypeT { using Type = T; };
+template <is_decimal T> struct NativeTypeT<T> { using Type = typename T::NativeType; };
+template <class T> using NativeType = typename NativeTypeT<T>::Type;
+
+/// Own FieldType for Decimal.
+/// It is only a "storage" for decimal.
+/// To perform operations, you also have to provide a scale (number of digits after point).
+template <typename T>
+struct Decimal
+{
+    using NativeType = T;
+
+    constexpr Decimal() = default;
+    constexpr Decimal(Decimal<T> &&) noexcept = default;
+    constexpr Decimal(const Decimal<T> &) = default;
+
+    constexpr Decimal(const T & value_): value(value_) {} // NOLINT(google-explicit-constructor)
+
+    template <typename U>
+    constexpr Decimal(const Decimal<U> & x): value(x.value) {} // NOLINT(google-explicit-constructor)
+
+    constexpr Decimal<T> & operator=(Decimal<T> &&) noexcept = default;
+    constexpr Decimal<T> & operator = (const Decimal<T> &) = default;
+
+    constexpr operator T () const { return value; } // NOLINT(google-explicit-constructor)
+
+    template <typename U>
+    constexpr U convertTo() const
+    {
+        if constexpr (is_decimal<U>)
+            return convertTo<typename U::NativeType>();
+        else
+            return static_cast<U>(value);
+    }
+
+    const Decimal<T> & operator += (const T & x) { value += x; return *this; }
+    const Decimal<T> & operator -= (const T & x) { value -= x; return *this; }
+    const Decimal<T> & operator *= (const T & x) { value *= x; return *this; }
+    const Decimal<T> & operator /= (const T & x) { value /= x; return *this; }
+    const Decimal<T> & operator %= (const T & x) { value %= x; return *this; }
+
+    template <typename U> const Decimal<T> & operator += (const Decimal<U> & x) { value += x.value; return *this; }
+    template <typename U> const Decimal<T> & operator -= (const Decimal<U> & x) { value -= x.value; return *this; }
+    template <typename U> const Decimal<T> & operator *= (const Decimal<U> & x) { value *= x.value; return *this; }
+    template <typename U> const Decimal<T> & operator /= (const Decimal<U> & x) { value /= x.value; return *this; }
+    template <typename U> const Decimal<T> & operator %= (const Decimal<U> & x) { value %= x.value; return *this; }
+
+    /// This is to avoid UB for sumWithOverflow()
+    void NO_SANITIZE_UNDEFINED addOverflow(const T & x) { value += x; }
+
+    T value;
+};
+
+template <typename T> inline bool operator< (const Decimal<T> & x, const Decimal<T> & y) { return x.value < y.value; }
+template <typename T> inline bool operator> (const Decimal<T> & x, const Decimal<T> & y) { return x.value > y.value; }
+template <typename T> inline bool operator<= (const Decimal<T> & x, const Decimal<T> & y) { return x.value <= y.value; }
+template <typename T> inline bool operator>= (const Decimal<T> & x, const Decimal<T> & y) { return x.value >= y.value; }
+template <typename T> inline bool operator== (const Decimal<T> & x, const Decimal<T> & y) { return x.value == y.value; }
+template <typename T> inline bool operator!= (const Decimal<T> & x, const Decimal<T> & y) { return x.value != y.value; }
+
+template <typename T> inline Decimal<T> operator+ (const Decimal<T> & x, const Decimal<T> & y) { return x.value + y.value; }
+template <typename T> inline Decimal<T> operator- (const Decimal<T> & x, const Decimal<T> & y) { return x.value - y.value; }
+template <typename T> inline Decimal<T> operator* (const Decimal<T> & x, const Decimal<T> & y) { return x.value * y.value; }
+template <typename T> inline Decimal<T> operator/ (const Decimal<T> & x, const Decimal<T> & y) { return x.value / y.value; }
+template <typename T> inline Decimal<T> operator- (const Decimal<T> & x) { return -x.value; }
+
+/// Distinguishable type to allow function resolution/deduction based on value type,
+/// but also relatively easy to convert to/from Decimal64.
+class DateTime64 : public Decimal64
+{
+public:
+    using Base = Decimal64;
+    using Base::Base;
+    using NativeType = Base::NativeType;
+
+    constexpr DateTime64(const Base & v): Base(v) {} // NOLINT(google-explicit-constructor)
+};
+}
+
+constexpr DB::UInt64 max_uint_mask = std::numeric_limits<DB::UInt64>::max();
+
+namespace std
+{
+    template <typename T>
+    struct hash<DB::Decimal<T>>
+    {
+        size_t operator()(const DB::Decimal<T> & x) const { return hash<T>()(x.value); }
+    };
+
+    template <>
+    struct hash<DB::Decimal128>
+    {
+        size_t operator()(const DB::Decimal128 & x) const
+        {
+            return std::hash<DB::Int64>()(x.value >> 64)
+                ^ std::hash<DB::Int64>()(x.value & max_uint_mask);
+        }
+    };
+
+    template <>
+    struct hash<DB::DateTime64>
+    {
+        size_t operator()(const DB::DateTime64 & x) const
+        {
+            return std::hash<DB::DateTime64::NativeType>()(x);
+        }
+    };
+
+    template <>
+    struct hash<DB::Decimal256>
+    {
+        size_t operator()(const DB::Decimal256 & x) const
+        {
+            // FIXME temp solution
+            return std::hash<DB::Int64>()(static_cast<DB::Int64>(x.value >> 64 & max_uint_mask))
+                ^ std::hash<DB::Int64>()(static_cast<DB::Int64>(x.value & max_uint_mask));
+        }
+    };
+}

+ 46 - 0
contrib/clickhouse/base/base/Decimal_fwd.h

@@ -0,0 +1,46 @@
+#pragma once
+
+#include <base/types.h>
+
+namespace wide
+{
+
+template <size_t Bits, typename Signed>
+class integer;
+
+}
+
+using Int128 = wide::integer<128, signed>;
+using UInt128 = wide::integer<128, unsigned>;
+using Int256 = wide::integer<256, signed>;
+using UInt256 = wide::integer<256, unsigned>;
+
+namespace DB
+{
+
+template <class> struct Decimal;
+
+using Decimal32 = Decimal<Int32>;
+using Decimal64 = Decimal<Int64>;
+using Decimal128 = Decimal<Int128>;
+using Decimal256 = Decimal<Int256>;
+
+class DateTime64;
+
+template <class T>
+concept is_decimal =
+    std::is_same_v<T, Decimal32>
+    || std::is_same_v<T, Decimal64>
+    || std::is_same_v<T, Decimal128>
+    || std::is_same_v<T, Decimal256>
+    || std::is_same_v<T, DateTime64>;
+
+template <class T>
+concept is_over_big_int =
+    std::is_same_v<T, Int128>
+    || std::is_same_v<T, UInt128>
+    || std::is_same_v<T, Int256>
+    || std::is_same_v<T, UInt256>
+    || std::is_same_v<T, Decimal128>
+    || std::is_same_v<T, Decimal256>;
+}

+ 219 - 0
contrib/clickhouse/base/base/DecomposedFloat.h

@@ -0,0 +1,219 @@
+#pragma once
+
+#include <cstdint>
+#include <cstddef>
+#include <cstring>
+#include <base/extended_types.h>
+
+
+/// Allows to check the internals of IEEE-754 floating point number.
+
+template <typename T> struct FloatTraits;
+
+template <>
+struct FloatTraits<float>
+{
+    using UInt = uint32_t;
+    static constexpr size_t bits = 32;
+    static constexpr size_t exponent_bits = 8;
+    static constexpr size_t mantissa_bits = bits - exponent_bits - 1;
+};
+
+template <>
+struct FloatTraits<double>
+{
+    using UInt = uint64_t;
+    static constexpr size_t bits = 64;
+    static constexpr size_t exponent_bits = 11;
+    static constexpr size_t mantissa_bits = bits - exponent_bits - 1;
+};
+
+
+/// x = sign * (2 ^ normalized_exponent) * (1 + mantissa * 2 ^ -mantissa_bits)
+/// x = sign * (2 ^ normalized_exponent + mantissa * 2 ^ (normalized_exponent - mantissa_bits))
+template <typename T>
+struct DecomposedFloat
+{
+    using Traits = FloatTraits<T>;
+
+    explicit DecomposedFloat(T x)
+    {
+        memcpy(&x_uint, &x, sizeof(x));
+    }
+
+    typename Traits::UInt x_uint;
+
+    bool isNegative() const
+    {
+        return x_uint >> (Traits::bits - 1);
+    }
+
+    /// Returns 0 for both +0. and -0.
+    int sign() const
+    {
+        return (exponent() == 0 && mantissa() == 0)
+            ? 0
+            : (isNegative()
+                ? -1
+                : 1);
+    }
+
+    uint16_t exponent() const
+    {
+        return (x_uint >> (Traits::mantissa_bits)) & (((1ull << (Traits::exponent_bits + 1)) - 1) >> 1);
+    }
+
+    int16_t normalizedExponent() const
+    {
+        return int16_t(exponent()) - ((1ull << (Traits::exponent_bits - 1)) - 1);
+    }
+
+    uint64_t mantissa() const
+    {
+        return x_uint & ((1ull << Traits::mantissa_bits) - 1);
+    }
+
+    int64_t mantissaWithSign() const
+    {
+        return isNegative() ? -mantissa() : mantissa();
+    }
+
+    /// NOTE Probably floating point instructions can be better.
+    bool isIntegerInRepresentableRange() const
+    {
+        return x_uint == 0
+            || (normalizedExponent() >= 0  /// The number is not less than one
+                /// The number is inside the range where every integer has exact representation in float
+                && normalizedExponent() <= static_cast<int16_t>(Traits::mantissa_bits)
+                /// After multiplying by 2^exp, the fractional part becomes zero, means the number is integer
+                && ((mantissa() & ((1ULL << (Traits::mantissa_bits - normalizedExponent())) - 1)) == 0));
+    }
+
+
+    /// Compare float with integer of arbitrary width (both signed and unsigned are supported). Assuming two's complement arithmetic.
+    /// This function is generic, big integers (128, 256 bit) are supported as well.
+    /// Infinities are compared correctly. NaNs are treat similarly to infinities, so they can be less than all numbers.
+    /// (note that we need total order)
+    /// Returns -1, 0 or 1.
+    template <typename Int>
+    int compare(Int rhs) const
+    {
+        if (rhs == 0)
+            return sign();
+
+        /// Different signs
+        if (isNegative() && rhs > 0)
+            return -1;
+        if (!isNegative() && rhs < 0)
+            return 1;
+
+        /// Fractional number with magnitude less than one
+        if (normalizedExponent() < 0)
+        {
+            if (!isNegative())
+                return rhs > 0 ? -1 : 1;
+            else
+                return rhs >= 0 ? -1 : 1;
+        }
+
+        /// The case of the most negative integer
+        if constexpr (is_signed_v<Int>)
+        {
+            if (rhs == std::numeric_limits<Int>::lowest())
+            {
+                assert(isNegative());
+
+                if (normalizedExponent() < static_cast<int16_t>(8 * sizeof(Int) - is_signed_v<Int>))
+                    return 1;
+                if (normalizedExponent() > static_cast<int16_t>(8 * sizeof(Int) - is_signed_v<Int>))
+                    return -1;
+
+                if (mantissa() == 0)
+                    return 0;
+                else
+                    return -1;
+            }
+        }
+
+        /// Too large number: abs(float) > abs(rhs). Also the case with infinities and NaN.
+        if (normalizedExponent() >= static_cast<int16_t>(8 * sizeof(Int) - is_signed_v<Int>))
+            return isNegative() ? -1 : 1;
+
+        using UInt = std::conditional_t<(sizeof(Int) > sizeof(typename Traits::UInt)), make_unsigned_t<Int>, typename Traits::UInt>;
+        UInt uint_rhs = rhs < 0 ? -rhs : rhs;
+
+        /// Smaller octave: abs(rhs) < abs(float)
+        /// FYI, TIL: octave is also called "binade", https://en.wikipedia.org/wiki/Binade
+        if (uint_rhs < (static_cast<UInt>(1) << normalizedExponent()))
+            return isNegative() ? -1 : 1;
+
+        /// Larger octave: abs(rhs) > abs(float)
+        if (normalizedExponent() + 1 < static_cast<int16_t>(8 * sizeof(Int) - is_signed_v<Int>)
+            && uint_rhs >= (static_cast<UInt>(1) << (normalizedExponent() + 1)))
+            return isNegative() ? 1 : -1;
+
+        /// The same octave
+        /// uint_rhs == 2 ^ normalizedExponent + mantissa * 2 ^ (normalizedExponent - mantissa_bits)
+
+        bool large_and_always_integer = normalizedExponent() >= static_cast<int16_t>(Traits::mantissa_bits);
+
+        UInt a = large_and_always_integer
+            ? static_cast<UInt>(mantissa()) << (normalizedExponent() - Traits::mantissa_bits)
+            : static_cast<UInt>(mantissa()) >> (Traits::mantissa_bits - normalizedExponent());
+
+        UInt b = uint_rhs - (static_cast<UInt>(1) << normalizedExponent());
+
+        if (a < b)
+            return isNegative() ? 1 : -1;
+        if (a > b)
+            return isNegative() ? -1 : 1;
+
+        /// Float has no fractional part means that the numbers are equal.
+        if (large_and_always_integer || (mantissa() & ((1ULL << (Traits::mantissa_bits - normalizedExponent())) - 1)) == 0)
+            return 0;
+        else
+            /// Float has fractional part means its abs value is larger.
+            return isNegative() ? -1 : 1;
+    }
+
+
+    template <typename Int>
+    bool equals(Int rhs) const
+    {
+        return compare(rhs) == 0;
+    }
+
+    template <typename Int>
+    bool notEquals(Int rhs) const
+    {
+        return compare(rhs) != 0;
+    }
+
+    template <typename Int>
+    bool less(Int rhs) const
+    {
+        return compare(rhs) < 0;
+    }
+
+    template <typename Int>
+    bool greater(Int rhs) const
+    {
+        return compare(rhs) > 0;
+    }
+
+    template <typename Int>
+    bool lessOrEquals(Int rhs) const
+    {
+        return compare(rhs) <= 0;
+    }
+
+    template <typename Int>
+    bool greaterOrEquals(Int rhs) const
+    {
+        return compare(rhs) >= 0;
+    }
+};
+
+
+using DecomposedFloat64 = DecomposedFloat<double>;
+using DecomposedFloat32 = DecomposedFloat<float>;

+ 39 - 0
contrib/clickhouse/base/base/EnumReflection.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include <magic_enum.hpp>
+#include <fmt/format.h>
+
+
+template <class T> concept is_enum = std::is_enum_v<T>;
+
+namespace detail
+{
+template <is_enum E, class F, size_t ...I>
+constexpr void static_for(F && f, std::index_sequence<I...>)
+{
+    (std::forward<F>(f)(std::integral_constant<E, magic_enum::enum_value<E>(I)>()) , ...);
+}
+}
+
+/**
+ * Iterate over enum values in compile-time (compile-time switch/case, loop unrolling).
+ *
+ * @example static_for<E>([](auto enum_value) { return template_func<enum_value>(); }
+ * ^ enum_value can be used as a template parameter
+ */
+template <is_enum E, class F>
+constexpr void static_for(F && f)
+{
+    constexpr size_t count = magic_enum::enum_count<E>();
+    detail::static_for<E>(std::forward<F>(f), std::make_index_sequence<count>());
+}
+
+/// Enable printing enum values as strings via fmt + magic_enum
+template <is_enum T>
+struct fmt::formatter<T> : fmt::formatter<std::string_view>
+{
+    constexpr auto format(T value, auto& format_context)
+    {
+        return formatter<string_view>::format(magic_enum::enum_name(value), format_context);
+    }
+};

+ 34 - 0
contrib/clickhouse/base/base/FnTraits.h

@@ -0,0 +1,34 @@
+#pragma once
+
+#include "TypeList.h"
+
+namespace detail
+{
+template <class T>
+struct FnTraits { template <class> static constexpr bool value = false; };
+
+template <class R, class ...A>
+struct FnTraits<R(A...)>
+{
+    template <class F>
+    static constexpr bool value = std::is_invocable_r_v<R, F, A...>;
+
+    using Ret = R;
+    using Args = TypeList<A...>;
+};
+
+template <class R, class ...A>
+struct FnTraits<R(*)(A...)> : FnTraits<R(A...)> {};
+}
+
+template <class T> using FnTraits = detail::FnTraits<T>;
+
+/**
+ * A less-typing alias for std::is_invokable_r_v.
+ * @example void foo(Fn<bool(int, char)> auto && functor)
+ */
+template <class F, class FS>
+concept Fn = FnTraits<FS>::template value<F>;
+
+template <auto Value>
+using Constant = std::integral_constant<decltype(Value), Value>;

+ 77 - 0
contrib/clickhouse/base/base/IPv4andIPv6.h

@@ -0,0 +1,77 @@
+#pragma once
+
+#include <base/strong_typedef.h>
+#include <base/extended_types.h>
+#include <Common/formatIPv6.h>
+#include <Common/memcmpSmall.h>
+
+namespace DB
+{
+
+    struct IPv4 : StrongTypedef<UInt32, struct IPv4Tag>
+    {
+        using StrongTypedef::StrongTypedef;
+        using StrongTypedef::operator=;
+        constexpr explicit IPv4(UInt64 value): StrongTypedef(static_cast<UnderlyingType>(value)) {}
+    };
+
+    struct IPv6 : StrongTypedef<UInt128, struct IPv6Tag>
+    {
+        using StrongTypedef::StrongTypedef;
+        using StrongTypedef::operator=;
+
+        bool operator<(const IPv6 & rhs) const
+        {
+            return
+                memcmp16(
+                    reinterpret_cast<const unsigned char *>(toUnderType().items),
+                    reinterpret_cast<const unsigned char *>(rhs.toUnderType().items)
+                ) < 0;
+        }
+
+        bool operator>(const IPv6 & rhs) const
+        {
+            return
+                memcmp16(
+                    reinterpret_cast<const unsigned char *>(toUnderType().items),
+                    reinterpret_cast<const unsigned char *>(rhs.toUnderType().items)
+                ) > 0;
+        }
+
+        bool operator==(const IPv6 & rhs) const
+        {
+            return
+                memcmp16(
+                    reinterpret_cast<const unsigned char *>(toUnderType().items),
+                    reinterpret_cast<const unsigned char *>(rhs.toUnderType().items)
+                ) == 0;
+        }
+
+        bool operator<=(const IPv6 & rhs) const { return !operator>(rhs); }
+        bool operator>=(const IPv6 & rhs) const { return !operator<(rhs); }
+        bool operator!=(const IPv6 & rhs) const { return !operator==(rhs); }
+    };
+
+}
+
+namespace std
+{
+    /// For historical reasons we hash IPv6 as a FixedString(16)
+    template <>
+    struct hash<DB::IPv6>
+    {
+        size_t operator()(const DB::IPv6 & x) const
+        {
+            return std::hash<std::string_view>{}(std::string_view(reinterpret_cast<const char*>(&x.toUnderType()), IPV6_BINARY_LENGTH));
+        }
+    };
+
+    template <>
+    struct hash<DB::IPv4>
+    {
+        size_t operator()(const DB::IPv4 & x) const
+        {
+            return std::hash<DB::IPv4::UnderlyingType>()(x.toUnderType());
+        }
+    };
+}

+ 827 - 0
contrib/clickhouse/base/base/JSON.cpp

@@ -0,0 +1,827 @@
+#include <string>
+#include <cstring>
+
+#include <Poco/UTF8Encoding.h>
+#include <Poco/NumberParser.h>
+#include <base/JSON.h>
+#include <base/find_symbols.h>
+#include <base/preciseExp10.h>
+
+#define JSON_MAX_DEPTH 100
+
+
+#ifdef __clang__
+#  pragma clang diagnostic push
+#  pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec"
+#endif
+POCO_IMPLEMENT_EXCEPTION(JSONException, Poco::Exception, "JSONException") // NOLINT(cert-err60-cpp, modernize-use-noexcept, hicpp-use-noexcept)
+#ifdef __clang__
+#  pragma clang diagnostic pop
+#endif
+
+
+/// Read unsigned integer in a simple form from a non-0-terminated string.
+static UInt64 readUIntText(const char * buf, const char * end)
+{
+    UInt64 x = 0;
+
+    if (buf == end)
+        throw JSONException("JSON: cannot parse unsigned integer: unexpected end of data.");
+
+    while (buf != end)
+    {
+        switch (*buf)
+        {
+            case '+':
+                break;
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                x *= 10;
+                x += *buf - '0';
+                break;
+            default:
+                return x;
+        }
+        ++buf;
+    }
+
+    return x;
+}
+
+
+/// Read signed integer in a simple form from a non-0-terminated string.
+static Int64 readIntText(const char * buf, const char * end)
+{
+    bool negative = false;
+    UInt64 x = 0;
+
+    if (buf == end)
+        throw JSONException("JSON: cannot parse signed integer: unexpected end of data.");
+
+    bool run = true;
+    while (buf != end && run)
+    {
+        switch (*buf)
+        {
+            case '+':
+                break;
+            case '-':
+                negative = true;
+                break;
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                x *= 10;
+                x += *buf - '0';
+                break;
+            default:
+                run = false;
+                break;
+        }
+        ++buf;
+    }
+
+    return negative ? -x : x;
+}
+
+
+/// Read floating point number in simple format, imprecisely, from a non-0-terminated string.
+static double readFloatText(const char * buf, const char * end)
+{
+    bool negative = false;
+    double x = 0;
+    bool after_point = false;
+    double power_of_ten = 1;
+
+    if (buf == end)
+        throw JSONException("JSON: cannot parse floating point number: unexpected end of data.");
+
+    bool run = true;
+    while (buf != end && run)
+    {
+        switch (*buf)
+        {
+            case '+':
+                break;
+            case '-':
+                negative = true;
+                break;
+            case '.':
+                after_point = true;
+                break;
+            case '0':
+            case '1':
+            case '2':
+            case '3':
+            case '4':
+            case '5':
+            case '6':
+            case '7':
+            case '8':
+            case '9':
+                if (after_point)
+                {
+                    power_of_ten /= 10;
+                    x += (*buf - '0') * power_of_ten;
+                }
+                else
+                {
+                    x *= 10;
+                    x += *buf - '0';
+                }
+                break;
+            case 'e':
+            case 'E':
+            {
+                ++buf;
+                auto exponent = readIntText(buf, end);
+                x *= preciseExp10(static_cast<double>(exponent));
+
+                run = false;
+                break;
+            }
+            default:
+                run = false;
+                break;
+        }
+        ++buf;
+    }
+    if (negative)
+        x = -x;
+
+    return x;
+}
+
+
+void JSON::checkInit() const
+{
+    if (!(ptr_begin < ptr_end))
+        throw JSONException("JSON: begin >= end.");
+
+    if (level > JSON_MAX_DEPTH)
+        throw JSONException("JSON: too deep.");
+}
+
+
+JSON::ElementType JSON::getType() const
+{
+    switch (*ptr_begin)
+    {
+        case '{':
+            return TYPE_OBJECT;
+        case '[':
+            return TYPE_ARRAY;
+        case 't':
+        case 'f':
+            return TYPE_BOOL;
+        case 'n':
+            return TYPE_NULL;
+        case '-':
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+            return TYPE_NUMBER;
+        case '"':
+        {
+            /// Is it a string or a name-value pair?
+            Pos after_string = skipString();
+            if (after_string < ptr_end && *after_string == ':')
+                return TYPE_NAME_VALUE_PAIR;
+            else
+                return TYPE_STRING;
+        }
+        default:
+            throw JSONException(std::string("JSON: unexpected char ") + *ptr_begin + ", expected one of '{[tfn-0123456789\"'");
+    }
+}
+
+
+void JSON::checkPos(Pos pos) const
+{
+    if (pos >= ptr_end || ptr_begin == nullptr)
+        throw JSONException("JSON: unexpected end of data.");
+}
+
+
+JSON::Pos JSON::skipString() const
+{
+    Pos pos = ptr_begin;
+    checkPos(pos);
+    if (*pos != '"')
+        throw JSONException(std::string("JSON: expected \", got ") + *pos);
+    ++pos;
+
+    /// fast path: find next double quote. If it is not escaped by backslash - then it's an end of string (assuming JSON is valid).
+    Pos closing_quote = reinterpret_cast<const char *>(memchr(reinterpret_cast<const void *>(pos), '\"', ptr_end - pos));
+    if (nullptr != closing_quote && closing_quote[-1] != '\\')
+        return closing_quote + 1;
+
+    /// slow path
+    while (pos < ptr_end && *pos != '"')
+    {
+        if (*pos == '\\')
+        {
+            ++pos;
+            checkPos(pos);
+            if (*pos == 'u')
+            {
+                pos += 4;
+                checkPos(pos);
+            }
+        }
+        ++pos;
+    }
+
+    checkPos(pos);
+    if (*pos != '"')
+        throw JSONException(std::string("JSON: expected \", got ") + *pos);
+    ++pos;
+
+    return pos;
+}
+
+
+JSON::Pos JSON::skipNumber() const
+{
+    Pos pos = ptr_begin;
+
+    checkPos(pos);
+    if (*pos == '-')
+        ++pos;
+
+    while (pos < ptr_end && *pos >= '0' && *pos <= '9')
+        ++pos;
+    if (pos < ptr_end && *pos == '.')
+        ++pos;
+    while (pos < ptr_end && *pos >= '0' && *pos <= '9')
+        ++pos;
+    if (pos < ptr_end && (*pos == 'e' || *pos == 'E'))
+        ++pos;
+    if (pos < ptr_end && *pos == '-')
+        ++pos;
+     while (pos < ptr_end && *pos >= '0' && *pos <= '9')
+        ++pos;
+
+    return pos;
+}
+
+
+JSON::Pos JSON::skipBool() const
+{
+    Pos pos = ptr_begin;
+    checkPos(pos);
+
+    if (*ptr_begin == 't')
+        pos += 4;
+    else if (*ptr_begin == 'f')
+        pos += 5;
+    else
+        throw JSONException("JSON: expected true or false.");
+
+    return pos;
+}
+
+
+JSON::Pos JSON::skipNull() const
+{
+    return ptr_begin + 4;
+}
+
+
+JSON::Pos JSON::skipNameValuePair() const
+{
+    Pos pos = skipString();
+    checkPos(pos);
+
+    if (*pos != ':')
+        throw JSONException("JSON: expected :.");
+    ++pos;
+
+    return JSON(pos, ptr_end, level + 1).skipElement();
+
+}
+
+
+JSON::Pos JSON::skipArray() const
+{
+    if (!isArray())
+        throw JSONException("JSON: expected [");
+    Pos pos = ptr_begin;
+    ++pos;
+    checkPos(pos);
+    if (*pos == ']')
+        return ++pos;
+
+    while (true)
+    {
+        pos = JSON(pos, ptr_end, level + 1).skipElement();
+
+        checkPos(pos);
+
+        switch (*pos)
+        {
+            case ',':
+                ++pos;
+                break;
+            case ']':
+                return ++pos;
+            default:
+                throw JSONException(std::string("JSON: expected one of ',]', got ") + *pos);
+        }
+    }
+}
+
+
+JSON::Pos JSON::skipObject() const
+{
+    if (!isObject())
+        throw JSONException("JSON: expected {");
+    Pos pos = ptr_begin;
+    ++pos;
+    checkPos(pos);
+    if (*pos == '}')
+        return ++pos;
+
+    while (true)
+    {
+        pos = JSON(pos, ptr_end, level + 1).skipNameValuePair();
+
+        checkPos(pos);
+
+        switch (*pos)
+        {
+            case ',':
+                ++pos;
+                break;
+            case '}':
+                return ++pos;
+            default:
+                throw JSONException(std::string("JSON: expected one of ',}', got ") + *pos);
+        }
+    }
+}
+
+
+JSON::Pos JSON::skipElement() const
+{
+    ElementType type = getType();
+
+    switch (type)
+    {
+        case TYPE_NULL:
+            return skipNull();
+        case TYPE_BOOL:
+            return skipBool();
+        case TYPE_NUMBER:
+            return skipNumber();
+        case TYPE_STRING:
+            return skipString();
+        case TYPE_NAME_VALUE_PAIR:
+            return skipNameValuePair();
+        case TYPE_ARRAY:
+            return skipArray();
+        case TYPE_OBJECT:
+            return skipObject();
+        default:
+            throw JSONException("Logical error in JSON: unknown element type: " + std::to_string(type));
+    }
+}
+
+size_t JSON::size() const
+{
+    size_t i = 0;
+
+    for (const_iterator it = begin(); it != end(); ++it)
+        ++i;
+
+    return i;
+}
+
+
+bool JSON::empty() const
+{
+    return size() == 0;
+}
+
+
+JSON JSON::operator[] (size_t n) const
+{
+    ElementType type = getType();
+
+    if (type != TYPE_ARRAY)
+        throw JSONException("JSON: not array when calling operator[](size_t) method.");
+
+    Pos pos = ptr_begin;
+    ++pos;
+    checkPos(pos);
+
+    size_t i = 0;
+    const_iterator it = begin();
+    while (i < n && it != end())
+    {
+        ++it;
+        ++i;
+    }
+
+    if (i != n)
+        throw JSONException("JSON: array index " + std::to_string(n) + " out of bounds.");
+
+    return *it;
+}
+
+
+JSON::Pos JSON::searchField(const char * data, size_t size) const
+{
+    ElementType type = getType();
+
+    if (type != TYPE_OBJECT)
+        throw JSONException("JSON: not object when calling operator[](const char *) or has(const char *) method.");
+
+    const_iterator it = begin();
+    for (; it != end(); ++it)
+    {
+        if (!it->hasEscapes())
+        {
+            const auto current_name = it->getRawName();
+            if (current_name.size() == size && 0 == memcmp(current_name.data(), data, size))
+                break;
+        }
+        else
+        {
+            std::string current_name = it->getName();
+            if (current_name.size() == size && 0 == memcmp(current_name.data(), data, size))
+                break;
+        }
+    }
+
+    if (it == end())
+        return nullptr;
+    else
+        return it->data();
+}
+
+
+bool JSON::hasEscapes() const
+{
+    Pos pos = ptr_begin + 1;
+    while (pos < ptr_end && *pos != '"' && *pos != '\\')
+        ++pos;
+
+    if (*pos == '"')
+        return false;
+    else if (*pos == '\\')
+        return true;
+    throw JSONException("JSON: unexpected end of data.");
+}
+
+
+bool JSON::hasSpecialChars() const
+{
+    Pos pos = ptr_begin + 1;
+    while (pos < ptr_end && *pos != '"'
+        && *pos != '\\' && *pos != '\r' && *pos != '\n' && *pos != '\t'
+        && *pos != '\f' && *pos != '\b' && *pos != '\0' && *pos != '\'')
+        ++pos;
+
+    if (*pos == '"')
+        return false;
+    else if (pos < ptr_end)
+        return true;
+    throw JSONException("JSON: unexpected end of data.");
+}
+
+
+JSON JSON::operator[] (const std::string & name) const
+{
+    Pos pos = searchField(name);
+    if (!pos)
+        throw JSONException("JSON: there is no element '" + std::string(name) + "' in object.");
+
+    return JSON(pos, ptr_end, level + 1).getValue();
+}
+
+
+bool JSON::has(const char * data, size_t size) const
+{
+    return nullptr != searchField(data, size);
+}
+
+
+double JSON::getDouble() const
+{
+    return readFloatText(ptr_begin, ptr_end);
+}
+
+Int64 JSON::getInt() const
+{
+    return readIntText(ptr_begin, ptr_end);
+}
+
+UInt64 JSON::getUInt() const
+{
+    return readUIntText(ptr_begin, ptr_end);
+}
+
+bool JSON::getBool() const
+{
+    if (*ptr_begin == 't')
+        return true;
+    if (*ptr_begin == 'f')
+        return false;
+    throw JSONException("JSON: cannot parse boolean.");
+}
+
+std::string JSON::getString() const
+{
+    Pos s = ptr_begin;
+
+    if (*s != '"')
+        throw JSONException(std::string("JSON: expected \", got ") + *s);
+    ++s;
+    checkPos(s);
+
+    std::string buf;
+    do
+    {
+        Pos p = find_first_symbols<'\\','"'>(s, ptr_end);
+        if (p >= ptr_end)
+        {
+            break;
+        }
+        buf.append(s, p);
+        s = p;
+        switch (*s)
+        {
+            case '\\':
+                ++s;
+                checkPos(s);
+
+                switch (*s)
+                {
+                    case '"':
+                        buf += '"';
+                        break;
+                    case '\\':
+                        buf += '\\';
+                        break;
+                    case '/':
+                        buf += '/';
+                        break;
+                    case 'b':
+                        buf += '\b';
+                        break;
+                    case 'f':
+                        buf += '\f';
+                        break;
+                    case 'n':
+                        buf += '\n';
+                        break;
+                    case 'r':
+                        buf += '\r';
+                        break;
+                    case 't':
+                        buf += '\t';
+                        break;
+                    case 'u':
+                    {
+                        Poco::UTF8Encoding utf8;
+
+                        ++s;
+                        checkPos(s + 4);
+                        std::string hex(s, 4);
+                        s += 3;
+                        int unicode;
+                        try
+                        {
+                            unicode = Poco::NumberParser::parseHex(hex);
+                        }
+                        catch (const Poco::SyntaxException &)
+                        {
+                            throw JSONException("JSON: incorrect syntax: incorrect HEX code.");
+                        }
+                        buf.resize(buf.size() + 6);    /// Max size of UTF-8 sequence, including pre-standard mapping of UCS-4 to UTF-8.
+                        int res = utf8.convert(unicode,
+                            reinterpret_cast<unsigned char *>(const_cast<char*>(buf.data())) + buf.size() - 6, 6);
+                        if (!res)
+                            throw JSONException("JSON: cannot convert unicode " + std::to_string(unicode)
+                                + " to UTF8.");
+                        buf.resize(buf.size() - 6 + res);
+                        break;
+                    }
+                    default:
+                        buf += *s;
+                        break;
+                }
+                ++s;
+                break;
+            case '"':
+                return buf;
+            default:
+                throw JSONException("find_first_symbols<...>() failed in unexpected way");
+        }
+    } while (s < ptr_end);
+    throw JSONException("JSON: incorrect syntax (expected end of string, found end of JSON).");
+}
+
+std::string JSON::getName() const
+{
+    return getString();
+}
+
+std::string_view JSON::getRawString() const
+{
+    Pos s = ptr_begin;
+    if (*s != '"')
+        throw JSONException(std::string("JSON: expected \", got ") + *s);
+    while (++s != ptr_end && *s != '"');
+    if (s != ptr_end)
+        return std::string_view(ptr_begin + 1, s - ptr_begin - 1);
+    throw JSONException("JSON: incorrect syntax (expected end of string, found end of JSON).");
+}
+
+std::string_view JSON::getRawName() const
+{
+    return getRawString();
+}
+
+JSON JSON::getValue() const
+{
+    Pos pos = skipString();
+    checkPos(pos);
+    if (*pos != ':')
+        throw JSONException("JSON: expected :.");
+    ++pos;
+    checkPos(pos);
+    return JSON(pos, ptr_end, level + 1);
+}
+
+
+double JSON::toDouble() const
+{
+    ElementType type = getType();
+
+    if (type == TYPE_NUMBER)
+        return getDouble();
+    else if (type == TYPE_STRING)
+        return JSON(ptr_begin + 1, ptr_end, level + 1).getDouble();
+    else
+        throw JSONException("JSON: cannot convert value to double.");
+}
+
+Int64 JSON::toInt() const
+{
+    ElementType type = getType();
+
+    if (type == TYPE_NUMBER)
+        return getInt();
+    else if (type == TYPE_STRING)
+        return JSON(ptr_begin + 1, ptr_end, level + 1).getInt();
+    else
+        throw JSONException("JSON: cannot convert value to signed integer.");
+}
+
+UInt64 JSON::toUInt() const
+{
+    ElementType type = getType();
+
+    if (type == TYPE_NUMBER)
+        return getUInt();
+    else if (type == TYPE_STRING)
+        return JSON(ptr_begin + 1, ptr_end, level + 1).getUInt();
+    else
+        throw JSONException("JSON: cannot convert value to unsigned integer.");
+}
+
+std::string JSON::toString() const
+{
+    ElementType type = getType();
+
+    if (type == TYPE_STRING)
+        return getString();
+    else
+    {
+        Pos pos = skipElement();
+        return std::string(ptr_begin, pos - ptr_begin);
+    }
+}
+
+
+JSON::iterator JSON::iterator::begin() const
+{
+    ElementType type = getType();
+
+    if (type != TYPE_ARRAY && type != TYPE_OBJECT)
+        throw JSONException("JSON: not array or object when calling begin() method.");
+
+    Pos pos = ptr_begin + 1;
+    checkPos(pos);
+    if (*pos == '}' || *pos == ']')
+        return end();
+
+    return JSON(pos, ptr_end, level + 1);
+}
+
+JSON::iterator JSON::iterator::end() const
+{
+    return JSON(nullptr, ptr_end, level + 1);
+}
+
+JSON::iterator & JSON::iterator::operator++()
+{
+    Pos pos = skipElement();
+    checkPos(pos);
+
+    if (*pos != ',')
+        ptr_begin = nullptr;
+    else
+    {
+        ++pos;
+        checkPos(pos);
+        ptr_begin = pos;
+    }
+
+    return *this;
+}
+
+JSON::iterator JSON::iterator::operator++(int) // NOLINT
+{
+    iterator copy(*this);
+    ++*this;
+    return copy;
+}
+
+template <>
+double JSON::get<double>() const
+{
+    return getDouble();
+}
+
+template <>
+std::string JSON::get<std::string>() const
+{
+    return getString();
+}
+
+template <>
+Int64 JSON::get<Int64>() const
+{
+    return getInt();
+}
+
+template <>
+UInt64 JSON::get<UInt64>() const
+{
+    return getUInt();
+}
+
+template <>
+bool JSON::get<bool>() const
+{
+    return getBool();
+}
+
+template <>
+bool JSON::isType<std::string>() const
+{
+    return isString();
+}
+
+template <>
+bool JSON::isType<UInt64>() const
+{
+    return isNumber();
+}
+
+template <>
+bool JSON::isType<Int64>() const
+{
+    return isNumber();
+}
+
+template <>
+bool JSON::isType<bool>() const
+{
+    return isBool();
+}

+ 215 - 0
contrib/clickhouse/base/base/JSON.h

@@ -0,0 +1,215 @@
+#pragma once
+
+#include <typeinfo>
+#include <Poco/Exception.h>
+#include <base/StringRef.h>
+#include <base/types.h>
+
+
+/** Очень простой класс для чтения JSON (или его кусочков).
+  * Представляет собой ссылку на кусок памяти, в котором содержится JSON (или его кусочек).
+  * Не создаёт никаких структур данных в оперативке. Не выделяет память (кроме std::string).
+  * Не парсит JSON до конца (парсит только часть, необходимую для выполнения вызванного метода).
+  * Парсинг необходимой части запускается каждый раз при вызове методов.
+  * Может работать с обрезанным JSON-ом.
+  * При этом, (в отличие от SAX-подобных парсеров), предоставляет удобные методы для работы.
+  *
+  * Эта структура данных более оптимальна, если нужно доставать несколько элементов из большого количества маленьких JSON-ов.
+  * То есть, подходит для обработки "параметров визитов" и "параметров интернет магазинов" в Яндекс.Метрике.
+  * Если нужно много работать с одним большим JSON-ом, то этот класс может быть менее оптимальным.
+  *
+  * Имеются следующие соглашения:
+  * 1. Предполагается, что в JSON-е нет пробельных символов.
+  * 2. Предполагается, что строки в JSON в кодировке UTF-8; также могут использоваться \u-последовательности.
+  *    Строки возвращаются в кодировке UTF-8, \u-последовательности переводятся в UTF-8.
+  * 3. Но суррогатная пара из двух \uXXXX\uYYYY переводится не в UTF-8, а в CESU-8.
+  * 4. Корректный JSON парсится корректно.
+  *    При работе с некорректным JSON-ом, кидается исключение или возвращаются неверные результаты.
+  *    (пример: считается, что если встретился символ 'n', то после него идёт 'ull' (null);
+  *     если после него идёт ',1,', то исключение не кидается, и, таким образом, возвращается неверный результат)
+  * 5. Глубина вложенности JSON ограничена (см. MAX_JSON_DEPTH в cpp файле).
+  *    При необходимости спуститься на большую глубину, кидается исключение.
+  * 6. В отличие от JSON, пользоволяет парсить значения вида 64-битное число, со знаком, или без.
+  *    При этом, если число дробное - то дробная часть тихо отбрасывается.
+  * 7. Числа с плавающей запятой парсятся не с максимальной точностью.
+  *
+  * Подходит только для чтения JSON, модификация не предусмотрена.
+  * Все методы immutable, кроме operator++.
+  */
+
+
+// NOLINTBEGIN(google-explicit-constructor)
+#ifdef __clang__
+#  pragma clang diagnostic push
+#  pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec"
+#endif
+POCO_DECLARE_EXCEPTION(Foundation_API, JSONException, Poco::Exception)
+#ifdef __clang__
+#  pragma clang diagnostic pop
+#endif
+// NOLINTEND(google-explicit-constructor)
+
+class JSON
+{
+private:
+    using Pos = const char *;
+    Pos ptr_begin;
+    Pos ptr_end;
+    unsigned level;
+
+public:
+    JSON(Pos ptr_begin_, Pos ptr_end_, unsigned level_ = 0) : ptr_begin(ptr_begin_), ptr_end(ptr_end_), level(level_)
+    {
+        checkInit();
+    }
+
+    explicit JSON(std::string_view s) : ptr_begin(s.data()), ptr_end(s.data() + s.size()), level(0)
+    {
+        checkInit();
+    }
+
+    JSON(const JSON & rhs)
+    {
+        *this = rhs;
+    }
+
+    JSON & operator=(const JSON & rhs) = default;
+
+    const char * data() const { return ptr_begin; }
+    const char * dataEnd() const { return ptr_end; }
+
+    enum ElementType
+    {
+        TYPE_OBJECT,
+        TYPE_ARRAY,
+        TYPE_NUMBER,
+        TYPE_STRING,
+        TYPE_BOOL,
+        TYPE_NULL,
+        TYPE_NAME_VALUE_PAIR,
+        TYPE_NOTYPE,
+    };
+
+    ElementType getType() const;
+
+    bool isObject() const        { return getType() == TYPE_OBJECT; }
+    bool isArray() const         { return getType() == TYPE_ARRAY; }
+    bool isNumber() const        { return getType() == TYPE_NUMBER; }
+    bool isString() const        { return getType() == TYPE_STRING; }
+    bool isBool() const          { return getType() == TYPE_BOOL; }
+    bool isNull() const          { return getType() == TYPE_NULL; }
+    bool isNameValuePair() const { return getType() == TYPE_NAME_VALUE_PAIR; }
+
+    /// Количество элементов в массиве или объекте; если элемент - не массив или объект, то исключение.
+    size_t size() const;
+
+    /// Является ли массив или объект пустыми; если элемент - не массив или объект, то исключение.
+    bool empty() const;
+
+    /// Получить элемент массива по индексу; если элемент - не массив, то исключение.
+    JSON operator[] (size_t n) const;
+
+    /// Получить элемент объекта по имени; если элемент - не объект, то исключение.
+    JSON operator[] (const std::string & name) const;
+
+    /// Есть ли в объекте элемент с заданным именем; если элемент - не объект, то исключение.
+    bool has(const std::string & name) const { return has(name.data(), name.size()); }
+    bool has(const char * data, size_t size) const;
+
+    /// Получить значение элемента; исключение, если элемент имеет неправильный тип.
+    template <class T>
+    T get() const;
+
+    /// если значения нет, или тип неверный, то возвращает дефолтное значение
+    template <class T>
+    T getWithDefault(const std::string & key, const T & default_ = T()) const;
+
+    double      getDouble() const;
+    Int64       getInt() const;    /// Отбросить дробную часть.
+    UInt64      getUInt() const;    /// Отбросить дробную часть. Если число отрицательное - исключение.
+    std::string getString() const;
+    bool        getBool() const;
+    std::string getName() const;    /// Получить имя name-value пары.
+    JSON        getValue() const;    /// Получить значение name-value пары.
+
+    std::string_view getRawString() const;
+    std::string_view getRawName() const;
+
+    /// Получить значение элемента; если элемент - строка, то распарсить значение из строки; если не строка или число - то исключение.
+    double      toDouble() const;
+    Int64       toInt() const;
+    UInt64      toUInt() const;
+
+    /** Преобразовать любой элемент в строку.
+      * Для строки возвращается её значение, для всех остальных элементов - сериализованное представление.
+      */
+    std::string toString() const;
+
+    /// Класс JSON одновременно является итератором по самому себе.
+    using iterator = JSON;
+    using const_iterator = JSON;
+
+    iterator operator* () const { return *this; }
+    const JSON * operator-> () const { return this; }
+    bool operator== (const JSON & rhs) const { return ptr_begin == rhs.ptr_begin; }
+    bool operator!= (const JSON & rhs) const { return ptr_begin != rhs.ptr_begin; }
+
+    /** Если элемент - массив или объект, то begin() возвращает iterator,
+      * который указывает на первый элемент массива или первую name-value пару объекта.
+      */
+    iterator begin() const;
+
+    /** end() - значение, которое нельзя использовать; сигнализирует о том, что элементы закончились.
+      */
+    iterator end() const;
+
+    /// Перейти к следующему элементу массива или следующей name-value паре объекта.
+    iterator & operator++();
+    iterator operator++(int); // NOLINT(cert-dcl21-cpp)
+
+    /// Есть ли в строке escape-последовательности
+    bool hasEscapes() const;
+
+    /// Есть ли в строке спец-символы из набора \, ', \0, \b, \f, \r, \n, \t, возможно, заэскейпленные.
+    bool hasSpecialChars() const;
+
+private:
+    /// Проверить глубину рекурсии, а также корректность диапазона памяти.
+    void checkInit() const;
+    /// Проверить, что pos лежит внутри диапазона памяти.
+    void checkPos(Pos pos) const;
+
+    /// Вернуть позицию после заданного элемента.
+    Pos skipString() const;
+    Pos skipNumber() const;
+    Pos skipBool() const;
+    Pos skipNull() const;
+    Pos skipNameValuePair() const;
+    Pos skipObject() const;
+    Pos skipArray() const;
+
+    Pos skipElement() const;
+
+    /// Найти name-value пару с заданным именем в объекте.
+    Pos searchField(const std::string & name) const { return searchField(name.data(), name.size()); }
+    Pos searchField(const char * data, size_t size) const;
+
+    template <class T>
+    bool isType() const;
+};
+
+template <class T>
+T JSON::getWithDefault(const std::string & key, const T & default_) const
+{
+    if (has(key))
+    {
+        JSON key_json = (*this)[key];
+
+        if (key_json.isType<T>())
+            return key_json.get<T>();
+        else
+            return default_;
+    }
+    else
+        return default_;
+}

Some files were not shown because too many files changed in this diff