org.apache.calcite.plan.RelTraitSet Maven / Gradle / Ivy
Show all versions of calcite-core Show documentation
/*
* 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 org.apache.calcite.plan;
import org.apache.calcite.runtime.FlatLists;
import org.apache.calcite.util.Pair;
import com.google.common.collect.ImmutableList;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
/**
* RelTraitSet represents an ordered set of {@link RelTrait}s.
*/
public final class RelTraitSet extends AbstractList {
private static final RelTrait[] EMPTY_TRAITS = new RelTrait[0];
//~ Instance fields --------------------------------------------------------
private final Cache cache;
private final RelTrait[] traits;
private final String string;
//~ Constructors -----------------------------------------------------------
/**
* Constructs a RelTraitSet with the given set of RelTraits.
*
* @param cache Trait set cache (and indirectly cluster) that this set
* belongs to
* @param traits Traits
*/
private RelTraitSet(Cache cache, RelTrait[] traits) {
// NOTE: We do not copy the array. It is important that the array is not
// shared. However, since this constructor is private, we assume that
// the caller has made a copy.
this.cache = cache;
this.traits = traits;
this.string = computeString();
}
//~ Methods ----------------------------------------------------------------
/**
* Creates an empty trait set.
*
* It has a new cache, which will be shared by any trait set created from
* it. Thus each empty trait set is the start of a new ancestral line.
*/
public static RelTraitSet createEmpty() {
return new RelTraitSet(new Cache(), EMPTY_TRAITS);
}
/**
* Retrieves a RelTrait from the set.
*
* @param index 0-based index into ordered RelTraitSet
* @return the RelTrait
* @throws ArrayIndexOutOfBoundsException if index greater than or equal to
* {@link #size()} or less than 0.
*/
public RelTrait getTrait(int index) {
return traits[index];
}
/**
* Retrieves a list of traits from the set.
*
* @param index 0-based index into ordered RelTraitSet
* @return the RelTrait
* @throws ArrayIndexOutOfBoundsException if index greater than or equal to
* {@link #size()} or less than 0.
*/
public List getTraits(int index) {
final RelTrait trait = traits[index];
if (trait instanceof RelCompositeTrait) {
//noinspection unchecked
return ((RelCompositeTrait) trait).traitList();
} else {
//noinspection unchecked
return ImmutableList.of((E) trait);
}
}
public RelTrait get(int index) {
return getTrait(index);
}
/**
* Returns whether a given kind of trait is enabled.
*/
public boolean isEnabled(RelTraitDef traitDef) {
return getTrait(traitDef) != null;
}
/**
* Retrieves a RelTrait of the given type from the set.
*
* @param traitDef the type of RelTrait to retrieve
* @return the RelTrait, or null if not found
*/
public T getTrait(RelTraitDef traitDef) {
int index = findIndex(traitDef);
if (index >= 0) {
//noinspection unchecked
return (T) getTrait(index);
}
return null;
}
/**
* Retrieves a list of traits of the given type from the set.
*
* Only valid for traits that support multiple entries. (E.g. collation.)
*
* @param traitDef the type of RelTrait to retrieve
* @return the RelTrait, or null if not found
*/
public List getTraits(
RelTraitDef traitDef) {
int index = findIndex(traitDef);
if (index >= 0) {
//noinspection unchecked
return (List) getTraits(index);
}
return null;
}
/**
* Replaces an existing RelTrait in the set.
* Returns a different trait set; does not modify this trait set.
*
* @param index 0-based index into ordered RelTraitSet
* @param trait the new RelTrait
* @return the old RelTrait at the index
*/
public RelTraitSet replace(int index, RelTrait trait) {
assert traits[index].getTraitDef() == trait.getTraitDef()
: "RelTrait has different RelTraitDef than replacement";
RelTrait canonizedTrait = canonize(trait);
if (traits[index] == canonizedTrait) {
return this;
}
RelTrait[] newTraits = traits.clone();
newTraits[index] = canonizedTrait;
return cache.getOrAdd(new RelTraitSet(cache, newTraits));
}
/**
* Returns a trait set consisting of the current set plus a new trait.
*
* If the set does not contain a trait of the same {@link RelTraitDef},
* the trait is ignored, and this trait set is returned.
*
* @param trait the new trait
* @return New set
* @see #plus(RelTrait)
*/
public RelTraitSet replace(
RelTrait trait) {
// Quick check for common case
if (containsShallow(traits, trait)) {
return this;
}
final RelTraitDef traitDef = trait.getTraitDef();
int index = findIndex(traitDef);
if (index < 0) {
// Trait is not present. Ignore it.
return this;
}
return replace(index, trait);
}
/** Returns whether an element occurs within an array.
*
*
Uses {@code ==}, not {@link #equals}. Nulls are allowed. */
private static boolean containsShallow(T[] ts, RelTrait seek) {
for (T t : ts) {
if (t == seek) {
return true;
}
}
return false;
}
/** Replaces the trait(s) of a given type with a list of traits of the same
* type.
*
* The list must not be empty, and all traits must be of the same type.
*/
public RelTraitSet replace(List traits) {
assert !traits.isEmpty();
final RelTraitDef def = traits.get(0).getTraitDef();
return replace(RelCompositeTrait.of(def, traits));
}
/** Replaces the trait(s) of a given type with a list of traits of the same
* type.
*
* The list must not be empty, and all traits must be of the same type.
*/
public RelTraitSet replace(RelTraitDef def,
List traits) {
return replace(RelCompositeTrait.of(def, traits));
}
/** If a given multiple trait is enabled, replaces it by calling the given
* function. */
public RelTraitSet replaceIfs(RelTraitDef def,
Supplier> traitSupplier) {
int index = findIndex(def);
if (index < 0) {
return this; // trait is not enabled; ignore it
}
final List traitList = traitSupplier.get();
return replace(index, RelCompositeTrait.of(def, traitList));
}
/** If a given trait is enabled, replaces it by calling the given function. */
public RelTraitSet replaceIf(RelTraitDef def,
Supplier traitSupplier) {
int index = findIndex(def);
if (index < 0) {
return this; // trait is not enabled; ignore it
}
final T traitList = traitSupplier.get();
return replace(index, traitList);
}
/**
* Returns the size of the RelTraitSet.
*
* @return the size of the RelTraitSet.
*/
public int size() {
return traits.length;
}
/**
* Converts a trait to canonical form.
*
* After canonization, t1.equals(t2) if and only if t1 == t2.
*
* @param trait Trait
* @return Trait in canonical form
*/
public T canonize(T trait) {
if (trait == null) {
return null;
}
if (trait instanceof RelCompositeTrait) {
// Composite traits are canonized on creation
//noinspection unchecked
return trait;
}
//noinspection unchecked
return (T) trait.getTraitDef().canonize(trait);
}
/**
* Compares two RelTraitSet objects for equality.
*
* @param obj another RelTraitSet
* @return true if traits are equal and in the same order, false otherwise
*/
@Override public boolean equals(Object obj) {
return this == obj
|| obj instanceof RelTraitSet
&& Arrays.equals(traits, ((RelTraitSet) obj).traits);
}
@Override public int hashCode() {
return Arrays.hashCode(traits);
}
/**
* Returns whether this trait set satisfies another trait set.
*
* For that to happen, each trait satisfies the corresponding trait in the
* other set. In particular, each trait set satisfies itself, because each
* trait subsumes itself.
*
*
Intuitively, if a relational expression is needed that has trait set
* S (A, B), and trait set S1 (A1, B1) subsumes S, then any relational
* expression R in S1 meets that need.
*
*
For example, if we need a relational expression that has
* trait set S = {enumerable convention, sorted on [C1 asc]}, and R
* has {enumerable convention, sorted on [C3], [C1, C2]}. R has two
* sort keys, but one them [C1, C2] satisfies S [C1], and that is enough.
*
* @param that another RelTraitSet
* @return whether this trait set satisfies other trait set
*
* @see org.apache.calcite.plan.RelTrait#satisfies(RelTrait)
*/
public boolean satisfies(RelTraitSet that) {
for (Pair pair : Pair.zip(traits, that.traits)) {
if (!pair.left.satisfies(pair.right)) {
return false;
}
}
return true;
}
/**
* Compares two RelTraitSet objects to see if they match for the purposes of
* firing a rule. A null RelTrait within a RelTraitSet indicates a wildcard:
* any RelTrait in the other RelTraitSet will match. If one RelTraitSet is
* smaller than the other, comparison stops when the last RelTrait from the
* smaller set has been examined and the remaining RelTraits in the larger
* set are assumed to match.
*
* @param that another RelTraitSet
* @return true if the RelTraitSets match, false otherwise
*/
public boolean matches(RelTraitSet that) {
final int n =
Math.min(
this.size(),
that.size());
for (int i = 0; i < n; i++) {
RelTrait thisTrait = this.traits[i];
RelTrait thatTrait = that.traits[i];
if ((thisTrait == null) || (thatTrait == null)) {
continue;
}
if (thisTrait != thatTrait) {
return false;
}
}
return true;
}
/**
* Returns whether this trait set contains a given trait.
*
* @param trait Sought trait
* @return Whether set contains given trait
*/
public boolean contains(RelTrait trait) {
for (RelTrait relTrait : traits) {
if (trait == relTrait) {
return true;
}
}
return false;
}
/**
* Returns whether this trait set contains the given trait, or whether the
* trait is not present because its {@link RelTraitDef} is not enabled.
* Returns false if another trait of the same {@code RelTraitDef} is
* present.
*
* @param trait Trait
* @return Whether trait is present, or is absent because disabled
*/
public boolean containsIfApplicable(RelTrait trait) {
// Note that '==' is sufficient, because trait should be canonized.
final RelTrait trait1 = getTrait(trait.getTraitDef());
return trait1 == null || trait1 == trait;
}
/**
* Returns whether this trait set comprises precisely the list of given
* traits.
*
* @param relTraits Traits
* @return Whether this trait set's traits are the same as the argument
*/
public boolean comprises(RelTrait... relTraits) {
return Arrays.equals(traits, relTraits);
}
@Override public String toString() {
return string;
}
/**
* Outputs the traits of this set as a String. Traits are output in order,
* separated by periods.
*/
protected String computeString() {
StringBuilder s = new StringBuilder();
for (int i = 0; i < traits.length; i++) {
final RelTrait trait = traits[i];
if (i > 0) {
s.append('.');
}
if ((trait == null)
&& (traits.length == 1)) {
// Special format for a list containing a single null trait;
// otherwise its string appears as "null", which is the same
// as if the whole trait set were null, and so confusing.
s.append("{null}");
} else {
s.append(trait);
}
}
return s.toString();
}
/**
* Finds the index of a trait of a given type in this set.
*
* @param traitDef Sought trait definition
* @return index of trait, or -1 if not found
*/
private int findIndex(RelTraitDef traitDef) {
for (int i = 0; i < traits.length; i++) {
RelTrait trait = traits[i];
if ((trait != null) && (trait.getTraitDef() == traitDef)) {
return i;
}
}
return -1;
}
/**
* Returns this trait set with a given trait added or overridden. Does not
* modify this trait set.
*
* @param trait Trait
* @return Trait set with given trait
*/
public RelTraitSet plus(RelTrait trait) {
if (contains(trait)) {
return this;
}
int i = findIndex(trait.getTraitDef());
if (i >= 0) {
return replace(i, trait);
}
// Optimize time & space to represent a trait set key.
//
// Don't build a trait set until we're sure there isn't an equivalent one.
// Then we can justify the cost of computing RelTraitSet.string in the
// constructor.
final RelTrait canonizedTrait = canonize(trait);
assert canonizedTrait != null;
List newTraits;
switch (traits.length) {
case 0:
newTraits = ImmutableList.of(canonizedTrait);
break;
case 1:
newTraits = FlatLists.of(traits[0], canonizedTrait);
break;
case 2:
newTraits = FlatLists.of(traits[0], traits[1], canonizedTrait);
break;
default:
newTraits = ImmutableList.builder().add(traits)
.add(canonizedTrait).build();
}
return cache.getOrAdd(newTraits);
}
public RelTraitSet plusAll(RelTrait[] traits) {
RelTraitSet t = this;
for (RelTrait trait : traits) {
t = t.plus(trait);
}
return t;
}
public RelTraitSet merge(RelTraitSet additionalTraits) {
return plusAll(additionalTraits.traits);
}
/** Returns a list of traits that are in {@code traitSet} but not in this
* RelTraitSet. */
public ImmutableList difference(RelTraitSet traitSet) {
final ImmutableList.Builder builder = ImmutableList.builder();
for (Pair pair : Pair.zip(traits, traitSet.traits)) {
if (pair.left != pair.right) {
builder.add(pair.right);
}
}
return builder.build();
}
/** Returns whether there are any composite traits in this set. */
public boolean allSimple() {
for (RelTrait trait : traits) {
if (trait instanceof RelCompositeTrait) {
return false;
}
}
return true;
}
/** Returns a trait set similar to this one but with all composite traits
* flattened. */
public RelTraitSet simplify() {
RelTraitSet x = this;
for (int i = 0; i < traits.length; i++) {
final RelTrait trait = traits[i];
if (trait instanceof RelCompositeTrait) {
x = x.replace(i,
((RelCompositeTrait) trait).size() == 1
? ((RelCompositeTrait) trait).trait(0)
: trait.getTraitDef().getDefault());
}
}
return x;
}
/** Cache of trait sets. */
private static class Cache {
final Map, RelTraitSet> map = new HashMap<>();
Cache() {
}
RelTraitSet getOrAdd(List traits) {
RelTraitSet traitSet1 = map.get(traits);
if (traitSet1 != null) {
return traitSet1;
}
final RelTraitSet traitSet =
new RelTraitSet(this, traits.toArray(new RelTrait[0]));
map.put(traits, traitSet);
return traitSet;
}
}
}
// End RelTraitSet.java