diff --git a/LICENSE b/LICENSE index ce4ab84..c1b9df2 100644 --- a/LICENSE +++ b/LICENSE @@ -267,3 +267,17 @@ https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp MurmurHash3 was written by Austin Appleby, and is placed in the public domain. The author disclaims copyright to this source code. + +-------------------------------------------------------------------------------- + +The file src/iceberg/util/checked_cast.h contains code adapted from + +https://github.com/apache/arrow/blob/main/cpp/src/arrow/util/checked_cast.h + +The file src/iceberg/util/visit_type.h contains code adapted from + +https://github.com/apache/arrow/blob/main/cpp/src/arrow/visit_type_inline.h + +Copyright: 2016-2025 The Apache Software Foundation. +Home page: https://arrow.apache.org/ +License: https://www.apache.org/licenses/LICENSE-2.0 diff --git a/NOTICE b/NOTICE index cad0c1a..b287ab0 100644 --- a/NOTICE +++ b/NOTICE @@ -12,3 +12,7 @@ This product includes code from smhasher * MurmurHash3 was written by Austin Appleby, and is placed in the public * domain. The author hereby disclaims copyright to this source code. * https://github.com/aappleby/smhasher + +This product includes code from Apache Arrow + * Copyright 2016-2025 The Apache Software Foundation + * https://github.com/apache/arrow diff --git a/src/iceberg/CMakeLists.txt b/src/iceberg/CMakeLists.txt index c9fe783..2ce3012 100644 --- a/src/iceberg/CMakeLists.txt +++ b/src/iceberg/CMakeLists.txt @@ -38,7 +38,8 @@ set(ICEBERG_SOURCES transform_function.cc type.cc util/murmurhash3_internal.cc - util/timepoint.cc) + util/timepoint.cc + util/unreachable.cc) set(ICEBERG_STATIC_BUILD_INTERFACE_LIBS) set(ICEBERG_SHARED_BUILD_INTERFACE_LIBS) diff --git a/src/iceberg/type.cc b/src/iceberg/type.cc index e594a12..e58cd0e 100644 --- a/src/iceberg/type.cc +++ b/src/iceberg/type.cc @@ -41,7 +41,7 @@ StructType::StructType(std::vector fields) : fields_(std::move(fiel } } -TypeId StructType::type_id() const { return TypeId::kStruct; } +TypeId StructType::type_id() const { return kTypeId; } std::string StructType::ToString() const { std::string repr = "struct<\n"; for (const auto& field : fields_) { @@ -93,7 +93,7 @@ ListType::ListType(SchemaField element) : element_(std::move(element)) { ListType::ListType(int32_t field_id, std::shared_ptr type, bool optional) : element_(field_id, std::string(kElementName), std::move(type), optional) {} -TypeId ListType::type_id() const { return TypeId::kList; } +TypeId ListType::type_id() const { return kTypeId; } std::string ListType::ToString() const { // XXX: work around Clang/libc++: "<{}>" in a format string appears to get // parsed as {<>} or something; split up the format string to avoid that @@ -146,7 +146,7 @@ MapType::MapType(SchemaField key, SchemaField value) const SchemaField& MapType::key() const { return fields_[0]; } const SchemaField& MapType::value() const { return fields_[1]; } -TypeId MapType::type_id() const { return TypeId::kMap; } +TypeId MapType::type_id() const { return kTypeId; } std::string MapType::ToString() const { // XXX: work around Clang/libc++: "<{}>" in a format string appears to get // parsed as {<>} or something; split up the format string to avoid that @@ -192,33 +192,25 @@ bool MapType::Equals(const Type& other) const { return fields_ == map.fields_; } -TypeId BooleanType::type_id() const { return TypeId::kBoolean; } +TypeId BooleanType::type_id() const { return kTypeId; } std::string BooleanType::ToString() const { return "boolean"; } -bool BooleanType::Equals(const Type& other) const { - return other.type_id() == TypeId::kBoolean; -} +bool BooleanType::Equals(const Type& other) const { return other.type_id() == kTypeId; } -TypeId IntType::type_id() const { return TypeId::kInt; } +TypeId IntType::type_id() const { return kTypeId; } std::string IntType::ToString() const { return "int"; } -bool IntType::Equals(const Type& other) const { return other.type_id() == TypeId::kInt; } +bool IntType::Equals(const Type& other) const { return other.type_id() == kTypeId; } -TypeId LongType::type_id() const { return TypeId::kLong; } +TypeId LongType::type_id() const { return kTypeId; } std::string LongType::ToString() const { return "long"; } -bool LongType::Equals(const Type& other) const { - return other.type_id() == TypeId::kLong; -} +bool LongType::Equals(const Type& other) const { return other.type_id() == kTypeId; } -TypeId FloatType::type_id() const { return TypeId::kFloat; } +TypeId FloatType::type_id() const { return kTypeId; } std::string FloatType::ToString() const { return "float"; } -bool FloatType::Equals(const Type& other) const { - return other.type_id() == TypeId::kFloat; -} +bool FloatType::Equals(const Type& other) const { return other.type_id() == kTypeId; } -TypeId DoubleType::type_id() const { return TypeId::kDouble; } +TypeId DoubleType::type_id() const { return kTypeId; } std::string DoubleType::ToString() const { return "double"; } -bool DoubleType::Equals(const Type& other) const { - return other.type_id() == TypeId::kDouble; -} +bool DoubleType::Equals(const Type& other) const { return other.type_id() == kTypeId; } DecimalType::DecimalType(int32_t precision, int32_t scale) : precision_(precision), scale_(scale) { @@ -230,57 +222,47 @@ DecimalType::DecimalType(int32_t precision, int32_t scale) int32_t DecimalType::precision() const { return precision_; } int32_t DecimalType::scale() const { return scale_; } -TypeId DecimalType::type_id() const { return TypeId::kDecimal; } +TypeId DecimalType::type_id() const { return kTypeId; } std::string DecimalType::ToString() const { return std::format("decimal({}, {})", precision_, scale_); } bool DecimalType::Equals(const Type& other) const { - if (other.type_id() != TypeId::kDecimal) { + if (other.type_id() != kTypeId) { return false; } const auto& decimal = static_cast(other); return precision_ == decimal.precision_ && scale_ == decimal.scale_; } -TypeId DateType::type_id() const { return TypeId::kDate; } +TypeId DateType::type_id() const { return kTypeId; } std::string DateType::ToString() const { return "date"; } -bool DateType::Equals(const Type& other) const { - return other.type_id() == TypeId::kDate; -} +bool DateType::Equals(const Type& other) const { return other.type_id() == kTypeId; } -TypeId TimeType::type_id() const { return TypeId::kTime; } +TypeId TimeType::type_id() const { return kTypeId; } std::string TimeType::ToString() const { return "time"; } -bool TimeType::Equals(const Type& other) const { - return other.type_id() == TypeId::kTime; -} +bool TimeType::Equals(const Type& other) const { return other.type_id() == kTypeId; } bool TimestampType::is_zoned() const { return false; } TimeUnit TimestampType::time_unit() const { return TimeUnit::kMicrosecond; } -TypeId TimestampType::type_id() const { return TypeId::kTimestamp; } +TypeId TimestampType::type_id() const { return kTypeId; } std::string TimestampType::ToString() const { return "timestamp"; } -bool TimestampType::Equals(const Type& other) const { - return other.type_id() == TypeId::kTimestamp; -} +bool TimestampType::Equals(const Type& other) const { return other.type_id() == kTypeId; } bool TimestampTzType::is_zoned() const { return true; } TimeUnit TimestampTzType::time_unit() const { return TimeUnit::kMicrosecond; } -TypeId TimestampTzType::type_id() const { return TypeId::kTimestampTz; } +TypeId TimestampTzType::type_id() const { return kTypeId; } std::string TimestampTzType::ToString() const { return "timestamptz"; } bool TimestampTzType::Equals(const Type& other) const { - return other.type_id() == TypeId::kTimestampTz; + return other.type_id() == kTypeId; } -TypeId StringType::type_id() const { return TypeId::kString; } +TypeId StringType::type_id() const { return kTypeId; } std::string StringType::ToString() const { return "string"; } -bool StringType::Equals(const Type& other) const { - return other.type_id() == TypeId::kString; -} +bool StringType::Equals(const Type& other) const { return other.type_id() == kTypeId; } -TypeId UuidType::type_id() const { return TypeId::kUuid; } +TypeId UuidType::type_id() const { return kTypeId; } std::string UuidType::ToString() const { return "uuid"; } -bool UuidType::Equals(const Type& other) const { - return other.type_id() == TypeId::kUuid; -} +bool UuidType::Equals(const Type& other) const { return other.type_id() == kTypeId; } FixedType::FixedType(int32_t length) : length_(length) { if (length < 0) { @@ -289,20 +271,18 @@ FixedType::FixedType(int32_t length) : length_(length) { } int32_t FixedType::length() const { return length_; } -TypeId FixedType::type_id() const { return TypeId::kFixed; } +TypeId FixedType::type_id() const { return kTypeId; } std::string FixedType::ToString() const { return std::format("fixed({})", length_); } bool FixedType::Equals(const Type& other) const { - if (other.type_id() != TypeId::kFixed) { + if (other.type_id() != kTypeId) { return false; } const auto& fixed = static_cast(other); return length_ == fixed.length_; } -TypeId BinaryType::type_id() const { return TypeId::kBinary; } +TypeId BinaryType::type_id() const { return kTypeId; } std::string BinaryType::ToString() const { return "binary"; } -bool BinaryType::Equals(const Type& other) const { - return other.type_id() == TypeId::kBinary; -} +bool BinaryType::Equals(const Type& other) const { return other.type_id() == kTypeId; } } // namespace iceberg diff --git a/src/iceberg/type.h b/src/iceberg/type.h index 5405106..9bb8623 100644 --- a/src/iceberg/type.h +++ b/src/iceberg/type.h @@ -104,6 +104,7 @@ class ICEBERG_EXPORT NestedType : public Type { /// \brief A data type representing a struct with nested fields. class ICEBERG_EXPORT StructType : public NestedType { public: + constexpr static TypeId kTypeId = TypeId::kStruct; explicit StructType(std::vector fields); ~StructType() override = default; @@ -128,6 +129,7 @@ class ICEBERG_EXPORT StructType : public NestedType { /// \brief A data type representing a list of values. class ICEBERG_EXPORT ListType : public NestedType { public: + constexpr static const TypeId kTypeId = TypeId::kList; constexpr static const std::string_view kElementName = "element"; /// \brief Construct a list of the given element. The name of the child @@ -157,6 +159,7 @@ class ICEBERG_EXPORT ListType : public NestedType { /// \brief A data type representing a dictionary of values. class ICEBERG_EXPORT MapType : public NestedType { public: + constexpr static const TypeId kTypeId = TypeId::kMap; constexpr static const std::string_view kKeyName = "key"; constexpr static const std::string_view kValueName = "value"; @@ -194,6 +197,8 @@ class ICEBERG_EXPORT MapType : public NestedType { /// \brief A data type representing a boolean (true or false). class ICEBERG_EXPORT BooleanType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kBoolean; + BooleanType() = default; ~BooleanType() override = default; @@ -207,6 +212,8 @@ class ICEBERG_EXPORT BooleanType : public PrimitiveType { /// \brief A data type representing a 32-bit signed integer. class ICEBERG_EXPORT IntType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kInt; + IntType() = default; ~IntType() override = default; @@ -220,6 +227,8 @@ class ICEBERG_EXPORT IntType : public PrimitiveType { /// \brief A data type representing a 64-bit signed integer. class ICEBERG_EXPORT LongType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kLong; + LongType() = default; ~LongType() override = default; @@ -234,6 +243,8 @@ class ICEBERG_EXPORT LongType : public PrimitiveType { /// float. class ICEBERG_EXPORT FloatType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kFloat; + FloatType() = default; ~FloatType() override = default; @@ -248,6 +259,8 @@ class ICEBERG_EXPORT FloatType : public PrimitiveType { /// float. class ICEBERG_EXPORT DoubleType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kDouble; + DoubleType() = default; ~DoubleType() override = default; @@ -261,6 +274,7 @@ class ICEBERG_EXPORT DoubleType : public PrimitiveType { /// \brief A data type representing a fixed-precision decimal. class ICEBERG_EXPORT DecimalType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kDecimal; constexpr static const int32_t kMaxPrecision = 38; /// \brief Construct a decimal type with the given precision and scale. @@ -288,6 +302,8 @@ class ICEBERG_EXPORT DecimalType : public PrimitiveType { /// timezone or time. class ICEBERG_EXPORT DateType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kDate; + DateType() = default; ~DateType() override = default; @@ -302,6 +318,8 @@ class ICEBERG_EXPORT DateType : public PrimitiveType { /// reference to a timezone or date. class ICEBERG_EXPORT TimeType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kTime; + TimeType() = default; ~TimeType() override = default; @@ -326,6 +344,8 @@ class ICEBERG_EXPORT TimestampBase : public PrimitiveType { /// reference to a timezone. class ICEBERG_EXPORT TimestampType : public TimestampBase { public: + constexpr static const TypeId kTypeId = TypeId::kTimestamp; + TimestampType() = default; ~TimestampType() override = default; @@ -343,6 +363,8 @@ class ICEBERG_EXPORT TimestampType : public TimestampBase { /// epoch in UTC. A time zone or offset is not stored. class ICEBERG_EXPORT TimestampTzType : public TimestampBase { public: + constexpr static const TypeId kTypeId = TypeId::kTimestampTz; + TimestampTzType() = default; ~TimestampTzType() override = default; @@ -359,6 +381,8 @@ class ICEBERG_EXPORT TimestampTzType : public TimestampBase { /// \brief A data type representing an arbitrary-length byte sequence. class ICEBERG_EXPORT BinaryType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kBinary; + BinaryType() = default; ~BinaryType() override = default; @@ -373,6 +397,8 @@ class ICEBERG_EXPORT BinaryType : public PrimitiveType { /// (encoded in UTF-8). class ICEBERG_EXPORT StringType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kString; + StringType() = default; ~StringType() override = default; @@ -386,6 +412,8 @@ class ICEBERG_EXPORT StringType : public PrimitiveType { /// \brief A data type representing a fixed-length bytestring. class ICEBERG_EXPORT FixedType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kFixed; + /// \brief Construct a fixed type with the given length. explicit FixedType(int32_t length); ~FixedType() override = default; @@ -407,6 +435,8 @@ class ICEBERG_EXPORT FixedType : public PrimitiveType { /// it is effectively a fixed(16). class ICEBERG_EXPORT UuidType : public PrimitiveType { public: + constexpr static const TypeId kTypeId = TypeId::kUuid; + UuidType() = default; ~UuidType() override = default; diff --git a/src/iceberg/util/checked_cast.h b/src/iceberg/util/checked_cast.h new file mode 100644 index 0000000..0b966b8 --- /dev/null +++ b/src/iceberg/util/checked_cast.h @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +/// \file iceberg/util/checked_cast.h +/// \brief Checked cast functions for dynamic_cast and static_cast. +/// Adapted from Apache Arrow +/// https://github.com/apache/arrow/blob/main/cpp/src/arrow/util/checked_cast.h + +#include +#include +#include + +namespace iceberg::internal { + +template +inline OutputType checked_cast(InputType&& value) { + static_assert( + std::is_class_v>>, + "checked_cast input type must be a class"); + static_assert( + std::is_class_v>>, + "checked_cast output type must be a class"); +#ifdef NDEBUG + return static_cast(value); +#else + return dynamic_cast(value); +#endif +} + +template +std::shared_ptr checked_pointer_cast(std::shared_ptr r) noexcept { +#ifdef NDEBUG + return std::static_pointer_cast(std::move(r)); +#else + return std::dynamic_pointer_cast(std::move(r)); +#endif +} + +template +std::unique_ptr checked_pointer_cast(std::unique_ptr r) noexcept { +#ifdef NDEBUG + return std::unique_ptr(static_cast(r.release())); +#else + return std::unique_ptr(dynamic_cast(r.release())); +#endif +} + +} // namespace iceberg::internal diff --git a/src/iceberg/util/unreachable.cc b/src/iceberg/util/unreachable.cc new file mode 100644 index 0000000..e86a0f2 --- /dev/null +++ b/src/iceberg/util/unreachable.cc @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#include "iceberg/util/unreachable.h" + +#include + +namespace iceberg::internal { + +// TODO(anyone): log the message before aborting +[[noreturn]] void Unreachable([[maybe_unused]] const char* message) { std::abort(); } + +[[noreturn]] void Unreachable([[maybe_unused]] std::string_view message) { std::abort(); } + +} // namespace iceberg::internal diff --git a/src/iceberg/util/unreachable.h b/src/iceberg/util/unreachable.h new file mode 100644 index 0000000..03c4b9d --- /dev/null +++ b/src/iceberg/util/unreachable.h @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 "iceberg/iceberg_export.h" + +namespace iceberg::internal { + +[[noreturn]] ICEBERG_EXPORT void Unreachable( + [[maybe_unused]] const char* message = "Unreachable"); + +[[noreturn]] ICEBERG_EXPORT void Unreachable([[maybe_unused]] std::string_view message); + +} // namespace iceberg::internal diff --git a/src/iceberg/util/visit_type.h b/src/iceberg/util/visit_type.h new file mode 100644 index 0000000..404a0a6 --- /dev/null +++ b/src/iceberg/util/visit_type.h @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +/// \file iceberg/util/visit_type.h +/// \brief Visitor pattern for Iceberg types +/// Adapted from Apache Arrow +/// https://github.com/apache/arrow/blob/main/cpp/src/arrow/visit_type_inline.h + +#include "iceberg/result.h" +#include "iceberg/type.h" +#include "iceberg/util/checked_cast.h" +#include "iceberg/util/unreachable.h" +#include "iceberg/util/visitor_generate.h" + +namespace iceberg { + +#define TYPE_VISIT_INLINE(TYPE_CLASS) \ + case TYPE_CLASS##Type::kTypeId: \ + return visitor->Visit( \ + iceberg::internal::checked_cast(type), \ + std::forward(args)...); + +/// \brief Calls `visitor` with the corresponding concrete type class +/// +/// \tparam VISITOR Visitor type that implements Visit() for all Iceberg types. +/// \tparam ARGS Additional arguments, if any, will be passed to the Visit function after +/// the `type` argument +/// \return Status +/// +/// A visitor is a type that implements specialized logic for each Iceberg type. +/// Example usage: +/// +/// ``` +/// class ExampleVisitor { +/// Status Visit(const IntType& type) { ... } +/// Status Visit(const LongType& type) { ... } +/// ... +/// } +/// ExampleVisitor visitor; +/// VisitTypeInline(some_type, &visitor); +/// ``` + +template +inline Status VisitTypeInline(const Type& type, VISITOR* visitor, ARGS&&... args) { + switch (type.type_id()) { + ICEBERG_GENERATE_FOR_ALL_TYPES(TYPE_VISIT_INLINE); + default: + break; + } + return NotImplemented("Type not implemented"); +} + +#undef TYPE_VISIT_INLINE + +#define TYPE_VISIT_INLINE(TYPE_CLASS) \ + case TYPE_CLASS##Type::kTypeId: \ + return std::forward(visitor)( \ + internal::checked_cast(type), \ + std::forward(args)...); + +/// \brief Call `visitor` with the corresponding concrete type class +/// \tparam ARGS Additional arguments, if any, will be passed to the Visit function after +/// the `type` argument +/// +/// Unlike VisitTypeInline which calls `visitor.Visit`, here `visitor` +/// itself is called. +/// `visitor` must support a `const Type&` argument as a fallback, +/// in addition to concrete type classes. +/// +/// The intent is for this to be called on a generic lambda +/// that may internally use `if constexpr` or similar constructs. +template +inline auto VisitType(const Type& type, VISITOR&& visitor, ARGS&&... args) + -> decltype(std::forward(visitor)(type, args...)) { + switch (type.type_id()) { + ICEBERG_GENERATE_FOR_ALL_TYPES(TYPE_VISIT_INLINE); + default: + internal::Unreachable("Type not implemented"); + } +} + +#undef TYPE_VISIT_INLINE + +#define TYPE_ID_VISIT_INLINE(TYPE_CLASS) \ + case TYPE_CLASS##Type::kTypeId: { \ + const TYPE_CLASS##Type* concrete_ptr = nullptr; \ + return visitor->Visit(concrete_ptr, std::forward(args)...); \ + } + +/// \brief Calls `visitor` with a nullptr of the corresponding concrete type class +/// +/// \tparam VISITOR Visitor type that implements Visit() for all Iceberg types. +/// \tparam ARGS Additional arguments, if any, will be passed to the Visit function after +/// the `type` argument +/// \return Status +template +inline Status VisitTypeIdInline(TypeId id, VISITOR* visitor, ARGS&&... args) { + switch (id) { + ICEBERG_GENERATE_FOR_ALL_TYPES(TYPE_ID_VISIT_INLINE); + default: + break; + } + return NotImplemented("Type not implemented"); +} + +#undef TYPE_ID_VISIT_INLINE + +} // namespace iceberg diff --git a/src/iceberg/util/visitor_generate.h b/src/iceberg/util/visitor_generate.h new file mode 100644 index 0000000..2ea8282 --- /dev/null +++ b/src/iceberg/util/visitor_generate.h @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + +namespace iceberg { + +#define ICEBERG_GENERATE_FOR_ALL_TYPES(ACTION) \ + ACTION(Boolean); \ + ACTION(Int); \ + ACTION(Long); \ + ACTION(Float); \ + ACTION(Double); \ + ACTION(Decimal); \ + ACTION(Date); \ + ACTION(Time); \ + ACTION(Timestamp); \ + ACTION(TimestampTz); \ + ACTION(String); \ + ACTION(Uuid); \ + ACTION(Fixed); \ + ACTION(Binary); \ + ACTION(Struct); \ + ACTION(List); \ + ACTION(Map); + +} // namespace iceberg diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 652d40c..31569a7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -58,7 +58,8 @@ target_link_libraries(json_serde_test PRIVATE iceberg_static GTest::gtest_main add_test(NAME json_serde_test COMMAND json_serde_test) add_executable(util_test) -target_sources(util_test PRIVATE expected_test.cc formatter_test.cc config_test.cc) +target_sources(util_test PRIVATE expected_test.cc formatter_test.cc config_test.cc + visit_type_test.cc) target_link_libraries(util_test PRIVATE iceberg_static GTest::gtest_main GTest::gmock) add_test(NAME util_test COMMAND util_test) diff --git a/test/type_test.cc b/test/type_test.cc index 607ddd5..c371753 100644 --- a/test/type_test.cc +++ b/test/type_test.cc @@ -88,7 +88,7 @@ TEST_P(TypeTest, StdFormat) { ASSERT_EQ(test_case.repr, std::format("{}", *test_case.type)); } -const static TypeTestCase kPrimitiveTypes[] = { +const static std::array kPrimitiveTypes = {{ { .name = "boolean", .type = std::make_shared(), @@ -201,9 +201,9 @@ const static TypeTestCase kPrimitiveTypes[] = { .primitive = true, .repr = "uuid", }, -}; +}}; -const static TypeTestCase kNestedTypes[] = { +const static std::array kNestedTypes = {{ { .name = "list_int", .type = std::make_shared( @@ -249,7 +249,7 @@ const static TypeTestCase kNestedTypes[] = { bar (2): string (optional) >)", }, -}; +}}; INSTANTIATE_TEST_SUITE_P(Primitive, TypeTest, ::testing::ValuesIn(kPrimitiveTypes), TypeTestCaseToString); diff --git a/test/visit_type_test.cc b/test/visit_type_test.cc new file mode 100644 index 0000000..2215ae5 --- /dev/null +++ b/test/visit_type_test.cc @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +#include "iceberg/util/visit_type.h" + +#include + +#include +#include +#include + +#include "gmock/gmock.h" +#include "iceberg/result.h" +#include "matchers.h" + +namespace iceberg { + +namespace { + +class TypeNameVisitor { + public: + Status Visit(const Type& type, std::ostringstream& oss) { + oss << type.ToString(); + return {}; + } +}; + +struct TypeTestCase { + /// Test case name, must be safe for Googletest (alphanumeric + underscore) + std::string name; + std::shared_ptr type; + iceberg::TypeId type_id; + bool primitive; + std::string repr; +}; + +std::string TypeTestCaseToString(const ::testing::TestParamInfo& info) { + return info.param.name; +} + +const static std::array kPrimitiveTypes = {{ + { + .name = "boolean", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kBoolean, + .primitive = true, + .repr = "boolean", + }, + { + .name = "int", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kInt, + .primitive = true, + .repr = "int", + }, + { + .name = "long", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kLong, + .primitive = true, + .repr = "long", + }, + { + .name = "float", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kFloat, + .primitive = true, + .repr = "float", + }, + { + .name = "double", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kDouble, + .primitive = true, + .repr = "double", + }, + { + .name = "decimal9_2", + .type = std::make_shared(9, 2), + .type_id = iceberg::TypeId::kDecimal, + .primitive = true, + .repr = "decimal(9, 2)", + }, + { + .name = "decimal38_10", + .type = std::make_shared(38, 10), + .type_id = iceberg::TypeId::kDecimal, + .primitive = true, + .repr = "decimal(38, 10)", + }, + { + .name = "date", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kDate, + .primitive = true, + .repr = "date", + }, + { + .name = "time", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kTime, + .primitive = true, + .repr = "time", + }, + { + .name = "timestamp", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kTimestamp, + .primitive = true, + .repr = "timestamp", + }, + { + .name = "timestamptz", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kTimestampTz, + .primitive = true, + .repr = "timestamptz", + }, + { + .name = "binary", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kBinary, + .primitive = true, + .repr = "binary", + }, + { + .name = "string", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kString, + .primitive = true, + .repr = "string", + }, + { + .name = "fixed10", + .type = std::make_shared(10), + .type_id = iceberg::TypeId::kFixed, + .primitive = true, + .repr = "fixed(10)", + }, + { + .name = "fixed255", + .type = std::make_shared(255), + .type_id = iceberg::TypeId::kFixed, + .primitive = true, + .repr = "fixed(255)", + }, + { + .name = "uuid", + .type = std::make_shared(), + .type_id = iceberg::TypeId::kUuid, + .primitive = true, + .repr = "uuid", + }, +}}; + +const static std::array kNestedTypes = {{ + { + .name = "list_int", + .type = std::make_shared( + 1, std::make_shared(), true), + .type_id = iceberg::TypeId::kList, + .primitive = false, + .repr = "list", + }, + { + .name = "list_list_int", + .type = std::make_shared( + 1, + std::make_shared(2, std::make_shared(), + true), + false), + .type_id = iceberg::TypeId::kList, + .primitive = false, + .repr = "list (required)>", + }, + { + .name = "map_int_string", + .type = std::make_shared( + iceberg::SchemaField::MakeRequired(1, "key", + std::make_shared()), + iceberg::SchemaField::MakeRequired(2, "value", + std::make_shared())), + .type_id = iceberg::TypeId::kMap, + .primitive = false, + .repr = "map", + }, + { + .name = "struct", + .type = std::make_shared(std::vector{ + iceberg::SchemaField::MakeRequired(1, "foo", + std::make_shared()), + iceberg::SchemaField::MakeOptional(2, "bar", + std::make_shared()), + }), + .type_id = iceberg::TypeId::kStruct, + .primitive = false, + .repr = R"(struct< + foo (1): long (required) + bar (2): string (optional) +>)", + }, +}}; + +} // namespace + +class TypeTest : public ::testing::TestWithParam {}; + +INSTANTIATE_TEST_SUITE_P(Primitive, TypeTest, ::testing::ValuesIn(kPrimitiveTypes), + TypeTestCaseToString); + +INSTANTIATE_TEST_SUITE_P(Nested, TypeTest, ::testing::ValuesIn(kNestedTypes), + TypeTestCaseToString); + +TEST_P(TypeTest, VisitTypePrintToString) { + TypeNameVisitor visitor; + std::ostringstream oss; + const auto& test_case = GetParam(); + ASSERT_THAT(VisitTypeInline(*test_case.type, &visitor, oss), IsOk()); + ASSERT_EQ(oss.str(), test_case.repr); +} + +TEST_P(TypeTest, VisitTypeReturnNestedTypeId) { + auto visitor = [&](auto&& type) -> Result { + using Type = std::decay_t; + // Check if the type is a nested type + if constexpr (std::is_base_of_v) { + return type.type_id(); + } else { + return NotImplemented("Type is not a nested type"); + } + }; + + const auto& test_case = GetParam(); + auto result = VisitType(*test_case.type, visitor); + + if (test_case.primitive) { + ASSERT_THAT(result, IsError(ErrorKind::kNotImplemented)); + ASSERT_THAT(result, HasErrorMessage("Type is not a nested type")); + } else { + ASSERT_THAT(result, IsOk()); + ASSERT_EQ(result.value(), test_case.type_id); + } +} + +} // namespace iceberg