seawinde commented on code in PR #62951:
URL: https://github.com/apache/doris/pull/62951#discussion_r3272639618
##########
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:
This returns immediately after synthesizing the first whole-bucket
date_trunc equality. If the query and MV contain multiple date_trunc
dimensions, only one range is converted and the remaining date ranges fall back
to the old
compensation path. Could we either support synthesizing all matched
whole-bucket ranges, or add a test/explicit limitation for the
multi-date-column case?
--
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]