-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
[Improvement](nereids) Support join derivation when mv rewrite #29609
Changes from 1 commit
04834dc
7701094
c2a6cef
0b71d2a
82c8005
f9fcd6a
7119dc1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…materialized view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,8 @@ | |
import org.apache.doris.nereids.trees.expressions.NamedExpression; | ||
import org.apache.doris.nereids.trees.expressions.Slot; | ||
import org.apache.doris.nereids.trees.expressions.SlotReference; | ||
import org.apache.doris.nereids.trees.expressions.functions.scalar.NonNullable; | ||
import org.apache.doris.nereids.trees.expressions.functions.scalar.Nullable; | ||
import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral; | ||
import org.apache.doris.nereids.trees.expressions.literal.Literal; | ||
import org.apache.doris.nereids.trees.plans.JoinType; | ||
|
@@ -50,6 +52,7 @@ | |
import org.apache.doris.nereids.trees.plans.logical.LogicalFilter; | ||
import org.apache.doris.nereids.trees.plans.logical.LogicalOlapScan; | ||
import org.apache.doris.nereids.util.ExpressionUtils; | ||
import org.apache.doris.nereids.util.TypeUtils; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableSet; | ||
|
@@ -149,7 +152,7 @@ protected List<Plan> rewrite(Plan queryPlan, CascadesContext cascadesContext) { | |
queryStructInfo.addPredicates(pulledUpExpressions); | ||
} | ||
SplitPredicate compensatePredicates = predicatesCompensate(queryStructInfo, viewStructInfo, | ||
queryToViewSlotMapping); | ||
queryToViewSlotMapping, comparisonResult); | ||
// Can not compensate, bail out | ||
if (compensatePredicates.isEmpty()) { | ||
materializationContext.recordFailReason(queryStructInfo.getOriginalPlanId(), | ||
|
@@ -315,20 +318,28 @@ protected List<Expression> rewriteExpression(List<? extends Expression> sourceEx | |
|
||
List<Expression> rewrittenExpressions = new ArrayList<>(); | ||
for (int index = 0; index < sourceShuttledExpressions.size(); index++) { | ||
Expression expressionToRewrite = sourceShuttledExpressions.get(index); | ||
if (expressionToRewrite instanceof Literal) { | ||
rewrittenExpressions.add(expressionToRewrite); | ||
Expression expressionShuttledToRewrite = sourceShuttledExpressions.get(index); | ||
if (expressionShuttledToRewrite instanceof Literal) { | ||
rewrittenExpressions.add(expressionShuttledToRewrite); | ||
continue; | ||
} | ||
final Set<Object> slotsToRewrite = expressionToRewrite.collectToSet( | ||
expression -> expression instanceof Slot); | ||
Expression replacedExpression = ExpressionUtils.replace(expressionToRewrite, | ||
final Set<Object> slotsToRewrite = | ||
expressionShuttledToRewrite.collectToSet(expression -> expression instanceof Slot); | ||
Expression replacedExpression = ExpressionUtils.replace(expressionShuttledToRewrite, | ||
targetToTargetReplacementMapping); | ||
if (replacedExpression.anyMatch(slotsToRewrite::contains)) { | ||
// if contains any slot to rewrite, which means can not be rewritten by target, bail out | ||
return ImmutableList.of(); | ||
} | ||
Expression sourceExpression = sourceExpressionsToWrite.get(index); | ||
if (sourceExpression instanceof NamedExpression | ||
&& replacedExpression.nullable() != sourceExpression.nullable()) { | ||
// if enable join eliminate, query maybe inner join and mv maybe outer join. | ||
// If the slot is at null generate side, the nullable maybe different between query and view | ||
// So need to force to consistent. | ||
replacedExpression = sourceExpression.nullable() ? | ||
new Nullable(replacedExpression) : new NonNullable(replacedExpression); | ||
} | ||
if (sourceExpression instanceof NamedExpression) { | ||
NamedExpression sourceNamedExpression = (NamedExpression) sourceExpression; | ||
replacedExpression = new Alias(sourceNamedExpression.getExprId(), replacedExpression, | ||
|
@@ -358,30 +369,92 @@ protected Expression rewriteExpression(Expression sourceExpressionsToWrite, Plan | |
* For another example as following: | ||
* predicate a = b in mv, and a = b and c = d in query, the compensatory predicate is c = d | ||
*/ | ||
protected SplitPredicate predicatesCompensate(StructInfo queryStructInfo, StructInfo viewStructInfo, | ||
SlotMapping queryToViewSlotMapping) { | ||
protected SplitPredicate predicatesCompensate( | ||
StructInfo queryStructInfo, | ||
StructInfo viewStructInfo, | ||
SlotMapping queryToViewSlotMapping, | ||
ComparisonResult comparisonResult | ||
) { | ||
// viewEquivalenceClass to query based | ||
SlotMapping viewToQuerySlotMapping = queryToViewSlotMapping.inverse(); | ||
final Set<Expression> equalCompensateConjunctions = compensateEquivalence( | ||
queryStructInfo, | ||
viewStructInfo, | ||
viewToQuerySlotMapping, | ||
comparisonResult); | ||
// range compensate | ||
final Set<Expression> rangeCompensatePredicates = compensateRangePredicate( | ||
queryStructInfo, | ||
viewStructInfo, | ||
viewToQuerySlotMapping, | ||
comparisonResult); | ||
// residual compensate | ||
final Set<Expression> residualCompensatePredicates = compensateResidualPredicate( | ||
queryStructInfo, | ||
viewStructInfo, | ||
viewToQuerySlotMapping, | ||
comparisonResult); | ||
// if the join type in query and mv plan is different, we should check and add filter on mv to make | ||
// the mv join type is accord with query | ||
Set<Set<Slot>> viewNoNullableSlot = comparisonResult.getViewNoNullableSlot(); | ||
// extract | ||
if (!viewNoNullableSlot.isEmpty()) { | ||
Set<Expression> queryUsedRejectNullSlotsViewBased = ExpressionUtils.extractConjunction( | ||
queryStructInfo.getSplitPredicate().getRangePredicate()).stream() | ||
.map(expression -> { | ||
if (TypeUtils.isNotNull(expression).isPresent()) { | ||
return ImmutableList.of(TypeUtils.isNotNull(expression).get()); | ||
} else { | ||
Set<Object> slotRefrenceSet = | ||
expression.collectToSet(expr -> expr instanceof SlotReference); | ||
if (slotRefrenceSet.size() != 1) { | ||
return null; | ||
} | ||
return slotRefrenceSet.iterator().next(); | ||
} | ||
}) | ||
.filter(Objects::nonNull) | ||
.map(expr -> ExpressionUtils.replace((Expression) expr, | ||
queryToViewSlotMapping.toSlotReferenceMap())) | ||
.collect(Collectors.toSet()); | ||
|
||
if (viewNoNullableSlot.stream().anyMatch( | ||
set -> Sets.intersection(set, queryUsedRejectNullSlotsViewBased).isEmpty())) { | ||
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. should be allMatch? 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. empty splitPredicate is invalid, so this should be anymatch |
||
return SplitPredicate.empty(); | ||
} | ||
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. what is empty splitPredicate meaning 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. Maybe an invalid instance is better 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. this means invalid, i will change it more semantic |
||
} | ||
return SplitPredicate.of(ExpressionUtils.and(equalCompensateConjunctions), | ||
rangeCompensatePredicates.isEmpty() ? BooleanLiteral.of(true) | ||
: ExpressionUtils.and(rangeCompensatePredicates), | ||
residualCompensatePredicates.isEmpty() ? BooleanLiteral.of(true) | ||
: ExpressionUtils.and(residualCompensatePredicates)); | ||
} | ||
|
||
protected Set<Expression> compensateEquivalence(StructInfo queryStructInfo, | ||
StructInfo viewStructInfo, | ||
SlotMapping viewToQuerySlotMapping, | ||
ComparisonResult comparisonResult) { | ||
EquivalenceClass queryEquivalenceClass = queryStructInfo.getEquivalenceClass(); | ||
EquivalenceClass viewEquivalenceClass = viewStructInfo.getEquivalenceClass(); | ||
// viewEquivalenceClass to query based | ||
Map<SlotReference, SlotReference> viewToQuerySlotMapping = queryToViewSlotMapping.inverse() | ||
.toSlotReferenceMap(); | ||
EquivalenceClass viewEquivalenceClassQueryBased = viewEquivalenceClass.permute(viewToQuerySlotMapping); | ||
Map<SlotReference, SlotReference> viewToQuerySlotMap = viewToQuerySlotMapping.toSlotReferenceMap(); | ||
EquivalenceClass viewEquivalenceClassQueryBased = viewEquivalenceClass.permute(viewToQuerySlotMap); | ||
if (viewEquivalenceClassQueryBased == null) { | ||
return SplitPredicate.empty(); | ||
return ImmutableSet.of(); | ||
} | ||
final List<Expression> equalCompensateConjunctions = new ArrayList<>(); | ||
final Set<Expression> equalCompensateConjunctions = new HashSet<>(); | ||
if (queryEquivalenceClass.isEmpty() && viewEquivalenceClass.isEmpty()) { | ||
equalCompensateConjunctions.add(BooleanLiteral.of(true)); | ||
} | ||
if (queryEquivalenceClass.isEmpty() && !viewEquivalenceClass.isEmpty()) { | ||
return SplitPredicate.empty(); | ||
if (queryEquivalenceClass.isEmpty() | ||
&& !viewEquivalenceClass.isEmpty()) { | ||
return ImmutableSet.of(); | ||
} | ||
EquivalenceClassSetMapping queryToViewEquivalenceMapping = EquivalenceClassSetMapping.generate( | ||
queryEquivalenceClass, viewEquivalenceClassQueryBased); | ||
EquivalenceClassSetMapping queryToViewEquivalenceMapping = | ||
EquivalenceClassSetMapping.generate(queryEquivalenceClass, viewEquivalenceClassQueryBased); | ||
// can not map all target equivalence class, can not compensate | ||
if (queryToViewEquivalenceMapping.getEquivalenceClassSetMap().size() | ||
< viewEquivalenceClass.getEquivalenceSetList().size()) { | ||
return SplitPredicate.empty(); | ||
return ImmutableSet.of(); | ||
} | ||
// do equal compensate | ||
Set<Set<SlotReference>> mappedQueryEquivalenceSet = | ||
|
@@ -410,49 +483,57 @@ protected SplitPredicate predicatesCompensate(StructInfo queryStructInfo, Struct | |
} | ||
} | ||
); | ||
// TODO range predicates and residual predicates compensate, Simplify implementation. | ||
return equalCompensateConjunctions; | ||
} | ||
|
||
protected Set<Expression> compensateResidualPredicate(StructInfo queryStructInfo, | ||
StructInfo viewStructInfo, | ||
SlotMapping viewToQuerySlotMapping, | ||
ComparisonResult comparisonResult) { | ||
SplitPredicate querySplitPredicate = queryStructInfo.getSplitPredicate(); | ||
SplitPredicate viewSplitPredicate = viewStructInfo.getSplitPredicate(); | ||
|
||
// range compensate | ||
List<Expression> rangeCompensate = new ArrayList<>(); | ||
Expression queryRangePredicate = querySplitPredicate.getRangePredicate(); | ||
Expression viewRangePredicate = viewSplitPredicate.getRangePredicate(); | ||
Expression viewRangePredicateQueryBased = ExpressionUtils.replace(viewRangePredicate, viewToQuerySlotMapping); | ||
|
||
Set<Expression> queryRangeSet = Sets.newHashSet(ExpressionUtils.extractConjunction(queryRangePredicate)); | ||
Set<Expression> viewRangeQueryBasedSet = Sets.newHashSet( | ||
ExpressionUtils.extractConjunction(viewRangePredicateQueryBased)); | ||
// query range predicate can not contain all view range predicate when view have range predicate, bail out | ||
if (!viewRangePredicateQueryBased.equals(BooleanLiteral.TRUE) && !queryRangeSet.containsAll( | ||
viewRangeQueryBasedSet)) { | ||
return SplitPredicate.empty(); | ||
} | ||
queryRangeSet.removeAll(viewRangeQueryBasedSet); | ||
rangeCompensate.addAll(queryRangeSet); | ||
|
||
// residual compensate | ||
List<Expression> residualCompensate = new ArrayList<>(); | ||
Expression queryResidualPredicate = querySplitPredicate.getResidualPredicate(); | ||
Expression viewResidualPredicate = viewSplitPredicate.getResidualPredicate(); | ||
Expression viewResidualPredicateQueryBased = | ||
ExpressionUtils.replace(viewResidualPredicate, viewToQuerySlotMapping); | ||
ExpressionUtils.replace(viewResidualPredicate, viewToQuerySlotMapping.toSlotReferenceMap()); | ||
Set<Expression> queryResidualSet = | ||
Sets.newHashSet(ExpressionUtils.extractConjunction(queryResidualPredicate)); | ||
Set<Expression> viewResidualQueryBasedSet = | ||
Sets.newHashSet(ExpressionUtils.extractConjunction(viewResidualPredicateQueryBased)); | ||
// query residual predicate can not contain all view residual predicate when view have residual predicate, | ||
// bail out | ||
if (!viewResidualPredicateQueryBased.equals(BooleanLiteral.TRUE) && !queryResidualSet.containsAll( | ||
viewResidualQueryBasedSet)) { | ||
return SplitPredicate.empty(); | ||
if (!viewResidualPredicateQueryBased.equals(BooleanLiteral.TRUE) | ||
&& !queryResidualSet.containsAll(viewResidualQueryBasedSet)) { | ||
return ImmutableSet.of(); | ||
} | ||
queryResidualSet.removeAll(viewResidualQueryBasedSet); | ||
residualCompensate.addAll(queryResidualSet); | ||
return queryResidualSet; | ||
} | ||
|
||
return SplitPredicate.of(ExpressionUtils.and(equalCompensateConjunctions), | ||
rangeCompensate.isEmpty() ? BooleanLiteral.of(true) : ExpressionUtils.and(rangeCompensate), | ||
residualCompensate.isEmpty() ? BooleanLiteral.of(true) : ExpressionUtils.and(residualCompensate)); | ||
protected Set<Expression> compensateRangePredicate(StructInfo queryStructInfo, | ||
StructInfo viewStructInfo, | ||
SlotMapping viewToQuerySlotMapping, | ||
ComparisonResult comparisonResult) { | ||
// TODO range predicates and residual predicates compensate, Simplify implementation. | ||
SplitPredicate querySplitPredicate = queryStructInfo.getSplitPredicate(); | ||
SplitPredicate viewSplitPredicate = viewStructInfo.getSplitPredicate(); | ||
|
||
Expression queryRangePredicate = querySplitPredicate.getRangePredicate(); | ||
Expression viewRangePredicate = viewSplitPredicate.getRangePredicate(); | ||
Expression viewRangePredicateQueryBased = | ||
ExpressionUtils.replace(viewRangePredicate, viewToQuerySlotMapping.toSlotReferenceMap()); | ||
|
||
Set<Expression> queryRangeSet = | ||
Sets.newHashSet(ExpressionUtils.extractConjunction(queryRangePredicate)); | ||
Set<Expression> viewRangeQueryBasedSet = | ||
Sets.newHashSet(ExpressionUtils.extractConjunction(viewRangePredicateQueryBased)); | ||
// query range predicate can not contain all view range predicate when view have range predicate, bail out | ||
if (!viewRangePredicateQueryBased.equals(BooleanLiteral.TRUE) | ||
&& !queryRangeSet.containsAll(viewRangeQueryBasedSet)) { | ||
return ImmutableSet.of(); | ||
} | ||
queryRangeSet.removeAll(viewRangeQueryBasedSet); | ||
return queryRangeSet; | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,10 @@ | |
import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression; | ||
import org.apache.doris.nereids.types.DataType; | ||
|
||
import com.google.common.base.Preconditions; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* change nullable input col to non_nullable col | ||
*/ | ||
|
@@ -39,4 +43,9 @@ public FunctionSignature customSignature() { | |
return FunctionSignature.ret(dataType).args(dataType); | ||
} | ||
|
||
@Override | ||
public Expression withChildren(List<Expression> children) { | ||
Preconditions.checkArgument(children.size() == 1); | ||
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. add check msg 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. OK |
||
return new NonNullable(children.get(0)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,10 @@ | |
import org.apache.doris.nereids.trees.expressions.shape.UnaryExpression; | ||
import org.apache.doris.nereids.types.DataType; | ||
|
||
import com.google.common.base.Preconditions; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* change non_nullable input col to nullable col | ||
*/ | ||
|
@@ -39,4 +43,9 @@ public FunctionSignature customSignature() { | |
return FunctionSignature.ret(dataType).args(dataType); | ||
} | ||
|
||
@Override | ||
public Expression withChildren(List<Expression> children) { | ||
Preconditions.checkArgument(children.size() == 1); | ||
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. add check msg 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. OK, I will fix it |
||
return new Nullable(children.get(0)); | ||
} | ||
} |
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.
maybe
requireNoNullableSlot
name is betterThere 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.
OK, it's better