blob: c5a26c7fa0ffe63e0e3c4ba5ead4239f7937dc9e [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.plan;
import org.apache.calcite.adapter.java.ReflectiveSchema;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelShuttleImpl;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.externalize.RelJsonReader;
import org.apache.calcite.rel.externalize.RelJsonWriter;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalCalc;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.rex.RexFieldCollation;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexProgramBuilder;
import org.apache.calcite.rex.RexWindowBound;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.SqlExplainFormat;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.calcite.sql.SqlIntervalQualifier;
import org.apache.calcite.sql.SqlWindow;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.fun.SqlTrimFunction;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.test.JdbcTest;
import org.apache.calcite.test.MockSqlOperatorTable;
import org.apache.calcite.test.RelBuilderTest;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.Holder;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.TestUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.apache.calcite.test.Matchers.isLinux;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJson}.
*/
public class RelWriterTest {
public static final String XX = "{\n"
+ " \"rels\": [\n"
+ " {\n"
+ " \"id\": \"0\",\n"
+ " \"relOp\": \"LogicalTableScan\",\n"
+ " \"table\": [\n"
+ " \"hr\",\n"
+ " \"emps\"\n"
+ " ],\n"
+ " \"inputs\": []\n"
+ " },\n"
+ " {\n"
+ " \"id\": \"1\",\n"
+ " \"relOp\": \"LogicalFilter\",\n"
+ " \"condition\": {\n"
+ " \"op\": {\n"
+ " \"name\": \"=\",\n"
+ " \"kind\": \"EQUALS\",\n"
+ " \"syntax\": \"BINARY\"\n"
+ " },\n"
+ " \"operands\": [\n"
+ " {\n"
+ " \"input\": 1,\n"
+ " \"name\": \"$1\"\n"
+ " },\n"
+ " {\n"
+ " \"literal\": 10,\n"
+ " \"type\": {\n"
+ " \"type\": \"INTEGER\",\n"
+ " \"nullable\": false\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": \"2\",\n"
+ " \"relOp\": \"LogicalAggregate\",\n"
+ " \"group\": [\n"
+ " 0\n"
+ " ],\n"
+ " \"aggs\": [\n"
+ " {\n"
+ " \"agg\": {\n"
+ " \"name\": \"COUNT\",\n"
+ " \"kind\": \"COUNT\",\n"
+ " \"syntax\": \"FUNCTION_STAR\"\n"
+ " },\n"
+ " \"type\": {\n"
+ " \"type\": \"BIGINT\",\n"
+ " \"nullable\": false\n"
+ " },\n"
+ " \"distinct\": true,\n"
+ " \"operands\": [\n"
+ " 1\n"
+ " ],\n"
+ " \"name\": \"c\"\n"
+ " },\n"
+ " {\n"
+ " \"agg\": {\n"
+ " \"name\": \"COUNT\",\n"
+ " \"kind\": \"COUNT\",\n"
+ " \"syntax\": \"FUNCTION_STAR\"\n"
+ " },\n"
+ " \"type\": {\n"
+ " \"type\": \"BIGINT\",\n"
+ " \"nullable\": false\n"
+ " },\n"
+ " \"distinct\": false,\n"
+ " \"operands\": [],\n"
+ " \"name\": \"d\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
public static final String XXNULL = "{\n"
+ " \"rels\": [\n"
+ " {\n"
+ " \"id\": \"0\",\n"
+ " \"relOp\": \"LogicalTableScan\",\n"
+ " \"table\": [\n"
+ " \"hr\",\n"
+ " \"emps\"\n"
+ " ],\n"
+ " \"inputs\": []\n"
+ " },\n"
+ " {\n"
+ " \"id\": \"1\",\n"
+ " \"relOp\": \"LogicalFilter\",\n"
+ " \"condition\": {\n"
+ " \"op\": {"
+ " \"name\": \"=\",\n"
+ " \"kind\": \"EQUALS\",\n"
+ " \"syntax\": \"BINARY\"\n"
+ " },\n"
+ " \"operands\": [\n"
+ " {\n"
+ " \"input\": 1,\n"
+ " \"name\": \"$1\"\n"
+ " },\n"
+ " {\n"
+ " \"literal\": null,\n"
+ " \"type\": \"INTEGER\"\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"id\": \"2\",\n"
+ " \"relOp\": \"LogicalAggregate\",\n"
+ " \"group\": [\n"
+ " 0\n"
+ " ],\n"
+ " \"aggs\": [\n"
+ " {\n"
+ " \"agg\": {\n"
+ " \"name\": \"COUNT\",\n"
+ " \"kind\": \"COUNT\",\n"
+ " \"syntax\": \"FUNCTION_STAR\"\n"
+ " },\n"
+ " \"type\": {\n"
+ " \"type\": \"BIGINT\",\n"
+ " \"nullable\": false\n"
+ " },\n"
+ " \"distinct\": true,\n"
+ " \"operands\": [\n"
+ " 1\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"agg\": {\n"
+ " \"name\": \"COUNT\",\n"
+ " \"kind\": \"COUNT\",\n"
+ " \"syntax\": \"FUNCTION_STAR\"\n"
+ " },\n"
+ " \"type\": {\n"
+ " \"type\": \"BIGINT\",\n"
+ " \"nullable\": false\n"
+ " },\n"
+ " \"distinct\": false,\n"
+ " \"operands\": []\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
public static final String XX2 = "{\n"
+ " \"rels\": [\n"
+ " {\n"
+ " \"id\": \"0\",\n"
+ " \"relOp\": \"LogicalTableScan\",\n"
+ " \"table\": [\n"
+ " \"hr\",\n"
+ " \"emps\"\n"
+ " ],\n"
+ " \"inputs\": []\n"
+ " },\n"
+ " {\n"
+ " \"id\": \"1\",\n"
+ " \"relOp\": \"LogicalProject\",\n"
+ " \"fields\": [\n"
+ " \"field0\",\n"
+ " \"field1\",\n"
+ " \"field2\"\n"
+ " ],\n"
+ " \"exprs\": [\n"
+ " {\n"
+ " \"input\": 0,\n"
+ " \"name\": \"$0\"\n"
+ " },\n"
+ " {\n"
+ " \"op\": {\n"
+ " \"name\": \"COUNT\",\n"
+ " \"kind\": \"COUNT\",\n"
+ " \"syntax\": \"FUNCTION_STAR\"\n"
+ " },\n"
+ " \"operands\": [\n"
+ " {\n"
+ " \"input\": 0,\n"
+ " \"name\": \"$0\"\n"
+ " }\n"
+ " ],\n"
+ " \"distinct\": false,\n"
+ " \"type\": {\n"
+ " \"type\": \"BIGINT\",\n"
+ " \"nullable\": false\n"
+ " },\n"
+ " \"window\": {\n"
+ " \"partition\": [\n"
+ " {\n"
+ " \"input\": 2,\n"
+ " \"name\": \"$2\"\n"
+ " }\n"
+ " ],\n"
+ " \"order\": [\n"
+ " {\n"
+ " \"expr\": {\n"
+ " \"input\": 1,\n"
+ " \"name\": \"$1\"\n"
+ " },\n"
+ " \"direction\": \"ASCENDING\",\n"
+ " \"null-direction\": \"LAST\"\n"
+ " }\n"
+ " ],\n"
+ " \"rows-lower\": {\n"
+ " \"type\": \"UNBOUNDED_PRECEDING\"\n"
+ " },\n"
+ " \"rows-upper\": {\n"
+ " \"type\": \"CURRENT_ROW\"\n"
+ " }\n"
+ " }\n"
+ " },\n"
+ " {\n"
+ " \"op\": {\n"
+ " \"name\": \"SUM\",\n"
+ " \"kind\": \"SUM\",\n"
+ " \"syntax\": \"FUNCTION\"\n"
+ " },\n"
+ " \"operands\": [\n"
+ " {\n"
+ " \"input\": 0,\n"
+ " \"name\": \"$0\"\n"
+ " }\n"
+ " ],\n"
+ " \"distinct\": false,\n"
+ " \"type\": {\n"
+ " \"type\": \"BIGINT\",\n"
+ " \"nullable\": false\n"
+ " },\n"
+ " \"window\": {\n"
+ " \"partition\": [\n"
+ " {\n"
+ " \"input\": 2,\n"
+ " \"name\": \"$2\"\n"
+ " }\n"
+ " ],\n"
+ " \"order\": [\n"
+ " {\n"
+ " \"expr\": {\n"
+ " \"input\": 1,\n"
+ " \"name\": \"$1\"\n"
+ " },\n"
+ " \"direction\": \"ASCENDING\",\n"
+ " \"null-direction\": \"LAST\"\n"
+ " }\n"
+ " ],\n"
+ " \"range-lower\": {\n"
+ " \"type\": \"CURRENT_ROW\"\n"
+ " },\n"
+ " \"range-upper\": {\n"
+ " \"type\": \"FOLLOWING\",\n"
+ " \"offset\": {\n"
+ " \"literal\": 1,\n"
+ " \"type\": {\n"
+ " \"type\": \"INTEGER\",\n"
+ " \"nullable\": false\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ "}";
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonWriter} on
* a simple tree of relational expressions, consisting of a table and a
* project including window expressions.
*/
@Test public void testWriter() {
String s =
Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
rootSchema.add("hr",
new ReflectiveSchema(new JdbcTest.HrSchema()));
LogicalTableScan scan =
LogicalTableScan.create(cluster,
relOptSchema.getTableForMember(
Arrays.asList("hr", "emps")));
final RexBuilder rexBuilder = cluster.getRexBuilder();
LogicalFilter filter =
LogicalFilter.create(scan,
rexBuilder.makeCall(
SqlStdOperatorTable.EQUALS,
rexBuilder.makeFieldAccess(
rexBuilder.makeRangeReference(scan),
"deptno", true),
rexBuilder.makeExactLiteral(BigDecimal.TEN)));
final RelJsonWriter writer = new RelJsonWriter();
final RelDataType bigIntType =
cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
LogicalAggregate aggregate =
LogicalAggregate.create(filter, ImmutableBitSet.of(0), null,
ImmutableList.of(
AggregateCall.create(SqlStdOperatorTable.COUNT,
true, false, false, ImmutableList.of(1), -1,
RelCollations.EMPTY, bigIntType, "c"),
AggregateCall.create(SqlStdOperatorTable.COUNT,
false, false, false, ImmutableList.of(), -1,
RelCollations.EMPTY, bigIntType, "d")));
aggregate.explain(writer);
return writer.asString();
});
assertThat(s, is(XX));
}
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonWriter} on
* a simple tree of relational expressions, consisting of a table, a filter
* and an aggregate node.
*/
@Test public void testWriter2() {
String s =
Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
rootSchema.add("hr",
new ReflectiveSchema(new JdbcTest.HrSchema()));
LogicalTableScan scan =
LogicalTableScan.create(cluster,
relOptSchema.getTableForMember(
Arrays.asList("hr", "emps")));
final RexBuilder rexBuilder = cluster.getRexBuilder();
final RelDataType bigIntType =
cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
LogicalProject project =
LogicalProject.create(scan,
ImmutableList.of(
rexBuilder.makeInputRef(scan, 0),
rexBuilder.makeOver(bigIntType,
SqlStdOperatorTable.COUNT,
ImmutableList.of(rexBuilder.makeInputRef(scan, 0)),
ImmutableList.of(rexBuilder.makeInputRef(scan, 2)),
ImmutableList.of(
new RexFieldCollation(
rexBuilder.makeInputRef(scan, 1), ImmutableSet.of())),
RexWindowBound.create(
SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO), null),
RexWindowBound.create(
SqlWindow.createCurrentRow(SqlParserPos.ZERO), null),
true, true, false, false, false),
rexBuilder.makeOver(bigIntType,
SqlStdOperatorTable.SUM,
ImmutableList.of(rexBuilder.makeInputRef(scan, 0)),
ImmutableList.of(rexBuilder.makeInputRef(scan, 2)),
ImmutableList.of(
new RexFieldCollation(
rexBuilder.makeInputRef(scan, 1), ImmutableSet.of())),
RexWindowBound.create(
SqlWindow.createCurrentRow(SqlParserPos.ZERO), null),
RexWindowBound.create(null,
rexBuilder.makeCall(
SqlWindow.FOLLOWING_OPERATOR,
rexBuilder.makeExactLiteral(BigDecimal.ONE))),
false, true, false, false, false)),
ImmutableList.of("field0", "field1", "field2"));
final RelJsonWriter writer = new RelJsonWriter();
project.explain(writer);
return writer.asString();
});
assertThat(s, is(XX2));
}
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonReader}.
*/
@Test public void testReader() {
String s =
Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
SchemaPlus schema =
rootSchema.add("hr",
new ReflectiveSchema(new JdbcTest.HrSchema()));
final RelJsonReader reader =
new RelJsonReader(cluster, relOptSchema, schema);
RelNode node;
try {
node = reader.read(XX);
} catch (IOException e) {
throw TestUtil.rethrow(e);
}
return RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
});
assertThat(s,
isLinux("LogicalAggregate(group=[{0}], c=[COUNT(DISTINCT $1)], d=[COUNT()])\n"
+ " LogicalFilter(condition=[=($1, 10)])\n"
+ " LogicalTableScan(table=[[hr, emps]])\n"));
}
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonReader}.
*/
@Test public void testReader2() {
String s =
Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
SchemaPlus schema =
rootSchema.add("hr",
new ReflectiveSchema(new JdbcTest.HrSchema()));
final RelJsonReader reader =
new RelJsonReader(cluster, relOptSchema, schema);
RelNode node;
try {
node = reader.read(XX2);
} catch (IOException e) {
throw TestUtil.rethrow(e);
}
return RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
});
assertThat(s,
isLinux("LogicalProject(field0=[$0],"
+ " field1=[COUNT($0) OVER (PARTITION BY $2 ORDER BY $1 NULLS LAST ROWS BETWEEN"
+ " UNBOUNDED PRECEDING AND CURRENT ROW)],"
+ " field2=[SUM($0) OVER (PARTITION BY $2 ORDER BY $1 NULLS LAST RANGE BETWEEN"
+ " CURRENT ROW AND 1 FOLLOWING)])\n"
+ " LogicalTableScan(table=[[hr, emps]])\n"));
}
/**
* Unit test for {@link org.apache.calcite.rel.externalize.RelJsonReader}.
*/
@Test public void testReaderNull() {
String s =
Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
SchemaPlus schema =
rootSchema.add("hr",
new ReflectiveSchema(new JdbcTest.HrSchema()));
final RelJsonReader reader =
new RelJsonReader(cluster, relOptSchema, schema);
RelNode node;
try {
node = reader.read(XXNULL);
} catch (IOException e) {
throw TestUtil.rethrow(e);
}
return RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
});
assertThat(s,
isLinux("LogicalAggregate(group=[{0}], agg#0=[COUNT(DISTINCT $1)], agg#1=[COUNT()])\n"
+ " LogicalFilter(condition=[=($1, null:INTEGER)])\n"
+ " LogicalTableScan(table=[[hr, emps]])\n"));
}
@Test public void testTrim() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder b = RelBuilder.create(config);
final RelNode rel =
b.scan("EMP")
.project(
b.alias(
b.call(SqlStdOperatorTable.TRIM,
b.literal(SqlTrimFunction.Flag.BOTH),
b.literal(" "),
b.field("ENAME")),
"trimmed_ename"))
.build();
RelJsonWriter jsonWriter = new RelJsonWriter();
rel.explain(jsonWriter);
String relJson = jsonWriter.asString();
final RelOptSchema schema = getSchema(rel);
final String s = deserializeAndDumpToTextFormat(schema, relJson);
final String expected = ""
+ "LogicalProject(trimmed_ename=[TRIM(FLAG(BOTH), ' ', $1)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testPlusOperator() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
final RelNode rel = builder
.scan("EMP")
.project(
builder.call(SqlStdOperatorTable.PLUS,
builder.field("SAL"),
builder.literal(10)))
.build();
RelJsonWriter jsonWriter = new RelJsonWriter();
rel.explain(jsonWriter);
String relJson = jsonWriter.asString();
String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson);
final String expected = ""
+ "LogicalProject($f0=[+($5, 10)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testAggregateWithAlias() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
// The rel node stands for sql: SELECT max(SAL) as max_sal from EMP group by JOB;
final RelNode rel = builder
.scan("EMP")
.project(
builder.field("JOB"),
builder.field("SAL"))
.aggregate(
builder.groupKey("JOB"),
builder.max("max_sal", builder.field("SAL")))
.project(
builder.field("max_sal"))
.build();
final RelJsonWriter jsonWriter = new RelJsonWriter();
rel.explain(jsonWriter);
final String relJson = jsonWriter.asString();
String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson);
final String expected = ""
+ "LogicalProject(max_sal=[$1])\n"
+ " LogicalAggregate(group=[{0}], max_sal=[MAX($1)])\n"
+ " LogicalProject(JOB=[$2], SAL=[$5])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testAggregateWithoutAlias() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
// The rel node stands for sql: SELECT max(SAL) from EMP group by JOB;
final RelNode rel = builder
.scan("EMP")
.project(
builder.field("JOB"),
builder.field("SAL"))
.aggregate(
builder.groupKey("JOB"),
builder.max(builder.field("SAL")))
.project(
builder.field(1))
.build();
final RelJsonWriter jsonWriter = new RelJsonWriter();
rel.explain(jsonWriter);
final String relJson = jsonWriter.asString();
String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson);
final String expected = ""
+ "LogicalProject($f1=[$1])\n"
+ " LogicalAggregate(group=[{0}], agg#0=[MAX($1)])\n"
+ " LogicalProject(JOB=[$2], SAL=[$5])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testCalc() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
final RexBuilder rexBuilder = builder.getRexBuilder();
final LogicalTableScan scan = (LogicalTableScan) builder.scan("EMP").build();
final RexProgramBuilder programBuilder =
new RexProgramBuilder(scan.getRowType(), rexBuilder);
final RelDataTypeField field = scan.getRowType().getField("SAL", false, false);
programBuilder.addIdentity();
programBuilder.addCondition(
rexBuilder.makeCall(SqlStdOperatorTable.GREATER_THAN,
new RexInputRef(field.getIndex(), field.getType()),
builder.literal(10)));
final LogicalCalc calc = LogicalCalc.create(scan, programBuilder.getProgram());
String relJson = RelOptUtil.dumpPlan("", calc,
SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES);
String s =
Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
final RelJsonReader reader = new RelJsonReader(
cluster, getSchema(calc), rootSchema);
RelNode node;
try {
node = reader.read(relJson);
} catch (IOException e) {
throw TestUtil.rethrow(e);
}
return RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
});
final String expected =
"LogicalCalc(expr#0..7=[{inputs}], expr#8=[10], expr#9=[>($t5, $t8)],"
+ " proj#0..7=[{exprs}], $condition=[$t9])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testCorrelateQuery() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
final Holder<RexCorrelVariable> v = Holder.of(null);
RelNode relNode = builder.scan("EMP")
.variable(v)
.scan("DEPT")
.filter(
builder.equals(builder.field(0), builder.field(v.get(), "DEPTNO")))
.correlate(
JoinRelType.INNER, v.get().id, builder.field(2, 0, "DEPTNO"))
.build();
RelJsonWriter jsonWriter = new RelJsonWriter();
relNode.explain(jsonWriter);
final String relJson = jsonWriter.asString();
String s = deserializeAndDumpToTextFormat(getSchema(relNode), relJson);
final String expected = ""
+ "LogicalCorrelate(correlation=[$cor0], joinType=[inner], requiredColumns=[{7}])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n"
+ " LogicalFilter(condition=[=($0, $cor0.DEPTNO)])\n"
+ " LogicalTableScan(table=[[scott, DEPT]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testOverWithoutPartition() {
// The rel stands for the sql of "select count(*) over (order by deptno) from EMP"
final RelNode rel = mockCountOver("EMP", ImmutableList.of(), ImmutableList.of("DEPTNO"));
String relJson = RelOptUtil.dumpPlan("", rel, SqlExplainFormat.JSON,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson);
final String expected = ""
+ "LogicalProject($f0=[COUNT() OVER (ORDER BY $7 NULLS LAST ROWS"
+ " BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testOverWithoutOrderKey() {
// The rel stands for the sql of "select count(*) over (partition by DEPTNO) from EMP"
final RelNode rel = mockCountOver("EMP", ImmutableList.of("DEPTNO"), ImmutableList.of());
String relJson = RelOptUtil.dumpPlan("", rel, SqlExplainFormat.JSON,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson);
final String expected = ""
+ "LogicalProject($f0=[COUNT() OVER"
+ " (PARTITION BY $7 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testInterval() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
SqlIntervalQualifier sqlIntervalQualifier =
new SqlIntervalQualifier(TimeUnit.DAY, TimeUnit.DAY, SqlParserPos.ZERO);
BigDecimal value = new BigDecimal(86400000);
RexLiteral intervalLiteral = builder.getRexBuilder()
.makeIntervalLiteral(value, sqlIntervalQualifier);
final RelNode rel = builder
.scan("EMP")
.project(
builder.call(
SqlStdOperatorTable.TUMBLE_END,
builder.field("HIREDATE"),
intervalLiteral))
.build();
RelJsonWriter jsonWriter = new RelJsonWriter();
rel.explain(jsonWriter);
String relJson = jsonWriter.asString();
String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson);
final String expected = ""
+ "LogicalProject($f0=[TUMBLE_END($4, 86400000:INTERVAL DAY)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
@Test public void testUdf() {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
final RelNode rel = builder
.scan("EMP")
.project(
builder.call(new MockSqlOperatorTable.MyFunction(),
builder.field("EMPNO")))
.build();
String relJson = RelOptUtil.dumpPlan("", rel,
SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES);
String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson);
final String expected = ""
+ "LogicalProject($f0=[MYFUN($0)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
assertThat(s, isLinux(expected));
}
/** Returns the schema of a {@link org.apache.calcite.rel.core.TableScan}
* in this plan, or null if there are no scans. */
private RelOptSchema getSchema(RelNode rel) {
final Holder<RelOptSchema> schemaHolder = Holder.of(null);
rel.accept(
new RelShuttleImpl() {
@Override public RelNode visit(TableScan scan) {
schemaHolder.set(scan.getTable().getRelOptSchema());
return super.visit(scan);
}
});
return schemaHolder.get();
}
/**
* Deserialize a relnode from the json string by {@link RelJsonReader},
* and dump it to text format.
*/
private String deserializeAndDumpToTextFormat(RelOptSchema schema, String relJson) {
String s =
Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> {
final RelJsonReader reader = new RelJsonReader(
cluster, schema, rootSchema);
RelNode node;
try {
node = reader.read(relJson);
} catch (IOException e) {
throw TestUtil.rethrow(e);
}
return RelOptUtil.dumpPlan("", node, SqlExplainFormat.TEXT,
SqlExplainLevel.EXPPLAN_ATTRIBUTES);
});
return s;
}
/**
* Mock a {@link RelNode} for sql:
* select count(*) over (partition by {@code partitionKeyNames}
* order by {@code orderKeyNames}) from {@code table}
* @param table Table name
* @param partitionKeyNames Partition by column names, may empty, can not be null
* @param orderKeyNames Order by column names, may empty, can not be null
* @return RelNode for the sql
*/
private RelNode mockCountOver(String table,
List<String> partitionKeyNames, List<String> orderKeyNames) {
final FrameworkConfig config = RelBuilderTest.config().build();
final RelBuilder builder = RelBuilder.create(config);
final RexBuilder rexBuilder = builder.getRexBuilder();
final RelDataType type = rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
List<RexNode> partitionKeys = new ArrayList<>(partitionKeyNames.size());
builder.scan(table);
for (String partitionkeyName: partitionKeyNames) {
partitionKeys.add(builder.field(partitionkeyName));
}
List<RexFieldCollation> orderKeys = new ArrayList<>(orderKeyNames.size());
for (String orderKeyName: orderKeyNames) {
orderKeys.add(new RexFieldCollation(builder.field(orderKeyName), ImmutableSet.of()));
}
final RelNode rel = builder
.project(
rexBuilder.makeOver(
type,
SqlStdOperatorTable.COUNT,
ImmutableList.of(),
partitionKeys,
ImmutableList.copyOf(orderKeys),
RexWindowBound.create(
SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO), null),
RexWindowBound.create(
SqlWindow.createCurrentRow(SqlParserPos.ZERO), null),
true, true, false, false, false))
.build();
return rel;
}
}
// End RelWriterTest.java