You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by hu...@apache.org on 2026/09/12 05:33:44 UTC

(iceberg) branch 1.10.x updated: Parquet: Backport variant STRING bounds UTF-8 fix (#17397) to 1.10.x (#17589)

This is an automated email from the ASF dual-hosted git repository.

huaxingao pushed a commit to branch 1.10.x
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/1.10.x by this push:
     new 1285e208f1 Parquet: Backport variant STRING bounds UTF-8 fix (#17397) to 1.10.x (#17589)
1285e208f1 is described below

commit 1285e208f17b49dc8c6fce5ceab3e2b8160bcd4a
Author: vishnu prakash <nv...@gmail.com>
AuthorDate: Sat Sep 12 11:03:37 2026 +0530

    Parquet: Backport variant STRING bounds UTF-8 fix (#17397) to 1.10.x (#17589)
    
    Parquet: Fix variant STRING metrics bounds to use UTF-8 byte order (#17397)
---
 .../apache/iceberg/parquet/ParquetVariantUtil.java | 11 +++--
 .../apache/iceberg/parquet/TestVariantMetrics.java | 53 ++++++++++++++++++++++
 2 files changed, 60 insertions(+), 4 deletions(-)

diff --git a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetVariantUtil.java b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetVariantUtil.java
index a9b088aaf2..9d5dbbba0a 100644
--- a/parquet/src/main/java/org/apache/iceberg/parquet/ParquetVariantUtil.java
+++ b/parquet/src/main/java/org/apache/iceberg/parquet/ParquetVariantUtil.java
@@ -184,10 +184,13 @@ class ParquetVariantUtil {
    */
   @SuppressWarnings("unchecked")
   static <T> Comparator<T> comparator(PhysicalType primitive) {
-    if (primitive == PhysicalType.BINARY) {
-      return (Comparator<T>) Comparators.unsignedBytes();
-    } else {
-      return (Comparator<T>) Comparator.naturalOrder();
+    switch (primitive) {
+      case BINARY:
+        return (Comparator<T>) Comparators.unsignedBytes();
+      case STRING:
+        return (Comparator<T>) Comparators.charSequences();
+      default:
+        return (Comparator<T>) Comparator.naturalOrder();
     }
   }
 
diff --git a/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java b/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java
index c9dcd2b8ee..9cfc0feb41 100644
--- a/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java
+++ b/parquet/src/test/java/org/apache/iceberg/parquet/TestVariantMetrics.java
@@ -18,6 +18,7 @@
  */
 package org.apache.iceberg.parquet;
 
+import static org.apache.iceberg.TableProperties.PARQUET_ROW_GROUP_SIZE_BYTES;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.IOException;
@@ -266,6 +267,33 @@ public class TestVariantMetrics {
         .isNull();
   }
 
+  @Test
+  public void testShreddedStringBoundsAcrossRowGroups() throws IOException {
+    // shredded string bounds must use UTF-8 order (Comparators.charSequences), like the scan path;
+    // String.compareTo (UTF-16) sorts a supplementary char below U+E000 and would invert the bounds
+    String belowSurrogate = new String(Character.toChars(0xE000));
+    String supplementary = new String(Character.toChars(0x10000));
+
+    Variant[] rows = new Variant[300];
+    for (int i = 0; i < rows.length; i += 1) {
+      rows[i] = Variant.of(EMPTY, Variants.of(i < 150 ? belowSurrogate : supplementary));
+    }
+
+    // a tiny row-group size forces multiple row groups so cross-chunk bound aggregation runs
+    Metrics metrics =
+        writeParquetWithRowGroupSize(
+            (id, name) -> ParquetVariantUtil.toParquetSchema(Variants.of(belowSurrogate)),
+            "1",
+            rows);
+
+    assertThat(metrics.lowerBounds().get(2))
+        .extracting(b -> Variant.from(b).value().asObject().get(ROOT_FIELD))
+        .isEqualTo(Variants.of(belowSurrogate));
+    assertThat(metrics.upperBounds().get(2))
+        .extracting(b -> Variant.from(b).value().asObject().get(ROOT_FIELD))
+        .isEqualTo(Variants.of(supplementary));
+  }
+
   @Test
   public void testVariantFloatNaN() throws IOException {
     // NaN values are not counted because there is no ID for FieldMetrics
@@ -552,6 +580,31 @@ public class TestVariantMetrics {
     return writer.metrics();
   }
 
+  private Metrics writeParquetWithRowGroupSize(
+      VariantShreddingFunction shredding, String rowGroupSizeBytes, Variant... variants)
+      throws IOException {
+    OutputFile out = new InMemoryOutputFile();
+    GenericRecord record = GenericRecord.create(SCHEMA);
+
+    FileAppender<Record> writer =
+        Parquet.write(out)
+            .schema(SCHEMA)
+            .variantShreddingFunc(shredding)
+            .set(PARQUET_ROW_GROUP_SIZE_BYTES, rowGroupSizeBytes)
+            .createWriterFunc(fileSchema -> InternalWriter.create(SCHEMA.asStruct(), fileSchema))
+            .build();
+
+    try (writer) {
+      for (int id = 0; id < variants.length; id += 1) {
+        record.setField("id", (long) id);
+        record.setField("var", variants[id]);
+        writer.add(record);
+      }
+    }
+
+    return writer.metrics();
+  }
+
   private static VariantValue increment(VariantValue value) {
     VariantPrimitive<?> primitive = value.asPrimitive();
     switch (value.type()) {