/* * 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.spi.predicate; import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.block.Block; import com.facebook.presto.spi.type.Type; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Objects; import java.util.Optional; import static java.util.Objects.requireNonNull; /** * A point on the continuous space defined by the specified type. * Each point may be just below, exact, or just above the specified value according to the Bound. */ public final class Marker implements Comparable<Marker> { public enum Bound { BELOW, // lower than the value, but infinitesimally close to the value EXACTLY, // exactly the value ABOVE // higher than the value, but infinitesimally close to the value } private final Type type; private final Optional<Block> valueBlock; private final Bound bound; /** * LOWER UNBOUNDED is specified with an empty value and a ABOVE bound * UPPER UNBOUNDED is specified with an empty value and a BELOW bound */ @JsonCreator public Marker( @JsonProperty("type") Type type, @JsonProperty("valueBlock") Optional<Block> valueBlock, @JsonProperty("bound") Bound bound) { requireNonNull(type, "type is null"); requireNonNull(valueBlock, "valueBlock is null"); requireNonNull(bound, "bound is null"); if (!type.isOrderable()) { throw new IllegalArgumentException("type must be orderable"); } if (!valueBlock.isPresent() && bound == Bound.EXACTLY) { throw new IllegalArgumentException("Can not be equal to unbounded"); } if (valueBlock.isPresent() && valueBlock.get().getPositionCount() != 1) { throw new IllegalArgumentException("value block should only have one position"); } this.type = type; this.valueBlock = valueBlock; this.bound = bound; } private static Marker create(Type type, Optional<Object> value, Bound bound) { return new Marker(type, value.map(object -> Utils.nativeValueToBlock(type, object)), bound); } public static Marker upperUnbounded(Type type) { requireNonNull(type, "type is null"); return create(type, Optional.empty(), Bound.BELOW); } public static Marker lowerUnbounded(Type type) { requireNonNull(type, "type is null"); return create(type, Optional.empty(), Bound.ABOVE); } public static Marker above(Type type, Object value) { requireNonNull(type, "type is null"); requireNonNull(value, "value is null"); return create(type, Optional.of(value), Bound.ABOVE); } public static Marker exactly(Type type, Object value) { requireNonNull(type, "type is null"); requireNonNull(value, "value is null"); return create(type, Optional.of(value), Bound.EXACTLY); } public static Marker below(Type type, Object value) { requireNonNull(type, "type is null"); requireNonNull(value, "value is null"); return create(type, Optional.of(value), Bound.BELOW); } @JsonProperty public Type getType() { return type; } @JsonProperty public Optional<Block> getValueBlock() { return valueBlock; } public Object getValue() { if (!valueBlock.isPresent()) { throw new IllegalStateException("No value to get"); } return Utils.blockToNativeValue(type, valueBlock.get()); } public Object getPrintableValue(ConnectorSession session) { if (!valueBlock.isPresent()) { throw new IllegalStateException("No value to get"); } return type.getObjectValue(session, valueBlock.get(), 0); } @JsonProperty public Bound getBound() { return bound; } public boolean isUpperUnbounded() { return !valueBlock.isPresent() && bound == Bound.BELOW; } public boolean isLowerUnbounded() { return !valueBlock.isPresent() && bound == Bound.ABOVE; } private void checkTypeCompatibility(Marker marker) { if (!type.equals(marker.getType())) { throw new IllegalArgumentException(String.format("Mismatched Marker types: %s vs %s", type, marker.getType())); } } /** * Adjacency is defined by two Markers being infinitesimally close to each other. * This means they must share the same value and have adjacent Bounds. */ public boolean isAdjacent(Marker other) { checkTypeCompatibility(other); if (isUpperUnbounded() || isLowerUnbounded() || other.isUpperUnbounded() || other.isLowerUnbounded()) { return false; } if (type.compareTo(valueBlock.get(), 0, other.valueBlock.get(), 0) != 0) { return false; } return (bound == Bound.EXACTLY && other.bound != Bound.EXACTLY) || (bound != Bound.EXACTLY && other.bound == Bound.EXACTLY); } public Marker greaterAdjacent() { if (!valueBlock.isPresent()) { throw new IllegalStateException("No marker adjacent to unbounded"); } switch (bound) { case BELOW: return new Marker(type, valueBlock, Bound.EXACTLY); case EXACTLY: return new Marker(type, valueBlock, Bound.ABOVE); case ABOVE: throw new IllegalStateException("No greater marker adjacent to an ABOVE bound"); default: throw new AssertionError("Unsupported type: " + bound); } } public Marker lesserAdjacent() { if (!valueBlock.isPresent()) { throw new IllegalStateException("No marker adjacent to unbounded"); } switch (bound) { case BELOW: throw new IllegalStateException("No lesser marker adjacent to a BELOW bound"); case EXACTLY: return new Marker(type, valueBlock, Bound.BELOW); case ABOVE: return new Marker(type, valueBlock, Bound.EXACTLY); default: throw new AssertionError("Unsupported type: " + bound); } } @Override public int compareTo(Marker o) { checkTypeCompatibility(o); if (isUpperUnbounded()) { return o.isUpperUnbounded() ? 0 : 1; } if (isLowerUnbounded()) { return o.isLowerUnbounded() ? 0 : -1; } if (o.isUpperUnbounded()) { return -1; } if (o.isLowerUnbounded()) { return 1; } // INVARIANT: value and o.value are present int compare = type.compareTo(valueBlock.get(), 0, o.valueBlock.get(), 0); if (compare == 0) { if (bound == o.bound) { return 0; } if (bound == Bound.BELOW) { return -1; } if (bound == Bound.ABOVE) { return 1; } // INVARIANT: bound == EXACTLY return (o.bound == Bound.BELOW) ? 1 : -1; } return compare; } public static Marker min(Marker marker1, Marker marker2) { return marker1.compareTo(marker2) <= 0 ? marker1 : marker2; } public static Marker max(Marker marker1, Marker marker2) { return marker1.compareTo(marker2) >= 0 ? marker1 : marker2; } @Override public int hashCode() { int hash = Objects.hash(type, bound); if (valueBlock.isPresent()) { hash = hash * 31 + (int) type.hash(valueBlock.get(), 0); } return hash; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Marker other = (Marker) obj; return Objects.equals(this.type, other.type) && Objects.equals(this.bound, other.bound) && ((this.valueBlock.isPresent()) == (other.valueBlock.isPresent())) && (!this.valueBlock.isPresent() || type.equalTo(this.valueBlock.get(), 0, other.valueBlock.get(), 0)); } public String toString(ConnectorSession session) { StringBuilder buffer = new StringBuilder("{"); buffer.append("type=").append(type); buffer.append(", value="); if (isLowerUnbounded()) { buffer.append("<min>"); } else if (isUpperUnbounded()) { buffer.append("<max>"); } else { buffer.append(getPrintableValue(session)); } buffer.append(", bound=").append(bound); buffer.append("}"); return buffer.toString(); } }