class RelOptUtil {

    public static RelNode pushDownJoinConditions(Join originalJoin,
        RelBuilder relBuilder) {
        RexNode joinCond = originalJoin.getCondition();
        final JoinRelType joinType = originalJoin.getJoinType();

        final List<RexNode> extraLeftExprs = new ArrayList<>();
        final List<RexNode> extraRightExprs = new ArrayList<>();
        final int leftCount = originalJoin.getLeft().getRowType().getFieldCount();
        final int rightCount = originalJoin.getRight().getRowType().getFieldCount();

        // You cannot push a 'get' because field names might change.
        //
        // Pushing sub-queries is OK in principle (if they don't reference both
        // sides of the join via correlating variables) but we'd rather not do it
        // yet.
        if (!containsGet(joinCond)
            && RexUtil.SubQueryFinder.find(joinCond) == null) {
            joinCond = pushDownEqualJoinConditions(joinCond, leftCount, rightCount, extraLeftExprs,
                extraRightExprs, relBuilder.getRexBuilder());
        }

        relBuilder.push(originalJoin.getLeft());
        if (!extraLeftExprs.isEmpty()) {
            final List<RelDataTypeField> fields =
                relBuilder.peek().getRowType().getFieldList();
            final List<Pair<RexNode, String>> pairs =
                new AbstractList<Pair<RexNode, String>>() {
                    public int size() {
                        return leftCount + extraLeftExprs.size();
                    }

                    public Pair<RexNode, String> get(int index) {
                        if (index < leftCount) {
                            RelDataTypeField field = fields.get(index);
                            return Pair.of(
                                new RexInputRef(index, field.getType()), field.getName());
                        } else {
                            return Pair.of(extraLeftExprs.get(index - leftCount), null);
                        }
                    }
                };
            relBuilder.project(Pair.left(pairs), Pair.right(pairs));
        }

        relBuilder.push(originalJoin.getRight());
        if (!extraRightExprs.isEmpty()) {
            final List<RelDataTypeField> fields =
                relBuilder.peek().getRowType().getFieldList();
            final int newLeftCount = leftCount + extraLeftExprs.size();
            final List<Pair<RexNode, String>> pairs =
                new AbstractList<Pair<RexNode, String>>() {
                    public int size() {
                        return rightCount + extraRightExprs.size();
                    }

                    public Pair<RexNode, String> get(int index) {
                        if (index < rightCount) {
                            RelDataTypeField field = fields.get(index);
                            return Pair.of(
                                new RexInputRef(index, field.getType()),
                                field.getName());
                        } else {
                            return Pair.of(
                                RexUtil.shift(
                                    extraRightExprs.get(index - rightCount),
                                    -newLeftCount),
                                null);
                        }
                    }
                };
            relBuilder.project(Pair.left(pairs), Pair.right(pairs));
        }

        final RelNode right = relBuilder.build();
        final RelNode left = relBuilder.build();
        relBuilder.push(
            originalJoin.copy(originalJoin.getTraitSet(),
                joinCond, left, right, joinType, originalJoin.isSemiJoinDone()));
        if (!extraLeftExprs.isEmpty() || !extraRightExprs.isEmpty()) {
            final int totalFields = joinType.projectsRight()
                ? leftCount + extraLeftExprs.size() + rightCount + extraRightExprs.size()
                : leftCount + extraLeftExprs.size();
            final int[] mappingRanges = joinType.projectsRight()
                ? new int[]{0, 0, leftCount, leftCount, leftCount + extraLeftExprs.size(), rightCount}
                : new int[]{0, 0, leftCount};
            Mappings.TargetMapping mapping =
                Mappings.createShiftMapping(
                    totalFields,
                    mappingRanges);
            relBuilder.project(relBuilder.fields(mapping.inverse()));
        }
        return relBuilder.build();
    }

}