[CALCITE-1174] When generating SQL, translate SUM0(x) to COALESCE(SUM(x), 0)

Don't require that '(' and ')' are formally part of a list; it the
caller may not know the full context within the parse tree.

Add some tests for RelToSql applied to empty GROUP BY.


Project: http://git-wip-us.apache.org/repos/asf/calcite/repo
Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/332ffb44
Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/332ffb44
Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/332ffb44

Branch: refs/heads/master
Commit: 332ffb44783142bd75ffd429e72bfba305aa3bf1
Parents: c33a4f4
Author: Julian Hyde <[email protected]>
Authored: Wed Oct 3 15:09:22 2018 -0700
Committer: Julian Hyde <[email protected]>
Committed: Wed Oct 31 12:01:27 2018 -0700

----------------------------------------------------------------------
 .../calcite/rel/rel2sql/SqlImplementor.java     | 39 +++++++----
 .../calcite/sql/pretty/SqlPrettyWriter.java     |  6 --
 .../rel/rel2sql/RelToSqlConverterTest.java      | 70 ++++++++++++++++++++
 3 files changed, 97 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/332ffb44/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
----------------------------------------------------------------------
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 8660951..0323a52 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
@@ -675,11 +675,21 @@ public abstract class SqlImplementor {
           orderList, isRows, lowerBound, upperBound, allowPartial, POS);
 
       final List<SqlNode> nodeList = toSql(program, rexOver.getOperands());
-      final SqlCall aggFunctionCall =
-          rexOver.getAggOperator().createCall(POS, nodeList);
+      return createOverCall(rexOver.getAggOperator(), nodeList, sqlWindow);
+    }
 
+    private SqlCall createOverCall(SqlAggFunction op, List<SqlNode> operands,
+        SqlWindow window) {
+      if (op instanceof SqlSumEmptyIsZeroAggFunction) {
+        // Rewrite "SUM0(x) OVER w" to "COALESCE(SUM(x) OVER w, 0)"
+        final SqlCall node =
+            createOverCall(SqlStdOperatorTable.SUM, operands, window);
+        return SqlStdOperatorTable.COALESCE.createCall(POS, node,
+            SqlLiteral.createExactNumeric("0", POS));
+      }
+      final SqlCall aggFunctionCall = op.createCall(POS, operands);
       return SqlStdOperatorTable.OVER.createCall(POS, aggFunctionCall,
-          sqlWindow);
+          window);
     }
 
     private SqlNode toSql(RexProgram program, RexFieldCollation rfc) {
@@ -760,17 +770,22 @@ public abstract class SqlImplementor {
 
     /** Converts a call to an aggregate function to an expression. */
     public SqlNode toSql(AggregateCall aggCall) {
-      SqlOperator op = aggCall.getAggregation();
-      if (op instanceof SqlSumEmptyIsZeroAggFunction) {
-        op = SqlStdOperatorTable.SUM;
-      }
-      final List<SqlNode> operands = Expressions.list();
+      final SqlOperator op = aggCall.getAggregation();
+      final List<SqlNode> operandList = Expressions.list();
       for (int arg : aggCall.getArgList()) {
-        operands.add(field(arg));
+        operandList.add(field(arg));
+      }
+      final SqlLiteral qualifier =
+          aggCall.isDistinct() ? SqlSelectKeyword.DISTINCT.symbol(POS) : null;
+      final SqlNode[] operands = operandList.toArray(new SqlNode[0]);
+      if (op instanceof SqlSumEmptyIsZeroAggFunction) {
+        final SqlNode node =
+            SqlStdOperatorTable.SUM.createCall(qualifier, POS, operands);
+        return SqlStdOperatorTable.COALESCE.createCall(POS, node,
+            SqlLiteral.createExactNumeric("0", POS));
+      } else {
+        return op.createCall(qualifier, POS, operands);
       }
-      return op.createCall(
-          aggCall.isDistinct() ? SqlSelectKeyword.DISTINCT.symbol(POS) : null,
-          POS, operands.toArray(new SqlNode[0]));
     }
 
     /** Converts a collation to an ORDER BY item. */

http://git-wip-us.apache.org/repos/asf/calcite/blob/332ffb44/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java 
b/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java
index 90ef611..06358a1 100644
--- a/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java
+++ b/core/src/main/java/org/apache/calcite/sql/pretty/SqlPrettyWriter.java
@@ -881,12 +881,6 @@ public class SqlPrettyWriter implements SqlWriter {
   }
 
   public void print(String s) {
-    if (s.equals("(")) {
-      throw new RuntimeException("Use 'startList'");
-    }
-    if (s.equals(")")) {
-      throw new RuntimeException("Use 'endList'");
-    }
     maybeWhitespace(s);
     pw.print(s);
     charCount += s.length();

http://git-wip-us.apache.org/repos/asf/calcite/blob/332ffb44/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
----------------------------------------------------------------------
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 7e51158..70bf317 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
@@ -178,6 +178,37 @@ public class RelToSqlConverterTest {
     sql(query).ok(expected);
   }
 
+  @Test public void testSelectQueryWithGroupByEmpty() {
+    final String sql0 = "select count(*) from \"product\" group by ()";
+    final String sql1 = "select count(*) from \"product\"";
+    final String expected = "SELECT COUNT(*)\n"
+        + "FROM \"foodmart\".\"product\"";
+    final String expectedMySql = "SELECT COUNT(*)\n"
+        + "FROM `foodmart`.`product`";
+    sql(sql0)
+        .ok(expected)
+        .withMysql()
+        .ok(expectedMySql);
+    sql(sql1)
+        .ok(expected)
+        .withMysql()
+        .ok(expectedMySql);
+  }
+
+  @Test public void testSelectQueryWithGroupByEmpty2() {
+    final String query = "select 42 as c from \"product\" group by ()";
+    final String expected = "SELECT 42 AS \"C\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "GROUP BY ()";
+    final String expectedMySql = "SELECT 42 AS `C`\n"
+        + "FROM `foodmart`.`product`\n"
+        + "GROUP BY ()";
+    sql(query)
+        .ok(expected)
+        .withMysql()
+        .ok(expectedMySql);
+  }
+
   @Test public void testSelectQueryWithMinAggregateFunction() {
     String query = "select min(\"net_weight\") from \"product\" group by 
\"product_class_id\" ";
     final String expected = "SELECT MIN(\"net_weight\")\n"
@@ -236,6 +267,45 @@ public class RelToSqlConverterTest {
   }
 
   /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1174";>[CALCITE-1174]
+   * When generating SQL, translate SUM0(x) to COALESCE(SUM(x), 0)</a>. */
+  @Test public void testSum0BecomesCoalesce() {
+    final RelBuilder builder = relBuilder();
+    final RelNode root = builder
+        .scan("EMP")
+        .aggregate(builder.groupKey(),
+            builder.aggregateCall(SqlStdOperatorTable.SUM0, false, false, null,
+                "s", builder.field(3)))
+        .build();
+    final String expectedMysql = "SELECT COALESCE(SUM(`MGR`), 0) AS `s`\n"
+        + "FROM `scott`.`EMP`";
+    assertThat(toSql(root, SqlDialect.DatabaseProduct.MYSQL.getDialect()),
+        isLinux(expectedMysql));
+    final String expectedPostgresql = "SELECT COALESCE(SUM(\"MGR\"), 0) AS 
\"s\"\n"
+        + "FROM \"scott\".\"EMP\"";
+    assertThat(toSql(root, SqlDialect.DatabaseProduct.POSTGRESQL.getDialect()),
+        isLinux(expectedPostgresql));
+  }
+
+  /** As {@link #testSum0BecomesCoalesce()} but for windowed aggregates. */
+  @Test public void testWindowedSum0BecomesCoalesce() {
+    final String query = "select\n"
+        + "  AVG(\"net_weight\") OVER (order by \"product_id\" rows 3 
preceding)\n"
+        + "from \"foodmart\".\"product\"";
+    final String expectedPostgresql = "SELECT CASE WHEN (COUNT(\"net_weight\")"
+        + " OVER (ORDER BY \"product_id\" ROWS BETWEEN 3 PRECEDING AND CURRENT 
ROW)) > 0 "
+        + "THEN CAST(COALESCE(SUM(\"net_weight\")"
+        + " OVER (ORDER BY \"product_id\" ROWS BETWEEN 3 PRECEDING AND CURRENT 
ROW), 0)"
+        + " AS DOUBLE PRECISION) "
+        + "ELSE NULL END / (COUNT(\"net_weight\")"
+        + " OVER (ORDER BY \"product_id\" ROWS BETWEEN 3 PRECEDING AND CURRENT 
ROW))\n"
+        + "FROM \"foodmart\".\"product\"";
+    sql(query)
+        .withPostgresql()
+        .ok(expectedPostgresql);
+  }
+
+  /** Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1946";>[CALCITE-1946]
    * JDBC adapter should generate sub-SELECT if dialect does not support nested
    * aggregate functions</a>. */

Reply via email to