You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by pv...@apache.org on 2025/07/23 10:56:28 UTC
(iceberg) branch main updated: Core: Implement Map comparator (#13626)
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 <pe...@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)));
+ }
}