// Copyright 2023 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include #include #include #include #include #include #include #include #include #include namespace DB { namespace ErrorCodes { extern const int BAD_TYPE_OF_FIELD; extern const int BAD_GET; extern const int NOT_IMPLEMENTED; extern const int LOGICAL_ERROR; extern const int DECIMAL_OVERFLOW_ERROR; } // namespace ErrorCodes class Field; using Array = std::vector; using TupleBackend = std::vector; STRONG_TYPEDEF(TupleBackend, Tuple); /// Array and Tuple are different types with equal representation inside Field. /** 32 is enough. Round number is used for alignment and for better arithmetic inside std::vector. * NOTE: Actually, sizeof(std::string) is 32 when using libc++, so Field is 40 bytes. */ #define DBMS_MIN_FIELD_SIZE 32 template bool decimalEqual(T x, T y, UInt32 x_scale, UInt32 y_scale); template bool decimalLess(T x, T y, UInt32 x_scale, UInt32 y_scale); template bool decimalLessOrEqual(T x, T y, UInt32 x_scale, UInt32 y_scale); #pragma GCC diagnostic push #if defined(__clang__) #pragma GCC diagnostic ignored "-Wunknown-warning-option" #endif #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" // this one should be a false positive template class DecimalField { static_assert(IsDecimal); public: using DecimalType = T; using NativeType = typename T::NativeType; DecimalField(T value, UInt32 scale_) : dec(value) , scale(scale_) {} operator T() const { return dec; } // NOLINT(google-explicit-constructor) template > * = nullptr> operator U() const // NOLINT(google-explicit-constructor) { // clang-format off static const double ScaleMultiplierArray[] = { 1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22 }; // clang-format on // Use double divide algorithm when both the dividend and the divisor can be precisely represented by double // 1e0,1e1,...1e22 can be precisely represented by double // Note: ensure that if precise_dividend is true, then the dividend must be precisely represented; // if precise_dividend is false, the dividend still could have chance to be precisely represented. It doesn't affect // the correctness. auto v = static_cast(dec.value); auto nearest_v = v > 0 ? v - 1 : v + 1; bool precise_dividend = ((dec.value <= (1LL << 53) && (dec.value >= -(1LL << 53))) || (static_cast(v) == dec.value && static_cast(nearest_v) != dec.value)); // Note: even if scale = 0, we should still check precise_dividend here, because cast(int256) doesn't // satisfy IEEE754 perfectly if likely (precise_dividend && scale <= 22) { v = v / ScaleMultiplierArray[scale]; return v; } return atof(toString().c_str()); } template < typename U, std::enable_if_t || std::is_same_v || std::is_same_v> * = nullptr> operator U() const // NOLINT(google-explicit-constructor) { Int256 v = dec.value; for (ScaleType i = 0; i < scale; i++) { v = v / 10 + (i + 1 == scale && v % 10 >= 5); } if (v > std::numeric_limits::max() || v < std::numeric_limits::min()) { throw TiFlashException("Decimal value overflow", Errors::Decimal::Overflow); } return static_cast(v); } T getValue() const { return dec; } UInt32 getScale() const { return scale; } template bool operator<(const DecimalField & r) const { using MaxType = std::conditional_t<(sizeof(T) > sizeof(U)), T, U>; return decimalLess(dec, r.getValue(), scale, r.getScale()); } UInt32 getPrec() const { UInt32 cnt = 0; auto x = dec.value; while (x != 0) { x /= 10; cnt++; } if (cnt == 0) cnt = 1; return std::max(cnt, scale); } /// In TiFlash there are 4 subtype of decimal: /// Decimal32, Decimal64, Decimal128 and Decimal256 /// they are not compatible with each other. So a DecimalField /// can not be inserted into a decimal column with DecimalType /// getPrecWithCurrentDecimalType will return the prec that fit /// current decimal type, that is to say, current DecimalField can be /// inserted into a decimal column with type `Decimal(getPrecWithCurrentDecimalType, getScale)` UInt32 getPrecWithCurrentDecimalType() const { auto raw_prec = getPrec(); return std::max(raw_prec, minDecimalPrecision()); } template bool operator<=(const DecimalField & r) const { using MaxType = std::conditional_t<(sizeof(T) > sizeof(U)), T, U>; return decimalLessOrEqual(dec, r.getValue(), scale, r.getScale()); } template bool operator==(const DecimalField & r) const { using MaxType = std::conditional_t<(sizeof(T) > sizeof(U)), T, U>; return decimalEqual(dec, r.getValue(), scale, r.getScale()); } template bool operator>(const DecimalField & r) const { return r < *this; } template bool operator>=(const DecimalField & r) const { return r <= *this; } template bool operator!=(const DecimalField & r) const { return !(*this == r); } String toString() const { return dec.toString(scale); } const DecimalField & operator+=(const DecimalField & r) { if (scale != r.getScale()) throw Exception("Add different decimal fields", ErrorCodes::LOGICAL_ERROR); dec += r.getValue(); return *this; } const DecimalField & operator-=(const DecimalField & r) { if (scale != r.getScale()) throw Exception("Sub different decimal fields", ErrorCodes::LOGICAL_ERROR); dec -= r.getValue(); return *this; } private: T dec{}; UInt32 scale{}; }; #pragma GCC diagnostic pop /** Discriminated union of several types. * Made for replacement of `boost::variant` * is not generalized, * but somewhat more efficient, and simpler. * * Used to represent a single value of one of several types in memory. * Warning! Prefer to use chunks of columns instead of single values. See Column.h */ class Field { public: struct Types { /// Type tag. enum Which { Null = 0, UInt64 = 1, Int64 = 2, Float64 = 3, UInt128 = 4, Int128 = 5, Int256 = 6, /// Non-POD types. String = 16, Array = 17, Tuple = 18, Decimal32 = 19, Decimal64 = 20, Decimal128 = 21, Decimal256 = 22, }; static const int MIN_NON_POD = 16; static const char * toString(Which which) { switch (which) { case Null: return "Null"; case UInt64: return "UInt64"; case Int64: return "Int64"; case Float64: return "Float64"; case UInt128: return "UInt128"; case Int128: return "Int128"; case Int256: return "Int256"; case String: return "String"; case Array: return "Array"; case Tuple: return "Tuple"; case Decimal32: return "Decimal32"; case Decimal64: return "Decimal64"; case Decimal128: return "Decimal128"; case Decimal256: return "Decimal256"; default: throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD); } } }; /// Returns an identifier for the type or vice versa. template struct TypeToEnum; template struct EnumToType; Field() : which(Types::Null) {} /** Despite the presence of a template constructor, this constructor is still needed, * since, in its absence, the compiler will still generate the default constructor. */ Field(const Field & rhs) { create(rhs); } Field(Field && rhs) { create(std::move(rhs)); } template Field( T && rhs, std::integral_constant< int, Field::TypeToEnum>::value> * = nullptr) // NOLINT(google-explicit-constructor) { createConcrete(std::forward(rhs)); } /// Create a string inplace. Field(const char * data, size_t size) { create(data, size); } Field(const unsigned char * data, size_t size) { create(data, size); } /// NOTE In case when field already has string type, more direct assign is possible. void assignString(const char * data, size_t size) { destroy(); create(data, size); } void assignString(const unsigned char * data, size_t size) { destroy(); create(data, size); } Field & operator=(const Field & rhs) { if (this != &rhs) { if (which != rhs.which) { destroy(); create(rhs); } else assign(rhs); /// This assigns string or vector without deallocation of existing buffer. } return *this; } template Field & operator=(T && rhs) // NOLINT(misc-unconventional-assign-operator) there is still a false-positive here { if constexpr (std::is_same_v, Field>) { if (this != &rhs) { if (which != rhs.which) { destroy(); create(std::forward(rhs)); } else assign(std::forward(rhs)); } } else { if (which != TypeToEnum>::value) { destroy(); createConcrete(std::forward(rhs)); } else assignConcrete(std::forward(rhs)); } return *this; } ~Field() { destroy(); } Types::Which getType() const { return which; } const char * getTypeName() const { return Types::toString(which); } String toString() const; bool isNull() const { return which == Types::Null; } template T & get() { using TWithoutRef = std::remove_reference_t; auto * MAY_ALIAS ptr = reinterpret_cast(&storage); return *ptr; }; template const T & get() const { using TWithoutRef = std::remove_reference_t; const auto * MAY_ALIAS ptr = reinterpret_cast(&storage); return *ptr; }; template bool tryGet(T & result) { const Types::Which requested = TypeToEnum>::value; if (which != requested) return false; result = get(); return true; } template bool tryGet(T & result) const { const Types::Which requested = TypeToEnum>::value; if (which != requested) return false; result = get(); return true; } template T & safeGet() { const Types::Which requested = TypeToEnum>::value; if (which != requested) throw Exception( "Bad get: has " + std::string(getTypeName()) + ", requested " + std::string(Types::toString(requested)), ErrorCodes::BAD_GET); return get(); } template const T & safeGet() const { const Types::Which requested = TypeToEnum>::value; if (which != requested) throw Exception( "Bad get: has " + std::string(getTypeName()) + ", requested " + std::string(Types::toString(requested)), ErrorCodes::BAD_GET); return get(); } bool operator<(const Field & rhs) const { if (which < rhs.which) return true; if (which > rhs.which) return false; switch (which) { case Types::Null: return false; case Types::UInt64: return get() < rhs.get(); case Types::UInt128: return get() < rhs.get(); case Types::Int64: return get() < rhs.get(); case Types::Float64: return get() < rhs.get(); case Types::String: return get() < rhs.get(); case Types::Array: return get() < rhs.get(); case Types::Tuple: return get() < rhs.get(); case Types::Decimal32: return get>() < rhs.get>(); case Types::Decimal64: return get>() < rhs.get>(); case Types::Decimal128: return get>() < rhs.get>(); case Types::Decimal256: return get>() < rhs.get>(); default: throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD); } } bool operator>(const Field & rhs) const { return rhs < *this; } bool operator<=(const Field & rhs) const { if (which < rhs.which) return true; if (which > rhs.which) return false; switch (which) { case Types::Null: return true; case Types::UInt64: return get() <= rhs.get(); case Types::UInt128: return get() <= rhs.get(); case Types::Int64: return get() <= rhs.get(); case Types::Float64: return get() <= rhs.get(); case Types::String: return get() <= rhs.get(); case Types::Array: return get() <= rhs.get(); case Types::Tuple: return get() <= rhs.get(); case Types::Decimal32: return get>() <= rhs.get>(); case Types::Decimal64: return get>() <= rhs.get>(); case Types::Decimal128: return get>() <= rhs.get>(); case Types::Decimal256: return get>() <= rhs.get>(); default: throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD); } } bool operator>=(const Field & rhs) const { return rhs <= *this; } bool operator==(const Field & rhs) const { if (which != rhs.which) return false; switch (which) { case Types::Null: return true; case Types::UInt64: case Types::Int64: case Types::Float64: return get() == rhs.get(); case Types::String: return get() == rhs.get(); case Types::Array: return get() == rhs.get(); case Types::Tuple: return get() == rhs.get(); case Types::UInt128: return get() == rhs.get(); case Types::Decimal32: return get>() == rhs.get>(); case Types::Decimal64: return get>() == rhs.get>(); case Types::Decimal128: return get>() == rhs.get>(); case Types::Decimal256: return get>() == rhs.get>(); default: throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD); } } bool operator!=(const Field & rhs) const { return !(*this == rhs); } private: std::aligned_union_t< DBMS_MIN_FIELD_SIZE - sizeof(Types::Which), Null, UInt64, UInt128, Int64, Float64, String, Array, Tuple, DecimalField, DecimalField, DecimalField, DecimalField> storage; Types::Which which; /// Assuming there was no allocated state or it was deallocated (see destroy). #pragma GCC diagnostic push #if defined(__clang__) #pragma GCC diagnostic ignored "-Wunknown-warning-option" #endif #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" template void createConcrete(T && x) { using JustT = std::decay_t; auto * MAY_ALIAS ptr = reinterpret_cast(&storage); new (ptr) JustT(std::forward(x)); which = TypeToEnum::value; } /// Assuming same types. template void assignConcrete(T && x) { using JustT = std::decay_t; auto * MAY_ALIAS ptr = reinterpret_cast(&storage); *ptr = std::forward(x); } template /// Field template parameter may be const or non-const Field. static void dispatch(F && f, Field & field) { switch (field.which) { case Types::Null: f(field.template get()); return; case Types::UInt64: f(field.template get()); return; case Types::UInt128: f(field.template get()); return; case Types::Int64: f(field.template get()); return; case Types::Float64: f(field.template get()); return; case Types::String: f(field.template get()); return; case Types::Array: f(field.template get()); return; case Types::Tuple: f(field.template get()); return; case Types::Decimal32: f(field.template get>()); return; case Types::Decimal64: f(field.template get>()); return; case Types::Decimal128: f(field.template get>()); return; case Types::Decimal256: f(field.template get>()); return; default: throw Exception("Bad type of Field", ErrorCodes::BAD_TYPE_OF_FIELD); } } #pragma GCC diagnostic pop void create(const Field & x) { dispatch([this](auto & value) { createConcrete(value); }, x); } void create(Field && x) { dispatch([this](auto & value) { createConcrete(std::move(value)); }, x); } void assign(const Field & x) { dispatch([this](auto & value) { assignConcrete(value); }, x); } void assign(Field && x) { dispatch([this](auto & value) { assignConcrete(std::move(value)); }, x); } void create(const char * data, size_t size) { auto * MAY_ALIAS ptr = reinterpret_cast(&storage); new (ptr) String(data, size); which = Types::String; } void create(const unsigned char * data, size_t size) { create(reinterpret_cast(data), size); } ALWAYS_INLINE void destroy() { if (which < Types::MIN_NON_POD) return; switch (which) { case Types::String: destroy(); break; case Types::Array: destroy(); break; case Types::Tuple: destroy(); break; default: break; } which = Types::Null; /// for exception safety in subsequent calls to destroy and create, when create fails. } template void destroy() { T * MAY_ALIAS ptr = reinterpret_cast(&storage); ptr->~T(); } }; #undef DBMS_MIN_FIELD_SIZE template <> struct Field::TypeToEnum { static const Types::Which value = Types::Null; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::UInt64; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::UInt128; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Int128; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Int256; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Int64; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Float64; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::String; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Array; }; template <> struct Field::TypeToEnum { static const Types::Which value = Types::Tuple; }; template <> struct Field::TypeToEnum> { static const Types::Which value = Types::Decimal32; }; template <> struct Field::TypeToEnum> { static const Types::Which value = Types::Decimal64; }; template <> struct Field::TypeToEnum> { static const Types::Which value = Types::Decimal128; }; template <> struct Field::TypeToEnum> { static const Types::Which value = Types::Decimal256; }; template <> struct Field::EnumToType { using Type = Null; }; template <> struct Field::EnumToType { using Type = UInt64; }; template <> struct Field::EnumToType { using Type = UInt128; }; template <> struct Field::EnumToType { using Type = Int64; }; template <> struct Field::EnumToType { using Type = Int128; }; template <> struct Field::EnumToType { using Type = Int256; }; template <> struct Field::EnumToType { using Type = Float64; }; template <> struct Field::EnumToType { using Type = String; }; template <> struct Field::EnumToType { using Type = Array; }; template <> struct Field::EnumToType { using Type = Tuple; }; template <> struct Field::EnumToType { using Type = DecimalField; }; template <> struct Field::EnumToType { using Type = DecimalField; }; template <> struct Field::EnumToType { using Type = DecimalField; }; template <> struct Field::EnumToType { using Type = DecimalField; }; template T get(const Field & field) { return field.template get(); } template T get(Field & field) { return field.template get(); } template T safeGet(const Field & field) { return field.template safeGet(); } template T safeGet(Field & field) { return field.template safeGet(); } template <> struct TypeName { static std::string get() { return "Array"; } }; template <> struct TypeName { static std::string get() { return "Tuple"; } }; template struct NearestFieldType; template <> struct NearestFieldType { using Type = UInt64; }; template <> struct NearestFieldType { using Type = UInt64; }; template <> struct NearestFieldType { using Type = UInt64; }; template <> struct NearestFieldType { using Type = UInt64; }; template <> struct NearestFieldType { using Type = UInt128; }; template <> struct NearestFieldType { using Type = Int64; }; template <> struct NearestFieldType { using Type = Int64; }; template <> struct NearestFieldType { using Type = Int64; }; template <> struct NearestFieldType { using Type = Int64; }; template <> struct NearestFieldType { using Type = Float64; }; template <> struct NearestFieldType { using Type = Float64; }; template <> struct NearestFieldType { using Type = String; }; template <> struct NearestFieldType { using Type = Array; }; template <> struct NearestFieldType { using Type = Tuple; }; template <> struct NearestFieldType { using Type = UInt64; }; template <> struct NearestFieldType { using Type = Null; }; template <> struct NearestFieldType> { using Type = DecimalField; }; template <> struct NearestFieldType> { using Type = DecimalField; }; template <> struct NearestFieldType> { using Type = DecimalField; }; template <> struct NearestFieldType> { using Type = DecimalField; }; template <> struct NearestFieldType { using Type = DecimalField; }; template <> struct NearestFieldType { using Type = DecimalField; }; template <> struct NearestFieldType { using Type = DecimalField; }; template <> struct NearestFieldType { using Type = DecimalField; }; template <> struct NearestFieldType { using Type = Int128; }; template <> struct NearestFieldType { using Type = Int256; }; template typename NearestFieldType::Type nearestFieldType(const T & x) { return typename NearestFieldType::Type(x); } class ReadBuffer; class WriteBuffer; /// It is assumed that all elements of the array have the same type. void readBinary(Array & x, ReadBuffer & buf); inline void readText(Array &, ReadBuffer &) { throw Exception("Cannot read Array.", ErrorCodes::NOT_IMPLEMENTED); } inline void readQuoted(Array &, ReadBuffer &) { throw Exception("Cannot read Array.", ErrorCodes::NOT_IMPLEMENTED); } /// It is assumed that all elements of the array have the same type. void writeBinary(const Array & x, WriteBuffer & buf); void writeText(const Array & x, WriteBuffer & buf); inline void writeQuoted(const Array &, WriteBuffer &) { throw Exception("Cannot write Array quoted.", ErrorCodes::NOT_IMPLEMENTED); } void readBinary(Tuple & x, ReadBuffer & buf); inline void readText(Tuple &, ReadBuffer &) { throw Exception("Cannot read Tuple.", ErrorCodes::NOT_IMPLEMENTED); } inline void readQuoted(Tuple &, ReadBuffer &) { throw Exception("Cannot read Tuple.", ErrorCodes::NOT_IMPLEMENTED); } void writeBinary(const Tuple & x, WriteBuffer & buf); void writeText(const Tuple & x, WriteBuffer & buf); inline void writeQuoted(const Tuple &, WriteBuffer &) { throw Exception("Cannot write Tuple quoted.", ErrorCodes::NOT_IMPLEMENTED); } } // namespace DB