blob: 1cc348c8b6f5557b711e628c9e11599e7ba1c942 [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.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);
}
}
}