Java Code Examples for org.apache.calcite.tools.RelBuilder#union()

The following examples show how to use org.apache.calcite.tools.RelBuilder#union() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: AbstractMaterializedViewRule.java    From Bats with Apache License 2.0 6 votes vote down vote up
@Override
protected RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder, RelNode topProject,
        RelNode unionInputQuery, RelNode unionInputView) {
    relBuilder.push(unionInputQuery);
    relBuilder.push(unionInputView);
    relBuilder.union(true);
    List<RexNode> exprList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
    List<String> nameList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
    for (int i = 0; i < relBuilder.peek().getRowType().getFieldCount(); i++) {
        // We can take unionInputQuery as it is query based.
        RelDataTypeField field = unionInputQuery.getRowType().getFieldList().get(i);
        exprList.add(
                rexBuilder.ensureType(field.getType(), rexBuilder.makeInputRef(relBuilder.peek(), i), true));
        nameList.add(field.getName());
    }
    relBuilder.project(exprList, nameList);
    return relBuilder.build();
}
 
Example 2
Source File: MaterializedViewJoinRule.java    From calcite with Apache License 2.0 6 votes vote down vote up
@Override protected RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder,
    RelNode topProject, RelNode unionInputQuery, RelNode unionInputView) {
  relBuilder.push(unionInputQuery);
  relBuilder.push(unionInputView);
  relBuilder.union(true);
  List<RexNode> exprList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
  List<String> nameList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
  for (int i = 0; i < relBuilder.peek().getRowType().getFieldCount(); i++) {
    // We can take unionInputQuery as it is query based.
    RelDataTypeField field = unionInputQuery.getRowType().getFieldList().get(i);
    exprList.add(
        rexBuilder.ensureType(
            field.getType(),
            rexBuilder.makeInputRef(relBuilder.peek(), i),
            true));
    nameList.add(field.getName());
  }
  relBuilder.project(exprList, nameList);
  return relBuilder.build();
}
 
Example 3
Source File: UnionToDistinctRule.java    From Bats with Apache License 2.0 5 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  final Union union = call.rel(0);
  if (union.all) {
    return; // nothing to do
  }
  final RelBuilder relBuilder = call.builder();
  relBuilder.pushAll(union.getInputs());
  relBuilder.union(true, union.getInputs().size());
  relBuilder.distinct();
  call.transformTo(relBuilder.build());
}
 
Example 4
Source File: AggregateUnionAggregateRule.java    From Bats with Apache License 2.0 5 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  final Aggregate topAggRel = call.rel(0);
  final Union union = call.rel(1);

  // If distincts haven't been removed yet, defer invoking this rule
  if (!union.all) {
    return;
  }

  final RelBuilder relBuilder = call.builder();
  final Aggregate bottomAggRel;
  if (call.rel(3) instanceof Aggregate) {
    // Aggregate is the second input
    bottomAggRel = call.rel(3);
    relBuilder.push(call.rel(2))
        .push(call.rel(3).getInput(0));
  } else if (call.rel(2) instanceof Aggregate) {
    // Aggregate is the first input
    bottomAggRel = call.rel(2);
    relBuilder.push(call.rel(2).getInput(0))
        .push(call.rel(3));
  } else {
    return;
  }

  // Only pull up aggregates if they are there just to remove distincts
  if (!topAggRel.getAggCallList().isEmpty()
      || !bottomAggRel.getAggCallList().isEmpty()) {
    return;
  }

  relBuilder.union(true);
  relBuilder.aggregate(relBuilder.groupKey(topAggRel.getGroupSet()),
      topAggRel.getAggCallList());
  call.transformTo(relBuilder.build());
}
 
Example 5
Source File: UnionToDistinctRule.java    From calcite with Apache License 2.0 5 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  final Union union = call.rel(0);
  final RelBuilder relBuilder = call.builder();
  relBuilder.pushAll(union.getInputs());
  relBuilder.union(true, union.getInputs().size());
  relBuilder.distinct();
  call.transformTo(relBuilder.build());
}
 
Example 6
Source File: AggregateUnionAggregateRule.java    From calcite with Apache License 2.0 5 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  final Aggregate topAggRel = call.rel(0);
  final Union union = call.rel(1);

  // If distincts haven't been removed yet, defer invoking this rule
  if (!union.all) {
    return;
  }

  final RelBuilder relBuilder = call.builder();
  final Aggregate bottomAggRel;
  if (call.rel(3) instanceof Aggregate) {
    // Aggregate is the second input
    bottomAggRel = call.rel(3);
    relBuilder.push(call.rel(2))
        .push(getInputWithSameRowType(bottomAggRel));
  } else if (call.rel(2) instanceof Aggregate) {
    // Aggregate is the first input
    bottomAggRel = call.rel(2);
    relBuilder.push(getInputWithSameRowType(bottomAggRel))
        .push(call.rel(3));
  } else {
    return;
  }

  // Only pull up aggregates if they are there just to remove distincts
  if (!topAggRel.getAggCallList().isEmpty()
      || !bottomAggRel.getAggCallList().isEmpty()) {
    return;
  }

  relBuilder.union(true);
  relBuilder.rename(union.getRowType().getFieldNames());
  relBuilder.aggregate(relBuilder.groupKey(topAggRel.getGroupSet()),
      topAggRel.getAggCallList());
  call.transformTo(relBuilder.build());
}
 
Example 7
Source File: AbstractMaterializedViewRule.java    From Bats with Apache License 2.0 4 votes vote down vote up
@Override
protected RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder, RelNode topProject,
        RelNode unionInputQuery, RelNode unionInputView) {
    // Union
    relBuilder.push(unionInputQuery);
    relBuilder.push(unionInputView);
    relBuilder.union(true);
    List<RexNode> exprList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
    List<String> nameList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
    for (int i = 0; i < relBuilder.peek().getRowType().getFieldCount(); i++) {
        // We can take unionInputQuery as it is query based.
        RelDataTypeField field = unionInputQuery.getRowType().getFieldList().get(i);
        exprList.add(
                rexBuilder.ensureType(field.getType(), rexBuilder.makeInputRef(relBuilder.peek(), i), true));
        nameList.add(field.getName());
    }
    relBuilder.project(exprList, nameList);
    // Rollup aggregate
    Aggregate aggregate = (Aggregate) unionInputQuery;
    final ImmutableBitSet groupSet = ImmutableBitSet.range(aggregate.getGroupCount());
    final List<AggCall> aggregateCalls = new ArrayList<>();
    for (int i = 0; i < aggregate.getAggCallList().size(); i++) {
        AggregateCall aggCall = aggregate.getAggCallList().get(i);
        if (aggCall.isDistinct()) {
            // Cannot ROLLUP distinct
            return null;
        }
        SqlAggFunction rollupAgg = getRollup(aggCall.getAggregation());
        if (rollupAgg == null) {
            // Cannot rollup this aggregate, bail out
            return null;
        }
        final RexInputRef operand = rexBuilder.makeInputRef(relBuilder.peek(), aggregate.getGroupCount() + i);
        aggregateCalls.add(
                // TODO: handle aggregate ordering
                relBuilder.aggregateCall(rollupAgg, operand).distinct(aggCall.isDistinct())
                        .approximate(aggCall.isApproximate()).as(aggCall.name));
    }
    RelNode prevNode = relBuilder.peek();
    RelNode result = relBuilder.aggregate(relBuilder.groupKey(groupSet), aggregateCalls).build();
    if (prevNode == result && groupSet.cardinality() != result.getRowType().getFieldCount()) {
        // Aggregate was not inserted but we need to prune columns
        result = relBuilder.push(result).project(relBuilder.fields(groupSet.asList())).build();
    }
    if (topProject != null) {
        // Top project
        return topProject.copy(topProject.getTraitSet(), ImmutableList.of(result));
    }
    // Result
    return result;
}
 
Example 8
Source File: UnionPullUpConstantsRule.java    From Bats with Apache License 2.0 4 votes vote down vote up
@Override public void onMatch(RelOptRuleCall call) {
  final Union union = call.rel(0);

  final int count = union.getRowType().getFieldCount();
  if (count == 1) {
    // No room for optimization since we cannot create an empty Project
    // operator. If we created a Project with one column, this rule would
    // cycle.
    return;
  }

  final RexBuilder rexBuilder = union.getCluster().getRexBuilder();
  final RelMetadataQuery mq = call.getMetadataQuery();
  final RelOptPredicateList predicates = mq.getPulledUpPredicates(union);
  if (predicates == null) {
    return;
  }

  final Map<Integer, RexNode> constants = new HashMap<>();
  for (Map.Entry<RexNode, RexNode> e : predicates.constantMap.entrySet()) {
    if (e.getKey() instanceof RexInputRef) {
      constants.put(((RexInputRef) e.getKey()).getIndex(), e.getValue());
    }
  }

  // None of the expressions are constant. Nothing to do.
  if (constants.isEmpty()) {
    return;
  }

  // Create expressions for Project operators before and after the Union
  List<RelDataTypeField> fields = union.getRowType().getFieldList();
  List<RexNode> topChildExprs = new ArrayList<>();
  List<String> topChildExprsFields = new ArrayList<>();
  List<RexNode> refs = new ArrayList<>();
  ImmutableBitSet.Builder refsIndexBuilder = ImmutableBitSet.builder();
  for (RelDataTypeField field : fields) {
    final RexNode constant = constants.get(field.getIndex());
    if (constant != null) {
      topChildExprs.add(constant);
      topChildExprsFields.add(field.getName());
    } else {
      final RexNode expr = rexBuilder.makeInputRef(union, field.getIndex());
      topChildExprs.add(expr);
      topChildExprsFields.add(field.getName());
      refs.add(expr);
      refsIndexBuilder.set(field.getIndex());
    }
  }
  ImmutableBitSet refsIndex = refsIndexBuilder.build();

  // Update top Project positions
  final Mappings.TargetMapping mapping =
      RelOptUtil.permutation(refs, union.getInput(0).getRowType()).inverse();
  topChildExprs = ImmutableList.copyOf(RexUtil.apply(mapping, topChildExprs));

  // Create new Project-Union-Project sequences
  final RelBuilder relBuilder = call.builder();
  for (RelNode input : union.getInputs()) {
    List<Pair<RexNode, String>> newChildExprs = new ArrayList<>();
    for (int j : refsIndex) {
      newChildExprs.add(
          Pair.of(rexBuilder.makeInputRef(input, j),
              input.getRowType().getFieldList().get(j).getName()));
    }
    if (newChildExprs.isEmpty()) {
      // At least a single item in project is required.
      newChildExprs.add(
          Pair.of(topChildExprs.get(0), topChildExprsFields.get(0)));
    }
    // Add the input with project on top
    relBuilder.push(input);
    relBuilder.project(Pair.left(newChildExprs), Pair.right(newChildExprs));
  }
  relBuilder.union(union.all, union.getInputs().size());
  // Create top Project fixing nullability of fields
  relBuilder.project(topChildExprs, topChildExprsFields);
  relBuilder.convert(union.getRowType(), false);

  call.transformTo(relBuilder.build());
}
 
Example 9
Source File: AggregateUnionTransposeRule.java    From Bats with Apache License 2.0 4 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  Aggregate aggRel = call.rel(0);
  Union union = call.rel(1);

  if (!union.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;
  }

  int groupCount = aggRel.getGroupSet().cardinality();

  List<AggregateCall> transformedAggCalls =
      transformAggCalls(
          aggRel.copy(aggRel.getTraitSet(), aggRel.getInput(), false,
              aggRel.getGroupSet(), null, aggRel.getAggCallList()),
          groupCount, aggRel.getAggCallList());
  if (transformedAggCalls == null) {
    // we've detected the presence of something like AVG,
    // which we can't handle
    return;
  }

  // create corresponding aggregates on top of each union child
  final RelBuilder relBuilder = call.builder();
  int transformCount = 0;
  final RelMetadataQuery mq = call.getMetadataQuery();
  for (RelNode input : union.getInputs()) {
    boolean alreadyUnique =
        RelMdUtil.areColumnsDefinitelyUnique(mq, input,
            aggRel.getGroupSet());

    relBuilder.push(input);
    if (!alreadyUnique) {
      ++transformCount;
      relBuilder.aggregate(relBuilder.groupKey(aggRel.getGroupSet()),
          aggRel.getAggCallList());
    }
  }

  if (transformCount == 0) {
    // none of the children could benefit from the push-down,
    // so bail out (preventing the infinite loop to which most
    // planners would succumb)
    return;
  }

  // create a new union whose children are the aggregates created above
  relBuilder.union(true, union.getInputs().size());
  relBuilder.aggregate(
      relBuilder.groupKey(aggRel.getGroupSet(), aggRel.getGroupSets()),
      transformedAggCalls);
  call.transformTo(relBuilder.build());
}
 
Example 10
Source File: UnionMergeRule.java    From Bats with Apache License 2.0 4 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  final SetOp topOp = call.rel(0);
  @SuppressWarnings("unchecked") final Class<? extends SetOp> setOpClass =
      (Class) operands.get(0).getMatchedClass();

  // For Union and Intersect, we want to combine the set-op that's in the
  // second input first.
  //
  // For example, we reduce
  //    Union(Union(a, b), Union(c, d))
  // to
  //    Union(Union(a, b), c, d)
  // in preference to
  //    Union(a, b, Union(c, d))
  //
  // But for Minus, we can only reduce the left input. It is not valid to
  // reduce
  //    Minus(a, Minus(b, c))
  // to
  //    Minus(a, b, c)
  //
  // Hence, that's why the rule pattern matches on generic RelNodes rather
  // than explicit sub-classes of SetOp.  By doing so, and firing this rule
  // in a bottom-up order, it allows us to only specify a single
  // pattern for this rule.
  final SetOp bottomOp;
  if (setOpClass.isInstance(call.rel(2))
      && !Minus.class.isAssignableFrom(setOpClass)) {
    bottomOp = call.rel(2);
  } else if (setOpClass.isInstance(call.rel(1))) {
    bottomOp = call.rel(1);
  } else {
    return;
  }

  // Can only combine (1) if all operators are ALL,
  // or (2) top operator is DISTINCT (i.e. not ALL).
  // In case (2), all operators become DISTINCT.
  if (topOp.all && !bottomOp.all) {
    return;
  }

  // Combine the inputs from the bottom set-op with the other inputs from
  // the top set-op.
  final RelBuilder relBuilder = call.builder();
  if (setOpClass.isInstance(call.rel(2))
      && !Minus.class.isAssignableFrom(setOpClass)) {
    relBuilder.push(topOp.getInput(0));
    relBuilder.pushAll(bottomOp.getInputs());
    // topOp.getInputs().size() may be more than 2
    for (int index = 2; index < topOp.getInputs().size(); index++) {
      relBuilder.push(topOp.getInput(index));
    }
  } else {
    relBuilder.pushAll(bottomOp.getInputs());
    relBuilder.pushAll(Util.skip(topOp.getInputs()));
  }
  int n = bottomOp.getInputs().size()
      + topOp.getInputs().size()
      - 1;
  if (topOp instanceof Union) {
    relBuilder.union(topOp.all, n);
  } else if (topOp instanceof Intersect) {
    relBuilder.intersect(topOp.all, n);
  } else if (topOp instanceof Minus) {
    relBuilder.minus(topOp.all, n);
  }
  call.transformTo(relBuilder.build());
}
 
Example 11
Source File: MaterializedViewAggregateRule.java    From calcite with Apache License 2.0 4 votes vote down vote up
@Override protected RelNode createUnion(RelBuilder relBuilder, RexBuilder rexBuilder,
    RelNode topProject, RelNode unionInputQuery, RelNode unionInputView) {
  // Union
  relBuilder.push(unionInputQuery);
  relBuilder.push(unionInputView);
  relBuilder.union(true);
  List<RexNode> exprList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
  List<String> nameList = new ArrayList<>(relBuilder.peek().getRowType().getFieldCount());
  for (int i = 0; i < relBuilder.peek().getRowType().getFieldCount(); i++) {
    // We can take unionInputQuery as it is query based.
    RelDataTypeField field = unionInputQuery.getRowType().getFieldList().get(i);
    exprList.add(
        rexBuilder.ensureType(
            field.getType(),
            rexBuilder.makeInputRef(relBuilder.peek(), i),
            true));
    nameList.add(field.getName());
  }
  relBuilder.project(exprList, nameList);
  // Rollup aggregate
  Aggregate aggregate = (Aggregate) unionInputQuery;
  final ImmutableBitSet groupSet = ImmutableBitSet.range(aggregate.getGroupCount());
  final List<AggCall> aggregateCalls = new ArrayList<>();
  for (int i = 0; i < aggregate.getAggCallList().size(); i++) {
    AggregateCall aggCall = aggregate.getAggCallList().get(i);
    if (aggCall.isDistinct()) {
      // Cannot ROLLUP distinct
      return null;
    }
    SqlAggFunction rollupAgg =
        getRollup(aggCall.getAggregation());
    if (rollupAgg == null) {
      // Cannot rollup this aggregate, bail out
      return null;
    }
    final RexInputRef operand =
        rexBuilder.makeInputRef(relBuilder.peek(),
            aggregate.getGroupCount() + i);
    aggregateCalls.add(
        relBuilder.aggregateCall(rollupAgg, operand)
            .distinct(aggCall.isDistinct())
            .approximate(aggCall.isApproximate())
            .as(aggCall.name));
  }
  RelNode prevNode = relBuilder.peek();
  RelNode result = relBuilder
      .aggregate(relBuilder.groupKey(groupSet), aggregateCalls)
      .build();
  if (prevNode == result && groupSet.cardinality() != result.getRowType().getFieldCount()) {
    // Aggregate was not inserted but we need to prune columns
    result = relBuilder
        .push(result)
        .project(relBuilder.fields(groupSet))
        .build();
  }
  if (topProject != null) {
    // Top project
    return topProject.copy(topProject.getTraitSet(), ImmutableList.of(result));
  }
  // Result
  return result;
}
 
Example 12
Source File: UnionPullUpConstantsRule.java    From calcite with Apache License 2.0 4 votes vote down vote up
@Override public void onMatch(RelOptRuleCall call) {
  final Union union = call.rel(0);

  final RexBuilder rexBuilder = union.getCluster().getRexBuilder();
  final RelMetadataQuery mq = call.getMetadataQuery();
  final RelOptPredicateList predicates = mq.getPulledUpPredicates(union);
  if (predicates == null) {
    return;
  }

  final Map<Integer, RexNode> constants = new HashMap<>();
  for (Map.Entry<RexNode, RexNode> e : predicates.constantMap.entrySet()) {
    if (e.getKey() instanceof RexInputRef) {
      constants.put(((RexInputRef) e.getKey()).getIndex(), e.getValue());
    }
  }

  // None of the expressions are constant. Nothing to do.
  if (constants.isEmpty()) {
    return;
  }

  // Create expressions for Project operators before and after the Union
  List<RelDataTypeField> fields = union.getRowType().getFieldList();
  List<RexNode> topChildExprs = new ArrayList<>();
  List<String> topChildExprsFields = new ArrayList<>();
  List<RexNode> refs = new ArrayList<>();
  ImmutableBitSet.Builder refsIndexBuilder = ImmutableBitSet.builder();
  for (RelDataTypeField field : fields) {
    final RexNode constant = constants.get(field.getIndex());
    if (constant != null) {
      topChildExprs.add(constant);
      topChildExprsFields.add(field.getName());
    } else {
      final RexNode expr = rexBuilder.makeInputRef(union, field.getIndex());
      topChildExprs.add(expr);
      topChildExprsFields.add(field.getName());
      refs.add(expr);
      refsIndexBuilder.set(field.getIndex());
    }
  }
  ImmutableBitSet refsIndex = refsIndexBuilder.build();

  // Update top Project positions
  final Mappings.TargetMapping mapping =
      RelOptUtil.permutation(refs, union.getInput(0).getRowType()).inverse();
  topChildExprs = RexUtil.apply(mapping, topChildExprs);

  // Create new Project-Union-Project sequences
  final RelBuilder relBuilder = call.builder();
  for (RelNode input : union.getInputs()) {
    List<Pair<RexNode, String>> newChildExprs = new ArrayList<>();
    for (int j : refsIndex) {
      newChildExprs.add(
          Pair.of(rexBuilder.makeInputRef(input, j),
              input.getRowType().getFieldList().get(j).getName()));
    }
    if (newChildExprs.isEmpty()) {
      // At least a single item in project is required.
      newChildExprs.add(
          Pair.of(topChildExprs.get(0), topChildExprsFields.get(0)));
    }
    // Add the input with project on top
    relBuilder.push(input);
    relBuilder.project(Pair.left(newChildExprs), Pair.right(newChildExprs));
  }
  relBuilder.union(union.all, union.getInputs().size());
  // Create top Project fixing nullability of fields
  relBuilder.project(topChildExprs, topChildExprsFields);
  relBuilder.convert(union.getRowType(), false);

  call.transformTo(relBuilder.build());
}
 
Example 13
Source File: AggregateUnionTransposeRule.java    From calcite with Apache License 2.0 4 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  Aggregate aggRel = call.rel(0);
  Union union = call.rel(1);

  if (!union.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;
  }

  int groupCount = aggRel.getGroupSet().cardinality();

  List<AggregateCall> transformedAggCalls =
      transformAggCalls(
          aggRel.copy(aggRel.getTraitSet(), aggRel.getInput(),
              aggRel.getGroupSet(), null, aggRel.getAggCallList()),
          groupCount, aggRel.getAggCallList());
  if (transformedAggCalls == null) {
    // we've detected the presence of something like AVG,
    // which we can't handle
    return;
  }

  // create corresponding aggregates on top of each union child
  final RelBuilder relBuilder = call.builder();
  int transformCount = 0;
  final RelMetadataQuery mq = call.getMetadataQuery();
  for (RelNode input : union.getInputs()) {
    boolean alreadyUnique =
        RelMdUtil.areColumnsDefinitelyUnique(mq, input,
            aggRel.getGroupSet());

    relBuilder.push(input);
    if (!alreadyUnique) {
      ++transformCount;
      relBuilder.aggregate(relBuilder.groupKey(aggRel.getGroupSet()),
          aggRel.getAggCallList());
    }
  }

  if (transformCount == 0) {
    // none of the children could benefit from the push-down,
    // so bail out (preventing the infinite loop to which most
    // planners would succumb)
    return;
  }

  // create a new union whose children are the aggregates created above
  relBuilder.union(true, union.getInputs().size());
  relBuilder.aggregate(
      relBuilder.groupKey(aggRel.getGroupSet(),
          (Iterable<ImmutableBitSet>) aggRel.getGroupSets()),
      transformedAggCalls);
  call.transformTo(relBuilder.build());
}
 
Example 14
Source File: UnionMergeRule.java    From calcite with Apache License 2.0 4 votes vote down vote up
public void onMatch(RelOptRuleCall call) {
  final SetOp topOp = call.rel(0);
  @SuppressWarnings("unchecked") final Class<? extends SetOp> setOpClass =
      (Class) operands.get(0).getMatchedClass();

  // For Union and Intersect, we want to combine the set-op that's in the
  // second input first.
  //
  // For example, we reduce
  //    Union(Union(a, b), Union(c, d))
  // to
  //    Union(Union(a, b), c, d)
  // in preference to
  //    Union(a, b, Union(c, d))
  //
  // But for Minus, we can only reduce the left input. It is not valid to
  // reduce
  //    Minus(a, Minus(b, c))
  // to
  //    Minus(a, b, c)
  //
  // Hence, that's why the rule pattern matches on generic RelNodes rather
  // than explicit sub-classes of SetOp.  By doing so, and firing this rule
  // in a bottom-up order, it allows us to only specify a single
  // pattern for this rule.
  final SetOp bottomOp;
  if (setOpClass.isInstance(call.rel(2))
      && !Minus.class.isAssignableFrom(setOpClass)) {
    bottomOp = call.rel(2);
  } else if (setOpClass.isInstance(call.rel(1))) {
    bottomOp = call.rel(1);
  } else {
    return;
  }

  // Can only combine (1) if all operators are ALL,
  // or (2) top operator is DISTINCT (i.e. not ALL).
  // In case (2), all operators become DISTINCT.
  if (topOp.all && !bottomOp.all) {
    return;
  }

  // Combine the inputs from the bottom set-op with the other inputs from
  // the top set-op.
  final RelBuilder relBuilder = call.builder();
  if (setOpClass.isInstance(call.rel(2))
      && !Minus.class.isAssignableFrom(setOpClass)) {
    relBuilder.push(topOp.getInput(0));
    relBuilder.pushAll(bottomOp.getInputs());
    // topOp.getInputs().size() may be more than 2
    for (int index = 2; index < topOp.getInputs().size(); index++) {
      relBuilder.push(topOp.getInput(index));
    }
  } else {
    relBuilder.pushAll(bottomOp.getInputs());
    relBuilder.pushAll(Util.skip(topOp.getInputs()));
  }
  int n = bottomOp.getInputs().size()
      + topOp.getInputs().size()
      - 1;
  if (topOp instanceof Union) {
    relBuilder.union(topOp.all, n);
  } else if (topOp instanceof Intersect) {
    relBuilder.intersect(topOp.all, n);
  } else if (topOp instanceof Minus) {
    relBuilder.minus(topOp.all, n);
  }
  call.transformTo(relBuilder.build());
}