-
Notifications
You must be signed in to change notification settings - Fork 4.2k
GH-41579: [C++][Python][Parquet] Support reading/writing key-value metadata from/to ColumnChunkMetaData #41580
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 2 commits
9aeb8b1
408270a
008d6cb
d23647e
db6b50d
d920564
5a5c242
4379c74
eb5cfa2
3179912
197ef0f
de099d8
cd5130e
1288d8e
ef4ae5b
807b01f
e5f0b9d
1faf669
25f2a25
c54926c
96fc780
ab5cb56
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 |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ | |
| #include "arrow/util/bit_util.h" | ||
| #include "arrow/util/bitmap_builders.h" | ||
| #include "arrow/util/config.h" | ||
| #include "arrow/util/key_value_metadata.h" | ||
|
|
||
| #include "parquet/column_page.h" | ||
| #include "parquet/column_reader.h" | ||
|
|
@@ -51,6 +52,9 @@ using schema::PrimitiveNode; | |
|
|
||
| namespace test { | ||
|
|
||
| using ::testing::IsNull; | ||
| using ::testing::NotNull; | ||
|
|
||
| // The default size used in most tests. | ||
| const int SMALL_SIZE = 100; | ||
| #ifdef PARQUET_VALGRIND | ||
|
|
@@ -385,6 +389,15 @@ class TestPrimitiveWriter : public PrimitiveTypedTest<TestType> { | |
| return metadata_accessor->encoding_stats(); | ||
| } | ||
|
|
||
| std::shared_ptr<const KeyValueMetadata> metadata_key_value_metadata() { | ||
| // Metadata accessor must be created lazily. | ||
| // This is because the ColumnChunkMetaData semantics dictate the metadata object is | ||
| // complete (no changes to the metadata buffer can be made after instantiation) | ||
| auto metadata_accessor = | ||
| ColumnChunkMetaData::Make(metadata_->contents(), this->descr_); | ||
| return metadata_accessor->key_value_metadata(); | ||
| } | ||
|
|
||
| protected: | ||
| int64_t values_read_; | ||
| // Keep the reader alive as for ByteArray the lifetime of the ByteArray | ||
|
|
@@ -1705,5 +1718,46 @@ TEST(TestColumnWriter, WriteDataPageV2HeaderNullCount) { | |
| } | ||
| } | ||
|
|
||
| using TestInt32Writer = TestPrimitiveWriter<Int32Type>; | ||
|
|
||
| TEST_F(TestInt32Writer, NoWriteKeyValueMetadata) { | ||
| auto writer = this->BuildWriter(); | ||
| writer->Close(); | ||
| auto key_value_metadata = metadata_key_value_metadata(); | ||
| ASSERT_THAT(key_value_metadata, IsNull()); | ||
| } | ||
|
|
||
| TEST_F(TestInt32Writer, WriteKeyValueMetadata) { | ||
| auto writer = this->BuildWriter(); | ||
| writer->key_value_metadata().Append("hello", "world"); | ||
| writer->Close(); | ||
| auto key_value_metadata = metadata_key_value_metadata(); | ||
| ASSERT_THAT(key_value_metadata, NotNull()); | ||
| ASSERT_THAT(key_value_metadata->size(), 1); | ||
| ASSERT_OK_AND_ASSIGN(auto value, key_value_metadata->Get("hello")); | ||
| ASSERT_THAT(value, "world"); | ||
|
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. Should we use
Contributor
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'm against using ASSERT_EQ because the direction is reversed, e.g.,
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. I'm ok with |
||
| } | ||
|
|
||
| TEST_F(TestInt32Writer, WriteKeyValueMetadataEndToEnd) { | ||
| auto sink = CreateOutputStream(); | ||
| { | ||
| auto file_writer = ParquetFileWriter::Open( | ||
| sink, std::dynamic_pointer_cast<schema::GroupNode>(schema_.schema_root())); | ||
| auto rg_writer = file_writer->AppendRowGroup(); | ||
| auto col_writer = rg_writer->NextColumn(); | ||
| col_writer->key_value_metadata().Append("foo", "bar"); | ||
| file_writer->Close(); | ||
| } | ||
| ASSERT_OK_AND_ASSIGN(auto buffer, sink->Finish()); | ||
| auto file_reader = | ||
| ParquetFileReader::Open(std::make_shared<::arrow::io::BufferReader>(buffer)); | ||
| auto key_value_metadata = | ||
| file_reader->metadata()->RowGroup(0)->ColumnChunk(0)->key_value_metadata(); | ||
| ASSERT_THAT(key_value_metadata, NotNull()); | ||
| ASSERT_THAT(key_value_metadata->size(), 1); | ||
| ASSERT_OK_AND_ASSIGN(auto value, key_value_metadata->Get("foo")); | ||
| ASSERT_THAT(value, "bar"); | ||
|
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. Same question here. |
||
| } | ||
|
|
||
| } // namespace test | ||
| } // namespace parquet | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -132,6 +132,31 @@ std::shared_ptr<Statistics> MakeColumnStats(const format::ColumnMetaData& meta_d | |
| throw ParquetException("Can't decode page statistics for selected column type"); | ||
| } | ||
|
|
||
| template <typename Metadata> | ||
| std::shared_ptr<KeyValueMetadata> CopyKeyValueMetadata(const Metadata& source) { | ||
|
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. Perhaps call this |
||
| std::shared_ptr<KeyValueMetadata> metadata = nullptr; | ||
| if (source.__isset.key_value_metadata) { | ||
| metadata = std::make_shared<KeyValueMetadata>(); | ||
| for (const auto& it : source.key_value_metadata) { | ||
| metadata->Append(it.key, it.value); | ||
| } | ||
|
mapleFU marked this conversation as resolved.
Outdated
|
||
| } | ||
| return metadata; | ||
| } | ||
|
|
||
| template <typename Metadata> | ||
| void SetKeyValueMetadata(Metadata& metadata, const KeyValueMetadata& source) { | ||
| metadata.key_value_metadata.clear(); | ||
| metadata.key_value_metadata.reserve(static_cast<size_t>(source.size())); | ||
| for (int64_t i = 0; i < source.size(); ++i) { | ||
| format::KeyValue kv_pair; | ||
| kv_pair.__set_key(source.key(i)); | ||
| kv_pair.__set_value(source.value(i)); | ||
| metadata.key_value_metadata.push_back(kv_pair); | ||
| } | ||
| metadata.__isset.key_value_metadata = true; | ||
|
clee704 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // MetaData Accessor | ||
|
|
||
| // ColumnCryptoMetaData | ||
|
|
@@ -230,6 +255,7 @@ class ColumnChunkMetaData::ColumnChunkMetaDataImpl { | |
| encoding_stats.count}); | ||
| } | ||
| possible_stats_ = nullptr; | ||
| InitKeyValueMetadata(); | ||
| } | ||
|
|
||
| bool Equals(const ColumnChunkMetaDataImpl& other) const { | ||
|
|
@@ -340,7 +366,15 @@ class ColumnChunkMetaData::ColumnChunkMetaDataImpl { | |
| return std::nullopt; | ||
| } | ||
|
|
||
| const std::shared_ptr<const KeyValueMetadata>& key_value_metadata() const { | ||
| return key_value_metadata_; | ||
| } | ||
|
|
||
| private: | ||
| void InitKeyValueMetadata() { | ||
| key_value_metadata_ = CopyKeyValueMetadata(*column_metadata_); | ||
| } | ||
|
|
||
| mutable std::shared_ptr<Statistics> possible_stats_; | ||
| std::vector<Encoding::type> encodings_; | ||
| std::vector<PageEncodingStats> encoding_stats_; | ||
|
|
@@ -350,6 +384,7 @@ class ColumnChunkMetaData::ColumnChunkMetaDataImpl { | |
| const ColumnDescriptor* descr_; | ||
| const ReaderProperties properties_; | ||
| const ApplicationVersion* writer_version_; | ||
| std::shared_ptr<const KeyValueMetadata> key_value_metadata_; | ||
| }; | ||
|
|
||
| std::unique_ptr<ColumnChunkMetaData> ColumnChunkMetaData::Make( | ||
|
|
@@ -468,6 +503,11 @@ bool ColumnChunkMetaData::Equals(const ColumnChunkMetaData& other) const { | |
| return impl_->Equals(*other.impl_); | ||
| } | ||
|
|
||
| const std::shared_ptr<const KeyValueMetadata>& ColumnChunkMetaData::key_value_metadata() | ||
| const { | ||
| return impl_->key_value_metadata(); | ||
| } | ||
|
|
||
| // row-group metadata | ||
| class RowGroupMetaData::RowGroupMetaDataImpl { | ||
| public: | ||
|
|
@@ -863,16 +903,7 @@ class FileMetaData::FileMetaDataImpl { | |
| schema_.updateColumnOrders(column_orders); | ||
| } | ||
|
|
||
| void InitKeyValueMetadata() { | ||
| std::shared_ptr<KeyValueMetadata> metadata = nullptr; | ||
| if (metadata_->__isset.key_value_metadata) { | ||
| metadata = std::make_shared<KeyValueMetadata>(); | ||
| for (const auto& it : metadata_->key_value_metadata) { | ||
| metadata->Append(it.key, it.value); | ||
| } | ||
| } | ||
| key_value_metadata_ = std::move(metadata); | ||
| } | ||
| void InitKeyValueMetadata() { key_value_metadata_ = CopyKeyValueMetadata(*metadata_); } | ||
| }; | ||
|
|
||
| std::shared_ptr<FileMetaData> FileMetaData::Make( | ||
|
|
@@ -1518,6 +1549,10 @@ class ColumnChunkMetaDataBuilder::ColumnChunkMetaDataBuilderImpl { | |
| column_chunk_->meta_data.__set_encodings(std::move(thrift_encodings)); | ||
| column_chunk_->meta_data.__set_encoding_stats(std::move(thrift_encoding_stats)); | ||
|
|
||
| if (key_value_metadata_) { | ||
| SetKeyValueMetadata(column_chunk_->meta_data, *key_value_metadata_); | ||
| } | ||
|
|
||
| const auto& encrypt_md = | ||
| properties_->column_encryption_properties(column_->path()->ToDotString()); | ||
| // column is encrypted | ||
|
|
@@ -1584,6 +1619,13 @@ class ColumnChunkMetaDataBuilder::ColumnChunkMetaDataBuilderImpl { | |
| return column_chunk_->meta_data.total_compressed_size; | ||
| } | ||
|
|
||
| KeyValueMetadata& key_value_metadata() { | ||
| if (!key_value_metadata_) { | ||
| key_value_metadata_ = std::make_unique<KeyValueMetadata>(); | ||
| } | ||
| return *key_value_metadata_; | ||
|
clee704 marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| private: | ||
| void Init(format::ColumnChunk* column_chunk) { | ||
| column_chunk_ = column_chunk; | ||
|
|
@@ -1598,6 +1640,7 @@ class ColumnChunkMetaDataBuilder::ColumnChunkMetaDataBuilderImpl { | |
| std::unique_ptr<format::ColumnChunk> owned_column_chunk_; | ||
| const std::shared_ptr<WriterProperties> properties_; | ||
| const ColumnDescriptor* column_; | ||
| std::unique_ptr<KeyValueMetadata> key_value_metadata_; | ||
| }; | ||
|
|
||
| std::unique_ptr<ColumnChunkMetaDataBuilder> ColumnChunkMetaDataBuilder::Make( | ||
|
|
@@ -1659,6 +1702,10 @@ int64_t ColumnChunkMetaDataBuilder::total_compressed_size() const { | |
| return impl_->total_compressed_size(); | ||
| } | ||
|
|
||
| KeyValueMetadata& ColumnChunkMetaDataBuilder::key_value_metadata() { | ||
| return impl_->key_value_metadata(); | ||
| } | ||
|
|
||
| class RowGroupMetaDataBuilder::RowGroupMetaDataBuilderImpl { | ||
| public: | ||
| explicit RowGroupMetaDataBuilderImpl(std::shared_ptr<WriterProperties> props, | ||
|
|
@@ -1853,16 +1900,7 @@ class FileMetaDataBuilder::FileMetaDataBuilderImpl { | |
| } else if (key_value_metadata) { | ||
| key_value_metadata_ = key_value_metadata_->Merge(*key_value_metadata); | ||
| } | ||
| metadata_->key_value_metadata.clear(); | ||
| metadata_->key_value_metadata.reserve( | ||
| static_cast<size_t>(key_value_metadata_->size())); | ||
| for (int64_t i = 0; i < key_value_metadata_->size(); ++i) { | ||
| format::KeyValue kv_pair; | ||
| kv_pair.__set_key(key_value_metadata_->key(i)); | ||
| kv_pair.__set_value(key_value_metadata_->value(i)); | ||
| metadata_->key_value_metadata.push_back(std::move(kv_pair)); | ||
| } | ||
| metadata_->__isset.key_value_metadata = true; | ||
| SetKeyValueMetadata(*metadata_, *key_value_metadata_); | ||
| } | ||
|
|
||
| int32_t file_version = 0; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,25 @@ void PrintPageEncodingStats(std::ostream& stream, | |
| // the fixed initial size is just for an example | ||
| #define COL_WIDTH 30 | ||
|
|
||
| void Put(std::ostream& stream, char c, int n) { | ||
|
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. Can you give this a more distinctive name, such as
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. done |
||
| for (int i = 0; i < n; ++i) { | ||
| stream.put(c); | ||
| } | ||
| } | ||
|
|
||
| void PrintKeyValueMetadata(std::ostream& stream, | ||
| const KeyValueMetadata& key_value_metadata, | ||
| int indent_level = 0, int indent_width = 1) { | ||
|
clee704 marked this conversation as resolved.
|
||
| const int64_t size_of_key_value_metadata = key_value_metadata.size(); | ||
| Put(stream, ' ', indent_level * indent_width); | ||
| stream << "Key Value Metadata: " << size_of_key_value_metadata << " entries\n"; | ||
| for (int64_t i = 0; i < size_of_key_value_metadata; i++) { | ||
| Put(stream, ' ', (indent_level + 1) * indent_width); | ||
| stream << "Key nr " << i << " " << key_value_metadata.key(i) << ": " | ||
| << key_value_metadata.value(i) << "\n"; | ||
| } | ||
| } | ||
|
|
||
| void ParquetFilePrinter::DebugPrint(std::ostream& stream, std::list<int> selected_columns, | ||
| bool print_values, bool format_dump, | ||
| bool print_key_value_metadata, const char* filename) { | ||
|
|
@@ -76,12 +95,7 @@ void ParquetFilePrinter::DebugPrint(std::ostream& stream, std::list<int> selecte | |
|
|
||
| if (print_key_value_metadata && file_metadata->key_value_metadata()) { | ||
| auto key_value_metadata = file_metadata->key_value_metadata(); | ||
| int64_t size_of_key_value_metadata = key_value_metadata->size(); | ||
| stream << "Key Value File Metadata: " << size_of_key_value_metadata << " entries\n"; | ||
| for (int64_t i = 0; i < size_of_key_value_metadata; i++) { | ||
| stream << " Key nr " << i << " " << key_value_metadata->key(i) << ": " | ||
| << key_value_metadata->value(i) << "\n"; | ||
| } | ||
| PrintKeyValueMetadata(stream, *key_value_metadata); | ||
| } | ||
|
|
||
| stream << "Number of RowGroups: " << file_metadata->num_row_groups() << "\n"; | ||
|
|
@@ -136,7 +150,11 @@ void ParquetFilePrinter::DebugPrint(std::ostream& stream, std::list<int> selecte | |
| std::shared_ptr<Statistics> stats = column_chunk->statistics(); | ||
|
|
||
| const ColumnDescriptor* descr = file_metadata->schema()->Column(i); | ||
| stream << "Column " << i << std::endl << " Values: " << column_chunk->num_values(); | ||
| stream << "Column " << i << std::endl; | ||
| if (print_key_value_metadata && column_chunk->key_value_metadata()) { | ||
| PrintKeyValueMetadata(stream, *column_chunk->key_value_metadata(), 1, 2); | ||
| } | ||
| stream << " Values: " << column_chunk->num_values(); | ||
| if (column_chunk->is_stats_set()) { | ||
| std::string min = stats->EncodeMin(), max = stats->EncodeMax(); | ||
| stream << ", Null Values: " << stats->null_count() | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.