blob: b909946494e0b0187729b999da38fbcabe8aac7b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.calcite.rel.logical;
import org.apache.calcite.adapter.enumerable.EnumerableConvention;
import org.apache.calcite.adapter.enumerable.EnumerableRules;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.rules.CoreRules;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.test.CalciteAssert;
import org.apache.calcite.test.RelBuilderTest;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.Planner;
import org.apache.calcite.tools.Program;
import org.apache.calcite.tools.Programs;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RuleSets;
import org.apache.calcite.util.Holder;
import org.apache.calcite.util.TestUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.junit.jupiter.api.Test;
import static org.apache.calcite.test.Matchers.hasTree;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Tests for {@link ToLogicalConverter}.
*/
class ToLogicalConverterTest {
private static final ImmutableSet<RelOptRule> RULE_SET =
ImmutableSet.of(
CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW,
EnumerableRules.ENUMERABLE_VALUES_RULE,
EnumerableRules.ENUMERABLE_JOIN_RULE,
EnumerableRules.ENUMERABLE_CORRELATE_RULE,
EnumerableRules.ENUMERABLE_PROJECT_RULE,
EnumerableRules.ENUMERABLE_FILTER_RULE,
EnumerableRules.ENUMERABLE_AGGREGATE_RULE,
EnumerableRules.ENUMERABLE_SORT_RULE,
EnumerableRules.ENUMERABLE_LIMIT_RULE,
EnumerableRules.ENUMERABLE_COLLECT_RULE,
EnumerableRules.ENUMERABLE_UNCOLLECT_RULE,
EnumerableRules.ENUMERABLE_UNION_RULE,
EnumerableRules.ENUMERABLE_INTERSECT_RULE,
EnumerableRules.ENUMERABLE_MINUS_RULE,
EnumerableRules.ENUMERABLE_WINDOW_RULE,
EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE,
EnumerableRules.TO_INTERPRETER);
private static final SqlToRelConverter.Config DEFAULT_REL_CONFIG =
SqlToRelConverter.config().withTrimUnusedFields(false);
private static FrameworkConfig frameworkConfig() {
final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
final SchemaPlus schema = CalciteAssert.addSchema(rootSchema,
CalciteAssert.SchemaSpec.JDBC_FOODMART);
return Frameworks.newConfigBuilder()
.defaultSchema(schema)
.sqlToRelConverterConfig(DEFAULT_REL_CONFIG)
.build();
}
private static RelBuilder builder() {
return RelBuilder.create(RelBuilderTest.config().build());
}
private static RelNode rel(String sql) {
final Planner planner = Frameworks.getPlanner(frameworkConfig());
try {
SqlNode parse = planner.parse(sql);
SqlNode validate = planner.validate(parse);
return planner.rel(validate).rel;
} catch (Exception e) {
throw TestUtil.rethrow(e);
}
}
private static RelNode toPhysical(RelNode rel) {
final RelOptPlanner planner = rel.getCluster().getPlanner();
planner.clear();
for (RelOptRule rule : RULE_SET) {
planner.addRule(rule);
}
final Program program = Programs.of(RuleSets.ofList(planner.getRules()));
return program.run(planner, rel, rel.getTraitSet().replace(EnumerableConvention.INSTANCE),
ImmutableList.of(), ImmutableList.of());
}
private static RelNode toLogical(RelNode rel) {
return rel.accept(new ToLogicalConverter(builder()));
}
private void verify(RelNode rel, String expectedPhysical, String expectedLogical) {
RelNode physical = toPhysical(rel);
RelNode logical = toLogical(physical);
assertThat(physical, hasTree(expectedPhysical));
assertThat(logical, hasTree(expectedLogical));
}
@Test void testValues() {
// Equivalent SQL:
// VALUES (true, 1), (false, -50) AS t(a, b)
final RelBuilder builder = builder();
final RelNode rel =
builder
.values(new String[]{"a", "b"}, true, 1, false, -50)
.build();
verify(rel,
"EnumerableValues(tuples=[[{ true, 1 }, { false, -50 }]])\n",
"LogicalValues(tuples=[[{ true, 1 }, { false, -50 }]])\n");
}
@Test void testScan() {
// Equivalent SQL:
// SELECT *
// FROM emp
final RelNode rel =
builder()
.scan("EMP")
.build();
verify(rel,
"EnumerableTableScan(table=[[scott, EMP]])\n",
"LogicalTableScan(table=[[scott, EMP]])\n");
}
@Test void testProject() {
// Equivalent SQL:
// SELECT deptno
// FROM emp
final RelBuilder builder = builder();
final RelNode rel =
builder.scan("EMP")
.project(builder.field("DEPTNO"))
.build();
String expectedPhysical = ""
+ "EnumerableProject(DEPTNO=[$7])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalProject(DEPTNO=[$7])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testFilter() {
// Equivalent SQL:
// SELECT *
// FROM emp
// WHERE deptno = 10
final RelBuilder builder = builder();
final RelNode rel =
builder.scan("EMP")
.filter(
builder.equals(
builder.field("DEPTNO"),
builder.literal(10)))
.build();
String expectedPhysical = ""
+ "EnumerableFilter(condition=[=($7, 10)])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalFilter(condition=[=($7, 10)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testSort() {
// Equivalent SQL:
// SELECT *
// FROM emp
// ORDER BY 3
final RelBuilder builder = builder();
final RelNode rel =
builder.scan("EMP")
.sort(builder.field(2))
.build();
String expectedPhysical = ""
+ "EnumerableSort(sort0=[$2], dir0=[ASC])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalSort(sort0=[$2], dir0=[ASC])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testLimit() {
// Equivalent SQL:
// SELECT *
// FROM emp
// FETCH 10
final RelBuilder builder = builder();
final RelNode rel =
builder.scan("EMP")
.limit(0, 10)
.build();
String expectedPhysical = ""
+ "EnumerableLimit(fetch=[10])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalSort(fetch=[10])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testSortLimit() {
// Equivalent SQL:
// SELECT *
// FROM emp
// ORDER BY deptno DESC FETCH 10
final RelBuilder builder = builder();
final RelNode rel =
builder.scan("EMP")
.sortLimit(-1, 10, builder.desc(builder.field("DEPTNO")))
.build();
String expectedPhysical = ""
+ "EnumerableLimit(fetch=[10])\n"
+ " EnumerableSort(sort0=[$7], dir0=[DESC])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalSort(sort0=[$7], dir0=[DESC], fetch=[10])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testAggregate() {
// Equivalent SQL:
// SELECT deptno, COUNT(sal) AS c
// FROM emp
// GROUP BY deptno
final RelBuilder builder = builder();
final RelNode rel =
builder.scan("EMP")
.aggregate(builder.groupKey(builder.field("DEPTNO")),
builder.count(false, "C", builder.field("SAL")))
.build();
String expectedPhysical = ""
+ "EnumerableAggregate(group=[{7}], C=[COUNT($5)])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalAggregate(group=[{7}], C=[COUNT($5)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testJoin() {
// Equivalent SQL:
// SELECT *
// FROM emp
// JOIN dept ON emp.deptno = dept.deptno
final RelBuilder builder = builder();
final RelNode rel =
builder.scan("EMP")
.scan("DEPT")
.join(JoinRelType.INNER,
builder.call(SqlStdOperatorTable.EQUALS,
builder.field(2, 0, "DEPTNO"),
builder.field(2, 1, "DEPTNO")))
.build();
String expectedPhysical = ""
+ "EnumerableHashJoin(condition=[=($7, $8)], joinType=[inner])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n"
+ " EnumerableTableScan(table=[[scott, DEPT]])\n";
String expectedLogical = ""
+ "LogicalJoin(condition=[=($7, $8)], joinType=[inner])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n"
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testDeepEquals() {
// Equivalent SQL:
// SELECT *
// FROM emp
// JOIN dept ON emp.deptno = dept.deptno
final RelBuilder builder = builder();
RelNode[] rels = new RelNode[2];
for (int i = 0; i < 2; i++) {
rels[i] = builder.scan("EMP")
.scan("DEPT")
.join(JoinRelType.INNER,
builder.call(SqlStdOperatorTable.EQUALS,
builder.field(2, 0, "DEPTNO"),
builder.field(2, 1, "DEPTNO")))
.build();
}
// Currently, default implementation uses identity equals
assertThat(rels[0].equals(rels[1]), is(false));
assertThat(rels[0].getInput(0).equals(rels[1].getInput(0)), is(false));
// Deep equals and hashCode check
assertThat(rels[0].deepEquals(rels[1]), is(true));
assertThat(rels[0].deepHashCode() == rels[1].deepHashCode(), is(true));
}
@Test void testCorrelation() {
final RelBuilder builder = builder();
final Holder<@Nullable RexCorrelVariable> v = Holder.empty();
final RelNode rel = builder.scan("EMP")
.variable(v)
.scan("DEPT")
.filter(
builder.equals(builder.field(0), builder.field(v.get(), "DEPTNO")))
.join(JoinRelType.LEFT,
builder.equals(builder.field(2, 0, "SAL"),
builder.literal(1000)),
ImmutableSet.of(v.get().id))
.build();
String expectedPhysical = ""
+ "EnumerableCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{5, 7}])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n"
+ " EnumerableFilter(condition=[=($cor0.SAL, 1000)])\n"
+ " EnumerableFilter(condition=[=($0, $cor0.DEPTNO)])\n"
+ " EnumerableTableScan(table=[[scott, DEPT]])\n";
String expectedLogical = ""
+ "LogicalCorrelate(correlation=[$cor0], joinType=[left], requiredColumns=[{5, 7}])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n"
+ " LogicalFilter(condition=[=($cor0.SAL, 1000)])\n"
+ " LogicalFilter(condition=[=($0, $cor0.DEPTNO)])\n"
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testUnion() {
// Equivalent SQL:
// SELECT deptno FROM emp
// UNION ALL
// SELECT deptno FROM dept
final RelBuilder builder = builder();
RelNode rel =
builder.scan("DEPT")
.project(builder.field("DEPTNO"))
.scan("EMP")
.project(builder.field("DEPTNO"))
.union(true)
.build();
String expectedPhysical = ""
+ "EnumerableUnion(all=[true])\n"
+ " EnumerableProject(DEPTNO=[$0])\n"
+ " EnumerableTableScan(table=[[scott, DEPT]])\n"
+ " EnumerableProject(DEPTNO=[$7])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalUnion(all=[true])\n"
+ " LogicalProject(DEPTNO=[$0])\n"
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
+ " LogicalProject(DEPTNO=[$7])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testIntersect() {
// Equivalent SQL:
// SELECT deptno FROM emp
// INTERSECT ALL
// SELECT deptno FROM dept
final RelBuilder builder = builder();
RelNode rel =
builder.scan("DEPT")
.project(builder.field("DEPTNO"))
.scan("EMP")
.project(builder.field("DEPTNO"))
.intersect(true)
.build();
String expectedPhysical = ""
+ "EnumerableIntersect(all=[true])\n"
+ " EnumerableProject(DEPTNO=[$0])\n"
+ " EnumerableTableScan(table=[[scott, DEPT]])\n"
+ " EnumerableProject(DEPTNO=[$7])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalIntersect(all=[true])\n"
+ " LogicalProject(DEPTNO=[$0])\n"
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
+ " LogicalProject(DEPTNO=[$7])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testMinus() {
// Equivalent SQL:
// SELECT deptno FROM emp
// EXCEPT ALL
// SELECT deptno FROM dept
final RelBuilder builder = builder();
RelNode rel =
builder.scan("DEPT")
.project(builder.field("DEPTNO"))
.scan("EMP")
.project(builder.field("DEPTNO"))
.minus(true)
.build();
String expectedPhysical = ""
+ "EnumerableMinus(all=[true])\n"
+ " EnumerableProject(DEPTNO=[$0])\n"
+ " EnumerableTableScan(table=[[scott, DEPT]])\n"
+ " EnumerableProject(DEPTNO=[$7])\n"
+ " EnumerableTableScan(table=[[scott, EMP]])\n";
String expectedLogical = ""
+ "LogicalMinus(all=[true])\n"
+ " LogicalProject(DEPTNO=[$0])\n"
+ " LogicalTableScan(table=[[scott, DEPT]])\n"
+ " LogicalProject(DEPTNO=[$7])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verify(rel, expectedPhysical, expectedLogical);
}
@Test void testUncollect() {
final String sql = ""
+ "select did\n"
+ "from unnest(select collect(\"department_id\") as deptid"
+ " from \"department\") as t(did)";
String expectedPhysical = ""
+ "EnumerableUncollect\n"
+ " EnumerableAggregate(group=[{}], DEPTID=[COLLECT($0)])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcProject(department_id=[$0])\n"
+ " JdbcTableScan(table=[[foodmart, department]])\n";
String expectedLogical = ""
+ "Uncollect\n"
+ " LogicalAggregate(group=[{}], DEPTID=[COLLECT($0)])\n"
+ " LogicalProject(department_id=[$0])\n"
+ " LogicalTableScan(table=[[foodmart, department]])\n";
verify(rel(sql), expectedPhysical, expectedLogical);
}
@Test void testWindow() {
String sql = "SELECT rank() over (order by \"hire_date\") FROM \"employee\"";
String expectedPhysical = ""
+ "EnumerableProject($0=[$17])\n"
+ " EnumerableWindow(window#0=[window(order by [9] aggs [RANK()])])\n"
+ " JdbcToEnumerableConverter\n"
+ " JdbcTableScan(table=[[foodmart, employee]])\n";
String expectedLogical = ""
+ "LogicalProject($0=[$17])\n"
+ " LogicalWindow(window#0=[window(order by [9] aggs [RANK()])])\n"
+ " LogicalTableScan(table=[[foodmart, employee]])\n";
verify(rel(sql), expectedPhysical, expectedLogical);
}
@Test void testTableModify() {
final String sql = "insert into \"employee\" select * from \"employee\"";
final String expectedPhysical = ""
+ "JdbcToEnumerableConverter\n"
+ " JdbcTableModify(table=[[foodmart, employee]], operation=[INSERT], flattened=[true])\n"
+ " JdbcTableScan(table=[[foodmart, employee]])\n";
final String expectedLogical = ""
+ "LogicalTableModify(table=[[foodmart, employee]], "
+ "operation=[INSERT], flattened=[true])\n"
+ " LogicalTableScan(table=[[foodmart, employee]])\n";
verify(rel(sql), expectedPhysical, expectedLogical);
}
}