|  | /** | 
|  | * Licensed to the Apache Software Foundation (ASF) under one | 
|  | * or more contributor license agreements.  See the NOTICE file | 
|  | * distributed with this work for additional information | 
|  | * regarding copyright ownership.  The ASF licenses this file | 
|  | * to you under the Apache License, Version 2.0 (the | 
|  | * "License"); you may not use this file except in compliance | 
|  | * with the License.  You may obtain a copy of the License at | 
|  | * | 
|  | *   http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, | 
|  | * software distributed under the License is distributed on an | 
|  | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
|  | * KIND, either express or implied.  See the License for the | 
|  | * specific language governing permissions and limitations | 
|  | * under the License. | 
|  | **/ | 
|  |  | 
|  | #include "query_optimizer/rules/GenerateJoins.hpp" | 
|  |  | 
|  | #include <memory> | 
|  | #include <unordered_map> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "query_optimizer/expressions/AttributeReference.hpp" | 
|  | #include "query_optimizer/expressions/ComparisonExpression.hpp" | 
|  | #include "query_optimizer/expressions/ExpressionUtil.hpp" | 
|  | #include "query_optimizer/expressions/LogicalAnd.hpp" | 
|  | #include "query_optimizer/expressions/PatternMatcher.hpp" | 
|  | #include "query_optimizer/expressions/Predicate.hpp" | 
|  | #include "query_optimizer/logical/Filter.hpp" | 
|  | #include "query_optimizer/logical/HashJoin.hpp" | 
|  | #include "query_optimizer/logical/Logical.hpp" | 
|  | #include "query_optimizer/logical/MultiwayCartesianJoin.hpp" | 
|  | #include "query_optimizer/logical/NestedLoopsJoin.hpp" | 
|  | #include "query_optimizer/logical/PatternMatcher.hpp" | 
|  | #include "query_optimizer/rules/Rule.hpp" | 
|  | #include "query_optimizer/rules/RuleHelper.hpp" | 
|  | #include "types/operations/comparisons/ComparisonFactory.hpp" | 
|  | #include "types/operations/comparisons/ComparisonID.hpp" | 
|  | #include "utility/SqlError.hpp" | 
|  | #include "utility/VectorUtil.hpp" | 
|  |  | 
|  | #include "glog/logging.h" | 
|  |  | 
|  | namespace quickstep { | 
|  | namespace optimizer { | 
|  |  | 
|  | namespace E = ::quickstep::optimizer::expressions; | 
|  | namespace L = ::quickstep::optimizer::logical; | 
|  |  | 
|  | typedef std::unordered_map<E::AttributeReferencePtr, | 
|  | L::LogicalPtr, | 
|  | E::NamedExpressionHash, | 
|  | E::NamedExpressionEqual> AttributeToLogicalMap; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Info for a hash join predicate. | 
|  | struct HashJoinPredicateInfo { | 
|  | HashJoinPredicateInfo(const E::AttributeReferencePtr &left_attribute_in, | 
|  | const E::AttributeReferencePtr &right_attribute_in) | 
|  | : left_join_attributes({left_attribute_in}), | 
|  | right_join_attributes({right_attribute_in}) {} | 
|  |  | 
|  | std::vector<E::AttributeReferencePtr> left_join_attributes; | 
|  | std::vector<E::AttributeReferencePtr> right_join_attributes; | 
|  | }; | 
|  |  | 
|  | typedef std::unordered_map<std::pair<L::LogicalPtr, L::LogicalPtr>, | 
|  | HashJoinPredicateInfo*> HashJoinPredicateInfoMap; | 
|  |  | 
|  | // Info for a non-hash join predicate. | 
|  | struct NonHashJoinPredicateInfo { | 
|  | explicit NonHashJoinPredicateInfo(const E::PredicatePtr &predicate_in) | 
|  | : predicate(predicate_in), | 
|  | referenced_attributes(predicate->getReferencedAttributes()) {} | 
|  |  | 
|  | // Returns true if the predicate is a binary predicate and returns the two | 
|  | // referenced logical nodes in <updated_referenced_logical_list>. | 
|  | // If the predicate involves only a single logical node, returns the node in | 
|  | // <updated_referenced_logical_list>. Otherwise, <updated_referenced_logical_list> | 
|  | // is empty. | 
|  | bool isBinaryJoin( | 
|  | const AttributeToLogicalMap &attribute_to_logical_map, | 
|  | std::vector<L::LogicalPtr> *updated_referenced_logical_list) const { | 
|  | for (const E::AttributeReferencePtr &referenced_attribute : | 
|  | referenced_attributes) { | 
|  | const AttributeToLogicalMap::const_iterator found_it = | 
|  | attribute_to_logical_map.find(referenced_attribute); | 
|  | DCHECK(found_it != attribute_to_logical_map.end()); | 
|  | if (AppendToVectorIfNotPresent(found_it->second, | 
|  | updated_referenced_logical_list) && | 
|  | updated_referenced_logical_list->size() > 2u) { | 
|  | updated_referenced_logical_list->clear(); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return updated_referenced_logical_list->size() == 2u; | 
|  | } | 
|  |  | 
|  | const E::PredicatePtr predicate; | 
|  | const std::vector<E::AttributeReferencePtr> referenced_attributes; | 
|  | }; | 
|  |  | 
|  | // Maps the output attributes of <logical_operator> to <logical_operator>. | 
|  | void MapAttributeToLogical(const L::LogicalPtr &logical_operator, | 
|  | AttributeToLogicalMap *attribute_to_logical_map) { | 
|  | for (const E::AttributeReferencePtr &output_attribute : | 
|  | logical_operator->getOutputAttributes()) { | 
|  | (*attribute_to_logical_map)[output_attribute] = logical_operator; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Generates n-1 NestedLoopsJoin with an empty predicate to combine all n | 
|  | // logical nodes in <inputs>. | 
|  | template <class Container> | 
|  | L::LogicalPtr MaybeGenerateNestedLoopsCartesianJoin(const Container &inputs) { | 
|  | typename Container::const_iterator non_joined_logical_it = inputs.begin(); | 
|  | L::LogicalPtr output = *non_joined_logical_it; | 
|  | ++non_joined_logical_it; | 
|  | while (non_joined_logical_it != inputs.end()) { | 
|  | output = L::NestedLoopsJoin::Create(output, | 
|  | *non_joined_logical_it, | 
|  | E::PredicatePtr()); | 
|  | ++non_joined_logical_it; | 
|  | } | 
|  | return output; | 
|  | } | 
|  |  | 
|  | // Classifies the predicates in <predicates> into hash join predicates and | 
|  | // non-hash join predicates. | 
|  | void ClassifyPredicates( | 
|  | const std::vector<E::PredicatePtr> &predicates, | 
|  | const AttributeToLogicalMap &attribute_to_logical_map, | 
|  | std::vector<std::unique_ptr<const HashJoinPredicateInfo>> *hash_join_predicates, | 
|  | std::vector<std::unique_ptr<const NonHashJoinPredicateInfo>> *non_hash_join_predicates) { | 
|  | DCHECK(hash_join_predicates->empty()); | 
|  | DCHECK(non_hash_join_predicates->empty()); | 
|  |  | 
|  | HashJoinPredicateInfoMap hash_join_predicate_map; | 
|  | for (const E::PredicatePtr &predicate : predicates) { | 
|  | E::ComparisonExpressionPtr comparison_predicate; | 
|  | bool is_hash_join_predicate = false; | 
|  |  | 
|  | // Determine whether it is a hash join predicate which should be an equality | 
|  | // predicate between two attribute references. | 
|  | if (E::SomeComparisonExpression::MatchesWithConditionalCast(predicate, | 
|  | &comparison_predicate)) { | 
|  | E::AttributeReferencePtr left_attribute_reference; | 
|  | E::AttributeReferencePtr right_attribute_reference; | 
|  | if (comparison_predicate->isEqualityComparisonPredicate() && | 
|  | E::SomeAttributeReference::MatchesWithConditionalCast(comparison_predicate->left(), | 
|  | &left_attribute_reference) && | 
|  | E::SomeAttributeReference::MatchesWithConditionalCast(comparison_predicate->right(), | 
|  | &right_attribute_reference)) { | 
|  | AttributeToLogicalMap::const_iterator left_logical_it = | 
|  | attribute_to_logical_map.find(left_attribute_reference); | 
|  | AttributeToLogicalMap::const_iterator right_logical_it = | 
|  | attribute_to_logical_map.find(right_attribute_reference); | 
|  |  | 
|  | if (left_logical_it->second != right_logical_it->second) { | 
|  | // Update join attributes if we have added the logical pair. | 
|  | const std::pair<L::LogicalPtr, L::LogicalPtr> logical_pair(left_logical_it->second, | 
|  | right_logical_it->second); | 
|  | const HashJoinPredicateInfoMap::iterator predicate_it = | 
|  | hash_join_predicate_map.find(logical_pair); | 
|  | if (predicate_it != hash_join_predicate_map.end()) { | 
|  | predicate_it->second->left_join_attributes.emplace_back(left_attribute_reference); | 
|  | predicate_it->second->right_join_attributes.emplace_back(right_attribute_reference); | 
|  | } else { | 
|  | const std::pair<L::LogicalPtr, L::LogicalPtr> reversed_logical_pair(right_logical_it->second, | 
|  | left_logical_it->second); | 
|  | const HashJoinPredicateInfoMap::iterator reversed_predicate_it = | 
|  | hash_join_predicate_map.find(reversed_logical_pair); | 
|  | if (reversed_predicate_it != hash_join_predicate_map.end()) { | 
|  | reversed_predicate_it->second->left_join_attributes.emplace_back(right_attribute_reference); | 
|  | reversed_predicate_it->second->right_join_attributes.emplace_back(left_attribute_reference); | 
|  | } else { | 
|  | // Create a new logical pair. | 
|  | std::unique_ptr<HashJoinPredicateInfo> hash_join_predicate( | 
|  | new HashJoinPredicateInfo(left_attribute_reference, | 
|  | right_attribute_reference)); | 
|  | hash_join_predicate_map.emplace( | 
|  | std::piecewise_construct, | 
|  | std::forward_as_tuple(left_logical_it->second, | 
|  | right_logical_it->second), | 
|  | std::forward_as_tuple(hash_join_predicate.get())); | 
|  | hash_join_predicates->emplace_back(hash_join_predicate.release()); | 
|  | } | 
|  | } | 
|  | is_hash_join_predicate = true; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Add it to <non_hash_join_predicates> if it is not a hash join predicate. | 
|  | if (!is_hash_join_predicate) { | 
|  | non_hash_join_predicates->emplace_back( | 
|  | new NonHashJoinPredicateInfo(predicate)); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Removes <left_logical> and <right_logical> from <logical_vec> and adds | 
|  | // <logical_join> into it. | 
|  | void UpdateLogicalVectors(const L::LogicalPtr &left_logical, | 
|  | const L::LogicalPtr &right_logical, | 
|  | const L::LogicalPtr &logical_join, | 
|  | std::vector<L::LogicalPtr> *logical_vec) { | 
|  | int num_removed_logical = 0; | 
|  | for (std::vector<L::LogicalPtr>::iterator logical_it = | 
|  | logical_vec->begin(); | 
|  | logical_it != logical_vec->end();) { | 
|  | if (*logical_it == left_logical || *logical_it == right_logical) { | 
|  | logical_it = logical_vec->erase(logical_it); | 
|  | ++num_removed_logical; | 
|  | if (num_removed_logical == 2) { | 
|  | break; | 
|  | } | 
|  | } else { | 
|  | ++logical_it; | 
|  | } | 
|  | } | 
|  | logical_vec->push_back(logical_join); | 
|  | } | 
|  |  | 
|  | // Creates a nested loops join between <left_logical> and <right_logical>, | 
|  | // updates the vector of unjoined logical nodes <unjoined_logical_vec> | 
|  | // and the map from attributes to unjoined logical nodes <attribute_logical_map>. | 
|  | // Note that we do not pass references for the first two arguments because they | 
|  | // may come from the fourth argument which is subject to change. | 
|  | void AddNestedLoopsJoin(const L::LogicalPtr left_logical, | 
|  | const L::LogicalPtr right_logical, | 
|  | const E::PredicatePtr &predicate, | 
|  | std::vector<L::LogicalPtr> *unjoined_logical_vec, | 
|  | AttributeToLogicalMap *attribute_logical_map) { | 
|  | const L::LogicalPtr join_logical = | 
|  | L::NestedLoopsJoin::Create(left_logical, right_logical, predicate); | 
|  | UpdateLogicalVectors(left_logical, right_logical, join_logical, | 
|  | unjoined_logical_vec); | 
|  | MapAttributeToLogical(join_logical, attribute_logical_map); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | L::LogicalPtr GenerateJoins::applyToNode(const L::LogicalPtr &input) { | 
|  | L::FilterPtr filter; | 
|  | L::MultiwayCartesianJoinPtr cartesian_join; | 
|  | L::HashJoinPtr hash_join; | 
|  |  | 
|  | // Merge filter predicates with cartesian joins to generate NestedLoopsJoin or | 
|  | // HashJoin. | 
|  | // TODO(qzeng): Maybe pull up MultiwayCartesianJoin first if there are | 
|  | // multiple MultiwayCartesianJoin and generate joins holistically for | 
|  | // all MultiwayCartesianJoin nodes. It have some benefits when tables in | 
|  | // the nested queries can only be hash joined with outer query tables. | 
|  | // However the case can be deemed as rare and it is low priority. | 
|  | if (L::SomeFilter::MatchesWithConditionalCast(input, &filter) && | 
|  | L::SomeMultiwayCartesianJoin::MatchesWithConditionalCast(filter->input(), &cartesian_join)) { | 
|  | const std::vector<L::LogicalPtr> join_inputs = cartesian_join->children(); | 
|  | // Logical nodes that have not participated in a join. | 
|  | std::vector<L::LogicalPtr> non_joined_logical_list(join_inputs.begin(), | 
|  | join_inputs.end()); | 
|  |  | 
|  | // Map from each attribute to the logical node that outputs the attribute. | 
|  | AttributeToLogicalMap attribute_to_logical_map; | 
|  | // Populate <attribute_to_logical_map>. | 
|  | for (const L::LogicalPtr &join_input : join_inputs) { | 
|  | MapAttributeToLogical(join_input, &attribute_to_logical_map); | 
|  | } | 
|  |  | 
|  | std::vector<std::unique_ptr<const HashJoinPredicateInfo>> | 
|  | hash_join_predicates; | 
|  | std::vector<std::unique_ptr<const NonHashJoinPredicateInfo>> | 
|  | non_hash_join_predicates; | 
|  | // Classify the predicates in Filter to either hash join predicates or non | 
|  | // hash join predicates. | 
|  | ClassifyPredicates(GetConjunctivePredicates(filter->filter_predicate()), | 
|  | attribute_to_logical_map, | 
|  | &hash_join_predicates, | 
|  | &non_hash_join_predicates); | 
|  |  | 
|  | // The predicates that are not used in any joins. | 
|  | std::vector<E::PredicatePtr> filter_predicates; | 
|  |  | 
|  | // First, create an inner HashJoin for each hash join predicate in the order | 
|  | // as specified in the query. | 
|  | for (const std::unique_ptr<const HashJoinPredicateInfo> & | 
|  | hash_join_predicate_info : hash_join_predicates) { | 
|  | const L::LogicalPtr left_logical = | 
|  | attribute_to_logical_map[hash_join_predicate_info->left_join_attributes[0]]; | 
|  | const L::LogicalPtr right_logical = | 
|  | attribute_to_logical_map[hash_join_predicate_info->right_join_attributes[0]]; | 
|  | DCHECK(left_logical != nullptr); | 
|  | DCHECK(right_logical != nullptr); | 
|  |  | 
|  | if (left_logical != right_logical) { | 
|  | L::LogicalPtr logical_join = L::HashJoin::Create( | 
|  | left_logical, | 
|  | right_logical, | 
|  | hash_join_predicate_info->left_join_attributes, | 
|  | hash_join_predicate_info->right_join_attributes, | 
|  | nullptr /* residual_predicate */, | 
|  | L::HashJoin::JoinType::kInnerJoin); | 
|  | UpdateLogicalVectors(left_logical, | 
|  | right_logical, | 
|  | logical_join, | 
|  | &non_joined_logical_list); | 
|  | MapAttributeToLogical(logical_join, &attribute_to_logical_map); | 
|  | } else { | 
|  | std::vector<E::PredicatePtr> equality_predicates; | 
|  | for (std::vector<E::AttributeReferencePtr>::size_type attr_idx = 0; | 
|  | attr_idx < hash_join_predicate_info->left_join_attributes.size(); | 
|  | ++attr_idx) { | 
|  | filter_predicates.emplace_back( | 
|  | E::ComparisonExpression::Create(ComparisonFactory::GetComparison(ComparisonID::kEqual), | 
|  | hash_join_predicate_info->left_join_attributes[attr_idx], | 
|  | hash_join_predicate_info->right_join_attributes[attr_idx])); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Second, add NestedLoopsJoin until there is no join predicates left. The | 
|  | // loop is guaranteed to be terminated, since each iteration will eliminate | 
|  | // at least one predicate. | 
|  | while (!non_hash_join_predicates.empty()) { | 
|  | bool has_new_join = false; | 
|  | std::vector<std::unique_ptr<const NonHashJoinPredicateInfo>> | 
|  | new_non_hash_join_predicates; | 
|  | for (std::unique_ptr<const NonHashJoinPredicateInfo> &non_hash_join_predicate : non_hash_join_predicates) { | 
|  | std::vector<L::LogicalPtr> referenced_logical_list; | 
|  |  | 
|  | // 1. If the join predicate is binary, join the two referenced logical | 
|  | //    nodes. | 
|  | // 2. If the join predicate involves more than two logical nodes, keep | 
|  | //    it in <new_non_hash_join_predicates> for consideration in the next iteration. | 
|  | // 3. If the join predicate can be applied to exactly one logical node, | 
|  | //    add it to unused_predicates which will be later added into a Filter. | 
|  | if (non_hash_join_predicate->isBinaryJoin(attribute_to_logical_map, | 
|  | &referenced_logical_list)) { | 
|  | AddNestedLoopsJoin( | 
|  | referenced_logical_list[0], | 
|  | referenced_logical_list[1], | 
|  | non_hash_join_predicate->predicate, | 
|  | &non_joined_logical_list, | 
|  | &attribute_to_logical_map); | 
|  | has_new_join = true; | 
|  | } else if (!referenced_logical_list.empty()) { | 
|  | DCHECK_EQ(referenced_logical_list.size(), 1u); | 
|  | filter_predicates.push_back(non_hash_join_predicate->predicate); | 
|  | } else { | 
|  | new_non_hash_join_predicates.emplace_back( | 
|  | non_hash_join_predicate.release()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Update non_hash_join_predicates with new_non_hash_join_predicate for | 
|  | // use in the next iteration. | 
|  | non_hash_join_predicates.clear(); | 
|  | for (std::unique_ptr<const NonHashJoinPredicateInfo> &new_non_hash_join_predicate : | 
|  | new_non_hash_join_predicates) { | 
|  | non_hash_join_predicates.emplace_back( | 
|  | new_non_hash_join_predicate.release()); | 
|  | } | 
|  |  | 
|  | // If we did not find a binary join predicate, join the first two logical | 
|  | // nodes in <non_joined_logical_list>. | 
|  | if (!has_new_join && non_joined_logical_list.size() >= 2u) { | 
|  | AddNestedLoopsJoin(non_joined_logical_list[0], | 
|  | non_joined_logical_list[1], | 
|  | E::PredicatePtr(), | 
|  | &non_joined_logical_list, | 
|  | &attribute_to_logical_map); | 
|  | } | 
|  | } | 
|  |  | 
|  | DCHECK(non_hash_join_predicates.empty()); | 
|  | // Assert that there must be one remaining logical node. | 
|  | DCHECK(!non_joined_logical_list.empty()); | 
|  |  | 
|  | // Cross product all remaining logical nodes. | 
|  | L::LogicalPtr output = MaybeGenerateNestedLoopsCartesianJoin(non_joined_logical_list); | 
|  |  | 
|  | if (!filter_predicates.empty()) { | 
|  | output = L::Filter::Create(output, filter_predicates); | 
|  | } | 
|  |  | 
|  | LOG_APPLYING_RULE(input, output); | 
|  | return output; | 
|  | } else if (L::SomeMultiwayCartesianJoin::MatchesWithConditionalCast(input, &cartesian_join)) { | 
|  | // If there is no filter upon the cartesian join node, convert it to nested | 
|  | // loop cartesian joins. | 
|  | L::LogicalPtr output = MaybeGenerateNestedLoopsCartesianJoin(cartesian_join->operands()); | 
|  | LOG_APPLYING_RULE(input, output); | 
|  | return output; | 
|  | } else if (L::SomeHashJoin::MatchesWithConditionalCast(input, &hash_join) && | 
|  | hash_join->join_type() == L::HashJoin::JoinType::kLeftOuterJoin) { | 
|  | L::LogicalPtr left_logical = hash_join->left(); | 
|  | L::LogicalPtr right_logical = hash_join->right(); | 
|  |  | 
|  | const std::vector<E::AttributeReferencePtr> left_relation_attributes = | 
|  | left_logical->getOutputAttributes(); | 
|  | const std::vector<E::AttributeReferencePtr> right_relation_attributes = | 
|  | right_logical->getOutputAttributes(); | 
|  |  | 
|  | DCHECK(hash_join->residual_predicate() != nullptr); | 
|  | const std::vector<E::PredicatePtr> on_predicate_items = | 
|  | GetConjunctivePredicates(hash_join->residual_predicate()); | 
|  |  | 
|  | std::vector<E::AttributeReferencePtr> left_join_attributes; | 
|  | std::vector<E::AttributeReferencePtr> right_join_attributes; | 
|  | std::vector<E::PredicatePtr> left_filter_predicates; | 
|  | std::vector<E::PredicatePtr> right_filter_predicates; | 
|  |  | 
|  | for (const E::PredicatePtr &predicate_item : on_predicate_items) { | 
|  | std::vector<E::AttributeReferencePtr> referenced_attrs = | 
|  | predicate_item->getReferencedAttributes(); | 
|  |  | 
|  | if (E::SubsetOfExpressions(referenced_attrs, left_relation_attributes)) { | 
|  | // The predicate refers attributes only from the left relation. | 
|  | left_filter_predicates.emplace_back(predicate_item); | 
|  | } else if (E::SubsetOfExpressions(referenced_attrs, right_relation_attributes)) { | 
|  | // The predicate refers attributes only from the right relation. | 
|  | right_filter_predicates.emplace_back(predicate_item); | 
|  | } else { | 
|  | // The predicate refers attributes from both relations. | 
|  | // | 
|  | // NOTE(jianqiao): In this case, since currently in the backend we do not | 
|  | // support residual predicates for hash outer joins, so the predicate can | 
|  | // only be an equal comparison between two attributes (one from left and | 
|  | // one from right). | 
|  | E::ComparisonExpressionPtr comp_expr; | 
|  | if (E::SomeComparisonExpression::MatchesWithConditionalCast(predicate_item, | 
|  | &comp_expr)) { | 
|  | E::AttributeReferencePtr left_attr; | 
|  | E::AttributeReferencePtr right_attr; | 
|  | if (comp_expr->isEqualityComparisonPredicate() && | 
|  | E::SomeAttributeReference::MatchesWithConditionalCast(comp_expr->left(), &left_attr) && | 
|  | E::SomeAttributeReference::MatchesWithConditionalCast(comp_expr->right(), &right_attr)) { | 
|  | if (E::ContainsExpression(left_relation_attributes, left_attr)) { | 
|  | DCHECK(E::ContainsExpression(right_relation_attributes, right_attr)); | 
|  |  | 
|  | left_join_attributes.emplace_back(left_attr); | 
|  | right_join_attributes.emplace_back(right_attr); | 
|  | } else { | 
|  | DCHECK(E::ContainsExpression(left_relation_attributes, right_attr)); | 
|  | DCHECK(E::ContainsExpression(right_relation_attributes, left_attr)); | 
|  |  | 
|  | left_join_attributes.emplace_back(right_attr); | 
|  | right_join_attributes.emplace_back(left_attr); | 
|  | } | 
|  | } else { | 
|  | THROW_SQL_ERROR() << "Join predicate for outer joins must be an " | 
|  | << "equality comparison between attributes"; | 
|  | } | 
|  | } else { | 
|  | THROW_SQL_ERROR() << "Non-equality join predicate is not allowed for outer joins"; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!left_filter_predicates.empty()) { | 
|  | // NOTE(jianqiao): Filter predicates on left table cannot be pushed down | 
|  | // but can be treated as residual predicates once we have support for that. | 
|  | THROW_SQL_ERROR() << "Filter predicates on left table is not allowed for outer joins"; | 
|  | } | 
|  |  | 
|  | if (!right_filter_predicates.empty()) { | 
|  | const E::PredicatePtr right_predicate = | 
|  | (right_filter_predicates.size() == 1u ? right_filter_predicates[0] | 
|  | : E::LogicalAnd::Create(right_filter_predicates)); | 
|  | right_logical = L::Filter::Create(right_logical, right_predicate); | 
|  | } | 
|  |  | 
|  | return L::HashJoin::Create(left_logical, | 
|  | right_logical, | 
|  | left_join_attributes, | 
|  | right_join_attributes, | 
|  | nullptr /* residual_predicate */, | 
|  | L::HashJoin::JoinType::kLeftOuterJoin); | 
|  | } | 
|  |  | 
|  | LOG_IGNORING_RULE(input); | 
|  | return input; | 
|  | } | 
|  |  | 
|  | }  // namespace optimizer | 
|  | }  // namespace quickstep |