Please wait. This can take some minutes ...
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.
io.trino.spi.predicate.Range Maven / Gradle / Ivy
/*
* 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.spi.predicate;
import io.trino.spi.connector.ConnectorSession;
import io.trino.spi.type.Type;
import java.lang.invoke.MethodHandle;
import java.util.Objects;
import java.util.Optional;
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.predicate.Utils.TUPLE_DOMAIN_TYPE_OPERATORS;
import static io.trino.spi.predicate.Utils.handleThrowable;
import static io.trino.spi.predicate.Utils.nativeValueToBlock;
import static io.trino.spi.type.TypeUtils.isFloatingPointNaN;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
/**
* A Range of values across the continuous space defined by the types of the Markers
*/
public final class Range
{
private final Type type;
private final boolean lowInclusive;
private final Optional lowValue;
private final boolean highInclusive;
private final Optional highValue;
private final MethodHandle comparisonOperator;
private final boolean isSingleValue;
Range(Type type, boolean lowInclusive, Optional lowValue, boolean highInclusive, Optional highValue, MethodHandle comparisonOperator)
{
requireNonNull(type, "type is null");
this.type = type;
requireNonNull(lowValue, "lowValue is null");
requireNonNull(highValue, "highValue is null");
requireNonNull(comparisonOperator, "comparisonOperator is null");
if (lowValue.isEmpty() && lowInclusive) {
throw new IllegalArgumentException("low bound must be exclusive for low unbounded range");
}
if (highValue.isEmpty() && highInclusive) {
throw new IllegalArgumentException("high bound must be exclusive for high unbounded range");
}
boolean isSingleValue = false;
if (lowValue.isPresent() && highValue.isPresent()) {
int compare = compareValues(comparisonOperator, lowValue.get(), highValue.get());
if (compare > 0) {
throw new IllegalArgumentException("low must be less than or equal to high. Actual: low=" + lowValue.get() + ", high=" + highValue.get());
}
if (compare == 0) {
if (!highInclusive || !lowInclusive) {
throw new IllegalArgumentException("invalid bounds for single value range");
}
isSingleValue = true;
}
}
lowValue.ifPresent(value -> verifyNotNan(type, value));
highValue.ifPresent(value -> verifyNotNan(type, value));
this.lowInclusive = lowInclusive;
this.lowValue = lowValue;
this.highInclusive = highInclusive;
this.highValue = highValue;
this.comparisonOperator = comparisonOperator;
this.isSingleValue = isSingleValue;
}
private static void verifyNotNan(Type type, Object value)
{
if (isFloatingPointNaN(type, value)) {
throw new IllegalArgumentException("cannot use NaN as range bound");
}
}
static MethodHandle getComparisonOperator(Type type)
{
// choice of placing unordered values first or last does not matter for this code
return TUPLE_DOMAIN_TYPE_OPERATORS.getComparisonUnorderedLastOperator(type, simpleConvention(FAIL_ON_NULL, NEVER_NULL, NEVER_NULL));
}
public static Range all(Type type)
{
return new Range(type, false, Optional.empty(), false, Optional.empty(), getComparisonOperator(type));
}
public static Range greaterThan(Type type, Object low)
{
requireNonNull(low, "low is null");
return new Range(type, false, Optional.of(low), false, Optional.empty(), getComparisonOperator(type));
}
public static Range greaterThanOrEqual(Type type, Object low)
{
requireNonNull(low, "low is null");
return new Range(type, true, Optional.of(low), false, Optional.empty(), getComparisonOperator(type));
}
public static Range lessThan(Type type, Object high)
{
requireNonNull(high, "high is null");
return new Range(type, false, Optional.empty(), false, Optional.of(high), getComparisonOperator(type));
}
public static Range lessThanOrEqual(Type type, Object high)
{
requireNonNull(high, "high is null");
return new Range(type, false, Optional.empty(), true, Optional.of(high), getComparisonOperator(type));
}
public static Range equal(Type type, Object value)
{
requireNonNull(value, "value is null");
Optional valueAsOptional = Optional.of(value);
return new Range(type, true, valueAsOptional, true, valueAsOptional, getComparisonOperator(type));
}
public static Range range(Type type, Object low, boolean lowInclusive, Object high, boolean highInclusive)
{
requireNonNull(low, "low is null");
requireNonNull(high, "high is null");
return new Range(type, lowInclusive, Optional.of(low), highInclusive, Optional.of(high), getComparisonOperator(type));
}
public Type getType()
{
return type;
}
public boolean isLowInclusive()
{
return lowInclusive;
}
public boolean isLowUnbounded()
{
return lowValue.isEmpty();
}
public Object getLowBoundedValue()
{
return lowValue.orElseThrow(() -> new IllegalStateException("The range is low-unbounded"));
}
public Optional getLowValue()
{
return lowValue;
}
public boolean isHighInclusive()
{
return highInclusive;
}
public boolean isHighUnbounded()
{
return highValue.isEmpty();
}
public Object getHighBoundedValue()
{
return highValue.orElseThrow(() -> new IllegalStateException("The range is high-unbounded"));
}
public Optional getHighValue()
{
return highValue;
}
public boolean isSingleValue()
{
return isSingleValue;
}
public Object getSingleValue()
{
if (!isSingleValue()) {
throw new IllegalStateException("Range does not have just a single value");
}
return lowValue.orElseThrow();
}
public boolean isAll()
{
return lowValue.isEmpty() && highValue.isEmpty();
}
public boolean contains(Range other)
{
checkTypeCompatibility(other);
return compareLowBound(other) <= 0 &&
compareHighBound(other) >= 0;
}
public Range span(Range other)
{
checkTypeCompatibility(other);
int compareLowBound = compareLowBound(other);
int compareHighBound = compareHighBound(other);
return new Range(
type,
compareLowBound <= 0 ? this.lowInclusive : other.lowInclusive,
compareLowBound <= 0 ? this.lowValue : other.lowValue,
compareHighBound >= 0 ? this.highInclusive : other.highInclusive,
compareHighBound >= 0 ? this.highValue : other.highValue,
comparisonOperator);
}
public Optional intersect(Range other)
{
checkTypeCompatibility(other);
if (!this.overlaps(other)) {
return Optional.empty();
}
int compareLowBound = compareLowBound(other);
int compareHighBound = compareHighBound(other);
return Optional.of(new Range(
type,
compareLowBound >= 0 ? this.lowInclusive : other.lowInclusive,
compareLowBound >= 0 ? this.lowValue : other.lowValue,
compareHighBound <= 0 ? this.highInclusive : other.highInclusive,
compareHighBound <= 0 ? this.highValue : other.highValue,
comparisonOperator));
}
public boolean overlaps(Range other)
{
checkTypeCompatibility(other);
return !this.isFullyBefore(other) && !other.isFullyBefore(this);
}
/**
* Returns unioned range if {@code this} and {@code next} overlap or are adjacent.
* The {@code next} lower bound must not be before {@code this} lower bound.
*/
Optional tryMergeWithNext(Range next)
{
if (this.compareLowBound(next) > 0) {
throw new IllegalArgumentException("next before this");
}
if (this.isHighUnbounded()) {
return Optional.of(this);
}
boolean merge;
if (next.isLowUnbounded()) {
// both are low-unbounded
merge = true;
}
else {
int compare = compareValues(comparisonOperator, this.highValue.orElseThrow(), next.lowValue.orElseThrow());
merge = compare > 0 // overlap
|| compare == 0 && (this.highInclusive || next.lowInclusive); // adjacent
}
if (merge) {
int compareHighBound = compareHighBound(next);
return Optional.of(new Range(
this.type,
this.lowInclusive,
this.lowValue,
// max of high bounds
compareHighBound <= 0 ? next.highInclusive : this.highInclusive,
compareHighBound <= 0 ? next.highValue : this.highValue,
comparisonOperator));
}
return Optional.empty();
}
private boolean isFullyBefore(Range other)
{
if (this.isHighUnbounded()) {
return false;
}
if (other.isLowUnbounded()) {
return false;
}
int compare = compareValues(comparisonOperator, this.highValue.orElseThrow(), other.lowValue.orElseThrow());
if (compare < 0) {
return true;
}
if (compare == 0) {
return !(this.highInclusive && other.lowInclusive);
}
return false;
}
private void checkTypeCompatibility(Range range)
{
if (!getType().equals(range.getType())) {
throw new IllegalArgumentException(format("Mismatched Range types: %s vs %s", getType(), range.getType()));
}
}
int compareLowBound(Range other)
{
if (this.isLowUnbounded() || other.isLowUnbounded()) {
return Boolean.compare(!this.isLowUnbounded(), !other.isLowUnbounded());
}
int compare = compareValues(comparisonOperator, this.lowValue.orElseThrow(), other.lowValue.orElseThrow());
if (compare != 0) {
return compare;
}
return Boolean.compare(!this.lowInclusive, !other.lowInclusive);
}
private int compareHighBound(Range other)
{
if (this.isHighUnbounded() || other.isHighUnbounded()) {
return Boolean.compare(this.isHighUnbounded(), other.isHighUnbounded());
}
int compare = compareValues(comparisonOperator, this.highValue.orElseThrow(), other.highValue.orElseThrow());
if (compare != 0) {
return compare;
}
return Boolean.compare(this.highInclusive, other.highInclusive);
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Range range = (Range) o;
return lowInclusive == range.lowInclusive &&
highInclusive == range.highInclusive &&
type.equals(range.type) &&
valuesEqual(lowValue, range.lowValue) &&
valuesEqual(highValue, range.highValue);
}
private boolean valuesEqual(Optional a, Optional b)
{
if (a.isEmpty() || b.isEmpty()) {
return a.isEmpty() == b.isEmpty();
}
return compareValues(comparisonOperator, a.get(), b.get()) == 0;
}
private static int compareValues(MethodHandle comparisonOperator, Object left, Object right)
{
try {
return (int) (long) comparisonOperator.invoke(left, right);
}
catch (Throwable throwable) {
throw handleThrowable(throwable);
}
}
@Override
public int hashCode()
{
return Objects.hash(type, lowInclusive, lowValue, highInclusive, highValue);
}
@Override
public String toString()
{
return toString(ToStringSession.INSTANCE);
}
public String toString(ConnectorSession session)
{
Object lowObject = lowValue
.map(value -> type.getObjectValue(session, nativeValueToBlock(type, value), 0))
.orElse("");
if (isSingleValue()) {
return format("[%s]", lowObject);
}
Object highObject = highValue
.map(value -> type.getObjectValue(session, nativeValueToBlock(type, value), 0))
.orElse("");
return format(
"%s%s, %s%s",
lowInclusive ? "[" : "(",
lowObject,
highObject,
highInclusive ? "]" : ")");
}
}