This is an automated email from the ASF dual-hosted git repository.

cbeikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 79d5001b945cbb9c47046b1b28b2345f78a72dc8
Author: Christian Beikov <[email protected]>
AuthorDate: Wed Feb 19 19:55:29 2020 +0100

    [CALCITE-3810] Render ANTI and SEMI join to NOT EXISTS and EXISTS in the 
JDBC adapter. Also add forgotten IS_DISTINCT_FROM translation support
---
 .../calcite/rel/rel2sql/RelToSqlConverter.java     |  96 +++++++++++++++++
 .../apache/calcite/rel/rel2sql/SqlImplementor.java |  16 ++-
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java | 114 +++++++++++++++++++++
 3 files changed, 221 insertions(+), 5 deletions(-)

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 5cc2606..63df2c4 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
@@ -76,6 +76,8 @@ import org.apache.calcite.sql.fun.SqlRowOperator;
 import org.apache.calcite.sql.fun.SqlSingleValueAggFunction;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.util.SqlShuttle;
+import org.apache.calcite.sql.util.SqlVisitor;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Pair;
@@ -145,8 +147,42 @@ public class RelToSqlConverter extends SqlImplementor
     throw new AssertionError("Need to implement " + e.getClass().getName());
   }
 
+  /**
+   * A SqlShuttle to replace references to a column of a table alias with the 
expression
+   * from the select item that is the source of that column.
+   * ANTI- and SEMI-joins generate an alias for right hand side relation which
+   * is used in the ON condition. But that alias is never created, so we have 
to inline references.
+   */
+  private static class AliasReplacementShuttle extends SqlShuttle {
+    private final String tableAlias;
+    private final RelDataType tableType;
+    private final SqlNodeList replaceSource;
+
+    AliasReplacementShuttle(String tableAlias, RelDataType tableType, 
SqlNodeList replaceSource) {
+      this.tableAlias = tableAlias;
+      this.tableType = tableType;
+      this.replaceSource = replaceSource;
+    }
+
+    @Override public SqlNode visit(SqlIdentifier id) {
+      if (tableAlias.equals(id.names.get(0))) {
+        int index = tableType.getField(
+            id.names.get(1), false, false).getIndex();
+        SqlNode selectItem = replaceSource.get(index);
+        if (selectItem.getKind() == SqlKind.AS) {
+          selectItem = ((SqlCall) selectItem).operand(0);
+        }
+        return selectItem.clone(id.getParserPosition());
+      }
+      return id;
+    }
+  }
+
   /** @see #dispatch */
   public Result visit(Join e) {
+    if (e.getJoinType() == JoinRelType.ANTI || e.getJoinType() == 
JoinRelType.SEMI) {
+      return visitAntiOrSemiJoin(e);
+    }
     final Result leftResult = visitChild(0, e.getLeft()).resetAlias();
     final Result rightResult = visitChild(1, e.getRight()).resetAlias();
     final Context leftContext = leftResult.qualifiedContext();
@@ -175,6 +211,66 @@ public class RelToSqlConverter extends SqlImplementor
     return result(join, leftResult, rightResult);
   }
 
+  protected Result visitAntiOrSemiJoin(Join e) {
+    final Result leftResult = visitChild(0, e.getLeft()).resetAlias();
+    final Result rightResult = visitChild(1, e.getRight()).resetAlias();
+    final Context leftContext = leftResult.qualifiedContext();
+    final Context rightContext = rightResult.qualifiedContext();
+    SqlNode sqlCondition = null;
+
+    SqlSelect sqlSelect = leftResult.asSelect();
+    sqlCondition = convertConditionToSqlNode(e.getCondition(),
+        leftContext,
+        rightContext,
+        e.getLeft().getRowType().getFieldCount(),
+        dialect);
+    if (leftResult.neededAlias != null) {
+      SqlVisitor<SqlNode> visitor = new 
AliasReplacementShuttle(leftResult.neededAlias,
+          e.getLeft().getRowType(), sqlSelect.getSelectList());
+      sqlCondition = sqlCondition.accept(visitor);
+    }
+    SqlNode fromPart = rightResult.asFrom();
+    SqlSelect existsSqlSelect;
+    if (fromPart.getKind() == SqlKind.SELECT) {
+      existsSqlSelect = (SqlSelect) fromPart;
+      existsSqlSelect.setSelectList(
+          new SqlNodeList(ImmutableList.of(SqlLiteral.createExactNumeric("1", 
POS)), POS));
+      if (existsSqlSelect.getWhere() != null) {
+        sqlCondition = SqlStdOperatorTable.AND.createCall(POS,
+            existsSqlSelect.getWhere(),
+            sqlCondition);
+      }
+      existsSqlSelect.setWhere(sqlCondition);
+    } else {
+      existsSqlSelect =
+          new SqlSelect(POS, null,
+              new SqlNodeList(
+                  ImmutableList.of(SqlLiteral.createExactNumeric("1", POS)), 
POS),
+              fromPart, sqlCondition, null,
+              null, null, null, null, null, null);
+    }
+    sqlCondition = SqlStdOperatorTable.EXISTS.createCall(POS, existsSqlSelect);
+    if (e.getJoinType() == JoinRelType.ANTI) {
+      sqlCondition = SqlStdOperatorTable.NOT.createCall(POS, sqlCondition);
+    }
+    if (sqlSelect.getWhere() != null) {
+      sqlCondition = SqlStdOperatorTable.AND.createCall(POS,
+          sqlSelect.getWhere(),
+          sqlCondition);
+    }
+    sqlSelect.setWhere(sqlCondition);
+    SqlNode resultNode;
+    if (leftResult.neededAlias != null) {
+      resultNode = SqlStdOperatorTable.AS.createCall(
+          new SqlNodeList(
+              ImmutableList.of(sqlSelect,
+                  new SqlIdentifier(leftResult.neededAlias, POS)), POS));
+    } else {
+      resultNode = sqlSelect;
+    }
+    return result(resultNode, leftResult, rightResult);
+  }
+
   private boolean isCrossJoin(final Join e) {
     return e.getJoinType() == JoinRelType.INNER && 
e.getCondition().isAlwaysTrue();
   }
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 979f313..31927a0 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
@@ -255,6 +255,7 @@ public abstract class SqlImplementor {
       return sqlCondition;
 
     case EQUALS:
+    case IS_DISTINCT_FROM:
     case IS_NOT_DISTINCT_FROM:
     case NOT_EQUALS:
     case GREATER_THAN:
@@ -464,11 +465,16 @@ public abstract class SqlImplementor {
   public Result result(SqlNode join, Result leftResult, Result rightResult) {
     final ImmutableMap.Builder<String, RelDataType> builder =
         ImmutableMap.builder();
-    collectAliases(builder, join,
-        Iterables.concat(leftResult.aliases.values(),
-            rightResult.aliases.values()).iterator());
-    return new Result(join, Expressions.list(Clause.FROM), null, null,
-        builder.build());
+    if (join.getKind() == SqlKind.JOIN) {
+      collectAliases(builder, join,
+          Iterables.concat(leftResult.aliases.values(),
+              rightResult.aliases.values()).iterator());
+      return new Result(join, Expressions.list(Clause.FROM), null, null,
+          builder.build());
+    } else {
+      return new Result(join, Expressions.list(Clause.FROM), null, null,
+          leftResult.aliases);
+    }
   }
 
   private void collectAliases(ImmutableMap.Builder<String, RelDataType> 
builder,
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 490bfa1..234323f 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
@@ -651,6 +651,120 @@ public class RelToSqlConverterTest {
     assertThat(sqlString, notNullValue());
   }
 
+  @Test public void testAntiJoin() {
+    final RelBuilder builder = relBuilder();
+    final RelNode root = builder
+        .scan("DEPT")
+        .scan("EMP")
+        .join(
+            JoinRelType.ANTI, builder.equals(
+              builder.field(2, 1, "DEPTNO"),
+              builder.field(2, 0, "DEPTNO")))
+        .project(builder.field("DEPTNO"))
+        .build();
+    final String expectedSql = "SELECT \"DEPTNO\"\n"
+        + "FROM \"scott\".\"DEPT\"\n"
+        + "WHERE NOT EXISTS (SELECT 1\n"
+        + "FROM \"scott\".\"EMP\"\n"
+        + "WHERE \"DEPT\".\"DEPTNO\" = \"EMP\".\"DEPTNO\")";
+    assertThat(toSql(root), isLinux(expectedSql));
+  }
+
+  @Test public void testSemiJoin() {
+    final RelBuilder builder = relBuilder();
+    final RelNode root = builder
+        .scan("DEPT")
+        .scan("EMP")
+        .join(
+            JoinRelType.SEMI, builder.equals(
+              builder.field(2, 1, "DEPTNO"),
+              builder.field(2, 0, "DEPTNO")))
+        .project(builder.field("DEPTNO"))
+        .build();
+    final String expectedSql = "SELECT \"DEPTNO\"\n"
+        + "FROM \"scott\".\"DEPT\"\n"
+        + "WHERE EXISTS (SELECT 1\n"
+        + "FROM \"scott\".\"EMP\"\n"
+        + "WHERE \"DEPT\".\"DEPTNO\" = \"EMP\".\"DEPTNO\")";
+    assertThat(toSql(root), isLinux(expectedSql));
+  }
+
+  @Test public void testSemiJoinFilter() {
+    final RelBuilder builder = relBuilder();
+    final RelNode root = builder
+        .scan("DEPT")
+        .scan("EMP")
+        .filter(
+            builder.call(SqlStdOperatorTable.GREATER_THAN,
+              builder.field(builder.peek().getRowType().getField("EMPNO", 
false, false).getIndex()),
+              builder.literal((short) 10)))
+        .join(
+            JoinRelType.SEMI, builder.equals(
+            builder.field(2, 1, "DEPTNO"),
+            builder.field(2, 0, "DEPTNO")))
+        .project(builder.field("DEPTNO"))
+        .build();
+    final String expectedSql = "SELECT \"DEPTNO\"\n"
+        + "FROM \"scott\".\"DEPT\"\n"
+        + "WHERE EXISTS (SELECT 1\n"
+        + "FROM (SELECT *\n"
+        + "FROM \"scott\".\"EMP\"\n"
+        + "WHERE \"EMPNO\" > 10) AS \"t\"\n"
+        + "WHERE \"DEPT\".\"DEPTNO\" = \"t\".\"DEPTNO\")";
+    assertThat(toSql(root), isLinux(expectedSql));
+  }
+
+  @Test public void testSemiJoinProject() {
+    final RelBuilder builder = relBuilder();
+    final RelNode root = builder
+        .scan("DEPT")
+        .scan("EMP")
+        .project(
+            builder.field(builder.peek().getRowType().getField("EMPNO", false, 
false).getIndex()),
+            builder.field(builder.peek().getRowType().getField("DEPTNO", 
false, false).getIndex()))
+        .join(
+            JoinRelType.SEMI, builder.equals(
+              builder.field(2, 1, "DEPTNO"),
+              builder.field(2, 0, "DEPTNO")))
+        .project(builder.field("DEPTNO"))
+        .build();
+    final String expectedSql = "SELECT \"DEPTNO\"\n"
+        + "FROM \"scott\".\"DEPT\"\n"
+        + "WHERE EXISTS (SELECT 1\n"
+        + "FROM (SELECT \"EMPNO\", \"DEPTNO\"\n"
+        + "FROM \"scott\".\"EMP\") AS \"t\"\n"
+        + "WHERE \"DEPT\".\"DEPTNO\" = \"t\".\"DEPTNO\")";
+    assertThat(toSql(root), isLinux(expectedSql));
+  }
+
+  @Test public void testSemiNestedJoin() {
+    final RelBuilder builder = relBuilder();
+    final RelNode base = builder
+        .scan("EMP")
+        .scan("EMP")
+        .join(
+            JoinRelType.INNER, builder.equals(
+              builder.field(2, 0, "EMPNO"),
+              builder.field(2, 1, "EMPNO")))
+        .build();
+    final RelNode root = builder
+        .scan("DEPT")
+        .push(base)
+        .join(
+            JoinRelType.SEMI, builder.equals(
+              builder.field(2, 1, "DEPTNO"),
+              builder.field(2, 0, "DEPTNO")))
+        .project(builder.field("DEPTNO"))
+        .build();
+    final String expectedSql = "SELECT \"DEPTNO\"\n"
+        + "FROM \"scott\".\"DEPT\"\n"
+        + "WHERE EXISTS (SELECT 1\n"
+        + "FROM \"scott\".\"EMP\"\n"
+        + "INNER JOIN \"scott\".\"EMP\" AS \"EMP0\" ON \"EMP\".\"EMPNO\" = 
\"EMP0\".\"EMPNO\"\n"
+        + "WHERE \"DEPT\".\"DEPTNO\" = \"EMP\".\"DEPTNO\")";
+    assertThat(toSql(root), isLinux(expectedSql));
+  }
+
   /** Test case for
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-2792";>[CALCITE-2792]
    * Stackoverflow while evaluating filter with large number of OR 
conditions</a>. */

Reply via email to