[CALCITE-1849] Support RexSubQuery in RelToSqlConverter

Close apache/calcite#477


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

Branch: refs/heads/master
Commit: e43520a8164f1ade4389ae853befaf85ff8a2c82
Parents: 9efefbc
Author: Zhiqiang-He <absolute...@qq.com>
Authored: Mon Jun 19 20:27:06 2017 +0800
Committer: Julian Hyde <jh...@apache.org>
Committed: Mon Aug 7 09:33:33 2017 -0700

----------------------------------------------------------------------
 .../calcite/rel/rel2sql/RelToSqlConverter.java  |  10 ++
 .../calcite/rel/rel2sql/SqlImplementor.java     |  51 ++++++++
 .../rel/rel2sql/RelToSqlConverterTest.java      | 117 ++++++++++++++++++-
 3 files changed, 172 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/calcite/blob/e43520a8/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java
----------------------------------------------------------------------
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 4896a74..39587a0 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
@@ -22,6 +22,7 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.core.Calc;
+import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.Filter;
 import org.apache.calcite.rel.core.Intersect;
 import org.apache.calcite.rel.core.Join;
@@ -138,6 +139,7 @@ public class RelToSqlConverter extends SqlImplementor
   public Result visit(Filter e) {
     final RelNode input = e.getInput();
     Result x = visitChild(0, input);
+    parseCorrelTable(e, x);
     if (input instanceof Aggregate) {
       final Builder builder;
       if (((Aggregate) input).getInput() instanceof Project) {
@@ -158,6 +160,7 @@ public class RelToSqlConverter extends SqlImplementor
   /** @see #dispatch */
   public Result visit(Project e) {
     Result x = visitChild(0, e.getInput());
+    parseCorrelTable(e, x);
     if (isStar(e.getChildExps(), e.getInput().getRowType())) {
       return x;
     }
@@ -239,6 +242,7 @@ public class RelToSqlConverter extends SqlImplementor
   /** @see #dispatch */
   public Result visit(Calc e) {
     Result x = visitChild(0, e.getInput());
+    parseCorrelTable(e, x);
     final RexProgram program = e.getProgram();
     Builder builder =
         program.getCondition() != null
@@ -474,6 +478,12 @@ public class RelToSqlConverter extends SqlImplementor
     }
     selectList.add(node);
   }
+
+  private void parseCorrelTable(RelNode relNode, Result x) {
+    for (CorrelationId id : relNode.getVariablesSet()) {
+      correlTableMap.put(id, x.qualifiedContext());
+    }
+  }
 }
 
 // End RelToSqlConverter.java

http://git-wip-us.apache.org/repos/asf/calcite/blob/e43520a8/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 8b227c7..285cfe4 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
@@ -21,11 +21,14 @@ import org.apache.calcite.linq4j.tree.Expressions;
 import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.CorrelationId;
 import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexCorrelVariable;
 import org.apache.calcite.rex.RexDynamicParam;
+import org.apache.calcite.rex.RexFieldAccess;
 import org.apache.calcite.rex.RexFieldCollation;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLiteral;
@@ -34,6 +37,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexOver;
 import org.apache.calcite.rex.RexPatternFieldRef;
 import org.apache.calcite.rex.RexProgram;
+import org.apache.calcite.rex.RexSubQuery;
 import org.apache.calcite.rex.RexWindow;
 import org.apache.calcite.rex.RexWindowBound;
 import org.apache.calcite.sql.JoinType;
@@ -115,6 +119,8 @@ public abstract class SqlImplementor {
   protected final Set<String> aliasSet = new LinkedHashSet<>();
   protected final Map<String, SqlNode> ordinalMap = new HashMap<>();
 
+  protected final Map<CorrelationId, Context> correlTableMap = new HashMap<>();
+
   protected SqlImplementor(SqlDialect dialect) {
     this.dialect = Preconditions.checkNotNull(dialect);
   }
@@ -511,6 +517,8 @@ public abstract class SqlImplementor {
      * @param rex Expression to convert
      */
     public SqlNode toSql(RexProgram program, RexNode rex) {
+      final RexSubQuery subQuery;
+      final SqlNode sqlSubQuery;
       switch (rex.getKind()) {
       case LOCAL_REF:
         final int index = ((RexLocalRef) rex).getIndex();
@@ -519,6 +527,13 @@ public abstract class SqlImplementor {
       case INPUT_REF:
         return field(((RexInputRef) rex).getIndex());
 
+      case FIELD_ACCESS:
+        RexFieldAccess access = (RexFieldAccess) rex;
+        final RexCorrelVariable variable =
+            (RexCorrelVariable) access.getReferenceExpr();
+        final Context aliasContext = correlTableMap.get(variable.id);
+        return aliasContext.field(access.getField().getIndex());
+
       case PATTERN_INPUT_REF:
         final RexPatternFieldRef ref = (RexPatternFieldRef) rex;
         String pv = ref.getAlpha();
@@ -602,6 +617,42 @@ public abstract class SqlImplementor {
         final RexDynamicParam caseParam = (RexDynamicParam) rex;
         return new SqlDynamicParam(caseParam.getIndex(), POS);
 
+      case IN:
+        subQuery = (RexSubQuery) rex;
+        sqlSubQuery = visitChild(0, subQuery.rel).asQueryOrValues();
+        List<RexNode> operands = subQuery.operands;
+        SqlNode op0;
+        if (operands.size() == 1) {
+          op0 = toSql(program, operands.get(0));
+        } else {
+          final List<SqlNode> cols = toSql(program, operands);
+          op0 = new SqlNodeList(cols, POS);
+        }
+        return subQuery.getOperator().createCall(POS, op0, sqlSubQuery);
+
+      case EXISTS:
+      case SCALAR_QUERY:
+        subQuery = (RexSubQuery) rex;
+        sqlSubQuery = visitChild(0, subQuery.rel).asQueryOrValues();
+        return subQuery.getOperator().createCall(POS, sqlSubQuery);
+
+      case NOT:
+        RexNode operand = ((RexCall) rex).operands.get(0);
+        final SqlNode node = toSql(program, operand);
+        switch (operand.getKind()) {
+        case IN:
+          return SqlStdOperatorTable.NOT_IN
+              .createCall(POS, ((SqlCall) node).getOperandList());
+        case LIKE:
+          return SqlStdOperatorTable.NOT_LIKE
+              .createCall(POS, ((SqlCall) node).getOperandList());
+        case SIMILAR:
+          return SqlStdOperatorTable.NOT_SIMILAR_TO
+              .createCall(POS, ((SqlCall) node).getOperandList());
+        default:
+          return SqlStdOperatorTable.NOT.createCall(POS, node);
+        }
+
       default:
         if (rex instanceof RexOver) {
           return toSql(program, (RexOver) rex);

http://git-wip-us.apache.org/repos/asf/calcite/blob/e43520a8/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 82fee32..15725ec 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
@@ -31,6 +31,7 @@ import org.apache.calcite.sql.SqlDialect;
 import org.apache.calcite.sql.SqlDialect.DatabaseProduct;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.sql2rel.SqlToRelConverter;
 import org.apache.calcite.test.CalciteAssert;
 import org.apache.calcite.tools.FrameworkConfig;
 import org.apache.calcite.tools.Frameworks;
@@ -57,20 +58,35 @@ import static org.junit.Assert.assertThat;
  * Tests for {@link RelToSqlConverter}.
  */
 public class RelToSqlConverterTest {
+  static final SqlToRelConverter.Config DEFAULT_REL_CONFIG =
+      SqlToRelConverter.configBuilder()
+          .withTrimUnusedFields(false)
+          .withConvertTableAccess(false)
+          .build();
+
+  static final SqlToRelConverter.Config NO_EXPAND_CONFIG =
+      SqlToRelConverter.configBuilder()
+          .withTrimUnusedFields(false)
+          .withConvertTableAccess(false)
+          .withExpand(false)
+          .build();
+
   /** Initiates a test case with a given SQL query. */
   private Sql sql(String sql) {
     return new Sql(CalciteAssert.SchemaSpec.JDBC_FOODMART, sql,
-        SqlDialect.CALCITE, ImmutableList.<Function<RelNode, RelNode>>of());
+        SqlDialect.CALCITE, DEFAULT_REL_CONFIG,
+        ImmutableList.<Function<RelNode, RelNode>>of());
   }
 
   private static Planner getPlanner(List<RelTraitDef> traitDefs,
       SqlParser.Config parserConfig, CalciteAssert.SchemaSpec schemaSpec,
-      Program... programs) {
+      SqlToRelConverter.Config sqlToRelConf, Program... programs) {
     final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
     final FrameworkConfig config = Frameworks.newConfigBuilder()
         .parserConfig(parserConfig)
         .defaultSchema(CalciteAssert.addSchema(rootSchema, schemaSpec))
         .traitDefs(traitDefs)
+        .sqlToRelConverterConfig(sqlToRelConf)
         .programs(programs)
         .build();
     return Frameworks.getPlanner(config);
@@ -830,6 +846,88 @@ public class RelToSqlConverterTest {
         .ok(expectedMssql);
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-1849";>[CALCITE-1849]
+   * Support sub-queries (RexSubQuery) in RelToSqlConverter</a>. */
+  @Test public void testExistsWithExpand() {
+    String query = "select \"product_name\" from \"product\" a "
+        + "where exists (select count(*) "
+        + "from \"sales_fact_1997\"b "
+        + "where b.\"product_id\" = a.\"product_id\")";
+    String expected = "SELECT \"product_name\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE EXISTS (SELECT COUNT(*)\n"
+        + "FROM \"foodmart\".\"sales_fact_1997\"\n"
+        + "WHERE \"product_id\" = \"product\".\"product_id\")";
+    sql(query).config(NO_EXPAND_CONFIG).ok(expected);
+  }
+
+  @Test public void testNotExistsWithExpand() {
+    String query = "select \"product_name\" from \"product\" a "
+        + "where not exists (select count(*) "
+        + "from \"sales_fact_1997\"b "
+        + "where b.\"product_id\" = a.\"product_id\")";
+    String expected = "SELECT \"product_name\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE NOT EXISTS (SELECT COUNT(*)\n"
+        + "FROM \"foodmart\".\"sales_fact_1997\"\n"
+        + "WHERE \"product_id\" = \"product\".\"product_id\")";
+    sql(query).config(NO_EXPAND_CONFIG).ok(expected);
+  }
+
+  @Test public void testSubQueryInWithExpand() {
+    String query = "select \"product_name\" from \"product\" a "
+        + "where \"product_id\" in (select \"product_id\" "
+        + "from \"sales_fact_1997\"b "
+        + "where b.\"product_id\" = a.\"product_id\")";
+    String expected = "SELECT \"product_name\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE \"product_id\" IN (SELECT \"product_id\"\n"
+        + "FROM \"foodmart\".\"sales_fact_1997\"\n"
+        + "WHERE \"product_id\" = \"product\".\"product_id\")";
+    sql(query).config(NO_EXPAND_CONFIG).ok(expected);
+  }
+
+  @Test public void testSubQueryInWithExpand2() {
+    String query = "select \"product_name\" from \"product\" a "
+        + "where \"product_id\" in (1, 2)";
+    String expected = "SELECT \"product_name\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE \"product_id\" = 1 OR \"product_id\" = 2";
+    sql(query).config(NO_EXPAND_CONFIG).ok(expected);
+  }
+
+  @Test public void testSubQueryNotInWithExpand() {
+    String query = "select \"product_name\" from \"product\" a "
+        + "where \"product_id\" not in (select \"product_id\" "
+        + "from \"sales_fact_1997\"b "
+        + "where b.\"product_id\" = a.\"product_id\")";
+    String expected = "SELECT \"product_name\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE \"product_id\" NOT IN (SELECT \"product_id\"\n"
+        + "FROM \"foodmart\".\"sales_fact_1997\"\n"
+        + "WHERE \"product_id\" = \"product\".\"product_id\")";
+    sql(query).config(NO_EXPAND_CONFIG).ok(expected);
+  }
+
+  @Test public void testLike() {
+    String query = "select \"product_name\" from \"product\" a "
+        + "where \"product_name\" like 'abc'";
+    String expected = "SELECT \"product_name\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE \"product_name\" LIKE 'abc'";
+    sql(query).ok(expected);
+  }
+
+  @Test public void testNotLike() {
+    String query = "select \"product_name\" from \"product\" a "
+        + "where \"product_name\" not like 'abc'";
+    String expected = "SELECT \"product_name\"\n"
+        + "FROM \"foodmart\".\"product\"\n"
+        + "WHERE \"product_name\" NOT LIKE 'abc'";
+    sql(query).ok(expected);
+  }
+
   @Test public void testMatchRecognizePatternExpression() {
     String sql = "select *\n"
         + "  from \"product\" match_recognize\n"
@@ -1806,21 +1904,28 @@ public class RelToSqlConverterTest {
     private final String sql;
     private final SqlDialect dialect;
     private final List<Function<RelNode, RelNode>> transforms;
+    private final SqlToRelConverter.Config config;
 
     Sql(CalciteAssert.SchemaSpec schemaSpec, String sql, SqlDialect dialect,
+        SqlToRelConverter.Config config,
         List<Function<RelNode, RelNode>> transforms) {
       this.schemaSpec = schemaSpec;
       this.sql = sql;
       this.dialect = dialect;
       this.transforms = ImmutableList.copyOf(transforms);
+      this.config = config;
     }
 
     Sql dialect(SqlDialect dialect) {
-      return new Sql(schemaSpec, sql, dialect, transforms);
+      return new Sql(schemaSpec, sql, dialect, config, transforms);
+    }
+
+    Sql config(SqlToRelConverter.Config config) {
+      return new Sql(schemaSpec, sql, dialect, config, transforms);
     }
 
     Sql optimize(final RuleSet ruleSet, final RelOptPlanner relOptPlanner) {
-      return new Sql(schemaSpec, sql, dialect,
+      return new Sql(schemaSpec, sql, dialect, config,
           FlatLists.append(transforms, new Function<RelNode, RelNode>() {
             public RelNode apply(RelNode r) {
               Program program = Programs.of(ruleSet);
@@ -1849,7 +1954,7 @@ public class RelToSqlConverterTest {
 
     String exec() {
       final Planner planner =
-          getPlanner(null, SqlParser.Config.DEFAULT, schemaSpec);
+          getPlanner(null, SqlParser.Config.DEFAULT, schemaSpec, config);
       try {
         SqlNode parse = planner.parse(sql);
         SqlNode validate = planner.validate(parse);
@@ -1869,7 +1974,7 @@ public class RelToSqlConverterTest {
     }
 
     public Sql schema(CalciteAssert.SchemaSpec schemaSpec) {
-      return new Sql(schemaSpec, sql, dialect, transforms);
+      return new Sql(schemaSpec, sql, dialect, config, transforms);
     }
   }
 }

Reply via email to