P::PhysicalPtr InjectJoinFilters::transformHashJoinToFilters(
    const P::PhysicalPtr &input) const {
  std::vector<P::PhysicalPtr> new_children;
  for (const P::PhysicalPtr &child : input->children()) {
    new_children.emplace_back(transformHashJoinToFilters(child));
  }

  P::HashJoinPtr hash_join;
  if (P::SomeHashJoin::MatchesWithConditionalCast(input, &hash_join) &&
      isTransformable(hash_join)) {
    const bool is_anti_join =
        hash_join->join_type() == P::HashJoin::JoinType::kLeftAntiJoin;

    DCHECK_EQ(2u, new_children.size());
    P::PhysicalPtr build_child = new_children[1];
    E::PredicatePtr build_side_filter_predicate = nullptr;
    P::SelectionPtr selection;
    if (hash_join->build_predicate() == nullptr) {
      if (P::SomeSelection::MatchesWithConditionalCast(build_child, &selection) &&
          E::SubsetOfExpressions(hash_join->right_join_attributes(),
                                 selection->input()->getOutputAttributes())) {
        build_child = selection->input();
        build_side_filter_predicate = selection->filter_predicate();
      }
    } else {
      build_child = hash_join->right();
      build_side_filter_predicate = hash_join->build_predicate();
    }
    return P::FilterJoin::Create(new_children[0],
                                 build_child,
                                 hash_join->left_join_attributes(),
                                 hash_join->right_join_attributes(),
                                 hash_join->project_expressions(),
                                 build_side_filter_predicate,
                                 is_anti_join,
                                 hash_join->hasRepartition(),
                                 hash_join->cloneOutputPartitionSchemeHeader());
  }

  if (input->children() != new_children) {
    return input->copyWithNewChildren(new_children);
  } else {
    return input;
  }
}
P::PhysicalPtr FuseHashSelect::applyToNode(
    const P::PhysicalPtr &node) {
  P::HashJoinPtr hash_join;

  // Check to see if the join is a hash join, if the hash join is an inner
  // join.. We also check that there are no partitions.
  // TODO(dbacon): Add support for other join types.

  if ((!P::SomeHashJoin::MatchesWithConditionalCast(node, &hash_join)) ||
      hash_join->getOutputPartitionSchemeHeader() != nullptr) {
    return node;
  }

  // Get the join attributes from the build side.

  P::PhysicalPtr right_child = hash_join->right();
  const std::vector<E::AttributeReferencePtr> &right_join_attributes =
      hash_join->right_join_attributes();
  E::PredicatePtr build_predicate = nullptr;

  // Check that the build side is a Selection and that the join attributes match up.
  // If so, set the new hash join build side to the Selection input and the build predicate
  // to the selection's filter.

  P::SelectionPtr selection;
  if (P::SomeSelection::MatchesWithConditionalCast(right_child, &selection)) {
    if (E::SubsetOfExpressions(right_join_attributes,
                               selection->input()->getOutputAttributes())) {
      right_child = selection->input();
      build_predicate = selection->filter_predicate();
    }
  }

  return P::HashJoin::Create(hash_join->left(),
                             right_child,
                             hash_join->left_join_attributes(),
                             right_join_attributes,
                             hash_join->residual_predicate(),
                             build_predicate,
                             hash_join->project_expressions(),
                             hash_join->join_type(),
                             hash_join->hasRepartition(),
                             hash_join->cloneOutputPartitionSchemeHeader());
}