zwy991114 commented on code in PR #62951:
URL: https://github.com/apache/doris/pull/62951#discussion_r3295731515


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java:
##########
@@ -254,6 +269,194 @@ private static Set<Expression> 
normalizeExpression(Expression expression, Cascad
         return ExpressionUtils.extractConjunctionToSet(expression);
     }
 
+    /**
+     * Detect if normalized expressions form a whole-bucket range and 
synthesize date_trunc equality predicates.
+     * Returns null if no whole-bucket pattern detected.
+     */
+    private static Map<Expression, ExpressionInfo> 
detectAndSynthesizeWholeBucketPredicates(
+            Set<Expression> normalizedExpressions,
+            StructInfo viewStructInfo,
+            SlotMapping viewToQuerySlotMapping) {
+        // Group predicates by slot
+        Map<Expression, List<Expression>> slotToPredicates = new HashMap<>();
+        for (Expression expr : normalizedExpressions) {
+            if (!(expr instanceof ComparisonPredicate)) {
+                continue;
+            }
+            ComparisonPredicate pred = (ComparisonPredicate) expr;
+            Expression left = pred.left();
+            if (left instanceof SlotReference) {
+                slotToPredicates.computeIfAbsent(left, k -> new 
ArrayList<>()).add(expr);
+            }
+        }
+
+        // Check if view has date_trunc on any of these slots
+        Map<SlotReference, DateTrunc> viewDateTruncMap = 
extractViewDateTruncExpressions(viewStructInfo);
+        if (viewDateTruncMap.isEmpty()) {
+            return null;
+        }
+
+        // Map view slots to query slots
+        Map<SlotReference, SlotReference> viewToQuerySlotMap = 
viewToQuerySlotMapping.toSlotReferenceMap();
+        Map<SlotReference, DateTrunc> querySlotToViewDateTrunc = new 
HashMap<>();
+        for (Map.Entry<SlotReference, DateTrunc> entry : 
viewDateTruncMap.entrySet()) {
+            SlotReference viewSlot = entry.getKey();
+            SlotReference querySlot = viewToQuerySlotMap.get(viewSlot);
+            if (querySlot != null) {
+                querySlotToViewDateTrunc.put(querySlot, entry.getValue());
+            }
+        }
+
+        // Try to detect whole-bucket ranges
+        for (Map.Entry<Expression, List<Expression>> entry : 
slotToPredicates.entrySet()) {
+            if (!(entry.getKey() instanceof SlotReference)) {
+                continue;
+            }
+            SlotReference slot = (SlotReference) entry.getKey();
+
+            // Only apply to DATE/DATEV2 types, not DATETIME/DATETIMEV2
+            // Reason: Boundary conversion (dt > '2024-12-31' → dt >= 
'2025-01-01') is only
+            // valid for DATE types. For DATETIME, dt <= '2025-01-31 00:00:00' 
does not cover
+            // the full day of Jan 31, so whole-bucket detection would be 
semantically incorrect.
+            if (!slot.getDataType().isDateType() && 
!slot.getDataType().isDateV2Type()) {
+                continue;
+            }
+
+            DateTrunc viewDateTrunc = querySlotToViewDateTrunc.get(slot);
+            if (viewDateTrunc == null) {
+                continue;
+            }
+
+            List<Expression> predicates = entry.getValue();
+            if (predicates.size() != 2) {
+                continue;
+            }
+
+            // Extract lower and upper bounds
+            DateLiteral lowerBound = null;
+            DateLiteral upperBound = null;
+            for (Expression pred : predicates) {
+                if (!(pred instanceof ComparisonPredicate)) {
+                    continue;
+                }
+                ComparisonPredicate cp = (ComparisonPredicate) pred;
+                // Exclude DateTimeLiteral (which extends DateLiteral) because 
DATETIME semantics differ
+                if (cp.right() instanceof DateTimeLiteral || !(cp.right() 
instanceof DateLiteral)) {
+                    continue;
+                }
+                DateLiteral literal = (DateLiteral) cp.right();
+                if (cp instanceof GreaterThanEqual) {
+                    lowerBound = literal;
+                } else if (cp instanceof GreaterThan) {
+                    // dt > '2024-12-31' is equivalent to dt >= '2025-01-01' 
for DATE type only
+                    lowerBound = (DateLiteral) literal.plusDays(1);
+                } else if (cp instanceof LessThanEqual) {
+                    upperBound = literal;
+                } else if (cp instanceof LessThan) {
+                    // dt < '2025-02-01' is equivalent to dt <= '2025-01-31' 
for DATE type only
+                    upperBound = (DateLiteral) literal.plusDays(-1);
+                }
+            }
+
+            if (lowerBound == null || upperBound == null) {
+                continue;
+            }
+            if (lowerBound.getDouble() > upperBound.getDouble()) {
+                continue;
+            }
+
+            // Detect whole bucket
+            java.util.Optional<DateTruncRangeDetector.BucketInfo> bucketInfo =
+                    DateTruncRangeDetector.detectWholeBucket(lowerBound, 
upperBound);
+            if (!bucketInfo.isPresent()) {
+                continue;
+            }
+
+            // Check if bucket unit matches view date_trunc unit
+            String bucketUnit = bucketInfo.get().unit;
+            String viewUnit = extractDateTruncUnit(viewDateTrunc);
+            if (viewUnit == null || !viewUnit.equalsIgnoreCase(bucketUnit)) {
+                continue;
+            }
+
+            // Synthesize date_trunc(slot, unit) = bucket_start
+            DateLiteral bucketStart = bucketInfo.get().bucketStart;
+            Expression syntheticPredicate = new EqualTo(
+                    rebuildDateTruncOnQuerySlot(viewDateTrunc, slot),
+                    bucketStart
+            );
+
+            // Build result map with synthetic predicate and remaining 
non-date predicates
+            Map<Expression, ExpressionInfo> result = new HashMap<>();
+            result.put(syntheticPredicate, new ExpressionInfo(bucketStart, 
true));
+
+            // Add remaining predicates that are not part of the detected date 
range
+            Set<Expression> consumedPredicates = new HashSet<>(predicates);
+            for (Expression expr : normalizedExpressions) {
+                if (!consumedPredicates.contains(expr)) {
+                    Set<Literal> literalSet = expr.collect(e -> e instanceof 
Literal);
+                    if (expr.anyMatch(AggregateFunction.class::isInstance)) {
+                        return null;
+                    }
+                    if (literalSet.size() == 1 && expr instanceof 
ComparisonPredicate
+                            && !(expr instanceof GreaterThan || expr 
instanceof LessThanEqual)) {
+                        result.put(expr, new 
ExpressionInfo(literalSet.iterator().next()));
+                    } else {
+                        result.put(expr, ExpressionInfo.EMPTY);
+                    }
+                }
+            }
+            return result;

Review Comment:
   fixed



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/exploration/mv/Predicates.java:
##########
@@ -254,6 +269,194 @@ private static Set<Expression> 
normalizeExpression(Expression expression, Cascad
         return ExpressionUtils.extractConjunctionToSet(expression);
     }
 
+    /**
+     * Detect if normalized expressions form a whole-bucket range and 
synthesize date_trunc equality predicates.
+     * Returns null if no whole-bucket pattern detected.
+     */
+    private static Map<Expression, ExpressionInfo> 
detectAndSynthesizeWholeBucketPredicates(
+            Set<Expression> normalizedExpressions,
+            StructInfo viewStructInfo,
+            SlotMapping viewToQuerySlotMapping) {
+        // Group predicates by slot
+        Map<Expression, List<Expression>> slotToPredicates = new HashMap<>();
+        for (Expression expr : normalizedExpressions) {
+            if (!(expr instanceof ComparisonPredicate)) {
+                continue;
+            }
+            ComparisonPredicate pred = (ComparisonPredicate) expr;
+            Expression left = pred.left();
+            if (left instanceof SlotReference) {
+                slotToPredicates.computeIfAbsent(left, k -> new 
ArrayList<>()).add(expr);
+            }
+        }
+
+        // Check if view has date_trunc on any of these slots
+        Map<SlotReference, DateTrunc> viewDateTruncMap = 
extractViewDateTruncExpressions(viewStructInfo);
+        if (viewDateTruncMap.isEmpty()) {
+            return null;
+        }
+
+        // Map view slots to query slots
+        Map<SlotReference, SlotReference> viewToQuerySlotMap = 
viewToQuerySlotMapping.toSlotReferenceMap();
+        Map<SlotReference, DateTrunc> querySlotToViewDateTrunc = new 
HashMap<>();
+        for (Map.Entry<SlotReference, DateTrunc> entry : 
viewDateTruncMap.entrySet()) {
+            SlotReference viewSlot = entry.getKey();
+            SlotReference querySlot = viewToQuerySlotMap.get(viewSlot);
+            if (querySlot != null) {
+                querySlotToViewDateTrunc.put(querySlot, entry.getValue());
+            }
+        }
+
+        // Try to detect whole-bucket ranges
+        for (Map.Entry<Expression, List<Expression>> entry : 
slotToPredicates.entrySet()) {
+            if (!(entry.getKey() instanceof SlotReference)) {
+                continue;
+            }
+            SlotReference slot = (SlotReference) entry.getKey();
+
+            // Only apply to DATE/DATEV2 types, not DATETIME/DATETIMEV2
+            // Reason: Boundary conversion (dt > '2024-12-31' → dt >= 
'2025-01-01') is only
+            // valid for DATE types. For DATETIME, dt <= '2025-01-31 00:00:00' 
does not cover
+            // the full day of Jan 31, so whole-bucket detection would be 
semantically incorrect.
+            if (!slot.getDataType().isDateType() && 
!slot.getDataType().isDateV2Type()) {
+                continue;
+            }
+
+            DateTrunc viewDateTrunc = querySlotToViewDateTrunc.get(slot);
+            if (viewDateTrunc == null) {
+                continue;
+            }
+
+            List<Expression> predicates = entry.getValue();

Review Comment:
   added



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


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to