diff --git a/core/src/main/java/org/apache/calcite/plan/Hints.java b/core/src/main/java/org/apache/calcite/plan/Hints.java new file mode 100644 index 00000000000..52f4414aac4 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/plan/Hints.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.calcite.plan; + +public class Hints { + private Hints() { + + } + public static final String CALCULATION_VIEW = "CALCULATIONVIEW"; +} diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java index 5387acd8cf8..0b6c1b39836 100644 --- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java +++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java @@ -878,6 +878,22 @@ public static RelNode createCastRel( rel, castRowType, rename, RelFactories.DEFAULT_PROJECT_FACTORY); } + public static boolean hasCalcViewHint(RelNode rel) { + boolean hasCalcViewHint = false; + for (RelNode input : rel.getInputs()) { + input = input.stripped(); + if (input instanceof TableScan) { + hasCalcViewHint = ((TableScan) input).getHints().stream().anyMatch(it -> it.hintName.equals(Hints.CALCULATION_VIEW)); + } else { + hasCalcViewHint = hasCalcViewHint(input); + } + if (hasCalcViewHint) { + return hasCalcViewHint; + } + } + return hasCalcViewHint; + } + /** * Creates a projection which casts a rel's output to a desired row type. * diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index f2ff2204095..6b552c83f02 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -21,6 +21,7 @@ import org.apache.calcite.linq4j.tree.Expressions; import org.apache.calcite.plan.RelOptSamplingParameters; import org.apache.calcite.plan.RelOptTable; +import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelCollations; import org.apache.calcite.rel.RelFieldCollation; @@ -608,6 +609,7 @@ protected void buildAggGroupList(Aggregate e, Builder builder, */ protected Builder buildAggregate(Aggregate e, Builder builder, List selectList, List groupByList) { + boolean isCountStar = false; for (AggregateCall aggCall : e.getAggCallList()) { SqlNode aggCallSqlNode = builder.context.toSql(aggCall); RelDataType aggCallRelDataType = aggCall.getType(); @@ -616,8 +618,21 @@ protected Builder buildAggregate(Aggregate e, Builder builder, } else if (aggCall.getAggregation() instanceof SqlMinMaxAggFunction) { aggCallSqlNode = dialect.rewriteMaxMinExpr(aggCallSqlNode, aggCallRelDataType); } + isCountStar = (RelOptUtil.hasCalcViewHint(e) && selectList.isEmpty() && aggCall.getAggregation().getKind() == SqlKind.COUNT); addSelect(selectList, aggCallSqlNode, e.getRowType()); } + if (isCountStar) { + SqlSelect oldSelect = builder.select; + boolean isStar = false; + if (oldSelect.getSelectList().size() == 1 && oldSelect.getSelectList().get(0) instanceof SqlIdentifier) { + isStar = ((SqlIdentifier) oldSelect.getSelectList().get(0)).isStar(); + } + + if (!isStar) { + builder.setFrom(oldSelect.clone(oldSelect.getParserPosition())); + } + } + builder.setSelect(new SqlNodeList(selectList, POS)); if (!groupByList.isEmpty() || e.getAggCallList().isEmpty()) { // Some databases don't support "GROUP BY ()". We can omit it as long diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java index 327bd1044dc..69a32b98b8a 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java @@ -2279,6 +2279,10 @@ public void setSelect(SqlNodeList nodeList) { select.setSelectList(nodeList); } + public void setFrom(SqlNode node) { + select.setFrom(node); + } + public void setWhere(SqlNode node) { assert clauses.contains(Clause.WHERE); select.setWhere(node); diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java index 9e38af6474f..3c05f4c16ee 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -40,17 +40,7 @@ import org.apache.calcite.rel.RelRoot; import org.apache.calcite.rel.RelShuttleImpl; import org.apache.calcite.rel.SingleRel; -import org.apache.calcite.rel.core.Aggregate; -import org.apache.calcite.rel.core.AggregateCall; -import org.apache.calcite.rel.core.Collect; -import org.apache.calcite.rel.core.CorrelationId; -import org.apache.calcite.rel.core.Filter; -import org.apache.calcite.rel.core.Join; -import org.apache.calcite.rel.core.JoinInfo; -import org.apache.calcite.rel.core.JoinRelType; -import org.apache.calcite.rel.core.Project; -import org.apache.calcite.rel.core.RelFactories; -import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rel.core.*; import org.apache.calcite.rel.hint.HintStrategyTable; import org.apache.calcite.rel.hint.Hintable; import org.apache.calcite.rel.hint.RelHint; @@ -3518,24 +3508,39 @@ protected final void createAggImpl( // compute inputs to the aggregator final PairList preExprs; - if (aggConverter.convertedInputExprs.isEmpty()) { - // Special case for COUNT(*), where we can end up with no inputs - // at all. The rest of the system doesn't like 0-tuples, so we - // select a dummy constant here. - final RexNode zero = rexBuilder.makeExactLiteral(BigDecimal.ZERO); - preExprs = PairList.of(zero, null); + if (bb.root != null && RelOptUtil.hasCalcViewHint(bb.root)) { + if (!aggConverter.convertedInputExprs.isEmpty()) { + preExprs = aggConverter.convertedInputExprs; + final RelNode inputRel = bb.root(); + bb.setRoot( + relBuilder.push(inputRel) + .projectNamed(preExprs.leftList(), preExprs.rightList(), false) + .build(), + false); + } } else { - preExprs = aggConverter.convertedInputExprs; + if (aggConverter.convertedInputExprs.isEmpty()) { + // Special case for COUNT(*), where we can end up with no inputs + // at all. The rest of the system doesn't like 0-tuples, so we + // select a dummy constant here. + final RexNode zero = rexBuilder.makeExactLiteral(BigDecimal.ZERO); + preExprs = PairList.of(zero, null); + } else { + preExprs = aggConverter.convertedInputExprs; + } + + final RelNode inputRel = bb.root(); + + // Project the expressions required by agg and having. + bb.setRoot( + relBuilder.push(inputRel) + .projectNamed(preExprs.leftList(), preExprs.rightList(), false) + .build(), + false); } - final RelNode inputRel = bb.root(); // Project the expressions required by agg and having. - bb.setRoot( - relBuilder.push(inputRel) - .projectNamed(preExprs.leftList(), preExprs.rightList(), false) - .build(), - false); bb.mapRootRelToFieldProjection.put(bb.root(), r.groupExprProjection); // REVIEW jvs 31-Oct-2007: doesn't the declaration of diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java index bfb57d0519c..0ec60e4e9f0 100644 --- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java +++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java @@ -2362,7 +2362,7 @@ private RelBuilder aggregate_(GroupKeyImpl groupKey, final ImmutableList aggCalls) { if (groupKey.nodes.isEmpty() && aggCalls.isEmpty() - && config.pruneInputOfAggregate()) { + && !(stack.peek() != null && RelOptUtil.hasCalcViewHint(stack.peek().rel))) { // Query is "SELECT /* no fields */ FROM t GROUP BY ()", which always // returns one row with zero columns. if (config.preventEmptyFieldList()) { @@ -2442,7 +2442,7 @@ private RelBuilder aggregate_(GroupKeyImpl groupKey, } return pruneAggregateInputFieldsAndDeduplicateAggCalls(r, groupSet, groupSets, aggregateCalls, - frame.fields, registrar.extraNodes); + frame.fields, registrar.extraNodes, RelOptUtil.hasCalcViewHint(r)); } /** @@ -2454,10 +2454,12 @@ private RelBuilder pruneAggregateInputFieldsAndDeduplicateAggCalls( final ImmutableList groupSets, final List aggregateCalls, PairList, RelDataTypeField> inFields, - final List extraNodes) { + final List extraNodes, + boolean hasCalViewHint) { final ImmutableBitSet groupSetAfterPruning; final ImmutableList groupSetsAfterPruning; - if (config.pruneInputOfAggregate() + + if (!hasCalViewHint && r instanceof Project) { final Set fieldsUsed = RelOptUtil.getAllFields2(groupSet, aggregateCalls); @@ -2565,6 +2567,7 @@ private AggregateCall removeRedundantAggregateDistinct( return aggregateCall; } + /** Returns whether an input is already unique, and therefore a Project * can be created instead of an Aggregate. *