io.prestosql.spi.predicate.TupleDomain 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.prestosql.spi.predicate;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.prestosql.spi.connector.ConnectorSession;
import io.prestosql.spi.type.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.stream.Collector;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
/**
* Defines a set of valid tuples according to the constraints on each of its constituent columns
*/
public final class TupleDomain
{
private static final TupleDomain> NONE = new TupleDomain<>(Optional.empty());
private static final TupleDomain> ALL = new TupleDomain<>(Optional.of(emptyMap()));
/**
* TupleDomain is internally represented as a normalized map of each column to its
* respective allowable value Domain. Conceptually, these Domains can be thought of
* as being AND'ed together to form the representative predicate.
*
* This map is normalized in the following ways:
* 1) The map will not contain Domain.none() as any of its values. If any of the Domain
* values are Domain.none(), then the whole map will instead be null. This enforces the fact that
* any single Domain.none() value effectively turns this TupleDomain into "none" as well.
* 2) The map will not contain Domain.all() as any of its values. Our convention here is that
* any unmentioned column is equivalent to having Domain.all(). To normalize this structure,
* we remove any Domain.all() values from the map.
*/
private final Optional
* In the above resulting TupleDomain, tuple (a => NaN) would be considered valid but would
* not be valid for either TupleDomain X or TupleDomain Y.
* However, this result is guaranteed to be a superset of the strict union.
*/
public static TupleDomain columnWiseUnion(List> tupleDomains)
{
if (tupleDomains.isEmpty()) {
throw new IllegalArgumentException("tupleDomains must have at least one element");
}
if (tupleDomains.size() == 1) {
return tupleDomains.get(0);
}
// gather all common columns
Set commonColumns = new HashSet<>();
// first, find a non-none domain
boolean found = false;
Iterator> domains = tupleDomains.iterator();
while (domains.hasNext()) {
TupleDomain domain = domains.next();
if (!domain.isNone()) {
found = true;
commonColumns.addAll(domain.getDomains().get().keySet());
break;
}
}
if (!found) {
return TupleDomain.none();
}
// then, get the common columns
while (domains.hasNext()) {
TupleDomain domain = domains.next();
if (!domain.isNone()) {
commonColumns.retainAll(domain.getDomains().get().keySet());
}
}
// group domains by column (only for common columns)
Map> domainsByColumn = new LinkedHashMap<>(tupleDomains.size());
for (TupleDomain domain : tupleDomains) {
if (!domain.isNone()) {
for (Map.Entry entry : domain.getDomains().get().entrySet()) {
if (commonColumns.contains(entry.getKey())) {
List domainForColumn = domainsByColumn.get(entry.getKey());
if (domainForColumn == null) {
domainForColumn = new ArrayList<>();
domainsByColumn.put(entry.getKey(), domainForColumn);
}
domainForColumn.add(entry.getValue());
}
}
}
}
// finally, do the column-wise union
Map result = new LinkedHashMap<>(domainsByColumn.size());
for (Map.Entry> entry : domainsByColumn.entrySet()) {
result.put(entry.getKey(), Domain.union(entry.getValue()));
}
return withColumnDomains(result);
}
/**
* Returns true only if there exists a strict intersection between the TupleDomains.
* i.e. there exists some potential tuple that would be allowable in both TupleDomains.
*/
public boolean overlaps(TupleDomain other)
{
return !this.intersect(other).isNone();
}
/**
* Returns true only if the this TupleDomain contains all possible tuples that would be allowable by
* the other TupleDomain.
*/
public boolean contains(TupleDomain other)
{
return other.isNone() || columnWiseUnion(this, other).equals(this);
}
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TupleDomain> other = (TupleDomain>) obj;
return Objects.equals(this.domains, other.domains);
}
@Override
public int hashCode()
{
return Objects.hash(domains);
}
@Override
public String toString()
{
if (isAll()) {
return "TupleDomain{ALL}";
}
if (isNone()) {
return "TupleDomain{NONE}";
}
return "TupleDomain{...}";
}
public String toString(ConnectorSession session)
{
StringBuilder buffer = new StringBuilder();
if (isAll()) {
buffer.append("ALL");
}
else if (isNone()) {
buffer.append("NONE");
}
else {
buffer.append(domains.get().entrySet().stream()
.collect(toLinkedMap(Map.Entry::getKey, entry -> entry.getValue().toString(session))));
}
return buffer.toString();
}
public TupleDomain filter(BiPredicate predicate)
{
requireNonNull(predicate, "predicate is null");
return transformDomains((key, domain) -> {
if (!predicate.test(key, domain)) {
return Domain.all(domain.getType());
}
return domain;
});
}
public TupleDomain transform(Function function)
{
if (isNone()) {
return none();
}
if (isAll()) {
return all();
}
Map domains = this.domains.orElseThrow();
HashMap result = new LinkedHashMap<>(domains.size());
for (Map.Entry entry : domains.entrySet()) {
U key = function.apply(entry.getKey());
if (key == null) {
continue;
}
Domain previous = result.put(key, entry.getValue());
if (previous != null) {
throw new IllegalArgumentException(format("Every argument must have a unique mapping. %s maps to %s and %s", entry.getKey(), entry.getValue(), previous));
}
}
return TupleDomain.withColumnDomains(result);
}
public TupleDomain simplify()
{
return transformDomains((key, domain) -> domain.simplify());
}
public TupleDomain simplify(int threshold)
{
return transformDomains((key, domain) -> domain.simplify(threshold));
}
public TupleDomain transformDomains(BiFunction transformation)
{
requireNonNull(transformation, "transformation is null");
if (isNone() || isAll()) {
return this;
}
return withColumnDomains(domains.get().entrySet().stream()
.collect(toLinkedMap(
Map.Entry::getKey,
entry -> {
Domain newDomain = transformation.apply(entry.getKey(), entry.getValue());
return requireNonNull(newDomain, "newDomain is null");
})));
}
// Available for Jackson serialization only!
public static class ColumnDomain
{
private final C column;
private final Domain domain;
@JsonCreator
public ColumnDomain(
@JsonProperty("column") C column,
@JsonProperty("domain") Domain domain)
{
this.column = requireNonNull(column, "column is null");
this.domain = requireNonNull(domain, "domain is null");
}
@JsonProperty
public C getColumn()
{
return column;
}
@JsonProperty
public Domain getDomain()
{
return domain;
}
}
private static Collector> toLinkedMap(Function super T, ? extends K> keyMapper, Function super T, ? extends U> valueMapper)
{
return toMap(
keyMapper,
valueMapper,
(u, v) -> { throw new IllegalStateException(format("Duplicate values for a key: %s and %s", u, v)); },
LinkedHashMap::new);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy