diff --git a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java index 509e939a0481..6994c5aa2e1b 100644 --- a/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java +++ b/client/trino-jdbc/src/test/java/io/trino/jdbc/TestTrinoDatabaseMetaData.java @@ -1292,8 +1292,8 @@ public void testGetColumnsMetadataCalls() list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "TYPE_NAME")), list(list(COUNTING_CATALOG, "test_schema1", "test_table1", "column_17", "varchar")), new MetadataCallsCount() - .withListSchemasCount(2) - .withListTablesCount(3) + .withListSchemasCount(1) + .withListTablesCount(2) .withGetColumnsCount(1)); // LIKE predicate on schema name and table name, but no predicate on catalog name @@ -1306,8 +1306,8 @@ public void testGetColumnsMetadataCalls() .mapToObj(columnIndex -> list(COUNTING_CATALOG, "test_schema1", "test_table1", "column_" + columnIndex, "varchar")) .collect(toImmutableList()), new MetadataCallsCount() - .withListSchemasCount(2) - .withListTablesCount(3) + .withListSchemasCount(1) + .withListTablesCount(2) .withGetColumnsCount(1)); // LIKE predicate on schema name, but no predicate on catalog name and table name @@ -1322,7 +1322,7 @@ public void testGetColumnsMetadataCalls() .mapToObj(columnIndex -> list(COUNTING_CATALOG, "test_schema1", "test_table" + tableIndex, "column_" + columnIndex, "varchar"))) .collect(toImmutableList()), new MetadataCallsCount() - .withListSchemasCount(2) + .withListSchemasCount(4) .withListTablesCount(1) .withGetColumnsCount(1000)); @@ -1338,9 +1338,9 @@ public void testGetColumnsMetadataCalls() .mapToObj(columnIndex -> list(COUNTING_CATALOG, "test_schema" + schemaIndex, "test_table1", "column_" + columnIndex, "varchar"))) .collect(toImmutableList()), new MetadataCallsCount() - .withListSchemasCount(3) - .withListTablesCount(8) - .withGetTableHandleCount(2) + .withListSchemasCount(5) + .withListTablesCount(5) + .withGetTableHandleCount(8) .withGetColumnsCount(2)); // Equality predicate on schema name and table name, but no predicate on catalog name @@ -1383,7 +1383,7 @@ public void testGetColumnsMetadataCalls() list("TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "TYPE_NAME")), list(), new MetadataCallsCount() - .withListSchemasCount(2) + .withListSchemasCount(1) .withListTablesCount(0) .withGetColumnsCount(0)); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java b/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java index b3a8f1c6b2de..eeb7f7f90b78 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/PlanOptimizers.java @@ -587,7 +587,7 @@ public PlanOptimizers( ImmutableSet.of( new ApplyTableScanRedirection(plannerContext), new PruneTableScanColumns(metadata), - new PushPredicateIntoTableScan(plannerContext, typeAnalyzer)))); + new PushPredicateIntoTableScan(plannerContext, typeAnalyzer, false)))); Set> pushIntoTableScanRulesExceptJoins = ImmutableSet.>builder() .addAll(columnPruningRules) @@ -595,7 +595,7 @@ public PlanOptimizers( .add(new PushProjectionIntoTableScan(plannerContext, typeAnalyzer, scalarStatsCalculator)) .add(new RemoveRedundantIdentityProjections()) .add(new PushLimitIntoTableScan(metadata)) - .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer)) + .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer, false)) .add(new PushSampleIntoTableScan(metadata)) .add(new PushAggregationIntoTableScan(plannerContext, typeAnalyzer)) .add(new PushDistinctLimitIntoTableScan(plannerContext, typeAnalyzer)) @@ -663,7 +663,7 @@ public PlanOptimizers( costCalculator, ImmutableSet.>builder() .addAll(simplifyOptimizerRules) // Should be always run after PredicatePushDown - .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer)) + .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer, false)) .build()), new UnaliasSymbolReferences(metadata), // Run again because predicate pushdown and projection pushdown might add more projections columnPruningOptimizer, // Make sure to run this before index join. Filtered projections may not have all the columns. @@ -728,7 +728,7 @@ public PlanOptimizers( costCalculator, ImmutableSet.>builder() .addAll(simplifyOptimizerRules) // Should be always run after PredicatePushDown - .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer)) + .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer, false)) .build()), pushProjectionIntoTableScanOptimizer, // Projection pushdown rules may push reducing projections (e.g. dereferences) below filters for potential @@ -742,7 +742,7 @@ public PlanOptimizers( costCalculator, ImmutableSet.>builder() .addAll(simplifyOptimizerRules) // Should be always run after PredicatePushDown - .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer)) + .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer, false)) .build()), columnPruningOptimizer, new IterativeOptimizer( @@ -808,6 +808,21 @@ public PlanOptimizers( // new join nodes without JoinNode.maySkipOutputDuplicates flag set new OptimizeDuplicateInsensitiveJoins(metadata)))); + // Previous invocations of PushPredicateIntoTableScan do not prune using predicate expression. The invocation in AddExchanges + // does this pruning - and we may end up with empty union branches after that. We invoke PushPredicateIntoTableScan + // and rules to remove empty branches here to get empty values node through pushdown and then prune them. + builder.add(new IterativeOptimizer( + plannerContext, + ruleStats, + statsCalculator, + costCalculator, + ImmutableSet.of( + new PushPredicateIntoTableScan(plannerContext, typeAnalyzer, true), + new RemoveEmptyUnionBranches(), + new EvaluateEmptyIntersect(), + new RemoveEmptyExceptBranches(), + new TransformFilteringSemiJoinToInnerJoin()))); + if (!forceSingleNode) { builder.add(new IterativeOptimizer( plannerContext, @@ -895,7 +910,7 @@ public PlanOptimizers( costCalculator, ImmutableSet.>builder() .addAll(simplifyOptimizerRules) // Should be always run after PredicatePushDown - .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer)) + .add(new PushPredicateIntoTableScan(plannerContext, typeAnalyzer, false)) .add(new RemoveRedundantPredicateAboveTableScan(plannerContext, typeAnalyzer)) .build())); builder.add(inlineProjections); diff --git a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPredicateIntoTableScan.java b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPredicateIntoTableScan.java index 972c85ae130b..cb2cbe6c41dc 100644 --- a/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPredicateIntoTableScan.java +++ b/core/trino-main/src/main/java/io/trino/sql/planner/iterative/rule/PushPredicateIntoTableScan.java @@ -50,6 +50,7 @@ import io.trino.sql.planner.plan.ValuesNode; import io.trino.sql.tree.Expression; import io.trino.sql.tree.NodeRef; +import org.assertj.core.util.VisibleForTesting; import java.util.ArrayList; import java.util.List; @@ -90,10 +91,13 @@ public class PushPredicateIntoTableScan private final PlannerContext plannerContext; private final TypeAnalyzer typeAnalyzer; - public PushPredicateIntoTableScan(PlannerContext plannerContext, TypeAnalyzer typeAnalyzer) + private final boolean pruneWithPredicateExpression; + + public PushPredicateIntoTableScan(PlannerContext plannerContext, TypeAnalyzer typeAnalyzer, boolean pruneWithPredicateExpression) { this.plannerContext = requireNonNull(plannerContext, "plannerContext is null"); this.typeAnalyzer = requireNonNull(typeAnalyzer, "typeAnalyzer is null"); + this.pruneWithPredicateExpression = pruneWithPredicateExpression; } @Override @@ -116,7 +120,7 @@ public Result apply(FilterNode filterNode, Captures captures, Context context) Optional rewritten = pushFilterIntoTableScan( filterNode, tableScan, - false, + pruneWithPredicateExpression, context.getSession(), context.getSymbolAllocator(), plannerContext, @@ -416,6 +420,12 @@ public static TupleDomain computeEnforced(TupleDomain !(optimizer instanceof AddLocalExchanges || optimizer instanceof CheckSubqueryNodesAreRewritten)); + optimizer -> ! + (optimizer instanceof AddLocalExchanges + || optimizer instanceof CheckSubqueryNodesAreRewritten + || isPushPredicateIntoTableScanWithPrunePredicateOperation(optimizer))); + } + + private boolean isPushPredicateIntoTableScanWithPrunePredicateOperation(PlanOptimizer optimizer) + { + if (optimizer instanceof IterativeOptimizer iterativeOptimizer) { + return iterativeOptimizer.getRules().stream().anyMatch(rule -> { + if (rule instanceof PushPredicateIntoTableScan pushPredicateIntoTableScan) { + return pushPredicateIntoTableScan.getPruneWithPredicateExpression(); + } + return false; + }); + } + + return false; } @Test diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java index b67ed21118b3..e43616cfd956 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/iterative/rule/TestPushPredicateIntoTableScan.java @@ -97,7 +97,7 @@ public class TestPushPredicateIntoTableScan @BeforeClass public void setUpBeforeClass() { - pushPredicateIntoTableScan = new PushPredicateIntoTableScan(tester().getPlannerContext(), createTestingTypeAnalyzer(tester().getPlannerContext())); + pushPredicateIntoTableScan = new PushPredicateIntoTableScan(tester().getPlannerContext(), createTestingTypeAnalyzer(tester().getPlannerContext()), false); CatalogHandle catalogHandle = tester().getCurrentCatalogHandle(); tester().getQueryRunner().createCatalog(MOCK_CATALOG, createMockFactory(), ImmutableMap.of()); diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestRemoveEmptyUnionBranches.java b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestRemoveEmptyUnionBranches.java new file mode 100644 index 000000000000..716db43f902f --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/sql/planner/optimizations/TestRemoveEmptyUnionBranches.java @@ -0,0 +1,210 @@ +/* + * 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 io.trino.sql.planner.optimizations; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import io.trino.connector.MockConnectorColumnHandle; +import io.trino.connector.MockConnectorFactory; +import io.trino.connector.MockConnectorTableHandle; +import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.connector.ConnectorTableProperties; +import io.trino.spi.connector.ConnectorViewDefinition; +import io.trino.spi.connector.Constraint; +import io.trino.spi.connector.ConstraintApplicationResult; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.predicate.Domain; +import io.trino.spi.predicate.NullableValue; +import io.trino.spi.predicate.TupleDomain; +import io.trino.sql.planner.assertions.BasePlanTest; +import io.trino.testing.LocalQueryRunner; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableMap.toImmutableMap; +import static io.trino.spi.type.VarcharType.VARCHAR; +import static io.trino.sql.planner.assertions.PlanMatchPattern.anyTree; +import static io.trino.sql.planner.assertions.PlanMatchPattern.join; +import static io.trino.sql.planner.assertions.PlanMatchPattern.output; +import static io.trino.sql.planner.assertions.PlanMatchPattern.tableScan; +import static io.trino.sql.planner.plan.JoinNode.Type.INNER; +import static io.trino.testing.TestingSession.testSessionBuilder; +import static java.util.Collections.emptyList; + +public class TestRemoveEmptyUnionBranches + extends BasePlanTest +{ + private static final String CATALOG_NAME = "test"; + private static final String SCHEMA_NAME = "default"; + + private final Set tables = ImmutableSet.of("table_one", "table_two", "table_three"); + private final List columnNames = ImmutableList.of("a", "b", "c"); + private final String pushdownColumn = "c"; + private final Map columnHandles = columnNames.stream() + .collect(toImmutableMap(Function.identity(), name -> new MockConnectorColumnHandle(name, VARCHAR))); + + private final Map views = + ImmutableMap.of( + new SchemaTableName(SCHEMA_NAME, "view_of_union"), + new ConnectorViewDefinition( + "SELECT " + + " t1.a, t1.b, t1.c " + + "FROM " + + " table_one t1 " + + "WHERE " + + " t1.c = 'X' " + + "UNION ALL " + + "SELECT " + + " t2.a, t2.b, t2.c " + + "FROM " + + " table_two t2 " + + "WHERE " + + " t2.c = 'Y'", + Optional.of(CATALOG_NAME), + Optional.of(SCHEMA_NAME), + columnNames.stream() + .map(name -> new ConnectorViewDefinition.ViewColumn(name, VARCHAR.getTypeId(), Optional.empty())) + .collect(toImmutableList()), + Optional.empty(), + Optional.empty(), + true)); + + @Override + protected LocalQueryRunner createLocalQueryRunner() + { + LocalQueryRunner queryRunner = LocalQueryRunner.create( + testSessionBuilder() + .setCatalog(CATALOG_NAME) + .setSchema(SCHEMA_NAME) + .build()); + queryRunner.createCatalog( + CATALOG_NAME, + createConnectorFactory(CATALOG_NAME), + ImmutableMap.of()); + return queryRunner; + } + + private MockConnectorFactory createConnectorFactory(String catalogHandle) + { + return MockConnectorFactory.builder() + .withGetTableHandle((session, tableName) -> { + if (tableName.getSchemaName().equals(SCHEMA_NAME) && tables.contains(tableName.getTableName())) { + return new MockConnectorTableHandle(tableName); + } + return null; + }) + .withGetViews((session, schemaName) -> { + return views; + }) + .withGetColumns(schemaTableName -> columnNames.stream() + .map(name -> new ColumnMetadata(name, VARCHAR)) + .collect(toImmutableList())) + .withGetTableProperties((session, handle) -> { + MockConnectorTableHandle table = (MockConnectorTableHandle) handle; + return new ConnectorTableProperties(table.getConstraint(), Optional.empty(), Optional.empty(), Optional.empty(), emptyList()); + }) + .withApplyFilter(applyFilter()) + .withName(catalogHandle) + .build(); + } + + private MockConnectorFactory.ApplyFilter applyFilter() + { + return (session, table, constraint) -> { + if (table instanceof MockConnectorTableHandle handle) { + SchemaTableName schemaTable = handle.getTableName(); + if (schemaTable.getSchemaName().equals(SCHEMA_NAME) && tables.contains(schemaTable.getTableName())) { + Predicate shouldPushdown = columnHandle -> ((MockConnectorColumnHandle) columnHandle).getName().equals(pushdownColumn); + TupleDomain oldDomain = handle.getConstraint(); + TupleDomain newDomain = oldDomain.intersect(constraint.getSummary() + .filter((columnHandle, domain) -> shouldPushdown.test(columnHandle))); + + // Check if predicate and constraint lead to Tupledomain.none(). + boolean nonePredicateOnPushdownColumn = discoveredNonePredicateOnPushdownColumn(newDomain, constraint); + if (nonePredicateOnPushdownColumn) { + return Optional.of( + new ConstraintApplicationResult<>( + new MockConnectorTableHandle(handle.getTableName(), TupleDomain.none(), Optional.empty()), + constraint.getSummary() + .filter((ch, domain) -> !shouldPushdown.test(ch)), + false)); + } + + if (oldDomain.equals(newDomain)) { + return Optional.empty(); + } + + return Optional.of( + new ConstraintApplicationResult<>( + new MockConnectorTableHandle(handle.getTableName(), newDomain, Optional.empty()), + constraint.getSummary() + .filter((columnHandle, domain) -> !shouldPushdown.test(columnHandle)), + false)); + } + } + + return Optional.empty(); + }; + } + + // This method tries to detect whether we encountered a domain and a predicate that yields no values - and hence + // results in a "none" domain effectively. + private boolean discoveredNonePredicateOnPushdownColumn(TupleDomain domain, Constraint constraint) + { + if (domain.isNone() || constraint.predicate().isEmpty()) { + // We're not discovering a new "none" domain if the domain is already none OR + // predicate isn't present + return false; + } + + Domain pushdownColumnDomain = domain.getDomains().get().get(columnHandles.get(pushdownColumn)); + Optional>> predicate = constraint.predicate(); + + if (pushdownColumnDomain != null && pushdownColumnDomain.isNullableDiscreteSet()) { + Domain.DiscreteSet discreteSet = pushdownColumnDomain.getNullableDiscreteSet(); + if (discreteSet != null) { + List nullableValues = discreteSet.getNonNullValues().stream() + .map(object -> NullableValue.of(VARCHAR, object)) + .collect(toImmutableList()); + ColumnHandle columnHandle = new MockConnectorColumnHandle("c", VARCHAR); + + return nullableValues.stream() + .allMatch(value -> !predicate.get().test(ImmutableMap.of(columnHandle, value))); + } + } + + return false; + } + + @Test + public void testRemoveUnionBranches() + { + assertPlan("SELECT v1.a FROM view_of_union v1 JOIN table_three t3 ON v1.a = t3.a WHERE substring(v1.c, 1, 10) = 'Y' ", + output( + join(INNER, builder -> builder + .equiCriteria("symbol_a", "symbol_a2") + .left(anyTree(tableScan("table_two", ImmutableMap.of("symbol_a", "a", "symbol_c", "c")))) + .right(anyTree(tableScan("table_three", ImmutableMap.of("symbol_a2", "a")))) + .build()))); + } +} diff --git a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java index 8515ecec3301..74c42ca9dd51 100644 --- a/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java +++ b/core/trino-main/src/test/java/io/trino/sql/planner/planprinter/TestJsonRepresentation.java @@ -101,7 +101,7 @@ public void testDistributedJsonPlan() ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 90, 0, 0)), ImmutableList.of(new JsonRenderedNode( - "147", + "149", "LocalExchange", ImmutableMap.of( "partitioning", "SINGLE", @@ -144,7 +144,7 @@ public void testLogicalJsonPlan() ImmutableList.of(), ImmutableList.of(new PlanNodeStatsAndCostSummary(10, 90, 90, 0, 0)), ImmutableList.of(new JsonRenderedNode( - "147", + "149", "LocalExchange", ImmutableMap.of( "partitioning", "SINGLE", diff --git a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java index e43ea3234444..18864c494069 100644 --- a/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java +++ b/plugin/trino-hive/src/test/java/io/trino/plugin/hive/optimizer/TestConnectorPushdownRulesWithHive.java @@ -210,7 +210,7 @@ public void testPredicatePushdown() String tableName = "predicate_test"; tester().getQueryRunner().execute(format("CREATE TABLE %s (a, b) AS SELECT 5, 6", tableName)); - PushPredicateIntoTableScan pushPredicateIntoTableScan = new PushPredicateIntoTableScan(tester().getPlannerContext(), tester().getTypeAnalyzer()); + PushPredicateIntoTableScan pushPredicateIntoTableScan = new PushPredicateIntoTableScan(tester().getPlannerContext(), tester().getTypeAnalyzer(), false); HiveTableHandle hiveTable = new HiveTableHandle(SCHEMA_NAME, tableName, ImmutableMap.of(), ImmutableList.of(), ImmutableList.of(), Optional.empty()); TableHandle table = new TableHandle(catalogHandle, hiveTable, new HiveTransactionHandle(false)); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java index 814de281f761..2c4c042a14ae 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/optimizer/TestConnectorPushdownRulesWithIceberg.java @@ -227,7 +227,7 @@ public void testPredicatePushdown() tester().getQueryRunner().execute(format("CREATE TABLE %s (a, b) AS SELECT 5, 6", tableName)); Long snapshotId = (Long) tester().getQueryRunner().execute(format("SELECT snapshot_id FROM \"%s$snapshots\" LIMIT 1", tableName)).getOnlyValue(); - PushPredicateIntoTableScan pushPredicateIntoTableScan = new PushPredicateIntoTableScan(tester().getPlannerContext(), tester().getTypeAnalyzer()); + PushPredicateIntoTableScan pushPredicateIntoTableScan = new PushPredicateIntoTableScan(tester().getPlannerContext(), tester().getTypeAnalyzer(), false); IcebergTableHandle icebergTable = new IcebergTableHandle( SCHEMA_NAME, diff --git a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java index 414df7fbc3c8..2bc0a4a77a13 100644 --- a/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java +++ b/testing/trino-tests/src/test/java/io/trino/execution/TestEventListenerBasic.java @@ -1172,7 +1172,7 @@ public void testAnonymizedJsonPlan() ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( - "171", + "173", "LocalExchange", ImmutableMap.of( "partitioning", "[connectorHandleType = SystemPartitioningHandle, partitioning = SINGLE, function = SINGLE]", @@ -1183,7 +1183,7 @@ public void testAnonymizedJsonPlan() ImmutableList.of(), ImmutableList.of(), ImmutableList.of(new JsonRenderedNode( - "138", + "140", "RemoteSource", ImmutableMap.of("sourceFragmentIds", "[1]"), ImmutableList.of(typedSymbol("symbol_1", "double")), @@ -1191,7 +1191,7 @@ public void testAnonymizedJsonPlan() ImmutableList.of(), ImmutableList.of()))))))), "1", new JsonRenderedNode( - "137", + "139", "LimitPartial", ImmutableMap.of( "count", "10",