/* * 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; import com.facebook.presto.Session; import com.facebook.presto.metadata.Metadata; import com.facebook.presto.metadata.Signature; import com.facebook.presto.spi.block.Block; import com.facebook.presto.spi.predicate.DiscreteValues; import com.facebook.presto.spi.predicate.Domain; import com.facebook.presto.spi.predicate.Marker; import com.facebook.presto.spi.predicate.NullableValue; import com.facebook.presto.spi.predicate.Range; import com.facebook.presto.spi.predicate.Ranges; import com.facebook.presto.spi.predicate.SortedRangeSet; import com.facebook.presto.spi.predicate.TupleDomain; import com.facebook.presto.spi.predicate.Utils; import com.facebook.presto.spi.predicate.ValueSet; import com.facebook.presto.spi.type.Type; import com.facebook.presto.sql.FunctionInvoker; import com.facebook.presto.sql.analyzer.ExpressionAnalyzer; import com.facebook.presto.sql.parser.SqlParser; import com.facebook.presto.sql.tree.AstVisitor; import com.facebook.presto.sql.tree.BetweenPredicate; import com.facebook.presto.sql.tree.BooleanLiteral; import com.facebook.presto.sql.tree.Cast; import com.facebook.presto.sql.tree.ComparisonExpression; import com.facebook.presto.sql.tree.ComparisonExpressionType; import com.facebook.presto.sql.tree.Expression; import com.facebook.presto.sql.tree.InListExpression; import com.facebook.presto.sql.tree.InPredicate; import com.facebook.presto.sql.tree.IsNotNullPredicate; import com.facebook.presto.sql.tree.IsNullPredicate; import com.facebook.presto.sql.tree.LogicalBinaryExpression; import com.facebook.presto.sql.tree.NotExpression; import com.facebook.presto.sql.tree.NullLiteral; import com.facebook.presto.sql.tree.SymbolReference; import com.facebook.presto.util.maps.IdentityLinkedHashMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.PeekingIterator; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import static com.facebook.presto.metadata.Signature.internalOperator; import static com.facebook.presto.spi.function.OperatorType.SATURATED_FLOOR_CAST; import static com.facebook.presto.sql.ExpressionUtils.and; import static com.facebook.presto.sql.ExpressionUtils.combineConjuncts; import static com.facebook.presto.sql.ExpressionUtils.combineDisjunctsWithDefault; import static com.facebook.presto.sql.ExpressionUtils.or; import static com.facebook.presto.sql.planner.LiteralInterpreter.toExpression; import static com.facebook.presto.sql.tree.BooleanLiteral.FALSE_LITERAL; import static com.facebook.presto.sql.tree.BooleanLiteral.TRUE_LITERAL; import static com.facebook.presto.sql.tree.ComparisonExpressionType.EQUAL; import static com.facebook.presto.sql.tree.ComparisonExpressionType.GREATER_THAN; import static com.facebook.presto.sql.tree.ComparisonExpressionType.GREATER_THAN_OR_EQUAL; import static com.facebook.presto.sql.tree.ComparisonExpressionType.LESS_THAN; import static com.facebook.presto.sql.tree.ComparisonExpressionType.LESS_THAN_OR_EQUAL; import static com.facebook.presto.sql.tree.ComparisonExpressionType.NOT_EQUAL; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Iterators.peekingIterator; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; public final class DomainTranslator { private DomainTranslator() { } public static Expression toPredicate(TupleDomain<Symbol> tupleDomain) { if (tupleDomain.isNone()) { return FALSE_LITERAL; } ImmutableList.Builder<Expression> conjunctBuilder = ImmutableList.builder(); for (Map.Entry<Symbol, Domain> entry : tupleDomain.getDomains().get().entrySet()) { Symbol symbol = entry.getKey(); SymbolReference reference = symbol.toSymbolReference(); conjunctBuilder.add(toPredicate(entry.getValue(), reference)); } return combineConjuncts(conjunctBuilder.build()); } private static Expression toPredicate(Domain domain, SymbolReference reference) { if (domain.getValues().isNone()) { return domain.isNullAllowed() ? new IsNullPredicate(reference) : FALSE_LITERAL; } if (domain.getValues().isAll()) { return domain.isNullAllowed() ? TRUE_LITERAL : new NotExpression(new IsNullPredicate(reference)); } List<Expression> disjuncts = new ArrayList<>(); disjuncts.addAll(domain.getValues().getValuesProcessor().transform( ranges -> extractDisjuncts(domain.getType(), ranges, reference), discreteValues -> extractDisjuncts(domain.getType(), discreteValues, reference), allOrNone -> { throw new IllegalStateException("Case should not be reachable"); })); // Add nullability disjuncts if (domain.isNullAllowed()) { disjuncts.add(new IsNullPredicate(reference)); } return combineDisjunctsWithDefault(disjuncts, TRUE_LITERAL); } private static Expression processRange(Type type, Range range, SymbolReference reference) { if (range.isAll()) { return TRUE_LITERAL; } if (isBetween(range)) { // specialize the range with BETWEEN expression if possible b/c it is currently more efficient return new BetweenPredicate(reference, toExpression(range.getLow().getValue(), type), toExpression(range.getHigh().getValue(), type)); } List<Expression> rangeConjuncts = new ArrayList<>(); if (!range.getLow().isLowerUnbounded()) { switch (range.getLow().getBound()) { case ABOVE: rangeConjuncts.add(new ComparisonExpression(GREATER_THAN, reference, toExpression(range.getLow().getValue(), type))); break; case EXACTLY: rangeConjuncts.add(new ComparisonExpression(GREATER_THAN_OR_EQUAL, reference, toExpression(range.getLow().getValue(), type))); break; case BELOW: throw new IllegalStateException("Low Marker should never use BELOW bound: " + range); default: throw new AssertionError("Unhandled bound: " + range.getLow().getBound()); } } if (!range.getHigh().isUpperUnbounded()) { switch (range.getHigh().getBound()) { case ABOVE: throw new IllegalStateException("High Marker should never use ABOVE bound: " + range); case EXACTLY: rangeConjuncts.add(new ComparisonExpression(LESS_THAN_OR_EQUAL, reference, toExpression(range.getHigh().getValue(), type))); break; case BELOW: rangeConjuncts.add(new ComparisonExpression(LESS_THAN, reference, toExpression(range.getHigh().getValue(), type))); break; default: throw new AssertionError("Unhandled bound: " + range.getHigh().getBound()); } } // If rangeConjuncts is null, then the range was ALL, which should already have been checked for checkState(!rangeConjuncts.isEmpty()); return combineConjuncts(rangeConjuncts); } private static Expression combineRangeWithExcludedPoints(Type type, SymbolReference reference, Range range, List<Expression> excludedPoints) { if (excludedPoints.isEmpty()) { return processRange(type, range, reference); } Expression excludedPointsExpression = new NotExpression(new InPredicate(reference, new InListExpression(excludedPoints))); if (excludedPoints.size() == 1) { excludedPointsExpression = new ComparisonExpression(NOT_EQUAL, reference, getOnlyElement(excludedPoints)); } return combineConjuncts(processRange(type, range, reference), excludedPointsExpression); } private static List<Expression> extractDisjuncts(Type type, Ranges ranges, SymbolReference reference) { List<Expression> disjuncts = new ArrayList<>(); List<Expression> singleValues = new ArrayList<>(); List<Range> orderedRanges = ranges.getOrderedRanges(); SortedRangeSet sortedRangeSet = SortedRangeSet.copyOf(type, orderedRanges); SortedRangeSet complement = sortedRangeSet.complement(); List<Range> singleValueExclusionsList = complement.getOrderedRanges().stream().filter(Range::isSingleValue).collect(toList()); List<Range> originalUnionSingleValues = SortedRangeSet.copyOf(type, singleValueExclusionsList).union(sortedRangeSet).getOrderedRanges(); PeekingIterator<Range> singleValueExclusions = peekingIterator(singleValueExclusionsList.iterator()); for (Range range : originalUnionSingleValues) { if (range.isSingleValue()) { singleValues.add(toExpression(range.getSingleValue(), type)); continue; } // attempt to optimize ranges that can be coalesced as long as single value points are excluded List<Expression> singleValuesInRange = new ArrayList<>(); while (singleValueExclusions.hasNext() && range.contains(singleValueExclusions.peek())) { singleValuesInRange.add(toExpression(singleValueExclusions.next().getSingleValue(), type)); } if (!singleValuesInRange.isEmpty()) { disjuncts.add(combineRangeWithExcludedPoints(type, reference, range, singleValuesInRange)); continue; } disjuncts.add(processRange(type, range, reference)); } // Add back all of the possible single values either as an equality or an IN predicate if (singleValues.size() == 1) { disjuncts.add(new ComparisonExpression(EQUAL, reference, getOnlyElement(singleValues))); } else if (singleValues.size() > 1) { disjuncts.add(new InPredicate(reference, new InListExpression(singleValues))); } return disjuncts; } private static List<Expression> extractDisjuncts(Type type, DiscreteValues discreteValues, SymbolReference reference) { List<Expression> values = discreteValues.getValues().stream() .map(object -> toExpression(object, type)) .collect(toList()); // If values is empty, then the equatableValues was either ALL or NONE, both of which should already have been checked for checkState(!values.isEmpty()); Expression predicate; if (values.size() == 1) { predicate = new ComparisonExpression(EQUAL, reference, getOnlyElement(values)); } else { predicate = new InPredicate(reference, new InListExpression(values)); } if (!discreteValues.isWhiteList()) { predicate = new NotExpression(predicate); } return ImmutableList.of(predicate); } private static boolean isBetween(Range range) { return !range.getLow().isLowerUnbounded() && range.getLow().getBound() == Marker.Bound.EXACTLY && !range.getHigh().isUpperUnbounded() && range.getHigh().getBound() == Marker.Bound.EXACTLY; } /** * Convert an Expression predicate into an ExtractionResult consisting of: * 1) A successfully extracted TupleDomain * 2) An Expression fragment which represents the part of the original Expression that will need to be re-evaluated * after filtering with the TupleDomain. */ public static ExtractionResult fromPredicate( Metadata metadata, Session session, Expression predicate, Map<Symbol, Type> types) { return new Visitor(metadata, session, types).process(predicate, false); } private static class Visitor extends AstVisitor<ExtractionResult, Boolean> { private final Metadata metadata; private final Session session; private final Map<Symbol, Type> types; private final FunctionInvoker functionInvoker; private Visitor(Metadata metadata, Session session, Map<Symbol, Type> types) { this.metadata = requireNonNull(metadata, "metadata is null"); this.session = requireNonNull(session, "session is null"); this.types = ImmutableMap.copyOf(requireNonNull(types, "types is null")); this.functionInvoker = new FunctionInvoker(metadata.getFunctionRegistry()); } private Type checkedTypeLookup(Symbol symbol) { Type type = types.get(symbol); checkArgument(type != null, "Types is missing info for symbol: %s", symbol); return type; } private static ValueSet complementIfNecessary(ValueSet valueSet, boolean complement) { return complement ? valueSet.complement() : valueSet; } private static Domain complementIfNecessary(Domain domain, boolean complement) { return complement ? domain.complement() : domain; } private static Expression complementIfNecessary(Expression expression, boolean complement) { return complement ? new NotExpression(expression) : expression; } @Override protected ExtractionResult visitExpression(Expression node, Boolean complement) { // If we don't know how to process this node, the default response is to say that the TupleDomain is "all" return new ExtractionResult(TupleDomain.all(), complementIfNecessary(node, complement)); } @Override protected ExtractionResult visitLogicalBinaryExpression(LogicalBinaryExpression node, Boolean complement) { ExtractionResult leftResult = process(node.getLeft(), complement); ExtractionResult rightResult = process(node.getRight(), complement); TupleDomain<Symbol> leftTupleDomain = leftResult.getTupleDomain(); TupleDomain<Symbol> rightTupleDomain = rightResult.getTupleDomain(); LogicalBinaryExpression.Type type = complement ? flipLogicalBinaryType(node.getType()) : node.getType(); switch (type) { case AND: return new ExtractionResult( leftTupleDomain.intersect(rightTupleDomain), combineConjuncts(leftResult.getRemainingExpression(), rightResult.getRemainingExpression())); case OR: TupleDomain<Symbol> columnUnionedTupleDomain = TupleDomain.columnWiseUnion(leftTupleDomain, rightTupleDomain); // In most cases, the columnUnionedTupleDomain is only a superset of the actual strict union // and so we can return the current node as the remainingExpression so that all bounds will be double checked again at execution time. Expression remainingExpression = complementIfNecessary(node, complement); // However, there are a few cases where the column-wise union is actually equivalent to the strict union, so we if can detect // some of these cases, we won't have to double check the bounds unnecessarily at execution time. // We can only make inferences if the remaining expressions on both side are equal and deterministic if (leftResult.getRemainingExpression().equals(rightResult.getRemainingExpression()) && DeterminismEvaluator.isDeterministic(leftResult.getRemainingExpression())) { // The column-wise union is equivalent to the strict union if // 1) If both TupleDomains consist of the same exact single column (e.g. left TupleDomain => (a > 0), right TupleDomain => (a < 10)) // 2) If one TupleDomain is a superset of the other (e.g. left TupleDomain => (a > 0, b > 0 && b < 10), right TupleDomain => (a > 5, b = 5)) boolean matchingSingleSymbolDomains = !leftTupleDomain.isNone() && !rightTupleDomain.isNone() && leftTupleDomain.getDomains().get().size() == 1 && rightTupleDomain.getDomains().get().size() == 1 && leftTupleDomain.getDomains().get().keySet().equals(rightTupleDomain.getDomains().get().keySet()); boolean oneSideIsSuperSet = leftTupleDomain.contains(rightTupleDomain) || rightTupleDomain.contains(leftTupleDomain); if (matchingSingleSymbolDomains || oneSideIsSuperSet) { remainingExpression = leftResult.getRemainingExpression(); } } return new ExtractionResult(columnUnionedTupleDomain, remainingExpression); default: throw new AssertionError("Unknown type: " + node.getType()); } } private static LogicalBinaryExpression.Type flipLogicalBinaryType(LogicalBinaryExpression.Type type) { switch (type) { case AND: return LogicalBinaryExpression.Type.OR; case OR: return LogicalBinaryExpression.Type.AND; default: throw new AssertionError("Unknown type: " + type); } } @Override protected ExtractionResult visitNotExpression(NotExpression node, Boolean complement) { return process(node.getValue(), !complement); } @Override protected ExtractionResult visitComparisonExpression(ComparisonExpression node, Boolean complement) { Optional<NormalizedSimpleComparison> optionalNormalized = toNormalizedSimpleComparison(node); if (!optionalNormalized.isPresent()) { return super.visitComparisonExpression(node, complement); } NormalizedSimpleComparison normalized = optionalNormalized.get(); Expression symbolExpression = normalized.getSymbolExpression(); if (symbolExpression instanceof SymbolReference) { Symbol symbol = Symbol.from(symbolExpression); NullableValue value = normalized.getValue(); Type type = value.getType(); // common type for symbol and value return createComparisonExtractionResult(normalized.getComparisonType(), symbol, type, value.getValue(), complement); } else if (symbolExpression instanceof Cast) { Cast castExpression = (Cast) symbolExpression; if (!isImplicitCoercion(castExpression)) { // // we cannot use non-coercion cast to literal_type on symbol side to build tuple domain // // example which illustrates the problem: // // let t be of timestamp type: // // and expression be: // cast(t as date) == date_literal // // after dropping cast we end up with: // // t == date_literal // // if we build tuple domain based coercion of date_literal to timestamp type we would // end up with tuple domain with just one time point (cast(date_literal as timestamp). // While we need range which maps to single date pointed by date_literal. // return super.visitComparisonExpression(node, complement); } NullableValue value = normalized.getValue(); Type valueType = value.getType(); // type of value Type castSourceType = typeOf(castExpression.getExpression(), session, metadata, types); // type of expression which is then cast to type of value // we use saturated floor cast value -> castSourceType to rewrite original expression to new one with one cast peeled off the symbol side Optional<Expression> coercedExpression = coerceComparisonWithRounding( castSourceType, castExpression.getExpression(), valueType, value.getValue(), normalized.getComparisonType()); if (coercedExpression.isPresent()) { return process(coercedExpression.get(), complement); } return super.visitComparisonExpression(node, complement); } else { return super.visitComparisonExpression(node, complement); } } /** * Extract a normalized simple comparison between a QualifiedNameReference and a native value if possible. */ private Optional<NormalizedSimpleComparison> toNormalizedSimpleComparison(ComparisonExpression comparison) { IdentityLinkedHashMap<Expression, Type> expressionTypes = analyzeExpression(comparison); Object left = ExpressionInterpreter.expressionOptimizer(comparison.getLeft(), metadata, session, expressionTypes).optimize(NoOpSymbolResolver.INSTANCE); Object right = ExpressionInterpreter.expressionOptimizer(comparison.getRight(), metadata, session, expressionTypes).optimize(NoOpSymbolResolver.INSTANCE); Type leftType = expressionTypes.get(comparison.getLeft()); Type rightType = expressionTypes.get(comparison.getRight()); // TODO: re-enable this check once we fix the type coercions in the optimizers // checkArgument(leftType.equals(rightType), "left and right type do not match in comparison expression (%s)", comparison); if (left instanceof Expression == right instanceof Expression) { // we expect one side to be expression and other to be value. return Optional.empty(); } Expression symbolExpression; ComparisonExpressionType comparisonType; NullableValue value; if (left instanceof Expression) { symbolExpression = comparison.getLeft(); comparisonType = comparison.getType(); value = new NullableValue(rightType, right); } else { symbolExpression = comparison.getRight(); comparisonType = comparison.getType().flip(); value = new NullableValue(leftType, left); } return Optional.of(new NormalizedSimpleComparison(symbolExpression, comparisonType, value)); } private boolean isImplicitCoercion(Cast cast) { IdentityLinkedHashMap<Expression, Type> expressionTypes = analyzeExpression(cast); return metadata.getTypeManager().canCoerce(expressionTypes.get(cast.getExpression()), expressionTypes.get(cast)); } private IdentityLinkedHashMap<Expression, Type> analyzeExpression(Expression expression) { return ExpressionAnalyzer.getExpressionTypes(session, metadata, new SqlParser(), types, expression, emptyList() /* parameters already replaced */); } private static ExtractionResult createComparisonExtractionResult(ComparisonExpressionType comparisonType, Symbol column, Type type, @Nullable Object value, boolean complement) { if (value == null) { switch (comparisonType) { case EQUAL: case GREATER_THAN: case GREATER_THAN_OR_EQUAL: case LESS_THAN: case LESS_THAN_OR_EQUAL: case NOT_EQUAL: return new ExtractionResult(TupleDomain.none(), TRUE_LITERAL); case IS_DISTINCT_FROM: Domain domain = complementIfNecessary(Domain.notNull(type), complement); return new ExtractionResult( TupleDomain.withColumnDomains(ImmutableMap.of(column, domain)), TRUE_LITERAL); default: throw new AssertionError("Unhandled type: " + comparisonType); } } Domain domain; if (type.isOrderable()) { domain = extractOrderableDomain(comparisonType, type, value, complement); } else if (type.isComparable()) { domain = extractEquatableDomain(comparisonType, type, value, complement); } else { throw new AssertionError("Type cannot be used in a comparison expression (should have been caught in analysis): " + type); } return new ExtractionResult( TupleDomain.withColumnDomains(ImmutableMap.of(column, domain)), TRUE_LITERAL); } private static Domain extractOrderableDomain(ComparisonExpressionType comparisonType, Type type, Object value, boolean complement) { checkArgument(value != null); switch (comparisonType) { case EQUAL: return Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.equal(type, value)), complement), false); case GREATER_THAN: return Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.greaterThan(type, value)), complement), false); case GREATER_THAN_OR_EQUAL: return Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.greaterThanOrEqual(type, value)), complement), false); case LESS_THAN: return Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.lessThan(type, value)), complement), false); case LESS_THAN_OR_EQUAL: return Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.lessThanOrEqual(type, value)), complement), false); case NOT_EQUAL: return Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.lessThan(type, value), Range.greaterThan(type, value)), complement), false); case IS_DISTINCT_FROM: // Need to potential complement the whole domain for IS_DISTINCT_FROM since it is null-aware return complementIfNecessary(Domain.create(ValueSet.ofRanges(Range.lessThan(type, value), Range.greaterThan(type, value)), true), complement); default: throw new AssertionError("Unhandled type: " + comparisonType); } } private static Domain extractEquatableDomain(ComparisonExpressionType comparisonType, Type type, Object value, boolean complement) { checkArgument(value != null); switch (comparisonType) { case EQUAL: return Domain.create(complementIfNecessary(ValueSet.of(type, value), complement), false); case NOT_EQUAL: return Domain.create(complementIfNecessary(ValueSet.of(type, value).complement(), complement), false); case IS_DISTINCT_FROM: // Need to potential complement the whole domain for IS_DISTINCT_FROM since it is null-aware return complementIfNecessary(Domain.create(ValueSet.of(type, value).complement(), true), complement); default: throw new AssertionError("Unhandled type: " + comparisonType); } } private Optional<Expression> coerceComparisonWithRounding( Type symbolExpressionType, Expression symbolExpression, Type valueType, Object value, ComparisonExpressionType comparisonType) { requireNonNull(value, "value is null"); return floorValue(valueType, symbolExpressionType, value) .map((floorValue) -> rewriteComparisonExpression(symbolExpressionType, symbolExpression, valueType, value, floorValue, comparisonType)); } private Expression rewriteComparisonExpression( Type symbolExpressionType, Expression symbolExpression, Type valueType, Object originalValue, Object coercedValue, ComparisonExpressionType comparisonType) { int originalComparedToCoerced = compareOriginalValueToCoerced(valueType, originalValue, symbolExpressionType, coercedValue); boolean coercedValueIsEqualToOriginal = originalComparedToCoerced == 0; boolean coercedValueIsLessThanOriginal = originalComparedToCoerced > 0; boolean coercedValueIsGreaterThanOriginal = originalComparedToCoerced < 0; Expression coercedLiteral = toExpression(coercedValue, symbolExpressionType); switch (comparisonType) { case GREATER_THAN_OR_EQUAL: case GREATER_THAN: { if (coercedValueIsGreaterThanOriginal) { return new ComparisonExpression(GREATER_THAN_OR_EQUAL, symbolExpression, coercedLiteral); } else if (coercedValueIsEqualToOriginal) { return new ComparisonExpression(comparisonType, symbolExpression, coercedLiteral); } else if (coercedValueIsLessThanOriginal) { return new ComparisonExpression(GREATER_THAN, symbolExpression, coercedLiteral); } } case LESS_THAN_OR_EQUAL: case LESS_THAN: { if (coercedValueIsLessThanOriginal) { return new ComparisonExpression(LESS_THAN_OR_EQUAL, symbolExpression, coercedLiteral); } else if (coercedValueIsEqualToOriginal) { return new ComparisonExpression(comparisonType, symbolExpression, coercedLiteral); } else if (coercedValueIsGreaterThanOriginal) { return new ComparisonExpression(LESS_THAN, symbolExpression, coercedLiteral); } } case EQUAL: { if (coercedValueIsEqualToOriginal) { return new ComparisonExpression(EQUAL, symbolExpression, coercedLiteral); } // Return something that is false for all non-null values return and(new ComparisonExpression(EQUAL, symbolExpression, coercedLiteral), new ComparisonExpression(NOT_EQUAL, symbolExpression, coercedLiteral)); } case NOT_EQUAL: { if (coercedValueIsEqualToOriginal) { return new ComparisonExpression(comparisonType, symbolExpression, coercedLiteral); } // Return something that is true for all non-null values return or(new ComparisonExpression(EQUAL, symbolExpression, coercedLiteral), new ComparisonExpression(NOT_EQUAL, symbolExpression, coercedLiteral)); } case IS_DISTINCT_FROM: { if (coercedValueIsEqualToOriginal) { return new ComparisonExpression(comparisonType, symbolExpression, coercedLiteral); } return TRUE_LITERAL; } } throw new IllegalArgumentException("Unhandled type: " + comparisonType); } private Optional<Object> floorValue(Type fromType, Type toType, Object value) { return getSaturatedFloorCastOperator(fromType, toType) .map((operator) -> functionInvoker.invoke(operator, session.toConnectorSession(), value)); } private Optional<Signature> getSaturatedFloorCastOperator(Type fromType, Type toType) { if (metadata.getFunctionRegistry().canResolveOperator(SATURATED_FLOOR_CAST, toType, ImmutableList.of(fromType))) { return Optional.of(internalOperator(SATURATED_FLOOR_CAST, toType, ImmutableList.of(fromType))); } return Optional.empty(); } private int compareOriginalValueToCoerced(Type originalValueType, Object originalValue, Type coercedValueType, Object coercedValue) { Signature castToOriginalTypeOperator = metadata.getFunctionRegistry().getCoercion(coercedValueType, originalValueType); Object coercedValueInOriginalType = functionInvoker.invoke(castToOriginalTypeOperator, session.toConnectorSession(), coercedValue); Block originalValueBlock = Utils.nativeValueToBlock(originalValueType, originalValue); Block coercedValueBlock = Utils.nativeValueToBlock(originalValueType, coercedValueInOriginalType); return originalValueType.compareTo(originalValueBlock, 0, coercedValueBlock, 0); } @Override protected ExtractionResult visitInPredicate(InPredicate node, Boolean complement) { if (!(node.getValue() instanceof SymbolReference) || !(node.getValueList() instanceof InListExpression)) { return super.visitInPredicate(node, complement); } InListExpression valueList = (InListExpression) node.getValueList(); checkState(!valueList.getValues().isEmpty(), "InListExpression should never be empty"); ImmutableList.Builder<Expression> disjuncts = ImmutableList.builder(); for (Expression expression : valueList.getValues()) { disjuncts.add(new ComparisonExpression(EQUAL, node.getValue(), expression)); } return process(or(disjuncts.build()), complement); } @Override protected ExtractionResult visitBetweenPredicate(BetweenPredicate node, Boolean complement) { // Re-write as two comparison expressions return process(and( new ComparisonExpression(GREATER_THAN_OR_EQUAL, node.getValue(), node.getMin()), new ComparisonExpression(LESS_THAN_OR_EQUAL, node.getValue(), node.getMax())), complement); } @Override protected ExtractionResult visitIsNullPredicate(IsNullPredicate node, Boolean complement) { if (!(node.getValue() instanceof SymbolReference)) { return super.visitIsNullPredicate(node, complement); } Symbol symbol = Symbol.from(node.getValue()); Type columnType = checkedTypeLookup(symbol); Domain domain = complementIfNecessary(Domain.onlyNull(columnType), complement); return new ExtractionResult( TupleDomain.withColumnDomains(ImmutableMap.of(symbol, domain)), TRUE_LITERAL); } @Override protected ExtractionResult visitIsNotNullPredicate(IsNotNullPredicate node, Boolean complement) { if (!(node.getValue() instanceof SymbolReference)) { return super.visitIsNotNullPredicate(node, complement); } Symbol symbol = Symbol.from(node.getValue()); Type columnType = checkedTypeLookup(symbol); Domain domain = complementIfNecessary(Domain.notNull(columnType), complement); return new ExtractionResult( TupleDomain.withColumnDomains(ImmutableMap.of(symbol, domain)), TRUE_LITERAL); } @Override protected ExtractionResult visitBooleanLiteral(BooleanLiteral node, Boolean complement) { boolean value = complement ? !node.getValue() : node.getValue(); return new ExtractionResult(value ? TupleDomain.all() : TupleDomain.none(), TRUE_LITERAL); } @Override protected ExtractionResult visitNullLiteral(NullLiteral node, Boolean complement) { return new ExtractionResult(TupleDomain.none(), TRUE_LITERAL); } } private static Type typeOf(Expression expression, Session session, Metadata metadata, Map<Symbol, Type> types) { IdentityLinkedHashMap<Expression, Type> expressionTypes = ExpressionAnalyzer.getExpressionTypes(session, metadata, new SqlParser(), types, expression, emptyList() /* parameters already replaced */); return expressionTypes.get(expression); } private static class NormalizedSimpleComparison { private final Expression symbolExpression; private final ComparisonExpressionType comparisonType; private final NullableValue value; public NormalizedSimpleComparison(Expression symbolExpression, ComparisonExpressionType comparisonType, NullableValue value) { this.symbolExpression = requireNonNull(symbolExpression, "nameReference is null"); this.comparisonType = requireNonNull(comparisonType, "comparisonType is null"); this.value = requireNonNull(value, "value is null"); } public Expression getSymbolExpression() { return symbolExpression; } public ComparisonExpressionType getComparisonType() { return comparisonType; } public NullableValue getValue() { return value; } } public static class ExtractionResult { private final TupleDomain<Symbol> tupleDomain; private final Expression remainingExpression; public ExtractionResult(TupleDomain<Symbol> tupleDomain, Expression remainingExpression) { this.tupleDomain = requireNonNull(tupleDomain, "tupleDomain is null"); this.remainingExpression = requireNonNull(remainingExpression, "remainingExpression is null"); } public TupleDomain<Symbol> getTupleDomain() { return tupleDomain; } public Expression getRemainingExpression() { return remainingExpression; } } }