Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.PeekingIterator;
import io.airlift.slice.Slice;
import io.airlift.slice.Slices;
import io.trino.Session;
import io.trino.connector.CatalogServiceProvider;
import io.trino.metadata.AnalyzePropertyManager;
import io.trino.metadata.OperatorNotFoundException;
import io.trino.metadata.ResolvedFunction;
import io.trino.metadata.TableFunctionRegistry;
import io.trino.metadata.TableProceduresPropertyManager;
import io.trino.metadata.TableProceduresRegistry;
import io.trino.metadata.TablePropertyManager;
import io.trino.security.AllowAllAccessControl;
import io.trino.spi.ErrorCode;
import io.trino.spi.TrinoException;
import io.trino.spi.function.CatalogSchemaFunctionName;
import io.trino.spi.predicate.DiscreteValues;
import io.trino.spi.predicate.Domain;
import io.trino.spi.predicate.NullableValue;
import io.trino.spi.predicate.Range;
import io.trino.spi.predicate.Ranges;
import io.trino.spi.predicate.SortedRangeSet;
import io.trino.spi.predicate.TupleDomain;
import io.trino.spi.predicate.ValueSet;
import io.trino.spi.type.DoubleType;
import io.trino.spi.type.RealType;
import io.trino.spi.type.Type;
import io.trino.spi.type.VarcharType;
import io.trino.sql.InterpretedFunctionInvoker;
import io.trino.sql.PlannerContext;
import io.trino.sql.analyzer.SessionTimeProvider;
import io.trino.sql.analyzer.StatementAnalyzerFactory;
import io.trino.sql.parser.SqlParser;
import io.trino.sql.tree.AstVisitor;
import io.trino.sql.tree.BetweenPredicate;
import io.trino.sql.tree.BooleanLiteral;
import io.trino.sql.tree.Cast;
import io.trino.sql.tree.ComparisonExpression;
import io.trino.sql.tree.Expression;
import io.trino.sql.tree.FunctionCall;
import io.trino.sql.tree.InListExpression;
import io.trino.sql.tree.InPredicate;
import io.trino.sql.tree.IsNotNullPredicate;
import io.trino.sql.tree.IsNullPredicate;
import io.trino.sql.tree.LogicalExpression;
import io.trino.sql.tree.NodeRef;
import io.trino.sql.tree.NotExpression;
import io.trino.sql.tree.NullLiteral;
import io.trino.sql.tree.StringLiteral;
import io.trino.sql.tree.SymbolReference;
import io.trino.transaction.NoOpTransactionManager;
import io.trino.type.LikeFunctions;
import io.trino.type.LikePattern;
import io.trino.type.LikePatternType;
import io.trino.type.TypeCoercion;
import jakarta.annotation.Nullable;
import java.lang.invoke.MethodHandle;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Iterators.peekingIterator;
import static io.airlift.slice.SliceUtf8.countCodePoints;
import static io.airlift.slice.SliceUtf8.getCodePointAt;
import static io.airlift.slice.SliceUtf8.lengthOfCodePoint;
import static io.airlift.slice.SliceUtf8.setCodePointAt;
import static io.airlift.slice.Slices.utf8Slice;
import static io.trino.metadata.GlobalFunctionCatalog.builtinFunctionName;
import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR;
import static io.trino.spi.StandardErrorCode.INVALID_CAST_ARGUMENT;
import static io.trino.spi.function.InvocationConvention.InvocationArgumentConvention.NEVER_NULL;
import static io.trino.spi.function.InvocationConvention.InvocationReturnConvention.FAIL_ON_NULL;
import static io.trino.spi.function.InvocationConvention.simpleConvention;
import static io.trino.spi.function.OperatorType.SATURATED_FLOOR_CAST;
import static io.trino.spi.type.BooleanType.BOOLEAN;
import static io.trino.spi.type.DateType.DATE;
import static io.trino.spi.type.TypeUtils.isFloatingPointNaN;
import static io.trino.sql.ExpressionUtils.and;
import static io.trino.sql.ExpressionUtils.combineConjuncts;
import static io.trino.sql.ExpressionUtils.combineDisjunctsWithDefault;
import static io.trino.sql.ExpressionUtils.or;
import static io.trino.sql.planner.ExpressionInterpreter.evaluateConstantExpression;
import static io.trino.sql.tree.BooleanLiteral.FALSE_LITERAL;
import static io.trino.sql.tree.BooleanLiteral.TRUE_LITERAL;
import static io.trino.sql.tree.ComparisonExpression.Operator.EQUAL;
import static io.trino.sql.tree.ComparisonExpression.Operator.GREATER_THAN;
import static io.trino.sql.tree.ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL;
import static io.trino.sql.tree.ComparisonExpression.Operator.IS_DISTINCT_FROM;
import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN;
import static io.trino.sql.tree.ComparisonExpression.Operator.LESS_THAN_OR_EQUAL;
import static io.trino.sql.tree.ComparisonExpression.Operator.NOT_EQUAL;
import static io.trino.type.LikeFunctions.LIKE_FUNCTION_NAME;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
public final class DomainTranslator
{
private final PlannerContext plannerContext;
private final LiteralEncoder literalEncoder;
public DomainTranslator(PlannerContext plannerContext)
{
this.plannerContext = requireNonNull(plannerContext, "plannerContext is null");
this.literalEncoder = new LiteralEncoder(plannerContext);
}
public Expression toPredicate(TupleDomain tupleDomain)
{
if (tupleDomain.isNone()) {
return FALSE_LITERAL;
}
Map domains = tupleDomain.getDomains().get();
return domains.entrySet().stream()
.map(entry -> toPredicate(entry.getValue(), entry.getKey().toSymbolReference()))
.collect(collectingAndThen(toImmutableList(), expressions -> combineConjuncts(plannerContext.getMetadata(), expressions)));
}
private 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 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(plannerContext.getMetadata(), disjuncts, TRUE_LITERAL);
}
private 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,
literalEncoder.toExpression(range.getLowBoundedValue(), type),
literalEncoder.toExpression(range.getHighBoundedValue(), type));
}
List rangeConjuncts = new ArrayList<>();
if (!range.isLowUnbounded()) {
rangeConjuncts.add(new ComparisonExpression(
range.isLowInclusive() ? GREATER_THAN_OR_EQUAL : GREATER_THAN,
reference,
literalEncoder.toExpression(range.getLowBoundedValue(), type)));
}
if (!range.isHighUnbounded()) {
rangeConjuncts.add(new ComparisonExpression(
range.isHighInclusive() ? LESS_THAN_OR_EQUAL : LESS_THAN,
reference,
literalEncoder.toExpression(range.getHighBoundedValue(), type)));
}
// If rangeConjuncts is null, then the range was ALL, which should already have been checked for
checkState(!rangeConjuncts.isEmpty());
return combineConjuncts(plannerContext.getMetadata(), rangeConjuncts);
}
private Expression combineRangeWithExcludedPoints(Type type, SymbolReference reference, Range range, List 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(plannerContext.getMetadata(), processRange(type, range, reference), excludedPointsExpression);
}
private List extractDisjuncts(Type type, Ranges ranges, SymbolReference reference)
{
List disjuncts = new ArrayList<>();
List singleValues = new ArrayList<>();
List orderedRanges = ranges.getOrderedRanges();
SortedRangeSet sortedRangeSet = SortedRangeSet.copyOf(type, orderedRanges);
SortedRangeSet complement = sortedRangeSet.complement();
List singleValueExclusionsList = complement.getOrderedRanges().stream().filter(Range::isSingleValue).collect(toList());
List originalUnionSingleValues = SortedRangeSet.copyOf(type, singleValueExclusionsList).union(sortedRangeSet).getOrderedRanges();
PeekingIterator singleValueExclusions = peekingIterator(singleValueExclusionsList.iterator());
/*
For types including NaN, it is incorrect to introduce range "all" while processing a set of ranges,
even if the component ranges cover the entire value set.
This is because partial ranges don't include NaN, while range "all" does.
Example: ranges (unbounded , 1.0) and (1.0, unbounded) should not be coalesced to (unbounded, unbounded) with excluded point 1.0.
That result would be further translated to expression "xxx <> 1.0", which is satisfied by NaN.
To avoid error, in such case the ranges are not optimised.
*/
if (type instanceof RealType || type instanceof DoubleType) {
boolean originalRangeIsAll = orderedRanges.stream().anyMatch(Range::isAll);
boolean coalescedRangeIsAll = originalUnionSingleValues.stream().anyMatch(Range::isAll);
if (!originalRangeIsAll && coalescedRangeIsAll) {
for (Range range : orderedRanges) {
disjuncts.add(processRange(type, range, reference));
}
return disjuncts;
}
}
for (Range range : originalUnionSingleValues) {
if (range.isSingleValue()) {
singleValues.add(literalEncoder.toExpression(range.getSingleValue(), type));
continue;
}
// attempt to optimize ranges that can be coalesced as long as single value points are excluded
List singleValuesInRange = new ArrayList<>();
while (singleValueExclusions.hasNext() && range.contains(singleValueExclusions.peek())) {
singleValuesInRange.add(literalEncoder.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 List extractDisjuncts(Type type, DiscreteValues discreteValues, SymbolReference reference)
{
List values = discreteValues.getValues().stream()
.map(object -> literalEncoder.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.isInclusive()) {
predicate = new NotExpression(predicate);
}
return ImmutableList.of(predicate);
}
private static boolean isBetween(Range range)
{
// inclusive implies bounded
return range.isLowInclusive() && range.isHighInclusive();
}
/**
* 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 getExtractionResult(PlannerContext plannerContext, Session session, Expression predicate, TypeProvider types)
{
// This is a limited type analyzer for the simple expressions used in this method
TypeAnalyzer typeAnalyzer = new TypeAnalyzer(
plannerContext,
new StatementAnalyzerFactory(
plannerContext,
new SqlParser(),
SessionTimeProvider.DEFAULT,
new AllowAllAccessControl(),
new NoOpTransactionManager(),
user -> ImmutableSet.of(),
new TableProceduresRegistry(CatalogServiceProvider.fail("procedures are not supported in domain translator")),
new TableFunctionRegistry(CatalogServiceProvider.fail("table functions are not supported in domain translator")),
new TablePropertyManager(CatalogServiceProvider.fail("table properties not supported in domain translator")),
new AnalyzePropertyManager(CatalogServiceProvider.fail("analyze properties not supported in domain translator")),
new TableProceduresPropertyManager(CatalogServiceProvider.fail("procedures are not supported in domain translator"))));
return new Visitor(plannerContext, session, types, typeAnalyzer).process(predicate, false);
}
private static class Visitor
extends AstVisitor
{
private final PlannerContext plannerContext;
private final LiteralEncoder literalEncoder;
private final Session session;
private final TypeProvider types;
private final InterpretedFunctionInvoker functionInvoker;
private final TypeAnalyzer typeAnalyzer;
private final TypeCoercion typeCoercion;
private Visitor(PlannerContext plannerContext, Session session, TypeProvider types, TypeAnalyzer typeAnalyzer)
{
this.plannerContext = requireNonNull(plannerContext, "plannerContext is null");
this.literalEncoder = new LiteralEncoder(plannerContext);
this.session = requireNonNull(session, "session is null");
this.types = requireNonNull(types, "types is null");
this.functionInvoker = new InterpretedFunctionInvoker(plannerContext.getFunctionManager());
this.typeAnalyzer = requireNonNull(typeAnalyzer, "typeAnalyzer is null");
this.typeCoercion = new TypeCoercion(plannerContext.getTypeManager()::getType);
}
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 visitLogicalExpression(LogicalExpression node, Boolean complement)
{
List results = node.getTerms().stream()
.map(term -> process(term, complement))
.collect(toImmutableList());
List> tupleDomains = results.stream()
.map(ExtractionResult::getTupleDomain)
.collect(toImmutableList());
List residuals = results.stream()
.map(ExtractionResult::getRemainingExpression)
.collect(toImmutableList());
LogicalExpression.Operator operator = complement ? node.getOperator().flip() : node.getOperator();
switch (operator) {
case AND:
return new ExtractionResult(
TupleDomain.intersect(tupleDomains),
combineConjuncts(plannerContext.getMetadata(), residuals));
case OR:
TupleDomain columnUnionedTupleDomain = TupleDomain.columnWiseUnion(tupleDomains);
// 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 all terms are equal and deterministic
if (Set.copyOf(residuals).size() == 1 && DeterminismEvaluator.isDeterministic(residuals.get(0), plannerContext.getMetadata())) {
// NONE are no-op for the purpose of OR
tupleDomains = tupleDomains.stream()
.filter(domain -> !domain.isNone())
.collect(toList());
// The column-wise union is equivalent to the strict union if
// 1) If all TupleDomains consist of the same exact single column (e.g. one TupleDomain => (a > 0), another TupleDomain => (a < 10))
// 2) If one TupleDomain is a superset of the others (e.g. TupleDomain => (a > 0, b > 0 && b < 10) vs TupleDomain => (a > 5, b = 5))
boolean matchingSingleSymbolDomains = tupleDomains.stream().allMatch(domain -> domain.getDomains().get().size() == 1);
matchingSingleSymbolDomains = matchingSingleSymbolDomains && tupleDomains.stream()
.map(tupleDomain -> tupleDomain.getDomains().get().keySet())
.distinct()
.count() == 1;
boolean oneTermIsSuperSet = TupleDomain.maximal(tupleDomains).isPresent();
if (oneTermIsSuperSet) {
remainingExpression = residuals.get(0);
}
else if (matchingSingleSymbolDomains) {
// Types REAL and DOUBLE require special handling because they include NaN value. In this case, we cannot rely on the union of domains.
// That is because domains covering the value set partially might union up to a domain covering the whole value set.
// While the component domains didn't include NaN, the resulting domain could be further translated to predicate "TRUE" or "a IS NOT NULL",
// which is satisfied by NaN. So during domain union, NaN might be implicitly added.
// Example: Let 'a' be a column of type DOUBLE.
// Let left TupleDomain => (a > 0) /false for NaN/, right TupleDomain => (a < 10) /false for NaN/.
// Unioned TupleDomain => "is not null" /true for NaN/
// To guard against wrong results, the current node is returned as the remainingExpression.
Type type = getOnlyElement(tupleDomains.get(0).getDomains().get().values()).getType();
// A Domain of a floating point type contains NaN in the following cases:
// 1. When it contains all the values of the type and null.
// In such case the domain is 'all', and if it is the only domain
// in the TupleDomain, the TupleDomain gets normalized to TupleDomain 'all'.
// 2. When it contains all the values of the type and doesn't contain null.
// In such case no normalization on the level of TupleDomain takes place,
// and the check for NaN is done by inspecting the Domain's valueSet.
// NaN is included when the valueSet is 'all'.
boolean unionedDomainContainsNaN = columnUnionedTupleDomain.isAll() ||
(columnUnionedTupleDomain.getDomains().isPresent() &&
getOnlyElement(columnUnionedTupleDomain.getDomains().get().values()).getValues().isAll());
boolean implicitlyAddedNaN = (type instanceof RealType || type instanceof DoubleType) &&
tupleDomains.stream().noneMatch(TupleDomain::isAll) &&
unionedDomainContainsNaN;
if (!implicitlyAddedNaN) {
remainingExpression = residuals.get(0);
}
}
}
return new ExtractionResult(columnUnionedTupleDomain, remainingExpression);
}
throw new AssertionError("Unknown operator: " + node.getOperator());
}
@Override
protected ExtractionResult visitCast(Cast node, Boolean context)
{
if (node.getExpression() instanceof NullLiteral) {
return new ExtractionResult(TupleDomain.none(), TRUE_LITERAL);
}
return super.visitCast(node, context);
}
@Override
protected ExtractionResult visitNotExpression(NotExpression node, Boolean complement)
{
return process(node.getValue(), !complement);
}
@Override
protected ExtractionResult visitSymbolReference(SymbolReference node, Boolean complement)
{
if (types.get(Symbol.from(node)).equals(BOOLEAN)) {
ComparisonExpression newNode = new ComparisonExpression(EQUAL, node, TRUE_LITERAL);
return visitComparisonExpression(newNode, complement);
}
return visitExpression(node, complement);
}
@Override
protected ExtractionResult visitComparisonExpression(ComparisonExpression node, Boolean complement)
{
Map, Type> expressionTypes = analyzeExpression(node);
Optional optionalNormalized = toNormalizedSimpleComparison(expressionTypes, node);
if (optionalNormalized.isEmpty()) {
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.getComparisonOperator(), symbol, type, value.getValue(), complement)
.orElseGet(() -> super.visitComparisonExpression(node, complement));
}
if (symbolExpression instanceof Cast castExpression) {
// type of expression which is then cast to type of value
Type castSourceType = requireNonNull(expressionTypes.get(NodeRef.of(castExpression.getExpression())), "No type for Cast source expression");
Type castTargetType = requireNonNull(expressionTypes.get(NodeRef.of(castExpression)), "No type for Cast target expression");
if (castSourceType instanceof VarcharType && castTargetType == DATE && !castExpression.isSafe()) {
Optional result = createVarcharCastToDateComparisonExtractionResult(
normalized,
(VarcharType) castSourceType,
complement,
node);
if (result.isPresent()) {
return result.get();
}
}
if (!isImplicitCoercion(expressionTypes, 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);
}
// we use saturated floor cast value -> castSourceType to rewrite original expression to new one with one cast peeled off the symbol side
Optional coercedExpression = coerceComparisonWithRounding(
castSourceType, castExpression.getExpression(), normalized.getValue(), normalized.getComparisonOperator());
if (coercedExpression.isPresent()) {
return process(coercedExpression.get(), complement);
}
return super.visitComparisonExpression(node, complement);
}
return super.visitComparisonExpression(node, complement);
}
/**
* Extract a normalized simple comparison between a QualifiedNameReference and a native value if possible.
*/
private Optional toNormalizedSimpleComparison(Map, Type> expressionTypes, ComparisonExpression comparison)
{
Object left = new ExpressionInterpreter(comparison.getLeft(), plannerContext, session, expressionTypes).optimize(NoOpSymbolResolver.INSTANCE);
Object right = new ExpressionInterpreter(comparison.getRight(), plannerContext, session, expressionTypes).optimize(NoOpSymbolResolver.INSTANCE);
Type leftType = expressionTypes.get(NodeRef.of(comparison.getLeft()));
Type rightType = expressionTypes.get(NodeRef.of(comparison.getRight()));
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;
ComparisonExpression.Operator comparisonOperator;
NullableValue value;
if (left instanceof Expression) {
symbolExpression = comparison.getLeft();
comparisonOperator = comparison.getOperator();
value = new NullableValue(rightType, right);
}
else {
symbolExpression = comparison.getRight();
comparisonOperator = comparison.getOperator().flip();
value = new NullableValue(leftType, left);
}
return Optional.of(new NormalizedSimpleComparison(symbolExpression, comparisonOperator, value));
}
private boolean isImplicitCoercion(Map, Type> expressionTypes, Cast cast)
{
Type castSourceType = requireNonNull(expressionTypes.get(NodeRef.of(cast.getExpression())), "No type for Cast source expression");
Type castTargetType = requireNonNull(expressionTypes.get(NodeRef.of(cast)), "No type for Cast expression");
return typeCoercion.canCoerce(castSourceType, castTargetType);
}
private Map, Type> analyzeExpression(Expression expression)
{
return typeAnalyzer.getTypes(session, types, expression);
}
private Optional createVarcharCastToDateComparisonExtractionResult(
NormalizedSimpleComparison comparison,
VarcharType sourceType,
boolean complement,
ComparisonExpression originalExpression)
{
Expression sourceExpression = ((Cast) comparison.getSymbolExpression()).getExpression();
ComparisonExpression.Operator operator = comparison.getComparisonOperator();
NullableValue value = comparison.getValue();
if (complement || value.isNull()) {
return Optional.empty();
}
if (!(sourceExpression instanceof SymbolReference)) {
// Calculation is not useful
return Optional.empty();
}
Symbol sourceSymbol = Symbol.from(sourceExpression);
if (!sourceType.isUnbounded() && sourceType.getBoundedLength() < 10) {
// too short
return Optional.empty();
}
LocalDate date = LocalDate.ofEpochDay(((long) value.getValue()));
if (date.getYear() < 1001 || date.getYear() > 9998) {
// Edge cases. 1-year margin so that we can go to next/prev year for < or > comparisons
return Optional.empty();
}
// superset of possible values, for the "normal case"
ValueSet valueSet;
boolean nullAllowed = false;
switch (operator) {
case EQUAL:
valueSet = dateStringRanges(date, sourceType);
break;
case NOT_EQUAL:
case IS_DISTINCT_FROM:
if (date.getDayOfMonth() < 10) {
// TODO: possible to handle but cumbersome
return Optional.empty();
}
valueSet = ValueSet.all(sourceType).subtract(dateStringRanges(date, sourceType));
nullAllowed = (operator == IS_DISTINCT_FROM);
break;
case LESS_THAN:
case LESS_THAN_OR_EQUAL:
valueSet = ValueSet.ofRanges(Range.lessThan(sourceType, utf8Slice(Integer.toString(date.getYear() + 1))));
break;
case GREATER_THAN:
case GREATER_THAN_OR_EQUAL:
valueSet = ValueSet.ofRanges(Range.greaterThan(sourceType, utf8Slice(Integer.toString(date.getYear() - 1))));
break;
default:
return Optional.empty();
}
// Date representations starting with whitespace, sign or leading zeroes.
valueSet = valueSet.union(ValueSet.ofRanges(
Range.lessThan(sourceType, utf8Slice("1")),
Range.greaterThan(sourceType, utf8Slice("9"))));
return Optional.of(new ExtractionResult(
TupleDomain.withColumnDomains(ImmutableMap.of(sourceSymbol, Domain.create(valueSet, nullAllowed))),
originalExpression));
}
/**
* @return Date representations of the form 2005-09-09, 2005-09-9, 2005-9-09 and 2005-9-9 expanded to ranges:
* {@code [2005-09-09, 2005-09-0:), [2005-09-9, 2005-09-:), [2005-9-09, 2005-9-0:), [2005-9-9, 2005-9-:)}
* (the {@code :} character is the next one after {@code 9}).
*/
private static SortedRangeSet dateStringRanges(LocalDate date, VarcharType domainType)
{
checkArgument(date.getYear() >= 1000 && date.getYear() <= 9999, "Unsupported date: %s", date);
int month = date.getMonthValue();
int day = date.getDayOfMonth();
boolean isMonthSingleDigit = date.getMonthValue() < 10;
boolean isDaySingleDigit = date.getDayOfMonth() < 10;
// A specific date value like 2005-09-10 can be a result of a CAST for number of various forms,
// as the value can have optional sign, leading zeros for the year, and surrounding whitespace,
// E.g. ' +002005-9-9 '.
List valueRanges = new ArrayList<>(4);
for (boolean useSingleDigitMonth : List.of(true, false)) {
for (boolean useSingleDigitDay : List.of(true, false)) {
if (useSingleDigitMonth && !isMonthSingleDigit) {
continue;
}
if (useSingleDigitDay && !isDaySingleDigit) {
continue;
}
String dateString = date.getYear() +
((!useSingleDigitMonth && isMonthSingleDigit) ? "-0" : "-") + month +
((!useSingleDigitDay && isDaySingleDigit) ? "-0" : "-") + day;
String nextStringPrefix = dateString.substring(0, dateString.length() - 1) + (char) (dateString.charAt(dateString.length() - 1) + 1); // cannot overflow
verify(dateString.length() <= domainType.getLength().orElse(Integer.MAX_VALUE), "dateString length exceeds type bounds");
verify(dateString.length() == nextStringPrefix.length(), "Next string length mismatch");
valueRanges.add(Range.range(domainType, utf8Slice(dateString), true, utf8Slice(nextStringPrefix), false));
}
}
return (SortedRangeSet) ValueSet.ofRanges(valueRanges);
}
private static Optional createComparisonExtractionResult(ComparisonExpression.Operator comparisonOperator, Symbol column, Type type, @Nullable Object value, boolean complement)
{
if (value == null) {
switch (comparisonOperator) {
case EQUAL:
case GREATER_THAN:
case GREATER_THAN_OR_EQUAL:
case LESS_THAN:
case LESS_THAN_OR_EQUAL:
case NOT_EQUAL:
return Optional.of(new ExtractionResult(TupleDomain.none(), TRUE_LITERAL));
case IS_DISTINCT_FROM:
Domain domain = complementIfNecessary(Domain.notNull(type), complement);
return Optional.of(new ExtractionResult(
TupleDomain.withColumnDomains(ImmutableMap.of(column, domain)),
TRUE_LITERAL));
}
throw new AssertionError("Unhandled operator: " + comparisonOperator);
}
if (type.isOrderable()) {
return extractOrderableDomain(comparisonOperator, type, value, complement)
.map(domain -> new ExtractionResult(TupleDomain.withColumnDomains(ImmutableMap.of(column, domain)), TRUE_LITERAL));
}
if (type.isComparable()) {
Domain domain = extractEquatableDomain(comparisonOperator, type, value, complement);
return Optional.of(new ExtractionResult(
TupleDomain.withColumnDomains(ImmutableMap.of(column, domain)),
TRUE_LITERAL));
}
throw new AssertionError("Type cannot be used in a comparison expression (should have been caught in analysis): " + type);
}
private static Optional extractOrderableDomain(ComparisonExpression.Operator comparisonOperator, Type type, Object value, boolean complement)
{
checkArgument(value != null);
// Handle orderable types which do not have NaN.
if (!(type instanceof DoubleType) && !(type instanceof RealType)) {
return switch (comparisonOperator) {
case EQUAL -> Optional.of(Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.equal(type, value)), complement), false));
case GREATER_THAN -> Optional.of(Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.greaterThan(type, value)), complement), false));
case GREATER_THAN_OR_EQUAL -> Optional.of(Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.greaterThanOrEqual(type, value)), complement), false));
case LESS_THAN -> Optional.of(Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.lessThan(type, value)), complement), false));
case LESS_THAN_OR_EQUAL -> Optional.of(Domain.create(complementIfNecessary(ValueSet.ofRanges(Range.lessThanOrEqual(type, value)), complement), false));
case NOT_EQUAL ->
Optional.of(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
Optional.of(complementIfNecessary(Domain.create(ValueSet.ofRanges(Range.lessThan(type, value), Range.greaterThan(type, value)), true), complement));
};
}
// Handle comparisons against NaN
if (isFloatingPointNaN(type, value)) {
return switch (comparisonOperator) {
case EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL ->
Optional.of(Domain.create(complementIfNecessary(ValueSet.none(type), complement), false));
case NOT_EQUAL -> Optional.of(Domain.create(complementIfNecessary(ValueSet.all(type), complement), false));
case IS_DISTINCT_FROM ->
// The Domain should be "all but NaN". It is currently not supported.
Optional.empty();
};
}
// Handle comparisons against a non-NaN value when the compared value might be NaN
return switch (comparisonOperator) {
/*
For comparison operators: EQUAL, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL,
the Domain should not contain NaN, but complemented Domain should contain NaN. It is currently not supported.
Currently, NaN is only included when ValueSet.isAll().
For comparison operators: NOT_EQUAL, IS_DISTINCT_FROM,
the Domain should consist of ranges (which do not sum to the whole ValueSet), and NaN.
Currently, NaN is only included when ValueSet.isAll().
*/
case EQUAL -> complement ?
Optional.empty() :
Optional.of(Domain.create(ValueSet.ofRanges(Range.equal(type, value)), false));
case GREATER_THAN -> complement ?
Optional.empty() :
Optional.of(Domain.create(ValueSet.ofRanges(Range.greaterThan(type, value)), false));
case GREATER_THAN_OR_EQUAL -> complement ?
Optional.empty() :
Optional.of(Domain.create(ValueSet.ofRanges(Range.greaterThanOrEqual(type, value)), false));
case LESS_THAN -> complement ?
Optional.empty() :
Optional.of(Domain.create(ValueSet.ofRanges(Range.lessThan(type, value)), false));
case LESS_THAN_OR_EQUAL -> complement ?
Optional.empty() :
Optional.of(Domain.create(ValueSet.ofRanges(Range.lessThanOrEqual(type, value)), false));
case NOT_EQUAL, IS_DISTINCT_FROM -> complement ?
Optional.of(Domain.create(ValueSet.ofRanges(Range.equal(type, value)), false)) :
Optional.empty();
};
}
private static Domain extractEquatableDomain(ComparisonExpression.Operator comparisonOperator, Type type, Object value, boolean complement)
{
checkArgument(value != null);
return switch (comparisonOperator) {
case EQUAL -> Domain.create(complementIfNecessary(ValueSet.of(type, value), complement), false);
case NOT_EQUAL -> Domain.create(complementIfNecessary(ValueSet.of(type, value).complement(), complement), false);
case IS_DISTINCT_FROM -> complementIfNecessary(Domain.create(ValueSet.of(type, value).complement(), true), complement); // Need to potential complement the whole domain for IS_DISTINCT_FROM since it is null-aware
default -> throw new IllegalArgumentException("Unhandled operator: " + comparisonOperator);
};
}
private Optional coerceComparisonWithRounding(
Type symbolExpressionType,
Expression symbolExpression,
NullableValue nullableValue,
ComparisonExpression.Operator comparisonOperator)
{
requireNonNull(nullableValue, "nullableValue is null");
if (nullableValue.isNull()) {
return Optional.empty();
}
Type valueType = nullableValue.getType();
Object value = nullableValue.getValue();
Optional