You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by xu...@apache.org on 2025/05/13 05:22:29 UTC
(iceberg-cpp) branch main updated: feat: add visit type support (#94)
This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new a994322 feat: add visit type support (#94)
a994322 is described below
commit a994322bb8b4204d38a566f02a5bf7851d67463d
Author: Junwang Zhao <zh...@gmail.com>
AuthorDate: Tue May 13 13:22:25 2025 +0800
feat: add visit type support (#94)
Add a `TypeId` field to each concrete type to enable macro-based type
visitor support.
---------
Signed-off-by: Junwang Zhao <zh...@gmail.com>
---
LICENSE | 14 ++
NOTICE | 4 +
src/iceberg/CMakeLists.txt | 3 +-
src/iceberg/type.cc | 82 +++++------
src/iceberg/type.h | 30 +++++
src/iceberg/util/checked_cast.h | 66 +++++++++
src/iceberg/util/unreachable.cc | 31 +++++
src/iceberg/util/unreachable.h | 33 +++++
src/iceberg/util/visit_type.h | 126 +++++++++++++++++
src/iceberg/util/visitor_generate.h | 43 ++++++
test/CMakeLists.txt | 3 +-
test/type_test.cc | 8 +-
test/visit_type_test.cc | 261 ++++++++++++++++++++++++++++++++++++
13 files changed, 647 insertions(+), 57 deletions(-)
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<SchemaField> 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> 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<const DecimalType&>(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<const FixedType&>(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<SchemaField> 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 <memory>
+#include <type_traits>
+#include <utility>
+
+namespace iceberg::internal {
+
+template <typename OutputType, typename InputType>
+inline OutputType checked_cast(InputType&& value) {
+ static_assert(
+ std::is_class_v<std::remove_pointer_t<std::remove_reference_t<InputType>>>,
+ "checked_cast input type must be a class");
+ static_assert(
+ std::is_class_v<std::remove_pointer_t<std::remove_reference_t<OutputType>>>,
+ "checked_cast output type must be a class");
+#ifdef NDEBUG
+ return static_cast<OutputType>(value);
+#else
+ return dynamic_cast<OutputType>(value);
+#endif
+}
+
+template <class T, class U>
+std::shared_ptr<T> checked_pointer_cast(std::shared_ptr<U> r) noexcept {
+#ifdef NDEBUG
+ return std::static_pointer_cast<T>(std::move(r));
+#else
+ return std::dynamic_pointer_cast<T>(std::move(r));
+#endif
+}
+
+template <class T, class U>
+std::unique_ptr<T> checked_pointer_cast(std::unique_ptr<U> r) noexcept {
+#ifdef NDEBUG
+ return std::unique_ptr<T>(static_cast<T*>(r.release()));
+#else
+ return std::unique_ptr<T>(dynamic_cast<T*>(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 <cstdlib>
+
+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 <string_view>
+
+#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<const TYPE_CLASS##Type&>(type), \
+ std::forward<ARGS>(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 <typename VISITOR, typename... ARGS>
+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>(visitor)( \
+ internal::checked_cast<const TYPE_CLASS##Type&>(type), \
+ std::forward<ARGS>(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 <typename VISITOR, typename... ARGS>
+inline auto VisitType(const Type& type, VISITOR&& visitor, ARGS&&... args)
+ -> decltype(std::forward<VISITOR>(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>(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 <typename VISITOR, typename... ARGS>
+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<TypeTestCase, 16> kPrimitiveTypes = {{
{
.name = "boolean",
.type = std::make_shared<iceberg::BooleanType>(),
@@ -201,9 +201,9 @@ const static TypeTestCase kPrimitiveTypes[] = {
.primitive = true,
.repr = "uuid",
},
-};
+}};
-const static TypeTestCase kNestedTypes[] = {
+const static std::array<TypeTestCase, 4> kNestedTypes = {{
{
.name = "list_int",
.type = std::make_shared<iceberg::ListType>(
@@ -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 <sstream>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <iceberg/type.h>
+
+#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<iceberg::Type> type;
+ iceberg::TypeId type_id;
+ bool primitive;
+ std::string repr;
+};
+
+std::string TypeTestCaseToString(const ::testing::TestParamInfo<TypeTestCase>& info) {
+ return info.param.name;
+}
+
+const static std::array<TypeTestCase, 16> kPrimitiveTypes = {{
+ {
+ .name = "boolean",
+ .type = std::make_shared<iceberg::BooleanType>(),
+ .type_id = iceberg::TypeId::kBoolean,
+ .primitive = true,
+ .repr = "boolean",
+ },
+ {
+ .name = "int",
+ .type = std::make_shared<iceberg::IntType>(),
+ .type_id = iceberg::TypeId::kInt,
+ .primitive = true,
+ .repr = "int",
+ },
+ {
+ .name = "long",
+ .type = std::make_shared<iceberg::LongType>(),
+ .type_id = iceberg::TypeId::kLong,
+ .primitive = true,
+ .repr = "long",
+ },
+ {
+ .name = "float",
+ .type = std::make_shared<iceberg::FloatType>(),
+ .type_id = iceberg::TypeId::kFloat,
+ .primitive = true,
+ .repr = "float",
+ },
+ {
+ .name = "double",
+ .type = std::make_shared<iceberg::DoubleType>(),
+ .type_id = iceberg::TypeId::kDouble,
+ .primitive = true,
+ .repr = "double",
+ },
+ {
+ .name = "decimal9_2",
+ .type = std::make_shared<iceberg::DecimalType>(9, 2),
+ .type_id = iceberg::TypeId::kDecimal,
+ .primitive = true,
+ .repr = "decimal(9, 2)",
+ },
+ {
+ .name = "decimal38_10",
+ .type = std::make_shared<iceberg::DecimalType>(38, 10),
+ .type_id = iceberg::TypeId::kDecimal,
+ .primitive = true,
+ .repr = "decimal(38, 10)",
+ },
+ {
+ .name = "date",
+ .type = std::make_shared<iceberg::DateType>(),
+ .type_id = iceberg::TypeId::kDate,
+ .primitive = true,
+ .repr = "date",
+ },
+ {
+ .name = "time",
+ .type = std::make_shared<iceberg::TimeType>(),
+ .type_id = iceberg::TypeId::kTime,
+ .primitive = true,
+ .repr = "time",
+ },
+ {
+ .name = "timestamp",
+ .type = std::make_shared<iceberg::TimestampType>(),
+ .type_id = iceberg::TypeId::kTimestamp,
+ .primitive = true,
+ .repr = "timestamp",
+ },
+ {
+ .name = "timestamptz",
+ .type = std::make_shared<iceberg::TimestampTzType>(),
+ .type_id = iceberg::TypeId::kTimestampTz,
+ .primitive = true,
+ .repr = "timestamptz",
+ },
+ {
+ .name = "binary",
+ .type = std::make_shared<iceberg::BinaryType>(),
+ .type_id = iceberg::TypeId::kBinary,
+ .primitive = true,
+ .repr = "binary",
+ },
+ {
+ .name = "string",
+ .type = std::make_shared<iceberg::StringType>(),
+ .type_id = iceberg::TypeId::kString,
+ .primitive = true,
+ .repr = "string",
+ },
+ {
+ .name = "fixed10",
+ .type = std::make_shared<iceberg::FixedType>(10),
+ .type_id = iceberg::TypeId::kFixed,
+ .primitive = true,
+ .repr = "fixed(10)",
+ },
+ {
+ .name = "fixed255",
+ .type = std::make_shared<iceberg::FixedType>(255),
+ .type_id = iceberg::TypeId::kFixed,
+ .primitive = true,
+ .repr = "fixed(255)",
+ },
+ {
+ .name = "uuid",
+ .type = std::make_shared<iceberg::UuidType>(),
+ .type_id = iceberg::TypeId::kUuid,
+ .primitive = true,
+ .repr = "uuid",
+ },
+}};
+
+const static std::array<TypeTestCase, 4> kNestedTypes = {{
+ {
+ .name = "list_int",
+ .type = std::make_shared<iceberg::ListType>(
+ 1, std::make_shared<iceberg::IntType>(), true),
+ .type_id = iceberg::TypeId::kList,
+ .primitive = false,
+ .repr = "list<element (1): int (optional)>",
+ },
+ {
+ .name = "list_list_int",
+ .type = std::make_shared<iceberg::ListType>(
+ 1,
+ std::make_shared<iceberg::ListType>(2, std::make_shared<iceberg::IntType>(),
+ true),
+ false),
+ .type_id = iceberg::TypeId::kList,
+ .primitive = false,
+ .repr = "list<element (1): list<element (2): int (optional)> (required)>",
+ },
+ {
+ .name = "map_int_string",
+ .type = std::make_shared<iceberg::MapType>(
+ iceberg::SchemaField::MakeRequired(1, "key",
+ std::make_shared<iceberg::LongType>()),
+ iceberg::SchemaField::MakeRequired(2, "value",
+ std::make_shared<iceberg::StringType>())),
+ .type_id = iceberg::TypeId::kMap,
+ .primitive = false,
+ .repr = "map<key (1): long (required): value (2): string (required)>",
+ },
+ {
+ .name = "struct",
+ .type = std::make_shared<iceberg::StructType>(std::vector<iceberg::SchemaField>{
+ iceberg::SchemaField::MakeRequired(1, "foo",
+ std::make_shared<iceberg::LongType>()),
+ iceberg::SchemaField::MakeOptional(2, "bar",
+ std::make_shared<iceberg::StringType>()),
+ }),
+ .type_id = iceberg::TypeId::kStruct,
+ .primitive = false,
+ .repr = R"(struct<
+ foo (1): long (required)
+ bar (2): string (optional)
+>)",
+ },
+}};
+
+} // namespace
+
+class TypeTest : public ::testing::TestWithParam<TypeTestCase> {};
+
+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<TypeId> {
+ using Type = std::decay_t<decltype(type)>;
+ // Check if the type is a nested type
+ if constexpr (std::is_base_of_v<NestedType, Type>) {
+ 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