com.facebook.presto.jdbc.internal.common.predicate.Domain 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.jdbc.internal.common.predicate;
import com.facebook.presto.jdbc.internal.common.function.SqlFunctionProperties;
import com.facebook.presto.jdbc.internal.common.type.Type;
import com.facebook.presto.jdbc.internal.jackson.annotation.JsonCreator;
import com.facebook.presto.jdbc.internal.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static java.util.Objects.requireNonNull;
/**
* Defines the possible values of a single variable in terms of its valid scalar values and nullability.
*
* For example:
*
*
* - Domain.none() => no scalar values allowed, NULL not allowed
*
- Domain.all() => all scalar values allowed, NULL allowed
*
- Domain.onlyNull() => no scalar values allowed, NULL allowed
*
- Domain.notNull() => all scalar values allowed, NULL not allowed
*
*
*/
public final class Domain
{
private final ValueSet values;
private final boolean nullAllowed;
private Domain(ValueSet values, boolean nullAllowed)
{
this.values = requireNonNull(values, "values is null");
this.nullAllowed = nullAllowed;
}
@JsonCreator
public static Domain create(
@JsonProperty("values") ValueSet values,
@JsonProperty("nullAllowed") boolean nullAllowed)
{
return new Domain(values, nullAllowed);
}
public static Domain none(Type type)
{
return new Domain(ValueSet.none(type), false);
}
public static Domain all(Type type)
{
return new Domain(ValueSet.all(type), true);
}
public static Domain onlyNull(Type type)
{
return new Domain(ValueSet.none(type), true);
}
public static Domain notNull(Type type)
{
return new Domain(ValueSet.all(type), false);
}
public static Domain singleValue(Type type, Object value)
{
return new Domain(ValueSet.of(type, value), false);
}
public static Domain multipleValues(Type type, List> values)
{
if (values.isEmpty()) {
throw new IllegalArgumentException("values cannot be empty");
}
if (values.size() == 1) {
return singleValue(type, values.get(0));
}
return new Domain(ValueSet.of(type, values.get(0), values.subList(1, values.size()).toArray()), false);
}
public Type getType()
{
return values.getType();
}
@JsonProperty
public ValueSet getValues()
{
return values;
}
@JsonProperty
public boolean isNullAllowed()
{
return nullAllowed;
}
public boolean isNone()
{
return values.isNone() && !nullAllowed;
}
public boolean isAll()
{
return values.isAll() && nullAllowed;
}
public boolean isSingleValue()
{
return !nullAllowed && values.isSingleValue();
}
public boolean isNullableSingleValue()
{
if (nullAllowed) {
return values.isNone();
}
else {
return values.isSingleValue();
}
}
public boolean isOnlyNull()
{
return values.isNone() && nullAllowed;
}
public Object getSingleValue()
{
if (!isSingleValue()) {
throw new IllegalStateException("Domain is not a single value");
}
return values.getSingleValue();
}
public Object getNullableSingleValue()
{
if (!isNullableSingleValue()) {
throw new IllegalStateException("Domain is not a nullable single value");
}
if (nullAllowed) {
return null;
}
else {
return values.getSingleValue();
}
}
public boolean includesNullableValue(Object value)
{
return value == null ? nullAllowed : values.containsValue(value);
}
public boolean overlaps(Domain other)
{
checkCompatibility(other);
return !this.intersect(other).isNone();
}
public boolean contains(Domain other)
{
checkCompatibility(other);
return this.union(other).equals(this);
}
public Domain intersect(Domain other)
{
checkCompatibility(other);
return new Domain(values.intersect(other.getValues()), this.isNullAllowed() && other.isNullAllowed());
}
public Domain union(Domain other)
{
checkCompatibility(other);
return new Domain(values.union(other.getValues()), this.isNullAllowed() || other.isNullAllowed());
}
public static Domain union(List domains)
{
if (domains.isEmpty()) {
throw new IllegalArgumentException("domains cannot be empty for union");
}
if (domains.size() == 1) {
return domains.get(0);
}
boolean nullAllowed = false;
List valueSets = new ArrayList<>(domains.size());
for (Domain domain : domains) {
valueSets.add(domain.getValues());
nullAllowed = nullAllowed || domain.nullAllowed;
}
ValueSet unionedValues = valueSets.get(0).union(valueSets.subList(1, valueSets.size()));
return new Domain(unionedValues, nullAllowed);
}
public Domain complement()
{
return new Domain(values.complement(), !nullAllowed);
}
public Domain subtract(Domain other)
{
checkCompatibility(other);
return new Domain(values.subtract(other.getValues()), this.isNullAllowed() && !other.isNullAllowed());
}
private void checkCompatibility(Domain domain)
{
if (!getType().equals(domain.getType())) {
throw new IllegalArgumentException(String.format("Mismatched Domain types: %s vs %s", getType(), domain.getType()));
}
if (values.getClass() != domain.values.getClass()) {
throw new IllegalArgumentException(String.format("Mismatched Domain value set classes: %s vs %s", values.getClass(), domain.values.getClass()));
}
}
@Override
public int hashCode()
{
return Objects.hash(values, nullAllowed);
}
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Domain other = (Domain) obj;
return Objects.equals(this.values, other.values) &&
this.nullAllowed == other.nullAllowed;
}
/**
* Reduces the number of discrete components in the Domain if there are too many.
*/
public Domain simplify()
{
ValueSet simplifiedValueSet = values.getValuesProcessor().>transform(
ranges -> {
if (ranges.getOrderedRanges().size() <= 32) {
return Optional.empty();
}
return Optional.of(ValueSet.ofRanges(ranges.getSpan()));
},
discreteValues -> {
if (discreteValues.getValues().size() <= 32) {
return Optional.empty();
}
return Optional.of(ValueSet.all(values.getType()));
},
allOrNone -> Optional.empty())
.orElse(values);
return Domain.create(simplifiedValueSet, nullAllowed);
}
/**
* @return A canonicalized Domain with a consistent representation.
*
* When removeSafeConstants is true, we return a Domain with all point constants removed,
* and range constants are kept.
* Example:
* `x = 1` is equivalent to `x = 1000`
* `x >= 1` is NOT equivalent to `x >= 1000`
*
* All types and bounds information is preserved.
*/
public Domain canonicalize(boolean removeConstants)
{
return new Domain(values.canonicalize(removeConstants), nullAllowed);
}
public String toString(SqlFunctionProperties properties)
{
return "[ " + (nullAllowed ? "NULL, " : "") + values.toString(properties) + " ]";
}
}