/* * 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.Comparator; import java.util.Iterator; import java.util.List; import java.util.NavigableMap; import java.util.NavigableSet; import com.google.common.collect.AbstractIterator; import org.apache.cassandra.cql3.ColumnIdentifier; 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.utils.ByteBufferUtil; import org.apache.cassandra.utils.Allocator; 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 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) { assert minCellNames.size() == maxCellNames.size(); Composite sStart = reversed ? finish : start; Composite sEnd = reversed ? start : finish; for (int i = 0; i < minCellNames.size(); i++) { AbstractType<?> t = comparator.subtype(i); if ( (i < sEnd.size() && t.compare(sEnd.get(i), minCellNames.get(i)) < 0) || (i < sStart.size() && t.compare(sStart.get(i), maxCellNames.get(i)) > 0)) return false; } return true; } @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, DataOutput 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); } } public static class NavigableMapIterator extends AbstractIterator<Cell> { private final NavigableMap<CellName, Cell> map; private final ColumnSlice[] slices; private int idx = 0; private Iterator<Cell> currentSlice; public NavigableMapIterator(NavigableMap<CellName, Cell> map, ColumnSlice[] slices) { this.map = map; this.slices = slices; } protected Cell computeNext() { if (currentSlice == null) { if (idx >= slices.length) return endOfData(); ColumnSlice slice = slices[idx++]; // Note: we specialize the case of start == "" and finish = "" because it is slightly more efficient, but also they have a specific // meaning (namely, they always extend to the beginning/end of the range). if (slice.start.isEmpty()) { if (slice.finish.isEmpty()) currentSlice = map.values().iterator(); else currentSlice = map.headMap(new FakeCellName(slice.finish), true).values().iterator(); } else if (slice.finish.isEmpty()) { currentSlice = map.tailMap(new FakeCellName(slice.start), true).values().iterator(); } else { currentSlice = map.subMap(new FakeCellName(slice.start), true, new FakeCellName(slice.finish), true).values().iterator(); } } if (currentSlice.hasNext()) return currentSlice.next(); currentSlice = null; return computeNext(); } } public static class NavigableSetIterator extends AbstractIterator<Cell> { private final NavigableSet<Cell> set; private final ColumnSlice[] slices; private int idx = 0; private Iterator<Cell> currentSlice; public NavigableSetIterator(NavigableSet<Cell> set, ColumnSlice[] slices) { this.set = set; this.slices = slices; } protected Cell computeNext() { if (currentSlice == null) { if (idx >= slices.length) return endOfData(); ColumnSlice slice = slices[idx++]; // We specialize the case of start == "" and finish = "" because it is slightly more efficient, // but also they have a specific meaning (namely, they always extend to the beginning/end of the range). if (slice.start.isEmpty()) { if (slice.finish.isEmpty()) currentSlice = set.iterator(); else currentSlice = set.headSet(fakeCell(slice.finish), true).iterator(); } else if (slice.finish.isEmpty()) { currentSlice = set.tailSet(fakeCell(slice.start), true).iterator(); } else { currentSlice = set.subSet(fakeCell(slice.start), true, fakeCell(slice.finish), true).iterator(); } } if (currentSlice.hasNext()) return currentSlice.next(); currentSlice = null; return computeNext(); } } private static Cell fakeCell(Composite name) { return new Cell(new FakeCellName(name), ByteBufferUtil.EMPTY_BYTE_BUFFER); } /* * We need to take a slice (headMap/tailMap/subMap) of a CellName map * based on a Composite. While CellName and Composite are comparable * and so this should work, I haven't found how to generify it properly. * So instead we create a "fake" CellName object that just encapsulate * the prefix. I might not be a valid CellName with respect to the CF * CellNameType, but this doesn't matter here (since we only care about * comparison). This is arguably a bit of a hack. */ private static class FakeCellName extends AbstractComposite implements CellName { private final Composite prefix; private FakeCellName(Composite prefix) { this.prefix = prefix; } public int size() { return prefix.size(); } public ByteBuffer get(int i) { return prefix.get(i); } public Composite.EOC eoc() { return prefix.eoc(); } public int clusteringSize() { throw new UnsupportedOperationException(); } public ColumnIdentifier cql3ColumnName() { throw new UnsupportedOperationException(); } public ByteBuffer collectionElement() { throw new UnsupportedOperationException(); } public boolean isCollectionCell() { throw new UnsupportedOperationException(); } public boolean isSameCQL3RowAs(CellName other) { throw new UnsupportedOperationException(); } public CellName copy(Allocator allocator) { throw new UnsupportedOperationException(); } public long memorySize() { throw new UnsupportedOperationException(); } } }