Skip to content

Commit 7c4f15c

Browse files
authored
feat: Support UUID values across row and file IO (#790)
closes #314
1 parent 61400e8 commit 7c4f15c

9 files changed

Lines changed: 399 additions & 7 deletions

File tree

src/iceberg/avro/avro_data_util.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "iceberg/avro/avro_schema_util_internal.h"
3636
#include "iceberg/metadata_columns.h"
3737
#include "iceberg/schema.h"
38+
#include "iceberg/schema_internal.h"
3839
#include "iceberg/schema_util.h"
3940
#include "iceberg/util/checked_cast.h"
4041
#include "iceberg/util/macros.h"
@@ -620,7 +621,9 @@ Status ExtractDatumFromArray(const ::arrow::Array& array, int64_t index,
620621
}
621622

622623
case ::arrow::Type::EXTENSION: {
623-
if (array.type()->name() == "arrow.uuid") {
624+
const auto& extension_type =
625+
internal::checked_cast<const ::arrow::ExtensionType&>(*array.type());
626+
if (extension_type.extension_name() == kArrowUuidExtensionName) {
624627
const auto& extension_array =
625628
internal::checked_cast<const ::arrow::ExtensionArray&>(array);
626629
const auto& fixed_array =
@@ -632,7 +635,8 @@ Status ExtractDatumFromArray(const ::arrow::Array& array, int64_t index,
632635
return {};
633636
}
634637

635-
return NotSupported("Unsupported Arrow extension type: {}", array.type()->name());
638+
return NotSupported("Unsupported Arrow extension type: {}",
639+
extension_type.extension_name());
636640
}
637641

638642
case ::arrow::Type::STRUCT: {

src/iceberg/avro/avro_reader.cc

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
#include "iceberg/avro/avro_reader.h"
2121

2222
#include <memory>
23+
#include <vector>
2324

2425
#include <arrow/array/builder_base.h>
2526
#include <arrow/c/bridge.h>
27+
#include <arrow/extension_type.h>
2628
#include <arrow/filesystem/filesystem.h>
2729
#include <arrow/record_batch.h>
2830
#include <arrow/result.h>
@@ -42,6 +44,7 @@
4244
#include "iceberg/metadata_columns.h"
4345
#include "iceberg/name_mapping.h"
4446
#include "iceberg/schema_internal.h"
47+
#include "iceberg/util/checked_cast.h"
4548
#include "iceberg/util/macros.h"
4649

4750
namespace iceberg::avro {
@@ -209,6 +212,63 @@ struct ReadContext {
209212
std::shared_ptr<::arrow::ArrayBuilder> builder_;
210213
};
211214

215+
std::shared_ptr<::arrow::DataType> StorageTypeForBuilder(
216+
const std::shared_ptr<::arrow::DataType>& type);
217+
218+
std::shared_ptr<::arrow::Field> StorageFieldForBuilder(
219+
const std::shared_ptr<::arrow::Field>& field) {
220+
return ::arrow::field(field->name(), StorageTypeForBuilder(field->type()),
221+
field->nullable(), field->metadata());
222+
}
223+
224+
// Arrow cannot construct builders for arrow.uuid extension arrays yet, so build
225+
// with the UUID storage type while keeping the public schema unchanged.
226+
std::shared_ptr<::arrow::DataType> StorageTypeForBuilder(
227+
const std::shared_ptr<::arrow::DataType>& type) {
228+
switch (type->id()) {
229+
case ::arrow::Type::EXTENSION: {
230+
const auto& extension_type =
231+
internal::checked_cast<const ::arrow::ExtensionType&>(*type);
232+
if (extension_type.extension_name() == kArrowUuidExtensionName) {
233+
return extension_type.storage_type();
234+
}
235+
return type;
236+
}
237+
case ::arrow::Type::STRUCT: {
238+
const auto& struct_type = internal::checked_cast<const ::arrow::StructType&>(*type);
239+
std::vector<std::shared_ptr<::arrow::Field>> fields;
240+
fields.reserve(struct_type.num_fields());
241+
for (const auto& field : struct_type.fields()) {
242+
fields.emplace_back(StorageFieldForBuilder(field));
243+
}
244+
return ::arrow::struct_(std::move(fields));
245+
}
246+
case ::arrow::Type::LIST: {
247+
const auto& list_type = internal::checked_cast<const ::arrow::ListType&>(*type);
248+
return ::arrow::list(StorageFieldForBuilder(list_type.value_field()));
249+
}
250+
case ::arrow::Type::LARGE_LIST: {
251+
const auto& list_type =
252+
internal::checked_cast<const ::arrow::LargeListType&>(*type);
253+
return ::arrow::large_list(StorageFieldForBuilder(list_type.value_field()));
254+
}
255+
case ::arrow::Type::FIXED_SIZE_LIST: {
256+
const auto& list_type =
257+
internal::checked_cast<const ::arrow::FixedSizeListType&>(*type);
258+
return ::arrow::fixed_size_list(StorageFieldForBuilder(list_type.value_field()),
259+
list_type.list_size());
260+
}
261+
case ::arrow::Type::MAP: {
262+
const auto& map_type = internal::checked_cast<const ::arrow::MapType&>(*type);
263+
return ::arrow::map(StorageTypeForBuilder(map_type.key_type()),
264+
StorageFieldForBuilder(map_type.item_field()),
265+
map_type.keys_sorted());
266+
}
267+
default:
268+
return type;
269+
}
270+
}
271+
212272
} // namespace
213273

214274
// TODO(gang.wu): collect basic reader metrics
@@ -349,7 +409,8 @@ class AvroReader::Impl {
349409
context_->arrow_schema_ = import_result.MoveValueUnsafe();
350410

351411
auto arrow_struct_type =
352-
std::make_shared<::arrow::StructType>(context_->arrow_schema_->fields());
412+
internal::checked_pointer_cast<::arrow::StructType>(StorageTypeForBuilder(
413+
std::make_shared<::arrow::StructType>(context_->arrow_schema_->fields())));
353414
auto builder_result = ::arrow::MakeBuilder(arrow_struct_type);
354415
if (!builder_result.ok()) {
355416
return InvalidSchema("Failed to make the arrow builder: {}",

src/iceberg/parquet/parquet_schema_util.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "iceberg/metadata_columns.h"
2929
#include "iceberg/parquet/parquet_schema_util_internal.h"
3030
#include "iceberg/result.h"
31+
#include "iceberg/schema_internal.h"
3132
#include "iceberg/schema_util_internal.h"
3233
#include "iceberg/util/checked_cast.h"
3334
#include "iceberg/util/formatter.h" // IWYU pragma: keep
@@ -222,7 +223,7 @@ Status ValidateParquetSchemaEvolution(
222223
if (arrow_type->id() == ::arrow::Type::EXTENSION) {
223224
const auto& extension_type =
224225
internal::checked_cast<const ::arrow::ExtensionType&>(*arrow_type);
225-
if (extension_type.extension_name() == "arrow.uuid") {
226+
if (extension_type.extension_name() == kArrowUuidExtensionName) {
226227
return {};
227228
}
228229
}

src/iceberg/row/struct_like.cc

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ Result<Scalar> LiteralToScalar(const Literal& literal) {
6262
return Scalar{
6363
std::string_view(reinterpret_cast<const char*>(bytes.data()), bytes.size())};
6464
}
65+
case TypeId::kUuid: {
66+
const auto& uuid = std::get<Uuid>(literal.value());
67+
const auto& bytes = uuid.bytes();
68+
return Scalar{
69+
std::string_view(reinterpret_cast<const char*>(bytes.data()), bytes.size())};
70+
}
6571
case TypeId::kDecimal:
6672
return Scalar{std::get<Decimal>(literal.value())};
6773
default:
@@ -162,8 +168,14 @@ Result<Literal> StructLikeAccessor::GetLiteral(const StructLike& struct_like) co
162168
const auto& fixed_data = std::get<std::string_view>(scalar);
163169
return Literal::Fixed(std::vector<uint8_t>(fixed_data.cbegin(), fixed_data.cend()));
164170
}
165-
case TypeId::kUuid:
166-
// TODO(gangwu): Implement UUID type
171+
case TypeId::kUuid: {
172+
const auto& uuid_data = std::get<std::string_view>(scalar);
173+
ICEBERG_ASSIGN_OR_RAISE(
174+
auto uuid,
175+
Uuid::FromBytes(std::span<const uint8_t>(
176+
reinterpret_cast<const uint8_t*>(uuid_data.data()), uuid_data.size())));
177+
return Literal::UUID(uuid);
178+
}
167179
default:
168180
return NotSupported("Cannot convert scalar to literal of type {}",
169181
type_->ToString());

src/iceberg/schema_internal.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ namespace {
3737
// Constants for Arrow schema metadata
3838
constexpr const char* kArrowExtensionName = "ARROW:extension:name";
3939
constexpr const char* kArrowExtensionMetadata = "ARROW:extension:metadata";
40-
constexpr const char* kArrowUuidExtensionName = "arrow.uuid";
4140
constexpr int32_t kUnknownFieldId = -1;
4241

4342
Status CheckArrowCompatible(const Type& type) {

src/iceberg/schema_internal.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030

3131
namespace iceberg {
3232

33+
/// \brief Canonical Arrow extension name used for Iceberg UUID values.
34+
inline constexpr const char* kArrowUuidExtensionName = "arrow.uuid";
35+
3336
/// \brief Convert an Iceberg schema to an Arrow schema.
3437
///
3538
/// \param[in] schema The Iceberg schema to convert.

0 commit comments

Comments
 (0)