diff options
author | max42 <max42@yandex-team.com> | 2023-06-30 03:37:03 +0300 |
---|---|---|
committer | max42 <max42@yandex-team.com> | 2023-06-30 03:37:03 +0300 |
commit | fac2bd72b4b31ec3238292caf8fb2a8aaa6d6c4a (patch) | |
tree | b8cbc1deb00309c7f1a7ab6df520a76cf0b5c6d7 /library/cpp/type_info | |
parent | 7bf166b1a7ed0af927f230022b245af618e998c1 (diff) | |
download | ydb-fac2bd72b4b31ec3238292caf8fb2a8aaa6d6c4a.tar.gz |
YT-19324: move YT provider to ydb/library/yql
This commit is formed by the following script: https://paste.yandex-team.ru/6f92e4b8-efc5-4d34-948b-15ee2accd7e7/text.
This commit has zero effect on all projects that depend on YQL.
The summary of changes:
- `yql/providers/yt -> ydb/library/yql/providers/yt `- the whole implementation of YT provider is moved into YDB code base for further export as a part of YT YQL plugin shared library;
- `yql/providers/stat/{expr_nodes,uploader} -> ydb/library/yql/providers/stat/{expr_nodes,uploader}` - a small interface without implementation and the description of stat expr nodes;
- `yql/core/extract_predicate/ut -> ydb/library/yql/core/extract_predicate/ut`;
- `yql/core/{ut,ut_common} -> ydb/library/yql/core/{ut,ut_common}`;
- `yql/core` is gone;
- `yql/library/url_preprocessing -> ydb/library/yql/core/url_preprocessing`.
**NB**: all new targets inside `ydb/` are under `IF (NOT CMAKE_EXPORT)` clause which disables them from open-source cmake generation and ya make build. They will be enabled in the subsequent commits.
Diffstat (limited to 'library/cpp/type_info')
40 files changed, 12487 insertions, 0 deletions
diff --git a/library/cpp/type_info/Readme.md b/library/cpp/type_info/Readme.md new file mode 100644 index 0000000000..e4501c8c11 --- /dev/null +++ b/library/cpp/type_info/Readme.md @@ -0,0 +1,9 @@ +# TI — unified in-memory representation for Common Yandex Typesystem + +Common Yandex Typesystem is a type system used across data storage and processing technologies developed by Yandex. A list of systems that support it includes YT, YDB, RTMR, YQL and others. + +The Type Info library provides classes that represent types from Common Yandex Typesystem. It allows constructing in-memory representations of types, inspecting said representations, combining them to derive new types, as well as serializing and deserializing them. + +For those familiar with Protobuf, Type Info implements the concept of message descriptors for Yandex type system. + +Here you can find open issues: https://st.yandex-team.ru/YT/order:updated:false/filter?tags=type_info&resolution=empty()
\ No newline at end of file diff --git a/library/cpp/type_info/builder.cpp b/library/cpp/type_info/builder.cpp new file mode 100644 index 0000000000..008e0af754 --- /dev/null +++ b/library/cpp/type_info/builder.cpp @@ -0,0 +1,458 @@ +#include "builder.h" + +#include "type_factory.h" + +namespace NTi { + TStructBuilderRaw::TStructBuilderRaw(IPoolTypeFactory& factory) noexcept + : Factory_(&factory) + { + } + + TStructBuilderRaw::TStructBuilderRaw(IPoolTypeFactory& factory, TStructTypePtr prototype) noexcept + : TStructBuilderRaw(factory, prototype.Get()) + { + } + + TStructBuilderRaw::TStructBuilderRaw(IPoolTypeFactory& factory, const TStructType* prototype) noexcept + : TStructBuilderRaw(factory) + { + Members_.reserve(prototype->GetMembers().size()); + + if (prototype->GetFactory() == Factory_) { + // members are in the same factory -- can reuse them. + for (auto member : prototype->GetMembers()) { + Members_.push_back(member); + } + } else { + // members are in a different factory -- should copy them. + for (auto member : prototype->GetMembers()) { + AddMember(member.GetName(), member.GetTypeRaw()); + } + } + } + + TStructBuilderRaw& TStructBuilderRaw::SetName(TMaybe<TStringBuf> name) & noexcept { + Name_ = Factory_->AllocateStringMaybe(name); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::SetName(TMaybe<TStringBuf> name) && noexcept { + return std::move(SetName(name)); + } + + bool TStructBuilderRaw::HasName() const noexcept { + return Name_.Defined(); + } + + TMaybe<TStringBuf> TStructBuilderRaw::GetName() const noexcept { + return Name_; + } + + TStructBuilderRaw& TStructBuilderRaw::Reserve(size_t size) & noexcept { + Members_.reserve(size); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::Reserve(size_t size) && noexcept { + return std::move(Reserve(size)); + } + + TStructBuilderRaw& TStructBuilderRaw::AddMember(TStringBuf name, TTypePtr type) & noexcept { + return AddMember(name, type.Get()); + } + + TStructBuilderRaw TStructBuilderRaw::AddMember(TStringBuf name, TTypePtr type) && noexcept { + return std::move(AddMember(name, type)); + } + + TStructBuilderRaw& TStructBuilderRaw::AddMember(TStringBuf name, const TType* type) & noexcept { + Members_.emplace_back(Factory_->AllocateString(name), Factory_->Own(type)); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::AddMember(TStringBuf name, const TType* type) && noexcept { + return std::move(AddMember(name, type)); + } + + TStructBuilderRaw& TStructBuilderRaw::AddMemberName(TStringBuf name) & noexcept { + PendingMemberName_ = Factory_->AllocateString(name); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::AddMemberName(TStringBuf name) && noexcept { + return std::move(AddMemberName(name)); + } + + TStructBuilderRaw& TStructBuilderRaw::DiscardMemberName() & noexcept { + PendingMemberName_.Clear(); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::DiscardMemberName() && noexcept { + return std::move(DiscardMemberName()); + } + + bool TStructBuilderRaw::HasMemberName() const noexcept { + return PendingMemberName_.Defined(); + } + + TMaybe<TStringBuf> TStructBuilderRaw::GetMemberName() const noexcept { + return PendingMemberName_; + } + + TStructBuilderRaw& TStructBuilderRaw::AddMemberType(TTypePtr type) & noexcept { + return AddMemberType(type.Get()); + } + + TStructBuilderRaw TStructBuilderRaw::AddMemberType(TTypePtr type) && noexcept { + return std::move(AddMemberType(type)); + } + + TStructBuilderRaw& TStructBuilderRaw::AddMemberType(const TType* type) & noexcept { + PendingMemberType_ = Factory_->Own(type); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::AddMemberType(const TType* type) && noexcept { + return std::move(AddMemberType(type)); + } + + TStructBuilderRaw& TStructBuilderRaw::DiscardMemberType() & noexcept { + PendingMemberType_.Clear(); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::DiscardMemberType() && noexcept { + return std::move(DiscardMemberType()); + } + + bool TStructBuilderRaw::HasMemberType() const noexcept { + return PendingMemberType_.Defined(); + } + + TMaybe<const TType*> TStructBuilderRaw::GetMemberType() const noexcept { + return PendingMemberType_; + } + + bool TStructBuilderRaw::CanAddMember() const noexcept { + return HasMemberName() && HasMemberType(); + } + + TStructBuilderRaw& TStructBuilderRaw::AddMember() & noexcept { + Y_VERIFY(CanAddMember()); + Members_.emplace_back(*PendingMemberName_, *PendingMemberType_); + DiscardMember(); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::AddMember() && noexcept { + return std::move(AddMember()); + } + + TStructBuilderRaw& TStructBuilderRaw::DiscardMember() & noexcept { + DiscardMemberName(); + DiscardMemberType(); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::DiscardMember() && noexcept { + return std::move(DiscardMember()); + } + + TStructType::TMembers TStructBuilderRaw::GetMembers() const noexcept { + return Members_; + } + + TStructBuilderRaw& TStructBuilderRaw::Reset() & noexcept { + Name_ = {}; + Members_.clear(); + return *this; + } + + TStructBuilderRaw TStructBuilderRaw::Reset() && noexcept { + return std::move(Reset()); + } + + TStructTypePtr TStructBuilderRaw::Build() { + return BuildRaw()->AsPtr(); + } + + const TStructType* TStructBuilderRaw::BuildRaw() { + return DoBuildRaw(Name_); + } + + TVariantTypePtr TStructBuilderRaw::BuildVariant() { + return BuildVariantRaw()->AsPtr(); + } + + const TVariantType* TStructBuilderRaw::BuildVariantRaw() { + return Factory_->New<TVariantType>(Nothing(), Name_, DoBuildRaw(Nothing())); + } + + const TStructType* TStructBuilderRaw::DoBuildRaw(TMaybe<TStringBuf> name) { + auto members = Factory_->NewArray<TStructType::TMember>(Members_.size(), [this](TStructType::TMember* member, size_t i) { + new (member) TStructType::TMember(Members_[i]); + }); + + auto sortedMembersArray = Factory_->AllocateArrayFor<size_t>(Members_.size()); + auto sortedMembers = TArrayRef(sortedMembersArray, members.size()); + TStructType::MakeSortedMembers(members, sortedMembers); + + return Factory_->New<TStructType>(Nothing(), name, members, sortedMembers); + } + + TTupleBuilderRaw::TTupleBuilderRaw(IPoolTypeFactory& factory) noexcept + : Factory_(&factory) + { + } + + TTupleBuilderRaw::TTupleBuilderRaw(IPoolTypeFactory& factory, TTupleTypePtr prototype) noexcept + : TTupleBuilderRaw(factory, prototype.Get()) + { + } + + TTupleBuilderRaw::TTupleBuilderRaw(IPoolTypeFactory& factory, const TTupleType* prototype) noexcept + : TTupleBuilderRaw(factory) + { + Elements_.reserve(prototype->GetElements().size()); + + if (prototype->GetFactory() == Factory_) { + // elements are in the same factory -- can reuse them + for (auto element : prototype->GetElements()) { + Elements_.push_back(element); + } + } else { + // items are in a different factory -- should copy them + for (auto element : prototype->GetElements()) { + AddElement(element.GetTypeRaw()); + } + } + } + + TTupleBuilderRaw& TTupleBuilderRaw::SetName(TMaybe<TStringBuf> name) & noexcept { + Name_ = Factory_->AllocateStringMaybe(name); + return *this; + } + + TTupleBuilderRaw TTupleBuilderRaw::SetName(TMaybe<TStringBuf> name) && noexcept { + return std::move(SetName(name)); + } + + bool TTupleBuilderRaw::HasName() const noexcept { + return Name_.Defined(); + } + + TMaybe<TStringBuf> TTupleBuilderRaw::GetName() const noexcept { + return Name_; + } + + TTupleBuilderRaw& TTupleBuilderRaw::Reserve(size_t size) & noexcept { + Elements_.reserve(size); + return *this; + } + + TTupleBuilderRaw TTupleBuilderRaw::Reserve(size_t size) && noexcept { + return std::move(Reserve(size)); + } + + TTupleBuilderRaw& TTupleBuilderRaw::AddElement(TTypePtr type) & noexcept { + return AddElement(type.Get()); + } + + TTupleBuilderRaw TTupleBuilderRaw::AddElement(TTypePtr type) && noexcept { + return std::move(AddElement(type)); + } + + TTupleBuilderRaw& TTupleBuilderRaw::AddElement(const TType* type) & noexcept { + Elements_.emplace_back(Factory_->Own(type)); + return *this; + } + + TTupleBuilderRaw TTupleBuilderRaw::AddElement(const TType* type) && noexcept { + return std::move(AddElement(type)); + } + + TTupleBuilderRaw& TTupleBuilderRaw::AddElementType(TTypePtr type) & noexcept { + return AddElementType(type.Get()); + } + + TTupleBuilderRaw TTupleBuilderRaw::AddElementType(TTypePtr type) && noexcept { + return std::move(AddElementType(type)); + } + + TTupleBuilderRaw& TTupleBuilderRaw::AddElementType(const TType* type) & noexcept { + PendingElementType_ = Factory_->Own(type); + return *this; + } + + TTupleBuilderRaw TTupleBuilderRaw::AddElementType(const TType* type) && noexcept { + return std::move(AddElementType(type)); + } + + TTupleBuilderRaw& TTupleBuilderRaw::DiscardElementType() & noexcept { + PendingElementType_.Clear(); + return *this; + } + + TTupleBuilderRaw TTupleBuilderRaw::DiscardElementType() && noexcept { + return std::move(DiscardElementType()); + } + + bool TTupleBuilderRaw::HasElementType() const noexcept { + return PendingElementType_.Defined(); + } + + TMaybe<const TType*> TTupleBuilderRaw::GetElementType() const noexcept { + return PendingElementType_; + } + + bool TTupleBuilderRaw::CanAddElement() const noexcept { + return HasElementType(); + } + + TTupleBuilderRaw& TTupleBuilderRaw::AddElement() & noexcept { + Y_VERIFY(CanAddElement()); + Elements_.emplace_back(*PendingElementType_); + DiscardElement(); + return *this; + } + + TTupleBuilderRaw TTupleBuilderRaw::AddElement() && noexcept { + return std::move(AddElement()); + } + + TTupleBuilderRaw& TTupleBuilderRaw::DiscardElement() & noexcept { + DiscardElementType(); + return *this; + } + + TTupleBuilderRaw TTupleBuilderRaw::DiscardElement() && noexcept { + return std::move(DiscardElement()); + } + + TTupleBuilderRaw& TTupleBuilderRaw::Reset() & noexcept { + Name_ = {}; + Elements_.clear(); + return *this; + } + + TTupleType::TElements TTupleBuilderRaw::GetElements() const noexcept { + return Elements_; + } + + TTupleBuilderRaw TTupleBuilderRaw::Reset() && noexcept { + return std::move(Reset()); + } + + TTupleTypePtr TTupleBuilderRaw::Build() { + return BuildRaw()->AsPtr(); + } + + const TTupleType* TTupleBuilderRaw::BuildRaw() { + return DoBuildRaw(Name_); + } + + TVariantTypePtr TTupleBuilderRaw::BuildVariant() { + return BuildVariantRaw()->AsPtr(); + } + + const TVariantType* TTupleBuilderRaw::BuildVariantRaw() { + return Factory_->New<TVariantType>(Nothing(), Name_, DoBuildRaw(Nothing())); + } + + const TTupleType* TTupleBuilderRaw::DoBuildRaw(TMaybe<TStringBuf> name) { + auto items = Factory_->NewArray<TTupleType::TElement>(Elements_.size(), [this](TTupleType::TElement* element, size_t i) { + new (element) TTupleType::TElement(Elements_[i]); + }); + + return Factory_->New<TTupleType>(Nothing(), name, items); + } + + TTaggedBuilderRaw::TTaggedBuilderRaw(IPoolTypeFactory& factory) noexcept + : Factory_(&factory) + { + } + + TTaggedBuilderRaw& TTaggedBuilderRaw::SetTag(TStringBuf tag) & noexcept { + Tag_ = Factory_->AllocateString(tag); + return *this; + } + + TTaggedBuilderRaw TTaggedBuilderRaw::SetTag(TStringBuf tag) && noexcept { + return std::move(SetTag(tag)); + } + + TTaggedBuilderRaw& TTaggedBuilderRaw::DiscardTag() & noexcept { + Tag_.Clear(); + return *this; + } + + TTaggedBuilderRaw TTaggedBuilderRaw::DiscardTag() && noexcept { + return std::move(DiscardTag()); + } + + bool TTaggedBuilderRaw::HasTag() const noexcept { + return Tag_.Defined(); + } + + TMaybe<TStringBuf> TTaggedBuilderRaw::GetTag() const noexcept { + return Tag_; + } + + TTaggedBuilderRaw& TTaggedBuilderRaw::SetItem(TTypePtr type) & noexcept { + return SetItem(type.Get()); + } + + TTaggedBuilderRaw TTaggedBuilderRaw::SetItem(TTypePtr type) && noexcept { + return std::move(SetItem(std::move(type))); + } + + TTaggedBuilderRaw& TTaggedBuilderRaw::SetItem(const TType* type) & noexcept { + Item_ = Factory_->Own(type); + return *this; + } + + TTaggedBuilderRaw TTaggedBuilderRaw::SetItem(const TType* type) && noexcept { + return std::move(SetItem(type)); + } + + TTaggedBuilderRaw& TTaggedBuilderRaw::DiscardItem() & noexcept { + Item_.Clear(); + return *this; + } + + TTaggedBuilderRaw TTaggedBuilderRaw::DiscardItem() && noexcept { + return std::move(DiscardItem()); + } + + bool TTaggedBuilderRaw::HasItem() const noexcept { + return Item_.Defined(); + } + + TMaybe<const TType*> TTaggedBuilderRaw::GetItem() const noexcept { + return Item_; + } + + bool TTaggedBuilderRaw::CanBuild() const noexcept { + return HasTag() && HasItem(); + } + + TTaggedBuilderRaw& TTaggedBuilderRaw::Reset() & noexcept { + DiscardTag(); + DiscardItem(); + return *this; + } + + TTaggedBuilderRaw TTaggedBuilderRaw::Reset() && noexcept { + return std::move(Reset()); + } + + TTaggedTypePtr TTaggedBuilderRaw::Build() { + return BuildRaw()->AsPtr(); + } + + const TTaggedType* TTaggedBuilderRaw::BuildRaw() { + Y_VERIFY(CanBuild()); + return Factory_->New<TTaggedType>(Nothing(), *Item_, *Tag_); + } +} diff --git a/library/cpp/type_info/builder.h b/library/cpp/type_info/builder.h new file mode 100644 index 0000000000..faae45ab51 --- /dev/null +++ b/library/cpp/type_info/builder.h @@ -0,0 +1,346 @@ +#pragma once + +//! @file builder.h +//! +//! Builders help with creating complex types piece-by-piece. + +#include <util/generic/vector.h> + +#include "type.h" + +#include "fwd.h" + +namespace NTi { + /// An interface for building structs using the raw interface (via memory-pool-based factory). + /// + /// This interface allows allocating structs piece-by-piece. You can feed it struct items, one-by-one, and they'll + /// be copied into the memory pool before the struct is created. This way, you don't have to allocate heap memory + /// to temporarily store item names or types. + /// + /// Note: this builder doesn't own the underlying factory. + class TStructBuilderRaw { + public: + /// Create a new builder with the given factory. + TStructBuilderRaw(IPoolTypeFactory& factory) noexcept; + + /// Create a new builder with the given factory and add fields from the given struct. + /// Note that struct name is not copied to the builder. + TStructBuilderRaw(IPoolTypeFactory& factory, TStructTypePtr prototype) noexcept; + TStructBuilderRaw(IPoolTypeFactory& factory, const TStructType* prototype) noexcept; + + /// Set a new struct name. + /// + /// Note that the name is copied to the factory right away. If you call this function twice, second call will + /// overwrite name from the first call, but will not remove it from the memory pool. + TStructBuilderRaw& SetName(TMaybe<TStringBuf> name) & noexcept; + TStructBuilderRaw SetName(TMaybe<TStringBuf> name) && noexcept; + + /// Check if there's a struct name set. + bool HasName() const noexcept; + + /// Get a struct name. + /// + /// Name was copied to the factory's memory pool and will live as long as the factory lives. + TMaybe<TStringBuf> GetName() const noexcept; + + /// Reserve some place in the underlying vector that collects struct items. + TStructBuilderRaw& Reserve(size_t size) & noexcept; + TStructBuilderRaw Reserve(size_t size) && noexcept; + + /// Append a new struct member. + TStructBuilderRaw& AddMember(TStringBuf name, TTypePtr type) & noexcept; + TStructBuilderRaw AddMember(TStringBuf name, TTypePtr type) && noexcept; + TStructBuilderRaw& AddMember(TStringBuf name, const TType* type) & noexcept; + TStructBuilderRaw AddMember(TStringBuf name, const TType* type) && noexcept; + + /// Partial member creation interface. + /// + /// This interface allows building individual struct items piece-by-piece. You can pass member's name + /// and copy it to the pool without passing its type. For example: + /// + /// ``` + /// auto builder = TStructBuilderRaw(factory); + /// builder.AddMemberName("name"); // add name for a new member. + /// builder.AddMemberType(type); // add type for a new member. + /// builder.AddMember(); // use added name and type to construct and append an member. + /// ``` + /// + /// This interface is useful when you can't store member name for a long time. For example, if you building + /// a parser, at some point have an member name, but you don't have an member type yet. Then you can add member name + /// when you have it, and add member type later. + /// + /// @{ + //- + /// Set name for pending member. + /// + /// Note that the name is copied to the factory right away. If you call this function twice, second call will + /// overwrite name from the first call, but will not remove it from the memory pool. + TStructBuilderRaw& AddMemberName(TStringBuf name) & noexcept; + TStructBuilderRaw AddMemberName(TStringBuf name) && noexcept; + + /// Unset name for pending member. + /// + /// Note that member name is copied to the factory right away. If you discard it via this method, + /// it will not be removed from the memory pool. + TStructBuilderRaw& DiscardMemberName() & noexcept; + TStructBuilderRaw DiscardMemberName() && noexcept; + + /// Check if there's a name set for pending member. + bool HasMemberName() const noexcept; + + /// Get name for pending member. + /// + /// Name was copied to the factory's memory pool and will live as long as the factory lives. + TMaybe<TStringBuf> GetMemberName() const noexcept; + + /// Set type for pending member. + /// + /// Note that the type is copied to the factory right away. If you call this function twice, second call will + /// overwrite type from the first call, but will not remove it from the memory pool. + TStructBuilderRaw& AddMemberType(TTypePtr type) & noexcept; + TStructBuilderRaw AddMemberType(TTypePtr type) && noexcept; + TStructBuilderRaw& AddMemberType(const TType* type) & noexcept; + TStructBuilderRaw AddMemberType(const TType* type) && noexcept; + + /// Unset type for pending member. + /// + /// Note that member type is copied to the factory right away. If you discard it via this method, + /// it will not be removed from the memory pool. + TStructBuilderRaw& DiscardMemberType() & noexcept; + TStructBuilderRaw DiscardMemberType() && noexcept; + + /// Check if there's a type set for pending member. + bool HasMemberType() const noexcept; + + /// Get type for pending member. + TMaybe<const TType*> GetMemberType() const noexcept; + + /// Check if both name and type are set for pending member. + bool CanAddMember() const noexcept; + + /// Use data added via `AddMemberName` and `AddMemberType` to construct a new struct member + /// and append it to this builder. This function panics if there's no name or no type set for pending member. + TStructBuilderRaw& AddMember() & noexcept; + TStructBuilderRaw AddMember() && noexcept; + + /// Discard all data added via `AddMemberName` and `AddMemberType` functions. + /// + /// Note that member name and type are copied to the factory right away. If you discard them via this method, + /// they will not be removed from the memory pool. + TStructBuilderRaw& DiscardMember() & noexcept; + TStructBuilderRaw DiscardMember() && noexcept; + + /// @} + + /// Get immutable list of items that've been added to this builder. + /// The returned object is invalidated when this builder dies or when `AddMember` is called. + TStructType::TMembers GetMembers() const noexcept; + + /// Reset struct name and items. + TStructBuilderRaw& Reset() & noexcept; + TStructBuilderRaw Reset() && noexcept; + + /// Create a new struct type using name and items from this builder. + TStructTypePtr Build(); + + /// Like `Build`, but returns a raw pointer. + const TStructType* BuildRaw(); + + /// Create a new variant over struct using items from this builder. + TVariantTypePtr BuildVariant(); + + /// Like `BuildVariant`, but returns a raw pointer. + const TVariantType* BuildVariantRaw(); + + private: + const TStructType* DoBuildRaw(TMaybe<TStringBuf> name); + + private: + IPoolTypeFactory* Factory_; + TMaybe<TStringBuf> Name_; + TVector<TStructType::TMember> Members_; + TMaybe<TStringBuf> PendingMemberName_; + TMaybe<const TType*> PendingMemberType_; + }; + + /// An interface for building tuples using the raw interface (via memory-pool-based factory). + /// + /// Note: this builder doesn't own the underlying factory. + class TTupleBuilderRaw { + public: + /// Create a new builder with the given factory. + TTupleBuilderRaw(IPoolTypeFactory& factory) noexcept; + + /// Create a new builder with the given factory and add fields from the given tuple. + /// Note that tuple name is not copied to the builder. + TTupleBuilderRaw(IPoolTypeFactory& factory, TTupleTypePtr prototype) noexcept; + TTupleBuilderRaw(IPoolTypeFactory& factory, const TTupleType* prototype) noexcept; + + /// Set a new tuple name. + /// Note that the name is copied to the factory right away. If you call this function twice, second call will + /// overwrite name from the first call, but will not remove it from the memory pool. + TTupleBuilderRaw& SetName(TMaybe<TStringBuf> name) & noexcept; + TTupleBuilderRaw SetName(TMaybe<TStringBuf> name) && noexcept; + + /// Check if there's a tuple name set. + bool HasName() const noexcept; + + /// Get a tuple name. + /// + /// Name was copied to the factory's memory pool and will live as long as the factory lives. + TMaybe<TStringBuf> GetName() const noexcept; + + /// Reserve some place in the underlying vector that collects tuple items. + TTupleBuilderRaw& Reserve(size_t size) & noexcept; + TTupleBuilderRaw Reserve(size_t size) && noexcept; + + /// Append a new tuple item. + TTupleBuilderRaw& AddElement(TTypePtr type) & noexcept; + TTupleBuilderRaw AddElement(TTypePtr type) && noexcept; + TTupleBuilderRaw& AddElement(const TType* type) & noexcept; + TTupleBuilderRaw AddElement(const TType* type) && noexcept; + + /// Partial item creation interface. + /// + /// This interface allows building individual tuple items piece-by-piece. It mirrors the same type of interface + /// in the struct builder. + /// + /// @{ + //- + /// Set type for pending item. + /// + /// Note that the type is copied to the factory right away. If you call this function twice, second call will + /// overwrite type from the first call, but will not remove it from the memory pool. + TTupleBuilderRaw& AddElementType(TTypePtr type) & noexcept; + TTupleBuilderRaw AddElementType(TTypePtr type) && noexcept; + TTupleBuilderRaw& AddElementType(const TType* type) & noexcept; + TTupleBuilderRaw AddElementType(const TType* type) && noexcept; + + /// Unset type for pending item. + /// + /// Note that item type is copied to the factory right away. If you discard it via this method, + /// it will not be removed from the memory pool. + TTupleBuilderRaw& DiscardElementType() & noexcept; + TTupleBuilderRaw DiscardElementType() && noexcept; + + /// Check if there's a type set for pending item. + bool HasElementType() const noexcept; + + /// Get type for pending item. + TMaybe<const TType*> GetElementType() const noexcept; + + /// Check if type is set for pending item. + bool CanAddElement() const noexcept; + + /// Use data added via `AddElementType` to construct a new tuple item and append it to this builder. + /// This function panics if there's no type set for pending item. + TTupleBuilderRaw& AddElement() & noexcept; + TTupleBuilderRaw AddElement() && noexcept; + + /// Discard all data added via `AddElementType` function. + /// + /// Note that item type is copied to the factory right away. If you discard it via this method, + /// it will not be removed from the memory pool. + TTupleBuilderRaw& DiscardElement() & noexcept; + TTupleBuilderRaw DiscardElement() && noexcept; + + /// @} + + /// Get immutable list of items that've been added to this builder. + /// The returned object is invalidated when this builder dies or when `AddElement` is called. + TTupleType::TElements GetElements() const noexcept; + + /// Reset tuple name and items. + TTupleBuilderRaw& Reset() & noexcept; + TTupleBuilderRaw Reset() && noexcept; + + /// Create a new tuple type using name and items from this builder. + TTupleTypePtr Build(); + + /// Like `Build`, but returns a raw pointer. + const TTupleType* BuildRaw(); + + /// Create a new variant over tuple using items from this builder. + TVariantTypePtr BuildVariant(); + + /// Like `BuildVariant`, but returns a raw pointer. + const TVariantType* BuildVariantRaw(); + + private: + const TTupleType* DoBuildRaw(TMaybe<TStringBuf> name); + + private: + IPoolTypeFactory* Factory_; + TMaybe<TStringBuf> Name_; + TVector<TTupleType::TElement> Elements_; + TMaybe<const TType*> PendingElementType_; + }; + + /// An interface for building tagged types using the raw interface (via memory-pool-based factory). + /// + /// Note: this builder doesn't own the underlying factory. + class TTaggedBuilderRaw { + public: + /// Create a new builder with the given factory. + TTaggedBuilderRaw(IPoolTypeFactory& factory) noexcept; + + /// Set a new tag. + /// + /// Note that the tag is copied to the factory right away. If you call this function twice, second call will + /// overwrite tag from the first call, but will not remove it from the memory pool. + TTaggedBuilderRaw& SetTag(TStringBuf tag) & noexcept; + TTaggedBuilderRaw SetTag(TStringBuf tag) && noexcept; + + /// Unset tag. + /// + /// Note that tag is copied to the factory right away. If you discard it via this method, + /// it will not be removed from the memory pool. + TTaggedBuilderRaw& DiscardTag() & noexcept; + TTaggedBuilderRaw DiscardTag() && noexcept; + + /// Check if a tag is set. + bool HasTag() const noexcept; + + /// Get a tag. + /// + /// The tag was copied to the factory's memory pool and will live as long as the factory lives. + TMaybe<TStringBuf> GetTag() const noexcept; + + /// Set type that's being tagged. + TTaggedBuilderRaw& SetItem(TTypePtr type) & noexcept; + TTaggedBuilderRaw SetItem(TTypePtr type) && noexcept; + TTaggedBuilderRaw& SetItem(const TType* type) & noexcept; + TTaggedBuilderRaw SetItem(const TType* type) && noexcept; + + /// Unset item type. + /// + /// Note that item type is copied to the factory right away. If you discard it via this method, + /// it will not be removed from the memory pool. + TTaggedBuilderRaw& DiscardItem() & noexcept; + TTaggedBuilderRaw DiscardItem() && noexcept; + + /// Check if there's an item type set. + bool HasItem() const noexcept; + + /// Get item type. + TMaybe<const TType*> GetItem() const noexcept; + + /// Check if there's both name and item type set. + bool CanBuild() const noexcept; + + /// Discard both tag and item. + TTaggedBuilderRaw& Reset() & noexcept; + TTaggedBuilderRaw Reset() && noexcept; + + /// Create a new tagged type using name and item from this builder. + TTaggedTypePtr Build(); + + /// Like `Build`, but returns a raw pointer. + const TTaggedType* BuildRaw(); + + private: + IPoolTypeFactory* Factory_; + TMaybe<TStringBuf> Tag_; + TMaybe<const TType*> Item_; + }; +} diff --git a/library/cpp/type_info/error.cpp b/library/cpp/type_info/error.cpp new file mode 100644 index 0000000000..fe9ecf1d9f --- /dev/null +++ b/library/cpp/type_info/error.cpp @@ -0,0 +1 @@ +#include "error.h" diff --git a/library/cpp/type_info/error.h b/library/cpp/type_info/error.h new file mode 100644 index 0000000000..26fd8853f9 --- /dev/null +++ b/library/cpp/type_info/error.h @@ -0,0 +1,33 @@ +#pragma once + +//! @file error.h +//! +//! All error classes that one can encounter when working with type info library. + +#include <util/generic/yexception.h> + +namespace NTi { + /// Base class for all exceptions that arise when working with Type Info library. + class TException: public yexception { + }; + + /// Type Info API used in an unintended way. + class TApiException: public TException { + }; + + /// Attempting to create an illegal type. + /// + /// For example, this exception is raised when attempting to create a struct with non-unique item names. + class TIllegalTypeException: public TException { + }; + + /// Type deserializer got an invalid input. + /// + /// See `TType::Serialize()` and `TType::Deserialize()` for more info on type serialization/deserialization. + class TDeserializationException: public TException { + }; + + /// No such item in type. + class TItemNotFound: public TException { + }; +} diff --git a/library/cpp/type_info/fwd.h b/library/cpp/type_info/fwd.h new file mode 100644 index 0000000000..644932f677 --- /dev/null +++ b/library/cpp/type_info/fwd.h @@ -0,0 +1,123 @@ +#pragma once + +#include <util/generic/fwd.h> + +namespace NTi { + class ITypeFactoryInternal; + + class TNamedTypeBuilderRaw; + class TStructBuilderRaw; + class TTupleBuilderRaw; + class TTaggedBuilderRaw; + + class ITypeFactory; + using ITypeFactoryPtr = TIntrusivePtr<ITypeFactory>; + + class IPoolTypeFactory; + using IPoolTypeFactoryPtr = TIntrusivePtr<IPoolTypeFactory>; + + class TType; + using TTypePtr = TIntrusiveConstPtr<TType>; + + class TVoidType; + using TVoidTypePtr = TIntrusiveConstPtr<TVoidType>; + + class TNullType; + using TNullTypePtr = TIntrusiveConstPtr<TNullType>; + + class TPrimitiveType; + using TPrimitiveTypePtr = TIntrusiveConstPtr<TPrimitiveType>; + + class TBoolType; + using TBoolTypePtr = TIntrusiveConstPtr<TBoolType>; + + class TInt8Type; + using TInt8TypePtr = TIntrusiveConstPtr<TInt8Type>; + + class TInt16Type; + using TInt16TypePtr = TIntrusiveConstPtr<TInt16Type>; + + class TInt32Type; + using TInt32TypePtr = TIntrusiveConstPtr<TInt32Type>; + + class TInt64Type; + using TInt64TypePtr = TIntrusiveConstPtr<TInt64Type>; + + class TUint8Type; + using TUint8TypePtr = TIntrusiveConstPtr<TUint8Type>; + + class TUint16Type; + using TUint16TypePtr = TIntrusiveConstPtr<TUint16Type>; + + class TUint32Type; + using TUint32TypePtr = TIntrusiveConstPtr<TUint32Type>; + + class TUint64Type; + using TUint64TypePtr = TIntrusiveConstPtr<TUint64Type>; + + class TFloatType; + using TFloatTypePtr = TIntrusiveConstPtr<TFloatType>; + + class TDoubleType; + using TDoubleTypePtr = TIntrusiveConstPtr<TDoubleType>; + + class TStringType; + using TStringTypePtr = TIntrusiveConstPtr<TStringType>; + + class TUtf8Type; + using TUtf8TypePtr = TIntrusiveConstPtr<TUtf8Type>; + + class TDateType; + using TDateTypePtr = TIntrusiveConstPtr<TDateType>; + + class TDatetimeType; + using TDatetimeTypePtr = TIntrusiveConstPtr<TDatetimeType>; + + class TTimestampType; + using TTimestampTypePtr = TIntrusiveConstPtr<TTimestampType>; + + class TTzDateType; + using TTzDateTypePtr = TIntrusiveConstPtr<TTzDateType>; + + class TTzDatetimeType; + using TTzDatetimeTypePtr = TIntrusiveConstPtr<TTzDatetimeType>; + + class TTzTimestampType; + using TTzTimestampTypePtr = TIntrusiveConstPtr<TTzTimestampType>; + + class TIntervalType; + using TIntervalTypePtr = TIntrusiveConstPtr<TIntervalType>; + + class TDecimalType; + using TDecimalTypePtr = TIntrusiveConstPtr<TDecimalType>; + + class TJsonType; + using TJsonTypePtr = TIntrusiveConstPtr<TJsonType>; + + class TYsonType; + using TYsonTypePtr = TIntrusiveConstPtr<TYsonType>; + + class TUuidType; + using TUuidTypePtr = TIntrusiveConstPtr<TUuidType>; + + class TOptionalType; + using TOptionalTypePtr = TIntrusiveConstPtr<TOptionalType>; + + class TListType; + using TListTypePtr = TIntrusiveConstPtr<TListType>; + + class TDictType; + using TDictTypePtr = TIntrusiveConstPtr<TDictType>; + + class TStructType; + using TStructTypePtr = TIntrusiveConstPtr<TStructType>; + + class TTupleType; + using TTupleTypePtr = TIntrusiveConstPtr<TTupleType>; + + class TVariantType; + using TVariantTypePtr = TIntrusiveConstPtr<TVariantType>; + + class TTaggedType; + using TTaggedTypePtr = TIntrusiveConstPtr<TTaggedType>; +} diff --git a/library/cpp/type_info/test-data/bad-types.txt b/library/cpp/type_info/test-data/bad-types.txt new file mode 100644 index 0000000000..c8f3120f63 --- /dev/null +++ b/library/cpp/type_info/test-data/bad-types.txt @@ -0,0 +1,229 @@ +# +# The format of this file is described in README.txt +# +# Each test case contains 3 fields: +# - Yson representation (that cannot be parsed to type). +# - Expected error description. +# - Path in yson that we expect to see in error. +# +# Suggested approach to writing test: +# 1. Try to deserialize type from yson (1st field). +# 2. Ensure that error is raised. +# 3. (Optionally) ensure that error description matches (contains) 2nd field. +# 4. (Optionally) ensure that error description contains path from 3rd field + +5 :: type must be either a string or a map :: ;; + +"" :: unknown type "" :: ;; + +"int" :: unknown type "int" :: ;; + +# Type names must be in lowercase. +"Int32" :: unknown type "Int32" :: ;; + +{typename=int32} :: missing required key "type_name" :: ;; + +{type_name=5} :: "type_name" must contain a string :: ;; + +# +# Decimal +# + +{ + type_name=decimal; + precision=3; +} :: missing required key "scale" :: ;; + +{ + type_name=decimal; + scale=3; +} :: missing required key "precision" :: ;; + +# +# Optional +# + +{ + type_name=optional; +} :: missing required key "item" :: ;; + +{ + item=int32; +} :: missing required key "type_name" :: ;; + +# +# List +# + +{ + type_name=list; +} :: missing required key "item" :: ;; + +# +# Struct +# + +{ + type_name=struct; +} :: missing required key "members" :: ;; + +{ + type_name=struct; + members=5; +} :: "members" must contain a list :: ;; + +{ + type_name=struct; + members=[ + {name=foo; type=int32;}; + 5; + ]; +} :: "members" must contain a list of maps :: ;; + +{ + type_name=struct; + members=[ + {type=int32;}; + ]; +} :: missing required key "name" :: ;; + +{ + type_name=struct; + members=[ + {name=foo;}; + ]; +} :: missing required key "type" :: ;; + +{ + type_name=struct; + members=[ + {name=foo; type=int32}; + {name=bar; type=5}; + ]; +} :: type must be either a string or a map :: /members/1/type ;; + +{ + type_name=struct; + members=[ + {name=4; type=int32}; + ]; +} :: "name" must contain a string :: ;; + +{ + type_name=struct; + elements=[ + {name=4; type=int32}; + ]; +} :: missing required key "members" :: ;; + +# +# Tuple +# + +{ + type_name=tuple; +} :: missing required key "elements" :: ;; + +{ + type_name=tuple; + elements=5; +} :: "elements" must contain a list :: ;; + +{ + type_name=tuple; + elements=[ + {type=int32;}; + 5; + ]; +} :: "elements" must contain a list of maps :: ;; + +{ + type_name=tuple; + elements=[ + {}; + ]; +} :: missing required key "type" :: /elements/0 ;; + +{ + type_name=tuple; + elements=[ + {type=5}; + ]; +} :: type must be either a string or a map :: /elements/1/type ;; + +{ + type_name=tuple; + members=[ + {name=foo; type=int32}; + ]; +} :: missing required key "elements" :: ;; + +# +# Variant +# + +# We don't specify exception message here because in C++ library that message is not good and we don't want to canonize it +# Though fixing it is not that easy too. +{ + type_name=variant; + members=[ + {name=foo; type=int32}; + ]; + elements=[ + {type=int8}; + ] +} :: :: ;; + +{ + type_name=variant; +} :: missing both keys "members" and "elements" :: ;; + +# +# Dict +# +{ + type_name=dict; + key=string; +} :: missing required key "value" :: ;; + +{ + type_name=dict; + value=string; +} :: missing required key "key" :: ;; + +{ + type_name=dict; + value=string; +} :: missing required key "key" :: ;; + +{ + type_name=dict; + key=5; + value=string; +} :: type must be either a string or a map :: /key ;; + +{ + type_name=dict; + key=string; + value=5; +} :: type must be either a string or a map :: /value ;; + +# +# Tagged +# + +{ + type_name=tagged; + item=string; +} :: missing required key "tag" :: ;; + +{ + type_name=tagged; + tag=string; +} :: missing required key "item" :: ;; + +{ + type_name=tagged; + tag=5; + item=string; +} :: "tag" must contain a string :: /tag ;; diff --git a/library/cpp/type_info/test-data/good-types.txt b/library/cpp/type_info/test-data/good-types.txt new file mode 100644 index 0000000000..cb082707b6 --- /dev/null +++ b/library/cpp/type_info/test-data/good-types.txt @@ -0,0 +1,478 @@ +# +# The format of this file is described in README.txt +# +# Each test case contains 2 fields: +# - Yson reporesentation of the type. +# - String representation of the type. +# +# Suggested approach to writing test: +# 1. Try to deserialize type from yson (1st field). +# 2. Check that text representation of parsed type matches 2nd field. +# 3. Serialize type to yson and deserialize it from it. +# 4. Check that typef from 1. and 3. are equal. + +# Integer +int8 :: Int8 ;; +int16 :: Int16 ;; +int32 :: Int32 ;; +int64 :: Int64 ;; +{type_name=int8} :: Int8 ;; +{type_name=int16} :: Int16 ;; +{type_name=int32} :: Int32 ;; +{type_name=int64} :: Int64 ;; + +# Unsigned integer +uint8 :: Uint8 ;; +uint16 :: Uint16 ;; +uint32 :: Uint32 ;; +uint64 :: Uint64 ;; +{type_name=uint8} :: Uint8 ;; +{type_name=uint16} :: Uint16 ;; +{type_name=uint32} :: Uint32 ;; +{type_name=uint64} :: Uint64 ;; + +# Floating +float :: Float ;; +double :: Double ;; +{type_name=float} :: Float ;; +{type_name=double} :: Double ;; + +# Strings +string :: String ;; +utf8 :: Utf8 ;; +{type_name=string} :: String ;; +{type_name=utf8} :: Utf8 ;; + +# Time +date :: Date ;; +datetime :: Datetime ;; +timestamp :: Timestamp ;; +tz_date :: TzDate ;; +tz_datetime :: TzDatetime ;; +tz_timestamp :: TzTimestamp ;; +interval :: Interval ;; +{type_name=date} :: Date ;; +{type_name=datetime} :: Datetime ;; +{type_name=timestamp} :: Timestamp ;; +{type_name=tz_date} :: TzDate ;; +{type_name=tz_datetime} :: TzDatetime ;; +{type_name=tz_timestamp} :: TzTimestamp ;; +{type_name=interval} :: Interval ;; + +# Singular +void :: Void ;; +null :: Null ;; +{type_name=void} :: Void ;; +{type_name=null} :: Null ;; + +# UUID +uuid :: Uuid ;; +{type_name=uuid} :: Uuid ;; + +# Json / Yson +yson :: Yson ;; +json :: Json ;; +{type_name=yson} :: Yson ;; +{type_name=json} :: Json ;; + +{ + type_name=string; + unknown_key=bar; +} :: String ;; + +# Decimal +{ + type_name=decimal; + precision=3; + scale=2; +} :: Decimal(3, 2) ;; + +{ + type_name=decimal; + precision=3; + scale=2; + unknown_column=ha; +} :: Decimal(3, 2) ;; + +# +# Optional +# +{ + type_name=optional; + item=string; +} :: Optional<String> ;; + +{ + type_name=optional; + item={ + type_name=string; + } +} :: Optional<String> ;; + +{ + type_name=optional; + item={ + type_name=optional; + item={ + type_name=list; + item={ + type_name=decimal; + precision=10; + scale=5; + } + + } + } +} :: Optional<Optional<List<Decimal(10, 5)>>> ;; + +{ + type_name=optional; + item={ + type_name=tagged; + item=int32; + tag="foo"; + }; +} :: Optional<Tagged<Int32, 'foo'>> ;; + +{ + type_name=optional; + item={ + type_name=string; + }; + unknown_column=ha; +} :: Optional<String> ;; + +# +# List +# +{ + type_name=list; + item=string; +} :: List<String> ;; + +{ + type_name=list; + item={ + type_name=string; + } +} :: List<String> ;; + +{ + type_name=list; + item={ + type_name=list; + item={ + type_name=optional; + item=string; + } + }; +} :: List<List<Optional<String>>> ;; + +{ + type_name=list; + item={ + type_name=tagged; + item=int32; + tag="foo"; + }; +} :: List<Tagged<Int32, 'foo'>> ;; + +{ + type_name=list; + item={ + type_name=string; + }; + unknown_column=ha; +} :: List<String> ;; + +# +# Dict +# + +{ + type_name=dict; + key=int32; + value=string; +} :: Dict<Int32, String> ;; + +{ + type_name=dict; + key={ + type_name=optional; + item=string; + }; + value={ + type_name=list; + item={ + type_name=int32; + } + }; +} :: Dict<Optional<String>, List<Int32>> ;; + +{ + type_name=dict; + key={ + type_name=tagged; + item=int32; + tag="foo"; + }; + value={ + type_name=tagged; + item=string; + tag="bar"; + }; +} :: Dict<Tagged<Int32, 'foo'>, Tagged<String, 'bar'>> ;; + +{ + type_name=dict; + key=int32; + value=string; + unknown_column=ha; +} :: Dict<Int32, String> ;; + +# +# Struct +# + +{ + type_name=struct; + members=[]; +} :: Struct<> ;; + +{ + type_name=struct; + members=[ + { + name=foo; + type=int32; + }; + { + name=bar; + type={ + type_name=optional; + item=string; + }; + }; + ]; +} :: Struct<'foo': Int32, 'bar': Optional<String>> ;; + +{ + type_name=struct; + members=[ + { + name=foo; + type={ + type_name=tagged; + item=string; + tag=foo; + }; + }; + ]; +} :: Struct<'foo': Tagged<String, 'foo'>> ;; + +{ + type_name=struct; + unknown_column=ha; + members=[ + { + unknown_column=ha; + name=foo; + type=int32; + }; + { + name=bar; + type={ + type_name=optional; + item=string; + }; + }; + ]; +} :: Struct<'foo': Int32, 'bar': Optional<String>> ;; + +# +# Tuple +# + +{ + type_name=tuple; + elements=[]; +} :: Tuple<> ;; + +{ + type_name=tuple; + elements=[ + { + type=int32; + }; + { + type={ + type_name=optional; + item=string; + }; + }; + ]; +} :: Tuple<Int32, Optional<String>> ;; + +{ + type_name=tuple; + elements=[ + { + type={ + type_name=tagged; + item=string; + tag=foo; + }; + }; + ]; +} :: Tuple<Tagged<String, 'foo'>> ;; + +{ + type_name=tuple; + unknown_column=ha; + elements=[ + { + type=int32; + unknown_column=ha; + }; + { + type={ + type_name=optional; + item=string; + }; + }; + ]; +} :: Tuple<Int32, Optional<String>> ;; + +# +# Variant +# + +{ + type_name=variant; + elements=[ + { + type=int32; + }; + { + type={type_name=string}; + }; + ]; +} :: Variant<Int32, String> ;; + +{ + type_name=variant; + members=[ + { + name=foo; + type=int32; + }; + { + name=bar; + type={ + type_name=optional; + item=string; + }; + }; + ]; +} :: Variant<'foo': Int32, 'bar': Optional<String>> ;; + +{ + type_name=variant; + elements=[ + { + type={ + type_name=tagged; + item=string; + tag=foo; + } + }; + ]; +} :: Variant<Tagged<String, 'foo'>> ;; + +{ + type_name=variant; + members=[ + { + name=bar; + type={ + type_name=tagged; + item=string; + tag=foo; + }; + }; + ]; +} :: Variant<'bar': Tagged<String, 'foo'>> ;; + +{ + type_name=variant; + unknown_column=ha; + elements=[ + { + type=int32; + unknown_column=ha; + }; + { + type={ + type_name=string; + unknown_column=ha; + }; + unknown_column=ha; + }; + ]; +} :: Variant<Int32, String> ;; + +{ + type_name=variant; + unknown_column=ha; + members=[ + { + unknown_column=ha; + name=foo; + type=int32; + }; + { + name=bar; + type={ + type_name=optional; + unknown_column=ha; + item=string; + }; + }; + ]; +} :: Variant<'foo': Int32, 'bar': Optional<String>> ;; + +# +# Tagged +# + +{ + type_name=tagged; + item=string; + tag="image/png" +} :: Tagged<String, 'image/png'> ;; + +{ + type_name=tagged; + item={ + type_name=optional; + item=string; + }; + tag="image/png" +} :: Tagged<Optional<String>, 'image/png'> ;; + +{ + type_name=tagged; + item={ + type_name=tagged; + item=string; + tag=foo; + }; + tag=bar; +} :: Tagged<Tagged<String, 'foo'>, 'bar'> ;; + +{ + type_name=tagged; + item=string; + unknown_column=ha; + tag="image/png" +} :: Tagged<String, 'image/png'> ;; diff --git a/library/cpp/type_info/type.cpp b/library/cpp/type_info/type.cpp new file mode 100644 index 0000000000..cee58a0a79 --- /dev/null +++ b/library/cpp/type_info/type.cpp @@ -0,0 +1,1662 @@ +#include "type.h" + +#include "type_factory.h" +#include "type_equivalence.h" + +#include <util/generic/overloaded.h> + +#include <util/digest/murmur.h> +#include <util/generic/hash_set.h> +#include <util/string/escape.h> + +namespace { + inline ui64 Hash(NTi::ETypeName type) { + return IntHash(static_cast<ui64>(type)); + } + + inline ui64 Hash(ui64 value, ui64 seed) { + return MurmurHash(&value, sizeof(value), seed); + } + + inline ui64 Hash(TStringBuf string, ui64 seed) { + seed = ::Hash(string.size(), seed); + return MurmurHash(string.data(), string.size(), seed); + } + + inline ui64 Hash(TMaybe<TStringBuf> string, ui64 seed) { + if (string.Defined()) { + return MurmurHash(string->data(), string->size(), seed); + } else { + return seed; + } + } + + TString Quote(TStringBuf s) { + TString result; + result.push_back('\''); + result += EscapeC(s); + result.push_back('\''); + return result; + } +} + +namespace NTi { + TType::TType(TMaybe<ui64> hash, ETypeName typeName) noexcept + : TypeName_(typeName) + , HasHash_(hash.Defined()) + , Hash_(hash.GetOrElse(0)) + { + } + + ui64 TType::CalculateHash() const noexcept { + return ::Hash(TypeName_); + } + + TMaybe<ui64> TType::GetHashRaw() const noexcept { + if (HasHash_.load(std::memory_order_seq_cst)) { + return Hash_.load(std::memory_order_seq_cst); + } else { + return Nothing(); + } + } + + ui64 TType::GetHash() const { + if (HasHash_.load(std::memory_order_seq_cst)) { + return Hash_.load(std::memory_order_seq_cst); + } else { + ui64 hash = VisitRaw([](const auto* type) { + return type->CalculateHash(); + }); + Hash_.store(hash, std::memory_order_seq_cst); + HasHash_.store(true, std::memory_order_seq_cst); + return hash; + } + } + + TTypePtr TType::StripTags() const noexcept { + return StripTagsRaw()->AsPtr(); + } + + const TType* TType::StripTagsRaw() const noexcept { + auto type = this; + while (type->IsTagged()) { + type = type->AsTaggedRaw()->GetItemTypeRaw(); + } + return type; + } + + TTypePtr TType::StripOptionals() const noexcept { + return StripOptionalsRaw()->AsPtr(); + } + + const TType* TType::StripOptionalsRaw() const noexcept { + auto type = this; + while (type->IsOptional()) { + type = type->AsOptionalRaw()->GetItemTypeRaw(); + } + return type; + } + + TTypePtr TType::StripTagsAndOptionals() const noexcept { + return StripTagsAndOptionalsRaw()->AsPtr(); + } + + const TType* TType::StripTagsAndOptionalsRaw() const noexcept { + auto type = this; + while (type->IsTagged() || type->IsOptional()) { + if (type->IsTagged()) { + type = type->AsTaggedRaw()->GetItemTypeRaw(); + } else { + type = type->AsOptionalRaw()->GetItemTypeRaw(); + } + } + return type; + } + + const TType* TType::Clone(ITypeFactoryInternal& factory) const noexcept { + return VisitRaw([&factory](const auto* type) -> const NTi::TType* { + return type->Clone(factory); + }); + } + + void TType::Drop(ITypeFactoryInternal& factory) noexcept { + VisitRaw([&factory](const auto* type) { + using T = std::remove_const_t<std::remove_pointer_t<decltype(type)>>; + return const_cast<T*>(type)->Drop(factory); + }); + } + + ITypeFactoryInternal* TType::GetFactory() const noexcept { + size_t manager_or_rc = FactoryOrRc_.load(std::memory_order_relaxed); + if (IsRc(manager_or_rc)) { + return NPrivate::GetDefaultHeapFactory(); + } else if (IsFactory(manager_or_rc)) { + return CastToFactory(manager_or_rc); + } else { + return nullptr; + } + } + + void TType::SetFactory(ITypeFactoryInternal* factory) noexcept { + if (factory == NPrivate::GetDefaultHeapFactory()) { + FactoryOrRc_.store(0b1u, std::memory_order_release); + } else { + FactoryOrRc_.store(CastFromFactory(factory), std::memory_order_release); + } + } + + ITypeFactoryInternal& TType::FactoryInternal(ITypeFactory& factory) noexcept { + return static_cast<ITypeFactoryInternal&>(factory); + } + + template <bool RefFactory> + void TType::RefImpl() noexcept { + size_t factoryOrRc = FactoryOrRc_.load(std::memory_order_relaxed); + if (Y_LIKELY(IsRc(factoryOrRc))) { + FactoryOrRc_.fetch_add(0b10u, std::memory_order_acq_rel); + } else if (Y_LIKELY(IsFactory(factoryOrRc))) { + auto factory = CastToFactory(factoryOrRc); + if (RefFactory) { + factory->Ref(); + } + factory->RefType(this); + } + } + + template void TType::RefImpl<true>() noexcept; + template void TType::RefImpl<false>() noexcept; + + template <bool UnRefFactory> + void TType::UnRefImpl() noexcept { + size_t factoryOrRc = FactoryOrRc_.load(std::memory_order_relaxed); + if (Y_LIKELY(IsRc(factoryOrRc))) { + size_t rc = FactoryOrRc_.fetch_sub(0b10u, std::memory_order_acq_rel); + if (rc == 0b11u) { + auto factory = NPrivate::GetDefaultHeapFactory(); + Drop(*factory); + factory->Delete(this); + } + } else if (Y_LIKELY(IsFactory(factoryOrRc))) { + auto factory = CastToFactory(factoryOrRc); + factory->UnRefType(this); + if (UnRefFactory) { + factory->UnRef(); + } + } + } + + template void TType::UnRefImpl<true>() noexcept; + template void TType::UnRefImpl<false>() noexcept; + + template <bool DecRefFactory> + void TType::DecRefImpl() noexcept { + size_t factoryOrRc = FactoryOrRc_.load(std::memory_order_relaxed); + if (Y_LIKELY(IsRc(factoryOrRc))) { + size_t rc = FactoryOrRc_.fetch_sub(2, std::memory_order_acq_rel); + if (rc == 2) { + Y_FAIL("DecRef isn't supposed to drop"); + } + } else if (Y_LIKELY(IsFactory(factoryOrRc))) { + auto factory = CastToFactory(factoryOrRc); + factory->DecRefType(this); + if (DecRefFactory) { + factory->DecRef(); + } + } + } + + template void TType::DecRefImpl<true>() noexcept; + template void TType::DecRefImpl<false>() noexcept; + + long TType::RefCountImpl() const noexcept { + size_t factoryOrRc = FactoryOrRc_.load(std::memory_order_relaxed); + if (Y_LIKELY(IsRc(factoryOrRc))) { + return factoryOrRc >> 1u; + } else if (Y_LIKELY(IsFactory(factoryOrRc))) { + return CastToFactory(factoryOrRc)->RefCountType(this); + } else { + return 0; + } + } + + template <typename T, typename TCtor> + const T* TType::Cached(const T* type, ITypeFactoryInternal& factory, TCtor&& ctor) { + const TType* result = factory.LookupCache(type); + + if (result == nullptr) { + result = std::forward<TCtor>(ctor)(); + factory.SaveCache(result); + } + + Y_VERIFY(result->GetTypeName() == type->GetTypeName()); + Y_VERIFY_DEBUG(result->GetHash() == type->GetHash()); + return static_cast<const T*>(result); + } + + bool operator==(const TType& lhs, const TType& rhs) { + Y_VERIFY(&lhs); + Y_VERIFY(&rhs); + return NEq::TStrictlyEqual().IgnoreHash(&lhs, &rhs); + } + + bool operator!=(const TType& lhs, const TType& rhs) + { + return !(lhs == rhs); + } + + TVoidType::TVoidType() + : TType({}, ETypeName::Void) + { + } + + TVoidTypePtr TVoidType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TVoidType* TVoidType::InstanceRaw() { + static auto singleton = TVoidType(); + return &singleton; + } + + const TVoidType* TVoidType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TVoidType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TNullType::TNullType() + : TType({}, ETypeName::Null) + { + } + + TNullTypePtr TNullType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TNullType* TNullType::InstanceRaw() { + static auto singleton = TNullType(); + return &singleton; + } + + const TNullType* TNullType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TNullType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TPrimitiveType::TPrimitiveType(TMaybe<ui64> hash, EPrimitiveTypeName primitiveTypeName) noexcept + : TType(hash, ToTypeName(primitiveTypeName)) + { + } + + TBoolType::TBoolType() + : TPrimitiveType({}, EPrimitiveTypeName::Bool) + { + } + + TBoolTypePtr TBoolType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TBoolType* TBoolType::InstanceRaw() { + static auto singleton = TBoolType(); + return &singleton; + } + + const TBoolType* TBoolType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TBoolType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TInt8Type::TInt8Type() + : TPrimitiveType({}, EPrimitiveTypeName::Int8) + { + } + + TInt8TypePtr TInt8Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TInt8Type* TInt8Type::InstanceRaw() { + static auto singleton = TInt8Type(); + return &singleton; + } + + const TInt8Type* TInt8Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TInt8Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TInt16Type::TInt16Type() + : TPrimitiveType({}, EPrimitiveTypeName::Int16) + { + } + + TInt16TypePtr TInt16Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TInt16Type* TInt16Type::InstanceRaw() { + static auto singleton = TInt16Type(); + return &singleton; + } + + const TInt16Type* TInt16Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TInt16Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TInt32Type::TInt32Type() + : TPrimitiveType({}, EPrimitiveTypeName::Int32) + { + } + + TInt32TypePtr TInt32Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TInt32Type* TInt32Type::InstanceRaw() { + static auto singleton = TInt32Type(); + return &singleton; + } + + const TInt32Type* TInt32Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TInt32Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TInt64Type::TInt64Type() + : TPrimitiveType({}, EPrimitiveTypeName::Int64) + { + } + + TInt64TypePtr TInt64Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TInt64Type* TInt64Type::InstanceRaw() { + static auto singleton = TInt64Type(); + return &singleton; + } + + const TInt64Type* TInt64Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TInt64Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TUint8Type::TUint8Type() + : TPrimitiveType({}, EPrimitiveTypeName::Uint8) + { + } + + TUint8TypePtr TUint8Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TUint8Type* TUint8Type::InstanceRaw() { + static auto singleton = TUint8Type(); + return &singleton; + } + + const TUint8Type* TUint8Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TUint8Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TUint16Type::TUint16Type() + : TPrimitiveType({}, EPrimitiveTypeName::Uint16) + { + } + + TUint16TypePtr TUint16Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TUint16Type* TUint16Type::InstanceRaw() { + static auto singleton = TUint16Type(); + return &singleton; + } + + const TUint16Type* TUint16Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TUint16Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TUint32Type::TUint32Type() + : TPrimitiveType({}, EPrimitiveTypeName::Uint32) + { + } + + TUint32TypePtr TUint32Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TUint32Type* TUint32Type::InstanceRaw() { + static auto singleton = TUint32Type(); + return &singleton; + } + + const TUint32Type* TUint32Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TUint32Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TUint64Type::TUint64Type() + : TPrimitiveType({}, EPrimitiveTypeName::Uint64) + { + } + + TUint64TypePtr TUint64Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TUint64Type* TUint64Type::InstanceRaw() { + static auto singleton = TUint64Type(); + return &singleton; + } + + const TUint64Type* TUint64Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TUint64Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TFloatType::TFloatType() + : TPrimitiveType({}, EPrimitiveTypeName::Float) + { + } + + TFloatTypePtr TFloatType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TFloatType* TFloatType::InstanceRaw() { + static auto singleton = TFloatType(); + return &singleton; + } + + const TFloatType* TFloatType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TFloatType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TDoubleType::TDoubleType() + : TPrimitiveType({}, EPrimitiveTypeName::Double) + { + } + + TDoubleTypePtr TDoubleType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TDoubleType* TDoubleType::InstanceRaw() { + static auto singleton = TDoubleType(); + return &singleton; + } + + const TDoubleType* TDoubleType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TDoubleType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TStringType::TStringType() + : TPrimitiveType({}, EPrimitiveTypeName::String) + { + } + + TStringTypePtr TStringType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TStringType* TStringType::InstanceRaw() { + static auto singleton = TStringType(); + return &singleton; + } + + const TStringType* TStringType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TStringType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TUtf8Type::TUtf8Type() + : TPrimitiveType({}, EPrimitiveTypeName::Utf8) + { + } + + TUtf8TypePtr TUtf8Type::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TUtf8Type* TUtf8Type::InstanceRaw() { + static auto singleton = TUtf8Type(); + return &singleton; + } + + const TUtf8Type* TUtf8Type::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TUtf8Type::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TDateType::TDateType() + : TPrimitiveType({}, EPrimitiveTypeName::Date) + { + } + + TDateTypePtr TDateType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TDateType* TDateType::InstanceRaw() { + static auto singleton = TDateType(); + return &singleton; + } + + const TDateType* TDateType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TDateType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TDatetimeType::TDatetimeType() + : TPrimitiveType({}, EPrimitiveTypeName::Datetime) + { + } + + TDatetimeTypePtr TDatetimeType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TDatetimeType* TDatetimeType::InstanceRaw() { + static auto singleton = TDatetimeType(); + return &singleton; + } + + const TDatetimeType* TDatetimeType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TDatetimeType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TTimestampType::TTimestampType() + : TPrimitiveType({}, EPrimitiveTypeName::Timestamp) + { + } + + TTimestampTypePtr TTimestampType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TTimestampType* TTimestampType::InstanceRaw() { + static auto singleton = TTimestampType(); + return &singleton; + } + + const TTimestampType* TTimestampType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TTimestampType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TTzDateType::TTzDateType() + : TPrimitiveType({}, EPrimitiveTypeName::TzDate) + { + } + + TTzDateTypePtr TTzDateType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TTzDateType* TTzDateType::InstanceRaw() { + static auto singleton = TTzDateType(); + return &singleton; + } + + const TTzDateType* TTzDateType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TTzDateType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TTzDatetimeType::TTzDatetimeType() + : TPrimitiveType({}, EPrimitiveTypeName::TzDatetime) + { + } + + TTzDatetimeTypePtr TTzDatetimeType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TTzDatetimeType* TTzDatetimeType::InstanceRaw() { + static auto singleton = TTzDatetimeType(); + return &singleton; + } + + const TTzDatetimeType* TTzDatetimeType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TTzDatetimeType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TTzTimestampType::TTzTimestampType() + : TPrimitiveType({}, EPrimitiveTypeName::TzTimestamp) + { + } + + TTzTimestampTypePtr TTzTimestampType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TTzTimestampType* TTzTimestampType::InstanceRaw() { + static auto singleton = TTzTimestampType(); + return &singleton; + } + + const TTzTimestampType* TTzTimestampType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TTzTimestampType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TIntervalType::TIntervalType() + : TPrimitiveType({}, EPrimitiveTypeName::Interval) + { + } + + TIntervalTypePtr TIntervalType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TIntervalType* TIntervalType::InstanceRaw() { + static auto singleton = TIntervalType(); + return &singleton; + } + + const TIntervalType* TIntervalType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TIntervalType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TDecimalType::TDecimalType(TMaybe<ui64> hash, ui8 precision, ui8 scale) noexcept + : TPrimitiveType(hash, EPrimitiveTypeName::Decimal) + , Precision_(precision) + , Scale_(scale) + { + } + + TDecimalTypePtr TDecimalType::Create(ITypeFactory& factory, ui8 precision, ui8 scale) { + return CreateRaw(factory, precision, scale)->AsPtr(); + } + + const TDecimalType* TDecimalType::CreateRaw(ITypeFactory& factory, ui8 precision, ui8 scale) { + Y_ENSURE_EX( + scale <= precision, + TIllegalTypeException() << "decimal scale " << (ui32)scale + << " should be no greater than decimal precision " << (ui32)precision); + + return TDecimalType({}, precision, scale).Clone(FactoryInternal(factory)); + } + + ui64 TDecimalType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Precision_, hash); + hash = ::Hash(Scale_, hash); + return hash; + } + + const TDecimalType* TDecimalType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TDecimalType* { + auto hash = GetHashRaw(); + auto precision = Precision_; + auto scale = Scale_; + return factory.New<TDecimalType>(hash, precision, scale); + }); + } + + void TDecimalType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TJsonType::TJsonType() + : TPrimitiveType({}, EPrimitiveTypeName::Json) + { + } + + TJsonTypePtr TJsonType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TJsonType* TJsonType::InstanceRaw() { + static auto singleton = TJsonType(); + return &singleton; + } + + const TJsonType* TJsonType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TJsonType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TYsonType::TYsonType() + : TPrimitiveType({}, EPrimitiveTypeName::Yson) + { + } + + TYsonTypePtr TYsonType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TYsonType* TYsonType::InstanceRaw() { + static auto singleton = TYsonType(); + return &singleton; + } + + const TYsonType* TYsonType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TYsonType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TUuidType::TUuidType() + : TPrimitiveType({}, EPrimitiveTypeName::Uuid) + { + } + + TUuidTypePtr TUuidType::Instance() { + return InstanceRaw()->AsPtr(); + } + + const TUuidType* TUuidType::InstanceRaw() { + static auto singleton = TUuidType(); + return &singleton; + } + + const TUuidType* TUuidType::Clone(ITypeFactoryInternal& factory) const noexcept { + Y_UNUSED(factory); + return InstanceRaw(); + } + + void TUuidType::Drop(ITypeFactoryInternal& factory) noexcept { + Y_UNUSED(factory); + } + + TOptionalType::TOptionalType(TMaybe<ui64> hash, const TType* item) noexcept + : TType(hash, ETypeName::Optional) + , Item_(item) + { + } + + TOptionalTypePtr TOptionalType::Create(ITypeFactory& factory, TTypePtr item) { + return CreateRaw(factory, item.Get())->AsPtr(); + } + + const TOptionalType* TOptionalType::CreateRaw(ITypeFactory& factory, const TType* item) { + return TOptionalType({}, item).Clone(FactoryInternal(factory)); + } + + const TOptionalType* TOptionalType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TOptionalType* { + auto hash = GetHashRaw(); + auto item = factory.Own(Item_); + return factory.New<TOptionalType>(hash, item); + }); + } + + void TOptionalType::Drop(ITypeFactoryInternal& factory) noexcept { + factory.Disown(Item_); + } + + ui64 TOptionalType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Item_->GetHash(), hash); + return hash; + } + + TListType::TListType(TMaybe<ui64> hash, const TType* item) noexcept + : TType(hash, ETypeName::List) + , Item_(item) + { + } + + TListTypePtr TListType::Create(ITypeFactory& factory, TTypePtr item) { + return CreateRaw(factory, item.Get())->AsPtr(); + } + + const TListType* TListType::CreateRaw(ITypeFactory& factory, const TType* item) { + return TListType({}, item).Clone(FactoryInternal(factory)); + } + + const TListType* TListType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TListType* { + auto hash = GetHashRaw(); + auto item = factory.Own(Item_); + return factory.New<TListType>(hash, item); + }); + } + + void TListType::Drop(ITypeFactoryInternal& factory) noexcept { + factory.Disown(Item_); + } + + ui64 TListType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Item_->GetHash(), hash); + return hash; + } + + TDictType::TDictType(TMaybe<ui64> hash, const TType* key, const TType* value) noexcept + : TType(hash, ETypeName::Dict) + , Key_(key) + , Value_(value) + { + } + + TDictTypePtr TDictType::Create(ITypeFactory& factory, TTypePtr key, TTypePtr value) { + return CreateRaw(factory, key.Get(), value.Get())->AsPtr(); + } + + const TDictType* TDictType::CreateRaw(ITypeFactory& factory, const TType* key, const TType* value) { + return TDictType({}, key, value).Clone(FactoryInternal(factory)); + } + + const TDictType* TDictType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TDictType* { + auto hash = GetHashRaw(); + auto key = factory.Own(Key_); + auto value = factory.Own(Value_); + return factory.New<TDictType>(hash, key, value); + }); + } + + void TDictType::Drop(ITypeFactoryInternal& factory) noexcept { + factory.Disown(Key_); + factory.Disown(Value_); + } + + ui64 TDictType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Key_->GetHash(), hash); + hash = ::Hash(Value_->GetHash(), hash); + return hash; + } + + TStructType::TMember::TMember(TStringBuf name, const TType* type) + : Name_(name) + , Type_(type) + { + } + + ui64 TStructType::TMember::Hash() const { + auto hash = 0x10000; + hash = ::Hash(Name_, hash); + hash = ::Hash(Type_->GetHash(), hash); + return hash; + } + + TStructType::TOwnedMember::TOwnedMember(TString name, TTypePtr type) + : Name_(std::move(name)) + , Type_(std::move(type)) + { + } + + TStructType::TOwnedMember::operator TStructType::TMember() const& { + return TStructType::TMember(Name_, Type_.Get()); + } + + TStructType::TStructType(TMaybe<ui64> hash, TMaybe<TStringBuf> name, TMembers members, TConstArrayRef<size_t> sortedItems) noexcept + : TType(hash, ETypeName::Struct) + , Name_(name) + , Members_(members) + , SortedMembers_(sortedItems) + { + } + + TStructTypePtr TStructType::Create(ITypeFactory& factory, TStructType::TOwnedMembers members) { + return Create(factory, Nothing(), members); + } + + TStructTypePtr TStructType::Create(ITypeFactory& factory, TMaybe<TStringBuf> name, TStructType::TOwnedMembers members) { + auto rawItems = TTempArray<TMember>(members.size()); + for (size_t i = 0; i < members.size(); ++i) { + new (rawItems.Data() + i) TMember(members[i]); + } + return CreateRaw(factory, name, TArrayRef(rawItems.Data(), members.size()))->AsPtr(); + } + + const TStructType* TStructType::CreateRaw(ITypeFactory& factory, TStructType::TMembers members) { + return CreateRaw(factory, Nothing(), members); + } + + const TStructType* TStructType::CreateRaw(ITypeFactory& factory, TMaybe<TStringBuf> name, TStructType::TMembers members) { + auto sortedMembersArray = TTempArray<size_t>(members.size()); + auto sortedMembers = TArrayRef(sortedMembersArray.Data(), members.size()); + MakeSortedMembers(members, sortedMembers); + return TStructType({}, name, members, sortedMembers).Clone(FactoryInternal(factory)); + } + + void TStructType::MakeSortedMembers(TStructType::TMembers members, TArrayRef<size_t> sortedItems) { + Y_VERIFY(members.size() == sortedItems.size()); + + for (size_t i = 0; i < members.size(); ++i) { + sortedItems[i] = i; + } + + Sort(sortedItems.begin(), sortedItems.end(), [members](size_t lhs, size_t rhs) { + return members[lhs].GetName() < members[rhs].GetName(); + }); + + for (size_t i = 1; i < members.size(); ++i) { + if (members[sortedItems[i - 1]].GetName() == members[sortedItems[i]].GetName()) { + ythrow TIllegalTypeException() << "duplicate struct item " << Quote(members[sortedItems[i]].GetName()); + } + } + } + + const TStructType* TStructType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TStructType* { + auto hash = GetHashRaw(); + auto name = factory.AllocateStringMaybe(Name_); + auto members = factory.NewArray<TMember>(Members_.size(), [this, &factory](TMember* item, size_t i) { + auto name = factory.AllocateString(Members_[i].GetName()); + auto type = factory.Own(Members_[i].GetTypeRaw()); + new (item) TMember(name, type); + }); + auto sortedItems = factory.AllocateArrayFor<size_t>(SortedMembers_.size()); + Copy(SortedMembers_.begin(), SortedMembers_.end(), sortedItems); + return factory.New<TStructType>(hash, name, members, TArrayRef{sortedItems, SortedMembers_.size()}); + }); + } + + void TStructType::Drop(ITypeFactoryInternal& factory) noexcept { + factory.FreeStringMaybe(Name_); + factory.DeleteArray(Members_, [&factory](const TMember* item, size_t) { + factory.FreeString(item->GetName()); + factory.Disown(item->GetTypeRaw()); + }); + factory.Free(const_cast<void*>(static_cast<const void*>(SortedMembers_.data()))); + } + + ui64 TStructType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Name_, hash); + hash = ::Hash(Members_.size(), hash); + for (auto& item : Members_) { + hash = ::Hash(item.Hash(), hash); + } + return hash; + } + + bool TStructType::HasMember(TStringBuf name) const noexcept { + return GetMemberIndex(name) != -1; + } + + const TStructType::TMember& TStructType::GetMember(TStringBuf name) const { + auto idx = GetMemberIndex(name); + if (idx == -1) { + ythrow TItemNotFound() << "no item named " << Quote(name); + } else { + return Members_[idx]; + } + } + + ssize_t TStructType::GetMemberIndex(TStringBuf name) const noexcept { + auto it = LowerBound(SortedMembers_.begin(), SortedMembers_.end(), name, [this](size_t i, TStringBuf name) { + return Members_[i].GetName() < name; + }); + + if (it == SortedMembers_.end() || Members_[*it].GetName() != name) { + return -1; + } else { + return *it; + } + } + + TTupleType::TElement::TElement(const TType* type) + : Type_(type) + { + } + + ui64 TTupleType::TElement::Hash() const { + auto hash = 0x10001; + hash = ::Hash(Type_->GetHash(), hash); + return hash; + } + + TTupleType::TOwnedElement::TOwnedElement(TTypePtr type) + : Type_(std::move(type)) + { + } + + TTupleType::TOwnedElement::operator TTupleType::TElement() const& { + return TTupleType::TElement(Type_.Get()); + } + + TTupleType::TTupleType(TMaybe<ui64> hash, TMaybe<TStringBuf> name, TElements elements) noexcept + : TType(hash, ETypeName::Tuple) + , Name_(name) + , Elements_(elements) + { + } + + TTupleTypePtr TTupleType::Create(ITypeFactory& factory, TTupleType::TOwnedElements elements) { + return Create(factory, Nothing(), elements); + } + + TTupleTypePtr TTupleType::Create(ITypeFactory& factory, TMaybe<TStringBuf> name, TTupleType::TOwnedElements elements) { + auto rawItems = TTempArray<TElement>(elements.size()); + for (size_t i = 0; i < elements.size(); ++i) { + new (rawItems.Data() + i) TElement(elements[i]); + } + return CreateRaw(factory, name, TArrayRef(rawItems.Data(), elements.size()))->AsPtr(); + } + + const TTupleType* TTupleType::CreateRaw(ITypeFactory& factory, TTupleType::TElements elements) { + return CreateRaw(factory, Nothing(), elements); + } + + const TTupleType* TTupleType::CreateRaw(ITypeFactory& factory, TMaybe<TStringBuf> name, TTupleType::TElements elements) { + return TTupleType({}, name, elements).Clone(FactoryInternal(factory)); + } + + const TTupleType* TTupleType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TTupleType* { + auto hash = GetHashRaw(); + auto name = factory.AllocateStringMaybe(Name_); + auto elements = factory.NewArray<TElement>(Elements_.size(), [this, &factory](TElement* item, size_t i) { + auto type = factory.Own(Elements_[i].GetTypeRaw()); + new (item) TElement(type); + }); + return factory.New<TTupleType>(hash, name, elements); + }); + } + + void TTupleType::Drop(ITypeFactoryInternal& factory) noexcept { + factory.FreeStringMaybe(Name_); + factory.DeleteArray(Elements_, [&factory](const TElement* item, size_t) { + factory.Disown(item->GetTypeRaw()); + }); + } + + ui64 TTupleType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Name_, hash); + hash = ::Hash(Elements_.size(), hash); + for (auto& item : Elements_) { + hash = ::Hash(item.Hash(), hash); + } + return hash; + } + + TVariantType::TVariantType(TMaybe<ui64> hash, TMaybe<TStringBuf> name, const TType* inner) noexcept + : TType(hash, ETypeName::Variant) + , Name_(name) + , Underlying_(inner) + { + } + + TVariantTypePtr TVariantType::Create(ITypeFactory& factory, TTypePtr inner) { + return Create(factory, Nothing(), std::move(inner)); + } + + TVariantTypePtr TVariantType::Create(ITypeFactory& factory, TMaybe<TStringBuf> name, TTypePtr inner) { + return CreateRaw(factory, name, inner.Get())->AsPtr(); + } + + const TVariantType* TVariantType::CreateRaw(ITypeFactory& factory, const TType* inner) { + return CreateRaw(factory, Nothing(), inner); + } + + const TVariantType* TVariantType::CreateRaw(ITypeFactory& factory, TMaybe<TStringBuf> name, const TType* inner) { + inner->VisitRaw(TOverloaded{ + [&](const TStructType* s) { + Y_ENSURE_EX( + !s->GetMembers().empty(), + TIllegalTypeException() << "variant should contain at least one alternative"); + }, + [&](const TTupleType* t) { + Y_ENSURE_EX( + !t->GetElements().empty(), + TIllegalTypeException() << "variant should contain at least one alternative"); + }, + [](const TType* t) { + ythrow TIllegalTypeException() << "variants can only contain structs and tuples, got " + << t->GetTypeName() << " instead"; + }}); + + return TVariantType({}, name, inner).Clone(FactoryInternal(factory)); + } + + const TVariantType* TVariantType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TVariantType* { + auto hash = GetHashRaw(); + auto name = factory.AllocateStringMaybe(Name_); + auto inner = factory.Own(Underlying_); + return factory.New<TVariantType>(hash, name, inner); + }); + } + + void TVariantType::Drop(ITypeFactoryInternal& factory) noexcept { + factory.FreeStringMaybe(Name_); + factory.Disown(Underlying_); + } + + ui64 TVariantType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Name_, hash); + hash = ::Hash(Underlying_->GetHash(), hash); + return hash; + } + + TTaggedType::TTaggedType(TMaybe<ui64> hash, const TType* item, TStringBuf tag) noexcept + : TType(hash, ETypeName::Tagged) + , Item_(item) + , Tag_(tag) + { + } + + TTaggedTypePtr TTaggedType::Create(ITypeFactory& factory, TTypePtr type, TStringBuf tag) { + return CreateRaw(factory, type.Get(), tag)->AsPtr(); + } + + const TTaggedType* TTaggedType::CreateRaw(ITypeFactory& factory, const TType* type, TStringBuf tag) { + return TTaggedType({}, type, tag).Clone(FactoryInternal(factory)); + } + + const TTaggedType* TTaggedType::Clone(ITypeFactoryInternal& factory) const noexcept { + return Cached(this, factory, [this, &factory]() -> const TTaggedType* { + auto hash = GetHashRaw(); + auto item = factory.Own(Item_); + auto tag = factory.AllocateString(Tag_); + return factory.New<TTaggedType>(hash, item, tag); + }); + } + + void TTaggedType::Drop(ITypeFactoryInternal& factory) noexcept { + factory.FreeString(Tag_); + factory.Disown(Item_); + } + + ui64 TTaggedType::CalculateHash() const noexcept { + auto hash = TType::CalculateHash(); + hash = ::Hash(Tag_, hash); + hash = ::Hash(Item_->GetHash(), hash); + return hash; + } + + TVoidTypePtr Void() { + return NPrivate::GetDefaultHeapFactory()->Void(); + } + + TNullTypePtr Null() { + return NPrivate::GetDefaultHeapFactory()->Null(); + } + + TBoolTypePtr Bool() { + return NPrivate::GetDefaultHeapFactory()->Bool(); + } + + TInt8TypePtr Int8() { + return NPrivate::GetDefaultHeapFactory()->Int8(); + } + + TInt16TypePtr Int16() { + return NPrivate::GetDefaultHeapFactory()->Int16(); + } + + TInt32TypePtr Int32() { + return NPrivate::GetDefaultHeapFactory()->Int32(); + } + + TInt64TypePtr Int64() { + return NPrivate::GetDefaultHeapFactory()->Int64(); + } + + TUint8TypePtr Uint8() { + return NPrivate::GetDefaultHeapFactory()->Uint8(); + } + + TUint16TypePtr Uint16() { + return NPrivate::GetDefaultHeapFactory()->Uint16(); + } + + TUint32TypePtr Uint32() { + return NPrivate::GetDefaultHeapFactory()->Uint32(); + } + + TUint64TypePtr Uint64() { + return NPrivate::GetDefaultHeapFactory()->Uint64(); + } + + TFloatTypePtr Float() { + return NPrivate::GetDefaultHeapFactory()->Float(); + } + + TDoubleTypePtr Double() { + return NPrivate::GetDefaultHeapFactory()->Double(); + } + + TStringTypePtr String() { + return NPrivate::GetDefaultHeapFactory()->String(); + } + + TUtf8TypePtr Utf8() { + return NPrivate::GetDefaultHeapFactory()->Utf8(); + } + + TDateTypePtr Date() { + return NPrivate::GetDefaultHeapFactory()->Date(); + } + + TDatetimeTypePtr Datetime() { + return NPrivate::GetDefaultHeapFactory()->Datetime(); + } + + TTimestampTypePtr Timestamp() { + return NPrivate::GetDefaultHeapFactory()->Timestamp(); + } + + TTzDateTypePtr TzDate() { + return NPrivate::GetDefaultHeapFactory()->TzDate(); + } + + TTzDatetimeTypePtr TzDatetime() { + return NPrivate::GetDefaultHeapFactory()->TzDatetime(); + } + + TTzTimestampTypePtr TzTimestamp() { + return NPrivate::GetDefaultHeapFactory()->TzTimestamp(); + } + + TIntervalTypePtr Interval() { + return NPrivate::GetDefaultHeapFactory()->Interval(); + } + + TDecimalTypePtr Decimal(ui8 precision, ui8 scale) { + return NPrivate::GetDefaultHeapFactory()->Decimal(precision, scale); + } + + TJsonTypePtr Json() { + return NPrivate::GetDefaultHeapFactory()->Json(); + } + + TYsonTypePtr Yson() { + return NPrivate::GetDefaultHeapFactory()->Yson(); + } + + TUuidTypePtr Uuid() { + return NPrivate::GetDefaultHeapFactory()->Uuid(); + } + + TOptionalTypePtr Optional(TTypePtr item) { + return NPrivate::GetDefaultHeapFactory()->Optional(std::move(item)); + } + + TListTypePtr List(TTypePtr item) { + return NPrivate::GetDefaultHeapFactory()->List(std::move(item)); + } + + TDictTypePtr Dict(TTypePtr key, TTypePtr value) { + return NPrivate::GetDefaultHeapFactory()->Dict(std::move(key), std::move(value)); + } + + TStructTypePtr Struct(TStructType::TOwnedMembers members) { + return NPrivate::GetDefaultHeapFactory()->Struct(members); + } + + TStructTypePtr Struct(TMaybe<TStringBuf> name, TStructType::TOwnedMembers members) { + return NPrivate::GetDefaultHeapFactory()->Struct(name, members); + } + + TTupleTypePtr Tuple(TTupleType::TOwnedElements elements) { + return NPrivate::GetDefaultHeapFactory()->Tuple(elements); + } + + TTupleTypePtr Tuple(TMaybe<TStringBuf> name, TTupleType::TOwnedElements elements) { + return NPrivate::GetDefaultHeapFactory()->Tuple(name, elements); + } + + TVariantTypePtr Variant(TTypePtr underlying) { + return NPrivate::GetDefaultHeapFactory()->Variant(std::move(underlying)); + } + + TVariantTypePtr Variant(TMaybe<TStringBuf> name, TTypePtr underlying) { + return NPrivate::GetDefaultHeapFactory()->Variant(name, std::move(underlying)); + } + + TTaggedTypePtr Tagged(TTypePtr type, TStringBuf tag) { + return NPrivate::GetDefaultHeapFactory()->Tagged(std::move(type), tag); + } +} + +Y_DECLARE_OUT_SPEC(, NTi::TType, o, v) { + v.VisitRaw([&o](const auto* v) { o << *v; }); +} + +Y_DECLARE_OUT_SPEC(, NTi::TPrimitiveType, o, v) { + v.VisitPrimitiveRaw([&o](const auto* v) { o << *v; }); +} + +Y_DECLARE_OUT_SPEC(, NTi::TVoidType, o, v) { + Y_UNUSED(v); + o << "Void"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TNullType, o, v) { + Y_UNUSED(v); + o << "Null"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TBoolType, o, v) { + Y_UNUSED(v); + o << "Bool"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TInt8Type, o, v) { + Y_UNUSED(v); + o << "Int8"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TInt16Type, o, v) { + Y_UNUSED(v); + o << "Int16"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TInt32Type, o, v) { + Y_UNUSED(v); + o << "Int32"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TInt64Type, o, v) { + Y_UNUSED(v); + o << "Int64"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TUint8Type, o, v) { + Y_UNUSED(v); + o << "Uint8"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TUint16Type, o, v) { + Y_UNUSED(v); + o << "Uint16"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TUint32Type, o, v) { + Y_UNUSED(v); + o << "Uint32"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TUint64Type, o, v) { + Y_UNUSED(v); + o << "Uint64"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TFloatType, o, v) { + Y_UNUSED(v); + o << "Float"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TDoubleType, o, v) { + Y_UNUSED(v); + o << "Double"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TStringType, o, v) { + Y_UNUSED(v); + o << "String"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TUtf8Type, o, v) { + Y_UNUSED(v); + o << "Utf8"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TDateType, o, v) { + Y_UNUSED(v); + o << "Date"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TDatetimeType, o, v) { + Y_UNUSED(v); + o << "Datetime"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TTimestampType, o, v) { + Y_UNUSED(v); + o << "Timestamp"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TTzDateType, o, v) { + Y_UNUSED(v); + o << "TzDate"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TTzDatetimeType, o, v) { + Y_UNUSED(v); + o << "TzDatetime"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TTzTimestampType, o, v) { + Y_UNUSED(v); + o << "TzTimestamp"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TIntervalType, o, v) { + Y_UNUSED(v); + o << "Interval"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TDecimalType, o, v) { + o << "Decimal(" << (i32)v.GetPrecision() << ", " << (i32)v.GetScale() << ')'; +} + +Y_DECLARE_OUT_SPEC(, NTi::TJsonType, o, v) { + Y_UNUSED(v); + o << "Json"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TYsonType, o, v) { + Y_UNUSED(v); + o << "Yson"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TUuidType, o, v) { + Y_UNUSED(v); + o << "Uuid"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TOptionalType, o, v) { + o << "Optional<" << *v.GetItemTypeRaw() << ">"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TListType, o, v) { + o << "List<" << *v.GetItemTypeRaw() << ">"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TDictType, o, v) { + o << "Dict<" << *v.GetKeyTypeRaw() << ", " << *v.GetValueTypeRaw() << ">"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TStructType, o, v) { + o << "Struct"; + + if (v.GetName().Defined()) { + o << "[" << Quote(*v.GetName()) << "]"; + } + + o << "<"; + const char* sep = ""; + for (auto& item : v.GetMembers()) { + o << sep << Quote(item.GetName()) << ": " << *item.GetTypeRaw(); + sep = ", "; + } + o << ">"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TTupleType, o, v) { + o << "Tuple"; + + if (v.GetName().Defined()) { + o << "[" << Quote(*v.GetName()) << "]"; + } + + o << "<"; + const char* sep = ""; + for (auto& item : v.GetElements()) { + o << sep << *item.GetTypeRaw(); + sep = ", "; + } + o << ">"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TVariantType, o, v) { + o << "Variant"; + + if (v.GetName().Defined()) { + o << "[" << Quote(*v.GetName()) << "]"; + } + + o << "<"; + v.VisitUnderlyingRaw( + TOverloaded{ + [&o](const NTi::TStructType* s) { + const char* sep = ""; + for (auto& item : s->GetMembers()) { + o << sep << Quote(item.GetName()) << ": " << *item.GetTypeRaw(); + sep = ", "; + } + }, + [&o](const NTi::TTupleType* t) { + const char* sep = ""; + for (auto& item : t->GetElements()) { + o << sep << *item.GetTypeRaw(); + sep = ", "; + } + }}); + o << ">"; +} + +Y_DECLARE_OUT_SPEC(, NTi::TTaggedType, o, v) { + o << "Tagged<" << *v.GetItemTypeRaw() << ", " << Quote(v.GetTag()) << ">"; +} + +static_assert(std::is_trivially_destructible_v<NTi::TVoidType>); +static_assert(std::is_trivially_destructible_v<NTi::TNullType>); +static_assert(std::is_trivially_destructible_v<NTi::TPrimitiveType>); +static_assert(std::is_trivially_destructible_v<NTi::TBoolType>); +static_assert(std::is_trivially_destructible_v<NTi::TInt8Type>); +static_assert(std::is_trivially_destructible_v<NTi::TInt16Type>); +static_assert(std::is_trivially_destructible_v<NTi::TInt32Type>); +static_assert(std::is_trivially_destructible_v<NTi::TInt64Type>); +static_assert(std::is_trivially_destructible_v<NTi::TUint8Type>); +static_assert(std::is_trivially_destructible_v<NTi::TUint16Type>); +static_assert(std::is_trivially_destructible_v<NTi::TUint32Type>); +static_assert(std::is_trivially_destructible_v<NTi::TUint64Type>); +static_assert(std::is_trivially_destructible_v<NTi::TFloatType>); +static_assert(std::is_trivially_destructible_v<NTi::TDoubleType>); +static_assert(std::is_trivially_destructible_v<NTi::TStringType>); +static_assert(std::is_trivially_destructible_v<NTi::TUtf8Type>); +static_assert(std::is_trivially_destructible_v<NTi::TDateType>); +static_assert(std::is_trivially_destructible_v<NTi::TDatetimeType>); +static_assert(std::is_trivially_destructible_v<NTi::TTimestampType>); +static_assert(std::is_trivially_destructible_v<NTi::TTzDateType>); +static_assert(std::is_trivially_destructible_v<NTi::TTzDatetimeType>); +static_assert(std::is_trivially_destructible_v<NTi::TTzTimestampType>); +static_assert(std::is_trivially_destructible_v<NTi::TIntervalType>); +static_assert(std::is_trivially_destructible_v<NTi::TDecimalType>); +static_assert(std::is_trivially_destructible_v<NTi::TJsonType>); +static_assert(std::is_trivially_destructible_v<NTi::TYsonType>); +static_assert(std::is_trivially_destructible_v<NTi::TUuidType>); +static_assert(std::is_trivially_destructible_v<NTi::TOptionalType>); +static_assert(std::is_trivially_destructible_v<NTi::TListType>); +static_assert(std::is_trivially_destructible_v<NTi::TDictType>); +static_assert(std::is_trivially_destructible_v<NTi::TStructType>); +static_assert(std::is_trivially_destructible_v<NTi::TStructType::TMember>); +static_assert(std::is_trivially_destructible_v<NTi::TTupleType>); +static_assert(std::is_trivially_destructible_v<NTi::TTupleType::TElement>); +static_assert(std::is_trivially_destructible_v<NTi::TVariantType>); +static_assert(std::is_trivially_destructible_v<NTi::TTaggedType>); diff --git a/library/cpp/type_info/type.h b/library/cpp/type_info/type.h new file mode 100644 index 0000000000..1577f1cee3 --- /dev/null +++ b/library/cpp/type_info/type.h @@ -0,0 +1,2421 @@ +#pragma once + +//! @file type.h +//! +//! Hierarchy of classes that represent types. +#include "fwd.h" + +#include "error.h" +#include "type_list.h" + +#include <atomic> +#include <util/generic/array_ref.h> +#include <util/generic/maybe.h> +#include <util/generic/strbuf.h> +#include <util/generic/string.h> + +namespace NTi { + /// Represents a single type. + /// + /// Create instances of types using type factory (see `NTi::ITypeFactory`). + /// + /// Introspect them using associated methods and functions that work with `ETypeName`. + /// + /// Pattern-match them using the `Visit` method. + /// + /// Serialize and deserialize them using functions from `NTi::NIo`. + class TType { + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + template <typename T> + friend class ::TDefaultIntrusivePtrOps; + + public: + TTypePtr AsPtr() const noexcept { + return const_cast<TType*>(this); + } + + protected: + explicit TType(TMaybe<ui64> hash, ETypeName typeName) noexcept; + + protected: + /// Calculate hash for this type. This function is lazily called by `GetHash`. + /// + /// Note: this function is not marked as `virtual` because we use our own dispatch via `Visit`. + ui64 CalculateHash() const noexcept; + + public: + /// Get hash of this type. Hashes follow the 'strict equivalence' relation (see `type_equivalence.h`). + ui64 GetHash() const; + + /// Get hash of this type. If hash is not calculated, returns nothing. + TMaybe<ui64> GetHashRaw() const noexcept; + + /// Get name of this type as a `NTi::ETypeName` enumerator. + ETypeName GetTypeName() const noexcept { + return TypeName_; + } + + /// @name Simple type downcast functions + /// + /// Check if type is of the given subclass and convert between subclasses. + /// Conversions panic if downcasting into an incompatible type. + /// + /// @{ + inline bool IsVoid() const noexcept; + inline TVoidTypePtr AsVoid() const noexcept; + inline const TVoidType* AsVoidRaw() const noexcept; + + inline bool IsNull() const noexcept; + inline TNullTypePtr AsNull() const noexcept; + inline const TNullType* AsNullRaw() const noexcept; + + inline bool IsPrimitive() const noexcept; + inline TPrimitiveTypePtr AsPrimitive() const noexcept; + inline const TPrimitiveType* AsPrimitiveRaw() const noexcept; + + inline bool IsBool() const noexcept; + inline TBoolTypePtr AsBool() const noexcept; + inline const TBoolType* AsBoolRaw() const noexcept; + + inline bool IsInt8() const noexcept; + inline TInt8TypePtr AsInt8() const noexcept; + inline const TInt8Type* AsInt8Raw() const noexcept; + + inline bool IsInt16() const noexcept; + inline TInt16TypePtr AsInt16() const noexcept; + inline const TInt16Type* AsInt16Raw() const noexcept; + + inline bool IsInt32() const noexcept; + inline TInt32TypePtr AsInt32() const noexcept; + inline const TInt32Type* AsInt32Raw() const noexcept; + + inline bool IsInt64() const noexcept; + inline TInt64TypePtr AsInt64() const noexcept; + inline const TInt64Type* AsInt64Raw() const noexcept; + + inline bool IsUint8() const noexcept; + inline TUint8TypePtr AsUint8() const noexcept; + inline const TUint8Type* AsUint8Raw() const noexcept; + + inline bool IsUint16() const noexcept; + inline TUint16TypePtr AsUint16() const noexcept; + inline const TUint16Type* AsUint16Raw() const noexcept; + + inline bool IsUint32() const noexcept; + inline TUint32TypePtr AsUint32() const noexcept; + inline const TUint32Type* AsUint32Raw() const noexcept; + + inline bool IsUint64() const noexcept; + inline TUint64TypePtr AsUint64() const noexcept; + inline const TUint64Type* AsUint64Raw() const noexcept; + + inline bool IsFloat() const noexcept; + inline TFloatTypePtr AsFloat() const noexcept; + inline const TFloatType* AsFloatRaw() const noexcept; + + inline bool IsDouble() const noexcept; + inline TDoubleTypePtr AsDouble() const noexcept; + inline const TDoubleType* AsDoubleRaw() const noexcept; + + inline bool IsString() const noexcept; + inline TStringTypePtr AsString() const noexcept; + inline const TStringType* AsStringRaw() const noexcept; + + inline bool IsUtf8() const noexcept; + inline TUtf8TypePtr AsUtf8() const noexcept; + inline const TUtf8Type* AsUtf8Raw() const noexcept; + + inline bool IsDate() const noexcept; + inline TDateTypePtr AsDate() const noexcept; + inline const TDateType* AsDateRaw() const noexcept; + + inline bool IsDatetime() const noexcept; + inline TDatetimeTypePtr AsDatetime() const noexcept; + inline const TDatetimeType* AsDatetimeRaw() const noexcept; + + inline bool IsTimestamp() const noexcept; + inline TTimestampTypePtr AsTimestamp() const noexcept; + inline const TTimestampType* AsTimestampRaw() const noexcept; + + inline bool IsTzDate() const noexcept; + inline TTzDateTypePtr AsTzDate() const noexcept; + inline const TTzDateType* AsTzDateRaw() const noexcept; + + inline bool IsTzDatetime() const noexcept; + inline TTzDatetimeTypePtr AsTzDatetime() const noexcept; + inline const TTzDatetimeType* AsTzDatetimeRaw() const noexcept; + + inline bool IsTzTimestamp() const noexcept; + inline TTzTimestampTypePtr AsTzTimestamp() const noexcept; + inline const TTzTimestampType* AsTzTimestampRaw() const noexcept; + + inline bool IsInterval() const noexcept; + inline TIntervalTypePtr AsInterval() const noexcept; + inline const TIntervalType* AsIntervalRaw() const noexcept; + + inline bool IsDecimal() const noexcept; + inline TDecimalTypePtr AsDecimal() const noexcept; + inline const TDecimalType* AsDecimalRaw() const noexcept; + + inline bool IsJson() const noexcept; + inline TJsonTypePtr AsJson() const noexcept; + inline const TJsonType* AsJsonRaw() const noexcept; + + inline bool IsYson() const noexcept; + inline TYsonTypePtr AsYson() const noexcept; + inline const TYsonType* AsYsonRaw() const noexcept; + + inline bool IsUuid() const noexcept; + inline TUuidTypePtr AsUuid() const noexcept; + inline const TUuidType* AsUuidRaw() const noexcept; + + inline bool IsOptional() const noexcept; + inline TOptionalTypePtr AsOptional() const noexcept; + inline const TOptionalType* AsOptionalRaw() const noexcept; + + inline bool IsList() const noexcept; + inline TListTypePtr AsList() const noexcept; + inline const TListType* AsListRaw() const noexcept; + + inline bool IsDict() const noexcept; + inline TDictTypePtr AsDict() const noexcept; + inline const TDictType* AsDictRaw() const noexcept; + + inline bool IsStruct() const noexcept; + inline TStructTypePtr AsStruct() const noexcept; + inline const TStructType* AsStructRaw() const noexcept; + + inline bool IsTuple() const noexcept; + inline TTupleTypePtr AsTuple() const noexcept; + inline const TTupleType* AsTupleRaw() const noexcept; + + inline bool IsVariant() const noexcept; + inline TVariantTypePtr AsVariant() const noexcept; + inline const TVariantType* AsVariantRaw() const noexcept; + + inline bool IsTagged() const noexcept; + inline TTaggedTypePtr AsTagged() const noexcept; + inline const TTaggedType* AsTaggedRaw() const noexcept; + + /// @} + + /// Recursively descends to tagged types and returns first non-tagged type. + TTypePtr StripTags() const noexcept; + + /// Like `StripTags`, but returns a raw pointer. + const TType* StripTagsRaw() const noexcept; + + /// Recursively descends to optional types and returns first non-optional type. + TTypePtr StripOptionals() const noexcept; + + /// Like `StripOptionals`, but returns a raw pointer. + const TType* StripOptionalsRaw() const noexcept; + + /// Recursively descends to tagged and optional types and returns first non-tagged non-optional type. + TTypePtr StripTagsAndOptionals() const noexcept; + + /// Like `StripTagsAndOptionals`, but returns a raw pointer. + const TType* StripTagsAndOptionalsRaw() const noexcept; + + /// Cast this base class down to the most-derived class and pass it to the `visitor`. + /// + /// This function is used as a safer alternative to manually downcasting types via `IsType`/`AsType` calls. + /// It works like `std::visit` for types, except that it doesn't produce as much code bloat as `std::visit` + /// does, and can be optimized better. It casts an instance of `NTi::TType` down to the most-derived class, + /// and passes an intrusive pointer to that concrete type to the `visitor` functor. That is, `visitor` should + /// be a callable which can handle `NTi::TVoidTypePtr`, `NTi::TOptionalTypePtr`, etc. + /// + /// This function returns whatever the `visitor` returns. + /// + /// + /// # Example: visitor + /// + /// A simple visitor that returns name for a type would look like this: + /// + /// ``` + /// struct TGetNameVisitor { + /// TString operator()(TVoidTypePtr) { + /// return "Void"; + /// } + /// + /// TString operator()(TStringTypePtr) { + /// return "String"; + /// } + /// + /// // ... + /// + /// TString operator()(TStructTypePtr type) { + /// return TString(type->GetName().GetOrElse("Struct"));; + /// } + /// + /// // ... + /// } + /// ``` + /// + /// Now, we can use this visitor as following: + /// + /// ``` + /// TString typeName = type->Visit(TGetNameVisitor()); + /// ``` + /// + /// + /// # Example: overloaded struct + /// + /// Writing a separate struct each time one needs a visitor is tedious. Thanks to C++17 magic, we may avoid it. + /// Using lambdas and `TOverloaded` from `library/cpp/overloaded` allows replacing separate struct with + /// a bunch of lambdas: + /// + /// ``` + /// TString typeName = type->Visit(TOverloaded{ + /// [](TVoidTypePtr) -> TString { + /// return "Void"; + /// }, + /// [](TStringTypePtr) -> TString { + /// return "String"; + /// }, + /// + /// // ... + /// + /// }); + /// ``` + /// + /// + /// # Example: handling all primitives at once + /// + /// Since all primitives derive from `TPrimitiveType`, they can be handled all at once, + /// by accepting `TPrimitiveTypePtr`: + /// + /// ``` + /// TString typeName = type->Visit(TOverloaded{ + /// // All primitive types are handled by this lambda. + /// [](TPrimitiveTypePtr) -> TString { + /// return "Primitive"; + /// }, + /// + /// // Special handler for string type. Strings are handled by this lambda + /// // because of how C++ prioritizes overloads. + /// [](TStringTypePtr) -> TString { + /// return "String"; + /// }, + /// + /// // ... + /// + /// }); + /// ``` + template <typename V> + inline decltype(auto) Visit(V&& visitor) const; + + /// Like `Visit`, but passes const raw pointers to the visitor. + template <typename V> + inline decltype(auto) VisitRaw(V&& visitor) const; + + /// @} + + protected: + /// @name Internal interface for adoption semantics support + /// + /// Do not call these functions manually! + /// + /// See `type_factory.h`'s section on implementation details for more info. + /// + /// @{ + //- + /// Create a new instance of the class using this instance as a prototype. + /// + /// This is a [virtual copy constructor]. Typical implementation does the following: + /// + /// 1. for nested types, if any, it calls the factory's `Own` function. The `Own` acquires internal + /// ownership over the nested types, thus guaranteeing that they'll outlive the object that've + /// owned them. Depending on the particular factory implementation, `Own` may either recursively deepcopy + /// the whole nested type, increment some reference counter, or do nothing; + /// 2. for other resources owned by this type (i.e. arrays, strings, etc.), it copies them into the given + /// factory by calling factory's `New` and `Allocate` functions; + /// 3. finally, it creates a new instance of the type by invoking its constructor via the factory's + /// `New` function. + /// + /// Note: there are no guarantees on the value stored in `FactoryOrRc_` during invocation of this function. + /// Specifically, creating types on the stack (`FactoryOrRc_` is `0` in this case) and moving them + /// into a factory is a valid technique used extensively throughout this library. + /// + /// See `type_factory.h`'s section on implementation details for more info. + /// + /// Note: this function is not marked as `virtual` because we use our own dispatch via `Visit`. + /// + /// [virtual move constructor]: https://isocpp.org/wiki/faq/virtual-functions#virtual-ctors + const TType* Clone(ITypeFactoryInternal& factory) const noexcept; + + /// Release internal resources that were allocated in `Clone`. + /// + /// This function is the opposite of `Clone`. It releases all memory that was allocated within `Clone`, + /// and disowns nested types. + /// + /// This function is called by factories that perform active memory management, such as the default + /// heap factory. Typical implementation does the following: + /// + /// 1. for each `Clone`'s call to `Own` it calls `Disown`. The `Disown` releases internal ownership + /// over the nested types, thus allowing factory to free their memory. Depending + /// on the particular factory implementation, `Disown` may either decrement some reference counter, + /// call `Drop` and free the underlying memory, or do nothing; + /// 2. for each `Clone`'s call to `New` and `Allocate`, it calls `Delete` and `Free`; + /// 3. it should *not* call `Delete(this)` to mirror `Clone`'s final call to `New` (see the third bullet + /// in the `Clone`'s documentation). It is the factory's job to release memory under the type that's + /// being dropped. + /// + /// Note: there are no guarantees on whether this method will be called or not. For example, the default + /// heap factory will call it when some type's reference counter reaches zero. The default memory pool + /// factory will not call it. + /// + /// Note: this function is not marked as `virtual` because we use our own dispatch via `Visit`. + /// + /// See `type_factory.h`'s section on implementation details for more info. + void Drop(ITypeFactoryInternal& factory) noexcept; + + /// Get factory that manages this instance. + /// + /// If this instance is refcounted, returns the default heap factory. If it is unmanaged, returns `nullptr`. + /// Otherwise, returns pointer to the instance's factory. + /// + /// Remember that factories are not thread safe, thus using factory from this method may not be safe. + ITypeFactoryInternal* GetFactory() const noexcept; + + /// Mark this instance as managed by the given factory. + void SetFactory(ITypeFactoryInternal* factory) noexcept; + + /// Get factory's internal interface. + static ITypeFactoryInternal& FactoryInternal(ITypeFactory& factory) noexcept; + + /// @} + + protected: + /// @name Internal interface for reference counting + /// + /// See `type_factory.h`'s section on implementation details for more info. + /// + /// @{ + //- + /// Increase reference count of this type. + void RefSelf() noexcept { + RefImpl</* RefFactory = */ false>(); + } + + /// Increase reference count of this type and its factory. + void Ref() noexcept { + RefImpl</* RefFactory = */ true>(); + } + + /// Decrease reference count of this type. + void UnRefSelf() noexcept { + UnRefImpl</* UnRefFactory = */ false>(); + } + + /// Decrease reference count of this type and its factory. + void UnRef() noexcept { + UnRefImpl</* UnRefFactory = */ true>(); + } + + /// Decrease reference count of this type. Panic if any of it reaches zero. + void DecRefSelf() noexcept { + DecRefImpl</* DecRefFactory = */ false>(); + } + + /// Decrease reference count of type and its factory. Panic if any of it reaches zero. + void DecRef() noexcept { + DecRefImpl</* DecRefFactory = */ true>(); + } + + /// Get current reference count for this type. + long RefCount() const noexcept { + return RefCountImpl(); + } + + /// @} + + private: + template <bool RefFactory> + void RefImpl() noexcept; + + template <bool UnRefFactory> + void UnRefImpl() noexcept; + + template <bool DecRefFactory> + void DecRefImpl() noexcept; + + long RefCountImpl() const noexcept; + + protected: + /// Helper for implementing `Clone` with caching. + template <typename T, typename TCtor> + static const T* Cached(const T* type, ITypeFactoryInternal& factory, TCtor&& ctor); + + private: + /// Pointer to the type factory that've created this instance. + /// If this instance is static, this variable contains zero. + /// If this instance was created by the default heap factory, this variable is used as a reference counter. + std::atomic<size_t> FactoryOrRc_ = 0; + + /// Name of this type. Can be used to check before downcast. + ETypeName TypeName_; + + /// Hash is calculated lazily. + mutable std::atomic<bool> HasHash_; + mutable std::atomic<ui64> Hash_; + + private: + static bool IsRc(size_t factoryOrRc) noexcept { + return factoryOrRc & 1u; + } + static bool IsFactory(size_t factoryOrRc) noexcept { + return factoryOrRc != 0 && !IsRc(factoryOrRc); + } + static size_t CastFromFactory(ITypeFactoryInternal* factory) noexcept { + return reinterpret_cast<size_t>(factory); + } + static ITypeFactoryInternal* CastToFactory(size_t factoryOrRc) noexcept { + return reinterpret_cast<ITypeFactoryInternal*>(factoryOrRc); + } + }; + + static_assert(sizeof(TType) == 24); + + bool operator==(const TTypePtr& lhs, const TTypePtr& rhs) = delete; + bool operator!=(const TTypePtr& lhs, const TTypePtr& rhs) = delete; + + /// @brief Check for strict equivalence of the types. + /// + /// @see NTi::NEq::TStrictlyEqual + /// + /// @{ + bool operator==(const TType& lhs, const TType& rhs); + bool operator!=(const TType& lhs, const TType& rhs); + /// @} + + /// A singular type. This type has only one value. When serialized, it takes no space because it carries no data. + /// + /// Historically, YQL's `Void` is what's known as unit type (see https://en.wikipedia.org/wiki/Unit_type), + /// i.e. a type with only one possible value. This is similar to Python's `NoneType` or Rust's `()`. + class TVoidType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TVoidTypePtr AsPtr() const noexcept { + return const_cast<TVoidType*>(this); + } + + private: + explicit TVoidType(); + + public: + static TVoidTypePtr Instance(); + static const TVoidType* InstanceRaw(); + + protected: + const TVoidType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// Create new `Void` type using the default heap factory. + TVoidTypePtr Void(); + + /// A singular type, value of an empty optional. + /// + /// This type is used by YQL for `NULL` literal. Use `TVoidType` unless you have reasons to use this one. + class TNullType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TNullTypePtr AsPtr() const noexcept { + return const_cast<TNullType*>(this); + } + + private: + explicit TNullType(); + + public: + static TNullTypePtr Instance(); + static const TNullType* InstanceRaw(); + + protected: + const TNullType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// Base class for all primitive types. + class TPrimitiveType: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TPrimitiveTypePtr AsPtr() const noexcept { + return const_cast<TPrimitiveType*>(this); + } + + protected: + explicit TPrimitiveType(TMaybe<ui64> hash, EPrimitiveTypeName primitiveTypeName) noexcept; + + public: + /// Get name of this primitive type as a `NTi::EPrimitiveTypeName` enumerator. + EPrimitiveTypeName GetPrimitiveTypeName() const noexcept { + return ToPrimitiveTypeName(GetTypeName()); + } + + /// Cast this scalar class down to the most-derived type and pass it to the `visitor`. + /// + /// This function works like `NTi::TType::Visit`, but only handles primitive types. + /// + /// + /// # Example: + /// + /// ``` + /// auto name = scalar->VisitPrimitive(TOverloaded{ + /// [](TBoolTypePtr t) { return "Bool" }, + /// [](TStringTypePtr t) { return "String" }, + /// // ... + /// }); + /// ``` + template <typename V> + inline decltype(auto) VisitPrimitive(V&& visitor) const; + + /// Like `VisitPrimitive`, but passes raw pointers to the visitor. + template <typename V> + inline decltype(auto) VisitPrimitiveRaw(V&& visitor) const; + }; + + /// A logical type capable of holding one of the two values: true or false. + class TBoolType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TBoolTypePtr AsPtr() const noexcept { + return const_cast<TBoolType*>(this); + } + + private: + explicit TBoolType(); + + public: + static TBoolTypePtr Instance(); + static const TBoolType* InstanceRaw(); + + protected: + const TBoolType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A signed integer, one byte. + class TInt8Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TInt8TypePtr AsPtr() const noexcept { + return const_cast<TInt8Type*>(this); + } + + private: + explicit TInt8Type(); + + public: + static TInt8TypePtr Instance(); + static const TInt8Type* InstanceRaw(); + + protected: + const TInt8Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A signed integer, two bytes. + class TInt16Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TInt16TypePtr AsPtr() const noexcept { + return const_cast<TInt16Type*>(this); + } + + private: + explicit TInt16Type(); + + public: + static TInt16TypePtr Instance(); + static const TInt16Type* InstanceRaw(); + + protected: + const TInt16Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A signed integer, four bytes. + class TInt32Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TInt32TypePtr AsPtr() const noexcept { + return const_cast<TInt32Type*>(this); + } + + private: + explicit TInt32Type(); + + public: + static TInt32TypePtr Instance(); + static const TInt32Type* InstanceRaw(); + + protected: + const TInt32Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A signed integer, eight bytes. + class TInt64Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TInt64TypePtr AsPtr() const noexcept { + return const_cast<TInt64Type*>(this); + } + + private: + explicit TInt64Type(); + + public: + static TInt64TypePtr Instance(); + static const TInt64Type* InstanceRaw(); + + protected: + const TInt64Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// An unsigned integer, one byte. + class TUint8Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TUint8TypePtr AsPtr() const noexcept { + return const_cast<TUint8Type*>(this); + } + + private: + explicit TUint8Type(); + + public: + static TUint8TypePtr Instance(); + static const TUint8Type* InstanceRaw(); + + protected: + const TUint8Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// An unsigned integer, two bytes. + class TUint16Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TUint16TypePtr AsPtr() const noexcept { + return const_cast<TUint16Type*>(this); + } + + private: + explicit TUint16Type(); + + public: + static TUint16TypePtr Instance(); + static const TUint16Type* InstanceRaw(); + + protected: + const TUint16Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// An unsigned integer, four bytes. + class TUint32Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TUint32TypePtr AsPtr() const noexcept { + return const_cast<TUint32Type*>(this); + } + + private: + explicit TUint32Type(); + + public: + static TUint32TypePtr Instance(); + static const TUint32Type* InstanceRaw(); + + protected: + const TUint32Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// An unsigned integer, eight bytes. + class TUint64Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TUint64TypePtr AsPtr() const noexcept { + return const_cast<TUint64Type*>(this); + } + + private: + explicit TUint64Type(); + + public: + static TUint64TypePtr Instance(); + static const TUint64Type* InstanceRaw(); + + protected: + const TUint64Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A floating point number, four bytes. + class TFloatType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TFloatTypePtr AsPtr() const noexcept { + return const_cast<TFloatType*>(this); + } + + private: + explicit TFloatType(); + + public: + static TFloatTypePtr Instance(); + static const TFloatType* InstanceRaw(); + + protected: + const TFloatType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A floating point number, eight bytes. + class TDoubleType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TDoubleTypePtr AsPtr() const noexcept { + return const_cast<TDoubleType*>(this); + } + + private: + explicit TDoubleType(); + + public: + static TDoubleTypePtr Instance(); + static const TDoubleType* InstanceRaw(); + + protected: + const TDoubleType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A binary blob. + /// + /// This type can be used for any binary data. For text, consider using type `Utf8`. + class TStringType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TStringTypePtr AsPtr() const noexcept { + return const_cast<TStringType*>(this); + } + + private: + explicit TStringType(); + + public: + static TStringTypePtr Instance(); + static const TStringType* InstanceRaw(); + + protected: + const TStringType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A utf-8 encoded text. + class TUtf8Type final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TUtf8TypePtr AsPtr() const noexcept { + return const_cast<TUtf8Type*>(this); + } + + private: + explicit TUtf8Type(); + + public: + static TUtf8TypePtr Instance(); + static const TUtf8Type* InstanceRaw(); + + protected: + const TUtf8Type* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// An absolute point in time in range `[1970-01-01, 2106-01-01)`, precision up to days. + class TDateType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TDateTypePtr AsPtr() const noexcept { + return const_cast<TDateType*>(this); + } + + private: + explicit TDateType(); + + public: + static TDateTypePtr Instance(); + static const TDateType* InstanceRaw(); + + protected: + const TDateType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// An absolute point in time in range `[1970-01-01, 2106-01-01)`, precision up to seconds. + class TDatetimeType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TDatetimeTypePtr AsPtr() const noexcept { + return const_cast<TDatetimeType*>(this); + } + + private: + explicit TDatetimeType(); + + public: + static TDatetimeTypePtr Instance(); + static const TDatetimeType* InstanceRaw(); + + protected: + const TDatetimeType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// An absolute point in time in range `[1970-01-01, 2106-01-01)`, precision up to microseconds. + class TTimestampType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TTimestampTypePtr AsPtr() const noexcept { + return const_cast<TTimestampType*>(this); + } + + private: + explicit TTimestampType(); + + public: + static TTimestampTypePtr Instance(); + static const TTimestampType* InstanceRaw(); + + protected: + const TTimestampType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// `TDateType` with additional timezone mark. + class TTzDateType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TTzDateTypePtr AsPtr() const noexcept { + return const_cast<TTzDateType*>(this); + } + + private: + explicit TTzDateType(); + + public: + static TTzDateTypePtr Instance(); + static const TTzDateType* InstanceRaw(); + + protected: + const TTzDateType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// `TDatetimeType` with additional timezone mark. + class TTzDatetimeType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TTzDatetimeTypePtr AsPtr() const noexcept { + return const_cast<TTzDatetimeType*>(this); + } + + private: + explicit TTzDatetimeType(); + + public: + static TTzDatetimeTypePtr Instance(); + static const TTzDatetimeType* InstanceRaw(); + + protected: + const TTzDatetimeType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// `TTimestampType` with additional timezone mark. + class TTzTimestampType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TTzTimestampTypePtr AsPtr() const noexcept { + return const_cast<TTzTimestampType*>(this); + } + + private: + explicit TTzTimestampType(); + + public: + static TTzTimestampTypePtr Instance(); + static const TTzTimestampType* InstanceRaw(); + + protected: + const TTzTimestampType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// Signed delta between two timestamps. + class TIntervalType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TIntervalTypePtr AsPtr() const noexcept { + return const_cast<TIntervalType*>(this); + } + + private: + explicit TIntervalType(); + + public: + static TIntervalTypePtr Instance(); + static const TIntervalType* InstanceRaw(); + + protected: + const TIntervalType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A 128-bit number with controlled exponent/significand size. + /// + /// Decimal is a type for extra precise calculations. Internally, it is represented by a float-like 128-bit number. + /// + /// Two parameters control number of decimal digits in the decimal value. `Precision` is the total number + /// of decimal digits. `Scale` is the number of digits after the decimal point. + class TDecimalType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TDecimalTypePtr AsPtr() const noexcept { + return const_cast<TDecimalType*>(this); + } + + private: + explicit TDecimalType(TMaybe<ui64> hash, ui8 precision, ui8 scale) noexcept; + static TDecimalTypePtr Create(ITypeFactory& factory, ui8 precision, ui8 scale); + static const TDecimalType* CreateRaw(ITypeFactory& factory, ui8 precision, ui8 scale); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get total number of decimal digits. + ui8 GetPrecision() const noexcept { + return Precision_; + } + + /// Get number of decimal digits after the decimal point. + ui8 GetScale() const noexcept { + return Scale_; + } + + protected: + const TDecimalType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + ui8 Precision_; + ui8 Scale_; + }; + + /// A string with valid JSON. + class TJsonType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TJsonTypePtr AsPtr() const noexcept { + return const_cast<TJsonType*>(this); + } + + private: + explicit TJsonType(); + + public: + static TJsonTypePtr Instance(); + static const TJsonType* InstanceRaw(); + + protected: + const TJsonType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A string with valid YSON. + class TYsonType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TYsonTypePtr AsPtr() const noexcept { + return const_cast<TYsonType*>(this); + } + + private: + explicit TYsonType(); + + public: + static TYsonTypePtr Instance(); + static const TYsonType* InstanceRaw(); + + protected: + const TYsonType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// A string with valid UUID. + class TUuidType final: public TPrimitiveType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TUuidTypePtr AsPtr() const noexcept { + return const_cast<TUuidType*>(this); + } + + private: + explicit TUuidType(); + + public: + static TUuidTypePtr Instance(); + static const TUuidType* InstanceRaw(); + + protected: + const TUuidType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + }; + + /// Object which can store a value or a singular `NULL` value. + /// + /// This type is used to encode a value or its absence. + class TOptionalType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TOptionalTypePtr AsPtr() const noexcept { + return const_cast<TOptionalType*>(this); + } + + private: + explicit TOptionalType(TMaybe<ui64> hash, const TType* item) noexcept; + static TOptionalTypePtr Create(ITypeFactory& factory, TTypePtr item); + static const TOptionalType* CreateRaw(ITypeFactory& factory, const TType* item); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get underlying type. + TTypePtr GetItemType() const noexcept { + return GetItemTypeRaw()->AsPtr(); + } + + /// Like `GetMemberType`, but returns a raw pointer. + const TType* GetItemTypeRaw() const noexcept { + return Item_; + } + + protected: + const TOptionalType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + const TType* Item_; + }; + + /// A variable-size collection of homogeneous values. + class TListType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TListTypePtr AsPtr() const noexcept { + return const_cast<TListType*>(this); + } + + private: + explicit TListType(TMaybe<ui64> hash, const TType* item) noexcept; + static TListTypePtr Create(ITypeFactory& factory, TTypePtr item); + static const TListType* CreateRaw(ITypeFactory& factory, const TType* item); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get underlying type. + TTypePtr GetItemType() const noexcept { + return GetItemTypeRaw()->AsPtr(); + } + + /// Like `GetMemberType`, but returns a raw pointer. + const TType* GetItemTypeRaw() const noexcept { + return Item_; + } + + protected: + const TListType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + const TType* Item_; + }; + + /// An associative key-value container. + /// + /// Values of this type are usually represented as hashmaps. + class TDictType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TDictTypePtr AsPtr() const noexcept { + return const_cast<TDictType*>(this); + } + + private: + explicit TDictType(TMaybe<ui64> hash, const TType* key, const TType* value) noexcept; + static TDictTypePtr Create(ITypeFactory& factory, TTypePtr key, TTypePtr value); + static const TDictType* CreateRaw(ITypeFactory& factory, const TType* key, const TType* value); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get the key type. + TTypePtr GetKeyType() const noexcept { + return GetKeyTypeRaw()->AsPtr(); + } + + /// Like `GetKeyType`, but returns a raw pointer. + const TType* GetKeyTypeRaw() const noexcept { + return Key_; + } + + /// Get the value type. + TTypePtr GetValueType() const noexcept { + return GetValueTypeRaw()->AsPtr(); + } + + /// Like `GetValueType`, but returns a raw pointer. + const TType* GetValueTypeRaw() const noexcept { + return Value_; + } + + protected: + const TDictType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + const TType* Key_; + const TType* Value_; + }; + + /// A fixed-size collection of named heterogeneous values. + /// + /// Structs represent multiple values grouped into a single entity. Values stored in a struct are called items. + /// Each item have an associated type and a name. + /// + /// Struct type is represented by an array of item types and their names. + /// + /// Even though struct elements are accessed by their names, we use vector to represent struct type because order + /// of struct items matters. If affects memory layout of a struct, how struct is serialized and deserialized. + /// Items order is vital for struct versioning. New fields should always be added to the end of the struct. + /// This way older parsers can read values serialized by newer writers: they'll simply read known head of a struct + /// and skip tail that contains unknown fields. + /// + /// + /// # Struct names + /// + /// Each struct defined by YDL must have an associated name. This name is used to refer struct in code, to generate + /// code representing this struct in other programming languages, and to report errors. The struct's name is saved + /// in this field. + /// + /// Note that, even though YDL requires struct names, name field is optional because other systems might + /// use anonymous structs (or maybe they dont't use struct names at all). + /// + /// Note also that type aliases (especially `newtype`) use tags to name types, so primitives don't have name field. + class TStructType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + friend class TStructBuilderRaw; + + public: + TStructTypePtr AsPtr() const noexcept { + return const_cast<TStructType*>(this); + } + + public: + /// A single struct element. + class TMember { + public: + TMember(TStringBuf name, const TType* type); + + public: + /// Get name of this item. + TStringBuf GetName() const { + return Name_; + } + + /// Get type of this item. + TTypePtr GetType() const { + return GetTypeRaw()->AsPtr(); + } + + /// Like `GetType`, but returns a raw pointer. + const TType* GetTypeRaw() const { + return Type_; + } + + /// Calculate this item's hash. Hashes follow the 'strict equivalence' (see type_equivalence.h). + ui64 Hash() const; + + private: + TStringBuf Name_; + const TType* Type_; + }; + + using TMembers = TConstArrayRef<TMember>; + + /// Like `TMember`, but owns its contents. Used in non-raw constructors to guarantee data validity. + class TOwnedMember { + public: + TOwnedMember(TString name, TTypePtr type); + + public: + operator TMember() const&; + + private: + TString Name_; + TTypePtr Type_; + }; + + using TOwnedMembers = TConstArrayRef<TOwnedMember>; + + private: + explicit TStructType(TMaybe<ui64> hash, TMaybe<TStringBuf> name, TMembers members, TConstArrayRef<size_t> sortedMembers) noexcept; + static TStructTypePtr Create(ITypeFactory& factory, TOwnedMembers members); + static TStructTypePtr Create(ITypeFactory& factory, TMaybe<TStringBuf> name, TOwnedMembers members); + static const TStructType* CreateRaw(ITypeFactory& factory, TMembers members); + static const TStructType* CreateRaw(ITypeFactory& factory, TMaybe<TStringBuf> name, TMembers members); + static void MakeSortedMembers(TMembers members, TArrayRef<size_t> sortedItems); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get name of this struct. + TMaybe<TStringBuf> GetName() const noexcept { + return Name_; + } + + /// Get description of structure members. + TMembers GetMembers() const noexcept { + return Members_; + } + + /// Check if there is an item with the given name in this struct. + bool HasMember(TStringBuf name) const noexcept; + + /// Lookup struct item by name. Throw an error if there is no such item. + const TMember& GetMember(TStringBuf name) const; + + /// Lookup struct item by name, return its index or `-1`, if item was not found. + /// This function works in `O(log(n))` time, where `n` is number of struct members. + ssize_t GetMemberIndex(TStringBuf name) const noexcept; + + protected: + const TStructType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + TMaybe<TStringBuf> Name_; + TMembers Members_; + TConstArrayRef<size_t> SortedMembers_; + }; + + /// A fixed-size collection of named heterogeneous values. + /// + /// Tuples, like structs, represent multiple values, also called items, grouped into a single entity. Unlike + /// structs, though, tuple items are unnamed. Instead of names, they are accessed by their indexes. + /// + /// For a particular tuple type, number of items, their order and types are fixed, they should be known before + /// creating instances of tuples and can't change over time. + /// + /// + /// # Tuple names + /// + /// YDL requires each tuple definition to have a name (see `TStructType`). The name might not be mandatory + /// in other systems, so name field is optional. + class TTupleType: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + friend class TTupleBuilderRaw; + + public: + TTupleTypePtr AsPtr() const noexcept { + return const_cast<TTupleType*>(this); + } + + public: + /// A single tuple element. + class TElement { + public: + TElement(const TType* type); + + public: + /// Get type of this item. + TTypePtr GetType() const { + return GetTypeRaw()->AsPtr(); + } + + /// Like `GetType`, but returns a raw pointer. + const TType* GetTypeRaw() const { + return Type_; + } + + /// Calculate this item's hash. Hashes follow the 'strict equivalence' (see type_equivalence.h). + ui64 Hash() const; + + private: + const TType* Type_; + }; + + using TElements = TConstArrayRef<TElement>; + + /// Like `TElement`, but owns its contents. Used in non-raw constructors to guarantee data validity. + class TOwnedElement { + public: + TOwnedElement(TTypePtr type); + + public: + operator TElement() const&; + + private: + TTypePtr Type_; + }; + + using TOwnedElements = TConstArrayRef<TOwnedElement>; + + private: + explicit TTupleType(TMaybe<ui64> hash, TMaybe<TStringBuf> name, TElements items) noexcept; + static TTupleTypePtr Create(ITypeFactory& factory, TOwnedElements items); + static TTupleTypePtr Create(ITypeFactory& factory, TMaybe<TStringBuf> name, TOwnedElements items); + static const TTupleType* CreateRaw(ITypeFactory& factory, TElements items); + static const TTupleType* CreateRaw(ITypeFactory& factory, TMaybe<TStringBuf> name, TElements items); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get name of this type. + TMaybe<TStringBuf> GetName() const noexcept { + return Name_; + } + + /// Get description of tuple items. + TElements GetElements() const noexcept { + return Elements_; + } + + protected: + const TTupleType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + TMaybe<TStringBuf> Name_; + TElements Elements_; + }; + + /// A tagged union with named or unnamed alternatives (a.k.a. variant over struct or variant over tuple). + /// + /// Variants are used to store values of different types. + /// + /// For example, a variant over struct which holds an ip address could look like + /// `Ip = Variant<v4: IpV4, v6: IpV6>`, where `IpV4` and `IpV6` are some other types. Now, a value of type `Ip` + /// could store either a value of type `IpV4` or a value of type `IpV6`. + /// + /// Even though item types can be the same, each item represents a distinct state of a variant. For example, + /// a variant for a user identifier can look like `Uid = Variant<yuid: String, ip: String>`. This variant can + /// contain either user's yandexuid of user's ip. Despite both items are of the same type `String`, `Uid` which + /// contains a `yuid` and `Uid` which contains an `ip` are never equal. + /// That is, `Uid.yuid("000000") != Uid.ip("000000")`. + /// + /// Exactly like with structs or tuples, order of variant items matter. Indexes of variant items are used + /// instead of names when variant is serialized. + /// + /// + /// # Variant names + /// + /// YDL requires each variant definition to have a name (see `TStructType`). The name might not be mandatory + /// in other systems, so name field is optional. + class TVariantType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + friend class TStructBuilderRaw; + friend class TTupleBuilderRaw; + + public: + TVariantTypePtr AsPtr() const noexcept { + return const_cast<TVariantType*>(this); + } + + private: + explicit TVariantType(TMaybe<ui64> hash, TMaybe<TStringBuf> name, const TType* inner) noexcept; + static TVariantTypePtr Create(ITypeFactory& factory, TTypePtr inner); + static TVariantTypePtr Create(ITypeFactory& factory, TMaybe<TStringBuf> name, TTypePtr inner); + static const TVariantType* CreateRaw(ITypeFactory& factory, const TType* inner); + static const TVariantType* CreateRaw(ITypeFactory& factory, TMaybe<TStringBuf> name, const TType* inner); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get name of this variant. + TMaybe<TStringBuf> GetName() const noexcept { + return Name_; + } + + /// Get vector of variant items. + TTypePtr GetUnderlyingType() const noexcept { + return Underlying_->AsPtr(); + } + + /// Like `GetUnderlyingType`, but returns a raw pointer. + const TType* GetUnderlyingTypeRaw() const noexcept { + return Underlying_; + } + + /// Check if this variant's inner type is a struct. + bool IsVariantOverStruct() const noexcept { + return GetUnderlyingTypeRaw()->GetTypeName() == ETypeName::Struct; + } + + /// Check if this variant's inner type is a tuple. + bool IsVariantOverTuple() const noexcept { + return GetUnderlyingTypeRaw()->GetTypeName() == ETypeName::Tuple; + } + + /// Visit inner type of this variant. This function works like `Visit`, but only casts inner type + /// to struct or tuple, so you don't need to handle other types in a visitor. + template <typename V> + inline decltype(auto) VisitUnderlying(V&& visitor) const; + + /// Like `VisitUnderlying`, but passes const raw pointers to the visitor. + template <typename V> + inline decltype(auto) VisitUnderlyingRaw(V&& visitor) const; + + protected: + const TVariantType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + TMaybe<TStringBuf> Name_; + const TType* Underlying_; + }; + + /// Named or tagged or user-defined type. + /// + /// Tags are used to create new types from existing ones by assigning them a tag, i.e. a name. They wrap other types + /// adding them additional semantics. + /// + /// On physical level, tags do not change types. Both `Tagged<Int32, 'GeoId'>` and `Int32` have exactly the same + /// representation when serialized. + /// + /// On logical level, tags change semantics of a type. This can affect how types are displayed, how they + /// are checked and converted, etc. + class TTaggedType final: public TType { + friend class TType; + friend class ITypeFactoryInternal; + friend class ITypeFactory; + friend class IPoolTypeFactory; + + public: + TTaggedTypePtr AsPtr() const noexcept { + return const_cast<TTaggedType*>(this); + } + + private: + explicit TTaggedType(TMaybe<ui64> hash, const TType* type, TStringBuf tag) noexcept; + static TTaggedTypePtr Create(ITypeFactory& factory, TTypePtr type, TStringBuf tag); + static const TTaggedType* CreateRaw(ITypeFactory& factory, const TType* type, TStringBuf tag); + + protected: + ui64 CalculateHash() const noexcept; + + public: + /// Get tag content, i.e. name of the new type. + TStringBuf GetTag() const noexcept { + return Tag_; + } + + /// Get the wrapped type. + TTypePtr GetItemType() const noexcept { + return GetItemTypeRaw()->AsPtr(); + } + + /// Like `GetMemberType`, but returns a raw pointer. + const TType* GetItemTypeRaw() const noexcept { + return Item_; + } + + protected: + const TTaggedType* Clone(ITypeFactoryInternal& factory) const noexcept; + void Drop(ITypeFactoryInternal& factory) noexcept; + + private: + const TType* Item_; + TStringBuf Tag_; + }; + +#ifdef __JETBRAINS_IDE__ +#pragma clang diagnostic push +#pragma ide diagnostic ignored "cppcoreguidelines-pro-type-static-cast-downcast" +#endif + + bool TType::IsPrimitive() const noexcept { + return ::NTi::IsPrimitive(TypeName_); + } + + TPrimitiveTypePtr TType::AsPrimitive() const noexcept { + return AsPrimitiveRaw()->AsPtr(); + } + + const TPrimitiveType* TType::AsPrimitiveRaw() const noexcept { + Y_VERIFY(IsPrimitive()); + return static_cast<const TPrimitiveType*>(this); + } + + bool TType::IsVoid() const noexcept { + return TypeName_ == ETypeName::Void; + } + + TVoidTypePtr TType::AsVoid() const noexcept { + return AsVoidRaw()->AsPtr(); + } + + const TVoidType* TType::AsVoidRaw() const noexcept { + Y_VERIFY(IsVoid()); + return static_cast<const TVoidType*>(this); + } + + bool TType::IsNull() const noexcept { + return TypeName_ == ETypeName::Null; + } + + TNullTypePtr TType::AsNull() const noexcept { + return AsNullRaw()->AsPtr(); + } + + const TNullType* TType::AsNullRaw() const noexcept { + Y_VERIFY(IsNull()); + return static_cast<const TNullType*>(this); + } + + bool TType::IsBool() const noexcept { + return TypeName_ == ETypeName::Bool; + } + + TBoolTypePtr TType::AsBool() const noexcept { + return AsBoolRaw()->AsPtr(); + } + + const TBoolType* TType::AsBoolRaw() const noexcept { + Y_VERIFY(IsBool()); + return static_cast<const TBoolType*>(this); + } + + bool TType::IsInt8() const noexcept { + return TypeName_ == ETypeName::Int8; + } + + TInt8TypePtr TType::AsInt8() const noexcept { + return AsInt8Raw()->AsPtr(); + } + + const TInt8Type* TType::AsInt8Raw() const noexcept { + Y_VERIFY(IsInt8()); + return static_cast<const TInt8Type*>(this); + } + + bool TType::IsInt16() const noexcept { + return TypeName_ == ETypeName::Int16; + } + + TInt16TypePtr TType::AsInt16() const noexcept { + return AsInt16Raw()->AsPtr(); + } + + const TInt16Type* TType::AsInt16Raw() const noexcept { + Y_VERIFY(IsInt16()); + return static_cast<const TInt16Type*>(this); + } + + bool TType::IsInt32() const noexcept { + return TypeName_ == ETypeName::Int32; + } + + TInt32TypePtr TType::AsInt32() const noexcept { + return AsInt32Raw()->AsPtr(); + } + + const TInt32Type* TType::AsInt32Raw() const noexcept { + Y_VERIFY(IsInt32()); + return static_cast<const TInt32Type*>(this); + } + + bool TType::IsInt64() const noexcept { + return TypeName_ == ETypeName::Int64; + } + + TInt64TypePtr TType::AsInt64() const noexcept { + return AsInt64Raw()->AsPtr(); + } + + const TInt64Type* TType::AsInt64Raw() const noexcept { + Y_VERIFY(IsInt64()); + return static_cast<const TInt64Type*>(this); + } + + bool TType::IsUint8() const noexcept { + return TypeName_ == ETypeName::Uint8; + } + + TUint8TypePtr TType::AsUint8() const noexcept { + return AsUint8Raw()->AsPtr(); + } + + const TUint8Type* TType::AsUint8Raw() const noexcept { + Y_VERIFY(IsUint8()); + return static_cast<const TUint8Type*>(this); + } + + bool TType::IsUint16() const noexcept { + return TypeName_ == ETypeName::Uint16; + } + + TUint16TypePtr TType::AsUint16() const noexcept { + return AsUint16Raw()->AsPtr(); + } + + const TUint16Type* TType::AsUint16Raw() const noexcept { + Y_VERIFY(IsUint16()); + return static_cast<const TUint16Type*>(this); + } + + bool TType::IsUint32() const noexcept { + return TypeName_ == ETypeName::Uint32; + } + + TUint32TypePtr TType::AsUint32() const noexcept { + return AsUint32Raw()->AsPtr(); + } + + const TUint32Type* TType::AsUint32Raw() const noexcept { + Y_VERIFY(IsUint32()); + return static_cast<const TUint32Type*>(this); + } + + bool TType::IsUint64() const noexcept { + return TypeName_ == ETypeName::Uint64; + } + + TUint64TypePtr TType::AsUint64() const noexcept { + return AsUint64Raw()->AsPtr(); + } + + const TUint64Type* TType::AsUint64Raw() const noexcept { + Y_VERIFY(IsUint64()); + return static_cast<const TUint64Type*>(this); + } + + bool TType::IsFloat() const noexcept { + return TypeName_ == ETypeName::Float; + } + + TFloatTypePtr TType::AsFloat() const noexcept { + return AsFloatRaw()->AsPtr(); + } + + const TFloatType* TType::AsFloatRaw() const noexcept { + Y_VERIFY(IsFloat()); + return static_cast<const TFloatType*>(this); + } + + bool TType::IsDouble() const noexcept { + return TypeName_ == ETypeName::Double; + } + + TDoubleTypePtr TType::AsDouble() const noexcept { + return AsDoubleRaw()->AsPtr(); + } + + const TDoubleType* TType::AsDoubleRaw() const noexcept { + Y_VERIFY(IsDouble()); + return static_cast<const TDoubleType*>(this); + } + + bool TType::IsString() const noexcept { + return TypeName_ == ETypeName::String; + } + + TStringTypePtr TType::AsString() const noexcept { + return AsStringRaw()->AsPtr(); + } + + const TStringType* TType::AsStringRaw() const noexcept { + Y_VERIFY(IsString()); + return static_cast<const TStringType*>(this); + } + + bool TType::IsUtf8() const noexcept { + return TypeName_ == ETypeName::Utf8; + } + + TUtf8TypePtr TType::AsUtf8() const noexcept { + return AsUtf8Raw()->AsPtr(); + } + + const TUtf8Type* TType::AsUtf8Raw() const noexcept { + Y_VERIFY(IsUtf8()); + return static_cast<const TUtf8Type*>(this); + } + + bool TType::IsDate() const noexcept { + return TypeName_ == ETypeName::Date; + } + + TDateTypePtr TType::AsDate() const noexcept { + return AsDateRaw()->AsPtr(); + } + + const TDateType* TType::AsDateRaw() const noexcept { + Y_VERIFY(IsDate()); + return static_cast<const TDateType*>(this); + } + + bool TType::IsDatetime() const noexcept { + return TypeName_ == ETypeName::Datetime; + } + + TDatetimeTypePtr TType::AsDatetime() const noexcept { + return AsDatetimeRaw()->AsPtr(); + } + + const TDatetimeType* TType::AsDatetimeRaw() const noexcept { + Y_VERIFY(IsDatetime()); + return static_cast<const TDatetimeType*>(this); + } + + bool TType::IsTimestamp() const noexcept { + return TypeName_ == ETypeName::Timestamp; + } + + TTimestampTypePtr TType::AsTimestamp() const noexcept { + return AsTimestampRaw()->AsPtr(); + } + + const TTimestampType* TType::AsTimestampRaw() const noexcept { + Y_VERIFY(IsTimestamp()); + return static_cast<const TTimestampType*>(this); + } + + bool TType::IsTzDate() const noexcept { + return TypeName_ == ETypeName::TzDate; + } + + TTzDateTypePtr TType::AsTzDate() const noexcept { + return AsTzDateRaw()->AsPtr(); + } + + const TTzDateType* TType::AsTzDateRaw() const noexcept { + Y_VERIFY(IsTzDate()); + return static_cast<const TTzDateType*>(this); + } + + bool TType::IsTzDatetime() const noexcept { + return TypeName_ == ETypeName::TzDatetime; + } + + TTzDatetimeTypePtr TType::AsTzDatetime() const noexcept { + return AsTzDatetimeRaw()->AsPtr(); + } + + const TTzDatetimeType* TType::AsTzDatetimeRaw() const noexcept { + Y_VERIFY(IsTzDatetime()); + return static_cast<const TTzDatetimeType*>(this); + } + + bool TType::IsTzTimestamp() const noexcept { + return TypeName_ == ETypeName::TzTimestamp; + } + + TTzTimestampTypePtr TType::AsTzTimestamp() const noexcept { + return AsTzTimestampRaw()->AsPtr(); + } + + const TTzTimestampType* TType::AsTzTimestampRaw() const noexcept { + Y_VERIFY(IsTzTimestamp()); + return static_cast<const TTzTimestampType*>(this); + } + + bool TType::IsInterval() const noexcept { + return TypeName_ == ETypeName::Interval; + } + + TIntervalTypePtr TType::AsInterval() const noexcept { + return AsIntervalRaw()->AsPtr(); + } + + const TIntervalType* TType::AsIntervalRaw() const noexcept { + Y_VERIFY(IsInterval()); + return static_cast<const TIntervalType*>(this); + } + + bool TType::IsDecimal() const noexcept { + return TypeName_ == ETypeName::Decimal; + } + + TDecimalTypePtr TType::AsDecimal() const noexcept { + return AsDecimalRaw()->AsPtr(); + } + + const TDecimalType* TType::AsDecimalRaw() const noexcept { + Y_VERIFY(IsDecimal()); + return static_cast<const TDecimalType*>(this); + } + + bool TType::IsJson() const noexcept { + return TypeName_ == ETypeName::Json; + } + + TJsonTypePtr TType::AsJson() const noexcept { + return AsJsonRaw()->AsPtr(); + } + + const TJsonType* TType::AsJsonRaw() const noexcept { + Y_VERIFY(IsJson()); + return static_cast<const TJsonType*>(this); + } + + bool TType::IsYson() const noexcept { + return TypeName_ == ETypeName::Yson; + } + + TYsonTypePtr TType::AsYson() const noexcept { + return AsYsonRaw()->AsPtr(); + } + + const TYsonType* TType::AsYsonRaw() const noexcept { + Y_VERIFY(IsYson()); + return static_cast<const TYsonType*>(this); + } + + bool TType::IsUuid() const noexcept { + return TypeName_ == ETypeName::Uuid; + } + + TUuidTypePtr TType::AsUuid() const noexcept { + return AsUuidRaw()->AsPtr(); + } + + const TUuidType* TType::AsUuidRaw() const noexcept { + Y_VERIFY(IsUuid()); + return static_cast<const TUuidType*>(this); + } + + bool TType::IsOptional() const noexcept { + return TypeName_ == ETypeName::Optional; + } + + TOptionalTypePtr TType::AsOptional() const noexcept { + return AsOptionalRaw()->AsPtr(); + } + + const TOptionalType* TType::AsOptionalRaw() const noexcept { + Y_VERIFY(IsOptional()); + return static_cast<const TOptionalType*>(this); + } + + bool TType::IsList() const noexcept { + return TypeName_ == ETypeName::List; + } + + TListTypePtr TType::AsList() const noexcept { + return AsListRaw()->AsPtr(); + } + + const TListType* TType::AsListRaw() const noexcept { + Y_VERIFY(IsList()); + return static_cast<const TListType*>(this); + } + + bool TType::IsDict() const noexcept { + return TypeName_ == ETypeName::Dict; + } + + TDictTypePtr TType::AsDict() const noexcept { + return AsDictRaw()->AsPtr(); + } + + const TDictType* TType::AsDictRaw() const noexcept { + Y_VERIFY(IsDict()); + return static_cast<const TDictType*>(this); + } + + bool TType::IsStruct() const noexcept { + return TypeName_ == ETypeName::Struct; + } + + TStructTypePtr TType::AsStruct() const noexcept { + return AsStructRaw()->AsPtr(); + } + + const TStructType* TType::AsStructRaw() const noexcept { + Y_VERIFY(IsStruct()); + return static_cast<const TStructType*>(this); + } + + bool TType::IsTuple() const noexcept { + return TypeName_ == ETypeName::Tuple; + } + + TTupleTypePtr TType::AsTuple() const noexcept { + return AsTupleRaw()->AsPtr(); + } + + const TTupleType* TType::AsTupleRaw() const noexcept { + Y_VERIFY(IsTuple()); + return static_cast<const TTupleType*>(this); + } + + bool TType::IsVariant() const noexcept { + return TypeName_ == ETypeName::Variant; + } + + TVariantTypePtr TType::AsVariant() const noexcept { + return AsVariantRaw()->AsPtr(); + } + + const TVariantType* TType::AsVariantRaw() const noexcept { + Y_VERIFY(IsVariant()); + return static_cast<const TVariantType*>(this); + } + + bool TType::IsTagged() const noexcept { + return TypeName_ == ETypeName::Tagged; + } + + TTaggedTypePtr TType::AsTagged() const noexcept { + return AsTaggedRaw()->AsPtr(); + } + + const TTaggedType* TType::AsTaggedRaw() const noexcept { + Y_VERIFY(IsTagged()); + return static_cast<const TTaggedType*>(this); + } + +#ifdef __JETBRAINS_IDE__ +#pragma clang diagnostic pop +#endif + + template <typename V> + decltype(auto) TType::Visit(V&& visitor) const { + switch (TypeName_) { + case ETypeName::Bool: + return std::forward<V>(visitor)(this->AsBool()); + case ETypeName::Int8: + return std::forward<V>(visitor)(this->AsInt8()); + case ETypeName::Int16: + return std::forward<V>(visitor)(this->AsInt16()); + case ETypeName::Int32: + return std::forward<V>(visitor)(this->AsInt32()); + case ETypeName::Int64: + return std::forward<V>(visitor)(this->AsInt64()); + case ETypeName::Uint8: + return std::forward<V>(visitor)(this->AsUint8()); + case ETypeName::Uint16: + return std::forward<V>(visitor)(this->AsUint16()); + case ETypeName::Uint32: + return std::forward<V>(visitor)(this->AsUint32()); + case ETypeName::Uint64: + return std::forward<V>(visitor)(this->AsUint64()); + case ETypeName::Float: + return std::forward<V>(visitor)(this->AsFloat()); + case ETypeName::Double: + return std::forward<V>(visitor)(this->AsDouble()); + case ETypeName::String: + return std::forward<V>(visitor)(this->AsString()); + case ETypeName::Utf8: + return std::forward<V>(visitor)(this->AsUtf8()); + case ETypeName::Date: + return std::forward<V>(visitor)(this->AsDate()); + case ETypeName::Datetime: + return std::forward<V>(visitor)(this->AsDatetime()); + case ETypeName::Timestamp: + return std::forward<V>(visitor)(this->AsTimestamp()); + case ETypeName::TzDate: + return std::forward<V>(visitor)(this->AsTzDate()); + case ETypeName::TzDatetime: + return std::forward<V>(visitor)(this->AsTzDatetime()); + case ETypeName::TzTimestamp: + return std::forward<V>(visitor)(this->AsTzTimestamp()); + case ETypeName::Interval: + return std::forward<V>(visitor)(this->AsInterval()); + case ETypeName::Decimal: + return std::forward<V>(visitor)(this->AsDecimal()); + case ETypeName::Json: + return std::forward<V>(visitor)(this->AsJson()); + case ETypeName::Yson: + return std::forward<V>(visitor)(this->AsYson()); + case ETypeName::Uuid: + return std::forward<V>(visitor)(this->AsUuid()); + case ETypeName::Void: + return std::forward<V>(visitor)(this->AsVoid()); + case ETypeName::Null: + return std::forward<V>(visitor)(this->AsNull()); + case ETypeName::Optional: + return std::forward<V>(visitor)(this->AsOptional()); + case ETypeName::List: + return std::forward<V>(visitor)(this->AsList()); + case ETypeName::Dict: + return std::forward<V>(visitor)(this->AsDict()); + case ETypeName::Struct: + return std::forward<V>(visitor)(this->AsStruct()); + case ETypeName::Tuple: + return std::forward<V>(visitor)(this->AsTuple()); + case ETypeName::Variant: + return std::forward<V>(visitor)(this->AsVariant()); + case ETypeName::Tagged: + return std::forward<V>(visitor)(this->AsTagged()); + } + + Y_UNREACHABLE(); + } + + template <typename V> + decltype(auto) TType::VisitRaw(V&& visitor) const { + switch (TypeName_) { + case ETypeName::Bool: + return std::forward<V>(visitor)(this->AsBoolRaw()); + case ETypeName::Int8: + return std::forward<V>(visitor)(this->AsInt8Raw()); + case ETypeName::Int16: + return std::forward<V>(visitor)(this->AsInt16Raw()); + case ETypeName::Int32: + return std::forward<V>(visitor)(this->AsInt32Raw()); + case ETypeName::Int64: + return std::forward<V>(visitor)(this->AsInt64Raw()); + case ETypeName::Uint8: + return std::forward<V>(visitor)(this->AsUint8Raw()); + case ETypeName::Uint16: + return std::forward<V>(visitor)(this->AsUint16Raw()); + case ETypeName::Uint32: + return std::forward<V>(visitor)(this->AsUint32Raw()); + case ETypeName::Uint64: + return std::forward<V>(visitor)(this->AsUint64Raw()); + case ETypeName::Float: + return std::forward<V>(visitor)(this->AsFloatRaw()); + case ETypeName::Double: + return std::forward<V>(visitor)(this->AsDoubleRaw()); + case ETypeName::String: + return std::forward<V>(visitor)(this->AsStringRaw()); + case ETypeName::Utf8: + return std::forward<V>(visitor)(this->AsUtf8Raw()); + case ETypeName::Date: + return std::forward<V>(visitor)(this->AsDateRaw()); + case ETypeName::Datetime: + return std::forward<V>(visitor)(this->AsDatetimeRaw()); + case ETypeName::Timestamp: + return std::forward<V>(visitor)(this->AsTimestampRaw()); + case ETypeName::TzDate: + return std::forward<V>(visitor)(this->AsTzDateRaw()); + case ETypeName::TzDatetime: + return std::forward<V>(visitor)(this->AsTzDatetimeRaw()); + case ETypeName::TzTimestamp: + return std::forward<V>(visitor)(this->AsTzTimestampRaw()); + case ETypeName::Interval: + return std::forward<V>(visitor)(this->AsIntervalRaw()); + case ETypeName::Decimal: + return std::forward<V>(visitor)(this->AsDecimalRaw()); + case ETypeName::Json: + return std::forward<V>(visitor)(this->AsJsonRaw()); + case ETypeName::Yson: + return std::forward<V>(visitor)(this->AsYsonRaw()); + case ETypeName::Uuid: + return std::forward<V>(visitor)(this->AsUuidRaw()); + case ETypeName::Void: + return std::forward<V>(visitor)(this->AsVoidRaw()); + case ETypeName::Null: + return std::forward<V>(visitor)(this->AsNullRaw()); + case ETypeName::Optional: + return std::forward<V>(visitor)(this->AsOptionalRaw()); + case ETypeName::List: + return std::forward<V>(visitor)(this->AsListRaw()); + case ETypeName::Dict: + return std::forward<V>(visitor)(this->AsDictRaw()); + case ETypeName::Struct: + return std::forward<V>(visitor)(this->AsStructRaw()); + case ETypeName::Tuple: + return std::forward<V>(visitor)(this->AsTupleRaw()); + case ETypeName::Variant: + return std::forward<V>(visitor)(this->AsVariantRaw()); + case ETypeName::Tagged: + return std::forward<V>(visitor)(this->AsTaggedRaw()); + } + + Y_UNREACHABLE(); + } + + template <typename V> + decltype(auto) TPrimitiveType::VisitPrimitive(V&& visitor) const { + switch (GetPrimitiveTypeName()) { + case EPrimitiveTypeName::Bool: + return std::forward<V>(visitor)(this->AsBool()); + case EPrimitiveTypeName::Int8: + return std::forward<V>(visitor)(this->AsInt8()); + case EPrimitiveTypeName::Int16: + return std::forward<V>(visitor)(this->AsInt16()); + case EPrimitiveTypeName::Int32: + return std::forward<V>(visitor)(this->AsInt32()); + case EPrimitiveTypeName::Int64: + return std::forward<V>(visitor)(this->AsInt64()); + case EPrimitiveTypeName::Uint8: + return std::forward<V>(visitor)(this->AsUint8()); + case EPrimitiveTypeName::Uint16: + return std::forward<V>(visitor)(this->AsUint16()); + case EPrimitiveTypeName::Uint32: + return std::forward<V>(visitor)(this->AsUint32()); + case EPrimitiveTypeName::Uint64: + return std::forward<V>(visitor)(this->AsUint64()); + case EPrimitiveTypeName::Float: + return std::forward<V>(visitor)(this->AsFloat()); + case EPrimitiveTypeName::Double: + return std::forward<V>(visitor)(this->AsDouble()); + case EPrimitiveTypeName::String: + return std::forward<V>(visitor)(this->AsString()); + case EPrimitiveTypeName::Utf8: + return std::forward<V>(visitor)(this->AsUtf8()); + case EPrimitiveTypeName::Date: + return std::forward<V>(visitor)(this->AsDate()); + case EPrimitiveTypeName::Datetime: + return std::forward<V>(visitor)(this->AsDatetime()); + case EPrimitiveTypeName::Timestamp: + return std::forward<V>(visitor)(this->AsTimestamp()); + case EPrimitiveTypeName::TzDate: + return std::forward<V>(visitor)(this->AsTzDate()); + case EPrimitiveTypeName::TzDatetime: + return std::forward<V>(visitor)(this->AsTzDatetime()); + case EPrimitiveTypeName::TzTimestamp: + return std::forward<V>(visitor)(this->AsTzTimestamp()); + case EPrimitiveTypeName::Interval: + return std::forward<V>(visitor)(this->AsInterval()); + case EPrimitiveTypeName::Decimal: + return std::forward<V>(visitor)(this->AsDecimal()); + case EPrimitiveTypeName::Json: + return std::forward<V>(visitor)(this->AsJson()); + case EPrimitiveTypeName::Yson: + return std::forward<V>(visitor)(this->AsYson()); + case EPrimitiveTypeName::Uuid: + return std::forward<V>(visitor)(this->AsUuid()); + } + + Y_UNREACHABLE(); + } + + template <typename V> + decltype(auto) TPrimitiveType::VisitPrimitiveRaw(V&& visitor) const { + switch (GetPrimitiveTypeName()) { + case EPrimitiveTypeName::Bool: + return std::forward<V>(visitor)(this->AsBoolRaw()); + case EPrimitiveTypeName::Int8: + return std::forward<V>(visitor)(this->AsInt8Raw()); + case EPrimitiveTypeName::Int16: + return std::forward<V>(visitor)(this->AsInt16Raw()); + case EPrimitiveTypeName::Int32: + return std::forward<V>(visitor)(this->AsInt32Raw()); + case EPrimitiveTypeName::Int64: + return std::forward<V>(visitor)(this->AsInt64Raw()); + case EPrimitiveTypeName::Uint8: + return std::forward<V>(visitor)(this->AsUint8Raw()); + case EPrimitiveTypeName::Uint16: + return std::forward<V>(visitor)(this->AsUint16Raw()); + case EPrimitiveTypeName::Uint32: + return std::forward<V>(visitor)(this->AsUint32Raw()); + case EPrimitiveTypeName::Uint64: + return std::forward<V>(visitor)(this->AsUint64Raw()); + case EPrimitiveTypeName::Float: + return std::forward<V>(visitor)(this->AsFloatRaw()); + case EPrimitiveTypeName::Double: + return std::forward<V>(visitor)(this->AsDoubleRaw()); + case EPrimitiveTypeName::String: + return std::forward<V>(visitor)(this->AsStringRaw()); + case EPrimitiveTypeName::Utf8: + return std::forward<V>(visitor)(this->AsUtf8Raw()); + case EPrimitiveTypeName::Date: + return std::forward<V>(visitor)(this->AsDateRaw()); + case EPrimitiveTypeName::Datetime: + return std::forward<V>(visitor)(this->AsDatetimeRaw()); + case EPrimitiveTypeName::Timestamp: + return std::forward<V>(visitor)(this->AsTimestampRaw()); + case EPrimitiveTypeName::TzDate: + return std::forward<V>(visitor)(this->AsTzDateRaw()); + case EPrimitiveTypeName::TzDatetime: + return std::forward<V>(visitor)(this->AsTzDatetimeRaw()); + case EPrimitiveTypeName::TzTimestamp: + return std::forward<V>(visitor)(this->AsTzTimestampRaw()); + case EPrimitiveTypeName::Interval: + return std::forward<V>(visitor)(this->AsIntervalRaw()); + case EPrimitiveTypeName::Decimal: + return std::forward<V>(visitor)(this->AsDecimalRaw()); + case EPrimitiveTypeName::Json: + return std::forward<V>(visitor)(this->AsJsonRaw()); + case EPrimitiveTypeName::Yson: + return std::forward<V>(visitor)(this->AsYsonRaw()); + case EPrimitiveTypeName::Uuid: + return std::forward<V>(visitor)(this->AsUuidRaw()); + } + + Y_UNREACHABLE(); + } + + template <typename V> + decltype(auto) TVariantType::VisitUnderlying(V&& visitor) const { + switch (GetUnderlyingTypeRaw()->GetTypeName()) { + case ETypeName::Struct: + return std::forward<V>(visitor)(this->GetUnderlyingTypeRaw()->AsStruct()); + case ETypeName::Tuple: + return std::forward<V>(visitor)(this->GetUnderlyingTypeRaw()->AsTuple()); + default: + Y_UNREACHABLE(); + } + } + + template <typename V> + decltype(auto) TVariantType::VisitUnderlyingRaw(V&& visitor) const { + switch (GetUnderlyingTypeRaw()->GetTypeName()) { + case ETypeName::Struct: + return std::forward<V>(visitor)(this->GetUnderlyingTypeRaw()->AsStructRaw()); + case ETypeName::Tuple: + return std::forward<V>(visitor)(this->GetUnderlyingTypeRaw()->AsTupleRaw()); + default: + Y_UNREACHABLE(); + } + } +} // namespace NTi diff --git a/library/cpp/type_info/type_complexity.cpp b/library/cpp/type_info/type_complexity.cpp new file mode 100644 index 0000000000..073c24b909 --- /dev/null +++ b/library/cpp/type_info/type_complexity.cpp @@ -0,0 +1,78 @@ +#include "type_complexity.h" + +#include "type.h" + + +namespace NTi { + +int ComputeTypeComplexity(const TTypePtr& type) +{ + return ComputeTypeComplexity(type.Get()); +} + +int ComputeTypeComplexity(const TType* type) +{ + switch (type->GetTypeName()) { + case ETypeName::Bool: + case ETypeName::Int8: + case ETypeName::Int16: + case ETypeName::Int32: + case ETypeName::Int64: + case ETypeName::Uint8: + case ETypeName::Uint16: + case ETypeName::Uint32: + case ETypeName::Uint64: + case ETypeName::Float: + case ETypeName::Double: + case ETypeName::String: + case ETypeName::Utf8: + case ETypeName::Date: + case ETypeName::Datetime: + case ETypeName::Timestamp: + case ETypeName::TzDate: + case ETypeName::TzDatetime: + case ETypeName::TzTimestamp: + case ETypeName::Interval: + case ETypeName::Decimal: + case ETypeName::Json: + case ETypeName::Yson: + case ETypeName::Uuid: + case ETypeName::Void: + case ETypeName::Null: + return 1; + + case ETypeName::Optional: + return 1 + ComputeTypeComplexity(type->AsOptionalRaw()->GetItemTypeRaw()); + + case ETypeName::List: + return 1 + ComputeTypeComplexity(type->AsListRaw()->GetItemTypeRaw()); + + case ETypeName::Dict: + return 1 + ComputeTypeComplexity(type->AsDictRaw()->GetKeyTypeRaw()) + + ComputeTypeComplexity(type->AsDictRaw()->GetValueTypeRaw()); + case ETypeName::Struct: { + int result = 1; + for (const auto& member : type->AsStructRaw()->GetMembers()) { + result += ComputeTypeComplexity(member.GetTypeRaw()); + } + return result; + } + case ETypeName::Tuple: { + int result = 1; + for (const auto& element : type->AsTupleRaw()->GetElements()) { + result += ComputeTypeComplexity(element.GetTypeRaw()); + } + return result; + } + case ETypeName::Variant: { + return ComputeTypeComplexity(type->AsVariantRaw()->GetUnderlyingTypeRaw()); + } + case ETypeName::Tagged: { + return 1 + ComputeTypeComplexity(type->AsTaggedRaw()->GetItemType()); + } + } + Y_FAIL("internal error: unreachable code"); +} + +} // namespace NTi + diff --git a/library/cpp/type_info/type_complexity.h b/library/cpp/type_info/type_complexity.h new file mode 100644 index 0000000000..fcf367eb81 --- /dev/null +++ b/library/cpp/type_info/type_complexity.h @@ -0,0 +1,18 @@ +#pragma once +#include "fwd.h" + +namespace NTi { + + /// + /// Compute type complexity. + /// Roughly speaking type complexity is a number of nodes in the schema tree. + /// Examples: + /// - Type complexity of simple or singular type (i.e. Int64, String, Null, Decimal) is 1. + /// - Type complexity of `Optional<Int64>` is 2 + /// - Type complexity of `Struct<a:Int64,b:Optional<String>` is 4 (1 for Int64, 2 for Optional<String> and 1 for struct) + /// + /// Systems might impose restrictions on the type complexity. + int ComputeTypeComplexity(const TTypePtr& type); + int ComputeTypeComplexity(const TType* type); + +} // namespace NTi diff --git a/library/cpp/type_info/type_constructors.h b/library/cpp/type_info/type_constructors.h new file mode 100644 index 0000000000..439d5a1887 --- /dev/null +++ b/library/cpp/type_info/type_constructors.h @@ -0,0 +1,110 @@ +#pragma once + +//! @file type_constructors.h + +#include "type.h" + +namespace NTi { + /// Create new `Null` type using the default heap factory. + TNullTypePtr Null(); + + /// Create new `Bool` type using the default heap factory. + TBoolTypePtr Bool(); + + /// Create new `Int8` type using the default heap factory. + TInt8TypePtr Int8(); + + /// Create new `Int16` type using the default heap factory. + TInt16TypePtr Int16(); + + /// Create new `Int32` type using the default heap factory. + TInt32TypePtr Int32(); + + /// Create new `Int64` type using the default heap factory. + TInt64TypePtr Int64(); + + /// Create new `Uint8` type using the default heap factory. + TUint8TypePtr Uint8(); + + /// Create new `Uint16` type using the default heap factory. + TUint16TypePtr Uint16(); + + /// Create new `Uint32` type using the default heap factory. + TUint32TypePtr Uint32(); + + /// Create new `Uint64` type using the default heap factory. + TUint64TypePtr Uint64(); + + /// Create new `Float` type using the default heap factory. + TFloatTypePtr Float(); + + /// Create new `Double` type using the default heap factory. + TDoubleTypePtr Double(); + + /// Create new `String` type using the default heap factory. + TStringTypePtr String(); + + /// Create new `Utf8` type using the default heap factory. + TUtf8TypePtr Utf8(); + + /// Create new `Date` type using the default heap factory. + TDateTypePtr Date(); + + /// Create new `Datetime` type using the default heap factory. + TDatetimeTypePtr Datetime(); + + /// Create new `Timestamp` type using the default heap factory. + TTimestampTypePtr Timestamp(); + + /// Create new `TzDate` type using the default heap factory. + TTzDateTypePtr TzDate(); + + /// Create new `TzDatetime` type using the default heap factory. + TTzDatetimeTypePtr TzDatetime(); + + /// Create new `TzTimestamp` type using the default heap factory. + TTzTimestampTypePtr TzTimestamp(); + + /// Create new `Interval` type using the default heap factory. + TIntervalTypePtr Interval(); + + /// Create new `Decimal` type using the default heap factory. + TDecimalTypePtr Decimal(ui8 precision, ui8 scale); + + /// Create new `Json` type using the default heap factory. + TJsonTypePtr Json(); + + /// Create new `Yson` type using the default heap factory. + TYsonTypePtr Yson(); + + /// Create new `Uuid` type using the default heap factory. + TUuidTypePtr Uuid(); + + /// Create new `Optional` type using the default heap factory. + TOptionalTypePtr Optional(TTypePtr item); + + /// Create new `List` type using the default heap factory. + TListTypePtr List(TTypePtr item); + + /// Create new `Dict` type using the default heap factory. + TDictTypePtr Dict(TTypePtr key, TTypePtr value); + + /// Create new `Struct` type using the default heap factory. + TStructTypePtr Struct(TStructType::TOwnedMembers items); + /// Create new `Struct` type using the default heap factory. + TStructTypePtr Struct(TMaybe<TStringBuf> name, TStructType::TOwnedMembers items); + + /// Create new `Tuple` type using the default heap factory. + TTupleTypePtr Tuple(TTupleType::TOwnedElements items); + /// Create new `Tuple` type using the default heap factory. + TTupleTypePtr Tuple(TMaybe<TStringBuf> name, TTupleType::TOwnedElements items); + + /// Create new `Variant` type using the default heap factory. + TVariantTypePtr Variant(TTypePtr underlying); + /// Create new `Variant` type using the default heap factory. + TVariantTypePtr Variant(TMaybe<TStringBuf> name, TTypePtr underlying); + + /// Create new `Tagged` type using the default heap factory. + TTaggedTypePtr Tagged(TTypePtr type, TStringBuf tag); + +} // namespace NTi diff --git a/library/cpp/type_info/type_equivalence.cpp b/library/cpp/type_info/type_equivalence.cpp new file mode 100644 index 0000000000..9f22f0308c --- /dev/null +++ b/library/cpp/type_info/type_equivalence.cpp @@ -0,0 +1,286 @@ +#include "type_equivalence.h" + +#include <util/generic/overloaded.h> + +#include "type.h" + +namespace NTi::NEq { + namespace { + template <bool IgnoreHash> + bool StrictlyEqual(const TType* lhs, const TType* rhs); + + // template <bool IgnoreHash> + // bool StrictlyEqual(const TDocumentation& lhs, const TDocumentation& rhs) { + // ... + // } + + // template <bool IgnoreHash> + // bool StrictlyEqual(const TAnnotations& lhs, const TAnnotations& rhs) { + // ... + // } + + template <bool IgnoreHash> + bool StrictlyEqual(const TVoidType&, const TVoidType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TNullType&, const TNullType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TBoolType&, const TBoolType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TInt8Type&, const TInt8Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TInt16Type&, const TInt16Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TInt32Type&, const TInt32Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TInt64Type&, const TInt64Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TUint8Type&, const TUint8Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TUint16Type&, const TUint16Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TUint32Type&, const TUint32Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TUint64Type&, const TUint64Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TFloatType&, const TFloatType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TDoubleType&, const TDoubleType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TStringType&, const TStringType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TUtf8Type&, const TUtf8Type&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TDateType&, const TDateType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TDatetimeType&, const TDatetimeType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TTimestampType&, const TTimestampType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TTzDateType&, const TTzDateType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TTzDatetimeType&, const TTzDatetimeType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TTzTimestampType&, const TTzTimestampType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TIntervalType&, const TIntervalType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TDecimalType& lhs, const TDecimalType& rhs) { + return lhs.GetPrecision() == rhs.GetPrecision() && lhs.GetScale() == rhs.GetScale(); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TJsonType&, const TJsonType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TYsonType&, const TYsonType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TUuidType&, const TUuidType&) { + return true; + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TOptionalType& lhs, const TOptionalType& rhs) { + return StrictlyEqual<IgnoreHash>(lhs.GetItemTypeRaw(), rhs.GetItemTypeRaw()); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TListType& lhs, const TListType& rhs) { + return StrictlyEqual<IgnoreHash>(lhs.GetItemTypeRaw(), rhs.GetItemTypeRaw()); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TDictType& lhs, const TDictType& rhs) { + return StrictlyEqual<IgnoreHash>(lhs.GetKeyTypeRaw(), rhs.GetKeyTypeRaw()) && + StrictlyEqual<IgnoreHash>(lhs.GetValueTypeRaw(), rhs.GetValueTypeRaw()); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TStructType& lhs, const TStructType& rhs) { + if (lhs.GetName() != rhs.GetName()) { + return false; + } + + return std::equal( + lhs.GetMembers().begin(), lhs.GetMembers().end(), + rhs.GetMembers().begin(), rhs.GetMembers().end(), + [](const TStructType::TMember& lhs, const TStructType::TMember& rhs) -> bool { + return lhs.GetName() == rhs.GetName() && + StrictlyEqual<IgnoreHash>(lhs.GetTypeRaw(), rhs.GetTypeRaw()); + // && StrictlyEqual(lhs.GetAnnotations(), rhs.GetAnnotations()) + // && StrictlyEqual(lhs.GetDocumentation(), rhs.GetDocumentation()); + }); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TTupleType& lhs, const TTupleType& rhs) { + if (lhs.GetName() != rhs.GetName()) { + return false; + } + + return std::equal( + lhs.GetElements().begin(), lhs.GetElements().end(), + rhs.GetElements().begin(), rhs.GetElements().end(), + [](const TTupleType::TElement& lhs, const TTupleType::TElement& rhs) -> bool { + return StrictlyEqual<IgnoreHash>(lhs.GetTypeRaw(), rhs.GetTypeRaw()); + // && StrictlyEqual(lhs.GetAnnotations(), rhs.GetAnnotations()) + // && StrictlyEqual(lhs.GetDocumentation(), rhs.GetDocumentation()); + }); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TVariantType& lhs, const TVariantType& rhs) { + if (lhs.GetName() != rhs.GetName()) { + return false; + } + + return StrictlyEqual<IgnoreHash>(lhs.GetUnderlyingTypeRaw(), rhs.GetUnderlyingTypeRaw()); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TTaggedType& lhs, const TTaggedType& rhs) { + return lhs.GetTag() == rhs.GetTag() && + StrictlyEqual<IgnoreHash>(lhs.GetItemTypeRaw(), rhs.GetItemTypeRaw()); + } + + template <bool IgnoreHash> + bool StrictlyEqual(const TType* lhs, const TType* rhs) { + if (lhs == rhs) { + return true; + } + + if (lhs == nullptr || rhs == nullptr) { + return false; + } + + if (!IgnoreHash && lhs->GetHash() != rhs->GetHash()) { + return false; + } + + if (lhs->GetTypeName() != rhs->GetTypeName()) { + return false; + } + + // FIXME: update `TStrictlyEqual`'s docs to explicitly state that documentation and annotations + // must be the same for types to compare strictly equal. + + // if (!StrictlyEqual(lhs->GetAnnotations(), rhs->GetAnnotations())) { + // return false; + // } + + // if (!StrictlyEqual(lhs->GetDocumentation(), rhs->GetDocumentation())) { + // return false; + // } + + return lhs->VisitRaw([&rhs](const auto* lhs) { + using TSameType = decltype(lhs); + return rhs->VisitRaw(TOverloaded{ + [lhs](TSameType rhs) { + return StrictlyEqual<IgnoreHash>(*lhs, *rhs); + }, + [](const auto* rhs) { + static_assert(!std::is_same_v<decltype(lhs), decltype(rhs)>); + return false; + }}); + }); + } + } + + bool TStrictlyEqual::operator()(const TType* lhs, const TType* rhs) const { + return StrictlyEqual<false>(lhs, rhs); + } + + bool TStrictlyEqual::operator()(TTypePtr lhs, TTypePtr rhs) const { + return operator()(lhs.Get(), rhs.Get()); + } + + bool TStrictlyEqual::IgnoreHash(const TType* lhs, const TType* rhs) const { + return StrictlyEqual<true>(lhs, rhs); + } + + bool TStrictlyEqual::IgnoreHash(TTypePtr lhs, TTypePtr rhs) const { + return IgnoreHash(lhs.Get(), rhs.Get()); + } + + ui64 TStrictlyEqualHash::operator()(const TType* type) const { + if (type == nullptr) { + return 0; + } + + return type->GetHash(); + } + + ui64 TStrictlyEqualHash::operator()(TTypePtr type) const { + return operator()(type.Get()); + } +} diff --git a/library/cpp/type_info/type_equivalence.h b/library/cpp/type_info/type_equivalence.h new file mode 100644 index 0000000000..70351dc689 --- /dev/null +++ b/library/cpp/type_info/type_equivalence.h @@ -0,0 +1,36 @@ +#pragma once + +//! @file type_equivalence.h +//! +//! Relations between types. +//! +//! Type info declares multiple ways to compare types. There's strict, nominal, structural and other equivalences, +//! as well as subtyping. See corresponding functors for more info. +//! +//! At the moment, only strict equivalence is implemented because others are not yet standartized. + +#include <type_traits> + +#include "fwd.h" + +#include <util/system/types.h> + +namespace NTi::NEq { + /// Strict equivalence is the strongest form of type equivalence. If two types are strictly equal, + /// they're literally the same type for all intents and purposes. This includes struct, tuple, variant and enum + /// names, order of their items, names of their items, names of tagged types, and so on. + struct TStrictlyEqual { + bool operator()(const TType* lhs, const TType* rhs) const; + bool operator()(TTypePtr lhs, TTypePtr rhs) const; + + /// Compare types without calculating and comparing their hashes first. + bool IgnoreHash(const TType* lhs, const TType* rhs) const; + bool IgnoreHash(TTypePtr lhs, TTypePtr rhs) const; + }; + + /// Hash that follows the strict equality rules (see `TStrictlyEqual`). + struct TStrictlyEqualHash { + ui64 operator()(const TType* type) const; + ui64 operator()(TTypePtr type) const; + }; +} diff --git a/library/cpp/type_info/type_factory.cpp b/library/cpp/type_info/type_factory.cpp new file mode 100644 index 0000000000..9d60307938 --- /dev/null +++ b/library/cpp/type_info/type_factory.cpp @@ -0,0 +1,495 @@ +#include "type_factory.h" + +#include "type.h" +#include "type_equivalence.h" + +#include <util/memory/pool.h> +#include <util/generic/hash_set.h> + +#include <cstdlib> + +namespace NTi { + TVoidTypePtr ITypeFactory::Void() { + return TVoidType::Instance(); + } + + const TVoidType* IPoolTypeFactory::VoidRaw() { + return TVoidType::InstanceRaw(); + } + + TNullTypePtr ITypeFactory::Null() { + return TNullType::Instance(); + } + + const TNullType* IPoolTypeFactory::NullRaw() { + return TNullType::InstanceRaw(); + } + + TBoolTypePtr ITypeFactory::Bool() { + return TBoolType::Instance(); + } + + const TBoolType* IPoolTypeFactory::BoolRaw() { + return TBoolType::InstanceRaw(); + } + + TInt8TypePtr ITypeFactory::Int8() { + return TInt8Type::Instance(); + } + + const TInt8Type* IPoolTypeFactory::Int8Raw() { + return TInt8Type::InstanceRaw(); + } + + TInt16TypePtr ITypeFactory::Int16() { + return TInt16Type::Instance(); + } + + const TInt16Type* IPoolTypeFactory::Int16Raw() { + return TInt16Type::InstanceRaw(); + } + + TInt32TypePtr ITypeFactory::Int32() { + return TInt32Type::Instance(); + } + + const TInt32Type* IPoolTypeFactory::Int32Raw() { + return TInt32Type::InstanceRaw(); + } + + TInt64TypePtr ITypeFactory::Int64() { + return TInt64Type::Instance(); + } + + const TInt64Type* IPoolTypeFactory::Int64Raw() { + return TInt64Type::InstanceRaw(); + } + + TUint8TypePtr ITypeFactory::Uint8() { + return TUint8Type::Instance(); + } + + const TUint8Type* IPoolTypeFactory::Uint8Raw() { + return TUint8Type::InstanceRaw(); + } + + TUint16TypePtr ITypeFactory::Uint16() { + return TUint16Type::Instance(); + } + + const TUint16Type* IPoolTypeFactory::Uint16Raw() { + return TUint16Type::InstanceRaw(); + } + + TUint32TypePtr ITypeFactory::Uint32() { + return TUint32Type::Instance(); + } + + const TUint32Type* IPoolTypeFactory::Uint32Raw() { + return TUint32Type::InstanceRaw(); + } + + TUint64TypePtr ITypeFactory::Uint64() { + return TUint64Type::Instance(); + } + + const TUint64Type* IPoolTypeFactory::Uint64Raw() { + return TUint64Type::InstanceRaw(); + } + + TFloatTypePtr ITypeFactory::Float() { + return TFloatType::Instance(); + } + + const TFloatType* IPoolTypeFactory::FloatRaw() { + return TFloatType::InstanceRaw(); + } + + TDoubleTypePtr ITypeFactory::Double() { + return TDoubleType::Instance(); + } + + const TDoubleType* IPoolTypeFactory::DoubleRaw() { + return TDoubleType::InstanceRaw(); + } + + TStringTypePtr ITypeFactory::String() { + return TStringType::Instance(); + } + + const TStringType* IPoolTypeFactory::StringRaw() { + return TStringType::InstanceRaw(); + } + + TUtf8TypePtr ITypeFactory::Utf8() { + return TUtf8Type::Instance(); + } + + const TUtf8Type* IPoolTypeFactory::Utf8Raw() { + return TUtf8Type::InstanceRaw(); + } + + TDateTypePtr ITypeFactory::Date() { + return TDateType::Instance(); + } + + const TDateType* IPoolTypeFactory::DateRaw() { + return TDateType::InstanceRaw(); + } + + TDatetimeTypePtr ITypeFactory::Datetime() { + return TDatetimeType::Instance(); + } + + const TDatetimeType* IPoolTypeFactory::DatetimeRaw() { + return TDatetimeType::InstanceRaw(); + } + + TTimestampTypePtr ITypeFactory::Timestamp() { + return TTimestampType::Instance(); + } + + const TTimestampType* IPoolTypeFactory::TimestampRaw() { + return TTimestampType::InstanceRaw(); + } + + TTzDateTypePtr ITypeFactory::TzDate() { + return TTzDateType::Instance(); + } + + const TTzDateType* IPoolTypeFactory::TzDateRaw() { + return TTzDateType::InstanceRaw(); + } + + TTzDatetimeTypePtr ITypeFactory::TzDatetime() { + return TTzDatetimeType::Instance(); + } + + const TTzDatetimeType* IPoolTypeFactory::TzDatetimeRaw() { + return TTzDatetimeType::InstanceRaw(); + } + + TTzTimestampTypePtr ITypeFactory::TzTimestamp() { + return TTzTimestampType::Instance(); + } + + const TTzTimestampType* IPoolTypeFactory::TzTimestampRaw() { + return TTzTimestampType::InstanceRaw(); + } + + TIntervalTypePtr ITypeFactory::Interval() { + return TIntervalType::Instance(); + } + + const TIntervalType* IPoolTypeFactory::IntervalRaw() { + return TIntervalType::InstanceRaw(); + } + + TDecimalTypePtr ITypeFactory::Decimal(ui8 precision, ui8 scale) { + return TDecimalType::Create(*this, precision, scale); + } + + const TDecimalType* IPoolTypeFactory::DecimalRaw(ui8 precision, ui8 scale) { + return TDecimalType::CreateRaw(*this, precision, scale); + } + + TJsonTypePtr ITypeFactory::Json() { + return TJsonType::Instance(); + } + + const TJsonType* IPoolTypeFactory::JsonRaw() { + return TJsonType::InstanceRaw(); + } + + TYsonTypePtr ITypeFactory::Yson() { + return TYsonType::Instance(); + } + + const TYsonType* IPoolTypeFactory::YsonRaw() { + return TYsonType::InstanceRaw(); + } + + TUuidTypePtr ITypeFactory::Uuid() { + return TUuidType::Instance(); + } + + const TUuidType* IPoolTypeFactory::UuidRaw() { + return TUuidType::InstanceRaw(); + } + + TOptionalTypePtr ITypeFactory::Optional(TTypePtr item) { + return TOptionalType::Create(*this, std::move(item)); + } + + const TOptionalType* IPoolTypeFactory::OptionalRaw(const TType* item) { + return TOptionalType::CreateRaw(*this, item); + } + + TListTypePtr ITypeFactory::List(TTypePtr item) { + return TListType::Create(*this, std::move(item)); + } + + const TListType* IPoolTypeFactory::ListRaw(const TType* item) { + return TListType::CreateRaw(*this, item); + } + + TDictTypePtr ITypeFactory::Dict(TTypePtr key, TTypePtr value) { + return TDictType::Create(*this, std::move(key), std::move(value)); + } + + const TDictType* IPoolTypeFactory::DictRaw(const TType* key, const TType* value) { + return TDictType::CreateRaw(*this, key, value); + } + + TStructTypePtr ITypeFactory::Struct(TStructType::TOwnedMembers items) { + return TStructType::Create(*this, items); + } + + TStructTypePtr ITypeFactory::Struct(TMaybe<TStringBuf> name, TStructType::TOwnedMembers items) { + return TStructType::Create(*this, name, items); + } + + const TStructType* IPoolTypeFactory::StructRaw(TStructType::TMembers items) { + return TStructType::CreateRaw(*this, items); + } + + const TStructType* IPoolTypeFactory::StructRaw(TMaybe<TStringBuf> name, TStructType::TMembers items) { + return TStructType::CreateRaw(*this, name, items); + } + + TTupleTypePtr ITypeFactory::Tuple(TTupleType::TOwnedElements items) { + return TTupleType::Create(*this, items); + } + + TTupleTypePtr ITypeFactory::Tuple(TMaybe<TStringBuf> name, TTupleType::TOwnedElements items) { + return TTupleType::Create(*this, name, items); + } + + const TTupleType* IPoolTypeFactory::TupleRaw(TTupleType::TElements items) { + return TTupleType::CreateRaw(*this, items); + } + + const TTupleType* IPoolTypeFactory::TupleRaw(TMaybe<TStringBuf> name, TTupleType::TElements items) { + return TTupleType::CreateRaw(*this, name, items); + } + + TVariantTypePtr ITypeFactory::Variant(TTypePtr inner) { + return TVariantType::Create(*this, std::move(inner)); + } + + TVariantTypePtr ITypeFactory::Variant(TMaybe<TStringBuf> name, TTypePtr inner) { + return TVariantType::Create(*this, name, std::move(inner)); + } + + const TVariantType* IPoolTypeFactory::VariantRaw(const TType* inner) { + return TVariantType::CreateRaw(*this, inner); + } + + const TVariantType* IPoolTypeFactory::VariantRaw(TMaybe<TStringBuf> name, const TType* inner) { + return TVariantType::CreateRaw(*this, name, inner); + } + + TTaggedTypePtr ITypeFactory::Tagged(TTypePtr type, TStringBuf tag) { + return TTaggedType::Create(*this, std::move(type), tag); + } + + const TTaggedType* IPoolTypeFactory::TaggedRaw(const TType* type, TStringBuf tag) { + return TTaggedType::CreateRaw(*this, type, tag); + } + + namespace { + class TPoolFactory: public NTi::IPoolTypeFactory { + public: + TPoolFactory(size_t initial, TMemoryPool::IGrowPolicy* grow, IAllocator* alloc, TMemoryPool::TOptions options) + : Pool_(initial, grow, alloc, options) + { + } + + public: + void* Allocate(size_t size, size_t align) noexcept override { + return Pool_.Allocate(size, align); + } + + void Free(void* data) noexcept override { + Y_UNUSED(data); + } + + protected: + const NTi::TType* LookupCache(const NTi::TType* type) noexcept override { + Y_UNUSED(type); + return nullptr; + } + + void SaveCache(const NTi::TType* type) noexcept override { + Y_UNUSED(type); + } + + void Ref() noexcept override { + Counter_.Inc(); + } + + void UnRef() noexcept override { + if (Counter_.Dec() == 0) { + delete this; + } + } + + void DecRef() noexcept override { + if (Counter_.Dec() == 0) { + Y_FAIL("DecRef is not supposed to drop"); + } + } + + long RefCount() const noexcept override { + return Counter_.Val(); + } + + void RefType(NTi::TType* type) noexcept override { + Y_UNUSED(type); + } + + void UnRefType(NTi::TType* type) noexcept override { + Y_UNUSED(type); + } + + void DecRefType(NTi::TType* type) noexcept override { + Y_UNUSED(type); + } + + long RefCountType(const NTi::TType* type) const noexcept override { + Y_UNUSED(type); + return RefCount(); + } + + public: + size_t Available() const noexcept override { + return Pool_.Available(); + } + + size_t MemoryAllocated() const noexcept override { + return Pool_.MemoryAllocated(); + } + + size_t MemoryWaste() const noexcept override { + return Pool_.MemoryWaste(); + } + + private: + TAtomicCounter Counter_; + TMemoryPool Pool_; + }; + + class TPoolFactoryDedup: public TPoolFactory { + public: + using TPoolFactory::TPoolFactory; + + public: + const NTi::TType* LookupCache(const NTi::TType* type) noexcept override { + if (auto it = Cache_.find(type); it != Cache_.end()) { + return *it; + } else { + return nullptr; + } + } + + void SaveCache(const NTi::TType* type) noexcept override { + Cache_.insert(type); + } + + protected: + TStringBuf AllocateString(TStringBuf str) noexcept override { + if (str.empty()) { + return TStringBuf(); // `str` could still point somewhere whereas empty strbuf points to NULL + } + + if (auto it = StringCache_.find(str); it != StringCache_.end()) { + return *it; + } else { + return *StringCache_.insert(it, ITypeFactoryInternal::AllocateString(str)); + } + } + + private: + THashSet<const NTi::TType*, NTi::NEq::TStrictlyEqualHash, NTi::NEq::TStrictlyEqual> Cache_; + THashSet<TStringBuf> StringCache_; + }; + + class THeapFactory: public NTi::ITypeFactory { + public: + void* Allocate(size_t size, size_t align) noexcept override { + Y_UNUSED(align); + return malloc(size); + } + + void Free(void* data) noexcept override { + free(data); + } + + protected: + const NTi::TType* LookupCache(const NTi::TType* type) noexcept override { + Y_UNUSED(type); + return nullptr; + } + + void SaveCache(const NTi::TType* type) noexcept override { + Y_UNUSED(type); + } + + void Ref() noexcept override { + // nothing + } + + void UnRef() noexcept override { + // nothing + } + + void DecRef() noexcept override { + // nothing + } + + long RefCount() const noexcept override { + return 0; + } + + void RefType(NTi::TType* type) noexcept override { + Y_UNUSED(type); + Y_FAIL("not supposed to be called"); + } + + void UnRefType(NTi::TType* type) noexcept override { + Y_UNUSED(type); + Y_FAIL("not supposed to be called"); + } + + void DecRefType(NTi::TType* type) noexcept override { + Y_UNUSED(type); + Y_FAIL("not supposed to be called"); + } + + long RefCountType(const NTi::TType* type) const noexcept override { + Y_UNUSED(type); + Y_FAIL("not supposed to be called"); + } + }; + + THeapFactory HEAP_FACTORY; + } + + IPoolTypeFactoryPtr PoolFactory(bool deduplicate, size_t initial, TMemoryPool::IGrowPolicy* grow, IAllocator* alloc, TMemoryPool::TOptions options) { + if (deduplicate) { + return new TPoolFactoryDedup(initial, grow, alloc, options); + } else { + return new TPoolFactory(initial, grow, alloc, options); + } + } + + namespace NPrivate { + ITypeFactory* GetDefaultHeapFactory() { + return &HEAP_FACTORY; + } + } + + ITypeFactoryPtr HeapFactory() { + return NPrivate::GetDefaultHeapFactory(); + } +} diff --git a/library/cpp/type_info/type_factory.h b/library/cpp/type_info/type_factory.h new file mode 100644 index 0000000000..df47c7082b --- /dev/null +++ b/library/cpp/type_info/type_factory.h @@ -0,0 +1,906 @@ +#pragma once + +//! @file type_factory.h +//! +//! Type factory creates type instances and manages their lifetimes and destruction. +//! +//! Type info supports multiple ways of allocating type instances and managing their lifetimes: +//! +//! - [heap-based allocation] is the standard memory allocation method for C++. It offers great flexibility +//! as it can allocate memory regions of almost arbitrary size and alignment, deallocate them, re-use them +//! for new allocations or return the to the operating system. This flexibility comes with a price, though. +//! Heap allocators usually require synchronisation (i.e. a mutex or a spinlock), and some memory overhead to track +//! allocated regions. Also, they provide no guarantees on data locality and CPU cache friendliness whatsoever. +//! +//! When using a heap-based factory, each type instance have a separate reference counter. `TTypePtr`s will +//! increment and decrement this counter. Whenever it reaches zero, they will destroy the type instance and free +//! its memory. This is the standard reference counting technique, just like in `TRefCounted`. +//! +//! - [memory pool] is a data structure for faster memory allocation. It pre-allocates large chunks of memory and uses +//! them to allocate smaller objects. This way, we don't have to `malloc` memory for each new object. +//! Also, we can drop all objects at once by discarding all memory chunks. +//! +//! When using memory-pool-based factory, types don't have individual reference counters. Instead, they store +//! a pointer to the factory that've created them. Whenever such type is referenced or unreferenced, the factory's +//! own reference counter is incremented or decremented. Factory dies and releases all pool's memory when the last +//! reference to a type in its pool dies. +//! +//! [heap-based allocation]: https://en.wikipedia.org/wiki/Memory_management#Dynamic_memory_allocation +//! [Reference counting]: https://en.wikipedia.org/wiki/Reference_counting +//! [memory pool]: https://en.wikipedia.org/wiki/Region-based_memory_management +//! +//! The rule of thumb is: if you have an intrusive pointer to a type, you can be sure it points to an alive object; +//! if you have a raw pointer to a type, it's your responsibility to ensure its validity. +//! +//! Whenever you have a valid raw pointer to a type, you can promote it to an intrusive pointer by calling `AsPtr`. +//! This is always safe because there's no way to create a type instance on the stack or in the static memory +//! of a program. +//! +//! +//! # Implementation details +//! +//! We're building a hierarchy of classes that work equally well with both heap-based allocation model +//! and memory-pool-based allocation model. In order to understand how we do it, we should first understand how +//! object ownership works in both models. +//! +//! +//! ## Ownership schema +//! +//! Depending on the chosen factory implementation, ownership schema may vary. +//! +//! When using the heap-based factory, each type has its own reference counter. User own types, and types own +//! their nested types. For `Optional<String>`, user owns the `Optional` type, and `Optional` type +//! owns the `String` type: +//! +//! ```text +//! Legend: A ───> B -- A owns B | A ─ ─> B -- A points to B +//! +//! User code: Objects: +//! ┌─────────────┐ ┌────────────────┐ +//! │ IFactoryPtr │───────>│ Factory │ +//! └─────────────┘ └────────────────┘ +//! ┌─────────────┐ ┌────────────────┐ +//! │ TTypePtr │───────>│ Type: Optional │──┐ +//! └─────────────┘ └────────────────┘ │ +//! ┌──────────────────────┘ +//! │ ┌────────────────┐ +//! └─>│ Type: String │ +//! └────────────────┘ +//! ``` +//! +//! When using a pool-based factory, all allocated types are owned by pool. So, in the `Optional<String>` example, +//! user owns factory, factory owns the `Optional` type and the `String` type. All references between types +//! are just borrows: +//! +//! ```text +//! Legend: A ───> B -- A owns B | A ─ ─> B -- A points to B +//! +//! User code: Objects: +//! ┌─────────────┐ ┌────────────────┐ +//! │ IFactoryPtr │─┌─────>│ Factory │──┐ +//! └─────────────┘ │ └────────────────┘ │ +//! ┌─────────────┐ │ ┌────────────────┐ │ +//! │ TTypePtr │─┘─ ─ ─>│ Type: Optional │<─┤ +//! └─────────────┘ └───────┬────────┘ │ +//! ┌ ─ ─ ─ ─ ─ ─┘ │ +//! ╎ ┌────────────────┐ │ +//! └─ ─>│ Type: String │<─┘ +//! └────────────────┘ +//! ``` +//! +//! Notice how `TTypePtr` points to `Optional`, but doesn't own it. Instead, it owns the factory which, +//! transitively, owns `Optional`. This way we can be sure that the factory will not be destroyed before +//! all `IFactoryPtr`s and `TTypePtr`s die, thus guaranteeing that all `TTypePtr` always stay valid. +//! +//! With that in mind, we can derive two types of ownership here. +//! +//! Whenever `TTypePtr` owns some type, we call it 'external ownership' (because it's code that is external +//! to this library owns something). In this situation, ref and unref procedures must increment type's own reference +//! counter, if there is one, and also increment factory's reference counter. `NTi::TType::Ref` and `NTi::TType::Unref` +//! do this by calling `NTi::ITypeFactoryInternal::Ref`, `NTi::ITypeFactoryInternal::RefType`, +//! `NTi::ITypeFactoryInternal::UnRef`, `NTi::ITypeFactoryInternal::UnRefType`. +//! +//! Whenever one type owns another type, we call it 'internal ownership'. In this situation, ref and unref procedures +//! must only increment and decrement type's own reference counter. They should *not* increment or decrement +//! factory's reference counter. `NTi::TType::RefSelf` and `NTi::TType::UnrefSelf` do this by calling +//! `NTi::ITypeFactoryInternal::RefType` and `NTi::ITypeFactoryInternal::UnRefType`, and not calling +//! `NTi::ITypeFactoryInternal::Ref` and `NTi::ITypeFactoryInternal::UnRef`. +//! +//! +//! ## Adoption semantics +//! +//! Let's see now what should happen when we create some complex type using more that one factory. +//! +//! Suppose we're creating a container type, such as `Optional`, using factory `a`. If its nested type is +//! managed by the same factory, we have no issues at all: +//! +//! ``` +//! auto a = NTi::PoolFactory(); +//! auto string = a->String(); +//! auto optional = a->Optional(string); +//! ``` +//! +//! But what if the nested type is managed by some other factory `b`? Consider: +//! +//! ``` +//! auto b = NTi::PoolFactory(); +//! auto a = NTi::PoolFactory(); +//! auto string = b->String(); +//! auto optional = a->Optional(string); +//! ``` +//! +//! Well, we're in trouble. It's not enough for `optional` to just acquire internal ownership over `string`. +//! Actually, `optional` has to own both `string` and its factory, `b`. Otherwise, if `b` dies, `string` will +//! die with it, leaving `optional` with a dangling pointer. +//! +//! However, we can't have a type owning its factory. It will create a loop. The only safe solution is to copy +//! the nested type from one factory to another. If we have some type managed by factory `a`, all nested types +//! must also be managed by the same factory `a`. +//! +//! So, whenever we create a type, we must ensure that all its nested types are managed by the same factory. +//! If they're not, we must deep-copy them to the current factory. This is called the 'adoption semantics'. That is, +//! we adopt nested types into the current factory. +//! +//! In fact, adoption semantics is a bit more complicated that this. Some types are not managed by any factory. +//! They have static storage duration and therefore need no management. So, if we see that there's no factory +//! associated with some type, we don't perform deep-copy. Instead, we just return it as is. +//! +//! +//! ## Overview +//! +//! So, now we're ready to put this puzzle together. +//! +//! When we create a new type instance, we do the following: +//! +//! 1: we adopt all nested types and acquire internal ownership over them. This is done by the `Own` function; +//! 2: we allocate some memory for the type. This is done by `New`, `Allocate` and other functions; +//! 3: we initialize all this memory; +//! 4: finally, we acquire external ownership over the newly created type and return it to the user. +//! +//! Steps 1 through 3 are performed by the `NTi::TType::Clone` function. Step 4 is performed by the factory. +//! +//! When we adopt some type, we check if it's managed by any factory other that the current one. If needed, +//! we deep-copy it by the `NTi::TType::Clone` function. That is, `Clone` copies type to a new factory and adopts +//! nested types, adoption calls nested type's `Clone`, causing recursive deep-copying. +//! +//! When we release external or internal ownership over some type, factory might decide to destroy it (if, for +//! example, this type's reference count reached zero). In this case, we need to release internal ownership +//! over the nested types, and release all memory that was allocated in step 3. +//! This is done by `NTi::TType::Drop` function. + +#include <util/generic/maybe.h> +#include <util/generic/ptr.h> +#include <util/generic/strbuf.h> +#include <util/memory/pool.h> + +#include "type.h" + +#include "fwd.h" + +namespace NTi { + namespace NPrivate { + /// If `T` is derived from `NTi::TType` (ignoring cv-qualification), provides the member constant `value` + /// equal to `true`. Otherwise `value` is `false`. + template <typename T> + struct TIsType: public std::is_base_of<TType, T> { + }; + + /// Helper template variable for `TIsType`. + template <typename T> + inline constexpr const bool IsTypeV = TIsType<T>::value; + + /// Statically assert that `T` is a managed object. + template <typename T> + void AssertIsType() { + static_assert(IsTypeV<T>, "object should be derived from 'NTi::TType'"); + } + } + + /// Internal interface for type factory. + /// + /// This interface is accessible from `TType` implementation, but not from outside. + class ITypeFactoryInternal { + friend class TType; + template <typename T> + friend class ::TDefaultIntrusivePtrOps; + + public: + virtual ~ITypeFactoryInternal() = default; + + public: + /// @name Object creation and lifetime management interface + /// + /// @{ + //- + /// Create a new object of type `T` by allocating memory for it and constructing it with arguments `args`. + /// + /// If `T` is derived from `TType`, it will be linked to this factory via the `NTi::TType::SetFactory` method. + /// + /// This method does not acquire any ownership over the created object, not internal nor external. It just + /// allocates some memory and calls a constructor and that's it. + template <typename T, typename... Args> + inline T* New(Args&&... args) { + // Note: it's important to understand difference between `New` and `Create` - read the docs! + + static_assert(std::is_trivially_destructible_v<T>, "can only create trivially destructible types"); + + auto value = new (AllocateFor<T>()) T(std::forward<Args>(args)...); + + if constexpr (NPrivate::IsTypeV<T>) { + value->SetFactory(this); + } + + return value; + } + + /// Delete an object that was created via the `New` function. + /// + /// This function essentially just calls `Free`. Note that `New` can only create trivially destructible types, + /// thus `Delete` does not neet to call object's destructor. + template <typename T> + inline void Delete(T* obj) noexcept { + Free(obj); + } + + /// Create a new array of `count` objects of type `T`. + /// + /// This function will allocate an array of `count` objects of type `T`. Then, for each object in the array, + /// it will call the `ctor(T* memory, size_t index)`, passing a pointer to the uninitialized object + /// and its index in the array. `ctor` should call placement new on the passed pointer. + /// + /// If `T` is derived from `TType`, it will be linked to this factory via the `NTi::TType::SetFactory` method. + /// + /// This method does not acquire any ownership over the created array elements, not internal nor external. + /// It just allocates some memory and calls a constructor and that's it. + /// + /// + /// # Examples + /// + /// Copy vector to factory: + /// + /// ``` + /// TVector<T> source = ...; + /// TArrayRef<T> copied = NMem::NewArray<T>(factory, source.size(), [&source](T* ptr, size_t i) { + /// new (ptr) T(source[i]); + /// }); + /// ``` + template <typename T, typename TCtor> + inline TArrayRef<T> NewArray(size_t count, TCtor&& ctor) { + // Note: it's important to understand difference between `New` and `Create` - read the docs! + + static_assert(std::is_trivially_destructible_v<T>, "can only create trivially destructible types"); + + auto items = AllocateArrayFor<T>(count); + + for (size_t i = 0; i < count; ++i) { + auto value = items + i; + ctor(value, i); + if constexpr (NPrivate::IsTypeV<T>) { + value->SetFactory(this); + } + } + + return TArrayRef(items, count); + } + + /// Delete an object that was created via the `NewArray` function. + /// + /// This function runs `dtor` on each element of the array and then just calls `Free`. Note that `NewArray` + /// can only create arrays for trivially destructible types, thus `DeleteArray` + /// does not call object destructors. + template <typename T, typename TDtor> + inline void DeleteArray(TArrayRef<T> obj, TDtor&& dtor) noexcept { + for (size_t i = 0; i < obj.size(); ++i) { + auto value = obj.data() + i; + dtor(value, i); + } + + Free(const_cast<void*>(static_cast<const void*>(obj.data()))); + } + + /// Adopt `type` into this factory. + /// + /// This function works like `AdoptRaw`, but it wraps its result to an intrusive pointer, thus acquiring + /// external ownership over it. + /// + /// It is used by external code to copy types between factories. + template <typename T> + inline TIntrusiveConstPtr<T> Adopt(TIntrusiveConstPtr<T> type) noexcept { + NPrivate::AssertIsType<T>(); + + return TIntrusiveConstPtr<T>(const_cast<T*>(AdoptRaw<T>(type.Get()))); + } + + /// Adopt `type` into this factory. + /// + /// This function is used to transfer types between factories. It has the following semantics: + /// + /// - if `type` is managed by this factory, this function returns its argument unchanged; + /// - if `type` is not managed by any factory, this function also returns its argument unchanged; + /// - if `type` is managed by another factory, this function deep-copies `type` into this factory and returns + /// a pointer to the new deep-copied value. + /// + /// This function uses `NTi::TType::Clone` to deep-copy types. It does not acquire any ownership over + /// the returned object, not internal nor external. If you need to adopt and acquire external ownership + /// (i.e. you're returning type to a user), use `Adopt`. If you need to adopt and acquire internal ownership + /// (i.e. you're writing `NTi::TType::Clone` implementation), use `Own`. + template <typename T> + inline const T* AdoptRaw(const T* type) noexcept { + NPrivate::AssertIsType<T>(); + + ITypeFactoryInternal* typeFactory = type->GetFactory(); + if (typeFactory == nullptr || typeFactory == this) { + return type; + } else { + return type->Clone(*this); + } + } + + /// Adopt some type and acquire internal ownership over it. + /// + /// This function works like `AdoptRaw`, but acquires internal ownership over the returned type. + /// + /// It is used by `NTi::TType::Clone` implementations to acquire ownership over the nested types. + template <typename T> + inline const T* Own(const T* type) noexcept { + NPrivate::AssertIsType<T>(); + + type = AdoptRaw(type); + const_cast<T*>(type)->RefSelf(); + return type; + } + + /// Release internal ownership over some type. + /// + /// This function is used by `NTi::TType::Drop` to release internal ownership over nested types. + template <typename T> + inline void Disown(const T* type) noexcept { + NPrivate::AssertIsType<T>(); + + const_cast<T*>(type)->UnRefSelf(); + } + + /// @} + + public: + /// @name Memory management interface + /// + /// Functions for dealing with raw unmanaged memory. + /// + /// @{ + //- + /// Allocate a new chunk of memory of size `size` aligned on `align`. + /// + /// The behaviour is undefined if `size` is zero, `align` is zero, is not a power of two, + /// or greater than `PLATFORM_DATA_ALIGN` (i.e., over-aligned). + /// + /// This function panics if memory can't be allocated. + /// + /// Returned memory may or may not be initialized. + virtual void* Allocate(size_t size, size_t align) noexcept = 0; + + /// Allocate a chunk of memory suitable for storing an object of type `T`. + /// + /// This function panics if memory can't be allocated. + /// + /// Returned memory may or may not be initialized. + template <typename T> + T* AllocateFor() noexcept { + static_assert(alignof(T) <= PLATFORM_DATA_ALIGN, "over-aligned types are not supported"); + return static_cast<T*>(Allocate(sizeof(T), alignof(T))); + } + + /// Allocate a new chunk of memory suitable for storing `count` of objects of size `size` aligned on `align`. + /// + /// The behaviour is undefined if `count` is zero, `size` is zero, `align` is zero, is not a power of two, + /// or greater than `PLATFORM_DATA_ALIGN` (i.e., over-aligned). + /// + /// This function panics if memory can't be allocated. + /// + /// Returned memory may or may not be initialized. + void* AllocateArray(size_t count, size_t size, size_t align) noexcept { + return Allocate(size * count, align); + } + + /// Allocate a chunk of memory suitable for storing an array of `count` objects of type `T`. + /// + /// This function panics if memory can't be allocated. + /// + /// Returned memory may or may not be initialized. + template <typename T> + T* AllocateArrayFor(size_t count) noexcept { + static_assert(alignof(T) <= PLATFORM_DATA_ALIGN, "over-aligned types are not supported"); + return static_cast<T*>(AllocateArray(count, sizeof(T), alignof(T))); + } + + /// Reclaim a chunk of memory memory that was allocated via one of the above `Allocate*` functions. + /// + /// This function is not suitable for freeing memory allocated via `AllocateString` or `AllocateStringMaybe`. + /// + /// Behaviour of this function varies between implementations. Specifically, in the memory-pool-based factory, + /// this function does nothing, while in heap-based factory, it calls `free`. + /// + /// Passing a null pointer here is ok and does nothing. + /// + /// The behaviour is undefined if the given pointer is not null and it wasn't obtained from a call + /// to the `Allocate` function or it was obtained from a call to the `Allocate` function + /// of a different factory. + virtual void Free(void* data) noexcept = 0; + + /// Allocate memory for string `str` and copy `str` to the allocated memory. + /// + /// Note: the returned string is not guaranteed to be null-terminated. + /// + /// Note: some factories may cache results of this function to avoid repeated allocations. Read documentation + /// for specific factory for more info. + virtual TStringBuf AllocateString(TStringBuf str) noexcept { + return TStringBuf(MemCopy(AllocateArrayFor<char>(str.size()), str.data(), str.size()), str.size()); + } + + /// Reclaim a chunk of memory memory that was allocated via the `AllocateString` function. + virtual void FreeString(TStringBuf str) noexcept { + Free(const_cast<char*>(str.Data())); + } + + /// Like `AllocateString`, but works with `TMaybe<TStringBuf>`. + /// + /// If the given `str` contains a value, pass it to `AllocateString`. + /// If it contains no value, return an empty `TMaybe`. + TMaybe<TStringBuf> AllocateStringMaybe(TMaybe<TStringBuf> str) noexcept { + if (str.Defined()) { + return AllocateString(str.GetRef()); + } else { + return Nothing(); + } + } + + /// Reclaim a chunk of memory memory that was allocated via the `AllocateStringMaybe` function. + void FreeStringMaybe(TMaybe<TStringBuf> str) noexcept { + if (str.Defined()) { + FreeString(str.GetRef()); + } + } + + /// @} + + public: + /// @name Type caching and deduplication interface + /// + /// @{ + //- + /// Lookup the given type in the factory cache. Return cached version of the type or `nullptr`. + virtual const TType* LookupCache(const TType* type) noexcept = 0; + + /// Save the given type to the factory cache. Type must be allocated via this factory. + virtual void SaveCache(const TType* type) noexcept = 0; + + /// @} + + public: + /// @name Factory reference counting interface + /// + /// @{ + //- + /// Increase reference count of this factory. + virtual void Ref() noexcept = 0; + + /// Decrease reference count of this factory. + /// If reference count reaches zero, drop this factory. + virtual void UnRef() noexcept = 0; + + /// Decrease reference count of this factory. + /// If reference count reaches zero, panic. + virtual void DecRef() noexcept = 0; + + /// Get current reference count of this factory. + virtual long RefCount() const noexcept = 0; + + /// @} + + public: + /// @name Type instance reference counting interface + /// + /// @{ + //- + /// Increase reference count of some object managed by this factory. + virtual void RefType(TType*) noexcept = 0; + + /// Decrease reference count of some object managed by this factory. + /// If reference count reaches zero, call `TType::Drop` and free object's memory, if needed. + virtual void UnRefType(TType*) noexcept = 0; + + /// Decrease reference count of some object managed by this factory. + /// If reference count reaches zero, panic. + virtual void DecRefType(TType*) noexcept = 0; + + /// Get current reference count of some object managed by this factory. + virtual long RefCountType(const TType*) const noexcept = 0; + + /// @} + }; + + /// Base interface for type factory. There are functions to create type instances, and also a function to + /// adopt a type from one factory to another. See the file-level documentation for more info. + class ITypeFactory: protected ITypeFactoryInternal { + friend class TType; + template <typename T> + friend class ::TDefaultIntrusivePtrOps; + + public: + /// Adopt type into this factory. + /// + /// This function is used to transfer types between factories. It has the following semantics: + /// + /// - if `type` is managed by this factory, this function returns its argument unchanged; + /// - if `type` is not managed by any factory, this function also returns its argument unchanged; + /// - if `type` is managed by another factory, this function deep-copies `type` into this factory and returns + /// a pointer to the new deep-copied value. + /// + /// This function should be used on API borders, whenever you receive ownership over some type that wasn't + /// created by you. For example, you may ensure that the type you got is allocated in heap, or you may want to + /// copy some type into a memory pool that you have control over. + template <typename T> + inline TIntrusiveConstPtr<T> Adopt(TIntrusiveConstPtr<T> value) noexcept { + return ITypeFactoryInternal::Adopt<T>(std::move(value)); + } + + public: + /// Create a new instance of a type using this factory. + /// @{ + //- + /// Create a new `Void` type. See `NTi::TVoidType` for more info. + TVoidTypePtr Void(); + + /// Create a new `Null` type. See `NTi::TNullType` for more info. + TNullTypePtr Null(); + + /// Create a new `Bool` type. See `NTi::TBoolType` for more info. + TBoolTypePtr Bool(); + + /// Create a new `Int8` type. See `NTi::TInt8Type` for more info. + TInt8TypePtr Int8(); + + /// Create a new `Int16` type. See `NTi::TInt16Type` for more info. + TInt16TypePtr Int16(); + + /// Create a new `Int32` type. See `NTi::TInt32Type` for more info. + TInt32TypePtr Int32(); + + /// Create a new `Int64` type. See `NTi::TInt64Type` for more info. + TInt64TypePtr Int64(); + + /// Create a new `Uint8` type. See `NTi::TUint8Type` for more info. + TUint8TypePtr Uint8(); + + /// Create a new `Uint16` type. See `NTi::TUint16Type` for more info. + TUint16TypePtr Uint16(); + + /// Create a new `Uint32` type. See `NTi::TUint32Type` for more info. + TUint32TypePtr Uint32(); + + /// Create a new `Uint64` type. See `NTi::TUint64Type` for more info. + TUint64TypePtr Uint64(); + + /// Create a new `Float` type. See `NTi::TFloatType` for more info. + TFloatTypePtr Float(); + + /// Create a new `Double` type. See `NTi::TDoubleType` for more info. + TDoubleTypePtr Double(); + + /// Create a new `String` type. See `NTi::TStringType` for more info. + TStringTypePtr String(); + + /// Create a new `Utf8` type. See `NTi::TUtf8Type` for more info. + TUtf8TypePtr Utf8(); + + /// Create a new `Date` type. See `NTi::TDateType` for more info. + TDateTypePtr Date(); + + /// Create a new `Datetime` type. See `NTi::TDatetimeType` for more info. + TDatetimeTypePtr Datetime(); + + /// Create a new `Timestamp` type. See `NTi::TTimestampType` for more info. + TTimestampTypePtr Timestamp(); + + /// Create a new `TzDate` type. See `NTi::TTzDateType` for more info. + TTzDateTypePtr TzDate(); + + /// Create a new `TzDatetime` type. See `NTi::TTzDatetimeType` for more info. + TTzDatetimeTypePtr TzDatetime(); + + /// Create a new `TzTimestamp` type. See `NTi::TTzTimestampType` for more info. + TTzTimestampTypePtr TzTimestamp(); + + /// Create a new `Interval` type. See `NTi::TIntervalType` for more info. + TIntervalTypePtr Interval(); + + /// Create a new `Decimal` type. See `NTi::TDecimalType` for more info. + TDecimalTypePtr Decimal(ui8 precision, ui8 scale); + + /// Create a new `Json` type. See `NTi::TJsonType` for more info. + TJsonTypePtr Json(); + + /// Create a new `Yson` type. See `NTi::TYsonType` for more info. + TYsonTypePtr Yson(); + + /// Create a new `Uuid` type. See `NTi::TUuidType` for more info. + TUuidTypePtr Uuid(); + + /// Create a new `Optional` type. See `NTi::TOptionalType` for more info. + /// If `item` is managed by some other factory, it will be deep-copied into this factory. + TOptionalTypePtr Optional(TTypePtr item); + + /// Create a new `List` type. See `NTi::TListType` for more info. + /// If `item` is managed by some other factory, it will be deep-copied into this factory. + TListTypePtr List(TTypePtr item); + + /// Create a new `Dict` type. See `NTi::TDictType` for more info. + /// If `key` or `value` are managed by some other factory, they will be deep-copied into this factory. + TDictTypePtr Dict(TTypePtr key, TTypePtr value); + + /// Create a new `Struct` type. See `NTi::TStructType` for more info. + /// If item types are managed by some other factory, they will be deep-copied into this factory. + TStructTypePtr Struct(TStructType::TOwnedMembers items); + TStructTypePtr Struct(TMaybe<TStringBuf> name, TStructType::TOwnedMembers items); + + /// Create a new `Tuple` type. See `NTi::TTupleType` for more info. + /// If item types are managed by some other factory, they will be deep-copied into this factory. + TTupleTypePtr Tuple(TTupleType::TOwnedElements items); + TTupleTypePtr Tuple(TMaybe<TStringBuf> name, TTupleType::TOwnedElements items); + + /// Create a new `Variant` type. See `NTi::TVariantType` for more info. + /// If `inner` is managed by some other factory, it will be deep-copied into this factory. + TVariantTypePtr Variant(TTypePtr inner); + TVariantTypePtr Variant(TMaybe<TStringBuf> name, TTypePtr inner); + + /// Create a new `Tagged` type. See `NTi::TTaggedType` for more info. + /// If `type` is managed by some other factory, it will be deep-copied into this factory. + TTaggedTypePtr Tagged(TTypePtr type, TStringBuf tag); + + /// @} + }; + + /// Type factory over a memory pool. + /// + /// This interface provides some additional info related to the underlying memory pool state. + /// + /// Also, since all types that're created via this factory will live exactly as long as this factory lives, + /// one doesn't have to actually refcount them. Thus, it is safe to work with raw pointers instead of intrusive + /// pointers when using this kind of factory. So we provide the 'raw' interface that creates type instances + /// as usual, but doesn't increment any reference counters, thus saving some atomic operations. It still + /// adopts nested types into this factory, though. + class IPoolTypeFactory: public ITypeFactory { + friend class TNamedTypeBuilderRaw; + friend class TStructBuilderRaw; + friend class TTupleBuilderRaw; + friend class TTaggedBuilderRaw; + + public: + /// Memory available in the current chunk. + /// + /// Allocating object of size greater than this number will cause allocation of a new chunk. When this happens, + /// available memory in the current chunk is lost, i.e. it'll never be used again. + virtual size_t Available() const noexcept = 0; + + /// Number of bytes that're currently used by some object (i.e. they were allocated). + /// + /// Total memory consumed by arena is `MemoryAllocated() + MemoryWaste()`. + virtual size_t MemoryAllocated() const noexcept = 0; + + /// Number of bytes that're not used by anyone. + /// + /// Total memory consumed by arena is `MemoryAllocated() + MemoryWaste()`. + /// + /// Memory that's lost and will not be reused is `MemoryWaste() - Available()`. + virtual size_t MemoryWaste() const noexcept = 0; + + public: + /// Adopt type into this factory. + /// + /// This works like `ITypeFactory::Adopt`, but returns a raw pointer. + template <typename T> + inline const T* AdoptRaw(const T* value) noexcept { + return ITypeFactoryInternal::AdoptRaw<T>(value); + } + + public: + /// Raw versions of type constructors. See the class-level documentation for more info. + /// + /// @{ + //- + /// Create a new `Void` type. See `NTi::TVoidType` for more info. + /// The returned object will live for as long as this factory lives. + const TVoidType* VoidRaw(); + + /// Create a new `Null` type. See `NTi::TNullType` for more info. + /// The returned object will live for as long as this factory lives. + const TNullType* NullRaw(); + + /// Create a new `Bool` type. See `NTi::TBoolType` for more info. + /// The returned object will live for as long as this factory lives. + const TBoolType* BoolRaw(); + + /// Create a new `Int8` type. See `NTi::TInt8Type` for more info. + /// The returned object will live for as long as this factory lives. + const TInt8Type* Int8Raw(); + + /// Create a new `Int16` type. See `NTi::TInt16Type` for more info. + /// The returned object will live for as long as this factory lives. + const TInt16Type* Int16Raw(); + + /// Create a new `Int32` type. See `NTi::TInt32Type` for more info. + /// The returned object will live for as long as this factory lives. + const TInt32Type* Int32Raw(); + + /// Create a new `Int64` type. See `NTi::TInt64Type` for more info. + /// The returned object will live for as long as this factory lives. + const TInt64Type* Int64Raw(); + + /// Create a new `Uint8` type. See `NTi::TUint8Type` for more info. + /// The returned object will live for as long as this factory lives. + const TUint8Type* Uint8Raw(); + + /// Create a new `Uint16` type. See `NTi::TUint16Type` for more info. + /// The returned object will live for as long as this factory lives. + const TUint16Type* Uint16Raw(); + + /// Create a new `Uint32` type. See `NTi::TUint32Type` for more info. + /// The returned object will live for as long as this factory lives. + const TUint32Type* Uint32Raw(); + + /// Create a new `Uint64` type. See `NTi::TUint64Type` for more info. + /// The returned object will live for as long as this factory lives. + const TUint64Type* Uint64Raw(); + + /// Create a new `Float` type. See `NTi::TFloatType` for more info. + /// The returned object will live for as long as this factory lives. + const TFloatType* FloatRaw(); + + /// Create a new `Double` type. See `NTi::TDoubleType` for more info. + /// The returned object will live for as long as this factory lives. + const TDoubleType* DoubleRaw(); + + /// Create a new `String` type. See `NTi::TStringType` for more info. + /// The returned object will live for as long as this factory lives. + const TStringType* StringRaw(); + + /// Create a new `Utf8` type. See `NTi::TUtf8Type` for more info. + /// The returned object will live for as long as this factory lives. + const TUtf8Type* Utf8Raw(); + + /// Create a new `Date` type. See `NTi::TDateType` for more info. + /// The returned object will live for as long as this factory lives. + const TDateType* DateRaw(); + + /// Create a new `Datetime` type. See `NTi::TDatetimeType` for more info. + /// The returned object will live for as long as this factory lives. + const TDatetimeType* DatetimeRaw(); + + /// Create a new `Timestamp` type. See `NTi::TTimestampType` for more info. + /// The returned object will live for as long as this factory lives. + const TTimestampType* TimestampRaw(); + + /// Create a new `TzDate` type. See `NTi::TTzDateType` for more info. + /// The returned object will live for as long as this factory lives. + const TTzDateType* TzDateRaw(); + + /// Create a new `TzDatetime` type. See `NTi::TTzDatetimeType` for more info. + /// The returned object will live for as long as this factory lives. + const TTzDatetimeType* TzDatetimeRaw(); + + /// Create a new `TzTimestamp` type. See `NTi::TTzTimestampType` for more info. + /// The returned object will live for as long as this factory lives. + const TTzTimestampType* TzTimestampRaw(); + + /// Create a new `Interval` type. See `NTi::TIntervalType` for more info. + /// The returned object will live for as long as this factory lives. + const TIntervalType* IntervalRaw(); + + /// Create a new `Decimal` type. See `NTi::TDecimalType` for more info. + /// The returned object will live for as long as this factory lives. + const TDecimalType* DecimalRaw(ui8 precision, ui8 scale); + + /// Create a new `Json` type. See `NTi::TJsonType` for more info. + /// The returned object will live for as long as this factory lives. + const TJsonType* JsonRaw(); + + /// Create a new `Yson` type. See `NTi::TYsonType` for more info. + /// The returned object will live for as long as this factory lives. + const TYsonType* YsonRaw(); + + /// Create a new `Uuid` type. See `NTi::TUuidType` for more info. + /// The returned object will live for as long as this factory lives. + const TUuidType* UuidRaw(); + + /// Create a new `Optional` type. See `NTi::TOptionalType` for more info. + /// The returned object will live for as long as this factory lives. + const TOptionalType* OptionalRaw(const TType* item); + + /// Create a new `List` type. See `NTi::TListType` for more info. + /// The returned object will live for as long as this factory lives. + const TListType* ListRaw(const TType* item); + + /// Create a new `Dict` type. See `NTi::TDictType` for more info. + /// The returned object will live for as long as this factory lives. + const TDictType* DictRaw(const TType* key, const TType* value); + + /// Create a new `Struct` type. See `NTi::TStructType` for more info. + /// The returned object will live for as long as this factory lives. + const TStructType* StructRaw(TStructType::TMembers items); + const TStructType* StructRaw(TMaybe<TStringBuf> name, TStructType::TMembers items); + + /// Create a new `Tuple` type. See `NTi::TTupleType` for more info. + /// The returned object will live for as long as this factory lives. + const TTupleType* TupleRaw(TTupleType::TElements items); + const TTupleType* TupleRaw(TMaybe<TStringBuf> name, TTupleType::TElements items); + + /// Create a new `Variant` type. See `NTi::TVariantType` for more info. + /// The returned object will live for as long as this factory lives. + const TVariantType* VariantRaw(const TType* inner); + const TVariantType* VariantRaw(TMaybe<TStringBuf> name, const TType* inner); + + /// Create a new `Tagged` type. See `NTi::TTaggedType` for more info. + /// The returned object will live for as long as this factory lives. + const TTaggedType* TaggedRaw(const TType* type, TStringBuf tag); + + /// @} + }; + + namespace NPrivate { + ITypeFactory* GetDefaultHeapFactory(); + } + + /// Create a heap-based factory. + /// + /// This factory uses heap to allocate type instances, and reference counting to track individual type lifetimes. + /// + /// Choose this factory if you need safety and flexibility. + ITypeFactoryPtr HeapFactory(); + + /// Create a memory-pool-based factory. + /// + /// This factory uses a memory pool to allocate type instances. All allocated memory will be released when + /// the factory dies. + /// + /// Choose this factory if you need speed and memory efficiency. You have to understand how memory pool works. + /// We advise testing your code with msan when using this factory. + /// + /// If in doubt, use a heap-based factory instead. + /// + /// @param deduplicate + /// if enabled, factory will cache all types and avoid allocations when creating a new type that is + /// strictly-equal (see `type_equivalence.h`) to some previously created type. + /// + /// For example: + /// + /// ``` + /// auto a = factory.Optional(factory.String()); + /// auto b = factory.Optional(factory.String()); + /// Y_ASSERT(a == b); + /// ``` + /// + /// If `deduplicate` is disabled, this assert will fail. If, however, `deduplicate` is enabled, + /// the assert will not fail because `a` and `b` will point to the same type instance. + /// + /// This behaviour slows down creation process, but can save some memory when working with complex types. + /// + /// @param initial + /// number of bytes in the first page of the memory pool. + /// By default, we pre-allocate `64` bytes. + /// + /// @param grow + /// policy for calculating size for a next pool page. + /// By default, next page is twice as big as the previous one. + /// + /// @param alloc + /// underlying allocator that'll be used to allocate pool pages. + /// + /// @param options + /// other memory pool options. + IPoolTypeFactoryPtr PoolFactory( + bool deduplicate = true, + size_t initial = 64, + TMemoryPool::IGrowPolicy* grow = TMemoryPool::TExpGrow::Instance(), + IAllocator* alloc = TDefaultAllocator::Instance(), + TMemoryPool::TOptions options = {}); +} diff --git a/library/cpp/type_info/type_info.cpp b/library/cpp/type_info/type_info.cpp new file mode 100644 index 0000000000..8c14b158d0 --- /dev/null +++ b/library/cpp/type_info/type_info.cpp @@ -0,0 +1 @@ +#include "type_info.h" diff --git a/library/cpp/type_info/type_info.h b/library/cpp/type_info/type_info.h new file mode 100644 index 0000000000..5c6e249c11 --- /dev/null +++ b/library/cpp/type_info/type_info.h @@ -0,0 +1,10 @@ +#pragma once + +#include "builder.h" +#include "error.h" +#include "type.h" +#include "type_constructors.h" +#include "type_equivalence.h" +#include "type_factory.h" +#include "type_io.h" +#include "type_list.h" diff --git a/library/cpp/type_info/type_io.cpp b/library/cpp/type_info/type_io.cpp new file mode 100644 index 0000000000..90b98c8baa --- /dev/null +++ b/library/cpp/type_info/type_io.cpp @@ -0,0 +1,1186 @@ +#include "type_io.h" + +#include "builder.h" +#include "type_constructors.h" +#include "type_factory.h" + +#include <util/generic/overloaded.h> + +#include <library/cpp/yson_pull/read_ops.h> + +#include <util/stream/mem.h> +#include <util/string/cast.h> +#include <util/generic/vector.h> +#include <util/generic/scope.h> + +namespace NTi::NIo { + namespace { + class TYsonDeserializer: private TNonCopyable { + public: + TYsonDeserializer(IPoolTypeFactory* factory, NYsonPull::TReader* reader) + : Factory_(factory) + , Reader_(reader) + { + } + + public: + const TType* ReadType() { + if (++Depth_ > 100) { + ythrow TDeserializationException() << "types are nested too deep"; + } + + Y_DEFER { + Depth_--; + }; + + auto event = Reader_->NextEvent(); + + if (event.Type() == NYsonPull::EEventType::BeginStream) { + event = Reader_->NextEvent(); + } + + if (event.Type() == NYsonPull::EEventType::EndStream) { + if (Depth_ == 1) { + return nullptr; + } else { + ythrow TDeserializationException() << "unexpected end of stream"; + } + } + + if (event.Type() == NYsonPull::EEventType::Scalar && event.AsScalar().Type() == NYsonPull::EScalarType::String) { + return ReadTypeFromData(TypeNameStringToEnum(event.AsString()), std::monostate{}); + } else if (event.Type() == NYsonPull::EEventType::BeginMap) { + return ReadTypeFromMap(); + } else { + ythrow TDeserializationException() << "type must be either a string or a map"; + } + } + + private: + struct TDictData { + const TType *Key, *Value; + }; + struct TDecimalData { + ui8 Precision, Scale; + }; + using TTypeData = std::variant< + std::monostate, + TDictData, + TDecimalData, + TStructBuilderRaw, + TTupleBuilderRaw, + TTaggedBuilderRaw>; + + const TType* ReadTypeFromMap() { + TMaybe<ETypeName> typeName; + TTypeData data; + + while (true) { + auto event = Reader_->NextEvent(); + if (event.Type() == NYsonPull::EEventType::Key) { + auto mapKey = event.AsString(); + + if (mapKey == "type_name") { + if (typeName.Defined()) { + ythrow TDeserializationException() << R"(duplicate key R"(type_name"))"; + } else { + typeName = TypeNameStringToEnum(ReadString(R"("type_name")")); + } + } else if (mapKey == "item") { + if (std::holds_alternative<std::monostate>(data)) { + data = TTaggedBuilderRaw(*Factory_); + } + if (std::holds_alternative<TTaggedBuilderRaw>(data)) { + auto& builder = std::get<TTaggedBuilderRaw>(data); + if (!builder.HasItem()) { + builder.SetItem(ReadType()); + } else { + ythrow TDeserializationException() << R"(duplicate key "item")"; + } + } else { + ythrow TDeserializationException() << R"(unexpected key "item")"; + } + } else if (mapKey == "key") { + if (std::holds_alternative<std::monostate>(data)) { + data = TDictData{nullptr, nullptr}; + } + if (std::holds_alternative<TDictData>(data)) { + auto& dictData = std::get<TDictData>(data); + if (dictData.Key == nullptr) { + dictData.Key = ReadType(); + } else { + ythrow TDeserializationException() << R"(duplicate key "key")"; + } + } else { + ythrow TDeserializationException() << R"(unexpected key "key")"; + } + } else if (mapKey == "value") { + if (std::holds_alternative<std::monostate>(data)) { + data = TDictData{nullptr, nullptr}; + } + if (std::holds_alternative<TDictData>(data)) { + auto& dictData = std::get<TDictData>(data); + if (dictData.Value == nullptr) { + dictData.Value = ReadType(); + } else { + ythrow TDeserializationException() << R"(duplicate key "value")"; + } + } else { + ythrow TDeserializationException() << R"(unexpected key "value")"; + } + } else if (mapKey == "members") { + if (std::holds_alternative<std::monostate>(data)) { + data = TStructBuilderRaw(*Factory_); + } + if (std::holds_alternative<TStructBuilderRaw>(data)) { + ReadMembers(std::get<TStructBuilderRaw>(data)); + } else { + ythrow TDeserializationException() << R"(unexpected key "members")"; + } + } else if (mapKey == "elements") { + if (std::holds_alternative<std::monostate>(data)) { + data = TTupleBuilderRaw(*Factory_); + } + if (std::holds_alternative<TTupleBuilderRaw>(data)) { + ReadElements(std::get<TTupleBuilderRaw>(data)); + } else { + ythrow TDeserializationException() << R"(unexpected key "elements")"; + } + } else if (mapKey == "tag") { + if (std::holds_alternative<std::monostate>(data)) { + data = TTaggedBuilderRaw(*Factory_); + } + if (std::holds_alternative<TTaggedBuilderRaw>(data)) { + auto& builder = std::get<TTaggedBuilderRaw>(data); + if (!builder.HasTag()) { + builder.SetTag(ReadString(R"("tag")")); + } else { + ythrow TDeserializationException() << R"(duplicate key "tag")"; + } + } else { + ythrow TDeserializationException() << R"(unexpected key "tag")"; + } + } else if (mapKey == "precision") { + if (std::holds_alternative<std::monostate>(data)) { + data = TDecimalData{ReadSmallInt(R"("precision")"), 0}; + } else if (std::holds_alternative<TDecimalData>(data)) { + auto& decimalData = std::get<TDecimalData>(data); + if (decimalData.Precision == 0) { + decimalData.Precision = ReadSmallInt(R"("precision")"); + } else { + ythrow TDeserializationException() << R"(duplicate key "precision")"; + } + } else { + ythrow TDeserializationException() << R"(unexpected key "precision")"; + } + } else if (mapKey == "scale") { + if (std::holds_alternative<std::monostate>(data)) { + data = TDecimalData{0, ReadSmallInt(R"("scale")")}; + } else if (std::holds_alternative<TDecimalData>(data)) { + auto& decimalData = std::get<TDecimalData>(data); + if (decimalData.Scale == 0) { + decimalData.Scale = ReadSmallInt(R"("scale")"); + } else { + ythrow TDeserializationException() << R"(duplicate key "scale")"; + } + } else { + ythrow TDeserializationException() << R"(unexpected key "scale")"; + } + } else { + NYsonPull::NReadOps::SkipValue(*Reader_); + } + } else if (event.Type() == NYsonPull::EEventType::EndMap) { + if (!typeName.Defined()) { + ythrow TDeserializationException() << R"(missing required key "type_name")"; + } + + return ReadTypeFromData(*typeName, std::move(data)); + } else { + ythrow TDeserializationException() << "unexpected event " << event.Type(); + } + } + } + + const TType* ReadTypeFromData(ETypeName typeName, TTypeData data) { + const TType* type; + + switch (typeName) { + case ETypeName::Bool: + type = TBoolType::InstanceRaw(); + break; + case ETypeName::Int8: + type = TInt8Type::InstanceRaw(); + break; + case ETypeName::Int16: + type = TInt16Type::InstanceRaw(); + break; + case ETypeName::Int32: + type = TInt32Type::InstanceRaw(); + break; + case ETypeName::Int64: + type = TInt64Type::InstanceRaw(); + break; + case ETypeName::Uint8: + type = TUint8Type::InstanceRaw(); + break; + case ETypeName::Uint16: + type = TUint16Type::InstanceRaw(); + break; + case ETypeName::Uint32: + type = TUint32Type::InstanceRaw(); + break; + case ETypeName::Uint64: + type = TUint64Type::InstanceRaw(); + break; + case ETypeName::Float: + type = TFloatType::InstanceRaw(); + break; + case ETypeName::Double: + type = TDoubleType::InstanceRaw(); + break; + case ETypeName::String: + type = TStringType::InstanceRaw(); + break; + case ETypeName::Utf8: + type = TUtf8Type::InstanceRaw(); + break; + case ETypeName::Date: + type = TDateType::InstanceRaw(); + break; + case ETypeName::Datetime: + type = TDatetimeType::InstanceRaw(); + break; + case ETypeName::Timestamp: + type = TTimestampType::InstanceRaw(); + break; + case ETypeName::TzDate: + type = TTzDateType::InstanceRaw(); + break; + case ETypeName::TzDatetime: + type = TTzDatetimeType::InstanceRaw(); + break; + case ETypeName::TzTimestamp: + type = TTzTimestampType::InstanceRaw(); + break; + case ETypeName::Interval: + type = TIntervalType::InstanceRaw(); + break; + case ETypeName::Decimal: { + if (!std::holds_alternative<TDecimalData>(data)) { + ythrow TDeserializationException() << R"(missing required keys "precision" and "scale" for type Decimal)"; + } + + auto& decimalData = std::get<TDecimalData>(data); + + if (decimalData.Precision == 0) { + ythrow TDeserializationException() << R"(missing required key "precision" for type Decimal)"; + } + + if (decimalData.Scale == 0) { + ythrow TDeserializationException() << R"(missing required key "scale" for type Decimal)"; + } + + return Factory_->DecimalRaw(decimalData.Precision, decimalData.Scale); + } + case ETypeName::Json: + type = TJsonType::InstanceRaw(); + break; + case ETypeName::Yson: + type = TYsonType::InstanceRaw(); + break; + case ETypeName::Uuid: + type = TUuidType::InstanceRaw(); + break; + case ETypeName::Void: + type = TVoidType::InstanceRaw(); + break; + case ETypeName::Null: + type = TNullType::InstanceRaw(); + break; + case ETypeName::Optional: { + if (!std::holds_alternative<TTaggedBuilderRaw>(data)) { + ythrow TDeserializationException() << R"(missing required key "item" for type Optional)"; + } + + auto& builder = std::get<TTaggedBuilderRaw>(data); + + if (!builder.HasItem()) { + ythrow TDeserializationException() << R"(missing required key "item" for type Optional)"; + } + + return Factory_->OptionalRaw(*builder.GetItem()); + } + case ETypeName::List: { + if (!std::holds_alternative<TTaggedBuilderRaw>(data)) { + ythrow TDeserializationException() << R"(missing required key "item" for type List)"; + } + + auto& builder = std::get<TTaggedBuilderRaw>(data); + + if (!builder.HasItem()) { + ythrow TDeserializationException() << R"(missing required key "item" for type List)"; + } + + return Factory_->ListRaw(*builder.GetItem()); + } + case ETypeName::Dict: { + if (!std::holds_alternative<TDictData>(data)) { + ythrow TDeserializationException() << R"(missing required keys "key" and "value" for type Dict)"; + } + + auto& dictData = std::get<TDictData>(data); + + if (dictData.Key == nullptr) { + ythrow TDeserializationException() << R"(missing required key "key" for type Dict)"; + } + + if (dictData.Value == nullptr) { + ythrow TDeserializationException() << R"(missing required key "value" for type Dict)"; + } + + return Factory_->DictRaw(dictData.Key, dictData.Value); + } + case ETypeName::Struct: { + if (!std::holds_alternative<TStructBuilderRaw>(data)) { + ythrow TDeserializationException() << R"(missing required key "members" for type Struct)"; + } + + return std::get<TStructBuilderRaw>(data).BuildRaw(); + } + case ETypeName::Tuple: { + if (!std::holds_alternative<TTupleBuilderRaw>(data)) { + ythrow TDeserializationException() << R"(missing required key "elements" for type Tuple)"; + } + + return std::get<TTupleBuilderRaw>(data).BuildRaw(); + } + case ETypeName::Variant: { + if (std::holds_alternative<TStructBuilderRaw>(data)) { + return std::get<TStructBuilderRaw>(data).BuildVariantRaw(); + } else if (std::holds_alternative<TTupleBuilderRaw>(data)) { + return std::get<TTupleBuilderRaw>(data).BuildVariantRaw(); + } else { + ythrow TDeserializationException() << R"(missing both keys "members" and "elements" for type Variant)"; + } + } + case ETypeName::Tagged: { + if (!std::holds_alternative<TTaggedBuilderRaw>(data)) { + ythrow TDeserializationException() << R"(missing required keys "tag" and "item" for type Tagged)"; + } + + auto& builder = std::get<TTaggedBuilderRaw>(data); + + if (!builder.HasItem()) { + ythrow TDeserializationException() << R"(missing required key "item" for type Tagged)"; + } + + if (!builder.HasTag()) { + ythrow TDeserializationException() << R"(missing required key "tag" for type Tagged)"; + } + + return builder.BuildRaw(); + } + } + + if (!std::holds_alternative<std::monostate>(data)) { + ythrow TDeserializationException() << "unexpected key for type " << typeName; + } + + return type; + } + + TStringBuf ReadString(TStringBuf what) { + auto event = Reader_->NextEvent(); + + if (event.Type() != NYsonPull::EEventType::Scalar || event.AsScalar().Type() != NYsonPull::EScalarType::String) { + ythrow TDeserializationException() << what << " must contain a string"; + } + + return event.AsString(); + } + + ui8 ReadSmallInt(TStringBuf what) { + auto event = Reader_->NextEvent(); + + if (event.Type() != NYsonPull::EEventType::Scalar || event.AsScalar().Type() != NYsonPull::EScalarType::Int64) { + ythrow TDeserializationException() << what << " must contain a signed integer"; + } + + auto result = event.AsScalar().AsInt64(); + + if (result <= 0) { + ythrow TDeserializationException() << what << " must be greater than zero"; + } + + if (result > Max<ui8>()) { + ythrow TDeserializationException() << what << " is too big"; + } + + return static_cast<ui8>(result); + } + + void ReadMembers(TStructBuilderRaw& builder) { + if (Reader_->NextEvent().Type() != NYsonPull::EEventType::BeginList) { + ythrow TDeserializationException() << R"("members" must contain a list)"; + } + + while (true) { + auto event = Reader_->NextEvent(); + + if (event.Type() == NYsonPull::EEventType::BeginMap) { + while (true) { + auto event = Reader_->NextEvent(); + + if (event.Type() == NYsonPull::EEventType::Key) { + auto mapKey = event.AsString(); + if (mapKey == "name") { + if (builder.HasMemberName()) { + ythrow TDeserializationException() << R"(duplicate key "name")"; + } + + builder.AddMemberName(ReadString(R"("name")")); + } else if (mapKey == "type") { + if (builder.HasMemberType()) { + ythrow TDeserializationException() << R"(duplicate key "type")"; + } + + builder.AddMemberType(ReadType()); + } else { + NYsonPull::NReadOps::SkipValue(*Reader_); + } + } else if (event.Type() == NYsonPull::EEventType::EndMap) { + if (!builder.HasMemberName()) { + ythrow TDeserializationException() << R"(missing required key "name")"; + } + if (!builder.HasMemberType()) { + ythrow TDeserializationException() << R"(missing required key "type")"; + } + + builder.AddMember(); + break; + } else { + ythrow TDeserializationException() << "unexpected event " << event.Type(); + } + } + } else if (event.Type() == NYsonPull::EEventType::EndList) { + break; + } else { + ythrow TDeserializationException() << R"("members" must contain a list of maps)"; + } + } + } + + void ReadElements(TTupleBuilderRaw& builder) { + if (Reader_->NextEvent().Type() != NYsonPull::EEventType::BeginList) { + ythrow TDeserializationException() << R"("elements" must contain a list)"; + } + + while (true) { + auto event = Reader_->NextEvent(); + + if (event.Type() == NYsonPull::EEventType::BeginMap) { + while (true) { + auto event = Reader_->NextEvent(); + + if (event.Type() == NYsonPull::EEventType::Key) { + auto mapKey = event.AsString(); + if (mapKey == "type") { + if (builder.HasElementType()) { + ythrow TDeserializationException() << R"(duplicate key "type")"; + } + + builder.AddElementType(ReadType()); + } else { + NYsonPull::NReadOps::SkipValue(*Reader_); + } + } else if (event.Type() == NYsonPull::EEventType::EndMap) { + if (!builder.HasElementType()) { + ythrow TDeserializationException() << R"(missing required key "type")"; + } + + builder.AddElement(); + break; + } else { + ythrow TDeserializationException() << "unexpected event " << event.Type(); + } + } + } else if (event.Type() == NYsonPull::EEventType::EndList) { + break; + } else { + ythrow TDeserializationException() << R"("elements" must contain a list of maps)"; + } + } + } + + static ETypeName TypeNameStringToEnum(TStringBuf name) { + static const THashMap<TStringBuf, ETypeName> dispatch = { + {"void", ETypeName::Void}, + {"null", ETypeName::Null}, + {"bool", ETypeName::Bool}, + {"int8", ETypeName::Int8}, + {"int16", ETypeName::Int16}, + {"int32", ETypeName::Int32}, + {"int64", ETypeName::Int64}, + {"uint8", ETypeName::Uint8}, + {"uint16", ETypeName::Uint16}, + {"uint32", ETypeName::Uint32}, + {"uint64", ETypeName::Uint64}, + {"float", ETypeName::Float}, + {"double", ETypeName::Double}, + {"string", ETypeName::String}, + {"utf8", ETypeName::Utf8}, + {"date", ETypeName::Date}, + {"datetime", ETypeName::Datetime}, + {"timestamp", ETypeName::Timestamp}, + {"tz_date", ETypeName::TzDate}, + {"tz_datetime", ETypeName::TzDatetime}, + {"tz_timestamp", ETypeName::TzTimestamp}, + {"interval", ETypeName::Interval}, + {"json", ETypeName::Json}, + {"yson", ETypeName::Yson}, + {"uuid", ETypeName::Uuid}, + {"decimal", ETypeName::Decimal}, + {"optional", ETypeName::Optional}, + {"list", ETypeName::List}, + {"dict", ETypeName::Dict}, + {"struct", ETypeName::Struct}, + {"tuple", ETypeName::Tuple}, + {"variant", ETypeName::Variant}, + {"tagged", ETypeName::Tagged}, + }; + + if (auto it = dispatch.find(name); it != dispatch.end()) { + return it->second; + } else { + ythrow TDeserializationException() << "unknown type " << TString{name}.Quote(); + } + } + + private: + IPoolTypeFactory* Factory_; + NYsonPull::TReader* Reader_; + size_t Depth_ = 0; + }; + } + + TTypePtr DeserializeYson(ITypeFactory& factory, NYsonPull::TReader& reader, bool deduplicate) { + auto pool = PoolFactory(deduplicate, 2048); + auto type = DeserializeYsonRaw(*pool, reader); + return factory.Adopt(type->AsPtr()); + } + + TTypePtr DeserializeYson(ITypeFactory& factory, TStringBuf data, bool deduplicate) { + auto reader = NYsonPull::TReader(NYsonPull::NInput::FromMemory(data), NYsonPull::EStreamType::Node); + return DeserializeYson(factory, reader, deduplicate); + } + + TTypePtr DeserializeYson(ITypeFactory& factory, IInputStream& input, bool deduplicate) { + auto reader = NYsonPull::TReader(NYsonPull::NInput::FromInputStream(&input), NYsonPull::EStreamType::Node); + return DeserializeYson(factory, reader, deduplicate); + } + + const TType* DeserializeYsonRaw(IPoolTypeFactory& factory, NYsonPull::TReader& reader) { + if (reader.LastEvent().Type() != NYsonPull::EEventType::BeginStream) { + ythrow TDeserializationException() << "stream contains extraneous data"; + } + + auto type = DeserializeYsonMultipleRaw(factory, reader); + + if (type == nullptr) { + ythrow TDeserializationException() << "unexpected end of stream"; + } + + if (reader.NextEvent().Type() != NYsonPull::EEventType::EndStream) { + ythrow TDeserializationException() << "stream contains extraneous data"; + } + + return type; + } + + const TType* DeserializeYsonRaw(IPoolTypeFactory& factory, TStringBuf data) { + auto reader = NYsonPull::TReader(NYsonPull::NInput::FromMemory(data), NYsonPull::EStreamType::Node); + return DeserializeYsonRaw(factory, reader); + } + + const TType* DeserializeYsonRaw(IPoolTypeFactory& factory, IInputStream& input) { + auto reader = NYsonPull::TReader(NYsonPull::NInput::FromInputStream(&input), NYsonPull::EStreamType::Node); + return DeserializeYsonRaw(factory, reader); + } + + const TType* DeserializeYsonMultipleRaw(IPoolTypeFactory& factory, NYsonPull::TReader& reader) { + return TYsonDeserializer(&factory, &reader).ReadType(); + } + + TString SerializeYson(const TType* type, bool humanReadable, bool includeTags) { + auto result = TString(); + auto writer = humanReadable + ? NYsonPull::MakePrettyTextWriter(NYsonPull::NOutput::FromString(&result), NYsonPull::EStreamType::Node) + : NYsonPull::MakeBinaryWriter(NYsonPull::NOutput::FromString(&result), NYsonPull::EStreamType::Node); + SerializeYson(type, writer.GetConsumer(), includeTags); + return result; + } + + void SerializeYson(const TType* type, NYsonPull::IConsumer& consumer, bool includeTags) { + consumer.OnBeginStream(); + SerializeYsonMultiple(type, consumer, includeTags); + consumer.OnEndStream(); + } + + void SerializeYson(const TType* type, IOutputStream& stream, bool humanReadable, bool includeTags) { + auto writer = humanReadable + ? NYsonPull::MakePrettyTextWriter(NYsonPull::NOutput::FromOutputStream(&stream), NYsonPull::EStreamType::Node) + : NYsonPull::MakeBinaryWriter(NYsonPull::NOutput::FromOutputStream(&stream), NYsonPull::EStreamType::Node); + SerializeYson(type, writer.GetConsumer(), includeTags); + } + + void SerializeYsonMultiple(const TType* type, NYsonPull::IConsumer& consumer, bool includeTags) { + type->VisitRaw(TOverloaded{ + [&consumer](const TVoidType*) { + consumer.OnScalarString("void"); + }, + [&consumer](const TNullType*) { + consumer.OnScalarString("null"); + }, + [&consumer](const TBoolType*) { + consumer.OnScalarString("bool"); + }, + [&consumer](const TInt8Type*) { + consumer.OnScalarString("int8"); + }, + [&consumer](const TInt16Type*) { + consumer.OnScalarString("int16"); + }, + [&consumer](const TInt32Type*) { + consumer.OnScalarString("int32"); + }, + [&consumer](const TInt64Type*) { + consumer.OnScalarString("int64"); + }, + [&consumer](const TUint8Type*) { + consumer.OnScalarString("uint8"); + }, + [&consumer](const TUint16Type*) { + consumer.OnScalarString("uint16"); + }, + [&consumer](const TUint32Type*) { + consumer.OnScalarString("uint32"); + }, + [&consumer](const TUint64Type*) { + consumer.OnScalarString("uint64"); + }, + [&consumer](const TFloatType*) { + consumer.OnScalarString("float"); + }, + [&consumer](const TDoubleType*) { + consumer.OnScalarString("double"); + }, + [&consumer](const TStringType*) { + consumer.OnScalarString("string"); + }, + [&consumer](const TUtf8Type*) { + consumer.OnScalarString("utf8"); + }, + [&consumer](const TDateType*) { + consumer.OnScalarString("date"); + }, + [&consumer](const TDatetimeType*) { + consumer.OnScalarString("datetime"); + }, + [&consumer](const TTimestampType*) { + consumer.OnScalarString("timestamp"); + }, + [&consumer](const TTzDateType*) { + consumer.OnScalarString("tz_date"); + }, + [&consumer](const TTzDatetimeType*) { + consumer.OnScalarString("tz_datetime"); + }, + [&consumer](const TTzTimestampType*) { + consumer.OnScalarString("tz_timestamp"); + }, + [&consumer](const TIntervalType*) { + consumer.OnScalarString("interval"); + }, + [&consumer](const TJsonType*) { + consumer.OnScalarString("json"); + }, + [&consumer](const TYsonType*) { + consumer.OnScalarString("yson"); + }, + [&consumer](const TUuidType*) { + consumer.OnScalarString("uuid"); + }, + [&consumer](const TDecimalType* t) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("decimal"); + + consumer.OnKey("precision"); + consumer.OnScalarInt64(t->GetPrecision()); + + consumer.OnKey("scale"); + consumer.OnScalarInt64(t->GetScale()); + + consumer.OnEndMap(); + }, + [&consumer, includeTags](const TOptionalType* t) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("optional"); + + consumer.OnKey("item"); + SerializeYsonMultiple(t->GetItemTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + }, + [&consumer, includeTags](const TListType* t) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("list"); + + consumer.OnKey("item"); + SerializeYsonMultiple(t->GetItemTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + }, + [&consumer, includeTags](const TDictType* t) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("dict"); + + consumer.OnKey("key"); + SerializeYsonMultiple(t->GetKeyTypeRaw(), consumer, includeTags); + + consumer.OnKey("value"); + SerializeYsonMultiple(t->GetValueTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + }, + [&consumer, includeTags](const TStructType* t) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("struct"); + + consumer.OnKey("members"); + consumer.OnBeginList(); + for (auto& item : t->GetMembers()) { + consumer.OnBeginMap(); + + consumer.OnKey("name"); + consumer.OnScalarString(item.GetName()); + + consumer.OnKey("type"); + SerializeYsonMultiple(item.GetTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + } + consumer.OnEndList(); + + consumer.OnEndMap(); + }, + [&consumer, includeTags](const TTupleType* t) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("tuple"); + + consumer.OnKey("elements"); + consumer.OnBeginList(); + for (auto& item : t->GetElements()) { + consumer.OnBeginMap(); + + consumer.OnKey("type"); + SerializeYsonMultiple(item.GetTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + } + consumer.OnEndList(); + + consumer.OnEndMap(); + }, + [&consumer, includeTags](const TVariantType* t) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("variant"); + + t->VisitUnderlyingRaw( + TOverloaded{ + [&consumer, includeTags](const TStructType* t) { + // Warning: we loose struct's name here. + // See https://ml.yandex-team.ru/thread/data-com-dev/171136785840079161/ + + consumer.OnKey("members"); + consumer.OnBeginList(); + for (auto& item : t->GetMembers()) { + consumer.OnBeginMap(); + + consumer.OnKey("name"); + consumer.OnScalarString(item.GetName()); + + consumer.OnKey("type"); + SerializeYsonMultiple(item.GetTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + } + consumer.OnEndList(); + }, + [&consumer, includeTags](const TTupleType* t) { + // Warning: we loose tuple's name here. + // See https://ml.yandex-team.ru/thread/data-com-dev/171136785840079161/ + + consumer.OnKey("elements"); + consumer.OnBeginList(); + for (auto& item : t->GetElements()) { + consumer.OnBeginMap(); + + consumer.OnKey("type"); + SerializeYsonMultiple(item.GetTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + } + consumer.OnEndList(); + }}); + + consumer.OnEndMap(); + }, + [&consumer, includeTags](const TTaggedType* t) { + if (includeTags) { + consumer.OnBeginMap(); + + consumer.OnKey("type_name"); + consumer.OnScalarString("tagged"); + + consumer.OnKey("tag"); + consumer.OnScalarString(t->GetTag()); + + consumer.OnKey("item"); + SerializeYsonMultiple(t->GetItemTypeRaw(), consumer, includeTags); + + consumer.OnEndMap(); + } else { + SerializeYsonMultiple(t->GetItemTypeRaw(), consumer, includeTags); + } + }, + }); + } + + namespace { + void WriteVoidType(NYsonPull::IConsumer& consumer) { + consumer.OnBeginList(); + consumer.OnScalarString("VoidType"); + consumer.OnEndList(); + } + + void WriteNullType(NYsonPull::IConsumer& consumer) { + consumer.OnBeginList(); + consumer.OnScalarString("NullType"); + consumer.OnEndList(); + } + + void WriteDataType(NYsonPull::IConsumer& consumer, EPrimitiveTypeName name) { + consumer.OnBeginList(); + consumer.OnScalarString("DataType"); + consumer.OnScalarString(ToString(name)); + consumer.OnEndList(); + } + } + + void AsYqlType(const TType* type, NYsonPull::IConsumer& consumer, bool includeTags) { + type->VisitRaw(TOverloaded{ + [&consumer](const TVoidType*) { + WriteVoidType(consumer); + }, + [&consumer](const TNullType*) { + WriteNullType(consumer); + }, + [&consumer](const TBoolType*) { + WriteDataType(consumer, EPrimitiveTypeName::Bool); + }, + [&consumer](const TInt8Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Int8); + }, + [&consumer](const TInt16Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Int16); + }, + [&consumer](const TInt32Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Int32); + }, + [&consumer](const TInt64Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Int64); + }, + [&consumer](const TUint8Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Uint8); + }, + [&consumer](const TUint16Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Uint16); + }, + [&consumer](const TUint32Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Uint32); + }, + [&consumer](const TUint64Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Uint64); + }, + [&consumer](const TFloatType*) { + WriteDataType(consumer, EPrimitiveTypeName::Float); + }, + [&consumer](const TDoubleType*) { + WriteDataType(consumer, EPrimitiveTypeName::Double); + }, + [&consumer](const TStringType*) { + WriteDataType(consumer, EPrimitiveTypeName::String); + }, + [&consumer](const TUtf8Type*) { + WriteDataType(consumer, EPrimitiveTypeName::Utf8); + }, + [&consumer](const TDateType*) { + WriteDataType(consumer, EPrimitiveTypeName::Date); + }, + [&consumer](const TDatetimeType*) { + WriteDataType(consumer, EPrimitiveTypeName::Datetime); + }, + [&consumer](const TTimestampType*) { + WriteDataType(consumer, EPrimitiveTypeName::Timestamp); + }, + [&consumer](const TTzDateType*) { + WriteDataType(consumer, EPrimitiveTypeName::TzDate); + }, + [&consumer](const TTzDatetimeType*) { + WriteDataType(consumer, EPrimitiveTypeName::TzDatetime); + }, + [&consumer](const TTzTimestampType*) { + WriteDataType(consumer, EPrimitiveTypeName::TzTimestamp); + }, + [&consumer](const TIntervalType*) { + WriteDataType(consumer, EPrimitiveTypeName::Interval); + }, + [&consumer](const TJsonType*) { + WriteDataType(consumer, EPrimitiveTypeName::Json); + }, + [&consumer](const TYsonType*) { + WriteDataType(consumer, EPrimitiveTypeName::Yson); + }, + [&consumer](const TUuidType*) { + WriteDataType(consumer, EPrimitiveTypeName::Uuid); + }, + [&consumer](const TDecimalType* t) { + consumer.OnBeginList(); + consumer.OnScalarString("DataType"); + consumer.OnScalarString("Decimal"); + consumer.OnScalarString(ToString(t->GetPrecision())); + consumer.OnScalarString(ToString(t->GetScale())); + consumer.OnEndList(); + }, + [&consumer, includeTags](const TOptionalType* t) { + consumer.OnBeginList(); + consumer.OnScalarString("OptionalType"); + AsYqlType(t->GetItemTypeRaw(), consumer, includeTags); + consumer.OnEndList(); + }, + [&consumer, includeTags](const TListType* t) { + consumer.OnBeginList(); + consumer.OnScalarString("ListType"); + AsYqlType(t->GetItemTypeRaw(), consumer, includeTags); + consumer.OnEndList(); + }, + [&consumer, includeTags](const TDictType* t) { + consumer.OnBeginList(); + consumer.OnScalarString("DictType"); + AsYqlType(t->GetKeyTypeRaw(), consumer, includeTags); + AsYqlType(t->GetValueTypeRaw(), consumer, includeTags); + consumer.OnEndList(); + }, + [&consumer, includeTags](const TStructType* t) { + consumer.OnBeginList(); + consumer.OnScalarString("StructType"); + { + consumer.OnBeginList(); + for (auto& item : t->GetMembers()) { + consumer.OnBeginList(); + consumer.OnScalarString(item.GetName()); + AsYqlType(item.GetTypeRaw(), consumer, includeTags); + consumer.OnEndList(); + } + consumer.OnEndList(); + } + consumer.OnEndList(); + }, + [&consumer, includeTags](const TTupleType* t) { + consumer.OnBeginList(); + consumer.OnScalarString("TupleType"); + { + consumer.OnBeginList(); + for (auto& item : t->GetElements()) { + AsYqlType(item.GetTypeRaw(), consumer, includeTags); + } + consumer.OnEndList(); + } + + consumer.OnEndList(); + }, + [&consumer, includeTags](const TVariantType* t) { + consumer.OnBeginList(); + consumer.OnScalarString("VariantType"); + AsYqlType(t->GetUnderlyingTypeRaw(), consumer, includeTags); + consumer.OnEndList(); + }, + [&consumer, includeTags](const TTaggedType* t) { + if (includeTags) { + consumer.OnBeginList(); + consumer.OnScalarString("TaggedType"); + consumer.OnScalarString(t->GetTag()); + AsYqlType(t->GetItemTypeRaw(), consumer, includeTags); + consumer.OnEndList(); + } else { + AsYqlType(t->GetItemTypeRaw(), consumer, includeTags); + } + }, + }); + } + + TString AsYqlType(const NTi::TType* type, bool includeTags) { + auto result = TString(); + auto writer = NYsonPull::MakePrettyTextWriter(NYsonPull::NOutput::FromString(&result), NYsonPull::EStreamType::Node); + writer.BeginStream(); + AsYqlType(type, writer.GetConsumer(), includeTags); + writer.EndStream(); + return result; + } + + void AsYqlRowSpec(const TType* maybeTagged, NYsonPull::IConsumer& consumer, bool includeTags) { + auto* type = maybeTagged->StripTagsRaw(); + + if (!type->IsStruct()) { + ythrow TApiException() << "AsYqlRowSpec expected a struct type but got " << type->GetTypeName(); + } + + consumer.OnBeginMap(); + consumer.OnKey("StrictSchema"); + consumer.OnScalarBoolean(true); + consumer.OnKey("Type"); + AsYqlType(type, consumer, includeTags); + consumer.OnEndMap(); + } + + TString AsYqlRowSpec(const NTi::TType* type, bool includeTags) { + auto result = TString(); + auto writer = NYsonPull::MakePrettyTextWriter(NYsonPull::NOutput::FromString(&result), NYsonPull::EStreamType::Node); + writer.BeginStream(); + AsYqlRowSpec(type, writer.GetConsumer(), includeTags); + writer.EndStream(); + return result; + } + + void AsYtSchema(const TType* maybeTagged, NYsonPull::IConsumer& consumer, bool failOnEmptyStruct) { + auto* type = maybeTagged->StripTagsRaw(); + + if (!type->IsStruct()) { + ythrow TApiException() << "AsYtSchema expected a struct type but got " << type->GetTypeName(); + } + + auto* structType = type->AsStructRaw(); + + if (structType->GetMembers().empty()) { + if (failOnEmptyStruct) { + ythrow TApiException() << "AsYtSchema expected a non-empty struct"; + } + + AsYtSchema(Struct({{"_yql_fake_column", Optional(Bool())}}).Get(), consumer); + return; + } + + consumer.OnBeginAttributes(); + + consumer.OnKey("strict"); + consumer.OnScalarBoolean(true); + + consumer.OnKey("unique_keys"); + consumer.OnScalarBoolean(false); + + consumer.OnEndAttributes(); + + consumer.OnBeginList(); + for (auto& item : structType->GetMembers()) { + auto* itemType = item.GetTypeRaw()->StripTagsRaw(); + + bool required = true; + + if (itemType->IsOptional()) { + // toplevel optionals make non-required columns + itemType = itemType->AsOptionalRaw()->GetItemTypeRaw(); + required = false; + } + + TStringBuf typeString = itemType->VisitRaw(TOverloaded{ + [](const TVoidType*) -> TStringBuf { return "any"; }, + [](const TNullType*) -> TStringBuf { return "any"; }, + [](const TBoolType*) -> TStringBuf { return "boolean"; }, + [](const TInt8Type*) -> TStringBuf { return "int8"; }, + [](const TInt16Type*) -> TStringBuf { return "int16"; }, + [](const TInt32Type*) -> TStringBuf { return "int32"; }, + [](const TInt64Type*) -> TStringBuf { return "int64"; }, + [](const TUint8Type*) -> TStringBuf { return "uint8"; }, + [](const TUint16Type*) -> TStringBuf { return "uint16"; }, + [](const TUint32Type*) -> TStringBuf { return "uint32"; }, + [](const TUint64Type*) -> TStringBuf { return "uint64"; }, + [](const TFloatType*) -> TStringBuf { return "double"; }, + [](const TDoubleType*) -> TStringBuf { return "double"; }, + [](const TStringType*) -> TStringBuf { return "string"; }, + [](const TUtf8Type*) -> TStringBuf { return "utf8"; }, + [](const TDateType*) -> TStringBuf { return "uint16"; }, + [](const TDatetimeType*) -> TStringBuf { return "uint32"; }, + [](const TTimestampType*) -> TStringBuf { return "uint64"; }, + [](const TTzDateType*) -> TStringBuf { return "string"; }, + [](const TTzDatetimeType*) -> TStringBuf { return "string"; }, + [](const TTzTimestampType*) -> TStringBuf { return "string"; }, + [](const TIntervalType*) -> TStringBuf { return "int64"; }, + [](const TJsonType*) -> TStringBuf { return "string"; }, + [](const TYsonType*) -> TStringBuf { return "any"; }, + [](const TUuidType*) -> TStringBuf { return "string"; }, + [](const TDecimalType*) -> TStringBuf { return "string"; }, + [](const TOptionalType*) -> TStringBuf { return "any"; }, + [](const TListType*) -> TStringBuf { return "any"; }, + [](const TDictType*) -> TStringBuf { return "any"; }, + [](const TStructType*) -> TStringBuf { return "any"; }, + [](const TTupleType*) -> TStringBuf { return "any"; }, + [](const TVariantType*) -> TStringBuf { return "any"; }, + [](const TTaggedType*) -> TStringBuf { return "any"; }, + }); + + if (typeString == "any") { + // columns of type `any` cannot be required + required = false; + } + + { + consumer.OnBeginMap(); + + consumer.OnKey("name"); + consumer.OnScalarString(item.GetName()); + + consumer.OnKey("required"); + consumer.OnScalarBoolean(required); + + consumer.OnKey("type"); + consumer.OnScalarString(typeString); + + consumer.OnEndMap(); + } + } + consumer.OnEndList(); + } + + TString AsYtSchema(const NTi::TType* type, bool failOnEmptyStruct) { + auto result = TString(); + auto writer = NYsonPull::MakePrettyTextWriter(NYsonPull::NOutput::FromString(&result), NYsonPull::EStreamType::Node); + writer.BeginStream(); + AsYtSchema(type, writer.GetConsumer(), failOnEmptyStruct); + writer.EndStream(); + return result; + } +} diff --git a/library/cpp/type_info/type_io.h b/library/cpp/type_info/type_io.h new file mode 100644 index 0000000000..e400414cff --- /dev/null +++ b/library/cpp/type_info/type_io.h @@ -0,0 +1,115 @@ +#pragma once + +//! @file type_io.h +//! +//! Utilities for serializing and deserializing type instances. + +#include "type.h" + +#include <library/cpp/yson_pull/yson.h> + +namespace NTi::NIo { + /// Load type from a serialized representation. + /// + /// Serialization uses YSON (either binary or text). Contents are described in the [docs page]. + /// + /// Throws `TTypeDeserializationException` if input is not valid. + /// + /// [docs page]: https://a.yandex-team.ru/arc/trunk/arcadia/library/cpp/type_info/docs/types_serialization.md + /// + /// @param factory factory that will be used to allocate new type. Technically, type will be deserialized into + /// a temporary pool factory and then adopted into a given one. + /// @param reader yson pull reader that'll be used to read types. + /// @param deduplicate use deduplication while creating new types. See `NTi::PoolFactory` function for more info. + /// @{ + TTypePtr DeserializeYson(ITypeFactory& factory, NYsonPull::TReader& reader, bool deduplicate = true); + TTypePtr DeserializeYson(ITypeFactory& factory, TStringBuf data, bool deduplicate = true); + TTypePtr DeserializeYson(ITypeFactory& factory, IInputStream& input, bool deduplicate = true); + /// @} + + /// Like `Deserialize`, but returns a raw pointer. + /// @{ + const TType* DeserializeYsonRaw(IPoolTypeFactory& factory, NYsonPull::TReader& reader); + const TType* DeserializeYsonRaw(IPoolTypeFactory& factory, TStringBuf data); + const TType* DeserializeYsonRaw(IPoolTypeFactory& factory, IInputStream& input); + /// @} + + /// Like `Deserialize`, but allows deserializing multiple types from the same reader. + /// + /// This function takes a reader created with `NYsonPull::EStreamType::ListFragment` mode. It reads a type, + /// but doesn't fails if there is no `BeginStream` event. If the reader is empty, it returns nullptr. + /// + /// This function mirrors `SerializeMultiple`. Call it multiple times on the same reader to read multiple types. + const TType* DeserializeYsonMultipleRaw(IPoolTypeFactory& factory, NYsonPull::TReader& reader); + + /// Serialize this type info. + /// + /// Serialization uses YSON (either binary or text). Contents are described in the [RFC]. + /// + /// [RFC]: https://a.yandex-team.ru/arc/trunk/arcadia/logfeller/mvp/docs/types_serialization.md + /// + /// @param humanReadable use pretty textual format instead of a binary one. + /// @param includeTags when disabled, tagged types will be removed from the result, only tagged type contents + /// will be dumped. For example, `Tagged<'Url', String>` will be rendered as just `String`. + /// This is useful if you only care about physical layout of a type and don't want to export + /// any semantical meaning. + /// @{ + void SerializeYson(const TType* type, NYsonPull::IConsumer& consumer, bool includeTags = true); + void SerializeYson(const TType* type, IOutputStream& stream, bool humanReadable = false, bool includeTags = true); + TString SerializeYson(const TType* type, bool humanReadable = false, bool includeTags = true); + /// @} + + /// Like `Serialize`, but allows serializing multiple types into the same consumer. + /// + /// This function takes a consumer created with `NYsonPull::EStreamType::ListFragment` mode. It writes type, + /// but doesn't emit the `BeginStream` and `EndStream` commands. + /// + /// Call this function multiple times on the same consumer to write multiple types. Note that you must emit + /// the `BeginStream` and `EndStream` commands to the consumer yourself. + void SerializeYsonMultiple(const TType* type, NYsonPull::IConsumer& consumer, bool includeTags = true); + + /// Convert type to the lisp-like representation used in YQL row specs. + /// + /// TODO: move this code to yql/ + /// + /// @param includeTags when disabled, tagged types will be removed from the result, only tagged type contents + /// will be dumped. For example, `Tagged<'Url', String>` will be rendered as just `String`. + /// This is useful if you only care about physical layout of a type and don't want to export + /// any semantic meaning. + /// @{ + void AsYqlType(const TType* type, NYsonPull::IConsumer& consumer, bool includeTags = true); + TString AsYqlType(const TType* type, bool includeTags = true); + /// @} + + /// Generate a strict YQL row spec. Toplevel tags will be ignored. + /// + /// Throws `TApiException` if the type is not a (possibly tagged) struct. + /// + /// TODO: move this code to yql/ + /// + /// @param type type that'll be converted to YQL row spec. + /// @param consumer yson pull consumer. Attention: `OnBeginStream` and `OnEndStream` should be emitted manually + /// before and after calling this function. + /// @param includeTags same as in `TType::AsYqlType`. + /// @{ + void AsYqlRowSpec(const TType* type, NYsonPull::IConsumer& consumer, bool includeTags = true); + TString AsYqlRowSpec(const TType* type, bool includeTags = true); + /// @} + + /// Generate a strict YT schema (only types are exported, no index/sorting information). + /// + /// Throws `TApiException` if the type is not a (possibly tagged) struct. + /// + /// The schema is generated according to the translation rules of YQL types, i.e. container types translate + /// to `Any`. + /// + /// TODO: move this code to mapreduce/yt/ + /// + /// @param failOnEmptyStruct if true, will throw `TApiException` if called on a struct with no fields; + /// if false, will emit a strict YT schema with a single column `'_yql_fake_column'` + /// of type `Optional<Bool>` (this is how YQL handles empty tables). + /// @{ + void AsYtSchema(const TType* type, NYsonPull::IConsumer& consumer, bool failOnEmptyStruct = true); + TString AsYtSchema(const TType* type, bool failOnEmptyStruct = true); + /// @} +} diff --git a/library/cpp/type_info/type_list.cpp b/library/cpp/type_info/type_list.cpp new file mode 100644 index 0000000000..b741b2858b --- /dev/null +++ b/library/cpp/type_info/type_list.cpp @@ -0,0 +1 @@ +#include "type_list.h" diff --git a/library/cpp/type_info/type_list.h b/library/cpp/type_info/type_list.h new file mode 100644 index 0000000000..c87c51918d --- /dev/null +++ b/library/cpp/type_info/type_list.h @@ -0,0 +1,183 @@ +#pragma once + +//! @file type_list.h +//! +//! Enum with all type names that are included in the Common Type System. +//! +//! +//! # Primitive and non-primitive types +//! +//! Some systems only work with primitive types, so we've split the enum in two: the first contains primitive types, +//! and the second contains all types, including the primitive ones. This way systems that are only interested +//! in primitives can stop handling containers and make all their switch-cases exhaustive. +//! +//! Consequently, the class hierarchy follows the same division: there is `NTi::TPrimitiveType`, +//! from which all primitives are derived. +//! +//! +//! # Enumerator values +//! +//! Enumerator values are implementation detail and should not be relied upon. In particular, use `NTi::ToTypeName` +//! and `NTi::ToPrimitiveTypeName` to safely cast between `NTi::EPrimitiveTypeName` and `NTi::ETypeName`. Also, don't +//! use enumerator values for serialization and deserialization — convert enumerator values to strings +//! it you need persistence. + +#include <util/system/types.h> +#include <util/generic/variant.h> + +namespace NTi { + /// Enum with names of all primitive types. + /// + /// See the file-level documentation. + enum class EPrimitiveTypeName : i32 { + Bool, + + Int8, + Int16, + Int32, + Int64, + Uint8, + Uint16, + Uint32, + Uint64, + + Float, + Double, + + String, + Utf8, + + Date, + Datetime, + Timestamp, + TzDate, + TzDatetime, + TzTimestamp, + Interval, + + Decimal, + Json, + Yson, + Uuid, + }; + + /// Enum with names of all types, including primitives. + /// + /// See the file-level documentation. + enum class ETypeName : i32 { + // + // # Primitive types + + Bool, + + Int8, + Int16, + Int32, + Int64, + Uint8, + Uint16, + Uint32, + Uint64, + + Float, + Double, + + String, + Utf8, + + Date, + Datetime, + Timestamp, + TzDate, + TzDatetime, + TzTimestamp, + Interval, + + Decimal, + Json, + Yson, + Uuid, + + FIRST_PRIMITIVE = Bool, + LAST_PRIMITIVE = Uuid, + + // + // # Singular types + + Void, + Null, + + FIRST_SINGULAR = Void, + LAST_SINGULAR = Null, + + // + // # Containers + + Optional, + List, + Dict, + Struct, + Tuple, + Variant, + Tagged, + + FIRST_CONTAINER = Optional, + LAST_CONTAINER = Tagged, + }; + + /// Return true if the given type is a primitive one. + /// + /// Primitive type is a type that have no type parameters and is not a singular one. + inline constexpr bool IsPrimitive(ETypeName typeName) { + return ETypeName::FIRST_PRIMITIVE <= typeName && typeName <= ETypeName::LAST_PRIMITIVE; + } + + /// Return true if the given type is one of singular types. + /// + /// Singular type is a type that has only one instance and therefore carries no information, + /// i.e. occupy zero-length memory buffer. + inline constexpr bool IsSingular(ETypeName typeName) { + return ETypeName::FIRST_SINGULAR <= typeName && typeName <= ETypeName::LAST_SINGULAR; + } + + /// Return true if the given type is one of containers. + /// + /// Container type is a type that has type parameters. + inline constexpr bool IsContainer(ETypeName typeName) { + return ETypeName::FIRST_CONTAINER <= typeName && typeName <= ETypeName::LAST_CONTAINER; + } + + /// Return true if the given type has any type parameters. + inline constexpr bool HasTypeParameters(ETypeName typeName) { + return IsContainer(typeName); + } + + /// Return true if the given type has any non-type parameters. + inline constexpr bool HasNonTypeParameters(ETypeName typeName) { + return typeName == ETypeName::Decimal; + } + + /// Return true if the given type has any type or non-type parameters. + inline constexpr bool HasParameters(ETypeName typeName) { + return HasTypeParameters(typeName) || HasNonTypeParameters(typeName); + } + + /// Safely cast `NTi::EPrimitiveTypeName` to `NTi::ETypeName`. + /// + /// Enumerator values should not relied upon, therefore users should not cast `NTi::EPrimitiveTypeName` + /// to `NTi::ETypeName` using `static_cast`. + inline constexpr ETypeName ToTypeName(EPrimitiveTypeName primitiveTypeName) { + // Note: there's a test in ut/type_list.cpp that checks this is a safe conversion + return static_cast<ETypeName>(primitiveTypeName); + } + + /// Cast `NTi::ETypeName` to `NTi::EPrimitiveTypeName`, panic if the given type is not a primitive one. + /// + /// Enumerator values should not relied upon, therefore users should not cast `NTi::ETypeName` + /// to `NTi::EPrimitiveTypeName` using `static_cast`. + inline constexpr EPrimitiveTypeName ToPrimitiveTypeName(ETypeName typeName) { + Y_VERIFY(IsPrimitive(typeName)); + // Note: there's a test in ut/type_list.cpp that checks this is a safe conversion + return static_cast<EPrimitiveTypeName>(typeName); + } +} diff --git a/library/cpp/type_info/ut/builder.cpp b/library/cpp/type_info/ut/builder.cpp new file mode 100644 index 0000000000..43b7bb8c9b --- /dev/null +++ b/library/cpp/type_info/ut/builder.cpp @@ -0,0 +1,125 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +class Builder: public NTesting::TTest { +public: + void SetUp() override { + F = NTi::PoolFactory(false); + } + + void TearDown() override { + F.Reset(); + } + + NTi::IPoolTypeFactoryPtr F; +}; + +TEST_F(Builder, TaggedBuilder) { + auto builder = NTi::TTaggedBuilderRaw(*F); + + ASSERT_FALSE(builder.CanBuild()); + ASSERT_FALSE(builder.HasTag()); + ASSERT_FALSE(builder.HasItem()); + + UNIT_ASSERT_EQUAL(builder.GetTag(), Nothing()); + UNIT_ASSERT_EQUAL(builder.GetItem(), Nothing()); + + { + auto tag = TString("Url"); + builder.SetTag(tag); + } + + ASSERT_FALSE(builder.CanBuild()); + ASSERT_TRUE(builder.HasTag()); + ASSERT_FALSE(builder.HasItem()); + + UNIT_ASSERT_EQUAL(builder.GetTag(), MakeMaybe<TStringBuf>("Url")); + UNIT_ASSERT_EQUAL(builder.GetItem(), Nothing()); + + { + auto ty = NTi::String(); + builder.SetItem(ty); + } + + ASSERT_TRUE(builder.CanBuild()); + ASSERT_TRUE(builder.HasTag()); + ASSERT_TRUE(builder.HasItem()); + + UNIT_ASSERT_EQUAL(builder.GetTag(), MakeMaybe<TStringBuf>("Url")); + UNIT_ASSERT_EQUAL(builder.GetItem(), MakeMaybe<const NTi::TType*>(NTi::TStringType::InstanceRaw())); + + { + auto tagged = builder.Build(); + ASSERT_STRICT_EQ(tagged, NTi::Tagged(NTi::String(), "Url")); + } + + ASSERT_TRUE(builder.CanBuild()); + + builder.Reset(); + + ASSERT_FALSE(builder.CanBuild()); + ASSERT_FALSE(builder.HasTag()); + ASSERT_FALSE(builder.HasItem()); + + UNIT_ASSERT_EQUAL(builder.GetTag(), Nothing()); + UNIT_ASSERT_EQUAL(builder.GetItem(), Nothing()); + + builder.SetTag("T"); + builder.SetItem(NTi::String()); + + ASSERT_TRUE(builder.CanBuild()); + ASSERT_TRUE(builder.HasTag()); + ASSERT_TRUE(builder.HasItem()); + + UNIT_ASSERT_EQUAL(builder.GetTag(), MakeMaybe<TStringBuf>("T")); + UNIT_ASSERT_EQUAL(builder.GetItem(), MakeMaybe<const NTi::TType*>(NTi::TStringType::InstanceRaw())); + + builder.DiscardItem(); + + ASSERT_FALSE(builder.CanBuild()); + ASSERT_TRUE(builder.HasTag()); + ASSERT_FALSE(builder.HasItem()); + + UNIT_ASSERT_EQUAL(builder.GetTag(), MakeMaybe<TStringBuf>("T")); + UNIT_ASSERT_EQUAL(builder.GetItem(), Nothing()); + + builder.DiscardTag(); + + builder.SetTag("T"); + builder.SetItem(NTi::String()); + + ASSERT_TRUE(builder.CanBuild()); + ASSERT_TRUE(builder.HasTag()); + ASSERT_TRUE(builder.HasItem()); +} + +TEST_F(Builder, TaggedBuilderChaining) { + auto builder = NTi::TTaggedBuilderRaw(*F) + .SetTag("Uuu") + .SetItem(NTi::Optional(NTi::String())); + + ASSERT_STRICT_EQ(builder.Build(), NTi::Tagged(NTi::Optional(NTi::String()), "Uuu")); + + builder = std::move(builder) + .DiscardTag() + .SetTag("Urls"); + + ASSERT_STRICT_EQ(builder.Build(), NTi::Tagged(NTi::Optional(NTi::String()), "Urls")); + + builder = std::move(builder) + .DiscardItem() + .SetItem(F->ListRaw(F->StringRaw())); + + ASSERT_STRICT_EQ(builder.Build(), NTi::Tagged(NTi::List(NTi::String()), "Urls")); + + auto type = std::move(builder) + .Reset() + .SetTag("Url") + .SetItem(F->StringRaw()) + .Build(); + + ASSERT_STRICT_EQ(type, NTi::Tagged(NTi::String(), "Url")); +} diff --git a/library/cpp/type_info/ut/test_data.cpp b/library/cpp/type_info/ut/test_data.cpp new file mode 100644 index 0000000000..36944e7bc4 --- /dev/null +++ b/library/cpp/type_info/ut/test_data.cpp @@ -0,0 +1,91 @@ +#include <library/cpp/testing/unittest/gtest.h> +#include <library/cpp/resource/resource.h> + +#include <library/cpp/type_info/type_info.h> +#include <library/cpp/type_info/type_io.h> + +#include <util/string/strip.h> +#include <util/string/split.h> + +using namespace NTi; + +std::vector<std::vector<TString>> ParseData(TStringBuf data, int expectedFieldsCount) { + TString noComments; + { + TMemoryInput in(data); + TString line; + while (in.ReadLine(line)) { + if (StripString(line).StartsWith('#')) { + continue; + } + noComments += line; + } + } + + std::vector<std::vector<TString>> result; + for (TStringBuf record : StringSplitter(noComments).SplitByString(";;")) { + record = StripString(record); + if (record.Empty()) { + continue; + } + std::vector<TString> fields; + for (TStringBuf field : StringSplitter(record).SplitByString("::")) { + fields.emplace_back(StripString(field)); + } + if (static_cast<int>(fields.size()) != expectedFieldsCount) { + ythrow yexception() << "Unexpected field count expected: " << expectedFieldsCount << " actual: " << fields.size(); + } + result.push_back(fields); + } + return result; +} + +TEST(TestData, GoodTypes) { + auto records = ParseData(NResource::Find("/good"), 2); + + for (const auto& record : records) { + const auto& typeYson = record.at(0); + const auto& typeText = record.at(1); + TString context = TStringBuilder() + << "text: " << typeText << Endl + << "yson: " << typeYson << Endl; + auto wrapError = [&] (const std::exception& ex) { + return yexception() << "Unexpected error: " << ex.what() << '\n' << context; + }; + + TTypePtr type; + try { + type = NIo::DeserializeYson(*HeapFactory(), typeYson); + } catch (const std::exception& ex) { + ythrow wrapError(ex); + } + UNIT_ASSERT_VALUES_EQUAL_C(ToString(*type), typeText, context); + + TTypePtr type2; + try { + auto yson2 = NIo::SerializeYson(type.Get(), true); + type2 = NIo::DeserializeYson(*HeapFactory(), yson2); + } catch (const std::exception& ex) { + ythrow wrapError(ex); + } + UNIT_ASSERT_VALUES_EQUAL_C(*type, *type2, context); + } +} + +TEST(TestData, BadTypes) { + auto records = ParseData(NResource::Find("/bad"), 3); + + for (const auto& record : records) { + const auto& typeYson = record.at(0); + const auto& exceptionMessage = record.at(1); + + TString context = TStringBuilder() + << "exception: " << exceptionMessage << Endl + << "yson: " << typeYson << Endl; + UNIT_ASSERT_EXCEPTION_CONTAINS_C( + NIo::DeserializeYson(*HeapFactory(), typeYson), + yexception, + exceptionMessage, + context); + } +}
\ No newline at end of file diff --git a/library/cpp/type_info/ut/type_basics.cpp b/library/cpp/type_info/ut/type_basics.cpp new file mode 100644 index 0000000000..83996d63d3 --- /dev/null +++ b/library/cpp/type_info/ut/type_basics.cpp @@ -0,0 +1,381 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +TEST_TF(TypeBasics, Void) { + auto t = f.Void(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Void); + ASSERT_TRUE(t->IsVoid()); +} + +TEST_TF(TypeBasics, Bool) { + auto t = f.Bool(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Bool); + ASSERT_TRUE(t->IsBool()); +} + +TEST_TF(TypeBasics, Int8) { + auto t = f.Int8(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int8); + ASSERT_TRUE(t->IsInt8()); +} + +TEST_TF(TypeBasics, Int16) { + auto t = f.Int16(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int16); + ASSERT_TRUE(t->IsInt16()); +} + +TEST_TF(TypeBasics, Int32) { + auto t = f.Int32(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int32); + ASSERT_TRUE(t->IsInt32()); +} + +TEST_TF(TypeBasics, Int64) { + auto t = f.Int64(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int64); + ASSERT_TRUE(t->IsInt64()); +} + +TEST_TF(TypeBasics, Uint8) { + auto t = f.Uint8(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint8); + ASSERT_TRUE(t->IsUint8()); +} + +TEST_TF(TypeBasics, Uint16) { + auto t = f.Uint16(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint16); + ASSERT_TRUE(t->IsUint16()); +} + +TEST_TF(TypeBasics, Uint32) { + auto t = f.Uint32(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint32); + ASSERT_TRUE(t->IsUint32()); +} + +TEST_TF(TypeBasics, Uint64) { + auto t = f.Uint64(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint64); + ASSERT_TRUE(t->IsUint64()); +} + +TEST_TF(TypeBasics, Float) { + auto t = f.Float(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Float); + ASSERT_TRUE(t->IsFloat()); +} + +TEST_TF(TypeBasics, Double) { + auto t = f.Double(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Double); + ASSERT_TRUE(t->IsDouble()); +} + +TEST_TF(TypeBasics, String) { + auto t = f.String(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::String); + ASSERT_TRUE(t->IsString()); +} + +TEST_TF(TypeBasics, Utf8) { + auto t = f.Utf8(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Utf8); + ASSERT_TRUE(t->IsUtf8()); +} + +TEST_TF(TypeBasics, Date) { + auto t = f.Date(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Date); + ASSERT_TRUE(t->IsDate()); +} + +TEST_TF(TypeBasics, Datetime) { + auto t = f.Datetime(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Datetime); + ASSERT_TRUE(t->IsDatetime()); +} + +TEST_TF(TypeBasics, Timestamp) { + auto t = f.Timestamp(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Timestamp); + ASSERT_TRUE(t->IsTimestamp()); +} + +TEST_TF(TypeBasics, TzDate) { + auto t = f.TzDate(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::TzDate); + ASSERT_TRUE(t->IsTzDate()); +} + +TEST_TF(TypeBasics, TzDatetime) { + auto t = f.TzDatetime(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::TzDatetime); + ASSERT_TRUE(t->IsTzDatetime()); +} + +TEST_TF(TypeBasics, TzTimestamp) { + auto t = f.TzTimestamp(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::TzTimestamp); + ASSERT_TRUE(t->IsTzTimestamp()); +} + +TEST_TF(TypeBasics, Interval) { + auto t = f.Interval(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Interval); + ASSERT_TRUE(t->IsInterval()); +} + +TEST_TF(TypeBasics, Decimal) { + auto t = f.Decimal(20, 10); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Decimal); + ASSERT_TRUE(t->IsDecimal()); + ASSERT_EQ(t->GetPrecision(), 20); + ASSERT_EQ(t->GetScale(), 10); +} + +TEST_TF(TypeBasics, Json) { + auto t = f.Json(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Json); + ASSERT_TRUE(t->IsJson()); +} + +TEST_TF(TypeBasics, Yson) { + auto t = f.Yson(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Yson); + ASSERT_TRUE(t->IsYson()); +} + +TEST_TF(TypeBasics, Uuid) { + auto t = f.Uuid(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uuid); + ASSERT_TRUE(t->IsUuid()); +} + +TEST_TF(TypeBasics, Optional) { + auto t = f.Optional(f.Void()); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Optional); + ASSERT_TRUE(t->IsOptional()); + ASSERT_TRUE(t->GetItemType()->IsVoid()); + ASSERT_EQ(t->GetItemType().Get(), t->GetItemTypeRaw()); +} + +TEST_TF(TypeBasics, List) { + auto t = f.List(f.Void()); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::List); + ASSERT_TRUE(t->IsList()); + ASSERT_TRUE(t->GetItemType()->IsVoid()); + ASSERT_EQ(t->GetItemType().Get(), t->GetItemTypeRaw()); +} + +TEST_TF(TypeBasics, Dict) { + auto t = f.Dict(f.Void(), f.String()); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Dict); + ASSERT_TRUE(t->IsDict()); + ASSERT_TRUE(t->GetKeyType()->IsVoid()); + ASSERT_EQ(t->GetKeyType().Get(), t->GetKeyTypeRaw()); + ASSERT_TRUE(t->GetValueType()->IsString()); + ASSERT_EQ(t->GetValueType().Get(), t->GetValueTypeRaw()); +} + +TEST_TF(TypeBasics, EmptyStruct) { + auto t = f.Struct({}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetMembers().size(), 0); +} + +TEST_TF(TypeBasics, NamedEmptyStruct) { + auto t = f.Struct("S", {}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "S"); + ASSERT_EQ(t->GetMembers().size(), 0); +} + +TEST_TF(TypeBasics, Struct) { + auto t = f.Struct({{"a", f.Int64()}, {"b", f.Int8()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetMembers().size(), 2); + ASSERT_EQ(t->GetMembers()[0].GetName(), "a"); + ASSERT_TRUE(t->GetMembers()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetMembers()[0].GetType().Get(), t->GetMembers()[0].GetTypeRaw()); + ASSERT_EQ(t->GetMembers()[1].GetName(), "b"); + ASSERT_TRUE(t->GetMembers()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetMembers()[1].GetType().Get(), t->GetMembers()[1].GetTypeRaw()); +} + +TEST_TF(TypeBasics, NamedStruct) { + auto t = f.Struct("S", {{"a", f.Int64()}, {"b", f.Int8()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "S"); + ASSERT_EQ(t->GetMembers().size(), 2); + ASSERT_EQ(t->GetMembers()[0].GetName(), "a"); + ASSERT_TRUE(t->GetMembers()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetMembers()[0].GetType().Get(), t->GetMembers()[0].GetTypeRaw()); + ASSERT_EQ(t->GetMembers()[1].GetName(), "b"); + ASSERT_TRUE(t->GetMembers()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetMembers()[1].GetType().Get(), t->GetMembers()[1].GetTypeRaw()); +} + +TEST_TF(TypeBasics, EmptyTuple) { + auto t = f.Tuple({}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetElements().size(), 0); +} + +TEST_TF(TypeBasics, NamedEmptyTuple) { + auto t = f.Tuple("T", {}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "T"); + ASSERT_EQ(t->GetElements().size(), 0); +} + +TEST_TF(TypeBasics, Tuple) { + auto t = f.Tuple({{f.Int64()}, {f.Int8()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetElements().size(), 2); + ASSERT_TRUE(t->GetElements()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetElements()[0].GetType().Get(), t->GetElements()[0].GetTypeRaw()); + ASSERT_TRUE(t->GetElements()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetElements()[1].GetType().Get(), t->GetElements()[1].GetTypeRaw()); +} + +TEST_TF(TypeBasics, NamedTuple) { + auto t = f.Tuple("T", {{f.Int64()}, {f.Int8()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "T"); + ASSERT_EQ(t->GetElements().size(), 2); + ASSERT_TRUE(t->GetElements()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetElements()[0].GetType().Get(), t->GetElements()[0].GetTypeRaw()); + ASSERT_TRUE(t->GetElements()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetElements()[1].GetType().Get(), t->GetElements()[1].GetTypeRaw()); +} + +TEST_TF(TypeBasics, VariantOverStruct) { + auto t = f.Variant(f.Struct("Inner", {{"x", f.Void()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_TRUE(t->IsVariantOverStruct()); + ASSERT_FALSE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsStruct()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST_TF(TypeBasics, NamedVariantOverStruct) { + auto t = f.Variant("V", f.Struct("Inner", {{"x", f.Void()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "V"); + ASSERT_TRUE(t->IsVariantOverStruct()); + ASSERT_FALSE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsStruct()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST_TF(TypeBasics, VariantOverTuple) { + auto t = f.Variant(f.Tuple("Inner", {{f.Void()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_FALSE(t->IsVariantOverStruct()); + ASSERT_TRUE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsTuple()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST_TF(TypeBasics, NamedVariantOverTuple) { + auto t = f.Variant("V", f.Tuple("Inner", {{f.Void()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "V"); + ASSERT_FALSE(t->IsVariantOverStruct()); + ASSERT_TRUE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsTuple()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST_TF(TypeBasics, Tagged) { + auto t = f.Tagged(f.Void(), "T"); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tagged); + ASSERT_TRUE(t->IsTagged()); + ASSERT_EQ(t->GetTag(), "T"); + ASSERT_TRUE(t->GetItemType()->IsVoid()); + ASSERT_EQ(t->GetItemType().Get(), t->GetItemTypeRaw()); +} + +TEST_TF(TypeBasics, EmptyStructLookupByName) { + auto s = f.Struct({}); + + ASSERT_FALSE(s->HasMember("item")); + ASSERT_EQ(s->GetMemberIndex("item"), -1); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + [=]() { + s->GetMember("item"); + }(), + NTi::TItemNotFound, "no item named 'item'"); +} + +TEST_TF(TypeBasics, StructLookupByName) { + auto s = f.Struct({ + {"a", f.Void()}, + {"field2", f.String()}, + {"field1", f.Utf8()}, + {"", f.Null()}, + }); + + ASSERT_TRUE(s->HasMember("a")); + ASSERT_EQ(s->GetMemberIndex("a"), 0); + + ASSERT_TRUE(s->HasMember("field2")); + ASSERT_EQ(s->GetMemberIndex("field2"), 1); + + ASSERT_TRUE(s->HasMember("field1")); + ASSERT_EQ(s->GetMemberIndex("field1"), 2); + + ASSERT_TRUE(s->HasMember("")); + ASSERT_EQ(s->GetMemberIndex(""), 3); + + ASSERT_FALSE(s->HasMember("b")); + ASSERT_EQ(s->GetMemberIndex("b"), -1); + + ASSERT_EQ(s->GetMember("a").GetName(), "a"); + ASSERT_TRUE(s->GetMember("a").GetTypeRaw()->IsVoid()); + + ASSERT_EQ(s->GetMember("field2").GetName(), "field2"); + ASSERT_TRUE(s->GetMember("field2").GetTypeRaw()->IsString()); + + ASSERT_EQ(s->GetMember("field1").GetName(), "field1"); + ASSERT_TRUE(s->GetMember("field1").GetTypeRaw()->IsUtf8()); + + ASSERT_EQ(s->GetMember("").GetName(), ""); + ASSERT_TRUE(s->GetMember("").GetTypeRaw()->IsNull()); + + UNIT_ASSERT_EXCEPTION_CONTAINS( + [=]() { + s->GetMember("b"); + }(), + NTi::TItemNotFound, "no item named 'b'"); +} diff --git a/library/cpp/type_info/ut/type_complexity_ut.cpp b/library/cpp/type_info/ut/type_complexity_ut.cpp new file mode 100644 index 0000000000..3b4f6e5372 --- /dev/null +++ b/library/cpp/type_info/ut/type_complexity_ut.cpp @@ -0,0 +1,33 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_complexity.h> +#include <library/cpp/type_info/type_constructors.h> + +using namespace NTi; + +TEST(TypeComplexity, Test) +{ + EXPECT_EQ(ComputeTypeComplexity(Int64()), 1); + EXPECT_EQ(ComputeTypeComplexity(String()), 1); + EXPECT_EQ(ComputeTypeComplexity(Null()), 1); + EXPECT_EQ(ComputeTypeComplexity(Decimal(4, 2)), 1); + + EXPECT_EQ(ComputeTypeComplexity(Optional(Utf8())), 2); + EXPECT_EQ(ComputeTypeComplexity(List(Json())), 2); + EXPECT_EQ(ComputeTypeComplexity(Tagged(String(), "jpeg")), 2); + EXPECT_EQ(ComputeTypeComplexity(Dict(String(), Optional(Int64()))), 4); + EXPECT_EQ(ComputeTypeComplexity(Struct({ + {"a", String()}, + {"b", List(Optional(Int64()))}, + })), 5); + EXPECT_EQ(ComputeTypeComplexity(Tuple({{Float()}, {Float()}})), 3); + + EXPECT_EQ(ComputeTypeComplexity(Variant(Struct({ + {"a", String()}, + {"b", Int64()}, + }))), 3); + EXPECT_EQ(ComputeTypeComplexity(Tuple({ + {String()}, + {Int64()}, + })), 3); +}
\ No newline at end of file diff --git a/library/cpp/type_info/ut/type_constraints.cpp b/library/cpp/type_info/ut/type_constraints.cpp new file mode 100644 index 0000000000..0f56bd89b0 --- /dev/null +++ b/library/cpp/type_info/ut/type_constraints.cpp @@ -0,0 +1,53 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +TEST_TF(TypeConstraints, DecimalScale) { + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + f.Decimal(20, 21); + }(), + NTi::TIllegalTypeException, "decimal scale 21 should be no greater than decimal precision 20"); +} + +TEST_TF(TypeConstraints, StructDuplicateItem) { + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + f.Struct({{"a", f.Void()}, {"a", f.String()}}); + }(), + NTi::TIllegalTypeException, "duplicate struct item 'a'"); + + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + f.Struct({{"a", f.Void()}, {"b", f.Bool()}, {"a", f.String()}}); + }(), + NTi::TIllegalTypeException, "duplicate struct item 'a'"); +} + +TEST_TF(TypeConstraints, StructEmpty) { + f.Struct({}); // empty structs are ok, this should not fail +} + +TEST_TF(TypeConstraints, TupleEmpty) { + f.Tuple({}); // empty tuples are ok, this should not fail +} + +TEST_TF(TypeConstraints, VariantStructEmpty) { + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + f.Variant(f.Struct({})); + }(), + NTi::TIllegalTypeException, "variant should contain at least one alternative"); +} + +TEST_TF(TypeConstraints, VariantTupleEmpty) { + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + f.Variant(f.Tuple({})); + }(), + NTi::TIllegalTypeException, "variant should contain at least one alternative"); +} + +TEST_TF(TypeConstraints, VariantWrongInnerType) { + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + f.Variant(f.String()); + }(), + NTi::TIllegalTypeException, "variants can only contain structs and tuples, got String instead"); +} diff --git a/library/cpp/type_info/ut/type_deserialize.cpp b/library/cpp/type_info/ut/type_deserialize.cpp new file mode 100644 index 0000000000..9e93a26bee --- /dev/null +++ b/library/cpp/type_info/ut/type_deserialize.cpp @@ -0,0 +1,528 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include <library/cpp/yson_pull/yson.h> + +#include "utils.h" + +TEST(TypeDeserialize, Void) { + ASSERT_DESERIALIZED_EQ(NTi::Void(), R"(void)"); + ASSERT_DESERIALIZED_EQ(NTi::Void(), R"({type_name=void})"); +} + +TEST(TypeDeserialize, Null) { + ASSERT_DESERIALIZED_EQ(NTi::Null(), R"(null)"); + ASSERT_DESERIALIZED_EQ(NTi::Null(), R"({type_name=null})"); +} + +TEST(TypeDeserialize, Bool) { + ASSERT_DESERIALIZED_EQ(NTi::Bool(), R"(bool)"); + ASSERT_DESERIALIZED_EQ(NTi::Bool(), R"({type_name=bool})"); +} + +TEST(TypeDeserialize, Int8) { + ASSERT_DESERIALIZED_EQ(NTi::Int8(), R"(int8)"); + ASSERT_DESERIALIZED_EQ(NTi::Int8(), R"({type_name=int8})"); +} + +TEST(TypeDeserialize, Int16) { + ASSERT_DESERIALIZED_EQ(NTi::Int16(), R"(int16)"); + ASSERT_DESERIALIZED_EQ(NTi::Int16(), R"({type_name=int16})"); +} + +TEST(TypeDeserialize, Int32) { + ASSERT_DESERIALIZED_EQ(NTi::Int32(), R"(int32)"); + ASSERT_DESERIALIZED_EQ(NTi::Int32(), R"({type_name=int32})"); +} + +TEST(TypeDeserialize, Int64) { + ASSERT_DESERIALIZED_EQ(NTi::Int64(), R"(int64)"); + ASSERT_DESERIALIZED_EQ(NTi::Int64(), R"({type_name=int64})"); +} + +TEST(TypeDeserialize, Uint8) { + ASSERT_DESERIALIZED_EQ(NTi::Uint8(), R"(uint8)"); + ASSERT_DESERIALIZED_EQ(NTi::Uint8(), R"({type_name=uint8})"); +} + +TEST(TypeDeserialize, Uint16) { + ASSERT_DESERIALIZED_EQ(NTi::Uint16(), R"(uint16)"); + ASSERT_DESERIALIZED_EQ(NTi::Uint16(), R"({type_name=uint16})"); +} + +TEST(TypeDeserialize, Uint32) { + ASSERT_DESERIALIZED_EQ(NTi::Uint32(), R"(uint32)"); + ASSERT_DESERIALIZED_EQ(NTi::Uint32(), R"({type_name=uint32})"); +} + +TEST(TypeDeserialize, Uint64) { + ASSERT_DESERIALIZED_EQ(NTi::Uint64(), R"(uint64)"); + ASSERT_DESERIALIZED_EQ(NTi::Uint64(), R"({type_name=uint64})"); +} + +TEST(TypeDeserialize, Float) { + ASSERT_DESERIALIZED_EQ(NTi::Float(), R"(float)"); + ASSERT_DESERIALIZED_EQ(NTi::Float(), R"({type_name=float})"); +} + +TEST(TypeDeserialize, Double) { + ASSERT_DESERIALIZED_EQ(NTi::Double(), R"(double)"); + ASSERT_DESERIALIZED_EQ(NTi::Double(), R"({type_name=double})"); +} + +TEST(TypeDeserialize, String) { + ASSERT_DESERIALIZED_EQ(NTi::String(), R"(string)"); + ASSERT_DESERIALIZED_EQ(NTi::String(), R"({type_name=string})"); +} + +TEST(TypeDeserialize, Utf8) { + ASSERT_DESERIALIZED_EQ(NTi::Utf8(), R"(utf8)"); + ASSERT_DESERIALIZED_EQ(NTi::Utf8(), R"({type_name=utf8})"); +} + +TEST(TypeDeserialize, Date) { + ASSERT_DESERIALIZED_EQ(NTi::Date(), R"(date)"); + ASSERT_DESERIALIZED_EQ(NTi::Date(), R"({type_name=date})"); +} + +TEST(TypeDeserialize, Datetime) { + ASSERT_DESERIALIZED_EQ(NTi::Datetime(), R"(datetime)"); + ASSERT_DESERIALIZED_EQ(NTi::Datetime(), R"({type_name=datetime})"); +} + +TEST(TypeDeserialize, Timestamp) { + ASSERT_DESERIALIZED_EQ(NTi::Timestamp(), R"(timestamp)"); + ASSERT_DESERIALIZED_EQ(NTi::Timestamp(), R"({type_name=timestamp})"); +} + +TEST(TypeDeserialize, TzDate) { + ASSERT_DESERIALIZED_EQ(NTi::TzDate(), R"(tz_date)"); + ASSERT_DESERIALIZED_EQ(NTi::TzDate(), R"({type_name=tz_date})"); +} + +TEST(TypeDeserialize, TzDatetime) { + ASSERT_DESERIALIZED_EQ(NTi::TzDatetime(), R"(tz_datetime)"); + ASSERT_DESERIALIZED_EQ(NTi::TzDatetime(), R"({type_name=tz_datetime})"); +} + +TEST(TypeDeserialize, TzTimestamp) { + ASSERT_DESERIALIZED_EQ(NTi::TzTimestamp(), R"(tz_timestamp)"); + ASSERT_DESERIALIZED_EQ(NTi::TzTimestamp(), R"({type_name=tz_timestamp})"); +} + +TEST(TypeDeserialize, Interval) { + ASSERT_DESERIALIZED_EQ(NTi::Interval(), R"(interval)"); + ASSERT_DESERIALIZED_EQ(NTi::Interval(), R"({type_name=interval})"); +} + +TEST(TypeDeserialize, Decimal) { + ASSERT_DESERIALIZED_EQ(NTi::Decimal(20, 10), R"({type_name=decimal; precision=20; scale=10})"); + ASSERT_DESERIALIZED_EQ(NTi::Decimal(20, 10), R"({scale=10; type_name=decimal; precision=20})"); + ASSERT_DESERIALIZED_EQ(NTi::Decimal(10, 10), R"({type_name=decimal; precision=10; scale=10})"); +} + +TEST(TypeDeserialize, DecimalMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({precision=20; scale=10})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; scale=10})"); + }(), + NTi::TDeserializationException, R"(missing required key "precision")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=decimal; precision=20})"); + }(), + NTi::TDeserializationException, R"(missing required key "scale")"); +} + +TEST(TypeDeserialize, Json) { + ASSERT_DESERIALIZED_EQ(NTi::Json(), R"(json)"); + ASSERT_DESERIALIZED_EQ(NTi::Json(), R"({type_name=json})"); +} + +TEST(TypeDeserialize, Yson) { + ASSERT_DESERIALIZED_EQ(NTi::Yson(), R"(yson)"); + ASSERT_DESERIALIZED_EQ(NTi::Yson(), R"({type_name=yson})"); +} + +TEST(TypeDeserialize, Uuid) { + ASSERT_DESERIALIZED_EQ(NTi::Uuid(), R"(uuid)"); + ASSERT_DESERIALIZED_EQ(NTi::Uuid(), R"({type_name=uuid})"); +} + +TEST(TypeDeserialize, Optional) { + ASSERT_DESERIALIZED_EQ(NTi::Optional(NTi::Void()), R"({type_name=optional; item=void})"); + ASSERT_DESERIALIZED_EQ(NTi::Optional(NTi::String()), R"({type_name=optional; item=string})"); + ASSERT_DESERIALIZED_EQ(NTi::Optional(NTi::String()), R"({item=string; type_name=optional})"); +} + +TEST(TypeDeserialize, OptionalMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({item=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=optional})"); + }(), + NTi::TDeserializationException, R"(missing required key "item")"); +} + +TEST(TypeDeserialize, List) { + ASSERT_DESERIALIZED_EQ(NTi::List(NTi::Void()), R"({type_name=list; item=void})"); + ASSERT_DESERIALIZED_EQ(NTi::List(NTi::String()), R"({type_name=list; item=string})"); + ASSERT_DESERIALIZED_EQ(NTi::List(NTi::String()), R"({item=string; type_name=list})"); +} + +TEST(TypeDeserialize, ListMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({item=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=list})"); + }(), + NTi::TDeserializationException, R"(missing required key "item")"); +} + +TEST(TypeDeserialize, Dict) { + ASSERT_DESERIALIZED_EQ(NTi::Dict(NTi::Void(), NTi::Void()), R"({type_name=dict; key=void; value=void})"); + ASSERT_DESERIALIZED_EQ(NTi::Dict(NTi::Int32(), NTi::String()), R"({type_name=dict; key=int32; value=string})"); + ASSERT_DESERIALIZED_EQ(NTi::Dict(NTi::Int32(), NTi::String()), R"({key=int32; value=string; type_name=dict})"); + ASSERT_DESERIALIZED_EQ(NTi::Dict(NTi::Int32(), NTi::String()), R"({value=string; key=int32; type_name=dict})"); +} + +TEST(TypeDeserialize, DictMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({key=string; value=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=dict})"); + }(), + NTi::TDeserializationException, R"(missing required keys "key" and "value")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=dict; value=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "key")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=dict; key=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "value")"); +} + +TEST(TypeDeserialize, StructEmpty) { + ASSERT_DESERIALIZED_EQ( + NTi::Struct({}), + R"({type_name=struct; members=[]})"); + ASSERT_DESERIALIZED_EQ( + NTi::Struct({}), + R"({members=[]; type_name=struct})"); +} + +TEST(TypeDeserialize, Struct) { + auto ty = NTi::Struct({{"ItemB", NTi::String()}, {"ItemA", NTi::List(NTi::Int64())}}); + + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=struct; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]; type_name=struct})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=struct; members=[{type=string; name=ItemB}; {name=ItemA; type={item=int64; type_name=list}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=struct; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]; name=#})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=struct; name=#; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); +} + +TEST(TypeDeserialize, StructMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({members=[]})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=struct})"); + }(), + NTi::TDeserializationException, R"(missing required key "members")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=struct; name=S})"); + }(), + NTi::TDeserializationException, R"(missing required key "members")"); +} + +TEST(TypeDeserialize, TupleEmpty) { + ASSERT_DESERIALIZED_EQ( + NTi::Tuple({}), + R"({type_name=tuple; elements=[]})"); + + ASSERT_DESERIALIZED_EQ( + NTi::Tuple({}), + R"({elements=[]; type_name=tuple})"); +} + +TEST(TypeDeserialize, Tuple) { + auto ty = NTi::Tuple({{NTi::String()}, {NTi::List(NTi::Int64())}}); + + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=tuple; elements=[{type=string}; {type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({elements=[{type=string}; {type={item=int64; type_name=list}}]; type_name=tuple})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=tuple; name=#; elements=[{type=string}; {type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=tuple; elements=[{type=string}; {type={type_name=list; item=int64}}]; name=#})"); +} + +TEST(TypeDeserialize, TupleMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({elements=[]})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=tuple})"); + }(), + NTi::TDeserializationException, R"(missing required key "elements")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=tuple; name=T})"); + }(), + NTi::TDeserializationException, R"(missing required key "elements")"); +} + +TEST(TypeDeserialize, VariantStruct) { + auto ty = NTi::Variant(NTi::Struct({{"ItemB", NTi::String()}, {"ItemA", NTi::List(NTi::Int64())}})); + + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=variant; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]; type_name=variant})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=variant; name=#; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=variant; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]; name=#})"); +} + +TEST(TypeDeserialize, VariantTuple) { + auto ty = NTi::Variant(NTi::Tuple({{NTi::String()}, {NTi::List(NTi::Int64())}})); + + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=variant; elements=[{type=string}; {type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({elements=[{type=string}; {type={type_name=list; item=int64}}]; type_name=variant})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=variant; name=#; elements=[{type=string}; {type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + ty, + R"({type_name=variant; elements=[{type=string}; {type={type_name=list; item=int64}}]; name=#})"); +} + +TEST(TypeDeserialize, VariantMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({elements=[]})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({name=X})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=variant})"); + }(), + NTi::TDeserializationException, R"(missing both keys "members" and "elements")"); +} + +TEST(TypeDeserialize, Tagged) { + ASSERT_DESERIALIZED_EQ( + NTi::Tagged(NTi::String(), "Url"), + R"({type_name=tagged; tag=Url; item=string})"); + ASSERT_DESERIALIZED_EQ( + NTi::Tagged(NTi::String(), "Url"), + R"({type_name=tagged; item=string; tag=Url})"); + ASSERT_DESERIALIZED_EQ( + NTi::Tagged(NTi::String(), "Url"), + R"({item=string; tag=Url; type_name=tagged})"); +} + +TEST(TypeDeserialize, TaggedMissingTypeParameters) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({tag=T; item=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({tag=T})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=tagged; tag=T})"); + }(), + NTi::TDeserializationException, R"(missing required key "item")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({type_name=tagged; item=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "tag")"); +} + +TEST(TypeDeserialize, ComplexTypeAsString) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(decimal)"); + }(), + NTi::TDeserializationException, R"(missing required keys "precision" and "scale")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(optional)"); + }(), + NTi::TDeserializationException, R"(missing required key "item")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(list)"); + }(), + NTi::TDeserializationException, R"(missing required key "item")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(dict)"); + }(), + NTi::TDeserializationException, R"(missing required keys "key" and "value")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(struct)"); + }(), + NTi::TDeserializationException, R"(missing required key "members")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(tuple)"); + }(), + NTi::TDeserializationException, R"(missing required key "elements")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(variant)"); + }(), + NTi::TDeserializationException, R"(missing both keys "members" and "elements")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"(tagged)"); + }(), + NTi::TDeserializationException, R"(missing required keys "tag" and "item")"); +} + +TEST(TypeDeserialize, MissingTypeName) { + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({item=string})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({tag=Url})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); + UNIT_ASSERT_EXCEPTION_CONTAINS( + []() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), R"({key=string; value=int32})"); + }(), + NTi::TDeserializationException, R"(missing required key "type_name")"); +} + +TEST(TypeDeserialize, UnknownKeys) { + auto tupleType = NTi::Tuple({{NTi::String()}, {NTi::List(NTi::Int64())}}); + + ASSERT_DESERIALIZED_EQ( + tupleType, + R"({type_name=tuple; unknown_key=<>0; elements=[{type=string}; {type={type_name=list; item=int64}}]})"); + + ASSERT_DESERIALIZED_EQ( + tupleType, + R"({type_name=tuple; elements=[{unknown_key={foo=<>0}; type=string}; {type={type_name=list; item=int64}}]})"); + + auto structType = NTi::Struct({{"ItemB", NTi::String()}, {"ItemA", NTi::List(NTi::Int64())}}); + ASSERT_DESERIALIZED_EQ( + structType, + R"({type_name=struct; unknown_key=[]; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); + ASSERT_DESERIALIZED_EQ( + structType, + R"({type_name=struct; members=[{name=ItemB; unknown_key=foo; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); + + auto utf8Type = NTi::Utf8(); + ASSERT_DESERIALIZED_EQ( + utf8Type, + R"({type_name=utf8; unknown_key=[];})"); +} + + +TEST(TypeDeserialize, DeepType) { + auto ty = TStringBuilder(); + for (size_t i = 0; i < 100; ++i) + ty << "{type_name=optional; item="; + ty << "string"; + for (size_t i = 0; i < 100; ++i) + ty << "}"; + + UNIT_ASSERT_EXCEPTION_CONTAINS([&ty]() { + NTi::NIo::DeserializeYson(*NTi::HeapFactory(), ty); + }(), + NTi::TDeserializationException, R"(too deep)"); +} + +TEST(TypeDeserialize, MultipleTypes) { + auto ty = "{type_name=optional; item=string}; {type_name=list; item=utf8}"; + + auto reader = NYsonPull::TReader(NYsonPull::NInput::FromMemory(ty), NYsonPull::EStreamType::ListFragment); + auto factory = NTi::PoolFactory(); + + { + auto ty1 = NTi::Optional(NTi::String()); + ASSERT_STRICT_EQ(NTi::NIo::DeserializeYsonMultipleRaw(*factory, reader), ty1.Get()); + } + + { + auto ty2 = NTi::List(NTi::Utf8()); + ASSERT_STRICT_EQ(NTi::NIo::DeserializeYsonMultipleRaw(*factory, reader), ty2.Get()); + } + + ASSERT_EQ(NTi::NIo::DeserializeYsonMultipleRaw(*factory, reader), nullptr); +} diff --git a/library/cpp/type_info/ut/type_equivalence.cpp b/library/cpp/type_info/ut/type_equivalence.cpp new file mode 100644 index 0000000000..ca697620ad --- /dev/null +++ b/library/cpp/type_info/ut/type_equivalence.cpp @@ -0,0 +1,394 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +TEST_TF(TypeEquivalence, StrictEqSelf) { + ASSERT_STRICT_EQ( + f.Void(), + f.Void()); + ASSERT_STRICT_EQ( + f.Bool(), + f.Bool()); + ASSERT_STRICT_EQ( + f.Int8(), + f.Int8()); + ASSERT_STRICT_EQ( + f.Int16(), + f.Int16()); + ASSERT_STRICT_EQ( + f.Int32(), + f.Int32()); + ASSERT_STRICT_EQ( + f.Int64(), + f.Int64()); + ASSERT_STRICT_EQ( + f.Uint8(), + f.Uint8()); + ASSERT_STRICT_EQ( + f.Uint16(), + f.Uint16()); + ASSERT_STRICT_EQ( + f.Uint32(), + f.Uint32()); + ASSERT_STRICT_EQ( + f.Uint64(), + f.Uint64()); + ASSERT_STRICT_EQ( + f.Float(), + f.Float()); + ASSERT_STRICT_EQ( + f.Double(), + f.Double()); + ASSERT_STRICT_EQ( + f.String(), + f.String()); + ASSERT_STRICT_EQ( + f.Utf8(), + f.Utf8()); + ASSERT_STRICT_EQ( + f.Date(), + f.Date()); + ASSERT_STRICT_EQ( + f.Datetime(), + f.Datetime()); + ASSERT_STRICT_EQ( + f.Timestamp(), + f.Timestamp()); + ASSERT_STRICT_EQ( + f.TzDate(), + f.TzDate()); + ASSERT_STRICT_EQ( + f.TzDatetime(), + f.TzDatetime()); + ASSERT_STRICT_EQ( + f.TzTimestamp(), + f.TzTimestamp()); + ASSERT_STRICT_EQ( + f.Interval(), + f.Interval()); + ASSERT_STRICT_EQ( + f.Decimal(20, 10), + f.Decimal(20, 10)); + ASSERT_STRICT_EQ( + f.Json(), + f.Json()); + ASSERT_STRICT_EQ( + f.Yson(), + f.Yson()); + ASSERT_STRICT_EQ( + f.Uuid(), + f.Uuid()); + ASSERT_STRICT_EQ( + f.Optional(f.Void()), + f.Optional(f.Void())); + ASSERT_STRICT_EQ( + f.List(f.Void()), + f.List(f.Void())); + ASSERT_STRICT_EQ( + f.Dict(f.Void(), f.String()), + f.Dict(f.Void(), f.String())); + ASSERT_STRICT_EQ( + f.Struct({}), + f.Struct({})); + ASSERT_STRICT_EQ( + f.Struct("S", {}), + f.Struct("S", {})); + ASSERT_STRICT_EQ( + f.Struct({{"a", f.Int64()}, {"b", f.Int8()}}), + f.Struct({{"a", f.Int64()}, {"b", f.Int8()}})); + ASSERT_STRICT_EQ( + f.Struct("S", {{"a", f.Int64()}, {"b", f.Int8()}}), + f.Struct("S", {{"a", f.Int64()}, {"b", f.Int8()}})); + ASSERT_STRICT_EQ( + f.Tuple({}), + f.Tuple({})); + ASSERT_STRICT_EQ( + f.Tuple("T", {}), + f.Tuple("T", {})); + ASSERT_STRICT_EQ( + f.Tuple({{f.Int64()}, {f.Int8()}}), + f.Tuple({{f.Int64()}, {f.Int8()}})); + ASSERT_STRICT_EQ( + f.Tuple("T", {{f.Int64()}, {f.Int8()}}), + f.Tuple("T", {{f.Int64()}, {f.Int8()}})); + ASSERT_STRICT_EQ( + f.Variant(f.Struct("Inner", {{"x", f.Void()}})), + f.Variant(f.Struct("Inner", {{"x", f.Void()}}))); + ASSERT_STRICT_EQ( + f.Variant("V", f.Struct("Inner", {{"x", f.Void()}})), + f.Variant("V", f.Struct("Inner", {{"x", f.Void()}}))); + ASSERT_STRICT_EQ( + f.Variant(f.Tuple("Inner", {{f.Void()}})), + f.Variant(f.Tuple("Inner", {{f.Void()}}))); + ASSERT_STRICT_EQ( + f.Variant("V", f.Tuple("Inner", {{f.Void()}})), + f.Variant("V", f.Tuple("Inner", {{f.Void()}}))); + ASSERT_STRICT_EQ( + f.Tagged(f.Void(), "T"), + f.Tagged(f.Void(), "T")); +} + +TEST_TF(TypeEquivalence, StrictNeOtherType) { + ASSERT_STRICT_NE( + f.Void(), + f.Bool()); + ASSERT_STRICT_NE( + f.Bool(), + f.Int8()); + ASSERT_STRICT_NE( + f.Int8(), + f.Int16()); + ASSERT_STRICT_NE( + f.Int16(), + f.Int32()); + ASSERT_STRICT_NE( + f.Int32(), + f.Int64()); + ASSERT_STRICT_NE( + f.Int64(), + f.Uint8()); + ASSERT_STRICT_NE( + f.Uint8(), + f.Uint16()); + ASSERT_STRICT_NE( + f.Uint16(), + f.Uint32()); + ASSERT_STRICT_NE( + f.Uint32(), + f.Uint64()); + ASSERT_STRICT_NE( + f.Uint64(), + f.Float()); + ASSERT_STRICT_NE( + f.Float(), + f.Double()); + ASSERT_STRICT_NE( + f.Double(), + f.String()); + ASSERT_STRICT_NE( + f.String(), + f.Utf8()); + ASSERT_STRICT_NE( + f.Utf8(), + f.Date()); + ASSERT_STRICT_NE( + f.Date(), + f.Datetime()); + ASSERT_STRICT_NE( + f.Datetime(), + f.Timestamp()); + ASSERT_STRICT_NE( + f.Timestamp(), + f.TzDate()); + ASSERT_STRICT_NE( + f.TzDate(), + f.TzDatetime()); + ASSERT_STRICT_NE( + f.TzDatetime(), + f.TzTimestamp()); + ASSERT_STRICT_NE( + f.TzTimestamp(), + f.Interval()); + ASSERT_STRICT_NE( + f.Interval(), + f.Decimal(20, 10)); + ASSERT_STRICT_NE( + f.Decimal(20, 10), + f.Json()); + ASSERT_STRICT_NE( + f.Json(), + f.Yson()); + ASSERT_STRICT_NE( + f.Yson(), + f.Uuid()); + ASSERT_STRICT_NE( + f.Uuid(), + f.Optional(f.Void())); + ASSERT_STRICT_NE( + f.Optional(f.Void()), + f.List(f.Void())); + ASSERT_STRICT_NE( + f.List(f.Void()), + f.Dict(f.Void(), f.String())); + ASSERT_STRICT_NE( + f.Dict(f.Void(), f.String()), + f.Struct({})); + ASSERT_STRICT_NE( + f.Struct({}), + f.Struct("S", {})); + ASSERT_STRICT_NE( + f.Struct("S", {}), + f.Struct({{"a", f.Int64()}, {"b", f.Int8()}})); + ASSERT_STRICT_NE( + f.Struct({{"a", f.Int64()}, {"b", f.Int8()}}), + f.Struct("S", {{"a", f.Int64()}, {"b", f.Int8()}})); + ASSERT_STRICT_NE( + f.Struct("S", {{"a", f.Int64()}, {"b", f.Int8()}}), + f.Tuple({})); + ASSERT_STRICT_NE( + f.Tuple({}), + f.Tuple("T", {})); + ASSERT_STRICT_NE( + f.Tuple("T", {}), + f.Tuple({{f.Int64()}, {f.Int8()}})); + ASSERT_STRICT_NE( + f.Tuple({{f.Int64()}, {f.Int8()}}), + f.Tuple("T", {{f.Int64()}, {f.Int8()}})); + ASSERT_STRICT_NE( + f.Tuple("T", {{f.Int64()}, {f.Int8()}}), + f.Variant(f.Struct("Inner", {{"x", f.Void()}}))); + ASSERT_STRICT_NE( + f.Variant(f.Struct("Inner", {{"x", f.Void()}})), + f.Variant("V", f.Struct("Inner", {{"x", f.Void()}}))); + ASSERT_STRICT_NE( + f.Variant("V", f.Struct("Inner", {{"x", f.Void()}})), + f.Variant(f.Tuple("Inner", {{f.Void()}}))); + ASSERT_STRICT_NE( + f.Variant(f.Tuple("Inner", {{f.Void()}})), + f.Variant("V", f.Tuple("Inner", {{f.Void()}}))); + ASSERT_STRICT_NE( + f.Variant("V", f.Tuple("Inner", {{f.Void()}})), + f.Tagged(f.Void(), "T")); + ASSERT_STRICT_NE( + f.Tagged(f.Void(), "T"), + f.Void()); +} + +TEST_TF(TypeEquivalence, StrictNeDecimal) { + ASSERT_STRICT_NE( + f.Decimal(20, 10), + f.Decimal(21, 10)); + ASSERT_STRICT_NE( + f.Decimal(20, 10), + f.Decimal(20, 11)); +} + +TEST_TF(TypeEquivalence, StrictNeStruct) { + ASSERT_STRICT_NE( + f.Struct({}), + f.Struct("", {})); + ASSERT_STRICT_NE( + f.Struct("name", {}), + f.Struct("other name", {})); + ASSERT_STRICT_NE( + f.Struct({}), + f.Struct({{"x", f.Void()}})); + ASSERT_STRICT_NE( + f.Struct({{"x", f.Void()}}), + f.Struct({{"y", f.Void()}})); + ASSERT_STRICT_NE( + f.Struct({{"x", f.Void()}}), + f.Struct({{"x", f.String()}})); + ASSERT_STRICT_NE( + f.Struct("name", {}), + f.Struct("name", {{"x", f.Void()}})); + ASSERT_STRICT_NE( + f.Struct("name", {{"x", f.Void()}}), + f.Struct("name", {{"y", f.Void()}})); + ASSERT_STRICT_NE( + f.Struct("name", {{"x", f.Void()}}), + f.Struct("name", {{"x", f.String()}})); + ASSERT_STRICT_NE( + f.Struct({{"x", f.Void()}, {"y", f.String()}}), + f.Struct({{"x", f.String()}, {"y", f.Void()}})); + ASSERT_STRICT_NE( + f.Struct({{"x", f.Void()}, {"y", f.Void()}}), + f.Struct({{"y", f.Void()}, {"x", f.Void()}})); + ASSERT_STRICT_NE( + f.Struct({{"x", f.Void()}, {"y", f.Void()}}), + f.Struct({{"x", f.Void()}, {"y", f.Void()}, {"z", f.Void()}})); +} + +TEST_TF(TypeEquivalence, StrictNeTuple) { + ASSERT_STRICT_NE( + f.Tuple({}), + f.Tuple("", {})); + ASSERT_STRICT_NE( + f.Tuple("name", {}), + f.Tuple("other name", {})); + ASSERT_STRICT_NE( + f.Tuple({}), + f.Tuple({{f.Void()}})); + ASSERT_STRICT_NE( + f.Tuple({{f.Void()}}), + f.Tuple({{f.String()}})); + ASSERT_STRICT_NE( + f.Tuple("name", {}), + f.Tuple("name", {{f.Void()}})); + ASSERT_STRICT_NE( + f.Tuple("name", {{f.Void()}}), + f.Tuple("name", {{f.String()}})); + ASSERT_STRICT_NE( + f.Tuple({{f.String()}, {f.Void()}}), + f.Tuple({{f.Void()}, {f.String()}})); + ASSERT_STRICT_NE( + f.Tuple({{f.Void()}, {f.Void()}}), + f.Tuple({{f.Void()}, {f.Void()}, {f.Void()}})); +} + +TEST_TF(TypeEquivalence, StrictNeVariant) { + ASSERT_STRICT_NE( + f.Variant(f.Tuple({{f.Void()}})), + f.Variant("", f.Tuple({{f.Void()}}))); + ASSERT_STRICT_NE( + f.Variant("", f.Tuple({{f.Void()}})), + f.Variant("X", f.Tuple({{f.Void()}}))); + ASSERT_STRICT_NE( + f.Variant(f.Tuple({{f.Void()}})), + f.Variant(f.Tuple({{f.String()}}))); + ASSERT_STRICT_NE( + f.Variant("X", f.Tuple({{f.Utf8()}})), + f.Variant("X", f.Tuple({{f.String()}}))); + ASSERT_STRICT_NE( + f.Variant(f.Tuple({{f.Utf8()}})), + f.Variant(f.Struct({{"_", f.Utf8()}}))); + ASSERT_STRICT_NE( + f.Variant(f.Struct({{"item1", f.String()}})), + f.Variant(f.Struct({{"item2", f.String()}}))); + ASSERT_STRICT_NE( + f.Variant("X", f.Struct({{"item2", f.String()}})), + f.Variant("X", f.Struct({{"item1", f.String()}}))); +} + +TEST_TF(TypeEquivalence, StrictNeTagged) { + ASSERT_STRICT_NE( + f.Tagged(f.String(), "Tag"), + f.Tagged(f.String(), "Other tag")); + ASSERT_STRICT_NE( + f.Tagged(f.String(), "Tag"), + f.Tagged(f.Utf8(), "Tag")); +} + +TEST_TF(TypeEquivalence, StrictNeDeep) { + auto t1 = f.Struct({ + {"i", f.Optional(f.String())}, + {"don't", f.Utf8()}, + {"have", f.Tuple("GeoCoordinates", {{f.Float()}, {f.Float()}})}, + {"enough", f.List(f.Optional(f.String()))}, + {"fantasy", f.Dict(f.String(), f.List(f.Utf8()))}, // < difference + {"to", f.Yson()}, + {"think", f.Optional(f.Json())}, + {"of", f.Optional(f.String())}, + {"a", f.List(f.Tuple({{f.String()}, {f.Tuple("GeoCoordinates", {{f.Float()}, {f.Float()}})}}))}, + {"meaningful", f.Optional(f.String())}, + {"example", f.Dict(f.Optional(f.Decimal(10, 5)), f.List(f.Dict(f.Int32(), f.Float())))}, + }); + + auto t2 = f.Struct({ + {"i", f.Optional(f.String())}, + {"don't", f.Utf8()}, + {"have", f.Tuple("GeoCoordinates", {{f.Float()}, {f.Float()}})}, + {"enough", f.List(f.Optional(f.String()))}, + {"fantasy", f.Dict(f.String(), f.List(f.String()))}, // < difference + {"to", f.Yson()}, + {"think", f.Optional(f.Json())}, + {"of", f.Optional(f.String())}, + {"a", f.List(f.Tuple({{f.String()}, {f.Tuple("GeoCoordinates", {{f.Float()}, {f.Float()}})}}))}, + {"meaningful", f.Optional(f.String())}, + {"example", f.Dict(f.Optional(f.Decimal(10, 5)), f.List(f.Dict(f.Int32(), f.Float())))}, + }); + + ASSERT_STRICT_NE(t1, t2); +} diff --git a/library/cpp/type_info/ut/type_factory.cpp b/library/cpp/type_info/ut/type_factory.cpp new file mode 100644 index 0000000000..0ab44129ad --- /dev/null +++ b/library/cpp/type_info/ut/type_factory.cpp @@ -0,0 +1,121 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +TEST(TypeFactory, AdoptPoolToPool) { + auto f1 = NTi::PoolFactory(); + auto f2 = NTi::PoolFactory(); + + auto t = f1->Optional(f1->List(f1->String())); + auto ta = f2->Adopt(t); + + ASSERT_NE(t.Get(), ta.Get()); + ASSERT_NE(t->GetItemTypeRaw(), ta->GetItemTypeRaw()); + ASSERT_EQ(t->GetItemTypeRaw()->AsListRaw()->GetItemTypeRaw(), ta->GetItemTypeRaw()->AsListRaw()->GetItemTypeRaw()); + + f1 = nullptr; + f2 = nullptr; + t = nullptr; + + // `ta` is still alive + + ASSERT_TRUE(ta->IsOptional()); + ASSERT_TRUE(ta->GetItemTypeRaw()->IsList()); +} + +TEST(TypeFactory, AdoptPoolToSamePool) { + auto f = NTi::PoolFactory(); + + auto t = f->Optional(f->List(f->String())); + auto ta = f->Adopt(t); + + ASSERT_EQ(t.Get(), ta.Get()); +} + +TEST(TypeFactory, AdoptHeapToHeap) { + auto f = NTi::HeapFactory(); + + auto t = f->Optional(f->List(f->String())); + auto ta = f->Adopt(t); + + ASSERT_EQ(t.Get(), ta.Get()); +} + +TEST(TypeFactory, AdoptHeapToPool) { + auto f1 = NTi::HeapFactory(); + auto f2 = NTi::PoolFactory(); + + auto t = f1->Optional(f1->List(f1->String())); + auto ta = f2->Adopt(t); + + ASSERT_NE(t.Get(), ta.Get()); + ASSERT_NE(t->GetItemTypeRaw(), ta->GetItemTypeRaw()); + ASSERT_EQ(t->GetItemTypeRaw()->AsListRaw()->GetItemTypeRaw(), ta->GetItemTypeRaw()->AsListRaw()->GetItemTypeRaw()); + + f1 = nullptr; + f2 = nullptr; + t = nullptr; + + // `ta` is still alive + + ASSERT_TRUE(ta->IsOptional()); + ASSERT_TRUE(ta->GetItemTypeRaw()->IsList()); +} + +TEST(TypeFactory, AdoptPoolToHeap) { + auto f1 = NTi::PoolFactory(); + auto f2 = NTi::HeapFactory(); + + auto t = f1->Optional(f1->List(f1->String())); + auto ta = f2->Adopt(t); + + ASSERT_NE(t.Get(), ta.Get()); + ASSERT_NE(t->GetItemTypeRaw(), ta->GetItemTypeRaw()); + ASSERT_EQ(t->GetItemTypeRaw()->AsListRaw()->GetItemTypeRaw(), ta->GetItemTypeRaw()->AsListRaw()->GetItemTypeRaw()); + + f1 = nullptr; + f2 = nullptr; + t = nullptr; + + // `ta` is still alive + + ASSERT_TRUE(ta->IsOptional()); + ASSERT_TRUE(ta->GetItemTypeRaw()->IsList()); +} + +TEST(TypeFactory, AdoptStaticToPool) { + auto f = NTi::PoolFactory(); + + auto t = NTi::Void(); + auto ta = f->Adopt(t); + + ASSERT_EQ(t.Get(), ta.Get()); +} + +TEST(TypeFactory, AdoptStaticToHeap) { + auto f = NTi::HeapFactory(); + + auto t = NTi::Void(); + auto ta = f->Adopt(t); + ASSERT_EQ(t.Get(), ta.Get()); +} + +TEST(TypeFactory, Dedup) { + { + auto f = NTi::PoolFactory(/* deduplicate = */ false); + + auto a = f->OptionalRaw(f->StringRaw()); + auto b = f->OptionalRaw(f->StringRaw()); + + ASSERT_NE(a, b); + } + + { + auto f = NTi::PoolFactory(/* deduplicate = */ true); + + auto a = f->OptionalRaw(f->StringRaw()); + auto b = f->OptionalRaw(f->StringRaw()); + + ASSERT_EQ(a, b); + } +} diff --git a/library/cpp/type_info/ut/type_factory_raw.cpp b/library/cpp/type_info/ut/type_factory_raw.cpp new file mode 100644 index 0000000000..37f5d71aa7 --- /dev/null +++ b/library/cpp/type_info/ut/type_factory_raw.cpp @@ -0,0 +1,365 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +TEST(TypeFactoryRaw, Void) { + auto f = NTi::PoolFactory(false); + auto t = f->VoidRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Void); + ASSERT_TRUE(t->IsVoid()); +} + +TEST(TypeFactoryRaw, Bool) { + auto f = NTi::PoolFactory(false); + auto t = f->BoolRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Bool); + ASSERT_TRUE(t->IsBool()); +} + +TEST(TypeFactoryRaw, Int8) { + auto f = NTi::PoolFactory(false); + auto t = f->Int8Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int8); + ASSERT_TRUE(t->IsInt8()); +} + +TEST(TypeFactoryRaw, Int16) { + auto f = NTi::PoolFactory(false); + auto t = f->Int16Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int16); + ASSERT_TRUE(t->IsInt16()); +} + +TEST(TypeFactoryRaw, Int32) { + auto f = NTi::PoolFactory(false); + auto t = f->Int32Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int32); + ASSERT_TRUE(t->IsInt32()); +} + +TEST(TypeFactoryRaw, Int64) { + auto f = NTi::PoolFactory(false); + auto t = f->Int64Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Int64); + ASSERT_TRUE(t->IsInt64()); +} + +TEST(TypeFactoryRaw, Uint8) { + auto f = NTi::PoolFactory(false); + auto t = f->Uint8Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint8); + ASSERT_TRUE(t->IsUint8()); +} + +TEST(TypeFactoryRaw, Uint16) { + auto f = NTi::PoolFactory(false); + auto t = f->Uint16Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint16); + ASSERT_TRUE(t->IsUint16()); +} + +TEST(TypeFactoryRaw, Uint32) { + auto f = NTi::PoolFactory(false); + auto t = f->Uint32Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint32); + ASSERT_TRUE(t->IsUint32()); +} + +TEST(TypeFactoryRaw, Uint64) { + auto f = NTi::PoolFactory(false); + auto t = f->Uint64Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uint64); + ASSERT_TRUE(t->IsUint64()); +} + +TEST(TypeFactoryRaw, Float) { + auto f = NTi::PoolFactory(false); + auto t = f->FloatRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Float); + ASSERT_TRUE(t->IsFloat()); +} + +TEST(TypeFactoryRaw, Double) { + auto f = NTi::PoolFactory(false); + auto t = f->DoubleRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Double); + ASSERT_TRUE(t->IsDouble()); +} + +TEST(TypeFactoryRaw, String) { + auto f = NTi::PoolFactory(false); + auto t = f->StringRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::String); + ASSERT_TRUE(t->IsString()); +} + +TEST(TypeFactoryRaw, Utf8) { + auto f = NTi::PoolFactory(false); + auto t = f->Utf8Raw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Utf8); + ASSERT_TRUE(t->IsUtf8()); +} + +TEST(TypeFactoryRaw, Date) { + auto f = NTi::PoolFactory(false); + auto t = f->DateRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Date); + ASSERT_TRUE(t->IsDate()); +} + +TEST(TypeFactoryRaw, Datetime) { + auto f = NTi::PoolFactory(false); + auto t = f->DatetimeRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Datetime); + ASSERT_TRUE(t->IsDatetime()); +} + +TEST(TypeFactoryRaw, Timestamp) { + auto f = NTi::PoolFactory(false); + auto t = f->TimestampRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Timestamp); + ASSERT_TRUE(t->IsTimestamp()); +} + +TEST(TypeFactoryRaw, TzDate) { + auto f = NTi::PoolFactory(false); + auto t = f->TzDateRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::TzDate); + ASSERT_TRUE(t->IsTzDate()); +} + +TEST(TypeFactoryRaw, TzDatetime) { + auto f = NTi::PoolFactory(false); + auto t = f->TzDatetimeRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::TzDatetime); + ASSERT_TRUE(t->IsTzDatetime()); +} + +TEST(TypeFactoryRaw, TzTimestamp) { + auto f = NTi::PoolFactory(false); + auto t = f->TzTimestampRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::TzTimestamp); + ASSERT_TRUE(t->IsTzTimestamp()); +} + +TEST(TypeFactoryRaw, Interval) { + auto f = NTi::PoolFactory(false); + auto t = f->IntervalRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Interval); + ASSERT_TRUE(t->IsInterval()); +} + +TEST(TypeFactoryRaw, Decimal) { + auto f = NTi::PoolFactory(false); + auto t = f->DecimalRaw(20, 10); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Decimal); + ASSERT_TRUE(t->IsDecimal()); + ASSERT_EQ(t->GetPrecision(), 20); + ASSERT_EQ(t->GetScale(), 10); +} + +TEST(TypeFactoryRaw, Json) { + auto f = NTi::PoolFactory(false); + auto t = f->JsonRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Json); + ASSERT_TRUE(t->IsJson()); +} + +TEST(TypeFactoryRaw, Yson) { + auto f = NTi::PoolFactory(false); + auto t = f->YsonRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Yson); + ASSERT_TRUE(t->IsYson()); +} + +TEST(TypeFactoryRaw, Uuid) { + auto f = NTi::PoolFactory(false); + auto t = f->UuidRaw(); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Uuid); + ASSERT_TRUE(t->IsUuid()); +} + +TEST(TypeFactoryRaw, Optional) { + auto f = NTi::PoolFactory(false); + auto t = f->OptionalRaw(f->VoidRaw()); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Optional); + ASSERT_TRUE(t->IsOptional()); + ASSERT_TRUE(t->GetItemType()->IsVoid()); + ASSERT_EQ(t->GetItemType().Get(), t->GetItemTypeRaw()); +} + +TEST(TypeFactoryRaw, List) { + auto f = NTi::PoolFactory(false); + auto t = f->ListRaw(f->VoidRaw()); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::List); + ASSERT_TRUE(t->IsList()); + ASSERT_TRUE(t->GetItemType()->IsVoid()); + ASSERT_EQ(t->GetItemType().Get(), t->GetItemTypeRaw()); +} + +TEST(TypeFactoryRaw, Dict) { + auto f = NTi::PoolFactory(false); + auto t = f->DictRaw(f->VoidRaw(), f->StringRaw()); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Dict); + ASSERT_TRUE(t->IsDict()); + ASSERT_TRUE(t->GetKeyType()->IsVoid()); + ASSERT_EQ(t->GetKeyType().Get(), t->GetKeyTypeRaw()); + ASSERT_TRUE(t->GetValueType()->IsString()); + ASSERT_EQ(t->GetValueType().Get(), t->GetValueTypeRaw()); +} + +TEST(TypeFactoryRaw, EmptyStruct) { + auto f = NTi::PoolFactory(false); + auto t = f->StructRaw({}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetMembers().size(), 0); +} + +TEST(TypeFactoryRaw, NamedEmptyStruct) { + auto f = NTi::PoolFactory(false); + auto t = f->StructRaw("S", {}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "S"); + ASSERT_EQ(t->GetMembers().size(), 0); +} + +TEST(TypeFactoryRaw, Struct) { + auto f = NTi::PoolFactory(false); + auto t = f->StructRaw({{"a", f->Int64Raw()}, {"b", f->Int8Raw()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetMembers().size(), 2); + ASSERT_EQ(t->GetMembers()[0].GetName(), "a"); + ASSERT_TRUE(t->GetMembers()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetMembers()[0].GetType().Get(), t->GetMembers()[0].GetTypeRaw()); + ASSERT_EQ(t->GetMembers()[1].GetName(), "b"); + ASSERT_TRUE(t->GetMembers()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetMembers()[1].GetType().Get(), t->GetMembers()[1].GetTypeRaw()); +} + +TEST(TypeFactoryRaw, NamedStruct) { + auto f = NTi::PoolFactory(false); + auto t = f->StructRaw("S", {{"a", f->Int64Raw()}, {"b", f->Int8Raw()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Struct); + ASSERT_TRUE(t->IsStruct()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "S"); + ASSERT_EQ(t->GetMembers().size(), 2); + ASSERT_EQ(t->GetMembers()[0].GetName(), "a"); + ASSERT_TRUE(t->GetMembers()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetMembers()[0].GetType().Get(), t->GetMembers()[0].GetTypeRaw()); + ASSERT_EQ(t->GetMembers()[1].GetName(), "b"); + ASSERT_TRUE(t->GetMembers()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetMembers()[1].GetType().Get(), t->GetMembers()[1].GetTypeRaw()); +} + +TEST(TypeFactoryRaw, EmptyTuple) { + auto f = NTi::PoolFactory(false); + auto t = f->TupleRaw({}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetElements().size(), 0); +} + +TEST(TypeFactoryRaw, NamedEmptyTuple) { + auto f = NTi::PoolFactory(false); + auto t = f->TupleRaw("T", {}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "T"); + ASSERT_EQ(t->GetElements().size(), 0); +} + +TEST(TypeFactoryRaw, Tuple) { + auto f = NTi::PoolFactory(false); + auto t = f->TupleRaw({{f->Int64Raw()}, {f->Int8Raw()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_EQ(t->GetElements().size(), 2); + ASSERT_TRUE(t->GetElements()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetElements()[0].GetType().Get(), t->GetElements()[0].GetTypeRaw()); + ASSERT_TRUE(t->GetElements()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetElements()[1].GetType().Get(), t->GetElements()[1].GetTypeRaw()); +} + +TEST(TypeFactoryRaw, NamedTuple) { + auto f = NTi::PoolFactory(false); + auto t = f->TupleRaw("T", {{f->Int64Raw()}, {f->Int8Raw()}}); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tuple); + ASSERT_TRUE(t->IsTuple()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "T"); + ASSERT_EQ(t->GetElements().size(), 2); + ASSERT_TRUE(t->GetElements()[0].GetTypeRaw()->IsInt64()); + ASSERT_EQ(t->GetElements()[0].GetType().Get(), t->GetElements()[0].GetTypeRaw()); + ASSERT_TRUE(t->GetElements()[1].GetTypeRaw()->IsInt8()); + ASSERT_EQ(t->GetElements()[1].GetType().Get(), t->GetElements()[1].GetTypeRaw()); +} + +TEST(TypeFactoryRaw, VariantOverStruct) { + auto f = NTi::PoolFactory(false); + auto t = f->VariantRaw(f->StructRaw("Inner", {{"x", f->VoidRaw()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_TRUE(t->IsVariantOverStruct()); + ASSERT_FALSE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsStruct()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST(TypeFactoryRaw, NamedVariantOverStruct) { + auto f = NTi::PoolFactory(false); + auto t = f->VariantRaw("V", f->StructRaw("Inner", {{"x", f->VoidRaw()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "V"); + ASSERT_TRUE(t->IsVariantOverStruct()); + ASSERT_FALSE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsStruct()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST(TypeFactoryRaw, VariantOverTuple) { + auto f = NTi::PoolFactory(false); + auto t = f->VariantRaw(f->TupleRaw("Inner", {{f->VoidRaw()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_FALSE(t->GetName().Defined()); + ASSERT_FALSE(t->IsVariantOverStruct()); + ASSERT_TRUE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsTuple()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST(TypeFactoryRaw, NamedVariantOverTuple) { + auto f = NTi::PoolFactory(false); + auto t = f->VariantRaw("V", f->TupleRaw("Inner", {{f->VoidRaw()}})); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Variant); + ASSERT_TRUE(t->IsVariant()); + ASSERT_TRUE(t->GetName().Defined()); + ASSERT_EQ(t->GetName().GetRef(), "V"); + ASSERT_FALSE(t->IsVariantOverStruct()); + ASSERT_TRUE(t->IsVariantOverTuple()); + ASSERT_EQ(t->GetUnderlyingType()->AsTuple()->GetName().GetRef(), "Inner"); + ASSERT_EQ(t->GetUnderlyingType().Get(), t->GetUnderlyingTypeRaw()); +} + +TEST(TypeFactoryRaw, Tagged) { + auto f = NTi::PoolFactory(false); + auto t = f->TaggedRaw(f->VoidRaw(), "T"); + ASSERT_EQ(t->GetTypeName(), NTi::ETypeName::Tagged); + ASSERT_TRUE(t->IsTagged()); + ASSERT_EQ(t->GetTag(), "T"); + ASSERT_TRUE(t->GetItemType()->IsVoid()); + ASSERT_EQ(t->GetItemType().Get(), t->GetItemTypeRaw()); +} diff --git a/library/cpp/type_info/ut/type_io.cpp b/library/cpp/type_info/ut/type_io.cpp new file mode 100644 index 0000000000..4feb9b7d83 --- /dev/null +++ b/library/cpp/type_info/ut/type_io.cpp @@ -0,0 +1,535 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +TEST_TF(TypeIO, AsYqlType) { + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Void().Get()), + "[VoidType]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Null().Get()), + "[NullType]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Bool().Get()), + "[DataType; Bool]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Int8().Get()), + "[DataType; Int8]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Int16().Get()), + "[DataType; Int16]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Int32().Get()), + "[DataType; Int32]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Int64().Get()), + "[DataType; Int64]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Uint8().Get()), + "[DataType; Uint8]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Uint16().Get()), + "[DataType; Uint16]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Uint32().Get()), + "[DataType; Uint32]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Uint64().Get()), + "[DataType; Uint64]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Float().Get()), + "[DataType; Float]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Double().Get()), + "[DataType; Double]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.String().Get()), + "[DataType; String]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Utf8().Get()), + "[DataType; Utf8]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Date().Get()), + "[DataType; Date]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Datetime().Get()), + "[DataType; Datetime]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Timestamp().Get()), + "[DataType; Timestamp]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.TzDate().Get()), + "[DataType; TzDate]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.TzDatetime().Get()), + "[DataType; TzDatetime]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.TzTimestamp().Get()), + "[DataType; TzTimestamp]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Interval().Get()), + "[DataType; Interval]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Json().Get()), + "[DataType; Json]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Yson().Get()), + "[DataType; Yson]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Uuid().Get()), + "[DataType; Uuid]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Decimal(20, 10).Get()), + "[DataType; Decimal; \"20\"; \"10\"]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Decimal(35, 35).Get()), + "[DataType; Decimal; \"35\"; \"35\"]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(Optional(f.Bool()).Get()), + "[OptionalType; [DataType; Bool]]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.List(f.Bool()).Get()), + "[ListType; [DataType; Bool]]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Dict(f.Bool(), f.Int32()).Get()), + "[DictType; [DataType; Bool]; [DataType; Int32]]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Struct({}).Get()), + "[StructType; []]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Struct({{"a", f.Bool()}}).Get()), + "[StructType; [[a; [DataType; Bool]]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Struct({{"a", f.Yson()}, {"b", f.Bool()}}).Get()), + "[StructType; [[a; [DataType; Yson]]; [b; [DataType; Bool]]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Struct({{"a", f.Int32()}, {"b", f.Int32()}, {"c", f.Int64()}}).Get()), + "[StructType; [[a; [DataType; Int32]]; [b; [DataType; Int32]]; [c; [DataType; Int64]]]]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Tuple({}).Get()), + "[TupleType; []]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Tuple({{f.Bool()}}).Get()), + "[TupleType; [[DataType; Bool]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Tuple({{f.Yson()}, {f.Bool()}}).Get()), + "[TupleType; [[DataType; Yson]; [DataType; Bool]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Tuple({{f.Int32()}, {f.Int32()}, {f.Int64()}}).Get()), + "[TupleType; [[DataType; Int32]; [DataType; Int32]; [DataType; Int64]]]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Variant(f.Struct({{"a", f.Bool()}})).Get()), + "[VariantType; [StructType; [[a; [DataType; Bool]]]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Variant(f.Struct({{"a", f.Yson()}, {"b", f.Bool()}})).Get()), + "[VariantType; [StructType; [[a; [DataType; Yson]]; [b; [DataType; Bool]]]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Variant(f.Struct({{"a", f.Int32()}, {"b", f.Int32()}, {"c", f.Int64()}})).Get()), + "[VariantType; [StructType; [[a; [DataType; Int32]]; [b; [DataType; Int32]]; [c; [DataType; Int64]]]]]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Variant(f.Tuple({{f.Bool()}})).Get()), + "[VariantType; [TupleType; [[DataType; Bool]]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Variant(f.Tuple({{f.Yson()}, {f.Bool()}})).Get()), + "[VariantType; [TupleType; [[DataType; Yson]; [DataType; Bool]]]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Variant(f.Tuple({{f.Int32()}, {f.Int32()}, {f.Int32()}})).Get()), + "[VariantType; [TupleType; [[DataType; Int32]; [DataType; Int32]; [DataType; Int32]]]]"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Tagged(f.String(), "Url").Get()), + "[TaggedType; Url; [DataType; String]]"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlType(f.Tagged(f.String(), "Url").Get(), /* includeTags = */ false), + "[DataType; String]"); +} + +TEST_TF(TypeIO, AsYqlRowSpec) { + { + auto type = f.Struct({}); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlRowSpec(type.Get()), + "{StrictSchema=%true; Type=[StructType; []]}"); + } + { + auto type = f.Tagged(f.Struct({}), "Event"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlRowSpec(type.Get()), + "{StrictSchema=%true; Type=[StructType; []]}"); + } + { + auto type = f.Struct({{"x", f.Tagged(f.String(), "Url")}}); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlRowSpec(type.Get()), + "{StrictSchema=%true; Type=[StructType; [[x; [TaggedType; Url; [DataType; String]]]]]}"); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlRowSpec(type.Get(), /* includeTags = */ false), + "{StrictSchema=%true; Type=[StructType; [[x; [DataType; String]]]]}"); + } + { + auto type = f.Struct({{"a", f.Bool()}}); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlRowSpec(type.Get()), + "{StrictSchema=%true; Type=[StructType; [[a; [DataType; Bool]]]]}"); + } + { + auto type = f.Struct({{"a", f.Yson()}, {"b", f.Bool()}}); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlRowSpec(type.Get()), + "{StrictSchema=%true; Type=[StructType; [[a; [DataType; Yson]]; [b; [DataType; Bool]]]]}"); + } + { + auto type = f.Struct({{"a", f.Int32()}, {"b", f.Int32()}, {"c", f.Int32()}}); + ASSERT_YSON_EQ( + NTi::NIo::AsYqlRowSpec(type.Get()), + "{StrictSchema=%true; Type=[StructType; [[a; [DataType; Int32]]; [b; [DataType; Int32]]; [c; [DataType; Int32]]]]}"); + } + + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + NTi::NIo::AsYqlRowSpec(f.Void().Get()); + }(), + NTi::TApiException, "expected a struct type"); + + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + NTi::NIo::AsYqlRowSpec(Optional(f.Struct({})).Get()); + }(), + NTi::TApiException, "expected a struct type"); +} + +TEST_TF(TypeIO, AsYtSchema) { + { + auto type = f.Struct({ + {"void", f.Void()}, + {"null", f.Null()}, + {"bool", f.Bool()}, + {"int8", f.Int8()}, + {"int16", f.Int16()}, + {"int32", f.Int32()}, + {"int64", f.Int64()}, + {"uint8", f.Uint8()}, + {"uint16", f.Uint16()}, + {"uint32", f.Uint32()}, + {"uint64", f.Uint64()}, + {"float", f.Float()}, + {"double", f.Double()}, + {"string", f.String()}, + {"utf8", f.Utf8()}, + {"date", f.Date()}, + {"datetime", f.Datetime()}, + {"timestamp", f.Timestamp()}, + {"tzdate", f.TzDate()}, + {"tzdatetime", f.TzDatetime()}, + {"tztimestamp", f.TzTimestamp()}, + {"interval", f.Interval()}, + {"decimal", f.Decimal(20, 10)}, + {"json", f.Json()}, + {"yson", f.Yson()}, + {"uuid", f.Uuid()}, + {"list", f.List(f.Bool())}, + {"dict", f.Dict(f.Bool(), f.Bool())}, + {"struct", f.Struct({})}, + {"tuple", f.Tuple({})}, + {"variant", f.Variant(f.Struct({{"x", f.Bool()}}))}, + }); + + ASSERT_YSON_EQ( + NTi::NIo::AsYtSchema(type.Get()), + R"( + <strict=%true; unique_keys=%false> + [ + {name=void; required=%false; type=any }; + {name=null; required=%false; type=any }; + {name=bool; required=%true; type=boolean }; + {name=int8; required=%true; type=int8 }; + {name=int16; required=%true; type=int16 }; + {name=int32; required=%true; type=int32 }; + {name=int64; required=%true; type=int64 }; + {name=uint8; required=%true; type=uint8 }; + {name=uint16; required=%true; type=uint16 }; + {name=uint32; required=%true; type=uint32 }; + {name=uint64; required=%true; type=uint64 }; + {name=float; required=%true; type=double }; + {name=double; required=%true; type=double }; + {name=string; required=%true; type=string }; + {name=utf8; required=%true; type=utf8 }; + {name=date; required=%true; type=uint16 }; + {name=datetime; required=%true; type=uint32 }; + {name=timestamp; required=%true; type=uint64 }; + {name=tzdate; required=%true; type=string }; + {name=tzdatetime; required=%true; type=string }; + {name=tztimestamp; required=%true; type=string }; + {name=interval; required=%true; type=int64 }; + {name=decimal; required=%true; type=string }; + {name=json; required=%true; type=string }; + {name=yson; required=%false; type=any }; + {name=uuid; required=%true; type=string }; + {name=list; required=%false; type=any }; + {name=dict; required=%false; type=any }; + {name=struct; required=%false; type=any }; + {name=tuple; required=%false; type=any }; + {name=variant; required=%false; type=any }; + ] + )"); + } + + { + auto type = f.Struct({ + {"void", f.Tagged(f.Void(), "Tag")}, + {"null", f.Tagged(f.Null(), "Tag")}, + {"bool", f.Tagged(f.Bool(), "Tag")}, + {"int8", f.Tagged(f.Int8(), "Tag")}, + {"int16", f.Tagged(f.Int16(), "Tag")}, + {"int32", f.Tagged(f.Int32(), "Tag")}, + {"int64", f.Tagged(f.Int64(), "Tag")}, + {"uint8", f.Tagged(f.Uint8(), "Tag")}, + {"uint16", f.Tagged(f.Uint16(), "Tag")}, + {"uint32", f.Tagged(f.Uint32(), "Tag")}, + {"uint64", f.Tagged(f.Uint64(), "Tag")}, + {"float", f.Tagged(f.Float(), "Tag")}, + {"double", f.Tagged(f.Double(), "Tag")}, + {"string", f.Tagged(f.String(), "Tag")}, + {"utf8", f.Tagged(f.Utf8(), "Tag")}, + {"date", f.Tagged(f.Date(), "Tag")}, + {"datetime", f.Tagged(f.Datetime(), "Tag")}, + {"timestamp", f.Tagged(f.Timestamp(), "Tag")}, + {"tzdate", f.Tagged(f.TzDate(), "Tag")}, + {"tzdatetime", f.Tagged(f.TzDatetime(), "Tag")}, + {"tztimestamp", f.Tagged(f.TzTimestamp(), "Tag")}, + {"interval", f.Tagged(f.Interval(), "Tag")}, + {"decimal", f.Tagged(f.Decimal(20, 10), "Tag")}, + {"json", f.Tagged(f.Json(), "Tag")}, + {"yson", f.Tagged(f.Yson(), "Tag")}, + {"uuid", f.Tagged(f.Uuid(), "Tag")}, + {"list", f.Tagged(f.List(f.Bool()), "Tag")}, + {"dict", f.Tagged(f.Dict(f.Bool(), f.Bool()), "Tag")}, + {"struct", f.Tagged(f.Struct({}), "Tag")}, + {"tuple", f.Tagged(f.Tuple({}), "Tag")}, + {"variant", f.Tagged(f.Variant(f.Struct({{"x", f.Bool()}})), "Tag")}, + }); + + ASSERT_YSON_EQ( + NTi::NIo::AsYtSchema(type.Get()), + R"( + <strict=%true; unique_keys=%false> + [ + {name=void; required=%false; type=any }; + {name=null; required=%false; type=any }; + {name=bool; required=%true; type=boolean }; + {name=int8; required=%true; type=int8 }; + {name=int16; required=%true; type=int16 }; + {name=int32; required=%true; type=int32 }; + {name=int64; required=%true; type=int64 }; + {name=uint8; required=%true; type=uint8 }; + {name=uint16; required=%true; type=uint16 }; + {name=uint32; required=%true; type=uint32 }; + {name=uint64; required=%true; type=uint64 }; + {name=float; required=%true; type=double }; + {name=double; required=%true; type=double }; + {name=string; required=%true; type=string }; + {name=utf8; required=%true; type=utf8 }; + {name=date; required=%true; type=uint16 }; + {name=datetime; required=%true; type=uint32 }; + {name=timestamp; required=%true; type=uint64 }; + {name=tzdate; required=%true; type=string }; + {name=tzdatetime; required=%true; type=string }; + {name=tztimestamp; required=%true; type=string }; + {name=interval; required=%true; type=int64 }; + {name=decimal; required=%true; type=string }; + {name=json; required=%true; type=string }; + {name=yson; required=%false; type=any }; + {name=uuid; required=%true; type=string }; + {name=list; required=%false; type=any }; + {name=dict; required=%false; type=any }; + {name=struct; required=%false; type=any }; + {name=tuple; required=%false; type=any }; + {name=variant; required=%false; type=any }; + ] + )"); + } + + { + auto type = f.Struct({ + {"void", Optional(f.Void())}, + {"null", Optional(f.Null())}, + {"bool", Optional(f.Bool())}, + {"int8", Optional(f.Int8())}, + {"int16", Optional(f.Int16())}, + {"int32", Optional(f.Int32())}, + {"int64", Optional(f.Int64())}, + {"uint8", Optional(f.Uint8())}, + {"uint16", Optional(f.Uint16())}, + {"uint32", Optional(f.Uint32())}, + {"uint64", Optional(f.Uint64())}, + {"float", Optional(f.Float())}, + {"double", Optional(f.Double())}, + {"string", Optional(f.String())}, + {"utf8", Optional(f.Utf8())}, + {"date", Optional(f.Date())}, + {"datetime", Optional(f.Datetime())}, + {"timestamp", Optional(f.Timestamp())}, + {"tzdate", Optional(f.TzDate())}, + {"tzdatetime", Optional(f.TzDatetime())}, + {"tztimestamp", Optional(f.TzTimestamp())}, + {"interval", Optional(f.Interval())}, + {"decimal", Optional(f.Decimal(20, 10))}, + {"json", Optional(f.Json())}, + {"yson", Optional(f.Yson())}, + {"uuid", Optional(f.Uuid())}, + {"list", Optional(f.List(f.Bool()))}, + {"dict", Optional(f.Dict(f.Bool(), f.Bool()))}, + {"struct", Optional(f.Struct({}))}, + {"tuple", Optional(f.Tuple({}))}, + {"variant", Optional(f.Variant(f.Struct({{"x", f.Bool()}})))}, + }); + + ASSERT_YSON_EQ( + NTi::NIo::AsYtSchema(type.Get()), + R"( + <strict=%true; unique_keys=%false> + [ + {name=void; required=%false; type=any }; + {name=null; required=%false; type=any }; + {name=bool; required=%false; type=boolean }; + {name=int8; required=%false; type=int8 }; + {name=int16; required=%false; type=int16 }; + {name=int32; required=%false; type=int32 }; + {name=int64; required=%false; type=int64 }; + {name=uint8; required=%false; type=uint8 }; + {name=uint16; required=%false; type=uint16 }; + {name=uint32; required=%false; type=uint32 }; + {name=uint64; required=%false; type=uint64 }; + {name=float; required=%false; type=double }; + {name=double; required=%false; type=double }; + {name=string; required=%false; type=string }; + {name=utf8; required=%false; type=utf8 }; + {name=date; required=%false; type=uint16 }; + {name=datetime; required=%false; type=uint32 }; + {name=timestamp; required=%false; type=uint64 }; + {name=tzdate; required=%false; type=string }; + {name=tzdatetime; required=%false; type=string }; + {name=tztimestamp; required=%false; type=string }; + {name=interval; required=%false; type=int64 }; + {name=decimal; required=%false; type=string }; + {name=json; required=%false; type=string }; + {name=yson; required=%false; type=any }; + {name=uuid; required=%false; type=string }; + {name=list; required=%false; type=any }; + {name=dict; required=%false; type=any }; + {name=struct; required=%false; type=any }; + {name=tuple; required=%false; type=any }; + {name=variant; required=%false; type=any }; + ] + )"); + } + + { + auto type = f.Struct({ + {"void", Optional(Optional(f.Void()))}, + {"null", Optional(Optional(f.Null()))}, + {"bool", Optional(Optional(f.Bool()))}, + {"int8", Optional(Optional(f.Int8()))}, + {"int16", Optional(Optional(f.Int16()))}, + {"int32", Optional(Optional(f.Int32()))}, + {"int64", Optional(Optional(f.Int64()))}, + {"uint8", Optional(Optional(f.Uint8()))}, + {"uint16", Optional(Optional(f.Uint16()))}, + {"uint32", Optional(Optional(f.Uint32()))}, + {"uint64", Optional(Optional(f.Uint64()))}, + {"float", Optional(Optional(f.Float()))}, + {"double", Optional(Optional(f.Double()))}, + {"string", Optional(Optional(f.String()))}, + {"utf8", Optional(Optional(f.Utf8()))}, + {"date", Optional(Optional(f.Date()))}, + {"datetime", Optional(Optional(f.Datetime()))}, + {"timestamp", Optional(Optional(f.Timestamp()))}, + {"tzdate", Optional(Optional(f.TzDate()))}, + {"tzdatetime", Optional(Optional(f.TzDatetime()))}, + {"tztimestamp", Optional(Optional(f.TzTimestamp()))}, + {"interval", Optional(Optional(f.Interval()))}, + {"decimal", Optional(Optional(f.Decimal(20, 10)))}, + {"json", Optional(Optional(f.Json()))}, + {"yson", Optional(Optional(f.Yson()))}, + {"uuid", Optional(Optional(f.Uuid()))}, + {"list", Optional(Optional(f.List(f.Bool())))}, + {"dict", Optional(Optional(f.Dict(f.Bool(), f.Bool())))}, + {"struct", Optional(Optional(f.Struct({})))}, + {"tuple", Optional(Optional(f.Tuple({})))}, + {"variant", Optional(Optional(f.Variant(f.Struct({{"x", f.Bool()}}))))}, + }); + + ASSERT_YSON_EQ( + NTi::NIo::AsYtSchema(type.Get()), + R"( + <strict=%true; unique_keys=%false> + [ + {name=void; required=%false; type=any }; + {name=null; required=%false; type=any }; + {name=bool; required=%false; type=any }; + {name=int8; required=%false; type=any }; + {name=int16; required=%false; type=any }; + {name=int32; required=%false; type=any }; + {name=int64; required=%false; type=any }; + {name=uint8; required=%false; type=any }; + {name=uint16; required=%false; type=any }; + {name=uint32; required=%false; type=any }; + {name=uint64; required=%false; type=any }; + {name=float; required=%false; type=any }; + {name=double; required=%false; type=any }; + {name=string; required=%false; type=any }; + {name=utf8; required=%false; type=any }; + {name=date; required=%false; type=any }; + {name=datetime; required=%false; type=any }; + {name=timestamp; required=%false; type=any }; + {name=tzdate; required=%false; type=any }; + {name=tzdatetime; required=%false; type=any }; + {name=tztimestamp; required=%false; type=any }; + {name=interval; required=%false; type=any }; + {name=decimal; required=%false; type=any }; + {name=json; required=%false; type=any }; + {name=yson; required=%false; type=any }; + {name=uuid; required=%false; type=any }; + {name=list; required=%false; type=any }; + {name=dict; required=%false; type=any }; + {name=struct; required=%false; type=any }; + {name=tuple; required=%false; type=any }; + {name=variant; required=%false; type=any }; + ] + )"); + } + + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + NTi::NIo::AsYtSchema(f.Void().Get()); + }(), + NTi::TApiException, "expected a struct type"); + + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + NTi::NIo::AsYtSchema(Optional(f.Struct({})).Get()); + }(), + NTi::TApiException, "expected a struct type"); + + UNIT_ASSERT_EXCEPTION_CONTAINS([&f]() { + NTi::NIo::AsYtSchema(f.Struct({}).Get()); + }(), + NTi::TApiException, "expected a non-empty struct"); + + ASSERT_YSON_EQ( + NTi::NIo::AsYtSchema(f.Struct({}).Get(), /* failOnEmptyf.Struct = */ false), + "<strict=%true; unique_keys=%false>[{name=_yql_fake_column; required=%false; type=boolean}]"); +} diff --git a/library/cpp/type_info/ut/type_list.cpp b/library/cpp/type_info/ut/type_list.cpp new file mode 100644 index 0000000000..56d9e16061 --- /dev/null +++ b/library/cpp/type_info/ut/type_list.cpp @@ -0,0 +1,64 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include <util/generic/serialized_enum.h> + +TEST(TypeList, PrimitiveTypeNameSequence) { + auto primitiveNames = GetEnumAllValues<NTi::EPrimitiveTypeName>(); + + ASSERT_GT(primitiveNames.size(), 0); + + ASSERT_EQ(static_cast<i32>(primitiveNames[0]), 0); + + for (size_t i = 0; i < primitiveNames.size() - 1; ++i) { + ASSERT_EQ(static_cast<i32>(primitiveNames[i]) + 1, static_cast<i32>(primitiveNames[i + 1])); + } +} + +TEST(TypeList, PrimitiveTypeGroup) { + auto primitiveNames = GetEnumAllValues<NTi::EPrimitiveTypeName>(); + + for (auto typeName : primitiveNames) { + ASSERT_TRUE(NTi::IsPrimitive(static_cast<NTi::ETypeName>(typeName))); + } +} + +TEST(TypeList, TypeNameInExactlyOneGroup) { + auto allNames = GetEnumAllValues<NTi::ETypeName>(); + + for (auto typeName : allNames) { + int groups = 0; + groups += NTi::IsPrimitive(typeName); + groups += NTi::IsSingular(typeName); + groups += NTi::IsContainer(typeName); + ASSERT_EQ(groups, 1); + } +} + +TEST(TypeList, EnumCoherence) { + auto primitiveNames = GetEnumAllValues<NTi::EPrimitiveTypeName>(); + auto allNames = GetEnumAllValues<NTi::ETypeName>(); + + ASSERT_GT(allNames.size(), primitiveNames.size()); + + size_t i = 0; + + for (; i < primitiveNames.size(); ++i) { + ASSERT_EQ(static_cast<i32>(primitiveNames[i]), static_cast<i32>(allNames[i])); + ASSERT_EQ(ToString(primitiveNames[i]), ToString(allNames[i])); + ASSERT_TRUE(NTi::IsPrimitive(allNames[i])); + } + + for (; i < allNames.size(); ++i) { + ASSERT_FALSE(NTi::IsPrimitive(allNames[i])); + } +} + +TEST(TypeList, Cast) { + auto primitiveNames = GetEnumAllValues<NTi::EPrimitiveTypeName>(); + + for (auto typeName : primitiveNames) { + ASSERT_EQ(typeName, NTi::ToPrimitiveTypeName(NTi::ToTypeName(typeName))); + } +} diff --git a/library/cpp/type_info/ut/type_serialize.cpp b/library/cpp/type_info/ut/type_serialize.cpp new file mode 100644 index 0000000000..0d7e184eca --- /dev/null +++ b/library/cpp/type_info/ut/type_serialize.cpp @@ -0,0 +1,251 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +TEST(TypeSerialize, Void) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Void().Get()), R"(void)"); +} + +TEST(TypeSerialize, Null) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Null().Get()), R"(null)"); +} + +TEST(TypeSerialize, Bool) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Bool().Get()), R"(bool)"); +} + +TEST(TypeSerialize, Int8) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Int8().Get()), R"(int8)"); +} + +TEST(TypeSerialize, Int16) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Int16().Get()), R"(int16)"); +} + +TEST(TypeSerialize, Int32) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Int32().Get()), R"(int32)"); +} + +TEST(TypeSerialize, Int64) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Int64().Get()), R"(int64)"); +} + +TEST(TypeSerialize, Uint8) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Uint8().Get()), R"(uint8)"); +} + +TEST(TypeSerialize, Uint16) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Uint16().Get()), R"(uint16)"); +} + +TEST(TypeSerialize, Uint32) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Uint32().Get()), R"(uint32)"); +} + +TEST(TypeSerialize, Uint64) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Uint64().Get()), R"(uint64)"); +} + +TEST(TypeSerialize, Float) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Float().Get()), R"(float)"); +} + +TEST(TypeSerialize, Double) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Double().Get()), R"(double)"); +} + +TEST(TypeSerialize, String) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::String().Get()), R"(string)"); +} + +TEST(TypeSerialize, Utf8) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Utf8().Get()), R"(utf8)"); +} + +TEST(TypeSerialize, Date) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Date().Get()), R"(date)"); +} + +TEST(TypeSerialize, Datetime) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Datetime().Get()), R"(datetime)"); +} + +TEST(TypeSerialize, Timestamp) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Timestamp().Get()), R"(timestamp)"); +} + +TEST(TypeSerialize, TzDate) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::TzDate().Get()), R"(tz_date)"); +} + +TEST(TypeSerialize, TzDatetime) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::TzDatetime().Get()), R"(tz_datetime)"); +} + +TEST(TypeSerialize, TzTimestamp) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::TzTimestamp().Get()), R"(tz_timestamp)"); +} + +TEST(TypeSerialize, Interval) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Interval().Get()), R"(interval)"); +} + +TEST(TypeSerialize, Decimal) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Decimal(20, 10).Get()), R"({type_name=decimal; precision=20; scale=10})"); + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Decimal(10, 10).Get()), R"({type_name=decimal; precision=10; scale=10})"); +} + +TEST(TypeSerialize, Json) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Json().Get()), R"(json)"); +} + +TEST(TypeSerialize, Yson) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Yson().Get()), R"(yson)"); +} + +TEST(TypeSerialize, Uuid) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Uuid().Get()), R"(uuid)"); +} + +TEST(TypeSerialize, Optional) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Optional(NTi::Void()).Get()), R"({type_name=optional; item=void})"); + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Optional(NTi::String()).Get()), R"({type_name=optional; item=string})"); +} + +TEST(TypeSerialize, List) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::List(NTi::Void()).Get()), R"({type_name=list; item=void})"); + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::List(NTi::String()).Get()), R"({type_name=list; item=string})"); +} + +TEST(TypeSerialize, Dict) { + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Dict(NTi::Void(), NTi::Void()).Get()), R"({type_name=dict; key=void; value=void})"); + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(NTi::Dict(NTi::Int32(), NTi::String()).Get()), R"({type_name=dict; key=int32; value=string})"); +} + +TEST(TypeSerialize, StructEmpty) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Struct({}).Get()), + R"({type_name=struct; members=[]})"); +} + +TEST(TypeSerialize, Struct) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Struct({{"ItemB", NTi::String()}, {"ItemA", NTi::List(NTi::Int64())}}).Get()), + R"({type_name=struct; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); +} + +TEST(TypeSerialize, StructNamedEmpty) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Struct("S", {}).Get()), + R"({type_name=struct; members=[]})"); +} + +TEST(TypeSerialize, StructNamedEmptyNoNames) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Struct("S", {}).Get(), + /* binary = */ false, + /* includeTags = */ true), + R"({type_name=struct; members=[]})"); +} + +TEST(TypeSerialize, StructNamed) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Struct("S", {{"ItemB", NTi::String()}, {"ItemA", NTi::List(NTi::Int64())}}).Get()), + R"({type_name=struct; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); +} + +TEST(TypeSerialize, TupleEmpty) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Tuple({}).Get()), + R"({type_name=tuple; elements=[]})"); +} + +TEST(TypeSerialize, Tuple) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Tuple({{NTi::String()}, {NTi::List(NTi::Int64())}}).Get()), + R"({type_name=tuple; elements=[{type=string}; {type={type_name=list; item=int64}}]})"); +} + +TEST(TypeSerialize, TupleNamedEmpty) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Tuple("S", {}).Get()), + R"({type_name=tuple; elements=[]})"); +} + +TEST(TypeSerialize, TupleNamedEmptyNoNames) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Tuple("S", {}).Get(), + /* binary = */ false), + R"({type_name=tuple; elements=[]})"); +} + +TEST(TypeSerialize, VariantStruct) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Variant( + NTi::Struct({{"ItemB", NTi::String()}, {"ItemA", NTi::List(NTi::Int64())}})) + .Get()), + R"({type_name=variant; members=[{name=ItemB; type=string}; {name=ItemA; type={type_name=list; item=int64}}]})"); +} + +TEST(TypeSerialize, VariantTuple) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Variant( + NTi::Tuple({ + {NTi::String()}, + {NTi::List(NTi::Int64())}, + })) + .Get()), + R"({type_name=variant; elements=[{type=string}; {type={type_name=list; item=int64}}]})"); +} + +TEST(TypeSerialize, Tagged) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Tagged(NTi::String(), "Url").Get()), + R"({type_name=tagged; tag=Url; item=string})"); +} + +TEST(TypeSerialize, TaggedNoTags) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Tagged(NTi::String(), "Url").Get(), + /* binary = */ false, + /* includeTags = */ false), + R"(string)"); +} + +TEST(TypeSerialize, MultipleType) { + auto result = TString(); + + { + auto writer = NYsonPull::MakeTextWriter(NYsonPull::NOutput::FromString(&result), NYsonPull::EStreamType::ListFragment); + + writer.BeginStream(); + NTi::NIo::SerializeYsonMultiple(NTi::Optional(NTi::String()).Get(), writer.GetConsumer()); + NTi::NIo::SerializeYsonMultiple(NTi::List(NTi::Utf8()).Get(), writer.GetConsumer()); + writer.EndStream(); + } + + ASSERT_YSON_EQ("[" + result + "]", R"([{type_name=optional; item=string}; {type_name=list; item=utf8}])"); +} + +TEST(TypeSerialize, Binary) { + ASSERT_YSON_EQ( + NTi::NIo::SerializeYson( + NTi::Tagged(NTi::String(), "Url").Get(), + /* binary = */ true), + R"({type_name=tagged; tag=Url; item=string})"); +} diff --git a/library/cpp/type_info/ut/type_show.cpp b/library/cpp/type_info/ut/type_show.cpp new file mode 100644 index 0000000000..cde0d9371a --- /dev/null +++ b/library/cpp/type_info/ut/type_show.cpp @@ -0,0 +1,199 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +TEST_TF(TypeShow, Void) { + ASSERT_EQ(ToString(*f.Void()), "Void"); +} + +TEST_TF(TypeShow, Null) { + ASSERT_EQ(ToString(*f.Null()), "Null"); +} + +TEST_TF(TypeShow, Bool) { + ASSERT_EQ(ToString(*f.Bool()), "Bool"); +} + +TEST_TF(TypeShow, Int8) { + ASSERT_EQ(ToString(*f.Int8()), "Int8"); +} + +TEST_TF(TypeShow, Int16) { + ASSERT_EQ(ToString(*f.Int16()), "Int16"); +} + +TEST_TF(TypeShow, Int32) { + ASSERT_EQ(ToString(*f.Int32()), "Int32"); +} + +TEST_TF(TypeShow, Int64) { + ASSERT_EQ(ToString(*f.Int64()), "Int64"); +} + +TEST_TF(TypeShow, Uint8) { + ASSERT_EQ(ToString(*f.Uint8()), "Uint8"); +} + +TEST_TF(TypeShow, Uint16) { + ASSERT_EQ(ToString(*f.Uint16()), "Uint16"); +} + +TEST_TF(TypeShow, Uint32) { + ASSERT_EQ(ToString(*f.Uint32()), "Uint32"); +} + +TEST_TF(TypeShow, Uint64) { + ASSERT_EQ(ToString(*f.Uint64()), "Uint64"); +} + +TEST_TF(TypeShow, Float) { + ASSERT_EQ(ToString(*f.Float()), "Float"); +} + +TEST_TF(TypeShow, Double) { + ASSERT_EQ(ToString(*f.Double()), "Double"); +} + +TEST_TF(TypeShow, String) { + ASSERT_EQ(ToString(*f.String()), "String"); +} + +TEST_TF(TypeShow, Utf8) { + ASSERT_EQ(ToString(*f.Utf8()), "Utf8"); +} + +TEST_TF(TypeShow, Date) { + ASSERT_EQ(ToString(*f.Date()), "Date"); +} + +TEST_TF(TypeShow, Datetime) { + ASSERT_EQ(ToString(*f.Datetime()), "Datetime"); +} + +TEST_TF(TypeShow, Timestamp) { + ASSERT_EQ(ToString(*f.Timestamp()), "Timestamp"); +} + +TEST_TF(TypeShow, TzDate) { + ASSERT_EQ(ToString(*f.TzDate()), "TzDate"); +} + +TEST_TF(TypeShow, TzDatetime) { + ASSERT_EQ(ToString(*f.TzDatetime()), "TzDatetime"); +} + +TEST_TF(TypeShow, TzTimestamp) { + ASSERT_EQ(ToString(*f.TzTimestamp()), "TzTimestamp"); +} + +TEST_TF(TypeShow, Interval) { + ASSERT_EQ(ToString(*f.Interval()), "Interval"); +} + +TEST_TF(TypeShow, Decimal) { + ASSERT_EQ(ToString(*f.Decimal(20, 10)), "Decimal(20, 10)"); +} + +TEST_TF(TypeShow, Json) { + ASSERT_EQ(ToString(*f.Json()), "Json"); +} + +TEST_TF(TypeShow, Yson) { + ASSERT_EQ(ToString(*f.Yson()), "Yson"); +} + +TEST_TF(TypeShow, Uuid) { + ASSERT_EQ(ToString(*f.Uuid()), "Uuid"); +} + +TEST_TF(TypeShow, Optional) { + ASSERT_EQ(ToString(*f.Optional(f.String())), "Optional<String>"); +} + +TEST_TF(TypeShow, List) { + ASSERT_EQ(ToString(*f.List(f.String())), "List<String>"); +} + +TEST_TF(TypeShow, Dict) { + ASSERT_EQ(ToString(*f.Dict(f.String(), f.Int32())), "Dict<String, Int32>"); +} + +TEST_TF(TypeShow, Struct) { + ASSERT_EQ( + ToString(*f.Struct({})), + "Struct<>"); + ASSERT_EQ( + ToString(*f.Struct({{"x1", f.Void()}})), + "Struct<'x1': Void>"); + ASSERT_EQ( + ToString(*f.Struct({{"x1", f.Void()}, {"x2", f.String()}})), + "Struct<'x1': Void, 'x2': String>"); + ASSERT_EQ( + ToString(*f.Struct("Name", {})), + "Struct['Name']<>"); + ASSERT_EQ( + ToString(*f.Struct("Name", {{"x1", f.Void()}})), + "Struct['Name']<'x1': Void>"); + ASSERT_EQ( + ToString(*f.Struct("Name", {{"x1", f.Void()}, {"x2", f.String()}})), + "Struct['Name']<'x1': Void, 'x2': String>"); +} + +TEST_TF(TypeShow, Tuple) { + ASSERT_EQ( + ToString(*f.Tuple({})), + "Tuple<>"); + ASSERT_EQ( + ToString(*f.Tuple({{f.Void()}})), + "Tuple<Void>"); + ASSERT_EQ( + ToString(*f.Tuple({{f.Void()}, {f.String()}})), + "Tuple<Void, String>"); + ASSERT_EQ( + ToString(*f.Tuple("Name", {})), + "Tuple['Name']<>"); + ASSERT_EQ( + ToString(*f.Tuple("Name", {{f.Void()}})), + "Tuple['Name']<Void>"); + ASSERT_EQ( + ToString(*f.Tuple("Name", {{f.Void()}, {f.String()}})), + "Tuple['Name']<Void, String>"); +} + +TEST_TF(TypeShow, VariantStruct) { + ASSERT_EQ( + ToString(*f.Variant(f.Struct({{"x1", f.Void()}}))), + "Variant<'x1': Void>"); + ASSERT_EQ( + ToString(*f.Variant(f.Struct({{"x1", f.Void()}, {"x2", f.String()}}))), + "Variant<'x1': Void, 'x2': String>"); + ASSERT_EQ( + ToString(*f.Variant("Name", f.Struct({{"x1", f.Void()}}))), + "Variant['Name']<'x1': Void>"); + ASSERT_EQ( + ToString(*f.Variant("Name", f.Struct({{"x1", f.Void()}, {"x2", f.String()}}))), + "Variant['Name']<'x1': Void, 'x2': String>"); +} + +TEST_TF(TypeShow, VariantTuple) { + ASSERT_EQ( + ToString(*f.Variant(f.Tuple({{f.Void()}}))), + "Variant<Void>"); + ASSERT_EQ( + ToString(*f.Variant(f.Tuple({{f.Void()}, {f.String()}}))), + "Variant<Void, String>"); + ASSERT_EQ( + ToString(*f.Variant("Name", f.Tuple({{f.Void()}}))), + "Variant['Name']<Void>"); + ASSERT_EQ( + ToString(*f.Variant("Name", f.Tuple({{f.Void()}, {f.String()}}))), + "Variant['Name']<Void, String>"); +} + +TEST_TF(TypeShow, Tagged) { + ASSERT_EQ( + ToString(*f.Tagged(f.Void(), "Tag")), + "Tagged<Void, 'Tag'>"); +} diff --git a/library/cpp/type_info/ut/type_strip_tags.cpp b/library/cpp/type_info/ut/type_strip_tags.cpp new file mode 100644 index 0000000000..b3ef4c0825 --- /dev/null +++ b/library/cpp/type_info/ut/type_strip_tags.cpp @@ -0,0 +1,31 @@ +#include <library/cpp/testing/unittest/gtest.h> + +#include <library/cpp/type_info/type_info.h> + +#include "utils.h" + +TEST_TF(TypeStripTags, StripTags) { + auto t = f.List(f.Tagged(f.Void(), "Tag")); + + ASSERT_EQ(t->StripTags().Get(), t.Get()); + ASSERT_EQ(f.Tagged(t, "Tag")->StripTags().Get(), t.Get()); + ASSERT_EQ(f.Tagged(f.Tagged(t, "Tag"), "Tag2")->StripTags().Get(), t.Get()); +} + +TEST_TF(TypeStripTags, StripOptionals) { + auto t = f.Tagged(f.Optional(f.Void()), "Tag"); + + ASSERT_EQ(t->StripOptionals().Get(), t.Get()); + ASSERT_EQ(f.Optional(t)->StripOptionals().Get(), t.Get()); + ASSERT_EQ(f.Optional(f.Optional(t))->StripOptionals().Get(), t.Get()); +} + +TEST_TF(TypeStripTags, StripTagsAndOptionals) { + auto t = f.Void(); + + ASSERT_EQ(t->StripTagsAndOptionals().Get(), t.Get()); + ASSERT_EQ(f.Optional(t)->StripTagsAndOptionals().Get(), t.Get()); + ASSERT_EQ(f.Optional(f.Optional(t))->StripTagsAndOptionals().Get(), t.Get()); + ASSERT_EQ(f.Optional(f.Tagged(t, "Tag"))->StripTagsAndOptionals().Get(), t.Get()); + ASSERT_EQ(f.Optional(f.Optional(f.Optional(t)))->StripTagsAndOptionals().Get(), t.Get()); +} diff --git a/library/cpp/type_info/ut/utils.h b/library/cpp/type_info/ut/utils.h new file mode 100644 index 0000000000..b35316cb2f --- /dev/null +++ b/library/cpp/type_info/ut/utils.h @@ -0,0 +1,73 @@ +#pragma once + +//! @file utils.h +//! +//! Infrastructure for running type info tests with different factories and settings. + +#include <library/cpp/testing/unittest/registar.h> + +#include <library/cpp/yson/consumer.h> +#include <library/cpp/yson/node/node.h> +#include <library/cpp/yson/node/node_builder.h> +#include <library/cpp/yson/node/node_io.h> + +#include <library/cpp/type_info/fwd.h> + +template <typename T> +inline TString ToCanonicalYson(const T& value) { + return NYT::NodeToCanonicalYsonString(NYT::NodeFromYsonString(TStringBuf(value))); +} + +/// Assert that two YSON strings are equal. +#define ASSERT_YSON_EQ(L, R) ASSERT_EQ(ToCanonicalYson(L), ToCanonicalYson(R)) + +/// Assert that two types are strictly equal. +#define ASSERT_STRICT_EQ(a, b) \ + do { \ + auto lhs = (a); \ + auto rhs = (b); \ + ASSERT_TRUE(NTi::NEq::TStrictlyEqual()(lhs, rhs)); \ + ASSERT_TRUE(NTi::NEq::TStrictlyEqual().IgnoreHash(lhs, rhs)); \ + ASSERT_EQ(lhs->GetHash(), NTi::NEq::TStrictlyEqualHash()(lhs)); \ + ASSERT_EQ(rhs->GetHash(), NTi::NEq::TStrictlyEqualHash()(rhs)); \ + ASSERT_EQ(lhs->GetHash(), rhs->GetHash()); \ + } while (false) + +/// Assert that two types are strictly unequal. +/// +/// Note: we check that, if types are not equal, their hashes are also not equal. +/// While this is not guaranteed, we haven't seen any collisions so far. +/// If some collision happen, check if hashing isn't broken before removing the assert. +#define ASSERT_STRICT_NE(a, b) \ + do { \ + auto lhs = (a); \ + auto rhs = (b); \ + ASSERT_FALSE(NTi::NEq::TStrictlyEqual()(lhs, rhs)); \ + ASSERT_FALSE(NTi::NEq::TStrictlyEqual().IgnoreHash(lhs, rhs)); \ + ASSERT_EQ(lhs->GetHash(), NTi::NEq::TStrictlyEqualHash()(lhs)); \ + ASSERT_EQ(rhs->GetHash(), NTi::NEq::TStrictlyEqualHash()(rhs)); \ + ASSERT_NE(lhs->GetHash(), rhs->GetHash()); \ + } while (false) + +/// Assert that a type string is equal to the given type after deserialization. +#define ASSERT_DESERIALIZED_EQ(canonicalType, serializedType) \ + do { \ + auto reader = NYsonPull::TReader(NYsonPull::NInput::FromMemory(serializedType), NYsonPull::EStreamType::Node); \ + auto deserializedType = NTi::NIo::DeserializeYson(*NTi::HeapFactory(), reader); \ + ASSERT_STRICT_EQ(deserializedType, canonicalType); \ + ASSERT_YSON_EQ(NTi::NIo::SerializeYson(canonicalType.Get()), NTi::NIo::SerializeYson(deserializedType.Get())); \ + } while (false) + +/// Test parametrized over different type factories. +#define TEST_TF(N, NN) \ + void Test##N##NN(NTi::ITypeFactory& f); \ + TEST(N, NN##_Heap) { \ + Test##N##NN(*NTi::HeapFactory()); \ + } \ + TEST(N, NN##_Pool) { \ + Test##N##NN(*NTi::PoolFactory(false)); \ + } \ + TEST(N, NN##_PoolDedup) { \ + Test##N##NN(*NTi::PoolFactory(true)); \ + } \ + void Test##N##NN(NTi::ITypeFactory& f) diff --git a/library/cpp/type_info/ut/ya.make b/library/cpp/type_info/ut/ya.make new file mode 100644 index 0000000000..dc27e2a7db --- /dev/null +++ b/library/cpp/type_info/ut/ya.make @@ -0,0 +1,32 @@ +UNITTEST() + +SRCS( + builder.cpp + type_basics.cpp + type_complexity_ut.cpp + type_constraints.cpp + type_deserialize.cpp + type_equivalence.cpp + type_factory.cpp + type_factory_raw.cpp + type_io.cpp + type_list.cpp + type_serialize.cpp + type_show.cpp + type_strip_tags.cpp + test_data.cpp +) + +PEERDIR( + library/cpp/type_info + library/cpp/yson + library/cpp/yson/node + library/cpp/resource +) + +RESOURCE( + ${ARCADIA_ROOT}/library/cpp/type_info/test-data/good-types.txt /good + ${ARCADIA_ROOT}/library/cpp/type_info/test-data/bad-types.txt /bad +) + +END() diff --git a/library/cpp/type_info/ya.make b/library/cpp/type_info/ya.make new file mode 100644 index 0000000000..555f0d1433 --- /dev/null +++ b/library/cpp/type_info/ya.make @@ -0,0 +1,26 @@ +LIBRARY() + +SRCS( + type_info.cpp + + builder.cpp + error.cpp + type.cpp + type_complexity.cpp + type_equivalence.cpp + type_factory.cpp + type_io.cpp + type_list.cpp +) + +GENERATE_ENUM_SERIALIZATION( + type_list.h +) + +PEERDIR( + library/cpp/yson_pull +) + +END() + +RECURSE_FOR_TESTS(ut) |