use of io.trino.sql.planner.plan.JoinNode.Type in project trino by trinodb.
the class DecorrelateUnnest method apply.
@Override
public Result apply(CorrelatedJoinNode correlatedJoinNode, Captures captures, Context context) {
// determine shape of the subquery
PlanNode searchRoot = correlatedJoinNode.getSubquery();
// 1. find EnforceSingleRowNode in the subquery
Optional<EnforceSingleRowNode> enforceSingleRow = PlanNodeSearcher.searchFrom(searchRoot, context.getLookup()).where(EnforceSingleRowNode.class::isInstance).recurseOnlyWhen(planNode -> false).findFirst();
if (enforceSingleRow.isPresent()) {
searchRoot = enforceSingleRow.get().getSource();
}
// 2. find correlated UnnestNode in the subquery
Optional<UnnestNode> subqueryUnnest = PlanNodeSearcher.searchFrom(searchRoot, context.getLookup()).where(node -> isSupportedUnnest(node, correlatedJoinNode.getCorrelation(), context.getLookup())).recurseOnlyWhen(node -> node instanceof ProjectNode || (node instanceof LimitNode && ((LimitNode) node).getCount() > 0) || (node instanceof TopNNode && ((TopNNode) node).getCount() > 0)).findFirst();
if (subqueryUnnest.isEmpty()) {
return Result.empty();
}
UnnestNode unnestNode = subqueryUnnest.get();
// assign unique id to input rows
Symbol uniqueSymbol = context.getSymbolAllocator().newSymbol("unique", BIGINT);
PlanNode input = new AssignUniqueId(context.getIdAllocator().getNextId(), correlatedJoinNode.getInput(), uniqueSymbol);
// pre-project unnest symbols if they were pre-projected in subquery
// The correlated UnnestNode either unnests correlation symbols directly, or unnests symbols produced by a projection that uses only correlation symbols.
// Here, any underlying projection that was a source of the correlated UnnestNode, is appended as a source of the rewritten UnnestNode.
// If the projection is not necessary for UnnestNode (i.e. it does not produce any unnest symbols), it should be pruned afterwards.
PlanNode unnestSource = context.getLookup().resolve(unnestNode.getSource());
if (unnestSource instanceof ProjectNode) {
ProjectNode sourceProjection = (ProjectNode) unnestSource;
input = new ProjectNode(sourceProjection.getId(), input, Assignments.builder().putIdentities(input.getOutputSymbols()).putAll(sourceProjection.getAssignments()).build());
}
// determine join type for rewritten UnnestNode
Type unnestJoinType = LEFT;
if (enforceSingleRow.isEmpty() && correlatedJoinNode.getType() == CorrelatedJoinNode.Type.INNER && unnestNode.getJoinType() == INNER) {
unnestJoinType = INNER;
}
// make sure that the rewritten node is with ordinality, which might be necessary to restore inner unnest semantics after rewrite.
Symbol ordinalitySymbol = unnestNode.getOrdinalitySymbol().orElseGet(() -> context.getSymbolAllocator().newSymbol("ordinality", BIGINT));
// rewrite correlated join to UnnestNode.
UnnestNode rewrittenUnnest = new UnnestNode(context.getIdAllocator().getNextId(), input, input.getOutputSymbols(), unnestNode.getMappings(), Optional.of(ordinalitySymbol), unnestJoinType, Optional.empty());
// restore all nodes from the subquery
PlanNode rewrittenPlan = Rewriter.rewriteNodeSequence(correlatedJoinNode.getSubquery(), input.getOutputSymbols(), ordinalitySymbol, uniqueSymbol, rewrittenUnnest, context.getSession(), metadata, context.getLookup(), context.getIdAllocator(), context.getSymbolAllocator());
// between unnested rows and synthetic rows added by left unnest.
if (unnestNode.getJoinType() == INNER && rewrittenUnnest.getJoinType() == LEFT) {
Assignments.Builder assignments = Assignments.builder().putIdentities(correlatedJoinNode.getInput().getOutputSymbols());
for (Symbol subquerySymbol : correlatedJoinNode.getSubquery().getOutputSymbols()) {
assignments.put(subquerySymbol, new IfExpression(new IsNullPredicate(ordinalitySymbol.toSymbolReference()), new Cast(new NullLiteral(), toSqlType(context.getSymbolAllocator().getTypes().get(subquerySymbol))), subquerySymbol.toSymbolReference()));
}
rewrittenPlan = new ProjectNode(context.getIdAllocator().getNextId(), rewrittenPlan, assignments.build());
}
// restrict outputs
return Result.ofPlanNode(restrictOutputs(context.getIdAllocator(), rewrittenPlan, ImmutableSet.copyOf(correlatedJoinNode.getOutputSymbols())).orElse(rewrittenPlan));
}
use of io.trino.sql.planner.plan.JoinNode.Type in project trino by trinodb.
the class TestDetermineJoinDistributionType method testReplicatesWhenSourceIsSmall.
@Test
public void testReplicatesWhenSourceIsSmall() {
// variable width so that average row size is respected
VarcharType symbolType = createUnboundedVarcharType();
int aRows = 10_000;
int bRows = 10;
// output size exceeds AUTOMATIC_RESTRICTED limit
PlanNodeStatsEstimate aStatsEstimate = PlanNodeStatsEstimate.builder().setOutputRowCount(aRows).addSymbolStatistics(ImmutableMap.of(new Symbol("A1"), new SymbolStatsEstimate(0, 100, 0, 640000d * 10000, 10))).build();
// output size exceeds AUTOMATIC_RESTRICTED limit
PlanNodeStatsEstimate bStatsEstimate = PlanNodeStatsEstimate.builder().setOutputRowCount(bRows).addSymbolStatistics(ImmutableMap.of(new Symbol("B1"), new SymbolStatsEstimate(0, 100, 0, 640000d * 10000, 10))).build();
// output size does not exceed AUTOMATIC_RESTRICTED limit
PlanNodeStatsEstimate bSourceStatsEstimate = PlanNodeStatsEstimate.builder().setOutputRowCount(bRows).addSymbolStatistics(ImmutableMap.of(new Symbol("B1"), new SymbolStatsEstimate(0, 100, 0, 64, 10))).build();
// immediate join sources exceeds AUTOMATIC_RESTRICTED limit but build tables are small
// therefore replicated distribution type is chosen
assertDetermineJoinDistributionType().setSystemProperty(JOIN_DISTRIBUTION_TYPE, JoinDistributionType.AUTOMATIC.name()).setSystemProperty(JOIN_MAX_BROADCAST_TABLE_SIZE, "100MB").overrideStats("valuesA", aStatsEstimate).overrideStats("filterB", bStatsEstimate).overrideStats("valuesB", bSourceStatsEstimate).on(p -> {
Symbol a1 = p.symbol("A1", symbolType);
Symbol b1 = p.symbol("B1", symbolType);
return p.join(INNER, p.values(new PlanNodeId("valuesA"), aRows, a1), p.filter(new PlanNodeId("filterB"), TRUE_LITERAL, p.values(new PlanNodeId("valuesB"), bRows, b1)), ImmutableList.of(new JoinNode.EquiJoinClause(a1, b1)), ImmutableList.of(a1), ImmutableList.of(b1), Optional.empty());
}).matches(join(INNER, ImmutableList.of(equiJoinClause("A1", "B1")), Optional.empty(), Optional.of(REPLICATED), values(ImmutableMap.of("A1", 0)), filter("true", values(ImmutableMap.of("B1", 0)))));
// same but with join sides reversed
assertDetermineJoinDistributionType().setSystemProperty(JOIN_DISTRIBUTION_TYPE, JoinDistributionType.AUTOMATIC.name()).setSystemProperty(JOIN_MAX_BROADCAST_TABLE_SIZE, "100MB").overrideStats("valuesA", aStatsEstimate).overrideStats("filterB", bStatsEstimate).overrideStats("valuesB", bSourceStatsEstimate).on(p -> {
Symbol a1 = p.symbol("A1", symbolType);
Symbol b1 = p.symbol("B1", symbolType);
return p.join(INNER, p.filter(new PlanNodeId("filterB"), TRUE_LITERAL, p.values(new PlanNodeId("valuesB"), bRows, b1)), p.values(new PlanNodeId("valuesA"), aRows, a1), ImmutableList.of(new JoinNode.EquiJoinClause(b1, a1)), ImmutableList.of(b1), ImmutableList.of(a1), Optional.empty());
}).matches(join(INNER, ImmutableList.of(equiJoinClause("A1", "B1")), Optional.empty(), Optional.of(REPLICATED), values(ImmutableMap.of("A1", 0)), filter("true", values(ImmutableMap.of("B1", 0)))));
// only probe side (with small tables) source stats are available, join sides should be flipped
assertDetermineJoinDistributionType().setSystemProperty(JOIN_DISTRIBUTION_TYPE, JoinDistributionType.AUTOMATIC.name()).setSystemProperty(JOIN_MAX_BROADCAST_TABLE_SIZE, "100MB").overrideStats("valuesA", PlanNodeStatsEstimate.unknown()).overrideStats("filterB", PlanNodeStatsEstimate.unknown()).overrideStats("valuesB", bSourceStatsEstimate).on(p -> {
Symbol a1 = p.symbol("A1", symbolType);
Symbol b1 = p.symbol("B1", symbolType);
return p.join(LEFT, p.filter(new PlanNodeId("filterB"), TRUE_LITERAL, p.values(new PlanNodeId("valuesB"), bRows, b1)), p.values(new PlanNodeId("valuesA"), aRows, a1), ImmutableList.of(new JoinNode.EquiJoinClause(b1, a1)), ImmutableList.of(b1), ImmutableList.of(a1), Optional.empty());
}).matches(join(RIGHT, ImmutableList.of(equiJoinClause("A1", "B1")), Optional.empty(), Optional.of(PARTITIONED), values(ImmutableMap.of("A1", 0)), filter("true", values(ImmutableMap.of("B1", 0)))));
}
Aggregations