/* * 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.optimizations; import com.facebook.presto.spi.block.SortOrder; import com.facebook.presto.sql.planner.Plan; import com.facebook.presto.sql.planner.StatsRecorder; import com.facebook.presto.sql.planner.assertions.BasePlanTest; import com.facebook.presto.sql.planner.assertions.ExpectedValueProvider; import com.facebook.presto.sql.planner.assertions.PlanAssert; import com.facebook.presto.sql.planner.assertions.PlanMatchPattern; import com.facebook.presto.sql.planner.iterative.IterativeOptimizer; import com.facebook.presto.sql.planner.iterative.rule.RemoveRedundantIdentityProjections; import com.facebook.presto.sql.planner.plan.JoinNode; import com.facebook.presto.sql.planner.plan.WindowNode; import com.facebook.presto.sql.tree.FrameBound; import com.facebook.presto.sql.tree.WindowFrame; import com.facebook.presto.testing.LocalQueryRunner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.intellij.lang.annotations.Language; import org.testng.annotations.Test; import java.util.List; import java.util.Optional; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.any; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.anyNot; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.anyTree; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.expression; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.filter; 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.project; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.specification; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.tableScan; import static com.facebook.presto.sql.planner.assertions.PlanMatchPattern.window; public class TestMergeWindows extends BasePlanTest { private static final String SUPPKEY_ALIAS = "SUPPKEY"; private static final String ORDERKEY_ALIAS = "ORDERKEY"; private static final String SHIPDATE_ALIAS = "SHIPDATE"; private static final String QUANTITY_ALIAS = "QUANTITY"; private static final String DISCOUNT_ALIAS = "DISCOUNT"; private static final String EXTENDEDPRICE_ALIAS = "EXTENDEDPRICE"; private static final PlanMatchPattern LINEITEM_TABLESCAN_DOQSS = tableScan( "lineitem", ImmutableMap.of(QUANTITY_ALIAS, "quantity", DISCOUNT_ALIAS, "discount", SUPPKEY_ALIAS, "suppkey", ORDERKEY_ALIAS, "orderkey", SHIPDATE_ALIAS, "shipdate")); private static final PlanMatchPattern LINEITEM_TABLESCAN_DOQS = tableScan( "lineitem", ImmutableMap.of(QUANTITY_ALIAS, "quantity", DISCOUNT_ALIAS, "discount", SUPPKEY_ALIAS, "suppkey", ORDERKEY_ALIAS, "orderkey")); private static final PlanMatchPattern LINEITEM_TABLESCAN_DEOQS = tableScan( "lineitem", ImmutableMap.of(QUANTITY_ALIAS, "quantity", SUPPKEY_ALIAS, "suppkey", ORDERKEY_ALIAS, "orderkey", DISCOUNT_ALIAS, "discount", EXTENDEDPRICE_ALIAS, "extendedprice")); private static final Optional<WindowFrame> COMMON_FRAME = Optional.of(new WindowFrame( WindowFrame.Type.ROWS, new FrameBound(FrameBound.Type.UNBOUNDED_PRECEDING), Optional.of(new FrameBound(FrameBound.Type.CURRENT_ROW)))); private static final Optional<WindowFrame> UNSPECIFIED_FRAME = Optional.empty(); private final ExpectedValueProvider<WindowNode.Specification> specificationA; private final ExpectedValueProvider<WindowNode.Specification> specificationB; public TestMergeWindows() { specificationA = specification( ImmutableList.of(SUPPKEY_ALIAS), ImmutableList.of(ORDERKEY_ALIAS), ImmutableMap.of(ORDERKEY_ALIAS, SortOrder.ASC_NULLS_LAST)); specificationB = specification( ImmutableList.of(ORDERKEY_ALIAS), ImmutableList.of(SHIPDATE_ALIAS), ImmutableMap.of(SHIPDATE_ALIAS, SortOrder.ASC_NULLS_LAST)); } /** * There are two types of tests in here, and they answer two different * questions about MergeWindows (MW): * * 1) Is MW working as it's supposed to be? The tests running the minimal * set of optimizers can tell us this. * 2) Has some other optimizer changed the plan in such a way that MW no * longer merges windows with identical specifications because the plan * that MW sees cannot be optimized by MW? The test running the full set * of optimizers answers this, though it isn't actually meaningful unless * we know the answer to question 1 is "yes". * * The tests that use only the minimal set of optimizers are closer to true * "unit" tests in that they verify the behavior of MW with as few * external dependencies as possible. Those dependencies to include the * parser and analyzer, so the phrase "unit" tests should be taken with a * grain of salt. Using the parser and anayzler instead of creating plan * nodes by hand does have a couple of advantages over a true unit test: * 1) The tests are more self-maintaining. * 2) They're a lot easier to read. * 3) It's a lot less typing. * * The test that runs with all of the optimzers acts as an integration test * and ensures that MW is effective when run with the complete set of * optimizers. */ @Test public void testMergeableWindowsAllOptimizers() { @Language("SQL") String sql = "SELECT " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_A, " + "SUM(quantity) OVER (PARTITION BY orderkey ORDER BY shipdate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_B, " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_discount_A " + "FROM lineitem"; PlanMatchPattern pattern = anyTree( window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS)), functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), anyTree( window(specificationB, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), anyNot(WindowNode.class, LINEITEM_TABLESCAN_DOQSS))))); // should be anyTree(LINEITEM_TABLESCAN_DOQSS) but anyTree does not handle zero nodes case correctly assertPlan(sql, pattern); } @Test public void testIdenticalWindowSpecificationsABA() { @Language("SQL") String sql = "SELECT " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_A, " + "SUM(quantity) OVER (PARTITION BY orderkey ORDER BY shipdate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_B, " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_discount_A " + "FROM lineitem"; assertUnitPlan(sql, anyTree( window(specificationB, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS)), functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), LINEITEM_TABLESCAN_DOQSS)))); } @Test public void testIdenticalWindowSpecificationsABcpA() { @Language("SQL") String sql = "SELECT " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_A, " + "LAG(quantity, 1, 0.0) OVER (PARTITION BY orderkey ORDER BY shipdate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_B, " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_discount_A " + "FROM lineitem"; assertUnitPlan(sql, anyTree( window(specificationA, ImmutableList.of(functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), window(specificationB, ImmutableList.of(functionCall("lag", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS, "ONE", "ZERO"))), project(ImmutableMap.of("ONE", expression("CAST(1 AS bigint)"), "ZERO", expression("0.0")), window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), LINEITEM_TABLESCAN_DOQSS)))))); } @Test public void testIdenticalWindowSpecificationsAAcpA() { @Language("SQL") String sql = "SELECT " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_A, " + "LAG(quantity, 1, 0.0) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_B, " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_discount_A " + "FROM lineitem"; assertUnitPlan(sql, anyTree( window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS)), functionCall("lag", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS, "ONE", "ZERO"))), project(ImmutableMap.of("ONE", expression("CAST(1 AS bigint)"), "ZERO", expression("0.0")), window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), LINEITEM_TABLESCAN_DOQS))))); } @Test public void testIdenticalWindowSpecificationsDefaultFrame() { ExpectedValueProvider<WindowNode.Specification> specificationC = specification( ImmutableList.of(SUPPKEY_ALIAS), ImmutableList.of(ORDERKEY_ALIAS), ImmutableMap.of(ORDERKEY_ALIAS, SortOrder.ASC_NULLS_LAST)); ExpectedValueProvider<WindowNode.Specification> specificationD = specification( ImmutableList.of(ORDERKEY_ALIAS), ImmutableList.of(SHIPDATE_ALIAS), ImmutableMap.of(SHIPDATE_ALIAS, SortOrder.ASC_NULLS_LAST)); @Language("SQL") String sql = "SELECT " + "SUM(quantity) OVER (PARTITION By suppkey ORDER BY orderkey), " + "SUM(quantity) OVER (PARTITION BY orderkey ORDER BY shipdate), " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey) " + "FROM lineitem"; assertUnitPlan(sql, anyTree( window(specificationD, ImmutableList.of( functionCall("sum", UNSPECIFIED_FRAME, ImmutableList.of(QUANTITY_ALIAS))), window(specificationC, ImmutableList.of( functionCall("sum", UNSPECIFIED_FRAME, ImmutableList.of(QUANTITY_ALIAS)), functionCall("sum", UNSPECIFIED_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), LINEITEM_TABLESCAN_DOQSS)))); } @Test public void testMergeDifferentFrames() { Optional<WindowFrame> frameC = Optional.of(new WindowFrame( WindowFrame.Type.ROWS, new FrameBound(FrameBound.Type.UNBOUNDED_PRECEDING), Optional.of(new FrameBound(FrameBound.Type.CURRENT_ROW)))); ExpectedValueProvider<WindowNode.Specification> specificationC = specification( ImmutableList.of(SUPPKEY_ALIAS), ImmutableList.of(ORDERKEY_ALIAS), ImmutableMap.of(ORDERKEY_ALIAS, SortOrder.ASC_NULLS_LAST)); Optional<WindowFrame> frameD = Optional.of(new WindowFrame( WindowFrame.Type.ROWS, new FrameBound(FrameBound.Type.CURRENT_ROW), Optional.of(new FrameBound(FrameBound.Type.UNBOUNDED_FOLLOWING)))); @Language("SQL") String sql = "SELECT " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_C, " + "AVG(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) avg_quantity_D, " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_discount_C " + "FROM lineitem"; assertUnitPlan(sql, anyTree( window(specificationC, ImmutableList.of( functionCall("avg", frameD, ImmutableList.of(QUANTITY_ALIAS)), functionCall("sum", frameC, ImmutableList.of(DISCOUNT_ALIAS)), functionCall("sum", frameC, ImmutableList.of(QUANTITY_ALIAS))), LINEITEM_TABLESCAN_DOQS))); } @Test public void testMergeDifferentFramesWithDefault() { Optional<WindowFrame> frameD = Optional.of(new WindowFrame( WindowFrame.Type.ROWS, new FrameBound(FrameBound.Type.CURRENT_ROW), Optional.of(new FrameBound(FrameBound.Type.UNBOUNDED_FOLLOWING)))); ExpectedValueProvider<WindowNode.Specification> specificationD = specification( ImmutableList.of(SUPPKEY_ALIAS), ImmutableList.of(ORDERKEY_ALIAS), ImmutableMap.of(ORDERKEY_ALIAS, SortOrder.ASC_NULLS_LAST)); @Language("SQL") String sql = "SELECT " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey) sum_quantity_C, " + "AVG(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) avg_quantity_D, " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey) sum_discount_C " + "FROM lineitem"; assertUnitPlan(sql, anyTree( window(specificationD, ImmutableList.of( functionCall("avg", frameD, ImmutableList.of(QUANTITY_ALIAS)), functionCall("sum", UNSPECIFIED_FRAME, ImmutableList.of(DISCOUNT_ALIAS)), functionCall("sum", UNSPECIFIED_FRAME, ImmutableList.of(QUANTITY_ALIAS))), LINEITEM_TABLESCAN_DOQS))); } @Test public void testNotMergeAcrossJoinBranches() { String rOrderkeyAlias = "R_ORDERKEY"; String rShipdateAlias = "R_SHIPDATE"; String rQuantityAlias = "R_QUANTITY"; @Language("SQL") String sql = "WITH foo AS (" + "SELECT " + "suppkey, orderkey, partkey, " + "SUM(discount) OVER (PARTITION BY orderkey ORDER BY shipdate, quantity DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) a " + "FROM lineitem WHERE (partkey = 272 OR partkey = 273) AND suppkey > 50 " + "), " + "bar AS ( " + "SELECT " + "suppkey, orderkey, partkey, " + "AVG(quantity) OVER (PARTITION BY orderkey ORDER BY shipdate, quantity DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) b " + "FROM lineitem WHERE (partkey = 272 OR partkey = 273) AND suppkey > 50 " + ")" + "SELECT * FROM foo, bar WHERE foo.a = bar.b"; ExpectedValueProvider<WindowNode.Specification> leftSpecification = specification( ImmutableList.of(ORDERKEY_ALIAS), ImmutableList.of(SHIPDATE_ALIAS, QUANTITY_ALIAS), ImmutableMap.of(SHIPDATE_ALIAS, SortOrder.ASC_NULLS_LAST, QUANTITY_ALIAS, SortOrder.DESC_NULLS_LAST)); ExpectedValueProvider<WindowNode.Specification> rightSpecification = specification( ImmutableList.of(rOrderkeyAlias), ImmutableList.of(rShipdateAlias, rQuantityAlias), ImmutableMap.of(rShipdateAlias, SortOrder.ASC_NULLS_LAST, rQuantityAlias, SortOrder.DESC_NULLS_LAST)); // Too many items in the map to call ImmutableMap.of() :-( ImmutableMap.Builder<String, String> leftTableScanBuilder = ImmutableMap.builder(); leftTableScanBuilder.put(DISCOUNT_ALIAS, "discount"); leftTableScanBuilder.put(ORDERKEY_ALIAS, "orderkey"); leftTableScanBuilder.put("PARTKEY", "partkey"); leftTableScanBuilder.put(SUPPKEY_ALIAS, "suppkey"); leftTableScanBuilder.put(QUANTITY_ALIAS, "quantity"); leftTableScanBuilder.put(SHIPDATE_ALIAS, "shipdate"); PlanMatchPattern leftTableScan = tableScan("lineitem", leftTableScanBuilder.build()); PlanMatchPattern rightTableScan = tableScan( "lineitem", ImmutableMap.of( rOrderkeyAlias, "orderkey", "R_PARTKEY", "partkey", "R_SUPPKEY", "suppkey", rQuantityAlias, "quantity", rShipdateAlias, "shipdate")); assertUnitPlan(sql, anyTree( filter("SUM = AVG", join(JoinNode.Type.INNER, ImmutableList.of(), any( window(leftSpecification, ImmutableMap.of("SUM", functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), any( leftTableScan))), any( window(rightSpecification, ImmutableMap.of("AVG", functionCall("avg", COMMON_FRAME, ImmutableList.of(rQuantityAlias))), any( rightTableScan))))))); } @Test public void testNotMergeDifferentPartition() { @Language("SQL") String sql = "SELECT " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_extendedprice_A, " + "SUM(quantity) over (PARTITION BY quantity ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_C " + "FROM lineitem"; ExpectedValueProvider<WindowNode.Specification> specificationC = specification( ImmutableList.of(QUANTITY_ALIAS), ImmutableList.of(ORDERKEY_ALIAS), ImmutableMap.of(ORDERKEY_ALIAS, SortOrder.ASC_NULLS_LAST)); assertUnitPlan(sql, anyTree( window(specificationC, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), LINEITEM_TABLESCAN_DOQS)))); } @Test public void testNotMergeDifferentOrderBy() { @Language("SQL") String sql = "SELECT " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_extendedprice_A, " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY quantity ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_C " + "FROM lineitem"; ExpectedValueProvider<WindowNode.Specification> specificationC = specification( ImmutableList.of(SUPPKEY_ALIAS), ImmutableList.of(QUANTITY_ALIAS), ImmutableMap.of(QUANTITY_ALIAS, SortOrder.ASC_NULLS_LAST)); assertUnitPlan(sql, anyTree( window(specificationC, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), LINEITEM_TABLESCAN_DOQS)))); } @Test public void testNotMergeDifferentOrdering() { @Language("SQL") String sql = "SELECT " + "SUM(extendedprice) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_extendedprice_A, " + "SUM(quantity) over (PARTITION BY suppkey ORDER BY orderkey DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_C, " + "SUM(discount) over (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_discount_A " + "FROM lineitem"; ExpectedValueProvider<WindowNode.Specification> specificationC = specification( ImmutableList.of(SUPPKEY_ALIAS), ImmutableList.of(ORDERKEY_ALIAS), ImmutableMap.of(ORDERKEY_ALIAS, SortOrder.DESC_NULLS_LAST)); assertUnitPlan(sql, anyTree( window(specificationC, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(EXTENDEDPRICE_ALIAS)), functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), LINEITEM_TABLESCAN_DEOQS)))); } @Test public void testNotMergeDifferentNullOrdering() { @Language("SQL") String sql = "SELECT " + "SUM(extendedprice) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_extendedprice_A, " + "SUM(quantity) OVER (PARTITION BY suppkey ORDER BY orderkey NULLS FIRST ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_quantity_C, " + "SUM(discount) OVER (PARTITION BY suppkey ORDER BY orderkey ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) sum_discount_A " + "FROM lineitem"; ExpectedValueProvider<WindowNode.Specification> specificationC = specification( ImmutableList.of(SUPPKEY_ALIAS), ImmutableList.of(ORDERKEY_ALIAS), ImmutableMap.of(ORDERKEY_ALIAS, SortOrder.ASC_NULLS_FIRST)); assertUnitPlan(sql, anyTree( window(specificationC, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(QUANTITY_ALIAS))), window(specificationA, ImmutableList.of( functionCall("sum", COMMON_FRAME, ImmutableList.of(EXTENDEDPRICE_ALIAS)), functionCall("sum", COMMON_FRAME, ImmutableList.of(DISCOUNT_ALIAS))), LINEITEM_TABLESCAN_DEOQS)))); } private void assertUnitPlan(@Language("SQL") String sql, PlanMatchPattern pattern) { LocalQueryRunner queryRunner = getQueryRunner(); List<PlanOptimizer> optimizers = ImmutableList.of( new UnaliasSymbolReferences(), new IterativeOptimizer(new StatsRecorder(), ImmutableSet.of(new RemoveRedundantIdentityProjections())), new MergeWindows(), new PruneUnreferencedOutputs()); queryRunner.inTransaction(transactionSession -> { Plan actualPlan = queryRunner.createPlan(transactionSession, sql, optimizers); PlanAssert.assertPlan(transactionSession, queryRunner.getMetadata(), actualPlan, pattern); return null; }); } }