This is an automated email from the ASF dual-hosted git repository.
pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git
The following commit(s) were added to refs/heads/main by this push:
new bffbbe7428 GH-51673: [C++][Parquet] Skip missing min/max in metadata
printer (#51705)
bffbbe7428 is described below
commit bffbbe7428216adde296edbcea43ed6a5708a45c
Author: 赵家贝 <[email protected]>
AuthorDate: Wed Oct 7 20:09:56 2026 +0800
GH-51673: [C++][Parquet] Skip missing min/max in metadata printer (#51705)
### Rationale for this change
Parquet column statistics can contain a null count without min/max values,
for example when every value in a column chunk is null.
`ParquetFilePrinter::DebugPrint` currently formats min and max whenever any
statistics are present. For a DECIMAL stored as `FIXED_LEN_BYTE_ARRAY`,
formatting the missing max passes an empty byte array to
`Decimal256::FromBigEndian` and makes `parquet-reader --only-metadata` fail.
Closes #51673.
### What changes are included in this PR?
- Print min and max only when their respective encoded statistics are
present. Keep the existing output for columns with both values.
- Add a self-contained reader test that writes an all-null DECIMAL column,
verifies that null-count statistics exist without min/max, and checks that the
text printer omits both values.
### Are these changes tested?
- `parquet-reader-test
--gtest_filter=TestDebugPrintWithMemoryFile.AllNullDecimalStatistics:TestAllTypesPlain.DebugPrintWorks`
- `parquet-reader --only-metadata` against the Open Prices `prices.parquet`
file referenced in the issue: all 313 row groups print successfully.
### Are there any user-facing changes?
`parquet-reader --only-metadata` now prints valid files with all-null
DECIMAL column chunks without an error. Missing min/max values are omitted
instead of being formatted.
### Was AI used for this PR?
In accordance with the [AI generation
guidelines](https://arrow.apache.org/docs/dev/developers/overview.html#ai-generated-code),
please disclose below whether and how AI was used in this PR.
**PR code and description written by:**
- [ ] Human
- [x] AI
**Reviewed before submission by:**
- [x] Human
- [x] AI
- [ ] Not reviewed
AI assisted with the code, test, and PR description. The issue was
reproduced on the public Parquet file and investigated at the throw site with
LLDB.
* GitHub Issue: #51673
Lead-authored-by: 赵家贝 <[email protected]>
Co-authored-by: 赵家贝 <[email protected]>
Signed-off-by: Antoine Pitrou <[email protected]>
---
cpp/src/parquet/printer.cc | 39 +++++++++++++++++++++++++--------------
cpp/src/parquet/reader_test.cc | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 59 insertions(+), 14 deletions(-)
diff --git a/cpp/src/parquet/printer.cc b/cpp/src/parquet/printer.cc
index cf4decb32f..996ae0e0a9 100644
--- a/cpp/src/parquet/printer.cc
+++ b/cpp/src/parquet/printer.cc
@@ -167,21 +167,32 @@ void ParquetFilePrinter::DebugPrint(std::ostream& stream,
std::list<int> selecte
}
stream << " Values: " << column_chunk->num_values();
if (column_chunk->is_stats_set()) {
- std::string min = stats->min(), max = stats->max();
- std::string max_exact =
- stats->is_max_value_exact.has_value()
- ? (stats->is_max_value_exact.value() ? "true" : "false")
- : "unknown";
- std::string min_exact =
- stats->is_min_value_exact.has_value()
- ? (stats->is_min_value_exact.value() ? "true" : "false")
- : "unknown";
stream << ", Null Values: " << stats->null_count
- << ", Distinct Values: " << stats->distinct_count << std::endl
- << " Max (exact: " << max_exact << "): "
- << FormatStatValue(descr->physical_type(), max,
descr->logical_type())
- << ", Min (exact: " << min_exact << "): "
- << FormatStatValue(descr->physical_type(), min,
descr->logical_type());
+ << ", Distinct Values: " << stats->distinct_count;
+ if (stats->has_max || stats->has_min) {
+ stream << std::endl << " ";
+ if (stats->has_max) {
+ std::string max_exact =
+ stats->is_max_value_exact.has_value()
+ ? (stats->is_max_value_exact.value() ? "true" : "false")
+ : "unknown";
+ stream << "Max (exact: " << max_exact << "): "
+ << FormatStatValue(descr->physical_type(), stats->max(),
+ descr->logical_type());
+ }
+ if (stats->has_min) {
+ std::string min_exact =
+ stats->is_min_value_exact.has_value()
+ ? (stats->is_min_value_exact.value() ? "true" : "false")
+ : "unknown";
+ if (stats->has_max) {
+ stream << ", ";
+ }
+ stream << "Min (exact: " << min_exact << "): "
+ << FormatStatValue(descr->physical_type(), stats->min(),
+ descr->logical_type());
+ }
+ }
} else {
stream << " Statistics Not Set";
}
diff --git a/cpp/src/parquet/reader_test.cc b/cpp/src/parquet/reader_test.cc
index 05ce298487..588339e1e6 100644
--- a/cpp/src/parquet/reader_test.cc
+++ b/cpp/src/parquet/reader_test.cc
@@ -568,6 +568,40 @@ TEST_F(TestAllTypesPlain, DebugPrintWorks) {
ASSERT_GT(result.size(), 0);
}
+// GH-51673: An all-null DECIMAL column has a null count but no min/max.
+// The printer must not try to decode missing min/max values.
+TEST(TestDebugPrintWithMemoryFile, AllNullDecimalStatistics) {
+ auto schema = std::static_pointer_cast<GroupNode>(GroupNode::Make(
+ "schema", Repetition::REQUIRED,
+ {PrimitiveNode::Make("decimal", Repetition::OPTIONAL,
LogicalType::Decimal(10, 3),
+ Type::FIXED_LEN_BYTE_ARRAY, 5)}));
+ ASSERT_OK_AND_ASSIGN(auto output, ::arrow::io::BufferOutputStream::Create());
+ auto writer = ParquetFileWriter::Open(output, schema);
+ auto row_group = writer->AppendRowGroup();
+ auto column = static_cast<FixedLenByteArrayWriter*>(row_group->NextColumn());
+ const std::vector<int16_t> definition_levels(3, 0);
+ column->WriteBatch(definition_levels.size(), definition_levels.data(),
nullptr,
+ nullptr);
+ row_group->Close();
+ writer->Close();
+
+ ASSERT_OK_AND_ASSIGN(auto buffer, output->Finish());
+ auto reader =
+
ParquetFileReader::Open(std::make_shared<::arrow::io::BufferReader>(buffer));
+ auto stats =
reader->metadata()->RowGroup(0)->ColumnChunk(0)->encoded_statistics();
+ ASSERT_NE(stats, nullptr);
+ ASSERT_TRUE(stats->has_null_count);
+ ASSERT_FALSE(stats->has_min);
+ ASSERT_FALSE(stats->has_max);
+
+ std::stringstream stream;
+ ParquetFilePrinter(reader.get()).DebugPrint(stream, {}, false);
+ const std::string result = stream.str();
+ EXPECT_THAT(result, testing::HasSubstr("Values: 3, Null Values: 3"));
+ EXPECT_THAT(result, testing::Not(testing::HasSubstr("Max (exact:")));
+ EXPECT_THAT(result, testing::Not(testing::HasSubstr("Min (exact:")));
+}
+
TEST_F(TestAllTypesPlain, ColumnSelection) {
std::stringstream ss;