com.facebook.presto.spi.predicate.SortedRangeSet 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 com.facebook.presto.spi.predicate;
import com.facebook.presto.spi.ConnectorSession;
import com.facebook.presto.spi.type.Type;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Objects;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
/**
* A set containing zero or more Ranges of the same type over a continuous space of possible values.
* Ranges are coalesced into the most compact representation of non-overlapping Ranges. This structure
* allows iteration across these compacted Ranges in increasing order, as well as other common
* set-related operation.
*/
public final class SortedRangeSet
implements ValueSet
{
private final Type type;
private final NavigableMap lowIndexedRanges;
private SortedRangeSet(Type type, NavigableMap lowIndexedRanges)
{
requireNonNull(type, "type is null");
requireNonNull(lowIndexedRanges, "lowIndexedRanges is null");
if (!type.isOrderable()) {
throw new IllegalArgumentException("Type is not orderable: " + type);
}
this.type = type;
this.lowIndexedRanges = lowIndexedRanges;
}
static SortedRangeSet none(Type type)
{
return copyOf(type, Collections.emptyList());
}
static SortedRangeSet all(Type type)
{
return copyOf(type, Collections.singletonList(Range.all(type)));
}
/**
* Provided discrete values that are unioned together to form the SortedRangeSet
*/
static SortedRangeSet of(Type type, Object first, Object... rest)
{
List ranges = new ArrayList<>(rest.length + 1);
ranges.add(Range.equal(type, first));
for (Object value : rest) {
ranges.add(Range.equal(type, value));
}
return copyOf(type, ranges);
}
/**
* Provided Ranges are unioned together to form the SortedRangeSet
*/
static SortedRangeSet of(Range first, Range... rest)
{
List rangeList = new ArrayList<>(rest.length + 1);
rangeList.add(first);
for (Range range : rest) {
rangeList.add(range);
}
return copyOf(first.getType(), rangeList);
}
/**
* Provided Ranges are unioned together to form the SortedRangeSet
*/
static SortedRangeSet copyOf(Type type, Iterable ranges)
{
return new Builder(type).addAll(ranges).build();
}
@JsonCreator
public static SortedRangeSet copyOf(
@JsonProperty("type") Type type,
@JsonProperty("ranges") List ranges)
{
return copyOf(type, (Iterable) ranges);
}
@Override
@JsonProperty
public Type getType()
{
return type;
}
@JsonProperty("ranges")
public List getOrderedRanges()
{
return new ArrayList<>(lowIndexedRanges.values());
}
public int getRangeCount()
{
return lowIndexedRanges.size();
}
@Override
public boolean isNone()
{
return lowIndexedRanges.isEmpty();
}
@Override
public boolean isAll()
{
return lowIndexedRanges.size() == 1 && lowIndexedRanges.values().iterator().next().isAll();
}
@Override
public boolean isSingleValue()
{
return lowIndexedRanges.size() == 1 && lowIndexedRanges.values().iterator().next().isSingleValue();
}
@Override
public Object getSingleValue()
{
if (!isSingleValue()) {
throw new IllegalStateException("SortedRangeSet does not have just a single value");
}
return lowIndexedRanges.values().iterator().next().getSingleValue();
}
@Override
public boolean containsValue(Object value)
{
return includesMarker(Marker.exactly(type, value));
}
boolean includesMarker(Marker marker)
{
requireNonNull(marker, "marker is null");
checkTypeCompatibility(marker);
Map.Entry floorEntry = lowIndexedRanges.floorEntry(marker);
return floorEntry != null && floorEntry.getValue().includes(marker);
}
public Range getSpan()
{
if (lowIndexedRanges.isEmpty()) {
throw new IllegalStateException("Can not get span if no ranges exist");
}
return lowIndexedRanges.firstEntry().getValue().span(lowIndexedRanges.lastEntry().getValue());
}
@Override
public Ranges getRanges()
{
return new Ranges()
{
@Override
public int getRangeCount()
{
return SortedRangeSet.this.getRangeCount();
}
@Override
public List getOrderedRanges()
{
return SortedRangeSet.this.getOrderedRanges();
}
@Override
public Range getSpan()
{
return SortedRangeSet.this.getSpan();
}
};
}
@Override
public ValuesProcessor getValuesProcessor()
{
return new ValuesProcessor()
{
@Override
public T transform(Function rangesFunction, Function valuesFunction, Function allOrNoneFunction)
{
return rangesFunction.apply(getRanges());
}
@Override
public void consume(Consumer rangesConsumer, Consumer valuesConsumer, Consumer allOrNoneConsumer)
{
rangesConsumer.accept(getRanges());
}
};
}
@Override
public SortedRangeSet intersect(ValueSet other)
{
SortedRangeSet otherRangeSet = checkCompatibility(other);
Builder builder = new Builder(type);
Iterator iterator1 = getOrderedRanges().iterator();
Iterator iterator2 = otherRangeSet.getOrderedRanges().iterator();
if (iterator1.hasNext() && iterator2.hasNext()) {
Range range1 = iterator1.next();
Range range2 = iterator2.next();
while (true) {
if (range1.overlaps(range2)) {
builder.add(range1.intersect(range2));
}
if (range1.getHigh().compareTo(range2.getHigh()) <= 0) {
if (!iterator1.hasNext()) {
break;
}
range1 = iterator1.next();
}
else {
if (!iterator2.hasNext()) {
break;
}
range2 = iterator2.next();
}
}
}
return builder.build();
}
@Override
public SortedRangeSet union(ValueSet other)
{
SortedRangeSet otherRangeSet = checkCompatibility(other);
return new Builder(type)
.addAll(this.getOrderedRanges())
.addAll(otherRangeSet.getOrderedRanges())
.build();
}
@Override
public SortedRangeSet union(Collection valueSets)
{
Builder builder = new Builder(type);
builder.addAll(this.getOrderedRanges());
for (ValueSet valueSet : valueSets) {
builder.addAll(checkCompatibility(valueSet).getOrderedRanges());
}
return builder.build();
}
@Override
public SortedRangeSet complement()
{
Builder builder = new Builder(type);
if (lowIndexedRanges.isEmpty()) {
return builder.add(Range.all(type)).build();
}
Iterator rangeIterator = lowIndexedRanges.values().iterator();
Range firstRange = rangeIterator.next();
if (!firstRange.getLow().isLowerUnbounded()) {
builder.add(new Range(Marker.lowerUnbounded(type), firstRange.getLow().lesserAdjacent()));
}
Range previousRange = firstRange;
while (rangeIterator.hasNext()) {
Range currentRange = rangeIterator.next();
Marker lowMarker = previousRange.getHigh().greaterAdjacent();
Marker highMarker = currentRange.getLow().lesserAdjacent();
builder.add(new Range(lowMarker, highMarker));
previousRange = currentRange;
}
Range lastRange = previousRange;
if (!lastRange.getHigh().isUpperUnbounded()) {
builder.add(new Range(lastRange.getHigh().greaterAdjacent(), Marker.upperUnbounded(type)));
}
return builder.build();
}
private SortedRangeSet checkCompatibility(ValueSet other)
{
if (!getType().equals(other.getType())) {
throw new IllegalStateException(String.format("Mismatched types: %s vs %s", getType(), other.getType()));
}
if (!(other instanceof SortedRangeSet)) {
throw new IllegalStateException(String.format("ValueSet is not a SortedRangeSet: %s", other.getClass()));
}
return (SortedRangeSet) other;
}
private void checkTypeCompatibility(Marker marker)
{
if (!getType().equals(marker.getType())) {
throw new IllegalStateException(String.format("Marker of %s does not match SortedRangeSet of %s", marker.getType(), getType()));
}
}
@Override
public int hashCode()
{
return Objects.hash(lowIndexedRanges);
}
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final SortedRangeSet other = (SortedRangeSet) obj;
return Objects.equals(this.lowIndexedRanges, other.lowIndexedRanges);
}
@Override
public String toString(ConnectorSession session)
{
return "[" + lowIndexedRanges.values().stream()
.map(range -> range.toString(session))
.collect(Collectors.joining(", ")) + "]";
}
static class Builder
{
private final Type type;
private final List ranges = new ArrayList<>();
Builder(Type type)
{
requireNonNull(type, "type is null");
if (!type.isOrderable()) {
throw new IllegalArgumentException("Type is not orderable: " + type);
}
this.type = type;
}
Builder add(Range range)
{
if (!type.equals(range.getType())) {
throw new IllegalArgumentException(String.format("Range type %s does not match builder type %s", range.getType(), type));
}
ranges.add(range);
return this;
}
Builder addAll(Iterable ranges)
{
for (Range range : ranges) {
add(range);
}
return this;
}
SortedRangeSet build()
{
Collections.sort(ranges, Comparator.comparing(Range::getLow));
NavigableMap result = new TreeMap<>();
Range current = null;
for (Range next : ranges) {
if (current == null) {
current = next;
continue;
}
if (current.overlaps(next) || current.getHigh().isAdjacent(next.getLow())) {
current = current.span(next);
}
else {
result.put(current.getLow(), current);
current = next;
}
}
if (current != null) {
result.put(current.getLow(), current);
}
return new SortedRangeSet(type, result);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy