Current section
Files
Jump to
Current section
Files
c_src/duckdb/src/planner/binder/tableref/plan_joinref.cpp
#include "duckdb/planner/expression/bound_columnref_expression.hpp"
#include "duckdb/planner/expression/bound_comparison_expression.hpp"
#include "duckdb/planner/expression/bound_conjunction_expression.hpp"
#include "duckdb/planner/expression/bound_constant_expression.hpp"
#include "duckdb/planner/expression/bound_operator_expression.hpp"
#include "duckdb/planner/expression/bound_subquery_expression.hpp"
#include "duckdb/planner/expression_iterator.hpp"
#include "duckdb/planner/binder.hpp"
#include "duckdb/planner/operator/logical_any_join.hpp"
#include "duckdb/planner/operator/logical_comparison_join.hpp"
#include "duckdb/planner/operator/logical_cross_product.hpp"
#include "duckdb/planner/operator/logical_filter.hpp"
#include "duckdb/planner/tableref/bound_joinref.hpp"
#include "duckdb/main/client_context.hpp"
namespace duckdb {
//! Create a JoinCondition from a comparison
static bool CreateJoinCondition(Expression &expr, unordered_set<idx_t> &left_bindings,
unordered_set<idx_t> &right_bindings, vector<JoinCondition> &conditions) {
// comparison
auto &comparison = (BoundComparisonExpression &)expr;
auto left_side = JoinSide::GetJoinSide(*comparison.left, left_bindings, right_bindings);
auto right_side = JoinSide::GetJoinSide(*comparison.right, left_bindings, right_bindings);
if (left_side != JoinSide::BOTH && right_side != JoinSide::BOTH) {
// join condition can be divided in a left/right side
JoinCondition condition;
condition.comparison = expr.type;
auto left = move(comparison.left);
auto right = move(comparison.right);
if (left_side == JoinSide::RIGHT) {
// left = right, right = left, flip the comparison symbol and reverse sides
swap(left, right);
condition.comparison = FlipComparisionExpression(expr.type);
}
condition.left = move(left);
condition.right = move(right);
conditions.push_back(move(condition));
return true;
}
return false;
}
unique_ptr<LogicalOperator> LogicalComparisonJoin::CreateJoin(JoinType type, unique_ptr<LogicalOperator> left_child,
unique_ptr<LogicalOperator> right_child,
unordered_set<idx_t> &left_bindings,
unordered_set<idx_t> &right_bindings,
vector<unique_ptr<Expression>> &expressions) {
vector<JoinCondition> conditions;
vector<unique_ptr<Expression>> arbitrary_expressions;
// first check if we can create
for (auto &expr : expressions) {
auto total_side = JoinSide::GetJoinSide(*expr, left_bindings, right_bindings);
if (total_side != JoinSide::BOTH) {
// join condition does not reference both sides, add it as filter under the join
if (type == JoinType::LEFT && total_side == JoinSide::RIGHT) {
// filter is on RHS and the join is a LEFT OUTER join, we can push it in the right child
if (right_child->type != LogicalOperatorType::LOGICAL_FILTER) {
// not a filter yet, push a new empty filter
auto filter = make_unique<LogicalFilter>();
filter->AddChild(move(right_child));
right_child = move(filter);
}
// push the expression into the filter
auto &filter = (LogicalFilter &)*right_child;
filter.expressions.push_back(move(expr));
continue;
}
} else if ((expr->type >= ExpressionType::COMPARE_EQUAL &&
expr->type <= ExpressionType::COMPARE_GREATERTHANOREQUALTO) ||
expr->type == ExpressionType::COMPARE_DISTINCT_FROM ||
expr->type == ExpressionType::COMPARE_NOT_DISTINCT_FROM) {
// comparison, check if we can create a comparison JoinCondition
if (CreateJoinCondition(*expr, left_bindings, right_bindings, conditions)) {
// successfully created the join condition
continue;
}
}
arbitrary_expressions.push_back(move(expr));
}
bool need_to_consider_arbitrary_expressions = true;
if (type == JoinType::INNER) {
// for inner joins we can push arbitrary expressions as a filter
// here we prefer to create a comparison join if possible
// that way we can use the much faster hash join to process the main join
// rather than doing a nested loop join to handle arbitrary expressions
// for left and full outer joins we HAVE to process all join conditions
// because pushing a filter will lead to an incorrect result, as non-matching tuples cannot be filtered out
need_to_consider_arbitrary_expressions = false;
}
if ((need_to_consider_arbitrary_expressions && !arbitrary_expressions.empty()) || conditions.empty()) {
if (arbitrary_expressions.empty()) {
// all conditions were pushed down, add TRUE predicate
arbitrary_expressions.push_back(make_unique<BoundConstantExpression>(Value::BOOLEAN(true)));
}
for (auto &condition : conditions) {
arbitrary_expressions.push_back(JoinCondition::CreateExpression(move(condition)));
}
// if we get here we could not create any JoinConditions
// turn this into an arbitrary expression join
auto any_join = make_unique<LogicalAnyJoin>(type);
// create the condition
any_join->children.push_back(move(left_child));
any_join->children.push_back(move(right_child));
// AND all the arbitrary expressions together
// do the same with any remaining conditions
any_join->condition = move(arbitrary_expressions[0]);
for (idx_t i = 1; i < arbitrary_expressions.size(); i++) {
any_join->condition = make_unique<BoundConjunctionExpression>(
ExpressionType::CONJUNCTION_AND, move(any_join->condition), move(arbitrary_expressions[i]));
}
return move(any_join);
} else {
// we successfully converted expressions into JoinConditions
// create a LogicalComparisonJoin
auto comp_join = make_unique<LogicalComparisonJoin>(type);
comp_join->conditions = move(conditions);
comp_join->children.push_back(move(left_child));
comp_join->children.push_back(move(right_child));
if (!arbitrary_expressions.empty()) {
// we have some arbitrary expressions as well
// add them to a filter
auto filter = make_unique<LogicalFilter>();
for (auto &expr : arbitrary_expressions) {
filter->expressions.push_back(move(expr));
}
LogicalFilter::SplitPredicates(filter->expressions);
filter->children.push_back(move(comp_join));
return move(filter);
}
return move(comp_join);
}
}
static bool HasCorrelatedColumns(Expression &expression) {
if (expression.type == ExpressionType::BOUND_COLUMN_REF) {
auto &colref = (BoundColumnRefExpression &)expression;
if (colref.depth > 0) {
return true;
}
}
bool has_correlated_columns = false;
ExpressionIterator::EnumerateChildren(expression, [&](Expression &child) {
if (HasCorrelatedColumns(child)) {
has_correlated_columns = true;
}
});
return has_correlated_columns;
}
unique_ptr<LogicalOperator> Binder::CreatePlan(BoundJoinRef &ref) {
auto left = CreatePlan(*ref.left);
auto right = CreatePlan(*ref.right);
if (ref.type == JoinType::RIGHT && context.enable_optimizer) {
// we turn any right outer joins into left outer joins for optimization purposes
// they are the same but with sides flipped, so treating them the same simplifies life
ref.type = JoinType::LEFT;
std::swap(left, right);
}
if (ref.type == JoinType::INNER && (ref.condition->HasSubquery() || HasCorrelatedColumns(*ref.condition))) {
// inner join, generate a cross product + filter
// this will be later turned into a proper join by the join order optimizer
auto cross_product = make_unique<LogicalCrossProduct>();
cross_product->AddChild(move(left));
cross_product->AddChild(move(right));
unique_ptr<LogicalOperator> root = move(cross_product);
auto filter = make_unique<LogicalFilter>(move(ref.condition));
// visit the expressions in the filter
for (auto &expression : filter->expressions) {
PlanSubqueries(&expression, &root);
}
filter->AddChild(move(root));
return move(filter);
}
// split the expressions by the AND clause
vector<unique_ptr<Expression>> expressions;
expressions.push_back(move(ref.condition));
LogicalFilter::SplitPredicates(expressions);
// find the table bindings on the LHS and RHS of the join
unordered_set<idx_t> left_bindings, right_bindings;
LogicalJoin::GetTableReferences(*left, left_bindings);
LogicalJoin::GetTableReferences(*right, right_bindings);
// now create the join operator from the set of join conditions
auto result = LogicalComparisonJoin::CreateJoin(ref.type, move(left), move(right), left_bindings, right_bindings,
expressions);
LogicalOperator *join;
if (result->type == LogicalOperatorType::LOGICAL_FILTER) {
join = result->children[0].get();
} else {
join = result.get();
}
for (auto &child : join->children) {
if (child->type == LogicalOperatorType::LOGICAL_FILTER) {
auto &filter = (LogicalFilter &)*child;
for (auto &expr : filter.expressions) {
PlanSubqueries(&expr, &filter.children[0]);
}
}
}
// we visit the expressions depending on the type of join
if (join->type == LogicalOperatorType::LOGICAL_COMPARISON_JOIN) {
// comparison join
// in this join we visit the expressions on the LHS with the LHS as root node
// and the expressions on the RHS with the RHS as root node
auto &comp_join = (LogicalComparisonJoin &)*join;
for (idx_t i = 0; i < comp_join.conditions.size(); i++) {
PlanSubqueries(&comp_join.conditions[i].left, &comp_join.children[0]);
PlanSubqueries(&comp_join.conditions[i].right, &comp_join.children[1]);
}
} else if (join->type == LogicalOperatorType::LOGICAL_ANY_JOIN) {
auto &any_join = (LogicalAnyJoin &)*join;
// for the any join we just visit the condition
if (any_join.condition->HasSubquery()) {
throw NotImplementedException("Cannot perform non-inner join on subquery!");
}
}
return result;
}
} // namespace duckdb