Search in sources :

Example 1 with JoinApplicationResult

use of io.trino.spi.connector.JoinApplicationResult in project trino by trinodb.

the class PushJoinIntoTableScan method apply.

@Override
public Result apply(JoinNode joinNode, Captures captures, Context context) {
    if (joinNode.isCrossJoin()) {
        return Result.empty();
    }
    TableScanNode left = captures.get(LEFT_TABLE_SCAN);
    TableScanNode right = captures.get(RIGHT_TABLE_SCAN);
    verify(!left.isUpdateTarget() && !right.isUpdateTarget(), "Unexpected Join over for-update table scan");
    Expression effectiveFilter = getEffectiveFilter(joinNode);
    FilterSplitResult filterSplitResult = splitFilter(effectiveFilter, left.getOutputSymbols(), right.getOutputSymbols(), context);
    if (!filterSplitResult.getRemainingFilter().equals(BooleanLiteral.TRUE_LITERAL)) {
        // TODO add extra filter node above join
        return Result.empty();
    }
    if (left.getEnforcedConstraint().isNone() || right.getEnforcedConstraint().isNone()) {
        // enforced constraint harder below.
        return Result.empty();
    }
    Map<String, ColumnHandle> leftAssignments = left.getAssignments().entrySet().stream().collect(toImmutableMap(entry -> entry.getKey().getName(), Map.Entry::getValue));
    Map<String, ColumnHandle> rightAssignments = right.getAssignments().entrySet().stream().collect(toImmutableMap(entry -> entry.getKey().getName(), Map.Entry::getValue));
    /*
         * We are (lazily) computing estimated statistics for join node and left and right table
         * and passing those to connector via applyJoin.
         *
         * There are a couple reasons for this approach:
         * - the engine knows how to estimate join and connector may not
         * - the engine may have cached stats for the table scans (within context.getStatsProvider()), so can be able to provide information more inexpensively
         * - in the future, the engine may be able to provide stats for table scan even in case when connector no longer can (see https://github.com/trinodb/trino/issues/6998)
         * - the pushdown feasibility assessment logic may be different (or configured differently) for different connectors/catalogs.
         */
    JoinStatistics joinStatistics = getJoinStatistics(joinNode, left, right, context);
    Optional<JoinApplicationResult<TableHandle>> joinApplicationResult = metadata.applyJoin(context.getSession(), getJoinType(joinNode), left.getTable(), right.getTable(), filterSplitResult.getPushableConditions(), // TODO we could pass only subset of assignments here, those which are needed to resolve filterSplitResult.getPushableConditions
    leftAssignments, rightAssignments, joinStatistics);
    if (joinApplicationResult.isEmpty()) {
        return Result.empty();
    }
    TableHandle handle = joinApplicationResult.get().getTableHandle();
    Map<ColumnHandle, ColumnHandle> leftColumnHandlesMapping = joinApplicationResult.get().getLeftColumnHandles();
    Map<ColumnHandle, ColumnHandle> rightColumnHandlesMapping = joinApplicationResult.get().getRightColumnHandles();
    ImmutableMap.Builder<Symbol, ColumnHandle> assignmentsBuilder = ImmutableMap.builder();
    assignmentsBuilder.putAll(left.getAssignments().entrySet().stream().collect(toImmutableMap(Map.Entry::getKey, entry -> leftColumnHandlesMapping.get(entry.getValue()))));
    assignmentsBuilder.putAll(right.getAssignments().entrySet().stream().collect(toImmutableMap(Map.Entry::getKey, entry -> rightColumnHandlesMapping.get(entry.getValue()))));
    Map<Symbol, ColumnHandle> assignments = assignmentsBuilder.buildOrThrow();
    // convert enforced constraint
    JoinNode.Type joinType = joinNode.getType();
    TupleDomain<ColumnHandle> leftConstraint = deriveConstraint(left.getEnforcedConstraint(), leftColumnHandlesMapping, joinType == RIGHT || joinType == FULL);
    TupleDomain<ColumnHandle> rightConstraint = deriveConstraint(right.getEnforcedConstraint(), rightColumnHandlesMapping, joinType == LEFT || joinType == FULL);
    TupleDomain<ColumnHandle> newEnforcedConstraint = TupleDomain.withColumnDomains(ImmutableMap.<ColumnHandle, Domain>builder().putAll(leftConstraint.getDomains().orElseThrow()).putAll(rightConstraint.getDomains().orElseThrow()).buildOrThrow());
    return Result.ofPlanNode(new ProjectNode(context.getIdAllocator().getNextId(), new TableScanNode(joinNode.getId(), handle, ImmutableList.copyOf(assignments.keySet()), assignments, newEnforcedConstraint, deriveTableStatisticsForPushdown(context.getStatsProvider(), context.getSession(), joinApplicationResult.get().isPrecalculateStatistics(), joinNode), false, Optional.empty()), Assignments.identity(joinNode.getOutputSymbols())));
}
Also used : PlanNode(io.trino.sql.planner.plan.PlanNode) LEFT(io.trino.sql.planner.plan.JoinNode.Type.LEFT) BooleanLiteral(io.trino.sql.tree.BooleanLiteral) Map(java.util.Map) JoinNode(io.trino.sql.planner.plan.JoinNode) TableScanNode(io.trino.sql.planner.plan.TableScanNode) PlanNodeStatsEstimate(io.trino.cost.PlanNodeStatsEstimate) Rules.deriveTableStatisticsForPushdown(io.trino.sql.planner.iterative.rule.Rules.deriveTableStatisticsForPushdown) ImmutableSet(com.google.common.collect.ImmutableSet) ImmutableMap(com.google.common.collect.ImmutableMap) Domain(io.trino.spi.predicate.Domain) ImmutableList.toImmutableList(com.google.common.collect.ImmutableList.toImmutableList) Assignments(io.trino.sql.planner.plan.Assignments) Set(java.util.Set) BasicRelationStatistics(io.trino.spi.connector.BasicRelationStatistics) Patterns.tableScan(io.trino.sql.planner.plan.Patterns.tableScan) ComparisonExpression(io.trino.sql.tree.ComparisonExpression) List(java.util.List) ImmutableMap.toImmutableMap(com.google.common.collect.ImmutableMap.toImmutableMap) Pattern(io.trino.matching.Pattern) SystemSessionProperties.isAllowPushdownIntoConnectors(io.trino.SystemSessionProperties.isAllowPushdownIntoConnectors) RIGHT(io.trino.sql.planner.plan.JoinNode.Type.RIGHT) SymbolReference(io.trino.sql.tree.SymbolReference) Optional(java.util.Optional) Expression(io.trino.sql.tree.Expression) ExpressionUtils.extractConjuncts(io.trino.sql.ExpressionUtils.extractConjuncts) Session(io.trino.Session) JoinCondition(io.trino.spi.connector.JoinCondition) Patterns(io.trino.sql.planner.plan.Patterns) Variable(io.trino.spi.expression.Variable) Capture.newCapture(io.trino.matching.Capture.newCapture) JoinStatistics(io.trino.spi.connector.JoinStatistics) ImmutableList(com.google.common.collect.ImmutableList) JoinType(io.trino.spi.connector.JoinType) Verify.verify(com.google.common.base.Verify.verify) Objects.requireNonNull(java.util.Objects.requireNonNull) ColumnHandle(io.trino.spi.connector.ColumnHandle) Rule(io.trino.sql.planner.iterative.Rule) Join.left(io.trino.sql.planner.plan.Patterns.Join.left) ProjectNode(io.trino.sql.planner.plan.ProjectNode) ExpressionUtils(io.trino.sql.ExpressionUtils) Symbol(io.trino.sql.planner.Symbol) FULL(io.trino.sql.planner.plan.JoinNode.Type.FULL) TupleDomain(io.trino.spi.predicate.TupleDomain) Capture(io.trino.matching.Capture) JoinApplicationResult(io.trino.spi.connector.JoinApplicationResult) TableHandle(io.trino.metadata.TableHandle) Join.right(io.trino.sql.planner.plan.Patterns.Join.right) ExpressionUtils.and(io.trino.sql.ExpressionUtils.and) Double.isNaN(java.lang.Double.isNaN) Captures(io.trino.matching.Captures) Metadata(io.trino.metadata.Metadata) TypeProvider(io.trino.sql.planner.TypeProvider) Domain.onlyNull(io.trino.spi.predicate.Domain.onlyNull) ColumnHandle(io.trino.spi.connector.ColumnHandle) Symbol(io.trino.sql.planner.Symbol) JoinNode(io.trino.sql.planner.plan.JoinNode) JoinApplicationResult(io.trino.spi.connector.JoinApplicationResult) ImmutableMap(com.google.common.collect.ImmutableMap) ImmutableMap.toImmutableMap(com.google.common.collect.ImmutableMap.toImmutableMap) JoinStatistics(io.trino.spi.connector.JoinStatistics) TableScanNode(io.trino.sql.planner.plan.TableScanNode) ComparisonExpression(io.trino.sql.tree.ComparisonExpression) Expression(io.trino.sql.tree.Expression) TableHandle(io.trino.metadata.TableHandle) ProjectNode(io.trino.sql.planner.plan.ProjectNode) Domain(io.trino.spi.predicate.Domain) TupleDomain(io.trino.spi.predicate.TupleDomain) Map(java.util.Map) ImmutableMap(com.google.common.collect.ImmutableMap) ImmutableMap.toImmutableMap(com.google.common.collect.ImmutableMap.toImmutableMap)

Example 2 with JoinApplicationResult

use of io.trino.spi.connector.JoinApplicationResult in project trino by trinodb.

the class TestPushJoinIntoTableScan method testPushJoinIntoTableRequiresFullColumnHandleMappingInResult.

@Test
public void testPushJoinIntoTableRequiresFullColumnHandleMappingInResult() {
    try (RuleTester ruleTester = defaultRuleTester()) {
        MockConnectorFactory connectorFactory = createMockConnectorFactory((session, applyJoinType, left, right, joinConditions, leftAssignments, rightAssignments) -> Optional.of(new JoinApplicationResult<>(JOIN_CONNECTOR_TABLE_HANDLE, ImmutableMap.of(COLUMN_A1_HANDLE, JOIN_COLUMN_A1_HANDLE, COLUMN_A2_HANDLE, JOIN_COLUMN_A2_HANDLE), // mapping for COLUMN_B1_HANDLE is missing
        ImmutableMap.of(), false)));
        ruleTester.getQueryRunner().createCatalog(MOCK_CATALOG, connectorFactory, ImmutableMap.of());
        assertThatThrownBy(() -> {
            ruleTester.assertThat(new PushJoinIntoTableScan(ruleTester.getMetadata())).on(p -> {
                Symbol columnA1Symbol = p.symbol(COLUMN_A1);
                Symbol columnA2Symbol = p.symbol(COLUMN_A2);
                Symbol columnB1Symbol = p.symbol(COLUMN_B1);
                TupleDomain<ColumnHandle> leftContraint = TupleDomain.fromFixedValues(ImmutableMap.of(COLUMN_A2_HANDLE, NullableValue.of(BIGINT, 44L)));
                TupleDomain<ColumnHandle> rightConstraint = TupleDomain.fromFixedValues(ImmutableMap.of(COLUMN_B1_HANDLE, NullableValue.of(BIGINT, 45L)));
                TableScanNode left = p.tableScan(TABLE_A_HANDLE, ImmutableList.of(columnA1Symbol, columnA2Symbol), ImmutableMap.of(columnA1Symbol, COLUMN_A1_HANDLE, columnA2Symbol, COLUMN_A2_HANDLE), leftContraint);
                TableScanNode right = p.tableScan(TABLE_B_HANDLE, ImmutableList.of(columnB1Symbol), ImmutableMap.of(columnB1Symbol, COLUMN_B1_HANDLE), rightConstraint);
                return p.join(INNER, left, right, new JoinNode.EquiJoinClause(columnA1Symbol, columnB1Symbol));
            }).withSession(MOCK_SESSION).matches(anyTree());
        }).isInstanceOf(IllegalStateException.class).hasMessageContaining("Column handle mappings do not match old column handles");
    }
}
Also used : MockConnectorFactory(io.trino.connector.MockConnectorFactory) ColumnHandle(io.trino.spi.connector.ColumnHandle) MockConnectorColumnHandle(io.trino.connector.MockConnectorColumnHandle) TableScanNode(io.trino.sql.planner.plan.TableScanNode) RuleTester.defaultRuleTester(io.trino.sql.planner.iterative.rule.test.RuleTester.defaultRuleTester) RuleTester(io.trino.sql.planner.iterative.rule.test.RuleTester) Symbol(io.trino.sql.planner.Symbol) JoinNode(io.trino.sql.planner.plan.JoinNode) JoinApplicationResult(io.trino.spi.connector.JoinApplicationResult) Test(org.testng.annotations.Test)

Example 3 with JoinApplicationResult

use of io.trino.spi.connector.JoinApplicationResult in project trino by trinodb.

the class TestPushJoinIntoTableScan method testPushJoinIntoTableScanPreservesEnforcedConstraint.

@Test(dataProvider = "testPushJoinIntoTableScanPreservesEnforcedConstraintParams")
public void testPushJoinIntoTableScanPreservesEnforcedConstraint(JoinNode.Type joinType, TupleDomain<ColumnHandle> leftConstraint, TupleDomain<ColumnHandle> rightConstraint, TupleDomain<Predicate<ColumnHandle>> expectedConstraint) {
    try (RuleTester ruleTester = defaultRuleTester()) {
        MockConnectorFactory connectorFactory = createMockConnectorFactory((session, applyJoinType, left, right, joinConditions, leftAssignments, rightAssignments) -> Optional.of(new JoinApplicationResult<>(JOIN_CONNECTOR_TABLE_HANDLE, JOIN_TABLE_A_COLUMN_MAPPING, JOIN_TABLE_B_COLUMN_MAPPING, false)));
        ruleTester.getQueryRunner().createCatalog(MOCK_CATALOG, connectorFactory, ImmutableMap.of());
        ruleTester.assertThat(new PushJoinIntoTableScan(ruleTester.getMetadata())).on(p -> {
            Symbol columnA1Symbol = p.symbol(COLUMN_A1);
            Symbol columnA2Symbol = p.symbol(COLUMN_A2);
            Symbol columnB1Symbol = p.symbol(COLUMN_B1);
            TableScanNode left = p.tableScan(TABLE_A_HANDLE, ImmutableList.of(columnA1Symbol, columnA2Symbol), ImmutableMap.of(columnA1Symbol, COLUMN_A1_HANDLE, columnA2Symbol, COLUMN_A2_HANDLE), leftConstraint);
            TableScanNode right = p.tableScan(TABLE_B_HANDLE, ImmutableList.of(columnB1Symbol), ImmutableMap.of(columnB1Symbol, COLUMN_B1_HANDLE), rightConstraint);
            return p.join(joinType, left, right, new JoinNode.EquiJoinClause(columnA1Symbol, columnB1Symbol));
        }).withSession(MOCK_SESSION).matches(project(tableScan(tableHandle -> JOIN_PUSHDOWN_SCHEMA_TABLE_NAME.equals(((MockConnectorTableHandle) tableHandle).getTableName()), expectedConstraint, ImmutableMap.of())));
    }
}
Also used : MockConnectorFactory(io.trino.connector.MockConnectorFactory) TableScanNode(io.trino.sql.planner.plan.TableScanNode) RuleTester.defaultRuleTester(io.trino.sql.planner.iterative.rule.test.RuleTester.defaultRuleTester) RuleTester(io.trino.sql.planner.iterative.rule.test.RuleTester) Symbol(io.trino.sql.planner.Symbol) JoinNode(io.trino.sql.planner.plan.JoinNode) MockConnectorTableHandle(io.trino.connector.MockConnectorTableHandle) JoinApplicationResult(io.trino.spi.connector.JoinApplicationResult) Test(org.testng.annotations.Test)

Example 4 with JoinApplicationResult

use of io.trino.spi.connector.JoinApplicationResult in project trino by trinodb.

the class MetadataManager method applyJoin.

@Override
public Optional<JoinApplicationResult<TableHandle>> applyJoin(Session session, JoinType joinType, TableHandle left, TableHandle right, List<JoinCondition> joinConditions, Map<String, ColumnHandle> leftAssignments, Map<String, ColumnHandle> rightAssignments, JoinStatistics statistics) {
    if (!right.getCatalogName().equals(left.getCatalogName())) {
        // Exact comparison is fine as catalog name here is passed from CatalogMetadata and is normalized to lowercase
        return Optional.empty();
    }
    CatalogName catalogName = left.getCatalogName();
    ConnectorTransactionHandle transaction = left.getTransaction();
    ConnectorMetadata metadata = getMetadata(session, catalogName);
    ConnectorSession connectorSession = session.toConnectorSession(catalogName);
    Optional<JoinApplicationResult<ConnectorTableHandle>> connectorResult = metadata.applyJoin(connectorSession, joinType, left.getConnectorHandle(), right.getConnectorHandle(), joinConditions, leftAssignments, rightAssignments, statistics);
    return connectorResult.map(result -> {
        Set<ColumnHandle> leftColumnHandles = ImmutableSet.copyOf(getColumnHandles(session, left).values());
        Set<ColumnHandle> rightColumnHandles = ImmutableSet.copyOf(getColumnHandles(session, right).values());
        Set<ColumnHandle> leftColumnHandlesMappingKeys = result.getLeftColumnHandles().keySet();
        Set<ColumnHandle> rightColumnHandlesMappingKeys = result.getRightColumnHandles().keySet();
        if (leftColumnHandlesMappingKeys.size() != leftColumnHandles.size() || rightColumnHandlesMappingKeys.size() != rightColumnHandles.size() || !leftColumnHandlesMappingKeys.containsAll(leftColumnHandles) || !rightColumnHandlesMappingKeys.containsAll(rightColumnHandles)) {
            throw new IllegalStateException(format("Column handle mappings do not match old column handles: left=%s; right=%s; newLeft=%s, newRight=%s", leftColumnHandles, rightColumnHandles, leftColumnHandlesMappingKeys, rightColumnHandlesMappingKeys));
        }
        return new JoinApplicationResult<>(new TableHandle(catalogName, result.getTableHandle(), transaction), result.getLeftColumnHandles(), result.getRightColumnHandles(), result.isPrecalculateStatistics());
    });
}
Also used : ColumnHandle(io.trino.spi.connector.ColumnHandle) ConnectorTransactionHandle(io.trino.spi.connector.ConnectorTransactionHandle) CatalogName(io.trino.connector.CatalogName) ConnectorSession(io.trino.spi.connector.ConnectorSession) JoinApplicationResult(io.trino.spi.connector.JoinApplicationResult) ConnectorOutputTableHandle(io.trino.spi.connector.ConnectorOutputTableHandle) ConnectorTableHandle(io.trino.spi.connector.ConnectorTableHandle) ConnectorInsertTableHandle(io.trino.spi.connector.ConnectorInsertTableHandle) ConnectorMetadata(io.trino.spi.connector.ConnectorMetadata)

Aggregations

JoinApplicationResult (io.trino.spi.connector.JoinApplicationResult)4 ColumnHandle (io.trino.spi.connector.ColumnHandle)3 Symbol (io.trino.sql.planner.Symbol)3 JoinNode (io.trino.sql.planner.plan.JoinNode)3 TableScanNode (io.trino.sql.planner.plan.TableScanNode)3 MockConnectorFactory (io.trino.connector.MockConnectorFactory)2 RuleTester (io.trino.sql.planner.iterative.rule.test.RuleTester)2 RuleTester.defaultRuleTester (io.trino.sql.planner.iterative.rule.test.RuleTester.defaultRuleTester)2 Test (org.testng.annotations.Test)2 Verify.verify (com.google.common.base.Verify.verify)1 ImmutableList (com.google.common.collect.ImmutableList)1 ImmutableList.toImmutableList (com.google.common.collect.ImmutableList.toImmutableList)1 ImmutableMap (com.google.common.collect.ImmutableMap)1 ImmutableMap.toImmutableMap (com.google.common.collect.ImmutableMap.toImmutableMap)1 ImmutableSet (com.google.common.collect.ImmutableSet)1 Session (io.trino.Session)1 SystemSessionProperties.isAllowPushdownIntoConnectors (io.trino.SystemSessionProperties.isAllowPushdownIntoConnectors)1 CatalogName (io.trino.connector.CatalogName)1 MockConnectorColumnHandle (io.trino.connector.MockConnectorColumnHandle)1 MockConnectorTableHandle (io.trino.connector.MockConnectorTableHandle)1