org.apache.cassandra.cql3.restrictions.PrimaryKeyRestrictionSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-all Show documentation
Show all versions of cassandra-all Show documentation
Palantir open source project
/*
* 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.cql3.restrictions;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.statements.Bound;
import org.apache.cassandra.db.IndexExpression;
import org.apache.cassandra.db.composites.*;
import org.apache.cassandra.db.composites.Composite.EOC;
import org.apache.cassandra.db.index.SecondaryIndexManager;
import org.apache.cassandra.exceptions.InvalidRequestException;
/**
* A set of single column restrictions on a primary key part (partition key or clustering key).
*/
final class PrimaryKeyRestrictionSet extends AbstractPrimaryKeyRestrictions implements Iterable
{
/**
* The restrictions.
*/
private final RestrictionSet restrictions;
/**
* true
if the restrictions are corresponding to an EQ, false
otherwise.
*/
private boolean eq;
/**
* true
if the restrictions are corresponding to an IN, false
otherwise.
*/
private boolean in;
/**
* true
if the restrictions are corresponding to a Slice, false
otherwise.
*/
private boolean slice;
/**
* true
if the restrictions are corresponding to a Contains, false
otherwise.
*/
private boolean contains;
public PrimaryKeyRestrictionSet(CType ctype)
{
super(ctype);
this.restrictions = new RestrictionSet();
this.eq = true;
}
private PrimaryKeyRestrictionSet(PrimaryKeyRestrictionSet primaryKeyRestrictions,
Restriction restriction) throws InvalidRequestException
{
super(primaryKeyRestrictions.ctype);
this.restrictions = primaryKeyRestrictions.restrictions.addRestriction(restriction);
if (restriction.isSlice() || primaryKeyRestrictions.isSlice())
this.slice = true;
else if (restriction.isContains() || primaryKeyRestrictions.isContains())
this.contains = true;
else if (restriction.isIN() || primaryKeyRestrictions.isIN())
this.in = true;
else
this.eq = true;
}
@Override
public boolean isSlice()
{
return slice;
}
@Override
public boolean isEQ()
{
return eq;
}
@Override
public boolean isIN()
{
return in;
}
@Override
public boolean isOnToken()
{
return false;
}
@Override
public boolean isContains()
{
return contains;
}
@Override
public boolean isMultiColumn()
{
return false;
}
@Override
public Iterable getFunctions()
{
return restrictions.getFunctions();
}
@Override
public PrimaryKeyRestrictions mergeWith(Restriction restriction) throws InvalidRequestException
{
if (restriction.isOnToken())
{
if (isEmpty())
return (PrimaryKeyRestrictions) restriction;
return new TokenFilter(this, (TokenRestriction) restriction);
}
return new PrimaryKeyRestrictionSet(this, restriction);
}
@Override
public List valuesAsComposites(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
{
return filterAndSort(appendTo(cfm, new CompositesBuilder(ctype), options).build());
}
@Override
public CompositesBuilder appendTo(CFMetaData cfm, CompositesBuilder builder, QueryOptions options)
{
for (Restriction r : restrictions)
{
r.appendTo(cfm, builder, options);
if (builder.hasMissingElements())
break;
}
return builder;
}
@Override
public CompositesBuilder appendBoundTo(CFMetaData cfm, CompositesBuilder builder, Bound bound, QueryOptions options)
{
throw new UnsupportedOperationException();
}
@Override
public List boundsAsComposites(CFMetaData cfm, Bound bound, QueryOptions options) throws InvalidRequestException
{
CompositesBuilder builder = new CompositesBuilder(ctype);
// The end-of-component of composite doesn't depend on whether the
// component type is reversed or not (i.e. the ReversedType is applied
// to the component comparator but not to the end-of-component itself),
// it only depends on whether the slice is reversed
int keyPosition = 0;
for (Restriction r : restrictions)
{
ColumnDefinition def = r.getFirstColumn();
if (keyPosition != def.position() || r.isContains())
break;
if (r.isSlice())
{
r.appendBoundTo(cfm, builder, bound, options);
// Since CASSANDRA-7281, the composites might not end with the same components and it is possible
// that one of the composites is an empty one. Unfortunatly, AbstractCType will always sort
// Composites.EMPTY before all the other components due to its EOC, even if it is not the desired
// behaviour in some cases. To avoid that problem the code will use normal composites for the empty
// ones until the composites are properly sorted. They will then be replaced by Composites.EMPTY as
// it is what is expected by the intra-node serialization.
// It is clearly a hack but it does not make a lot of sense to refactor 2.2 for that as the problem is
// already solved in 3.0.
List composites = filterAndSort(setEocs(r, bound, builder.build()));
for (Composite c : composites)
if (c.isEmpty())
return normalizeEmptyComposites(composites);
return composites;
}
r.appendBoundTo(cfm, builder, bound, options);
if (builder.hasMissingElements())
return Collections.emptyList();
keyPosition = r.getLastColumn().position() + 1;
}
// Means no relation at all or everything was an equal
// Note: if the builder is "full", there is no need to use the end-of-component bit. For columns selection,
// it would be harmless to do it. However, we use this method got the partition key too. And when a query
// with 2ndary index is done, and with the the partition provided with an EQ, we'll end up here, and in that
// case using the eoc would be bad, since for the random partitioner we have no guarantee that
// prefix.end() will sort after prefix (see #5240).
EOC eoc = !builder.hasRemaining() ? EOC.NONE : (bound.isEnd() ? EOC.END : EOC.START);
return filterAndSort(builder.buildWithEOC(eoc));
}
/**
* Removes duplicates and sort the specified composites.
*
* @param composites the composites to filter and sort
* @return the composites sorted and without duplicates
*/
private List filterAndSort(List composites)
{
if (composites.size() <= 1)
return composites;
TreeSet set = new TreeSet(ctype);
set.addAll(composites);
return new ArrayList<>(set);
}
private List normalizeEmptyComposites(List composites)
{
List transformed = new ArrayList<>(composites.size());
for (Composite c : composites)
transformed.add(c.isEmpty() ? Composites.EMPTY : c);
return transformed;
}
/**
* Sets EOCs for the composites returned by the specified slice restriction for the given bound.
*
* @param r the slice restriction
* @param bound the bound
* @param composites the composites
* @return the composites with their EOCs properly set
*/
private List setEocs(Restriction r, Bound bound, List composites)
{
List list = new ArrayList<>(composites.size());
// The first column of the slice might not be the first clustering column (e.g. clustering_0 = ? AND (clustering_1, clustering_2) >= (?, ?)
int offset = r.getFirstColumn().position();
for (int i = 0, m = composites.size(); i < m; i++)
{
Composite composite = composites.get(i);
// Handle the no bound case
if (composite.size() == offset)
{
list.add(composite.withEOC(bound.isEnd() ? EOC.END : EOC.START));
continue;
}
// In the case of mixed order columns, we will have some extra slices where the columns change directions.
// For example: if we have clustering_0 DESC and clustering_1 ASC a slice like (clustering_0, clustering_1) > (1, 2)
// will produce 2 slices: [EMPTY, 1.START] and [1.2.END, 1.END]
// So, the END bound will return 2 composite with the same values 1
if (composite.size() <= r.getLastColumn().position() && i < m - 1 && composite.equals(composites.get(i + 1)))
{
list.add(composite.withEOC(EOC.START));
list.add(composites.get(i++).withEOC(EOC.END));
continue;
}
// Handle the normal bounds
ColumnDefinition column = r.getColumnDefs().get(composite.size() - 1 - offset);
Bound b = reverseBoundIfNeeded(column, bound);
Composite.EOC eoc = eocFor(r, bound, b);
list.add(composite.withEOC(eoc));
}
return list;
}
@Override
public List values(CFMetaData cfm, QueryOptions options) throws InvalidRequestException
{
return Composites.toByteBuffers(valuesAsComposites(cfm, options));
}
@Override
public List bounds(CFMetaData cfm, Bound b, QueryOptions options) throws InvalidRequestException
{
return Composites.toByteBuffers(boundsAsComposites(cfm, b, options));
}
private static Composite.EOC eocFor(Restriction r, Bound eocBound, Bound inclusiveBound)
{
if (eocBound.isStart())
return r.isInclusive(inclusiveBound) ? Composite.EOC.NONE : Composite.EOC.END;
return r.isInclusive(inclusiveBound) ? Composite.EOC.END : Composite.EOC.START;
}
@Override
public boolean hasBound(Bound b)
{
if (isEmpty())
return false;
return restrictions.lastRestriction().hasBound(b);
}
@Override
public boolean isInclusive(Bound b)
{
if (isEmpty())
return false;
return restrictions.lastRestriction().isInclusive(b);
}
@Override
public boolean hasSupportingIndex(SecondaryIndexManager indexManager)
{
return restrictions.hasSupportingIndex(indexManager);
}
@Override
public void addIndexExpressionTo(List expressions,
SecondaryIndexManager indexManager,
QueryOptions options) throws InvalidRequestException
{
Boolean clusteringColumns = null;
int position = 0;
for (Restriction restriction : restrictions)
{
ColumnDefinition columnDef = restriction.getFirstColumn();
// PrimaryKeyRestrictionSet contains only one kind of column, either partition key or clustering columns.
// Therefore we only need to check the column kind once. All the other columns will be of the same kind.
if (clusteringColumns == null)
clusteringColumns = columnDef.isClusteringColumn() ? Boolean.TRUE : Boolean.FALSE;
// We ignore all the clustering columns that can be handled by slices.
if (!clusteringColumns || handleInFilter(restriction, position) || restriction.hasSupportingIndex(indexManager))
{
restriction.addIndexExpressionTo(expressions, indexManager, options);
continue;
}
if (!restriction.isSlice())
position = restriction.getLastColumn().position() + 1;
}
}
@Override
public List getColumnDefs()
{
return restrictions.getColumnDefs();
}
@Override
public ColumnDefinition getFirstColumn()
{
return restrictions.firstColumn();
}
@Override
public ColumnDefinition getLastColumn()
{
return restrictions.lastColumn();
}
public final boolean needsFiltering()
{
// Backported from ClusteringColumnRestrictions from CASSANDRA-11310 for 3.6
// As that suggests, this should only be called on clustering column
// and not partition key restrictions.
int position = 0;
for (Restriction restriction : restrictions)
{
if (handleInFilter(restriction, position))
return true;
if (!restriction.isSlice())
position = restriction.getLastColumn().position() + 1;
}
return false;
}
@Override
public boolean isNotReturningAnyRows(CFMetaData cfm, QueryOptions options)
{
for (Restriction restriction : restrictions)
{
if (restriction.isNotReturningAnyRows(cfm, options))
return true;
}
return false;
}
private boolean handleInFilter(Restriction restriction, int index)
{
return restriction.isContains() || index != restriction.getFirstColumn().position();
}
public Iterator iterator()
{
return restrictions.iterator();
}
}