presto-main/src/main/java/io/prestosql/sql/planner/iterative/rule/PushPredicateIntoTableScan.java/PushPredicateIntoTableScan/pushFilterIntoTableScan
class PushPredicateIntoTableScan:    public static Optional<PlanNode> pushFilterIntoTableScan(
            TableScanNode node,
            Expression predicate,
            boolean pruneWithPredicateExpression,
            Session session,
            TypeProvider types,
            PlanNodeIdAllocator idAllocator,
            Metadata metadata,
            TypeAnalyzer typeAnalyzer,
            DomainTranslator domainTranslator)
    {
        // don't include non-deterministic predicates
        Expression deterministicPredicate = filterDeterministicConjuncts(metadata, predicate);
        Expression nonDeterministicPredicate = filterNonDeterministicConjuncts(metadata, predicate);

        DomainTranslator.ExtractionResult decomposedPredicate = DomainTranslator.fromPredicate(
                metadata,
                session,
                deterministicPredicate,
                types);

        TupleDomain<ColumnHandle> newDomain = decomposedPredicate.getTupleDomain()
                .transform(node.getAssignments()::get)
                .intersect(node.getEnforcedConstraint());

        Map<ColumnHandle, Symbol> assignments = ImmutableBiMap.copyOf(node.getAssignments()).inverse();

        Constraint constraint;
        // use evaluator only when there is some predicate which could not be translated into tuple domain
        if (pruneWithPredicateExpression && !TRUE_LITERAL.equals(decomposedPredicate.getRemainingExpression())) {
            LayoutConstraintEvaluator evaluator = new LayoutConstraintEvaluator(
                    metadata,
                    typeAnalyzer,
                    session,
                    types,
                    node.getAssignments(),
                    combineConjuncts(
                            metadata,
                            deterministicPredicate,
                            // Simplify the tuple domain to avoid creating an expression with too many nodes,
                            // which would be expensive to evaluate in the call to isCandidate below.
                            domainTranslator.toPredicate(newDomain.simplify().transform(assignments::get))));
            constraint = new Constraint(newDomain, evaluator::isCandidate);
        }
        else {
            // Currently, invoking the expression interpreter is very expensive.
            // TODO invoke the interpreter unconditionally when the interpreter becomes cheap enough.
            constraint = new Constraint(newDomain);
        }

        TableHandle newTable;
        TupleDomain<ColumnHandle> remainingFilter;
        if (!metadata.usesLegacyTableLayouts(session, node.getTable())) {
            // check if new domain is wider than domain already provided by table scan
            if (!constraint.predicate().isPresent() && newDomain.contains(node.getEnforcedConstraint())) {
                Expression resultingPredicate = createResultingPredicate(
                        metadata,
                        TRUE_LITERAL,
                        nonDeterministicPredicate,
                        decomposedPredicate.getRemainingExpression());

                if (!TRUE_LITERAL.equals(resultingPredicate)) {
                    return Optional.of(new FilterNode(idAllocator.getNextId(), node, resultingPredicate));
                }

                return Optional.of(node);
            }

            if (newDomain.isNone()) {
                // TODO: DomainTranslator.fromPredicate can infer that the expression is "false" in some cases (TupleDomain.none()).
                // This should move to another rule that simplifies the filter using that logic and then rely on RemoveTrivialFilters
                // to turn the subtree into a Values node
                return Optional.of(new ValuesNode(node.getId(), node.getOutputSymbols(), ImmutableList.of()));
            }

            Optional<ConstraintApplicationResult<TableHandle>> result = metadata.applyFilter(session, node.getTable(), constraint);

            if (!result.isPresent()) {
                return Optional.empty();
            }

            newTable = result.get().getHandle();

            if (metadata.getTableProperties(session, newTable).getPredicate().isNone()) {
                return Optional.of(new ValuesNode(node.getId(), node.getOutputSymbols(), ImmutableList.of()));
            }

            remainingFilter = result.get().getRemainingFilter();
        }
        else {
            Optional<TableLayoutResult> layout = metadata.getLayout(
                    session,
                    node.getTable(),
                    constraint,
                    Optional.of(node.getOutputSymbols().stream()
                            .map(node.getAssignments()::get)
                            .collect(toImmutableSet())));

            if (!layout.isPresent() || layout.get().getTableProperties().getPredicate().isNone()) {
                return Optional.of(new ValuesNode(node.getId(), node.getOutputSymbols(), ImmutableList.of()));
            }

            newTable = layout.get().getNewTableHandle();
            remainingFilter = layout.get().getUnenforcedConstraint();
        }

        TableScanNode tableScan = new TableScanNode(
                node.getId(),
                newTable,
                node.getOutputSymbols(),
                node.getAssignments(),
                computeEnforced(newDomain, remainingFilter));

        Expression resultingPredicate = createResultingPredicate(
                metadata,
                domainTranslator.toPredicate(remainingFilter.transform(assignments::get)),
                nonDeterministicPredicate,
                decomposedPredicate.getRemainingExpression());

        if (!TRUE_LITERAL.equals(resultingPredicate)) {
            return Optional.of(new FilterNode(idAllocator.getNextId(), tableScan, resultingPredicate));
        }

        return Optional.of(tableScan);
    }