protected override BoundRelation RewriteJoinRelation(BoundJoinRelation node) { // First, let's rewrite our inputs node = (BoundJoinRelation)base.RewriteJoinRelation(node); // Get defined values of left and right var leftDefinedValues = new HashSet <ValueSlot>(node.Left.GetDefinedValues()); var rightDefinedValues = new HashSet <ValueSlot>(node.Right.GetDefinedValues()); var extractedConjunctions = new List <BoundExpression>(); // Try to pull up conjunctions that contain outer references from a left sided filter and combine // them with the join predicate. // // NOTE: This is only possible if the join is not a LEFT OUTER or FULL OUTER JOIN since this // operation would change the join's semantic. if (node.JoinType != BoundJoinType.LeftOuter && node.JoinType != BoundJoinType.FullOuter) { if (node.Left is BoundFilterRelation leftAsFilter) { var anythingExtracted = false; var remainingConjunctions = new List <BoundExpression>(); foreach (var conjunction in Expression.SplitConjunctions(leftAsFilter.Condition)) { if (!ConjunctionHasOuterReference(leftDefinedValues, conjunction)) { remainingConjunctions.Add(conjunction); } else { anythingExtracted = true; extractedConjunctions.Add(conjunction); } } if (!anythingExtracted) { // We haven't extracted any conjunctions. // // In order to avoid creating new node, we just do nothing. } else { var newCondition = Expression.And(remainingConjunctions); if (newCondition == null) { node = node.WithLeft(leftAsFilter.Input); } else { leftAsFilter = leftAsFilter.WithCondition(newCondition); node = node.WithLeft(leftAsFilter); } } } } // Try to pull up conjunctions that contain outer references from a right sided filter and combine // them with the join predicate. // // NOTE: This is only possible if the join is not a RIGHT OUTER or FULL OUTER JOIN since this // operation would change the join's semantic. if (node.JoinType != BoundJoinType.RightOuter && node.JoinType != BoundJoinType.FullOuter) { if (node.Right is BoundFilterRelation rightAsFilter) { var anythingExtracted = false; var remainingConjunctions = new List <BoundExpression>(); foreach (var conjunction in Expression.SplitConjunctions(rightAsFilter.Condition)) { if (!ConjunctionHasOuterReference(rightDefinedValues, conjunction)) { remainingConjunctions.Add(conjunction); } else { anythingExtracted = true; extractedConjunctions.Add(conjunction); } } if (!anythingExtracted) { // We haven't extracted any conjunctions. // // In order to avoid creating new node, we just do nothing. } else { var newCondition = Expression.And(remainingConjunctions); if (newCondition == null) { node = node.WithRight(rightAsFilter.Input); } else { rightAsFilter = rightAsFilter.WithCondition(newCondition); node = node.WithRight(rightAsFilter); } } } } // If we found any conjunctions that could be pulled up, merge them with the join predicate. if (extractedConjunctions.Any()) { var newCondition = Expression.And(new[] { node.Condition }.Concat(extractedConjunctions)); node = node.WithCondition(newCondition); } // Now we try to extract conjunctions that contain outer references from the join // predicate itself. // // NOTE: This is only possible if the node is not an OUTER JOIN. If the node is a // SEMI JOIN the operation is only legal if the conjunction does not reference any // columns from the side that is used as filter criteria (i.e. for LSJ this is the // right side, for RSJ this is the left side). if (node.JoinType != BoundJoinType.LeftOuter && node.JoinType != BoundJoinType.RightOuter && node.JoinType != BoundJoinType.FullOuter && node.Condition != null) { var conjunctionsAboveJoin = new List <BoundExpression>(); var remainingConjunctions = new List <BoundExpression>(); var definedValues = new HashSet <ValueSlot>(leftDefinedValues.Concat(rightDefinedValues)); foreach (var conjunction in Expression.SplitConjunctions(node.Condition)) { if (ConjunctionHasOuterReference(definedValues, conjunction) && SemiJoinDoesNotDependOn(node.JoinType, conjunction, rightDefinedValues)) { conjunctionsAboveJoin.Add(conjunction); } else { remainingConjunctions.Add(conjunction); } } if (conjunctionsAboveJoin.Any()) { var newCondition = Expression.And(remainingConjunctions); node = node.WithCondition(newCondition); var filterCondition = Expression.And(conjunctionsAboveJoin); var filter = new BoundFilterRelation(node, filterCondition); return(filter); } } return(node); }
private BoundRelation MergeWithOrPushOverJoin(BoundFilterRelation node, BoundJoinRelation input) { // TODO: Right now, we're not pushing over a join if it has any probing. // // That might be too restristive. It should be OK to push a over // a join to the side that isn't affecting the probe column. // // In other words: // // * pushing to the left is OK if it's a left (anti) semi join // * pushing to the right is OK if it's a right (anti) semi join if (input.Probe != null) { return(node.WithInput(RewriteRelation(input))); } if (AllowsMerge(input.JoinType)) { var newCondition = Expression.And(input.Condition, node.Condition); var newInput = input.WithCondition(newCondition); return(RewriteRelation(newInput)); } else { BoundExpression pushedLeft = null; BoundExpression pushedRight = null; BoundExpression remainder = null; foreach (var conjunction in Expression.SplitConjunctions(node.Condition)) { if (AllowsLeftPushOver(input.JoinType) && !conjunction.DependsOnAny(input.Right.GetOutputValues())) { pushedLeft = Expression.And(pushedLeft, conjunction); } else if (AllowsRightPushOver(input.JoinType) && !conjunction.DependsOnAny(input.Left.GetOutputValues())) { pushedRight = Expression.And(pushedRight, conjunction); } else { remainder = Expression.And(remainder, conjunction); } } var newLeft = pushedLeft == null ? input.Left : new BoundFilterRelation(input.Left, pushedLeft); var newRight = pushedRight == null ? input.Right : new BoundFilterRelation(input.Right, pushedRight); var newInput = input.Update(input.JoinType, RewriteRelation(newLeft), RewriteRelation(newRight), input.Condition, RewriteValueSlot(input.Probe), RewriteExpression(input.PassthruPredicate)); var newNode = remainder == null ? (BoundRelation)newInput : node.Update(newInput, remainder); return(newNode); } }