| /* |
| * 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.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.Objects; |
| |
| import com.google.common.base.Joiner; |
| import com.google.common.collect.ImmutableList; |
| |
| import org.apache.cassandra.db.rows.Row; |
| import org.apache.cassandra.db.marshal.AbstractType; |
| import org.apache.cassandra.serializers.MarshalException; |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| import org.apache.cassandra.utils.FastByteOperations; |
| |
| import static org.apache.cassandra.io.sstable.IndexHelper.IndexInfo; |
| |
| /** |
| * A comparator of clustering prefixes (or more generally of {@link Clusterable}}. |
| * <p> |
| * This is essentially just a composite comparator that the clustering values of the provided |
| * clustering prefixes in lexicographical order, with each component being compared based on |
| * the type of the clustering column this is a value of. |
| */ |
| public class ClusteringComparator implements Comparator<Clusterable> |
| { |
| private final List<AbstractType<?>> clusteringTypes; |
| |
| private final Comparator<IndexInfo> indexComparator; |
| private final Comparator<IndexInfo> indexReverseComparator; |
| private final Comparator<Clusterable> reverseComparator; |
| |
| private final Comparator<Row> rowComparator = (r1, r2) -> compare(r1.clustering(), r2.clustering()); |
| |
| public ClusteringComparator(AbstractType<?>... clusteringTypes) |
| { |
| this(ImmutableList.copyOf(clusteringTypes)); |
| } |
| |
| public ClusteringComparator(List<AbstractType<?>> clusteringTypes) |
| { |
| // copy the list to ensure despatch is monomorphic |
| this.clusteringTypes = ImmutableList.copyOf(clusteringTypes); |
| |
| this.indexComparator = (o1, o2) -> ClusteringComparator.this.compare(o1.lastName, o2.lastName); |
| this.indexReverseComparator = (o1, o2) -> ClusteringComparator.this.compare(o1.firstName, o2.firstName); |
| this.reverseComparator = (c1, c2) -> ClusteringComparator.this.compare(c2, c1); |
| for (AbstractType<?> type : clusteringTypes) |
| type.checkComparable(); // this should already be enforced by CFMetaData.rebuild, but we check again for other constructors |
| } |
| |
| /** |
| * The number of clustering columns for the table this is the comparator of. |
| */ |
| public int size() |
| { |
| return clusteringTypes.size(); |
| } |
| |
| /** |
| * The "subtypes" of this clustering comparator, that is the types of the clustering |
| * columns for the table this is a comparator of. |
| */ |
| public List<AbstractType<?>> subtypes() |
| { |
| return clusteringTypes; |
| } |
| |
| /** |
| * Returns the type of the ith clustering column of the table. |
| */ |
| public AbstractType<?> subtype(int i) |
| { |
| return clusteringTypes.get(i); |
| } |
| |
| /** |
| * Creates a row clustering based on the clustering values. |
| * <p> |
| * Every argument can either be a {@code ByteBuffer}, in which case it is used as-is, or a object |
| * corresponding to the type of the corresponding clustering column, in which case it will be |
| * converted to a byte buffer using the column type. |
| * |
| * @param values the values to use for the created clustering. There should be exactly {@code size()} |
| * values which must be either byte buffers or of the type the column expect. |
| * |
| * @return the newly created clustering. |
| */ |
| public Clustering make(Object... values) |
| { |
| if (values.length != size()) |
| throw new IllegalArgumentException(String.format("Invalid number of components, expecting %d but got %d", size(), values.length)); |
| |
| CBuilder builder = CBuilder.create(this); |
| for (Object val : values) |
| { |
| if (val instanceof ByteBuffer) |
| builder.add((ByteBuffer) val); |
| else |
| builder.add(val); |
| } |
| return builder.build(); |
| } |
| |
| public int compare(Clusterable c1, Clusterable c2) |
| { |
| return compare(c1.clustering(), c2.clustering()); |
| } |
| |
| public int compare(ClusteringPrefix c1, ClusteringPrefix c2) |
| { |
| int s1 = c1.size(); |
| int s2 = c2.size(); |
| int minSize = Math.min(s1, s2); |
| |
| for (int i = 0; i < minSize; i++) |
| { |
| int cmp = compareComponent(i, c1.get(i), c2.get(i)); |
| if (cmp != 0) |
| return cmp; |
| } |
| |
| if (s1 == s2) |
| return ClusteringPrefix.Kind.compare(c1.kind(), c2.kind()); |
| |
| return s1 < s2 ? c1.kind().comparedToClustering : -c2.kind().comparedToClustering; |
| } |
| |
| public int compare(Clustering c1, Clustering c2) |
| { |
| for (int i = 0; i < size(); i++) |
| { |
| int cmp = compareComponent(i, c1.get(i), c2.get(i)); |
| if (cmp != 0) |
| return cmp; |
| } |
| return 0; |
| } |
| |
| public int compareComponent(int i, ByteBuffer v1, ByteBuffer v2) |
| { |
| if (v1 == null) |
| return v2 == null ? 0 : -1; |
| if (v2 == null) |
| return 1; |
| |
| return clusteringTypes.get(i).compare(v1, v2); |
| } |
| |
| /** |
| * Returns whether this clustering comparator is compatible with the provided one, |
| * that is if the provided one can be safely replaced by this new one. |
| * |
| * @param previous the previous comparator that we want to replace and test |
| * compatibility with. |
| * |
| * @return whether {@code previous} can be safely replaced by this comparator. |
| */ |
| public boolean isCompatibleWith(ClusteringComparator previous) |
| { |
| if (this == previous) |
| return true; |
| |
| // Extending with new components is fine, shrinking is not |
| if (size() < previous.size()) |
| return false; |
| |
| for (int i = 0; i < previous.size(); i++) |
| { |
| AbstractType<?> tprev = previous.subtype(i); |
| AbstractType<?> tnew = subtype(i); |
| if (!tnew.isCompatibleWith(tprev)) |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Validates the provided prefix for corrupted data. |
| * |
| * @param clustering the clustering prefix to validate. |
| * |
| * @throws MarshalException if {@code clustering} contains some invalid data. |
| */ |
| public void validate(ClusteringPrefix clustering) |
| { |
| for (int i = 0; i < clustering.size(); i++) |
| { |
| ByteBuffer value = clustering.get(i); |
| if (value != null) |
| subtype(i).validate(value); |
| } |
| } |
| |
| /** |
| * A comparator for rows. |
| * |
| * A {@code Row} is a {@code Clusterable} so {@code ClusteringComparator} can be used |
| * to compare rows directly, but when we know we deal with rows (and not {@code Clusterable} in |
| * general), this is a little faster because by knowing we compare {@code Clustering} objects, |
| * we know that 1) they all have the same size and 2) they all have the same kind. |
| */ |
| public Comparator<Row> rowComparator() |
| { |
| return rowComparator; |
| } |
| |
| public Comparator<IndexInfo> indexComparator(boolean reversed) |
| { |
| return reversed ? indexReverseComparator : indexComparator; |
| } |
| |
| public Comparator<Clusterable> reversed() |
| { |
| return reverseComparator; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return String.format("comparator(%s)", Joiner.on(", ").join(clusteringTypes)); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (this == o) |
| return true; |
| |
| if (!(o instanceof ClusteringComparator)) |
| return false; |
| |
| ClusteringComparator that = (ClusteringComparator)o; |
| return this.clusteringTypes.equals(that.clusteringTypes); |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return Objects.hashCode(clusteringTypes); |
| } |
| } |