/* * Licensed 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 com.facebook.presto.sql.planner; import com.facebook.presto.sql.planner.assertions.BasePlanTest; import com.facebook.presto.sql.planner.plan.OutputNode; import com.facebook.presto.sql.planner.plan.TableScanNode; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.testng.annotations.Test; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.aggregation; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.any; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.anyTree; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.columnReference; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.equiJoinClause; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.expression; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.functionCall; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.join; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.node; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.output; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.project; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.strictOutput; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.strictProject; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.strictTableScan; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.tableScan; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.values; import static com.facebook.presto.sql.planner.plan.JoinNode.Type.INNER; import static org.testng.Assert.fail; public class TestPlanMatchingFramework extends BasePlanTest { @Test public void testOutput() { assertMinimallyOptimizedPlan("SELECT orderkey FROM lineitem", node(OutputNode.class, node(TableScanNode.class).withAlias("ORDERKEY", columnReference("lineitem", "orderkey"))) .withOutputs(ImmutableList.of("ORDERKEY"))); } @Test public void testOutputSameColumnMultipleTimes() { assertMinimallyOptimizedPlan("SELECT orderkey, orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY", "ORDERKEY"), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey")))); } @Test public void testOutputSameColumnMultipleTimesWithOtherOutputs() { assertMinimallyOptimizedPlan("SELECT extendedprice, orderkey, discount, orderkey, linenumber FROM lineitem", output(ImmutableList.of("ORDERKEY", "ORDERKEY"), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey")))); } @Test public void testStrictOutput() { assertMinimallyOptimizedPlan("SELECT orderkey, extendedprice FROM lineitem", strictOutput(ImmutableList.of("ORDERKEY", "EXTENDEDPRICE"), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey", "EXTENDEDPRICE", "extendedprice")))); } @Test public void testStrictTableScan() { assertMinimallyOptimizedPlan("SELECT orderkey, extendedprice FROM lineitem", output(ImmutableList.of("ORDERKEY", "EXTENDEDPRICE"), strictTableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey", "EXTENDEDPRICE", "extendedprice")))); } @Test public void testUnreferencedSymbolsDontNeedBinding() { assertMinimallyOptimizedPlan("SELECT orderkey, 2 FROM lineitem", output(ImmutableList.of("ORDERKEY"), anyTree( tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey"))))); } @Test public void testAliasConstantFromProject() { assertMinimallyOptimizedPlan("SELECT orderkey, 2 FROM lineitem", output(ImmutableList.of("ORDERKEY", "TWO"), project(ImmutableMap.of("TWO", expression("2")), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey"))))); } @Test public void testAliasExpressionFromProject() { assertMinimallyOptimizedPlan("SELECT orderkey, 1 + orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY", "EXPRESSION"), project(ImmutableMap.of("EXPRESSION", expression("CAST(1 AS bigint) + ORDERKEY")), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey"))))); } @Test public void testStrictProject() { assertMinimallyOptimizedPlan("SELECT orderkey, 1 + orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY", "EXPRESSION"), strictProject(ImmutableMap.of("EXPRESSION", expression("CAST(1 AS BIGINT) + ORDERKEY"), "ORDERKEY", expression("ORDERKEY")), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey"))))); } @Test public void testIdentityAliasFromProject() { assertMinimallyOptimizedPlan("SELECT orderkey, 1 + orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY", "EXPRESSION"), project(ImmutableMap.of("ORDERKEY", expression("ORDERKEY"), "EXPRESSION", expression("CAST(1 AS bigint) + ORDERKEY")), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey"))))); } @Test public void testTableScan() { assertMinimallyOptimizedPlan("SELECT orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY"), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey")))); } @Test public void testJoinMatcher() { assertPlan("SELECT o.orderkey FROM orders o, lineitem l WHERE l.orderkey = o.orderkey", anyTree( join(INNER, ImmutableList.of(equiJoinClause("ORDERS_OK", "LINEITEM_OK")), any( tableScan("orders").withAlias("ORDERS_OK", columnReference("orders", "orderkey"))), anyTree( tableScan("lineitem").withAlias("LINEITEM_OK", columnReference("lineitem", "orderkey")))))); } @Test public void testSelfJoin() { assertPlan("SELECT l.orderkey FROM orders l, orders r WHERE l.orderkey = r.orderkey", anyTree( join(INNER, ImmutableList.of(equiJoinClause("L_ORDERS_OK", "R_ORDERS_OK")), any( tableScan("orders").withAlias("L_ORDERS_OK", columnReference("orders", "orderkey"))), anyTree( tableScan("orders").withAlias("R_ORDERS_OK", columnReference("orders", "orderkey")))))); } @Test public void testAggregation() { assertMinimallyOptimizedPlan("SELECT COUNT(nationkey) FROM nation", output(ImmutableList.of("COUNT"), aggregation(ImmutableMap.of("COUNT", functionCall("count", ImmutableList.of("NATIONKEY"))), tableScan("nation", ImmutableMap.of("NATIONKEY", "nationkey"))))); } @Test public void testValues() { assertMinimallyOptimizedPlan("SELECT * from (VALUES 1, 2)", output(ImmutableList.of("VALUE"), values(ImmutableMap.of("VALUE", 0)))); } @Test(expectedExceptions = {IllegalStateException.class}, expectedExceptionsMessageRegExp = ".* doesn't have column .*") public void testAliasNonexistentColumn() { assertMinimallyOptimizedPlan("SELECT orderkey FROM lineitem", node(OutputNode.class, node(TableScanNode.class).withAlias("ORDERKEY", columnReference("lineitem", "NXCOLUMN")))); } @Test(expectedExceptions = {IllegalStateException.class}, expectedExceptionsMessageRegExp = "missing expression for alias .*") public void testReferenceNonexistentAlias() { assertMinimallyOptimizedPlan("SELECT orderkey FROM lineitem", output(ImmutableList.of("NXALIAS"), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey")))); } /* * There are so many ways for matches to fail that this is not likely to be generally useful. * Pending better diagnostics, please leave this here, and restrict its use to simple queries * that have few ways to not match a pattern, and functionality that is well-tested with * positive tests. */ private void assertFails(Runnable runnable) { try { runnable.run(); fail("Plans should not have matched!"); } catch (AssertionError e) { //ignored } } @Test public void testStrictOutputExtraSymbols() { assertFails(() -> assertMinimallyOptimizedPlan("SELECT orderkey, extendedprice FROM lineitem", strictOutput(ImmutableList.of("ORDERKEY"), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey", "EXTENDEDPRICE", "extendedprice"))))); } @Test public void testStrictTableScanExtraSymbols() { assertFails(() -> assertMinimallyOptimizedPlan("SELECT orderkey, extendedprice FROM lineitem", output(ImmutableList.of("ORDERKEY", "EXTENDEDPRICE"), strictTableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey"))))); } @Test public void testStrictProjectExtraSymbols() { assertFails(() -> assertMinimallyOptimizedPlan("SELECT discount, orderkey, 1 + orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY", "EXPRESSION"), strictProject(ImmutableMap.of("EXPRESSION", expression("1 + ORDERKEY"), "ORDERKEY", expression("ORDERKEY")), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey")))))); } @Test(expectedExceptions = {IllegalStateException.class}, expectedExceptionsMessageRegExp = ".*already bound to expression.*") public void testDuplicateAliases() { assertPlan("SELECT o.orderkey FROM orders o, lineitem l WHERE l.orderkey = o.orderkey", anyTree( join(INNER, ImmutableList.of(equiJoinClause("ORDERS_OK", "LINEITEM_OK")), any( tableScan("orders").withAlias("ORDERS_OK", columnReference("orders", "orderkey"))), anyTree( tableScan("lineitem").withAlias("ORDERS_OK", columnReference("lineitem", "orderkey")))))); } @Test(expectedExceptions = {IllegalStateException.class}, expectedExceptionsMessageRegExp = ".*already bound in.*") public void testBindMultipleAliasesSameExpression() { assertMinimallyOptimizedPlan("SELECT orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY", "TWO"), tableScan("lineitem", ImmutableMap.of("FIRST", "orderkey", "SECOND", "orderkey")))); } @Test(expectedExceptions = {IllegalStateException.class}, expectedExceptionsMessageRegExp = "missing expression for alias .*") public void testProjectLimitsScope() { assertMinimallyOptimizedPlan("SELECT 1 + orderkey FROM lineitem", output(ImmutableList.of("ORDERKEY"), project(ImmutableMap.of("EXPRESSION", expression("CAST(1 AS bigint) + ORDERKEY")), tableScan("lineitem", ImmutableMap.of("ORDERKEY", "orderkey"))))); } }