| /* |
| * 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.util.*; |
| |
| import org.apache.cassandra.config.ColumnDefinition; |
| import org.apache.cassandra.cql3.QueryOptions; |
| import org.apache.cassandra.cql3.functions.Function; |
| import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction.ContainsRestriction; |
| import org.apache.cassandra.db.filter.RowFilter; |
| import org.apache.cassandra.exceptions.InvalidRequestException; |
| import org.apache.cassandra.index.SecondaryIndexManager; |
| |
| /** |
| * Sets of column restrictions. |
| * |
| * <p>This class is immutable in order to be use within {@link PrimaryKeyRestrictionSet} which as |
| * an implementation of {@link Restriction} need to be immutable. |
| */ |
| final class RestrictionSet implements Restrictions, Iterable<Restriction> |
| { |
| /** |
| * The comparator used to sort the <code>Restriction</code>s. |
| */ |
| private static final Comparator<ColumnDefinition> COLUMN_DEFINITION_COMPARATOR = new Comparator<ColumnDefinition>() |
| { |
| @Override |
| public int compare(ColumnDefinition column, ColumnDefinition otherColumn) |
| { |
| int value = Integer.compare(column.position(), otherColumn.position()); |
| return value != 0 ? value : column.name.bytes.compareTo(otherColumn.name.bytes); |
| } |
| }; |
| |
| /** |
| * The restrictions per column. |
| */ |
| protected final TreeMap<ColumnDefinition, Restriction> restrictions; |
| |
| public RestrictionSet() |
| { |
| this(new TreeMap<ColumnDefinition, Restriction>(COLUMN_DEFINITION_COMPARATOR)); |
| } |
| |
| private RestrictionSet(TreeMap<ColumnDefinition, Restriction> restrictions) |
| { |
| this.restrictions = restrictions; |
| } |
| |
| @Override |
| public final void addRowFilterTo(RowFilter filter, SecondaryIndexManager indexManager, QueryOptions options) throws InvalidRequestException |
| { |
| for (Restriction restriction : restrictions.values()) |
| restriction.addRowFilterTo(filter, indexManager, options); |
| } |
| |
| @Override |
| public final List<ColumnDefinition> getColumnDefs() |
| { |
| return new ArrayList<>(restrictions.keySet()); |
| } |
| |
| @Override |
| public void addFunctionsTo(List<Function> functions) |
| { |
| Restriction previous = null; |
| for (Restriction restriction : restrictions.values()) |
| { |
| // For muti-column restriction, we can have multiple time the same restriction. |
| if (!restriction.equals(previous)) |
| { |
| previous = restriction; |
| restriction.addFunctionsTo(functions); |
| } |
| } |
| } |
| |
| @Override |
| public final boolean isEmpty() |
| { |
| return getColumnDefs().isEmpty(); |
| } |
| |
| @Override |
| public final int size() |
| { |
| return getColumnDefs().size(); |
| } |
| |
| /** |
| * Adds the specified restriction to this set of restrictions. |
| * |
| * @param restriction the restriction to add |
| * @return the new set of restrictions |
| * @throws InvalidRequestException if the new restriction cannot be added |
| */ |
| public RestrictionSet addRestriction(Restriction restriction) throws InvalidRequestException |
| { |
| // RestrictionSet is immutable so we need to clone the restrictions map. |
| TreeMap<ColumnDefinition, Restriction> newRestrictions = new TreeMap<>(this.restrictions); |
| return new RestrictionSet(mergeRestrictions(newRestrictions, restriction)); |
| } |
| |
| private TreeMap<ColumnDefinition, Restriction> mergeRestrictions(TreeMap<ColumnDefinition, Restriction> restrictions, |
| Restriction restriction) |
| throws InvalidRequestException |
| { |
| Collection<ColumnDefinition> columnDefs = restriction.getColumnDefs(); |
| Set<Restriction> existingRestrictions = getRestrictions(columnDefs); |
| |
| if (existingRestrictions.isEmpty()) |
| { |
| for (ColumnDefinition columnDef : columnDefs) |
| restrictions.put(columnDef, restriction); |
| } |
| else |
| { |
| for (Restriction existing : existingRestrictions) |
| { |
| Restriction newRestriction = mergeRestrictions(existing, restriction); |
| |
| for (ColumnDefinition columnDef : columnDefs) |
| restrictions.put(columnDef, newRestriction); |
| } |
| } |
| |
| return restrictions; |
| } |
| |
| /** |
| * Returns all the restrictions applied to the specified columns. |
| * |
| * @param columnDefs the column definitions |
| * @return all the restrictions applied to the specified columns |
| */ |
| private Set<Restriction> getRestrictions(Collection<ColumnDefinition> columnDefs) |
| { |
| Set<Restriction> set = new HashSet<>(); |
| for (ColumnDefinition columnDef : columnDefs) |
| { |
| Restriction existing = restrictions.get(columnDef); |
| if (existing != null) |
| set.add(existing); |
| } |
| return set; |
| } |
| |
| @Override |
| public final boolean hasSupportingIndex(SecondaryIndexManager indexManager) |
| { |
| for (Restriction restriction : restrictions.values()) |
| { |
| if (restriction.hasSupportingIndex(indexManager)) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the column after the specified one. |
| * |
| * @param columnDef the column for which the next one need to be found |
| * @return the column after the specified one. |
| */ |
| ColumnDefinition nextColumn(ColumnDefinition columnDef) |
| { |
| return restrictions.tailMap(columnDef, false).firstKey(); |
| } |
| |
| /** |
| * Returns the definition of the first column. |
| * |
| * @return the definition of the first column. |
| */ |
| ColumnDefinition firstColumn() |
| { |
| return isEmpty() ? null : this.restrictions.firstKey(); |
| } |
| |
| /** |
| * Returns the definition of the last column. |
| * |
| * @return the definition of the last column. |
| */ |
| ColumnDefinition lastColumn() |
| { |
| return isEmpty() ? null : this.restrictions.lastKey(); |
| } |
| |
| /** |
| * Returns the last restriction. |
| * |
| * @return the last restriction. |
| */ |
| Restriction lastRestriction() |
| { |
| return isEmpty() ? null : this.restrictions.lastEntry().getValue(); |
| } |
| |
| /** |
| * Merges the two specified restrictions. |
| * |
| * @param restriction the first restriction |
| * @param otherRestriction the second restriction |
| * @return the merged restriction |
| * @throws InvalidRequestException if the two restrictions cannot be merged |
| */ |
| private static Restriction mergeRestrictions(Restriction restriction, |
| Restriction otherRestriction) throws InvalidRequestException |
| { |
| return restriction == null ? otherRestriction |
| : restriction.mergeWith(otherRestriction); |
| } |
| |
| /** |
| * Checks if the restrictions contains multiple contains, contains key, or map[key] = value. |
| * |
| * @return <code>true</code> if the restrictions contains multiple contains, contains key, or , |
| * map[key] = value; <code>false</code> otherwise |
| */ |
| public final boolean hasMultipleContains() |
| { |
| int numberOfContains = 0; |
| for (Restriction restriction : restrictions.values()) |
| { |
| if (restriction.isContains()) |
| { |
| ContainsRestriction contains = (ContainsRestriction) restriction; |
| numberOfContains += (contains.numberOfValues() + contains.numberOfKeys() + contains.numberOfEntries()); |
| } |
| } |
| return numberOfContains > 1; |
| } |
| |
| @Override |
| public Iterator<Restriction> iterator() |
| { |
| return new LinkedHashSet<>(restrictions.values()).iterator(); |
| } |
| } |