twalthr commented on code in PR #27603:
URL: https://github.com/apache/flink/pull/27603#discussion_r2817114698


##########
flink-table/flink-table-common/src/main/java/org/apache/flink/table/types/logical/utils/LogicalTypeCasts.java:
##########
@@ -293,6 +354,83 @@ public static boolean supportsExplicitCast(LogicalType 
sourceType, LogicalType t
         return supportsCasting(sourceType, targetType, true);
     }
 
+    /**
+     * Returns whether the cast from source type to target type is injective
+     * (uniqueness-preserving).
+     *
+     * <p>An injective cast guarantees that every distinct input value maps to 
a distinct output
+     * value. This property is useful for upsert key tracking through 
projections: if a key column
+     * is cast using an injective conversion, the uniqueness of the key is 
preserved.
+     *
+     * <p>Injective casts are explicitly defined in the casting rules, 
separate from implicit casts.
+     * Not all implicit casts are injective (e.g., TIMESTAMP → DATE loses time 
information).
+     *
+     * <p>For constructed types (ROW), this method recursively checks if all 
field casts are
+     * injective.
+     *
+     * @param sourceType the source type
+     * @param targetType the target type
+     * @return {@code true} if the cast preserves uniqueness
+     */
+    public static boolean supportsInjectiveCast(
+            final LogicalType sourceType, final LogicalType targetType) {
+        final LogicalTypeRoot sourceRoot = sourceType.getTypeRoot();
+        final LogicalTypeRoot targetRoot = targetType.getTypeRoot();
+
+        // Handle DISTINCT types by unwrapping
+        if (sourceRoot == DISTINCT_TYPE) {
+            return supportsInjectiveCast(((DistinctType) 
sourceType).getSourceType(), targetType);
+        }
+        if (targetRoot == DISTINCT_TYPE) {
+            return supportsInjectiveCast(sourceType, ((DistinctType) 
targetType).getSourceType());
+        }
+
+        // Handle NULL type
+        if (sourceRoot == NULL) {
+            return true;
+        }
+
+        // Handle constructed types (ROW, STRUCTURED_TYPE) with recursive 
field checks
+        final boolean isSourceConstructed = sourceRoot == ROW || sourceRoot == 
STRUCTURED_TYPE;
+        final boolean isTargetConstructed = targetRoot == ROW || targetRoot == 
STRUCTURED_TYPE;
+        if (isSourceConstructed && isTargetConstructed) {
+            return supportsInjectiveConstructedCast(sourceType, targetType);
+        }
+
+        // Handle DECIMAL → DECIMAL: injective only if target can represent 
all source values
+        if (sourceRoot == DECIMAL && targetRoot == DECIMAL) {
+            final DecimalType sourceDecimal = (DecimalType) sourceType;
+            final DecimalType targetDecimal = (DecimalType) targetType;
+            // Injective when target has at least as much precision and scale 
(identity or widening)
+            return targetDecimal.getPrecision() >= sourceDecimal.getPrecision()
+                    && targetDecimal.getScale() >= sourceDecimal.getScale();

Review Comment:
   this is not correct, if you increase the scale while keeping the precision 
at the same level, you loose pre-comma digits. better to just test for identity 
;-)



-- 
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