com.hazelcast.shaded.org.apache.calcite.util.Sarg Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you 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.hazelcast.shaded.org.apache.calcite.util;
import com.hazelcast.shaded.org.apache.calcite.linq4j.Ord;
import com.hazelcast.shaded.org.apache.calcite.rex.RexUnknownAs;
import com.hazelcast.shaded.org.apache.calcite.sql.fun.SqlStdOperatorTable;
import com.hazelcast.shaded.com.google.common.collect.ImmutableRangeSet;
import com.hazelcast.shaded.com.google.common.collect.Iterables;
import com.hazelcast.shaded.com.google.common.collect.Range;
import com.hazelcast.shaded.com.google.common.collect.RangeSet;
import com.hazelcast.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import java.util.function.BiConsumer;
import static java.util.Objects.requireNonNull;
/** Set of values (or ranges) that are the target of a search.
*
* The name is derived from Search argument, an ancient
* concept in database implementation; see Access Path Selection in a Relational
* Database Management System — Selinger et al. 1979 or the
* "morning
* paper summary.
*
*
In RexNode, a Sarg only occur as the right-hand operand in a call to
* {@link SqlStdOperatorTable#SEARCH}, wrapped in a
* {@link com.hazelcast.shaded.org.apache.calcite.rex.RexLiteral}. Lifecycle methods:
*
*
* - {@link com.hazelcast.shaded.org.apache.calcite.rex.RexUtil#expandSearch} removes
* calls to SEARCH and the included Sarg, converting them to comparisons;
*
- {@link com.hazelcast.shaded.org.apache.calcite.rex.RexSimplify} converts complex comparisons
* on the same argument into SEARCH calls with an included Sarg;
*
- Various {@link com.hazelcast.shaded.org.apache.calcite.tools.RelBuilder} methods,
* including {@link com.hazelcast.shaded.org.apache.calcite.tools.RelBuilder#in}
* and {@link com.hazelcast.shaded.org.apache.calcite.tools.RelBuilder#between}
* call {@link com.hazelcast.shaded.org.apache.calcite.rex.RexBuilder}
* methods {@link com.hazelcast.shaded.org.apache.calcite.rex.RexBuilder#makeIn}
* and {@link com.hazelcast.shaded.org.apache.calcite.rex.RexBuilder#makeBetween}
* that create Sarg instances directly;
*
- {@link com.hazelcast.shaded.org.apache.calcite.rel.rel2sql.SqlImplementor} converts
* {@link com.hazelcast.shaded.org.apache.calcite.rex.RexCall}s
* to SEARCH into {@link com.hazelcast.shaded.org.apache.calcite.sql.SqlNode} AST expressions
* such as comparisons, {@code BETWEEN} and {@code IN}.
*
*
* @param Value type
*
* @see SqlStdOperatorTable#SEARCH
*/
@SuppressWarnings({"BetaApi", "type.argument.type.incompatible", "UnstableApiUsage"})
public class Sarg> implements Comparable> {
public final RangeSet rangeSet;
public final RexUnknownAs nullAs;
public final int pointCount;
/** Returns FALSE for all null and not-null values.
*
* {@code SEARCH(x, FALSE)} is equivalent to {@code FALSE}. */
private static final SpecialSarg FALSE =
new SpecialSarg(ImmutableRangeSet.of(), RexUnknownAs.FALSE,
"Sarg[FALSE]", 2);
/** Returns TRUE for all not-null values, FALSE for null.
*
*
{@code SEARCH(x, IS_NOT_NULL)} is equivalent to
* {@code x IS NOT NULL}. */
private static final SpecialSarg IS_NOT_NULL =
new SpecialSarg(ImmutableRangeSet.of().complement(), RexUnknownAs.FALSE,
"Sarg[IS NOT NULL]", 3);
/** Returns FALSE for all not-null values, TRUE for null.
*
*
{@code SEARCH(x, IS_NULL)} is equivalent to {@code x IS NULL}. */
private static final SpecialSarg IS_NULL =
new SpecialSarg(ImmutableRangeSet.of(), RexUnknownAs.TRUE,
"Sarg[IS NULL]", 4);
/** Returns TRUE for all null and not-null values.
*
*
{@code SEARCH(x, TRUE)} is equivalent to {@code TRUE}. */
private static final SpecialSarg TRUE =
new SpecialSarg(ImmutableRangeSet.of().complement(), RexUnknownAs.TRUE,
"Sarg[TRUE]", 5);
/** Returns FALSE for all not-null values, UNKNOWN for null.
*
*
{@code SEARCH(x, NOT_EQUAL)} is equivalent to {@code x <> x}. */
private static final SpecialSarg NOT_EQUAL =
new SpecialSarg(ImmutableRangeSet.of(), RexUnknownAs.UNKNOWN,
"Sarg[<>]", 6);
/** Returns TRUE for all not-null values, UNKNOWN for null.
*
*
{@code SEARCH(x, EQUAL)} is equivalent to {@code x = x}. */
private static final SpecialSarg EQUAL =
new SpecialSarg(ImmutableRangeSet.of().complement(), RexUnknownAs.UNKNOWN,
"Sarg[=]", 7);
private Sarg(ImmutableRangeSet rangeSet, RexUnknownAs nullAs) {
this.rangeSet = requireNonNull(rangeSet, "rangeSet");
this.nullAs = requireNonNull(nullAs, "nullAs");
this.pointCount = RangeSets.countPoints(rangeSet);
}
@Deprecated // to be removed before 2.0
public static > Sarg of(boolean containsNull,
RangeSet rangeSet) {
return of(containsNull ? RexUnknownAs.TRUE : RexUnknownAs.UNKNOWN,
rangeSet);
}
/** Creates a search argument. */
public static > Sarg of(RexUnknownAs nullAs,
RangeSet rangeSet) {
if (rangeSet.isEmpty()) {
switch (nullAs) {
case FALSE:
return FALSE;
case TRUE:
return IS_NULL;
default:
return NOT_EQUAL;
}
}
if (rangeSet.equals(RangeSets.rangeSetAll())) {
switch (nullAs) {
case FALSE:
return IS_NOT_NULL;
case TRUE:
return TRUE;
default:
return EQUAL;
}
}
return new Sarg<>(ImmutableRangeSet.copyOf(rangeSet), nullAs);
}
/**
* {@inheritDoc}
*
* Produces a similar result to {@link RangeSet},
* but adds "; NULL AS FALSE" or "; NULL AS TRUE" to indicate {@link #nullAs},
* and simplifies point ranges.
*
*
For example, the Sarg that allows the range set
*
*
{@code [[7..7], [9..9], (10..+∞)]}
*
* and also null is printed as
*
*
{@code Sarg[7, 9, (10..+∞); NULL AS TRUE]}
*/
@Override public String toString() {
final StringBuilder sb = new StringBuilder();
printTo(sb, StringBuilder::append);
return sb.toString();
}
/** Prints this Sarg to a StringBuilder, using the given printer to deal
* with each embedded value. */
public StringBuilder printTo(StringBuilder sb,
BiConsumer valuePrinter) {
sb.append("Sarg[");
final RangeSets.Consumer printer = RangeSets.printer(sb, valuePrinter);
Ord.forEach(rangeSet.asRanges(), (r, i) -> {
if (i > 0) {
sb.append(", ");
}
RangeSets.forEach(r, printer);
});
switch (nullAs) {
case FALSE:
return sb.append("; NULL AS FALSE]");
case TRUE:
return sb.append("; NULL AS TRUE]");
case UNKNOWN:
return sb.append("]");
default:
throw new AssertionError();
}
}
@Override public int compareTo(Sarg o) {
return RangeSets.compare(rangeSet, o.rangeSet);
}
@Override public int hashCode() {
return RangeSets.hashCode(rangeSet) * 31 + nullAs.ordinal();
}
@Override public boolean equals(@Nullable Object o) {
return o == this
|| o instanceof Sarg
&& nullAs == ((Sarg) o).nullAs
&& rangeSet.equals(((Sarg) o).rangeSet);
}
/** Returns whether this Sarg includes all values (including or not including
* null). */
public boolean isAll() {
return false;
}
/** Returns whether this Sarg includes no values (including or not including
* null). */
public boolean isNone() {
return false;
}
/** Returns whether this Sarg is a collection of 1 or more
* points (and perhaps an {@code IS NULL} if
* {@code nullAs == RexUnknownAs.TRUE}).
*
* Such sargs could be translated as {@code ref = value}
* or {@code ref IN (value1, ...)}. */
public boolean isPoints() {
return pointCount == rangeSet.asRanges().size();
}
/** Returns whether this Sarg, when negated, is a collection of 1 or more
* points (and perhaps an {@code IS NULL} if
* {@code nullAs == RexUnknownAs.TRUE}).
*
*
Such sargs could be translated as {@code ref <> value}
* or {@code ref NOT IN (value1, ...)}. */
public boolean isComplementedPoints() {
return rangeSet.span().encloses(Range.all())
&& !rangeSet.equals(RangeSets.rangeSetAll())
&& rangeSet.complement().asRanges().stream()
.allMatch(RangeSets::isPoint);
}
/** Returns a measure of the complexity of this expression.
*
*
It is basically the number of values that need to be checked against
* (including NULL).
*
*
Examples:
*
* - {@code x = 1}, {@code x <> 1}, {@code x > 1} have complexity 1
*
- {@code x > 1 or x is null} has complexity 2
*
- {@code x in (2, 4, 6) or x > 20} has complexity 4
*
- {@code x between 3 and 8 or x between 10 and 20} has complexity 2
*
*/
public int complexity() {
int complexity;
if (rangeSet.asRanges().size() == 2
&& rangeSet.complement().asRanges().size() == 1
&& RangeSets.isPoint(
Iterables.getOnlyElement(rangeSet.complement().asRanges()))) {
// The complement of a point is a range set with two elements.
// For example, "x <> 1" is "[(-inf, 1), (1, inf)]".
// We want this to have complexity 1.
complexity = 1;
} else {
complexity = rangeSet.asRanges().size();
}
if (nullAs == RexUnknownAs.TRUE) {
++complexity;
}
return complexity;
}
/** Returns a Sarg that matches a value if and only this Sarg does not. */
public Sarg negate() {
return Sarg.of(nullAs.negate(), rangeSet.complement());
}
/** Sarg whose range is all or none.
*
* There are only 6 instances: {all, none} * {true, false, unknown}.
*
* @param Value type */
private static class SpecialSarg> extends Sarg {
final String name;
final int ordinal;
SpecialSarg(ImmutableRangeSet rangeSet, RexUnknownAs nullAs, String name,
int ordinal) {
super(rangeSet, nullAs);
this.name = name;
this.ordinal = ordinal;
assert rangeSet.isEmpty() == ((ordinal & 1) == 0);
assert rangeSet.equals(RangeSets.rangeSetAll()) == ((ordinal & 1) == 1);
}
@Override public boolean equals(@Nullable Object o) {
return this == o;
}
@Override public int hashCode() {
return ordinal;
}
@Override public boolean isAll() {
return (ordinal & 1) == 1;
}
@Override public boolean isNone() {
return (ordinal & 1) == 0;
}
@Override public int complexity() {
switch (ordinal) {
case 2: // Sarg[FALSE]
return 0; // for backwards compatibility
case 5: // Sarg[TRUE]
return 2; // for backwards compatibility
default:
return 1;
}
}
@Override public StringBuilder printTo(StringBuilder sb,
BiConsumer valuePrinter) {
return sb.append(name);
}
@Override public String toString() {
return name;
}
}
}