private void rewrite(final Mode mode, final RelNode inputToKeep, final RelNode inputToRemove,
      final Join join, List<RexNode> topProjExprs, RelOptRuleCall call, final Project project, List<RexNode> nullableNodes) {
    RexBuilder rexBuilder = join.getCluster().getRexBuilder();

    final RelNode leftInput = join.getLeft();
    final RelNode rightInput = join.getRight();

    // If we reach here, we trigger the transform
    if (mode == Mode.REMOVE) {
      if (inputToKeep == join.getRight()) {
        // First, if FK is the right input, we need to shift
        nullableNodes = nullableNodes.stream()
            .map(node -> RexUtil.shift(node, 0, -leftInput.getRowType().getFieldCount()))
            .collect(Collectors.toList());
        topProjExprs = topProjExprs.stream()
            .map(node -> RexUtil.shift(node, 0, -leftInput.getRowType().getFieldCount()))
            .collect(Collectors.toList());
      }
      // Fix nullability in references to the input node
      topProjExprs = HiveCalciteUtil.fixNullability(rexBuilder, topProjExprs, RelOptUtil.getFieldTypeList(inputToKeep.getRowType()));
      // Trigger transformation
      if (nullableNodes.isEmpty()) {
        call.transformTo(call.builder()
            .push(inputToKeep)
            .project(topProjExprs)
            .convert(project.getRowType(), false)
            .build());
      } else {
        RexNode newFilterCond;
        if (nullableNodes.size() == 1) {
          newFilterCond = rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, nullableNodes.get(0));
        } else {
          List<RexNode> isNotNullConds = new ArrayList<>();
          for (RexNode nullableNode : nullableNodes) {
            isNotNullConds.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, nullableNode));
          }
          newFilterCond = rexBuilder.makeCall(SqlStdOperatorTable.AND, isNotNullConds);
        }
        call.transformTo(call.builder()
            .push(inputToKeep)
            .filter(newFilterCond)
            .project(topProjExprs)
            .convert(project.getRowType(), false)
            .build());
      }
    } else { // Mode.TRANSFORM
      // Trigger transformation
      call.transformTo(call.builder()
          .push(leftInput).push(rightInput)
          .join(JoinRelType.INNER, join.getCondition())
          .convert(call.rel(1).getRowType(), false) // Preserve nullability
          .project(project.getProjects())
          .build());
    }
  }
The rule can perform two different optimizations.
1) Removes a join if is does not alter the cardinality of the one of its inputs.
In particular, this rule is triggered if:
- it is a join on PK-FK/UK-FK,
- project on top only references columns from the FK side, and
- PK/UK side is not filtered
It optionally adds an IS NOT NULL filter if any FK column can be nullable
2) Transforms a left/right outer join into an inner join if:
- it is a join on PK-FK/UK-FK,
- FK is not nullable
- PK/UK side is not filtered
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveJoinConstraintsRule.java