nssalian commented on code in PR #17557:
URL: https://github.com/apache/iceberg/pull/17557#discussion_r3972708677


##########
parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java:
##########
@@ -466,6 +473,122 @@ public void testShreddedStringBoundsAcrossRowGroups() 
throws IOException {
         .isEqualTo(Variants.of(supplementary));
   }
 
+  @Test
+  public void testMissingNullCountAcrossRowGroups() throws IOException {
+    // A variant column chunk may omit null_count in its footer statistics, 
which Parquet reports
+    // as -1. When one row group is missing the count and another has it, the 
total must be
+    // reported as unknown rather than summing the -1 into a lower count.
+    ParquetMetadata footer =
+        footer(Variant.of(EMPTY, Variants.of(1)), null, null); // 1 value, 2 
nulls
+
+    // build a two row group footer: the first as written, the second with 
null_count removed
+    // from the variant sub columns
+    BlockMetaData withCount = footer.getBlocks().get(0);
+    BlockMetaData withoutCount = 
dropVariantNullCounts(footer.getBlocks().get(0));
+    ParquetMetadata twoRowGroups =
+        new ParquetMetadata(footer.getFileMetaData(), 
Lists.newArrayList(withCount, withoutCount));
+
+    Metrics metrics =
+        ParquetUtil.footerMetrics(twoRowGroups, Stream.empty(), 
MetricsConfig.getDefault());
+
+    // the variant column (id 2) null count is unknown because one row group 
did not report it
+    assertThat(metrics.nullValueCounts()).doesNotContainKey(2);
+    assertThat(metrics.valueCounts()).containsEntry(2, 6L);
+  }
+
+  @Test
+  public void testShreddedNullVariantsWithMissingNullCount() throws 
IOException {
+    // For a shredded column where the values are either shredded into 
typed_value or are null
+    // variants, the value column holds only null variants. That count comes 
from the value count,
+    // not the footer null count, so shredded bounds are still trusted even 
when a row group omits
+    // null_count.
+    VariantValue value = Variants.of(1234);
+    ParquetMetadata footer =
+        footer(
+            (id, name) -> ParquetVariantUtil.toParquetSchema(value),
+            Variant.of(EMPTY, value),
+            Variant.of(EMPTY, Variants.ofNull()));
+
+    BlockMetaData withCount = footer.getBlocks().get(0);
+    BlockMetaData withoutCount = 
dropVariantNullCounts(footer.getBlocks().get(0));
+    ParquetMetadata twoRowGroups =
+        new ParquetMetadata(footer.getFileMetaData(), 
Lists.newArrayList(withCount, withoutCount));
+
+    Metrics metrics =
+        ParquetUtil.footerMetrics(twoRowGroups, Stream.empty(), 
MetricsConfig.getDefault());
+
+    // the shredded bounds survive: the all-null value column does not depend 
on footer null counts
+    assertThat(metrics.lowerBounds().get(2))
+        .extracting(b -> Variant.from(b).value().asObject().get(ROOT_FIELD))
+        .isEqualTo(value);
+    assertThat(metrics.upperBounds().get(2))
+        .extracting(b -> Variant.from(b).value().asObject().get(ROOT_FIELD))
+        .isEqualTo(value);
+  }
+
+  /** Rebuilds a row group with null_count removed from the variant column's 
statistics. */
+  private static BlockMetaData dropVariantNullCounts(BlockMetaData block) {
+    BlockMetaData result = new BlockMetaData();
+    result.setRowCount(block.getRowCount());
+    result.setTotalByteSize(block.getTotalByteSize());
+
+    for (ColumnChunkMetaData column : block.getColumns()) {
+      Statistics<?> stats = column.getStatistics();
+      if (column.getPath().toDotString().startsWith("var")) {
+        Statistics.Builder builder = 
Statistics.getBuilderForReading(column.getPrimitiveType());
+        if (stats.hasNonNullValue()) {
+          builder.withMin(stats.getMinBytes()).withMax(stats.getMaxBytes());
+        }
+
+        stats = builder.build(); // built without withNumNulls, so getNumNulls 
returns -1
+      }
+
+      result.addColumn(
+          ColumnChunkMetaData.get(
+              column.getPath(),
+              column.getPrimitiveType(),
+              column.getCodec(),
+              column.getEncodingStats(),
+              column.getEncodings(),
+              stats,
+              column.getFirstDataPageOffset(),
+              column.getDictionaryPageOffset(),
+              column.getValueCount(),
+              column.getTotalSize(),
+              column.getTotalUncompressedSize()));
+    }
+
+    return result;
+  }
+
+  private ParquetMetadata footer(Variant... variants) throws IOException {
+    return footer(null, variants);
+  }
+
+  private ParquetMetadata footer(VariantShreddingFunction shredding, 
Variant... variants)

Review Comment:
   Some de-duping here: footer() repeats the same Parquet.write(out)...build() 
+ write-loop as writeParquetWithMetricsConfig and   
writeParquetWithRowGroupSize. The only difference is the builder option and 
return. A writeVariants() helper could be useful here. 



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to