protected void perform(RelOptRuleCall call, Project project, Filter filter, Join join) {
    LOG.debug("Start Matching HiveAntiJoinRule");

    //TODO : Need to support this scenario.
    //https://issues.apache.org/jira/browse/HIVE-23991
    if (join.getCondition().isAlwaysTrue()) {
      return;
    }

    //We support conversion from left outer join only.
    if (join.getJoinType() != JoinRelType.LEFT) {
      return;
    }

    assert (filter != null);

    List<RexNode> filterList = getResidualFilterNodes(filter, join);
    if (filterList == null) {
      return;
    }

    // If any projection is there from right side, then we can not convert to anti join.
    boolean hasProjection = HiveCalciteUtil.hasAnyExpressionFromRightSide(join, project.getProjects());
    if (hasProjection) {
      return;
    }

    LOG.debug("Matched HiveAntiJoinRule");

    // Build anti join with same left, right child and condition as original left outer join.
    Join anti = HiveAntiJoin.getAntiJoin(join.getLeft().getCluster(), join.getLeft().getTraitSet(),
            join.getLeft(), join.getRight(), join.getCondition());
    RelNode newProject;
    if (filterList.isEmpty()) {
      newProject = project.copy(project.getTraitSet(), anti, project.getProjects(), project.getRowType());
    } else {
      // Collate the filter condition using AND as the filter was decomposed based
      // on AND condition (RelOptUtil.conjunctions).
      RexNode condition = filterList.size() == 1 ? filterList.get(0) :
              join.getCluster().getRexBuilder().makeCall(SqlStdOperatorTable.AND, filterList);
      Filter newFilter = filter.copy(filter.getTraitSet(), anti, condition);
      newProject = project.copy(project.getTraitSet(), newFilter, project.getProjects(), project.getRowType());
    }
    call.transformTo(newProject);
  }
Planner rule that converts a join plus filter to anti join.
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveAntiSemiJoinRule.java