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;
 

Reply via email to