/* * 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.filter; import java.io.*; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import org.apache.cassandra.db.*; import org.apache.cassandra.db.composites.*; import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.io.ISerializer; import org.apache.cassandra.io.IVersionedSerializer; import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.utils.ByteBufferUtil; public class ColumnSlice { public static final ColumnSlice ALL_COLUMNS = new ColumnSlice(Composites.EMPTY, Composites.EMPTY); public static final ColumnSlice[] ALL_COLUMNS_ARRAY = new ColumnSlice[]{ ALL_COLUMNS }; public final Composite start; public final Composite finish; public ColumnSlice(Composite start, Composite finish) { assert start != null && finish != null; this.start = start; this.finish = finish; } public boolean isAlwaysEmpty(CellNameType comparator, boolean reversed) { Comparator<Composite> orderedComparator = reversed ? comparator.reverseComparator() : comparator; return !start.isEmpty() && !finish.isEmpty() && orderedComparator.compare(start, finish) > 0; } public boolean includes(Comparator<Composite> cmp, Composite name) { return (start.isEmpty() || cmp.compare(start, name) <= 0) && (finish.isEmpty() || cmp.compare(finish, name) >= 0); } public boolean isBefore(Comparator<Composite> cmp, Composite name) { return !finish.isEmpty() && cmp.compare(finish, name) < 0; } public boolean intersects(List<ByteBuffer> minCellNames, List<ByteBuffer> maxCellNames, CellNameType comparator, boolean reversed) { Composite sStart = reversed ? finish : start; Composite sEnd = reversed ? start : finish; if (compare(sStart, maxCellNames, comparator, true) > 0 || compare(sEnd, minCellNames, comparator, false) < 0) return false; // We could safely return true here, but there's a minor optimization: if the first component is restricted // to a single value, we can check that the second component falls within the min/max for that component // (and repeat for all components). for (int i = 0; i < minCellNames.size() && i < maxCellNames.size(); i++) { AbstractType<?> t = comparator.subtype(i); ByteBuffer s = i < sStart.size() ? sStart.get(i) : ByteBufferUtil.EMPTY_BYTE_BUFFER; ByteBuffer f = i < sEnd.size() ? sEnd.get(i) : ByteBufferUtil.EMPTY_BYTE_BUFFER; // we already know the first component falls within its min/max range (otherwise we wouldn't get here) if (i > 0 && (i < sEnd.size() && t.compare(f, minCellNames.get(i)) < 0 || i < sStart.size() && t.compare(s, maxCellNames.get(i)) > 0)) return false; // if this component isn't equal in the start and finish, we don't need to check any more if (i >= sStart.size() || i >= sEnd.size() || t.compare(s, f) != 0) break; } return true; } /** Helper method for intersects() */ private int compare(Composite sliceBounds, List<ByteBuffer> sstableBounds, CellNameType comparator, boolean isSliceStart) { for (int i = 0; i < sstableBounds.size(); i++) { if (i >= sliceBounds.size()) { // When isSliceStart is true, we're comparing the end of the slice against the min cell name for the sstable, // so the slice is something like [(1, 0), (1, 0)], and the sstable max is something like (1, 0, 1). // We want to return -1 (slice start is smaller than max column name) so that we say the slice intersects. // The opposite is true when dealing with the end slice. For example, with the same slice and a min // cell name of (1, 0, 1), we want to return 1 (slice end is bigger than min column name). return isSliceStart ? -1 : 1; } int comparison = comparator.subtype(i).compare(sliceBounds.get(i), sstableBounds.get(i)); if (comparison != 0) return comparison; } // the slice bound and sstable bound have been equal in all components so far if (sliceBounds.size() > sstableBounds.size()) { // We have the opposite situation from the one described above. With a slice of [(1, 0), (1, 0)], // and a min/max cell name of (1), we want to say the slice start is smaller than the max and the slice // end is larger than the min. return isSliceStart ? -1 : 1; } return 0; } /** * Validates that the provided slice array contains only non-overlapped slices valid for a query {@code reversed} * or not on a table using {@code comparator}. */ public static boolean validateSlices(ColumnSlice[] slices, CellNameType type, boolean reversed) { Comparator<Composite> comparator = reversed ? type.reverseComparator() : type; for (int i = 0; i < slices.length; i++) { Composite start = slices[i].start; Composite finish = slices[i].finish; if (start.isEmpty() || finish.isEmpty()) { if (start.isEmpty() && i > 0) return false; if (finish.isEmpty()) return i == slices.length - 1; } else { // !finish.isEmpty() is imposed by prior loop if (i > 0 && comparator.compare(slices[i - 1].finish, start) >= 0) return false; if (comparator.compare(start, finish) > 0) return false; } } return true; } /** * Takes an array of slices (potentially overlapping and in any order, though each individual slice must have * its start before or equal its end in {@code comparator} orde) and return an equivalent array of non-overlapping * slices in {@code comparator order}. * * @param slices an array of slices. This may be modified by this method. * @param comparator the order in which to sort the slices. * @return the smallest possible array of non-overlapping slices in {@code compator} order. If the original * slices are already non-overlapping and in comparator order, this may or may not return the provided slices * directly. */ public static ColumnSlice[] deoverlapSlices(ColumnSlice[] slices, final Comparator<Composite> comparator) { if (slices.length <= 1) return slices; Arrays.sort(slices, new Comparator<ColumnSlice>() { @Override public int compare(ColumnSlice s1, ColumnSlice s2) { if (s1.start.isEmpty() || s2.start.isEmpty()) { if (s1.start.isEmpty() != s2.start.isEmpty()) return s1.start.isEmpty() ? -1 : 1; } else { int c = comparator.compare(s1.start, s2.start); if (c != 0) return c; } // For the finish, empty always means greater return s1.finish.isEmpty() || s2.finish.isEmpty() ? (s1.finish.isEmpty() ? 1 : -1) : comparator.compare(s1.finish, s2.finish); } }); List<ColumnSlice> slicesCopy = new ArrayList<>(slices.length); ColumnSlice last = slices[0]; for (int i = 1; i < slices.length; i++) { ColumnSlice s2 = slices[i]; boolean includesStart = last.includes(comparator, s2.start); boolean includesFinish = s2.finish.isEmpty() ? last.finish.isEmpty() : last.includes(comparator, s2.finish); if (includesStart && includesFinish) continue; if (!includesStart && !includesFinish) { slicesCopy.add(last); last = s2; continue; } if (includesStart) { last = new ColumnSlice(last.start, s2.finish); continue; } assert !includesFinish; } slicesCopy.add(last); return slicesCopy.toArray(new ColumnSlice[slicesCopy.size()]); } @Override public final int hashCode() { int hashCode = 31 + start.hashCode(); return 31*hashCode + finish.hashCode(); } @Override public final boolean equals(Object o) { if(!(o instanceof ColumnSlice)) return false; ColumnSlice that = (ColumnSlice)o; return start.equals(that.start) && finish.equals(that.finish); } @Override public String toString() { return "[" + ByteBufferUtil.bytesToHex(start.toByteBuffer()) + ", " + ByteBufferUtil.bytesToHex(finish.toByteBuffer()) + "]"; } public static class Serializer implements IVersionedSerializer<ColumnSlice> { private final CType type; public Serializer(CType type) { this.type = type; } public void serialize(ColumnSlice cs, DataOutputPlus out, int version) throws IOException { ISerializer<Composite> serializer = type.serializer(); serializer.serialize(cs.start, out); serializer.serialize(cs.finish, out); } public ColumnSlice deserialize(DataInput in, int version) throws IOException { ISerializer<Composite> serializer = type.serializer(); Composite start = serializer.deserialize(in); Composite finish = serializer.deserialize(in); return new ColumnSlice(start, finish); } public long serializedSize(ColumnSlice cs, int version) { ISerializer<Composite> serializer = type.serializer(); return serializer.serializedSize(cs.start, TypeSizes.NATIVE) + serializer.serializedSize(cs.finish, TypeSizes.NATIVE); } } }