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

lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 23a220da4 [core] Support ignoring specified fields while generating 
-U, +U changelog for the same record (#4126)
23a220da4 is described below

commit 23a220da43a2dad962df7f6c1588fe97d02c4247
Author: chenxinwei <[email protected]>
AuthorDate: Wed Sep 11 13:05:13 2024 +0800

    [core] Support ignoring specified fields while generating -U, +U changelog 
for the same record (#4126)
---
 .../shortcodes/generated/core_configuration.html   |  6 ++
 .../paimon/codegen/EqualiserCodeGenerator.scala    |  3 +-
 .../main/java/org/apache/paimon/CoreOptions.java   | 13 ++++
 .../java/org/apache/paimon/KeyValueFileStore.java  | 10 +++-
 .../paimon/utils/ValueEqualiserSupplier.java       | 29 ++++++++-
 .../FullChangelogMergeFunctionWrapperTestBase.java | 63 ++++++++++++++++++++
 .../LookupChangelogMergeFunctionWrapperTest.java   | 69 ++++++++++++++++++++++
 7 files changed, 188 insertions(+), 5 deletions(-)

diff --git a/docs/layouts/shortcodes/generated/core_configuration.html 
b/docs/layouts/shortcodes/generated/core_configuration.html
index 6dfd125d8..85cdd416d 100644
--- a/docs/layouts/shortcodes/generated/core_configuration.html
+++ b/docs/layouts/shortcodes/generated/core_configuration.html
@@ -74,6 +74,12 @@ under the License.
             <td>Boolean</td>
             <td>Whether to generate -U, +U changelog for the same record. This 
configuration is only valid for the changelog-producer is lookup or 
full-compaction.</td>
         </tr>
+        <tr>
+            <td><h5>changelog-producer.row-deduplicate-ignore-fields</h5></td>
+            <td style="word-wrap: break-word;">(none)</td>
+            <td>String</td>
+            <td>Fields that are ignored for comparison while generating -U, +U 
changelog for the same record. This configuration is only valid for the 
changelog-producer.row-deduplicate is true.</td>
+        </tr>
         <tr>
             <td><h5>changelog.num-retained.max</h5></td>
             <td style="word-wrap: break-word;">(none)</td>
diff --git 
a/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
 
b/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
index 3548d5b4c..314734adb 100644
--- 
a/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
+++ 
b/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
@@ -40,6 +40,7 @@ class EqualiserCodeGenerator(fieldTypes: Array[DataType], 
fields: Array[Int]) {
     val ctx = new CodeGeneratorContext
     val className = newName(name)
 
+    val containsIgnoreFields = fieldTypes.length > fields.length
     val equalsMethodCodes = for (idx <- fields) yield 
generateEqualsMethod(ctx, idx)
     val equalsMethodCalls = for (idx <- fields) yield {
       val methodName = getEqualsMethodName(idx)
@@ -57,7 +58,7 @@ class EqualiserCodeGenerator(fieldTypes: Array[DataType], 
fields: Array[Int]) {
 
           @Override
           public boolean equals($ROW_DATA $LEFT_INPUT, $ROW_DATA $RIGHT_INPUT) 
{
-            if ($LEFT_INPUT instanceof $BINARY_ROW && $RIGHT_INPUT instanceof 
$BINARY_ROW) {
+            if ($LEFT_INPUT instanceof $BINARY_ROW && $RIGHT_INPUT instanceof 
$BINARY_ROW && !$containsIgnoreFields) {
               return $LEFT_INPUT.equals($RIGHT_INPUT);
             }
 
diff --git a/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java 
b/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java
index 85d732787..5df8f0235 100644
--- a/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java
+++ b/paimon-common/src/main/java/org/apache/paimon/CoreOptions.java
@@ -566,6 +566,13 @@ public class CoreOptions implements Serializable {
                     .withDescription(
                             "Whether to generate -U, +U changelog for the same 
record. This configuration is only valid for the changelog-producer is lookup 
or full-compaction.");
 
+    public static final ConfigOption<String> 
CHANGELOG_PRODUCER_ROW_DEDUPLICATE_IGNORE_FIELDS =
+            key("changelog-producer.row-deduplicate-ignore-fields")
+                    .stringType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "Fields that are ignored for comparison while 
generating -U, +U changelog for the same record. This configuration is only 
valid for the changelog-producer.row-deduplicate is true.");
+
     @Immutable
     public static final ConfigOption<String> SEQUENCE_FIELD =
             key("sequence.field")
@@ -1786,6 +1793,12 @@ public class CoreOptions implements Serializable {
         return options.get(CHANGELOG_PRODUCER_ROW_DEDUPLICATE);
     }
 
+    public List<String> changelogRowDeduplicateIgnoreFields() {
+        return 
options.getOptional(CHANGELOG_PRODUCER_ROW_DEDUPLICATE_IGNORE_FIELDS)
+                .map(s -> Arrays.asList(s.split(",")))
+                .orElse(Collections.emptyList());
+    }
+
     public boolean scanPlanSortPartition() {
         return options.get(SCAN_PLAN_SORT_PARTITION);
     }
diff --git a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java 
b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
index 383b2458d..d9ca0e175 100644
--- a/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
+++ b/paimon-core/src/main/java/org/apache/paimon/KeyValueFileStore.java
@@ -67,7 +67,7 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
     private final RowType valueType;
     private final KeyValueFieldsExtractor keyValueFieldsExtractor;
     private final Supplier<Comparator<InternalRow>> keyComparatorSupplier;
-    private final Supplier<RecordEqualiser> valueEqualiserSupplier;
+    private final Supplier<RecordEqualiser> logDedupEqualSupplier;
     private final MergeFunctionFactory<KeyValue> mfFactory;
     private final String tableName;
 
@@ -93,7 +93,11 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
         this.keyValueFieldsExtractor = keyValueFieldsExtractor;
         this.mfFactory = mfFactory;
         this.keyComparatorSupplier = new KeyComparatorSupplier(keyType);
-        this.valueEqualiserSupplier = new ValueEqualiserSupplier(valueType);
+        List<String> ignoreFields = 
options.changelogRowDeduplicateIgnoreFields();
+        this.logDedupEqualSupplier =
+                options.changelogRowDeduplicate() && !ignoreFields.isEmpty()
+                        ? ValueEqualiserSupplier.fromIgnoreFields(valueType, 
ignoreFields)
+                        : new ValueEqualiserSupplier(valueType);
         this.tableName = tableName;
     }
 
@@ -174,7 +178,7 @@ public class KeyValueFileStore extends 
AbstractFileStore<KeyValue> {
                 valueType,
                 keyComparatorSupplier,
                 () -> UserDefinedSeqComparator.create(valueType, options),
-                valueEqualiserSupplier,
+                logDedupEqualSupplier,
                 mfFactory,
                 pathFactory(),
                 format2PathFactory(),
diff --git 
a/paimon-core/src/main/java/org/apache/paimon/utils/ValueEqualiserSupplier.java 
b/paimon-core/src/main/java/org/apache/paimon/utils/ValueEqualiserSupplier.java
index 6de3ded2f..315ac519c 100644
--- 
a/paimon-core/src/main/java/org/apache/paimon/utils/ValueEqualiserSupplier.java
+++ 
b/paimon-core/src/main/java/org/apache/paimon/utils/ValueEqualiserSupplier.java
@@ -22,8 +22,11 @@ import org.apache.paimon.codegen.RecordEqualiser;
 import org.apache.paimon.types.DataType;
 import org.apache.paimon.types.RowType;
 
+import javax.annotation.Nullable;
+
 import java.util.List;
 import java.util.function.Supplier;
+import java.util.stream.IntStream;
 
 import static org.apache.paimon.codegen.CodeGenUtils.newRecordEqualiser;
 
@@ -34,12 +37,36 @@ public class ValueEqualiserSupplier implements 
SerializableSupplier<RecordEquali
 
     private final List<DataType> fieldTypes;
 
+    private final int[] projection;
+
     public ValueEqualiserSupplier(RowType keyType) {
         this.fieldTypes = keyType.getFieldTypes();
+        this.projection = null;
+    }
+
+    public ValueEqualiserSupplier(RowType keyType, int[] projection) {
+        this.fieldTypes = keyType.getFieldTypes();
+        this.projection = projection;
     }
 
     @Override
     public RecordEqualiser get() {
-        return newRecordEqualiser(fieldTypes);
+        return this.projection == null
+                ? newRecordEqualiser(fieldTypes)
+                : newRecordEqualiser(fieldTypes, projection);
+    }
+
+    public static ValueEqualiserSupplier fromIgnoreFields(
+            RowType rowType, @Nullable List<String> ignoreFields) {
+        int[] projection = getProjectionWithIgnoreFields(rowType, 
ignoreFields);
+        return new ValueEqualiserSupplier(rowType, projection);
+    }
+
+    private static int[] getProjectionWithIgnoreFields(RowType rowType, 
List<String> ignoreFields) {
+        List<String> fieldNames = rowType.getFieldNames();
+        IntStream projectionStream = IntStream.range(0, 
rowType.getFieldCount());
+        return projectionStream
+                .filter(idx -> !ignoreFields.contains(fieldNames.get(idx)))
+                .toArray();
     }
 }
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/FullChangelogMergeFunctionWrapperTestBase.java
 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/FullChangelogMergeFunctionWrapperTestBase.java
index 88bba7722..950e99d8f 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/FullChangelogMergeFunctionWrapperTestBase.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/FullChangelogMergeFunctionWrapperTestBase.java
@@ -20,7 +20,11 @@ package org.apache.paimon.mergetree.compact;
 
 import org.apache.paimon.KeyValue;
 import org.apache.paimon.codegen.RecordEqualiser;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowKind;
+import org.apache.paimon.types.RowType;
+import org.apache.paimon.utils.ValueEqualiserSupplier;
 
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -31,6 +35,7 @@ import java.util.Collections;
 import java.util.List;
 
 import static org.apache.paimon.io.DataFileTestUtils.row;
+import static org.assertj.core.api.Assertions.assertThat;
 
 /** Tests for {@link FullChangelogMergeFunctionWrapper}. */
 public abstract class FullChangelogMergeFunctionWrapperTestBase {
@@ -214,5 +219,63 @@ public abstract class 
FullChangelogMergeFunctionWrapperTestBase {
         protected boolean changelogRowDeduplicate() {
             return true;
         }
+
+        @Test
+        public void testFullChangelogMergeFunctionWrapperWithIgnoreFields() {
+            RowType valueType =
+                    RowType.builder()
+                            .fields(
+                                    new DataType[] {DataTypes.INT(), 
DataTypes.INT()},
+                                    new String[] {"f0", "f1"})
+                            .build();
+            List<String> ignoreFields = Collections.singletonList("f1");
+            ValueEqualiserSupplier logDedupEqualSupplier =
+                    ValueEqualiserSupplier.fromIgnoreFields(valueType, 
ignoreFields);
+            FullChangelogMergeFunctionWrapper function =
+                    new FullChangelogMergeFunctionWrapper(
+                            createMergeFunction(), MAX_LEVEL, 
logDedupEqualSupplier.get(), true);
+
+            // With level-0 'insert' record, with max level same record. 
Notice that the specified
+            // ignored
+            // fields in records are different.
+            function.reset();
+            function.add(
+                    new KeyValue()
+                            .replace(row(1), 1, RowKind.INSERT, row(1, 1))
+                            .setLevel(MAX_LEVEL));
+            function.add(new KeyValue().replace(row(1), 2, RowKind.INSERT, 
row(1, 2)).setLevel(0));
+            ChangelogResult result = function.getResult();
+            assertThat(result).isNotNull();
+            List<KeyValue> changelogs = result.changelogs();
+            assertThat(changelogs).isEmpty();
+            KeyValue kv = result.result();
+            assertThat(kv).isNotNull();
+            assertThat(kv.valueKind()).isEqualTo(RowKind.INSERT);
+            assertThat(kv.value().getInt(0)).isEqualTo(1);
+            assertThat(kv.value().getInt(1)).isEqualTo(2);
+
+            // With level-0 'insert' record, with max level different record.
+            function.reset();
+            function.add(
+                    new KeyValue()
+                            .replace(row(1), 1, RowKind.INSERT, row(1, 1))
+                            .setLevel(MAX_LEVEL));
+            function.add(new KeyValue().replace(row(1), 2, RowKind.INSERT, 
row(2, 2)).setLevel(0));
+            result = function.getResult();
+            assertThat(result).isNotNull();
+            changelogs = result.changelogs();
+            assertThat(changelogs).hasSize(2);
+            
assertThat(changelogs.get(0).valueKind()).isEqualTo(RowKind.UPDATE_BEFORE);
+            assertThat(changelogs.get(0).value().getInt(0)).isEqualTo(1);
+            assertThat(changelogs.get(0).value().getInt(1)).isEqualTo(1);
+            
assertThat(changelogs.get(1).valueKind()).isEqualTo(RowKind.UPDATE_AFTER);
+            assertThat(changelogs.get(1).value().getInt(0)).isEqualTo(2);
+            assertThat(changelogs.get(1).value().getInt(1)).isEqualTo(2);
+            kv = result.result();
+            assertThat(kv).isNotNull();
+            assertThat(kv.valueKind()).isEqualTo(RowKind.INSERT);
+            assertThat(kv.value().getInt(0)).isEqualTo(2);
+            assertThat(kv.value().getInt(1)).isEqualTo(2);
+        }
     }
 }
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/LookupChangelogMergeFunctionWrapperTest.java
 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/LookupChangelogMergeFunctionWrapperTest.java
index e2d37eae4..576f31802 100644
--- 
a/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/LookupChangelogMergeFunctionWrapperTest.java
+++ 
b/paimon-core/src/test/java/org/apache/paimon/mergetree/compact/LookupChangelogMergeFunctionWrapperTest.java
@@ -29,10 +29,12 @@ import 
org.apache.paimon.mergetree.compact.aggregate.FieldAggregator;
 import org.apache.paimon.mergetree.compact.aggregate.FieldLastValueAgg;
 import org.apache.paimon.mergetree.compact.aggregate.FieldSumAgg;
 import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataType;
 import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.IntType;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.UserDefinedSeqComparator;
+import org.apache.paimon.utils.ValueEqualiserSupplier;
 
 import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap;
 
@@ -41,6 +43,7 @@ import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -214,6 +217,72 @@ public class LookupChangelogMergeFunctionWrapperTest {
         assertThat(kv.value().getInt(0)).isEqualTo(2);
     }
 
+    @Test
+    public void testDeduplicateWithIgnoreFields() {
+        Map<InternalRow, KeyValue> highLevel = new HashMap<>();
+        RowType valueType =
+                RowType.builder()
+                        .fields(
+                                new DataType[] {DataTypes.INT(), 
DataTypes.INT()},
+                                new String[] {"f0", "f1"})
+                        .build();
+        UserDefinedSeqComparator userDefinedSeqComparator =
+                UserDefinedSeqComparator.create(
+                        valueType, 
CoreOptions.fromMap(ImmutableMap.of("sequence.field", "f1")));
+        assert userDefinedSeqComparator != null;
+        List<String> ignoreFields = Collections.singletonList("f1");
+        ValueEqualiserSupplier logDedupEqualSupplier =
+                ValueEqualiserSupplier.fromIgnoreFields(valueType, 
ignoreFields);
+        LookupChangelogMergeFunctionWrapper function =
+                new LookupChangelogMergeFunctionWrapper(
+                        LookupMergeFunction.wrap(
+                                DeduplicateMergeFunction.factory(),
+                                RowType.of(DataTypes.INT()),
+                                valueType),
+                        highLevel::get,
+                        logDedupEqualSupplier.get(),
+                        true,
+                        LookupStrategy.from(false, true, false, false),
+                        null,
+                        userDefinedSeqComparator);
+
+        // With level-0 'insert' record, with level-x (x > 0) same record. 
Notice that the specified
+        // ignored
+        // fields in records are different.
+        function.reset();
+        function.add(new KeyValue().replace(row(1), 1, INSERT, row(1, 
1)).setLevel(2));
+        function.add(new KeyValue().replace(row(1), 2, INSERT, row(1, 
2)).setLevel(0));
+        ChangelogResult result = function.getResult();
+        assertThat(result).isNotNull();
+        List<KeyValue> changelogs = result.changelogs();
+        assertThat(changelogs).isEmpty();
+        KeyValue kv = result.result();
+        assertThat(kv).isNotNull();
+        assertThat(kv.valueKind()).isEqualTo(INSERT);
+        assertThat(kv.value().getInt(0)).isEqualTo(1);
+        assertThat(kv.value().getInt(1)).isEqualTo(2);
+
+        // With level-0 'insert' record, with level-x (x > 0) different record.
+        function.reset();
+        function.add(new KeyValue().replace(row(1), 1, INSERT, row(1, 
1)).setLevel(1));
+        function.add(new KeyValue().replace(row(1), 2, INSERT, row(2, 
2)).setLevel(0));
+        result = function.getResult();
+        assertThat(result).isNotNull();
+        changelogs = result.changelogs();
+        assertThat(changelogs).hasSize(2);
+        assertThat(changelogs.get(0).valueKind()).isEqualTo(UPDATE_BEFORE);
+        assertThat(changelogs.get(0).value().getInt(0)).isEqualTo(1);
+        assertThat(changelogs.get(0).value().getInt(1)).isEqualTo(1);
+        assertThat(changelogs.get(1).valueKind()).isEqualTo(UPDATE_AFTER);
+        assertThat(changelogs.get(1).value().getInt(0)).isEqualTo(2);
+        assertThat(changelogs.get(1).value().getInt(1)).isEqualTo(2);
+        kv = result.result();
+        assertThat(kv).isNotNull();
+        assertThat(kv.valueKind()).isEqualTo(INSERT);
+        assertThat(kv.value().getInt(0)).isEqualTo(2);
+        assertThat(kv.value().getInt(1)).isEqualTo(2);
+    }
+
     @ParameterizedTest
     @ValueSource(booleans = {false, true})
     public void testSum(boolean changelogRowDeduplicate) {

Reply via email to