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))); + } }