gustavodemorais commented on code in PR #29653:
URL: https://github.com/apache/flink/pull/29653#discussion_r4523969632


##########
flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/utils/UpsertKeyUtil.java:
##########
@@ -63,4 +78,97 @@ public static Optional<int[]> smallestKey(@Nullable 
Set<ImmutableBitSet> upsertK
                 
upsertKeys.stream().map(ImmutableBitSet::toArray).collect(Collectors.toList());
         return Optional.of(UpsertKeyUtils.smallestKey(asArrays));
     }
+
+    /**
+     * Returns the upsert keys of the given node, plus each key whose columns 
outside {@code
+     * columns} can all be replaced by columns inside that are equal to them 
on every row, e.g.
+     * through an inner equi-join condition. For example, for {@code t1 JOIN 
t2 ON a1 = a2} and
+     * columns {a1, b2}, the upsert key {a2, b2} adds {a1, b2}.
+     *
+     * @return the upsert keys and the aligned ones, or null if the upsert 
keys are unknown
+     */
+    public static @Nullable Set<ImmutableBitSet> getUpsertKeysAlignedTo(
+            RelNode rel, ImmutableBitSet columns, RelMetadataQuery mq) {
+        final FlinkRelMetadataQuery fmq = 
FlinkRelMetadataQuery.reuseOrCreate(mq);
+        final Set<ImmutableBitSet> upsertKeys = fmq.getUpsertKeys(rel);
+        if (upsertKeys == null || 
upsertKeys.stream().allMatch(columns::contains)) {
+            return upsertKeys;
+        }
+
+        final Map<Integer, Integer> parents = getEqualColumnParents(rel, fmq);
+        final Map<Integer, Integer> replacements = new HashMap<>();
+        for (int column : columns) {
+            replacements.putIfAbsent(root(parents, column), column);
+        }
+        final Set<ImmutableBitSet> alignedKeys = new HashSet<>(upsertKeys);
+        for (ImmutableBitSet key : upsertKeys) {
+            final ImmutableBitSet.Builder alignedKey = 
ImmutableBitSet.builder();
+            for (int column : key) {
+                alignedKey.set(
+                        columns.get(column)
+                                ? column
+                                : replacements.getOrDefault(root(parents, 
column), column));
+            }
+            final ImmutableBitSet aligned = alignedKey.build();
+            if (columns.contains(aligned)) {
+                alignedKeys.add(aligned);
+            }
+        }
+        return alignedKeys;
+    }
+
+    /**
+     * Returns {@code columns} and every column of the given node that is 
equal to one of them on
+     * every row.
+     */
+    public static ImmutableBitSet getEqualColumns(
+            RelNode rel, ImmutableBitSet columns, RelMetadataQuery mq) {
+        final Map<Integer, Integer> parents = getEqualColumnParents(rel, mq);
+        final Set<Integer> roots =
+                columns.asList().stream()
+                        .map(column -> root(parents, column))
+                        .collect(Collectors.toSet());
+        final ImmutableBitSet.Builder equalColumns = columns.rebuild();
+        for (int column : parents.keySet()) {
+            final int root = root(parents, column);
+            if (roots.contains(root)) {
+                equalColumns.set(column).set(root);
+            }
+        }
+        return equalColumns.build();
+    }
+
+    /** Groups the columns that are equal on every row, as a union-find forest 
of parents. */
+    private static Map<Integer, Integer> getEqualColumnParents(RelNode rel, 
RelMetadataQuery mq) {
+        final Map<Integer, Integer> parents = new HashMap<>();
+        for (RexNode predicate : 
mq.getPulledUpPredicates(rel).pulledUpPredicates) {
+            if (!predicate.isA(EQUALITY_KINDS)) {
+                continue;
+            }
+            final RexNode left = ((RexCall) predicate).getOperands().get(0);
+            final RexNode right = ((RexCall) predicate).getOperands().get(1);
+            // comparing different types may implicitly cast one side, which 
needn't be injective,
+            // and FLOAT/DOUBLE compare -0.0 = 0.0 although they are different 
keys
+            if (left instanceof RexInputRef
+                    && right instanceof RexInputRef
+                    && SqlTypeUtil.equalSansNullability(left.getType(), 
right.getType())
+                    && !SqlTypeUtil.isApproximateNumeric(left.getType())) {

Review Comment:
   `!isApproximateNumeric` only looks at the top-level type, but `=` on 
MAP/MULTISET ignores entry order and ROW compares nested DOUBLE fields with 
`==`, while upsert keys compare bytes. Example: `r` with `PRIMARY KEY (id, k)`, 
sink `PRIMARY KEY (id, v)`:
   
   ```sql
   INSERT INTO sink SELECT l.id, r.k, r.v, r.name FROM l JOIN r ON l.id = r.id 
WHERE r.k = r.v
   ```
   
   With `ROW<d DOUBLE>` columns and rows `+I(1, k1=ROW(0.0), v=ROW(-0.0), r1)`, 
`+I(1, k2=ROW(-0.0), v, r2)`, `-D(1, k1, v, r1)`, both rows pass the filter and 
share the sink key, so the delete wipes `r2` and the sink ends up empty (same 
with MAP `{1:a, 2:b}` vs `{2:b, 1:a}`).
   
   ```suggestion
               // comparing different types may implicitly cast one side, which 
needn't be injective
               if (left instanceof RexInputRef
                       && right instanceof RexInputRef
                       && SqlTypeUtil.equalSansNullability(left.getType(), 
right.getType())
                       && isEqualityByBytes(left.getType())) {
   ```
   
   with
   
   ```java
       // types whose "=" is byte equality, unlike FLOAT/DOUBLE (-0.0 = 0.0), 
MAP, MULTISET or ROW
       private static boolean isEqualityByBytes(RelDataType type) {
           return SqlTypeUtil.isCharacter(type)
                   || SqlTypeUtil.isBinary(type)
                   || SqlTypeUtil.isExactNumeric(type)
                   || SqlTypeUtil.isBoolean(type)
                   || SqlTypeUtil.isDatetime(type)
                   || SqlTypeUtil.isInterval(type);
       }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to