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

pvary pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new 8dfd9de791 Core: Implement Map comparator (#13626)
8dfd9de791 is described below

commit 8dfd9de791ac28d2f349aa016f1846a95576b2c3
Author: pvary <peter.vary.apa...@gmail.com>
AuthorDate: Wed Jul 23 12:56:23 2025 +0200

    Core: Implement Map comparator (#13626)
---
 .../java/org/apache/iceberg/types/Comparators.java | 53 ++++++++++++++++++
 .../org/apache/iceberg/types/TestComparators.java  | 63 ++++++++++++++++++++++
 2 files changed, 116 insertions(+)

diff --git a/api/src/main/java/org/apache/iceberg/types/Comparators.java 
b/api/src/main/java/org/apache/iceberg/types/Comparators.java
index 98416c6943..32168d9a09 100644
--- a/api/src/main/java/org/apache/iceberg/types/Comparators.java
+++ b/api/src/main/java/org/apache/iceberg/types/Comparators.java
@@ -21,9 +21,11 @@ package org.apache.iceberg.types;
 import java.nio.ByteBuffer;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.function.IntFunction;
 import org.apache.iceberg.StructLike;
 import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
+import org.apache.iceberg.relocated.com.google.common.collect.Lists;
 import org.apache.iceberg.util.UnicodeUtil;
 
 public class Comparators {
@@ -56,6 +58,10 @@ public class Comparators {
     return new ListComparator<>(list);
   }
 
+  public static <K, V> Comparator<Map<K, V>> forType(Types.MapType mapType) {
+    return new MapComparator<>(mapType);
+  }
+
   @SuppressWarnings("unchecked")
   public static <T> Comparator<T> forType(Type.PrimitiveType type) {
     Comparator<?> cmp = COMPARATORS.get(type);
@@ -78,6 +84,8 @@ public class Comparators {
       return (Comparator<T>) forType(type.asStructType());
     } else if (type.isListType()) {
       return (Comparator<T>) forType(type.asListType());
+    } else if (type.isMapType()) {
+      return (Comparator<T>) forType(type.asMapType());
     }
 
     throw new UnsupportedOperationException("Cannot determine comparator for 
type: " + type);
@@ -149,6 +157,51 @@ public class Comparators {
     }
   }
 
+  private static class MapComparator<K, V> implements Comparator<Map<K, V>> {
+    private final Comparator<K> keyComparator;
+    private final Comparator<V> valueComparator;
+    private final Comparator<List<K>> keyListComparator;
+
+    private MapComparator(Types.MapType mapType) {
+      this.keyComparator = internal(mapType.keyType());
+      this.valueComparator =
+          mapType.isValueOptional()
+              ? 
Comparators.<V>nullsFirst().thenComparing(internal(mapType.valueType()))
+              : internal(mapType.valueType());
+      this.keyListComparator =
+          internal(Types.ListType.ofRequired(mapType.keyId(), 
mapType.keyType()));
+    }
+
+    @Override
+    public int compare(Map<K, V> o1, Map<K, V> o2) {
+      if (o1 == o2) {
+        return 0;
+      }
+
+      List<K> keys1 = Lists.newArrayList(o1.keySet());
+      List<K> keys2 = Lists.newArrayList(o2.keySet());
+      keys1.sort(keyComparator);
+      keys2.sort(keyComparator);
+
+      int cmp = keyListComparator.compare(keys1, keys2);
+      if (cmp != 0) {
+        return cmp;
+      }
+
+      for (K key : keys1) {
+        V value1 = o1.get(key);
+        V value2 = o2.get(key);
+
+        cmp = valueComparator.compare(value1, value2);
+        if (cmp != 0) {
+          return cmp;
+        }
+      }
+
+      return 0;
+    }
+  }
+
   public static Comparator<ByteBuffer> unsignedBytes() {
     return UnsignedByteBufComparator.INSTANCE;
   }
diff --git a/api/src/test/java/org/apache/iceberg/types/TestComparators.java 
b/api/src/test/java/org/apache/iceberg/types/TestComparators.java
index 07653ba3c8..691e3f04a0 100644
--- a/api/src/test/java/org/apache/iceberg/types/TestComparators.java
+++ b/api/src/test/java/org/apache/iceberg/types/TestComparators.java
@@ -24,9 +24,13 @@ import java.math.BigDecimal;
 import java.nio.ByteBuffer;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Map;
 import java.util.UUID;
+import org.apache.iceberg.StructLike;
 import org.apache.iceberg.TestHelpers;
 import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList;
+import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
+import org.apache.iceberg.relocated.com.google.common.collect.Maps;
 import org.junit.jupiter.api.Test;
 
 public class TestComparators {
@@ -156,4 +160,63 @@ public class TestComparators {
         TestHelpers.Row.of((String) null),
         TestHelpers.Row.of("a"));
   }
+
+  @Test
+  public void testMap() {
+    Comparator<Map<String, Integer>> requiredComparator =
+        Comparators.forType(
+            Types.MapType.ofRequired(18, 19, Types.StringType.get(), 
Types.IntegerType.get()));
+
+    assertComparesCorrectly(
+        requiredComparator, ImmutableMap.of("a", 1, "b", 2), 
ImmutableMap.of("a", 1, "b", 3));
+
+    assertComparesCorrectly(
+        requiredComparator, ImmutableMap.of("a", 1, "b", 2), 
ImmutableMap.of("a", 1, "c", 2));
+
+    assertComparesCorrectly(
+        requiredComparator, ImmutableMap.of("a", 1), ImmutableMap.of("a", 1, 
"b", 2));
+
+    assertComparesCorrectly(
+        requiredComparator,
+        ImmutableMap.of("a", 1, "c", 3, "b", 2),
+        ImmutableMap.of("a", 1, "b", 2, "c", 4));
+
+    Map<String, Integer> mapWithNull = Maps.newHashMapWithExpectedSize(1);
+    mapWithNull.put("a", 1);
+    mapWithNull.put("b", null);
+    mapWithNull.put("c", 2);
+    assertComparesCorrectly(
+        Comparators.forType(
+            Types.MapType.ofOptional(18, 19, Types.StringType.get(), 
Types.IntegerType.get())),
+        mapWithNull,
+        ImmutableMap.of("a", 1, "b", 2, "c", 3));
+  }
+
+  @Test
+  public void testNested() {
+    Comparator<StructLike> comparator =
+        Comparators.forType(
+            Types.StructType.of(
+                Types.NestedField.required(18, "str19", 
Types.StringType.get()),
+                Types.NestedField.required(
+                    19,
+                    "struct",
+                    Types.StructType.of(
+                        Types.NestedField.required(20, "struct_str", 
Types.StringType.get()),
+                        Types.NestedField.required(21, "struct_int", 
Types.IntegerType.get()))),
+                Types.NestedField.required(
+                    22, "list", Types.ListType.ofRequired(23, 
Types.IntegerType.get())),
+                Types.NestedField.required(
+                    24,
+                    "map",
+                    Types.MapType.ofRequired(
+                        25, 26, Types.StringType.get(), 
Types.IntegerType.get()))));
+
+    assertComparesCorrectly(
+        comparator,
+        TestHelpers.Row.of(
+            "a", TestHelpers.Row.of("b", 1), ImmutableList.of(1, 2), 
ImmutableMap.of("c", 3)),
+        TestHelpers.Row.of(
+            "a", TestHelpers.Row.of("b", 1), ImmutableList.of(1, 2), 
ImmutableMap.of("c", 4)));
+  }
 }

Reply via email to