diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDelete.java b/core/src/main/java/org/apache/calcite/sql/SqlDelete.java index 9e5ff8c87d4..c9da35c48bf 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlDelete.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlDelete.java @@ -152,8 +152,7 @@ public SqlNode getTargetTable() { } SqlNode condition = this.condition; if (condition != null) { - writer.sep("WHERE"); - condition.unparse(writer, opLeft, opRight); + SqlUtil.unparseWhereClause(writer, condition, opLeft, opRight); } writer.endList(frame); } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java index b8db2d8f5ab..7e37ac732f2 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlSelectOperator.java @@ -16,7 +16,6 @@ */ package org.apache.calcite.sql; -import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.sql.parser.SqlParserPos; import org.apache.calcite.sql.type.ReturnTypes; import org.apache.calcite.sql.util.SqlBasicVisitor; @@ -24,7 +23,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.ArrayList; import java.util.List; import static org.apache.calcite.linq4j.Nullness.castNonNull; @@ -175,34 +173,7 @@ public SqlSelect createCall( SqlNode where = select.where; if (where != null) { - writer.sep("WHERE"); - - if (!writer.isAlwaysUseParentheses()) { - SqlNode node = where; - - // decide whether to split on ORs or ANDs - SqlBinaryOperator whereSep = SqlStdOperatorTable.AND; - if ((node instanceof SqlCall) - && node.getKind() == SqlKind.OR) { - whereSep = SqlStdOperatorTable.OR; - } - - // unroll whereClause - final List list = new ArrayList<>(0); - while (node.getKind() == whereSep.kind) { - assert node instanceof SqlCall; - final SqlCall call1 = (SqlCall) node; - list.add(0, call1.operand(1)); - node = call1.operand(0); - } - list.add(0, node); - - // unparse in a WHERE_LIST frame - writer.list(SqlWriter.FrameTypeEnum.WHERE_LIST, whereSep, - new SqlNodeList(list, where.getParserPosition())); - } else { - where.unparse(writer, 0, 0); - } + SqlUtil.unparseWhereClause(writer, where, 0, 0); } if (select.groupBy != null) { SqlNodeList groupBy = diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java b/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java index 82985f2bd72..22d669efcf0 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlUpdate.java @@ -202,8 +202,7 @@ public void setSourceSelect(SqlSelect sourceSelect) { writer.endList(setFrame); SqlNode condition = this.condition; if (condition != null) { - writer.sep("WHERE"); - condition.unparse(writer, opLeft, opRight); + SqlUtil.unparseWhereClause(writer, condition, opLeft, opRight); } writer.endList(frame); } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java index d561bc8abb0..1bafd7cfff8 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java @@ -454,6 +454,49 @@ public static void unparseBinarySyntax( writer.endList(frame); } + /** + * Unparses a WHERE clause. + * + *

Unparsing the condition in a {@link SqlWriter.FrameTypeEnum#WHERE_LIST} + * frame lets sub-queries in predicates recognize that they need + * parentheses. + * + * @param writer Writer + * @param where WHERE condition + * @param leftPrec Left precedence + * @param rightPrec Right precedence + */ + public static void unparseWhereClause(SqlWriter writer, SqlNode where, + int leftPrec, int rightPrec) { + writer.sep("WHERE"); + + if (!writer.isAlwaysUseParentheses()) { + SqlNode node = where; + + // Decide whether to split on ORs or ANDs. + SqlBinaryOperator whereSep = SqlStdOperatorTable.AND; + if ((node instanceof SqlCall) + && node.getKind() == SqlKind.OR) { + whereSep = SqlStdOperatorTable.OR; + } + + // Unroll whereClause. + final List list = new ArrayList<>(0); + while (node.getKind() == whereSep.kind) { + assert node instanceof SqlCall; + final SqlCall call1 = (SqlCall) node; + list.add(0, call1.operand(1)); + node = call1.operand(0); + } + list.add(0, node); + + writer.list(SqlWriter.FrameTypeEnum.WHERE_LIST, whereSep, + new SqlNodeList(list, where.getParserPosition())); + } else { + where.unparse(writer, leftPrec, rightPrec); + } + } + /** * Concatenates string literals. * 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 2953f3b186f..d87d8c1681f 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 @@ -10163,6 +10163,30 @@ private void checkLiteral2(String expression, String expected) { final String expected2 = "UPDATE \"foodmart\".\"product\" SET \"product_name\" = 'calcite', " + "\"product_id\" = 10\nWHERE \"product_id\" = 1"; sql(sql2).ok(expected2); + + final String sql3 = "update \"foodmart\".\"product\"\n" + + "set \"product_name\" = 'calcite'\n" + + "where exists (\n" + + " select 1 from \"foodmart\".\"product_class\")"; + final String expected3 = "UPDATE \"foodmart\".\"product\" SET \"product_name\" = " + + "'calcite'\nWHERE EXISTS (SELECT *\nFROM \"foodmart\".\"product_class\")"; + sql(sql3).ok(expected3); + } + + @Test void testDelete() { + final String sql0 = "delete from \"foodmart\".\"product\"\n" + + "where exists (\n" + + " select 1 from \"foodmart\".\"product_class\")"; + final String expected0 = "DELETE FROM \"foodmart\".\"product\"\n" + + "WHERE EXISTS (SELECT *\nFROM \"foodmart\".\"product_class\")"; + sql(sql0).ok(expected0); + + final String sql1 = "delete from \"foodmart\".\"product\"\n" + + "where not exists (\n" + + " select 1 from \"foodmart\".\"product_class\")"; + final String expected1 = "DELETE FROM \"foodmart\".\"product\"\n" + + "WHERE NOT EXISTS (SELECT *\nFROM \"foodmart\".\"product_class\")"; + sql(sql1).ok(expected1); } /**