/** @return true if all tuples match rowType; otherwise, assert on mismatch */ private boolean assertRowType() { for (List<RexLiteral> tuple : tuples) { assert tuple.size() == rowType.getFieldCount(); for (Pair<RexLiteral, RelDataTypeField> pair : Pair.zip(tuple, rowType.getFieldList())) { RexLiteral literal = pair.left; RelDataType fieldType = pair.right.getType(); // TODO jvs 19-Feb-2006: strengthen this a bit. For example, // overflow, rounding, and padding/truncation must already have // been dealt with. if (!RexLiteral.isNullLiteral(literal)) { assert (SqlTypeUtil.canAssignFrom(fieldType, literal.getType())); } } } return true; }
// implement RelNode public void explain(RelOptPlanWriter pw) { // A little adapter just to get the tuples to come out // with curly brackets instead of square brackets. Plus // more whitespace for readability. List<String> renderList = new ArrayList<String>(); for (List<RexLiteral> tuple : tuples) { String s = tuple.toString(); assert (s.startsWith("[")); assert (s.endsWith("]")); renderList.add("{ " + s.substring(1, s.length() - 1) + " }"); } if (pw.getDetailLevel() == SqlExplainLevel.DIGEST_ATTRIBUTES) { // For rel digest, include the row type since a rendered // literal may leave the type ambiguous (e.g. "null"). pw.explain(this, new String[] {"type", "tuples"}, new Object[] {rowType, renderList}); } else { // For normal EXPLAIN PLAN, omit the type. pw.explain(this, new String[] {"tuples"}, new Object[] {renderList}); } }
private List<AggregateCall> transformAggCalls( RelDataTypeFactory typeFactory, int nGroupCols, List<AggregateCall> origCalls) { List<AggregateCall> newCalls = new ArrayList<AggregateCall>(); int iInput = nGroupCols; for (AggregateCall origCall : origCalls) { if (origCall.isDistinct() || !SUPPORTED_AGGREGATES.containsKey(origCall.getAggregation().getClass())) { return null; } Aggregation aggFun; RelDataType aggType; if (origCall.getAggregation().getName().equals("COUNT")) { aggFun = new SqlSumEmptyIsZeroAggFunction(origCall.getType()); SqlAggFunction af = (SqlAggFunction) aggFun; final AggregateRelBase.AggCallBinding binding = new AggregateRelBase.AggCallBinding( typeFactory, af, Collections.singletonList(origCall.getType()), nGroupCols); // count(any) is always not null, however nullability of sum might // depend on the number of columns in GROUP BY. // Here we use SUM0 since we are sure we will not face nullable // inputs nor we'll face empty set. aggType = af.inferReturnType(binding); } else { aggFun = origCall.getAggregation(); aggType = origCall.getType(); } AggregateCall newCall = new AggregateCall( aggFun, origCall.isDistinct(), Collections.singletonList(iInput), aggType, origCall.getName()); newCalls.add(newCall); ++iInput; } return newCalls; }
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); }