| /* |
| * 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.prepare.Prepare; |
| import org.apache.calcite.rel.RelCollations; |
| import org.apache.calcite.rel.RelDistribution; |
| import org.apache.calcite.rel.RelDistributions; |
| 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.TableModify; |
| 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.LogicalTableModify; |
| 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.RexWindowBounds; |
| 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.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.MockSqlOperatorTable; |
| import org.apache.calcite.test.RelBuilderTest; |
| import org.apache.calcite.test.schemata.hr.HrSchema; |
| 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 com.google.common.collect.Lists; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.MethodSource; |
| |
| import java.io.IOException; |
| import java.math.BigDecimal; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.stream.Stream; |
| |
| import static org.apache.calcite.test.Matchers.isLinux; |
| |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| |
| /** |
| * Unit test for {@link org.apache.calcite.rel.externalize.RelJson}. |
| */ |
| 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" |
| + "}"; |
| |
| public static final String XX3 = "{\n" |
| + " \"rels\": [\n" |
| + " {\n" |
| + " \"id\": \"0\",\n" |
| + " \"relOp\": \"LogicalTableScan\",\n" |
| + " \"table\": [\n" |
| + " \"scott\",\n" |
| + " \"EMP\"\n" |
| + " ],\n" |
| + " \"inputs\": []\n" |
| + " },\n" |
| + " {\n" |
| + " \"id\": \"1\",\n" |
| + " \"relOp\": \"LogicalSortExchange\",\n" |
| + " \"distribution\": {\n" |
| + " \"type\": \"HASH_DISTRIBUTED\",\n" |
| + " \"keys\": [\n" |
| + " 0\n" |
| + " ]\n" |
| + " },\n" |
| + " \"collation\": [\n" |
| + " {\n" |
| + " \"field\": 0,\n" |
| + " \"direction\": \"ASCENDING\",\n" |
| + " \"nulls\": \"LAST\"\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}"; |
| |
| public static final String HASH_DIST_WITHOUT_KEYS = "{\n" |
| + " \"rels\": [\n" |
| + " {\n" |
| + " \"id\": \"0\",\n" |
| + " \"relOp\": \"LogicalTableScan\",\n" |
| + " \"table\": [\n" |
| + " \"scott\",\n" |
| + " \"EMP\"\n" |
| + " ],\n" |
| + " \"inputs\": []\n" |
| + " },\n" |
| + " {\n" |
| + " \"id\": \"1\",\n" |
| + " \"relOp\": \"LogicalSortExchange\",\n" |
| + " \"distribution\": {\n" |
| + " \"type\": \"HASH_DISTRIBUTED\"\n" |
| + " },\n" |
| + " \"collation\": [\n" |
| + " {\n" |
| + " \"field\": 0,\n" |
| + " \"direction\": \"ASCENDING\",\n" |
| + " \"nulls\": \"LAST\"\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}"; |
| |
| static Stream<SqlExplainFormat> explainFormats() { |
| return Stream.of(SqlExplainFormat.TEXT, SqlExplainFormat.DOT); |
| } |
| |
| /** |
| * 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 void testWriter() { |
| String s = |
| Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> { |
| rootSchema.add("hr", |
| new ReflectiveSchema(new HrSchema())); |
| LogicalTableScan scan = |
| LogicalTableScan.create(cluster, |
| relOptSchema.getTableForMember( |
| Arrays.asList("hr", "emps")), |
| ImmutableList.of()); |
| 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, |
| ImmutableList.of(), |
| ImmutableBitSet.of(0), |
| null, |
| ImmutableList.of( |
| AggregateCall.create(SqlStdOperatorTable.COUNT, |
| true, false, false, ImmutableList.of(1), -1, null, |
| RelCollations.EMPTY, bigIntType, "c"), |
| AggregateCall.create(SqlStdOperatorTable.COUNT, |
| false, false, false, ImmutableList.of(), -1, null, |
| 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 void testWriter2() { |
| String s = |
| Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> { |
| rootSchema.add("hr", |
| new ReflectiveSchema(new HrSchema())); |
| LogicalTableScan scan = |
| LogicalTableScan.create(cluster, |
| relOptSchema.getTableForMember( |
| Arrays.asList("hr", "emps")), |
| ImmutableList.of()); |
| final RexBuilder rexBuilder = cluster.getRexBuilder(); |
| final RelDataType bigIntType = |
| cluster.getTypeFactory().createSqlType(SqlTypeName.BIGINT); |
| LogicalProject project = |
| LogicalProject.create(scan, |
| ImmutableList.of(), |
| 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())), |
| RexWindowBounds.UNBOUNDED_PRECEDING, |
| RexWindowBounds.CURRENT_ROW, |
| 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())), |
| RexWindowBounds.CURRENT_ROW, |
| RexWindowBounds.following( |
| 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 void testReader() { |
| String s = |
| Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> { |
| SchemaPlus schema = |
| rootSchema.add("hr", |
| new ReflectiveSchema(new 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 void testReader2() { |
| String s = |
| Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> { |
| SchemaPlus schema = |
| rootSchema.add("hr", |
| new ReflectiveSchema(new 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 UNBOUNDED PRECEDING)]," |
| + " 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 void testReaderNull() { |
| String s = |
| Frameworks.withPlanner((cluster, relOptSchema, rootSchema) -> { |
| SchemaPlus schema = |
| rootSchema.add("hr", |
| new ReflectiveSchema(new 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 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 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)); |
| } |
| |
| @ParameterizedTest |
| @MethodSource("explainFormats") |
| void testAggregateWithAlias(SqlExplainFormat format) { |
| 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 = deserializeAndDump(getSchema(rel), relJson, format); |
| String expected = null; |
| switch (format) { |
| case TEXT: |
| 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"; |
| break; |
| case DOT: |
| expected = "digraph {\n" |
| + "\"LogicalAggregate\\ngroup = {0}\\nmax_sal = MAX($1)\\n\" -> " |
| + "\"LogicalProject\\nmax_sal = $1\\n\" [label=\"0\"]\n" |
| + "\"LogicalProject\\nJOB = $2\\nSAL = $5\\n\" -> \"LogicalAggregate\\ngroup = " |
| + "{0}\\nmax_sal = MAX($1)\\n\" [label=\"0\"]\n" |
| + "\"LogicalTableScan\\ntable = [scott, EMP]\\n\" -> \"LogicalProject\\nJOB = $2\\nSAL = " |
| + "$5\\n\" [label=\"0\"]\n" |
| + "}\n"; |
| break; |
| } |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test void testDeserializeInvalidOperatorName() { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| final RelNode rel = builder |
| .scan("EMP") |
| .project( |
| builder.field("JOB"), |
| builder.field("SAL")) |
| .aggregate( |
| builder.groupKey("JOB"), |
| builder.max("max_sal", builder.field("SAL")), |
| builder.min("min_sal", builder.field("SAL"))) |
| .project( |
| builder.field("max_sal"), |
| builder.field("min_sal")) |
| .build(); |
| final RelJsonWriter jsonWriter = new RelJsonWriter(); |
| rel.explain(jsonWriter); |
| // mock a non exist SqlOperator |
| String relJson = jsonWriter.asString().replace("\"name\": \"MAX\"", "\"name\": \"MAXS\""); |
| assertThrows(RuntimeException.class, |
| () -> deserializeAndDumpToTextFormat(getSchema(rel), relJson), |
| "org.apache.calcite.runtime.CalciteException: " |
| + "No operator for 'MAXS' with kind: 'MAX', syntax: 'FUNCTION' during JSON deserialization"); |
| } |
| |
| @Test 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 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)); |
| } |
| |
| @ParameterizedTest |
| @MethodSource("explainFormats") |
| void testCorrelateQuery(SqlExplainFormat format) { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| final Holder<RexCorrelVariable> v = Holder.empty(); |
| 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 = deserializeAndDump(getSchema(relNode), relJson, format); |
| String expected = null; |
| switch (format) { |
| case TEXT: |
| 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"; |
| break; |
| case DOT: |
| expected = "digraph {\n" |
| + "\"LogicalTableScan\\ntable = [scott, EMP]\\n\" -> \"LogicalCorrelate\\ncorrelation = " |
| + "$cor0\\njoinType = inner\\nrequiredColumns = {7\\n}\\n\" [label=\"0\"]\n" |
| + "\"LogicalFilter\\ncondition = =($0, $c\\nor0.DEPTNO)\\n\" -> " |
| + "\"LogicalCorrelate\\ncorrelation = $cor0\\njoinType = inner\\nrequiredColumns = " |
| + "{7\\n}\\n\" [label=\"1\"]\n" |
| + "\"LogicalTableScan\\ntable = [scott, DEPT\\n]\\n\" -> \"LogicalFilter\\ncondition = =" |
| + "($0, $c\\nor0.DEPTNO)\\n\" [label=\"0\"]\n" |
| + "}\n"; |
| break; |
| } |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test 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 UNBOUNDED PRECEDING)])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test 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)])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test 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 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)); |
| } |
| |
| @ParameterizedTest |
| @MethodSource("explainFormats") |
| void testUDAF(SqlExplainFormat format) { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| final RelNode rel = builder |
| .scan("EMP") |
| .project(builder.field("ENAME"), builder.field("DEPTNO")) |
| .aggregate( |
| builder.groupKey("ENAME"), |
| builder.aggregateCall(new MockSqlOperatorTable.MyAggFunc(), |
| builder.field("DEPTNO"))) |
| .build(); |
| final String relJson = RelOptUtil.dumpPlan("", rel, |
| SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES); |
| final String result = deserializeAndDump(getSchema(rel), relJson, format); |
| String expected = null; |
| switch (format) { |
| case TEXT: |
| expected = "" |
| + "LogicalAggregate(group=[{0}], agg#0=[myAggFunc($1)])\n" |
| + " LogicalProject(ENAME=[$1], DEPTNO=[$7])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| break; |
| case DOT: |
| expected = "digraph {\n" |
| + "\"LogicalProject\\nENAME = $1\\nDEPTNO = $7\\n\" -> \"LogicalAggregate\\ngroup = " |
| + "{0}\\nagg#0 = myAggFunc($1\\n)\\n\" [label=\"0\"]\n" |
| + "\"LogicalTableScan\\ntable = [scott, EMP]\\n\" -> \"LogicalProject\\nENAME = " |
| + "$1\\nDEPTNO = $7\\n\" [label=\"0\"]\n" |
| + "}\n"; |
| break; |
| } |
| assertThat(result, isLinux(expected)); |
| } |
| |
| @Test void testArrayType() { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| final RelNode rel = builder |
| .scan("EMP") |
| .project( |
| builder.call(new MockSqlOperatorTable.SplitFunction(), |
| builder.field("ENAME"), builder.literal(","))) |
| .build(); |
| final String relJson = RelOptUtil.dumpPlan("", rel, |
| SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES); |
| final String s = deserializeAndDumpToTextFormat(getSchema(rel), relJson); |
| final String expected = "" |
| + "LogicalProject($f0=[SPLIT($1, ',')])\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<@Nullable RelOptSchema> schemaHolder = Holder.empty(); |
| 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 the given format. |
| */ |
| private String deserializeAndDump( |
| RelOptSchema schema, String relJson, SqlExplainFormat format) { |
| 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, format, |
| SqlExplainLevel.EXPPLAN_ATTRIBUTES); |
| }); |
| return s; |
| } |
| |
| /** |
| * Deserialize a relnode from the json string by {@link RelJsonReader}, |
| * and dump it to text format. |
| */ |
| private String deserializeAndDumpToTextFormat(RelOptSchema schema, String relJson) { |
| return deserializeAndDump(schema, relJson, SqlExplainFormat.TEXT); |
| } |
| |
| /** |
| * Creates a mock {@link RelNode} that contains OVER. The SQL is as follows: |
| * |
| * <blockquote> |
| * select count(*) over (partition by {@code partitionKeyNames}<br> |
| * order by {@code orderKeyNames}) from {@code table} |
| * </blockquote> |
| * |
| * @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), |
| RexWindowBounds.UNBOUNDED_PRECEDING, |
| RexWindowBounds.CURRENT_ROW, |
| true, true, false, false, false)) |
| .build(); |
| return rel; |
| } |
| |
| @Test void testHashDistributionWithoutKeys() { |
| final RelNode root = createSortPlan(RelDistributions.hash(Collections.emptyList())); |
| final RelJsonWriter writer = new RelJsonWriter(); |
| root.explain(writer); |
| final String json = writer.asString(); |
| assertThat(json, is(HASH_DIST_WITHOUT_KEYS)); |
| |
| final String s = deserializeAndDumpToTextFormat(getSchema(root), json); |
| final String expected = |
| "LogicalSortExchange(distribution=[hash], collation=[[0]])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test void testWriteSortExchangeWithHashDistribution() { |
| final RelNode root = createSortPlan(RelDistributions.hash(Lists.newArrayList(0))); |
| final RelJsonWriter writer = new RelJsonWriter(); |
| root.explain(writer); |
| final String json = writer.asString(); |
| assertThat(json, is(XX3)); |
| |
| final String s = deserializeAndDumpToTextFormat(getSchema(root), json); |
| final String expected = |
| "LogicalSortExchange(distribution=[hash[0]], collation=[[0]])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test void testWriteSortExchangeWithRandomDistribution() { |
| final RelNode root = createSortPlan(RelDistributions.RANDOM_DISTRIBUTED); |
| final RelJsonWriter writer = new RelJsonWriter(); |
| root.explain(writer); |
| final String json = writer.asString(); |
| final String s = deserializeAndDumpToTextFormat(getSchema(root), json); |
| final String expected = |
| "LogicalSortExchange(distribution=[random], collation=[[0]])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test void testTableModifyInsert() { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| RelNode project = builder |
| .scan("EMP") |
| .project(builder.fields(), ImmutableList.of(), true) |
| .build(); |
| LogicalTableModify modify = LogicalTableModify.create( |
| project.getInput(0).getTable(), |
| (Prepare.CatalogReader) project.getInput(0).getTable().getRelOptSchema(), |
| project, |
| TableModify.Operation.INSERT, |
| null, |
| null, |
| false); |
| String relJson = RelOptUtil.dumpPlan("", modify, |
| SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES); |
| String s = deserializeAndDumpToTextFormat(getSchema(modify), relJson); |
| final String expected = "" |
| + "LogicalTableModify(table=[[scott, EMP]], operation=[INSERT], flattened=[false])\n" |
| + " LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], " |
| + "COMM=[$6], DEPTNO=[$7])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test void testTableModifyUpdate() { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| RelNode filter = builder |
| .scan("EMP") |
| .filter( |
| builder.call( |
| SqlStdOperatorTable.EQUALS, |
| builder.field("JOB"), |
| builder.literal("c"))) |
| .build(); |
| LogicalTableModify modify = LogicalTableModify.create( |
| filter.getInput(0).getTable(), |
| (Prepare.CatalogReader) filter.getInput(0).getTable().getRelOptSchema(), |
| filter, |
| TableModify.Operation.UPDATE, |
| ImmutableList.of("ENAME"), |
| ImmutableList.of(builder.literal("a")), |
| false); |
| String relJson = RelOptUtil.dumpPlan("", modify, |
| SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES); |
| String s = deserializeAndDumpToTextFormat(getSchema(modify), relJson); |
| final String expected = "" |
| + "LogicalTableModify(table=[[scott, EMP]], operation=[UPDATE], updateColumnList=[[ENAME]]," |
| + " sourceExpressionList=[['a']], flattened=[false])\n" |
| + " LogicalFilter(condition=[=($2, 'c')])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test void testTableModifyDelete() { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| RelNode filter = builder |
| .scan("EMP") |
| .filter( |
| builder.call( |
| SqlStdOperatorTable.EQUALS, |
| builder.field("JOB"), |
| builder.literal("c"))) |
| .build(); |
| LogicalTableModify modify = LogicalTableModify.create( |
| filter.getInput(0).getTable(), |
| (Prepare.CatalogReader) filter.getInput(0).getTable().getRelOptSchema(), |
| filter, |
| TableModify.Operation.DELETE, |
| null, |
| null, |
| false); |
| String relJson = RelOptUtil.dumpPlan("", modify, |
| SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES); |
| String s = deserializeAndDumpToTextFormat(getSchema(modify), relJson); |
| final String expected = "" |
| + "LogicalTableModify(table=[[scott, EMP]], operation=[DELETE], flattened=[false])\n" |
| + " LogicalFilter(condition=[=($2, 'c')])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| @Test void testTableModifyMerge() { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| RelNode deptScan = builder.scan("DEPT").build(); |
| RelNode empScan = builder.scan("EMP").build(); |
| builder.push(deptScan); |
| builder.push(empScan); |
| RelNode project = builder |
| .join(JoinRelType.LEFT, |
| builder.call( |
| SqlStdOperatorTable.EQUALS, |
| builder.field(2, 0, "DEPTNO"), |
| builder.field(2, 1, "DEPTNO"))) |
| .project( |
| builder.literal(0), |
| builder.literal("x"), |
| builder.literal("x"), |
| builder.literal(0), |
| builder.literal("20200501 10:00:00"), |
| builder.literal(0), |
| builder.literal(0), |
| builder.literal(0), |
| builder.literal("false"), |
| builder.field(1, 0, 2), |
| builder.field(1, 0, 3), |
| builder.field(1, 0, 4), |
| builder.field(1, 0, 5), |
| builder.field(1, 0, 6), |
| builder.field(1, 0, 7), |
| builder.field(1, 0, 8), |
| builder.field(1, 0, 9), |
| builder.field(1, 0, 10), |
| builder.literal("a")) |
| .build(); |
| // for sql: |
| // merge into emp using dept on emp.deptno = dept.deptno |
| // when matched then update set job = 'a' |
| // when not matched then insert values(0, 'x', 'x', 0, '20200501 10:00:00', 0, 0, 0, 0) |
| LogicalTableModify modify = LogicalTableModify.create( |
| empScan.getTable(), |
| (Prepare.CatalogReader) empScan.getTable().getRelOptSchema(), |
| project, |
| TableModify.Operation.MERGE, |
| ImmutableList.of("ENAME"), |
| null, |
| false); |
| String relJson = RelOptUtil.dumpPlan("", modify, |
| SqlExplainFormat.JSON, SqlExplainLevel.EXPPLAN_ATTRIBUTES); |
| String s = deserializeAndDumpToTextFormat(getSchema(modify), relJson); |
| final String expected = "" |
| + "LogicalTableModify(table=[[scott, EMP]], operation=[MERGE], " |
| + "updateColumnList=[[ENAME]], flattened=[false])\n" |
| + " LogicalProject($f0=[0], $f1=['x'], $f2=['x'], $f3=[0], $f4=['20200501 10:00:00'], " |
| + "$f5=[0], $f6=[0], $f7=[0], $f8=['false'], LOC=[$2], EMPNO=[$3], ENAME=[$4], JOB=[$5], " |
| + "MGR=[$6], HIREDATE=[$7], SAL=[$8], COMM=[$9], DEPTNO=[$10], $f18=['a'])\n" |
| + " LogicalJoin(condition=[=($0, $10)], joinType=[left])\n" |
| + " LogicalTableScan(table=[[scott, DEPT]])\n" |
| + " LogicalTableScan(table=[[scott, EMP]])\n"; |
| assertThat(s, isLinux(expected)); |
| } |
| |
| private RelNode createSortPlan(RelDistribution distribution) { |
| final FrameworkConfig config = RelBuilderTest.config().build(); |
| final RelBuilder builder = RelBuilder.create(config); |
| return builder.scan("EMP") |
| .sortExchange(distribution, |
| RelCollations.of(0)) |
| .build(); |
| } |
| } |