// implement RelOptRule public void onMatch(RelOptRuleCall call) { ProjectRel origProj = call.rel(0); JoinRel joinRel = call.rel(1); // locate all fields referenced in the projection and join condition; // determine which inputs are referenced in the projection and // join condition; if all fields are being referenced and there are no // special expressions, no point in proceeding any further PushProjector pushProject = new PushProjector(origProj, joinRel.getCondition(), joinRel, preserveExprCondition); if (pushProject.locateAllRefs()) { return; } // create left and right projections, projecting only those // fields referenced on each side RelNode leftProjRel = pushProject.createProjectRefsAndExprs(joinRel.getLeft(), true, false); RelNode rightProjRel = pushProject.createProjectRefsAndExprs(joinRel.getRight(), true, true); // convert the join condition to reference the projected columns RexNode newJoinFilter = null; int[] adjustments = pushProject.getAdjustments(); if (joinRel.getCondition() != null) { List<RelDataTypeField> projJoinFieldList = new ArrayList<RelDataTypeField>(); projJoinFieldList.addAll(joinRel.getSystemFieldList()); projJoinFieldList.addAll(leftProjRel.getRowType().getFieldList()); projJoinFieldList.addAll(rightProjRel.getRowType().getFieldList()); newJoinFilter = pushProject.convertRefsAndExprs(joinRel.getCondition(), projJoinFieldList, adjustments); } // create a new joinrel with the projected children JoinRel newJoinRel = new JoinRel( joinRel.getCluster(), leftProjRel, rightProjRel, newJoinFilter, joinRel.getJoinType(), Collections.<String>emptySet(), joinRel.isSemiJoinDone(), joinRel.getSystemFieldList()); // put the original project on top of the join, converting it to // reference the modified projection list ProjectRel topProject = pushProject.createNewProject(newJoinRel, adjustments); call.transformTo(topProject); }
public void onMatch(RelOptRuleCall call) { AggregateRel aggRel = call.rel(0); UnionRel unionRel = call.rel(1); if (!unionRel.all) { // This transformation is only valid for UNION ALL. // Consider t1(i) with rows (5), (5) and t2(i) with // rows (5), (10), and the query // select sum(i) from (select i from t1) union (select i from t2). // The correct answer is 15. If we apply the transformation, // we get // select sum(i) from // (select sum(i) as i from t1) union (select sum(i) as i from t2) // which yields 25 (incorrect). return; } RelOptCluster cluster = unionRel.getCluster(); List<AggregateCall> transformedAggCalls = transformAggCalls( aggRel.getCluster().getTypeFactory(), aggRel.getGroupSet().cardinality(), aggRel.getAggCallList()); if (transformedAggCalls == null) { // we've detected the presence of something like AVG, // which we can't handle return; } boolean anyTransformed = false; // create corresponding aggs on top of each union child List<RelNode> newUnionInputs = new ArrayList<RelNode>(); for (RelNode input : unionRel.getInputs()) { boolean alreadyUnique = RelMdUtil.areColumnsDefinitelyUnique(input, aggRel.getGroupSet()); if (alreadyUnique) { newUnionInputs.add(input); } else { anyTransformed = true; newUnionInputs.add( new AggregateRel(cluster, input, aggRel.getGroupSet(), aggRel.getAggCallList())); } } if (!anyTransformed) { // none of the children could benefit from the pushdown, // so bail out (preventing the infinite loop to which most // planners would succumb) return; } // create a new union whose children are the aggs created above UnionRel newUnionRel = new UnionRel(cluster, newUnionInputs, true); AggregateRel newTopAggRel = new AggregateRel(cluster, newUnionRel, aggRel.getGroupSet(), transformedAggCalls); // In case we transformed any COUNT (which is always NOT NULL) // to SUM (which is always NULLABLE), cast back to keep the // planner happy. RelNode castRel = RelOptUtil.createCastRel(newTopAggRel, aggRel.getRowType(), false); call.transformTo(castRel); }
private void onMatchRight(RelOptRuleCall call) { final JoinRelBase topJoin = call.rel(0); final JoinRelBase bottomJoin = call.rel(1); final RelNode relC = call.rel(2); final RelNode relA = bottomJoin.getLeft(); final RelNode relB = bottomJoin.getRight(); final RelOptCluster cluster = topJoin.getCluster(); // topJoin // / \ // bottomJoin C // / \ // A B final int aCount = relA.getRowType().getFieldCount(); final int bCount = relB.getRowType().getFieldCount(); final int cCount = relC.getRowType().getFieldCount(); final BitSet bBitSet = BitSets.range(aCount, aCount + bCount); // becomes // // newTopJoin // / \ // newBottomJoin B // / \ // A C // If either join is not inner, we cannot proceed. // (Is this too strict?) if (topJoin.getJoinType() != JoinRelType.INNER || bottomJoin.getJoinType() != JoinRelType.INNER) { return; } // Split the condition of topJoin into a conjunction. Each of the // parts that does not use columns from B can be pushed down. final List<RexNode> intersecting = new ArrayList<RexNode>(); final List<RexNode> nonIntersecting = new ArrayList<RexNode>(); split(topJoin.getCondition(), bBitSet, intersecting, nonIntersecting); // If there's nothing to push down, it's not worth proceeding. if (nonIntersecting.isEmpty()) { return; } // Split the condition of bottomJoin into a conjunction. Each of the // parts that use columns from B will need to be pulled up. final List<RexNode> bottomIntersecting = new ArrayList<RexNode>(); final List<RexNode> bottomNonIntersecting = new ArrayList<RexNode>(); split(bottomJoin.getCondition(), bBitSet, bottomIntersecting, bottomNonIntersecting); // target: | A | C | // source: | A | B | C | final Mappings.TargetMapping bottomMapping = Mappings.createShiftMapping( aCount + bCount + cCount, 0, 0, aCount, aCount, aCount + bCount, cCount); List<RexNode> newBottomList = new ArrayList<RexNode>(); new RexPermuteInputsShuttle(bottomMapping, relA, relC) .visitList(nonIntersecting, newBottomList); final Mappings.TargetMapping bottomBottomMapping = Mappings.createShiftMapping(aCount + bCount, 0, 0, aCount); new RexPermuteInputsShuttle(bottomBottomMapping, relA, relC) .visitList(bottomNonIntersecting, newBottomList); final RexBuilder rexBuilder = cluster.getRexBuilder(); RexNode newBottomCondition = RexUtil.composeConjunction(rexBuilder, newBottomList, false); final JoinRelBase newBottomJoin = bottomJoin.copy( bottomJoin.getTraitSet(), newBottomCondition, relA, relC, bottomJoin.getJoinType()); // target: | A | C | B | // source: | A | B | C | final Mappings.TargetMapping topMapping = Mappings.createShiftMapping( aCount + bCount + cCount, 0, 0, aCount, aCount + cCount, aCount, bCount, aCount, aCount + bCount, cCount); List<RexNode> newTopList = new ArrayList<RexNode>(); new RexPermuteInputsShuttle(topMapping, newBottomJoin, relB) .visitList(intersecting, newTopList); new RexPermuteInputsShuttle(topMapping, newBottomJoin, relB) .visitList(bottomIntersecting, newTopList); RexNode newTopCondition = RexUtil.composeConjunction(rexBuilder, newTopList, false); @SuppressWarnings("SuspiciousNameCombination") final JoinRelBase newTopJoin = topJoin.copy( topJoin.getTraitSet(), newTopCondition, newBottomJoin, relB, topJoin.getJoinType()); assert !Mappings.isIdentity(topMapping); final RelNode newProject = RelFactories.createProject(projectFactory, newTopJoin, Mappings.asList(topMapping)); call.transformTo(newProject); }