You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2023/05/28 04:22:05 UTC
[calcite] 07/07: [CALCITE-5717] RelBuilder.project of literals on a single-row Aggregate should create a Values
This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
commit 379f41d3be465992328d5659ea62b8355e0399d1
Author: Julian Hyde <jh...@apache.org>
AuthorDate: Sat May 27 10:50:28 2023 -0700
[CALCITE-5717] RelBuilder.project of literals on a single-row Aggregate should create a Values
If RelBuilder.project is applied to an Aggregate with no group keys
(which always returns one row) and all of the projected expressions are
literals, then the result should be a Values with one row.
For example,
SELECT 42 FROM myTable GROUP BY ()
becomes
VALUES 42
(Both queries always return one row, even if myTable is empty.)
Close apache/calcite#3209
---
.../java/org/apache/calcite/tools/RelBuilder.java | 28 ++++++++++++++++++----
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 27 ++++++++++++++++++---
.../org/apache/calcite/test/RelBuilderTest.java | 22 +++++++++++++++--
.../apache/calcite/test/SqlToRelConverterTest.java | 4 ++++
.../apache/calcite/test/SqlToRelConverterTest.xml | 5 +---
5 files changed, 72 insertions(+), 14 deletions(-)
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 f5fa384af4..d745488cb3 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -2097,17 +2097,19 @@ public class RelBuilder {
}
// If the expressions are all literals, and the input is a Values with N
- // rows, replace with a Values with same tuple N times.
+ // rows (or an Aggregate with 1 row), replace with a Values with same tuple
+ // N times.
+ final int rowCount;
if (config.simplifyValues()
- && frame.rel instanceof Values
- && nodeList.stream().allMatch(e -> e instanceof RexLiteral)) {
- final Values values = (Values) build();
+ && nodeList.stream().allMatch(e -> e instanceof RexLiteral)
+ && (rowCount = fixedRowCount(frame)) >= 0) {
+ RelNode unused = build();
final RelDataTypeFactory.Builder typeBuilder = getTypeFactory().builder();
Pair.forEach(fieldNameList, nodeList, (name, expr) ->
typeBuilder.add(requireNonNull(name, "name"), expr.getType()));
@SuppressWarnings({"unchecked", "rawtypes"})
final List<RexLiteral> tuple = (List<RexLiteral>) (List) nodeList;
- return values(Collections.nCopies(values.tuples.size(), tuple),
+ return values(Collections.nCopies(rowCount, tuple),
typeBuilder.build());
}
@@ -2122,6 +2124,22 @@ public class RelBuilder {
return this;
}
+ /** If current frame will return a known, constant number of
+ * rows, returns that number; otherwise returns -1. */
+ private static int fixedRowCount(Frame frame) {
+ if (frame.rel instanceof Values) {
+ return ((Values) frame.rel).tuples.size();
+ }
+ if (frame.rel instanceof Aggregate) {
+ final Aggregate aggregate = (Aggregate) frame.rel;
+ if (aggregate.getGroupSet().isEmpty()
+ && aggregate.getGroupType() == Aggregate.Group.SIMPLE) {
+ return 1;
+ }
+ }
+ return -1;
+ }
+
/** Creates a {@link Project} of the given
* expressions and field names, and optionally optimizing.
*
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 3c2d2adc65..53fde5cdf9 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -694,9 +694,30 @@ class RelToSqlConverterTest {
relFn(fn).ok("SELECT \"t\".\"V\" AS \"l_v\"\n"
+ "FROM (VALUES (1, 2)) AS \"t\" (\"K\", \"V\")\n"
+ "INNER JOIN "
- + "(SELECT 1 AS \"K\"\n"
- + "FROM (SELECT COUNT(\"ENAME\") AS \"DUMMY\"\n"
- + "FROM \"scott\".\"EMP\") AS \"t0\") AS \"t1\" ON \"t\".\"K\" = \"t1\".\"K\"");
+ + "(VALUES (1)) AS \"t0\" (\"K\") ON \"t\".\"K\" = \"t0\".\"K\"");
+ }
+
+ /** As {@link #testTrimmedAggregateUnderProject()}
+ * but the "COUNT(*) AS DUMMY" field is used. */
+ @Test public void testTrimmedAggregateUnderProject2() {
+ final Function<RelBuilder, RelNode> fn = b -> b
+ .values(new String[]{"K", "V"}, 1, 2)
+ .scan("EMP")
+ .aggregate(b.groupKey(),
+ b.aggregateCall(SqlStdOperatorTable.COUNT, b.field(1))
+ .as("DUMMY"))
+ .project(b.alias(b.field("DUMMY"), "K"))
+ .join(JoinRelType.INNER,
+ b.call(SqlStdOperatorTable.EQUALS,
+ b.field(2, 0, "K"),
+ b.field(2, 1, "K")))
+ .project(b.alias(b.field(1), "l_v"))
+ .build();
+ // RelFieldTrimmer maybe build the RelNode.
+ relFn(fn).ok("SELECT \"t\".\"V\" AS \"l_v\"\n"
+ + "FROM (VALUES (1, 2)) AS \"t\" (\"K\", \"V\")\n"
+ + "INNER JOIN (SELECT COUNT(\"ENAME\") AS \"DUMMY\"\n"
+ + "FROM \"scott\".\"EMP\") AS \"t0\" ON \"t\".\"K\" = \"t0\".\"DUMMY\"");
}
/** Tests GROUP BY ROLLUP of two columns. The SQL for MySQL has
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 727fe5ea7b..3000e84d71 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -1356,13 +1356,31 @@ public class RelBuilderTest {
RelNode root =
builder.scan("EMP")
.aggregate(builder.groupKey(), builder.count().as("C"))
+ .filter(
+ builder.greaterThan(builder.field("C"), builder.literal(5)))
.project(builder.literal(4), builder.literal(2), builder.field(0))
.aggregate(builder.groupKey(builder.field(0), builder.field(1)))
.build();
final String expected = ""
+ "LogicalProject($f0=[4], $f1=[2])\n"
- + " LogicalAggregate(group=[{}], C=[COUNT()])\n"
- + " LogicalTableScan(table=[[scott, EMP]])\n";
+ + " LogicalFilter(condition=[>($0, 5)])\n"
+ + " LogicalAggregate(group=[{}], C=[COUNT()])\n"
+ + " LogicalTableScan(table=[[scott, EMP]])\n";
+ assertThat(root, hasTree(expected));
+ }
+
+ /** Unlike {@link #testAggregate5()}, where the input to the Aggregate has at
+ * most one row (zero or one rows), the input is known to be exactly one row,
+ * and therefore can be converted to {@code Values}. */
+ @Test void testAggregate5b() {
+ final RelBuilder builder = RelBuilder.create(config().build());
+ RelNode root =
+ builder.scan("EMP")
+ .aggregate(builder.groupKey(), builder.count().as("C"))
+ .project(builder.literal(4), builder.literal(2), builder.field(0))
+ .aggregate(builder.groupKey(builder.field(0), builder.field(1)))
+ .build();
+ final String expected = "LogicalValues(tuples=[[{ 4, 2 }]])\n";
assertThat(root, hasTree(expected));
}
diff --git a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 95b6274a31..4e2c029fb9 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -426,6 +426,10 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
sql("select sum(deptno) from emp group by ()").ok();
}
+ /** Test case for
+ * <a href="https://issues.apache.org/jira/browse/CALCITE-5717">[CALCITE-5717]
+ * RelBuilder.project should generate a Values if all expressions are literals
+ * and the input is an Aggregate that returns exactly one row</a>. */
@Test void testGroupEmptyYieldLiteral() {
// Expected plan is "VALUES 42". The result is one row even if EMP is empty.
sql("select 42 from emp group by ()").ok();
diff --git a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 6902fe5f81..b48f9015ad 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -2350,10 +2350,7 @@ LogicalAggregate(group=[{}], EXPR$0=[SUM($0)])
</Resource>
<Resource name="plan">
<![CDATA[
-LogicalProject(EXPR$0=[42])
- LogicalAggregate(group=[{}])
- LogicalProject($f0=[0])
- LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+LogicalValues(tuples=[[{ 42 }]])
]]>
</Resource>
</TestCase>