#include "optimizer/remove_unnecessary_join_optimizer.h"
#include "planner/operator/logical_accumulate.h"
#include "planner/operator/logical_cross_product.h"
#include "planner/operator/logical_hash_join.h"
#include "planner/operator/scan/logical_scan_node_table.h"
using namespace lbug::binder;
using namespace lbug::common;
using namespace lbug::planner;
namespace lbug {
namespace optimizer {
void RemoveUnnecessaryJoinOptimizer::rewrite(LogicalPlan* plan) {
visitOperator(plan->getLastOperator());
}
std::shared_ptr<LogicalOperator> RemoveUnnecessaryJoinOptimizer::visitOperator(
const std::shared_ptr<LogicalOperator>& op) {
for (auto i = 0u; i < op->getNumChildren(); ++i) {
op->setChild(i, visitOperator(op->getChild(i)));
}
auto result = visitOperatorReplaceSwitch(op);
result->computeFlatSchema();
return result;
}
static bool nullsBuildOnlyColumns(JoinType joinType) {
switch (joinType) {
case JoinType::INNER:
return false;
case JoinType::MARK:
case JoinType::COUNT:
return false;
case JoinType::LEFT:
return true;
}
return true;
}
static bool isOptional(AccumulateType accumulateType) {
switch (accumulateType) {
case AccumulateType::REGULAR:
return false;
case AccumulateType::OPTIONAL_:
return true;
}
return true;
}
static bool canBeNull(const LogicalOperator& op, const Expression& key) {
switch (op.getOperatorType()) {
case LogicalOperatorType::HASH_JOIN: {
const auto& join = op.constCast<LogicalHashJoin>();
if (nullsBuildOnlyColumns(join.getJoinType()) && !join.hasMark() &&
!op.getChild(0)->getSchema()->isExpressionInScope(key) &&
op.getChild(1)->getSchema()->isExpressionInScope(key)) {
return true;
}
} break;
case LogicalOperatorType::CROSS_PRODUCT: {
const auto& crossProduct = op.constCast<LogicalCrossProduct>();
if (isOptional(crossProduct.getAccumulateType()) && !crossProduct.hasMark() &&
op.getChild(1)->getSchema()->isExpressionInScope(key)) {
return true;
}
} break;
case LogicalOperatorType::ACCUMULATE: {
const auto& accumulate = op.constCast<LogicalAccumulate>();
if (isOptional(accumulate.getAccumulateType()) && !accumulate.hasMark() &&
op.getChild(0)->getSchema()->isExpressionInScope(key)) {
return true;
}
} break;
default:
break;
}
for (auto i = 0u; i < op.getNumChildren(); ++i) {
if (canBeNull(*op.getChild(i), key)) {
return true;
}
}
return false;
}
static bool keptSideKeysCanBeNull(const LogicalHashJoin& hashJoin, uint32_t keptChildIdx) {
for (auto& [probeKey, buildKey] : hashJoin.getJoinConditions()) {
const auto& key = keptChildIdx == 0 ? *probeKey : *buildKey;
if (canBeNull(*hashJoin.getChild(keptChildIdx), key)) {
return true;
}
}
return false;
}
std::shared_ptr<LogicalOperator> RemoveUnnecessaryJoinOptimizer::visitHashJoinReplace(
std::shared_ptr<LogicalOperator> op) {
auto hashJoin = (LogicalHashJoin*)op.get();
switch (hashJoin->getJoinType()) {
case JoinType::MARK:
case JoinType::LEFT: {
return op;
}
default:
break;
}
if (op->getChild(1)->getOperatorType() == LogicalOperatorType::SCAN_NODE_TABLE) {
const auto scanNode = dynamic_cast_checked<LogicalScanNodeTable*>(op->getChild(1).get());
if (scanNode->getProperties().empty() && !keptSideKeysCanBeNull(*hashJoin, 0)) {
return op->getChild(0);
}
}
if (op->getChild(0)->getOperatorType() == LogicalOperatorType::SCAN_NODE_TABLE) {
const auto scanNode = dynamic_cast_checked<LogicalScanNodeTable*>(op->getChild(0).get());
if (scanNode->getProperties().empty() && !keptSideKeysCanBeNull(*hashJoin, 1)) {
return op->getChild(1);
}
}
return op;
}
} }