-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[fix](rbo) Preserve semantics in predicate inference #67919
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
| import org.apache.doris.nereids.trees.expressions.literal.Literal; | ||
| import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; | ||
| import org.apache.doris.nereids.trees.plans.Plan; | ||
| import org.apache.doris.nereids.types.DataType; | ||
| import org.apache.doris.nereids.types.DecimalV2Type; | ||
| import org.apache.doris.nereids.types.DecimalV3Type; | ||
| import org.apache.doris.nereids.util.ExpressionUtils; | ||
|
|
@@ -153,12 +154,15 @@ private static <T extends Expression> Set<Expression> getEqualSetAndDoReplace(T | |
| ExpressionAnalyzer analyzer = new ReplaceAnalyzer(null, new Scope(ImmutableList.of()), null, false, false); | ||
| Set<Expression> res = new LinkedHashSet<>(); | ||
| for (T equals : equalSet) { | ||
| Map<Expression, Expression> replaceMap = new HashMap<>(); | ||
| replaceMap.put(equals, replaceToThis); | ||
| if (!exprPredicates.containsKey(equals)) { | ||
| continue; | ||
| } | ||
| Map<Expression, Expression> replaceMap = new HashMap<>(); | ||
| replaceMap.put(equals, replaceToThis); | ||
| for (Expression predicate : exprPredicates.get(equals)) { | ||
| if (!canReplace(equals, replaceToThis, predicate)) { | ||
| continue; | ||
| } | ||
| Expression newPredicates = ExpressionUtils.replace(predicate, replaceMap); | ||
| try { | ||
| Expression analyzed = analyzer.analyze(newPredicates); | ||
|
|
@@ -171,6 +175,27 @@ private static <T extends Expression> Set<Expression> getEqualSetAndDoReplace(T | |
| return res; | ||
| } | ||
|
|
||
| private static boolean canReplace(Expression source, Expression target, Expression predicate) { | ||
| Expression comparison = predicate instanceof Not ? predicate.child(0) : predicate; | ||
| // Direct comparisons observe comparison equality rather than a value's type or representation. | ||
| // Do not descend through functions, casts or OR to apply this exception. | ||
| if ((comparison instanceof ComparisonPredicate || comparison instanceof InPredicate) | ||
| && comparison.child(0).equals(source)) { | ||
| return true; | ||
| } | ||
| DataType type = source.getDataType(); | ||
| // Comparison equality across types does not preserve type-sensitive expressions such as CAST to STRING. | ||
| if (!type.equals(target.getDataType())) { | ||
| return false; | ||
| } | ||
| // Only substitute types whose equality preserves the value observed by enclosing expressions. | ||
| // In particular, FLOAT/DOUBLE equality cannot distinguish signed zero, but SIGNBIT can. | ||
| // Comparisons can still be propagated separately by UnequalPredicateInfer. | ||
| return type.isBooleanType() || type.isIntegralType() || type.isDecimalLikeType() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Exclude NoneMovableFunction from replacement inference This allowlist still treats every deterministic expression over equal same-type values as movable, but |
||
| || type.isStringLikeType() || type.isIPType() | ||
| || (type.isDateLikeType() && !type.isTimeStampTzType()); | ||
| } | ||
|
|
||
| /* Extract the equivalence relationship a=b, and when case (d_tinyint as int)=d_int is encountered, | ||
| remove the cast and extract d_tinyint=d_int | ||
| EqualPairs is the output parameter and the equivalent pair of predicate derivation input, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -448,7 +448,8 @@ public Set<Expression> chooseInputPredicates(Relation[][] chosen) { | |
| clear(chosen, left, right, type); | ||
| } else if (deduced[left][right] != type) { | ||
| keep[i] = true; | ||
| set(deduced, left, right, Relation.EQ); | ||
| // Preserve the relation of the retained predicate; an inequality is not an equality. | ||
| set(deduced, left, right, type); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Preserve strict edges after recording the retained relation This assignment creates a head-only weakening when equality and strict/non-strict edges overlap. For the ordered filter |
||
| expandGraph(deduced, left, right); | ||
| if (type == Relation.EQ) { | ||
| expandGraph(deduced, right, left); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| -- This file is automatically generated. You should know what you did if you want to edit this | ||
| -- !strict -- | ||
|
|
||
| -- !non_strict -- | ||
| 1 2 1 1 | ||
| 2 2 1 2 | ||
|
|
||
| -- !mixed -- | ||
| 1 2 1 1 | ||
|
|
||
| -- !reordered -- | ||
|
|
||
| -- !matching_row -- | ||
| 3 4 1 3 | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| -- This file is automatically generated. You should know what you did if you want to edit this | ||
| -- !date_length -- | ||
| 1 10 | ||
|
|
||
| -- !datetime_length -- | ||
| 1 10 | ||
|
|
||
| -- !date_same_type -- | ||
| 1 1 | ||
|
|
||
| -- !date_comparison -- | ||
| 1 10 | ||
|
|
||
| -- !signed_zero_facts -- | ||
| 1 true true false | ||
| 2 true false true | ||
| 3 true false false | ||
| 4 true false false | ||
| 5 true true true | ||
|
|
||
| -- !signed_zero_or -- | ||
| 1 | ||
| 3 | ||
| 5 | ||
|
|
||
| -- !signed_zero_or_reversed -- | ||
| 2 | ||
| 3 | ||
| 5 | ||
|
|
||
| -- !float_comparison -- | ||
| 3 | ||
|
|
||
| -- !decimal_scale -- | ||
| 1 10 | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] Do not propagate through lossy TIMESTAMPTZ casts
This direct exception assumes the equality pair was extracted through injective casts, but
PredicateInferUtils.validForInfercurrently peels anyTIMESTAMPTZ -> DATETIMEV2cast even thoughTimeStampTzType.isInjectiveCastTorejects that conversion. InAmerica/New_Yorkfall-back, the reduced planInnerJoin(CAST(l.tz AS DATETIMEV2(0)) = r.dt)can joinl.tz=06:30Ztor.dt=01:30; a left filterNOT(l.tz = 05:30Z)is true, yet replacement producesNOT(r.dt = 01:30)and pushes false to the right child because both UTC instants cast to local 01:30. Scale reduction has the same problem (.1236rounds to.124). Please stop exposing a raw equality through this cast unlesschildType.isInjectiveCastTo(targetType)holds, and cover the production join path; guarding only this branch would leave the same extracted pair available to inequality inference.