-
Notifications
You must be signed in to change notification settings - Fork 4.2k
GH-43807: [C++][Python] Add UUID extension type conversion support to/from Parquet #45866
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
a365b61
4cee020
3d1a4ec
dc696cf
300f5eb
af8227c
bd9c9a9
f187586
d11fcd8
f7a965e
b4d0730
2193fd4
a81d5c1
62f92b3
d1b37e8
4bd607b
7529947
807a48f
ced4d6b
d2d98f2
f3894b8
a101c8b
4c66a75
234475e
8e8d5ee
26afc40
4b63ec7
b48f7f5
555ed75
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ | |
|
|
||
| #include "arrow/array.h" | ||
| #include "arrow/extension/json.h" | ||
| #include "arrow/extension/uuid.h" | ||
| #include "arrow/ipc/writer.h" | ||
| #include "arrow/testing/gtest_util.h" | ||
| #include "arrow/type.h" | ||
|
|
@@ -866,7 +867,7 @@ Status ArrowSchemaToParquetMetadata(std::shared_ptr<::arrow::Schema>& arrow_sche | |
| return Status::OK(); | ||
| } | ||
|
|
||
| TEST_F(TestConvertParquetSchema, ParquetSchemaArrowExtensions) { | ||
| TEST_F(TestConvertParquetSchema, ParquetSchemaArrowJsonExtension) { | ||
| std::vector<NodePtr> parquet_fields; | ||
| parquet_fields.push_back(PrimitiveNode::Make( | ||
| "json_1", Repetition::OPTIONAL, ParquetType::BYTE_ARRAY, ConvertedType::JSON)); | ||
|
|
@@ -948,6 +949,68 @@ TEST_F(TestConvertParquetSchema, ParquetSchemaArrowExtensions) { | |
| } | ||
| } | ||
|
|
||
| TEST_F(TestConvertParquetSchema, ParquetSchemaArrowUuidExtension) { | ||
| std::vector<NodePtr> parquet_fields; | ||
| parquet_fields.push_back(PrimitiveNode::Make("uuid", Repetition::OPTIONAL, | ||
| LogicalType::UUID(), | ||
| ParquetType::FIXED_LEN_BYTE_ARRAY, 16)); | ||
|
|
||
| { | ||
| // Parquet file does not contain Arrow schema. | ||
| // By default, field should be treated as fixed_size_binary(16) in Arrow. | ||
| auto arrow_schema = | ||
| ::arrow::schema({::arrow::field("uuid", ::arrow::fixed_size_binary(16), true)}); | ||
| std::shared_ptr<KeyValueMetadata> metadata{}; | ||
| ASSERT_OK(ConvertSchema(parquet_fields, metadata)); | ||
| CheckFlatSchema(arrow_schema); | ||
| } | ||
|
|
||
| { | ||
| // Parquet file does not contain Arrow schema. | ||
| // If Arrow extensions are enabled, field will be interpreted as uuid() | ||
| // extension field. | ||
| ArrowReaderProperties props; | ||
| props.set_arrow_extensions_enabled(true); | ||
| auto arrow_schema = | ||
| ::arrow::schema({::arrow::field("uuid", ::arrow::extension::uuid(), true)}); | ||
|
paleolimbot marked this conversation as resolved.
Outdated
|
||
| std::shared_ptr<KeyValueMetadata> metadata{}; | ||
| ASSERT_OK(ConvertSchema(parquet_fields, metadata, props)); | ||
| CheckFlatSchema(arrow_schema); | ||
| } | ||
|
|
||
| { | ||
| // Parquet file contains Arrow schema. | ||
| // uuid will be interpreted as uuid() field | ||
| ArrowReaderProperties props; | ||
| props.set_arrow_extensions_enabled(false); | ||
| std::shared_ptr<KeyValueMetadata> field_metadata = | ||
|
wgtmac marked this conversation as resolved.
|
||
| ::arrow::key_value_metadata({"foo", "bar"}, {"biz", "baz"}); | ||
| auto arrow_schema = ::arrow::schema( | ||
| {::arrow::field("uuid", ::arrow::extension::uuid(), true, field_metadata)}); | ||
|
paleolimbot marked this conversation as resolved.
Outdated
|
||
|
|
||
| std::shared_ptr<KeyValueMetadata> metadata; | ||
| ASSERT_OK(ArrowSchemaToParquetMetadata(arrow_schema, metadata)); | ||
| ASSERT_OK(ConvertSchema(parquet_fields, metadata, props)); | ||
| CheckFlatSchema(arrow_schema, true /* check_metadata */); | ||
| } | ||
|
|
||
| { | ||
| // Parquet file contains Arrow schema. | ||
| // uuid will be interpreted as uuid() field even though extensions are not enabled. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are below. |
||
| ArrowReaderProperties props; | ||
| props.set_arrow_extensions_enabled(true); | ||
| std::shared_ptr<KeyValueMetadata> field_metadata = | ||
| ::arrow::key_value_metadata({"foo", "bar"}, {"biz", "baz"}); | ||
| auto arrow_schema = ::arrow::schema( | ||
| {::arrow::field("uuid", ::arrow::extension::uuid(), true, field_metadata)}); | ||
|
paleolimbot marked this conversation as resolved.
Outdated
|
||
|
|
||
| std::shared_ptr<KeyValueMetadata> metadata; | ||
| ASSERT_OK(ArrowSchemaToParquetMetadata(arrow_schema, metadata)); | ||
| ASSERT_OK(ConvertSchema(parquet_fields, metadata, props)); | ||
| CheckFlatSchema(arrow_schema, true /* check_metadata */); | ||
| } | ||
| } | ||
|
|
||
| class TestConvertArrowSchema : public ::testing::Test { | ||
| public: | ||
| virtual void SetUp() {} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ | |
| #include <vector> | ||
|
|
||
| #include "arrow/extension/json.h" | ||
| #include "arrow/extension/uuid.h" | ||
| #include "arrow/extension_type.h" | ||
| #include "arrow/io/memory.h" | ||
| #include "arrow/ipc/api.h" | ||
|
|
@@ -434,7 +435,13 @@ Status FieldToNode(const std::string& name, const std::shared_ptr<Field>& field, | |
| type = ParquetType::BYTE_ARRAY; | ||
| logical_type = LogicalType::JSON(); | ||
| break; | ||
| } else if (ext_type->extension_name() == std::string("arrow.uuid")) { | ||
| type = ParquetType::FIXED_LEN_BYTE_ARRAY; | ||
| logical_type = LogicalType::UUID(); | ||
| length = 16; | ||
| break; | ||
| } | ||
|
|
||
| std::shared_ptr<::arrow::Field> storage_field = ::arrow::field( | ||
| name, ext_type->storage_type(), field->nullable(), field->metadata()); | ||
| return FieldToNode(name, storage_field, properties, arrow_properties, out); | ||
|
|
@@ -1053,6 +1060,29 @@ Result<bool> ApplyOriginalMetadata(const Field& origin_field, SchemaField* infer | |
| // inferred_type is arrow::extension::json(arrow::utf8()) | ||
| auto origin_storage_field = origin_field.WithType(ex_type.storage_type()); | ||
|
|
||
| // Apply metadata recursively to storage type | ||
| RETURN_NOT_OK(ApplyOriginalStorageMetadata(*origin_storage_field, inferred)); | ||
| inferred->field = inferred->field->WithType(origin_type); | ||
| } else if (inferred_type->id() == ::arrow::Type::FIXED_SIZE_BINARY && | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These branches are growing longer while they look pretty similar. Not sure if we can refactor it a little bit to look nicer.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I spent quite a bit of time rewriting the logic here...I think it's better than it was? |
||
| ex_type.extension_name() == std::string("arrow.uuid")) { | ||
| // Schema mismatch. | ||
| // | ||
| // Arrow extensions are DISABLED in Parquet. | ||
| // origin_type is ::arrow::extension::uuid() | ||
| // inferred_type is ::arrow::fixed_size_binary() | ||
| // | ||
| // Origin type is restored as Arrow should be considered the source of truth. | ||
| inferred->field = inferred->field->WithType(origin_type); | ||
| RETURN_NOT_OK(ApplyOriginalStorageMetadata(origin_field, inferred)); | ||
| } else if (inferred_type->id() == ::arrow::Type::EXTENSION && | ||
| ex_type.extension_name() == std::string("arrow.uuid")) { | ||
| // Schema match. | ||
| // | ||
| // Arrow extensions are ENABLED in Parquet. | ||
| // origin_type is arrow::extension::uuid() | ||
| // inferred_type is arrow::extension::uuid() | ||
| auto origin_storage_field = origin_field.WithType(ex_type.storage_type()); | ||
|
|
||
| // Apply metadata recursively to storage type | ||
| RETURN_NOT_OK(ApplyOriginalStorageMetadata(*origin_storage_field, inferred)); | ||
| inferred->field = inferred->field->WithType(origin_type); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| #include "parquet/arrow/schema_internal.h" | ||
|
|
||
| #include "arrow/extension/json.h" | ||
| #include "arrow/extension/uuid.h" | ||
| #include "arrow/type.h" | ||
|
|
||
| #include "parquet/properties.h" | ||
|
|
@@ -134,16 +135,22 @@ Result<std::shared_ptr<ArrowType>> FromByteArray( | |
| } | ||
| } | ||
|
|
||
| Result<std::shared_ptr<ArrowType>> FromFLBA(const LogicalType& logical_type, | ||
| int32_t physical_length) { | ||
| Result<std::shared_ptr<ArrowType>> FromFLBA( | ||
| const LogicalType& logical_type, int32_t physical_length, | ||
| const ArrowReaderProperties& reader_properties) { | ||
| switch (logical_type.type()) { | ||
| case LogicalType::Type::DECIMAL: | ||
| return MakeArrowDecimal(logical_type); | ||
| case LogicalType::Type::FLOAT16: | ||
| return ::arrow::float16(); | ||
| case LogicalType::Type::NONE: | ||
| case LogicalType::Type::INTERVAL: | ||
| return ::arrow::fixed_size_binary(physical_length); | ||
| case LogicalType::Type::UUID: | ||
| if (reader_properties.get_arrow_extensions_enabled()) { | ||
| return ::arrow::extension::uuid(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to check |
||
| } | ||
|
|
||
| return ::arrow::fixed_size_binary(physical_length); | ||
| default: | ||
| return Status::NotImplemented("Unhandled logical logical_type ", | ||
|
paleolimbot marked this conversation as resolved.
Outdated
|
||
|
|
@@ -211,7 +218,7 @@ Result<std::shared_ptr<ArrowType>> GetArrowType( | |
| case ParquetType::BYTE_ARRAY: | ||
| return FromByteArray(logical_type, reader_properties); | ||
| case ParquetType::FIXED_LEN_BYTE_ARRAY: | ||
| return FromFLBA(logical_type, type_length); | ||
| return FromFLBA(logical_type, type_length, reader_properties); | ||
| default: { | ||
| // PARQUET-1565: This can occur if the file is corrupt | ||
| return Status::IOError("Invalid physical column type: ", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -703,7 +703,7 @@ cdef class ParquetFragmentScanOptions(FragmentScanOptions): | |
| cache_options : pyarrow.CacheOptions, default None | ||
| Cache options used when pre_buffer is enabled. The default values should | ||
| be good for most use cases. You may want to adjust these for example if | ||
| you have exceptionally high latency to the file system. | ||
| you have exceptionally high latency to the file system. | ||
| thrift_string_size_limit : int, default None | ||
| If not None, override the maximum total string size allocated | ||
| when decoding Thrift structures. The default limit should be | ||
|
|
@@ -720,6 +720,9 @@ cdef class ParquetFragmentScanOptions(FragmentScanOptions): | |
| Parquet file. | ||
| page_checksum_verification : bool, default False | ||
| If True, verify the page checksum for each page read from the file. | ||
| arrow_extensions_enabled : bool, default False | ||
| If True, read Parquet logical types as Arrow Extension Types where possible, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please see comments I posted on this on the Geometry PR. |
||
| (e.g., JSON as arrow.json or UUID as arrow.uuid). | ||
| """ | ||
|
|
||
| # Avoid mistakingly creating attributes | ||
|
|
@@ -733,7 +736,8 @@ cdef class ParquetFragmentScanOptions(FragmentScanOptions): | |
| thrift_container_size_limit=None, | ||
| decryption_config=None, | ||
| decryption_properties=None, | ||
| bint page_checksum_verification=False): | ||
| bint page_checksum_verification=False, | ||
| bint arrow_extensions_enabled=False): | ||
| self.init(shared_ptr[CFragmentScanOptions]( | ||
| new CParquetFragmentScanOptions())) | ||
| self.use_buffered_stream = use_buffered_stream | ||
|
|
@@ -752,6 +756,7 @@ cdef class ParquetFragmentScanOptions(FragmentScanOptions): | |
| if decryption_properties is not None: | ||
| self.decryption_properties = decryption_properties | ||
| self.page_checksum_verification = page_checksum_verification | ||
| self.arrow_extensions_enabled = arrow_extensions_enabled | ||
|
|
||
| cdef void init(self, const shared_ptr[CFragmentScanOptions]& sp): | ||
| FragmentScanOptions.init(self, sp) | ||
|
|
@@ -868,6 +873,14 @@ cdef class ParquetFragmentScanOptions(FragmentScanOptions): | |
| def page_checksum_verification(self, bint page_checksum_verification): | ||
| self.reader_properties().set_page_checksum_verification(page_checksum_verification) | ||
|
|
||
| @property | ||
| def arrow_extensions_enabled(self): | ||
| return self.arrow_reader_properties().get_arrow_extensions_enabled() | ||
|
|
||
| @arrow_extensions_enabled.setter | ||
| def arrow_extensions_enabled(self, bint arrow_extensions_enabled): | ||
| self.arrow_reader_properties().set_arrow_extensions_enabled(arrow_extensions_enabled) | ||
|
|
||
| def equals(self, ParquetFragmentScanOptions other): | ||
| """ | ||
| Parameters | ||
|
|
@@ -881,11 +894,12 @@ cdef class ParquetFragmentScanOptions(FragmentScanOptions): | |
| attrs = ( | ||
| self.use_buffered_stream, self.buffer_size, self.pre_buffer, self.cache_options, | ||
| self.thrift_string_size_limit, self.thrift_container_size_limit, | ||
| self.page_checksum_verification) | ||
| self.page_checksum_verification, self.arrow_extensions_enabled) | ||
| other_attrs = ( | ||
| other.use_buffered_stream, other.buffer_size, other.pre_buffer, other.cache_options, | ||
| other.thrift_string_size_limit, | ||
| other.thrift_container_size_limit, other.page_checksum_verification) | ||
| other.thrift_container_size_limit, other.page_checksum_verification, | ||
| other.arrow_extensions_enabled) | ||
| return attrs == other_attrs | ||
|
|
||
| @staticmethod | ||
|
|
@@ -902,7 +916,8 @@ cdef class ParquetFragmentScanOptions(FragmentScanOptions): | |
| cache_options=self.cache_options, | ||
| thrift_string_size_limit=self.thrift_string_size_limit, | ||
| thrift_container_size_limit=self.thrift_container_size_limit, | ||
| page_checksum_verification=self.page_checksum_verification | ||
| page_checksum_verification=self.page_checksum_verification, | ||
| arrow_extensions_enabled=self.arrow_extensions_enabled | ||
| ) | ||
| return ParquetFragmentScanOptions._reconstruct, (kwargs,) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.