blob: 99bbc64a65e2b23835f889d5a4e4558c1cd92758 [file] [log] [blame]
/*
* 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.cassandra.db;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableRangeSet;
import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import org.apache.cassandra.cql3.restrictions.ClusteringElements;
import org.apache.cassandra.utils.btree.BTreeSet;
/**
* Builder that allows to build multiple Clusterings/Slices at the same time.
*/
public final class MultiCBuilder
{
/**
* The table comparator.
*/
private final ClusteringComparator comparator;
/**
* {@code true} if the clusterings have been build, <code>false</code> otherwise.
*/
private boolean built;
/**
* The clusterings set.
*/
private List<ClusteringElements> clusterings = ImmutableList.of();
/**
* The clustering ranges.
*/
private RangeSet<ClusteringElements> clusteringsRanges;
/**
* Track if some elements are missing. This can happen with a <pre>WHERE c IN ()</pre>.
* making the query not returning any results.
*/
private boolean hasMissingElements;
public MultiCBuilder(ClusteringComparator comparator)
{
this.comparator = comparator;
}
/**
* Adds individually each of the specified elements to the end of all the existing clusterings.
* The number of result clusterings is the product of the number of current clusterings and the number
* of elements added.
*
* <p>
* If this builder contains 2 composites: A-B and A-C a call to this method to add D and E will result in the 4
* clusterings: A-B-D, A-B-E, A-C-D and A-C-E.
* </p>
*
* <p>
* Added elements can be composites as well.
* If this builder contains 2 composites: A-B and A-C a call to this method to add [[D, E], [F, G]] will result in
* 4 composites: A-B-D-E, A-B-F-G, A-C-D-E and A-C-F-G.
* </p>
*
* @param suffixes the elements to add
* @return this <code>MultiCBuilder</code>
*/
public MultiCBuilder extend(List<ClusteringElements> suffixes)
{
checkUpdateable();
if (suffixes.isEmpty())
{
hasMissingElements = true;
return this;
}
this.clusterings = this.clusterings.isEmpty() ? suffixes : cartesianProduct(clusterings, suffixes);
return this;
}
/**
* Adds individually each of the specified elements ranges to the end of all the existing clusterings.
* The number of result clusterings is the product of the number of current clusterings and the number
* of range added.
*
* <p>
* If this builder contains 2 composites: A-B and A-C a call to this method to add [D..E) and (F..G] will result in the 4
* clusterings range : [A-B-D..A-B-E), (A-B-F..A-B-G], [A-C-D..A-C-E) and (A-C-F..A-C-G].
* </p>
*
* <p>
* Added elements can be composites as well.
* If this builder contains 2 composites: A-B and A-C a call to this method to add [[D-E..F-G), (H-I..J-K]] will result in
* 4 composites: [A-B-D-E..A-B-F-G), (A-B-H-I..A-B-J-K], [A-C-D-E..A-C-F-G) and (A-C-H-I..A-C-J-K].
* </p>
*
* @param suffixes the ranges to add
* @return this <code>MultiCBuilder</code>
*/
public MultiCBuilder extend(RangeSet<ClusteringElements> suffixes)
{
checkUpdateable();
this.clusteringsRanges = this.clusterings.isEmpty() ? suffixes : cartesianProduct(clusterings, suffixes);
this.clusterings = null;
return this;
}
private static RangeSet<ClusteringElements> cartesianProduct(List<ClusteringElements> prefixes, RangeSet<ClusteringElements> suffixes)
{
ImmutableRangeSet.Builder<ClusteringElements> builder = ImmutableRangeSet.builder();
for (ClusteringElements prefix: prefixes)
{
for (Range<ClusteringElements> suffix : suffixes.asRanges())
{
builder.add(Range.range(prefix.extend(suffix.lowerEndpoint()),
suffix.lowerBoundType(),
prefix.extend(suffix.upperEndpoint()),
suffix.upperBoundType()));
}
}
return builder.build();
}
private static List<ClusteringElements> cartesianProduct(List<ClusteringElements> prefixes, List<ClusteringElements> suffixes)
{
ImmutableList.Builder<ClusteringElements> builder = ImmutableList.builderWithExpectedSize(prefixes.size() * suffixes.size());
for (ClusteringElements prefix: prefixes)
{
for (ClusteringElements suffix: suffixes)
{
builder.add(prefix.extend(suffix));
}
}
return builder.build();
}
/**
* Returns the current number of results when {@link #build()} is called
*
* @return the current number of build results
*/
public int buildSize()
{
return hasMissingElements ? 0
: clusterings != null ? clusterings.size()
: clusteringsRanges.asRanges().size();
}
/**
* Checks if some clusterings have some missing elements due to a <pre>WHERE c IN ()</pre>.
* @return {@code true} if the clusterings have some missing elements, {@code false} otherwise.
*/
public boolean hasMissingElements()
{
return hasMissingElements;
}
/**
* Builds the <code>clusterings</code>.
*
* @return the clusterings
*/
public NavigableSet<Clustering<?>> build()
{
assert clusteringsRanges == null;
built = true;
if (hasMissingElements)
return BTreeSet.empty(comparator);
if (clusterings.isEmpty())
return BTreeSet.of(comparator, Clustering.EMPTY);
CBuilder builder = CBuilder.create(comparator);
BTreeSet.Builder<Clustering<?>> set = BTreeSet.builder(builder.comparator());
for (ClusteringElements clustering : clusterings)
{
set.add(builder.buildWith(clustering));
}
return set.build();
}
public Slices buildSlices()
{
if (clusterings != null)
{
if (hasMissingElements)
return Slices.NONE;
if (clusterings.isEmpty())
return Slices.ALL;
Slices.Builder builder = new Slices.Builder(comparator, clusterings.size());
for (ClusteringElements clustering : clusterings)
{
builder.add(clustering.toBound(true, true),
clustering.toBound(false, true));
}
return builder.build();
}
Set<Range<ClusteringElements>> ranges = clusteringsRanges.asRanges();
Slices.Builder builder = new Slices.Builder(comparator, ranges.size());
for (Range<ClusteringElements> range : ranges)
{
builder.add(range.lowerEndpoint().toBound(true, isInclusive(range.lowerBoundType())),
range.upperEndpoint().toBound(false, isInclusive(range.upperBoundType())));
}
return builder.build();
}
private boolean isInclusive(BoundType boundType)
{
return boundType == BoundType.CLOSED;
}
private void checkUpdateable()
{
if (built)
throw new IllegalStateException("This builder cannot be updated anymore");
if (clusteringsRanges != null)
throw new IllegalStateException("Cannot extend clusterings that contain ranges");
if (hasMissingElements)
throw new IllegalStateException("Cannot extend clusterings with missing elements");
}
}