| /* |
| * 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.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.security.MessageDigest; |
| import java.util.*; |
| |
| import org.apache.cassandra.config.CFMetaData; |
| import org.apache.cassandra.db.marshal.AbstractType; |
| import org.apache.cassandra.db.marshal.MarshalException; |
| import org.apache.cassandra.io.ISSTableSerializer; |
| import org.apache.cassandra.io.sstable.Descriptor; |
| import org.apache.cassandra.io.util.DataOutputBuffer; |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| import org.apache.cassandra.utils.Interval; |
| |
| public class RangeTombstone extends Interval<ByteBuffer, DeletionTime> implements OnDiskAtom |
| { |
| public static final Serializer serializer = new Serializer(); |
| |
| public RangeTombstone(ByteBuffer start, ByteBuffer stop, long markedForDeleteAt, int localDeletionTime) |
| { |
| this(start, stop, new DeletionTime(markedForDeleteAt, localDeletionTime)); |
| } |
| |
| public RangeTombstone(ByteBuffer start, ByteBuffer stop, DeletionTime delTime) |
| { |
| super(start, stop, delTime); |
| } |
| |
| public ByteBuffer name() |
| { |
| return min; |
| } |
| |
| public int getLocalDeletionTime() |
| { |
| return data.localDeletionTime; |
| } |
| |
| public long minTimestamp() |
| { |
| return data.markedForDeleteAt; |
| } |
| |
| public long maxTimestamp() |
| { |
| return data.markedForDeleteAt; |
| } |
| |
| public int serializedSize(TypeSizes typeSizes) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| public long serializedSizeForSSTable() |
| { |
| TypeSizes typeSizes = TypeSizes.NATIVE; |
| return typeSizes.sizeof((short)min.remaining()) + min.remaining() |
| + 1 // serialization flag |
| + typeSizes.sizeof((short)max.remaining()) + max.remaining() |
| + DeletionTime.serializer.serializedSize(data, typeSizes); |
| } |
| |
| public void validateFields(CFMetaData metadata) throws MarshalException |
| { |
| AbstractType<?> nameValidator = metadata.cfType == ColumnFamilyType.Super ? metadata.subcolumnComparator : metadata.comparator; |
| nameValidator.validate(min); |
| nameValidator.validate(max); |
| } |
| |
| public void updateDigest(MessageDigest digest) |
| { |
| digest.update(min.duplicate()); |
| digest.update(max.duplicate()); |
| DataOutputBuffer buffer = new DataOutputBuffer(); |
| try |
| { |
| buffer.writeLong(data.markedForDeleteAt); |
| } |
| catch (IOException e) |
| { |
| throw new RuntimeException(e); |
| } |
| digest.update(buffer.getData(), 0, buffer.getLength()); |
| } |
| |
| /** |
| * This tombstone supersedes another one if it is more recent and cover a |
| * bigger range than rt. |
| */ |
| public boolean supersedes(RangeTombstone rt, Comparator<ByteBuffer> comparator) |
| { |
| if (rt.data.markedForDeleteAt > data.markedForDeleteAt) |
| return false; |
| |
| return comparator.compare(min, rt.min) <= 0 && comparator.compare(max, rt.max) >= 0; |
| } |
| |
| public static class Tracker |
| { |
| private final Comparator<ByteBuffer> comparator; |
| private final Deque<RangeTombstone> ranges = new ArrayDeque<RangeTombstone>(); |
| private final SortedSet<RangeTombstone> maxOrderingSet = new TreeSet<RangeTombstone>(new Comparator<RangeTombstone>() |
| { |
| public int compare(RangeTombstone t1, RangeTombstone t2) |
| { |
| return comparator.compare(t1.max, t2.max); |
| } |
| }); |
| private int atomCount; |
| |
| public Tracker(Comparator<ByteBuffer> comparator) |
| { |
| this.comparator = comparator; |
| } |
| |
| /** |
| * Compute RangeTombstone that are needed at the beginning of an index |
| * block starting with {@code firstColumn}. |
| * Returns the total serialized size of said tombstones and write them |
| * to {@code out} it if isn't null. |
| */ |
| public long writeOpenedMarker(OnDiskAtom firstColumn, DataOutput out, OnDiskAtom.Serializer atomSerializer) throws IOException |
| { |
| long size = 0; |
| if (ranges.isEmpty()) |
| return size; |
| |
| /* |
| * Compute the marker that needs to be written at the beginning of |
| * this block. We need to write one if it the more recent |
| * (opened) tombstone for at least some part of its range. |
| */ |
| List<RangeTombstone> toWrite = new LinkedList<RangeTombstone>(); |
| outer: |
| for (RangeTombstone tombstone : ranges) |
| { |
| // If ever the first column is outside the range, skip it (in |
| // case update() hasn't been called yet) |
| if (comparator.compare(firstColumn.name(), tombstone.max) > 0) |
| continue; |
| |
| RangeTombstone updated = new RangeTombstone(firstColumn.name(), tombstone.max, tombstone.data); |
| |
| Iterator<RangeTombstone> iter = toWrite.iterator(); |
| while (iter.hasNext()) |
| { |
| RangeTombstone other = iter.next(); |
| if (other.supersedes(updated, comparator)) |
| break outer; |
| if (updated.supersedes(other, comparator)) |
| iter.remove(); |
| } |
| toWrite.add(tombstone); |
| } |
| |
| for (RangeTombstone tombstone : toWrite) |
| { |
| size += tombstone.serializedSizeForSSTable(); |
| atomCount++; |
| if (out != null) |
| atomSerializer.serializeForSSTable(tombstone, out); |
| } |
| return size; |
| } |
| |
| public int writtenAtom() |
| { |
| return atomCount; |
| } |
| |
| /** |
| * Update this tracker given an {@code atom}. |
| * If column is a IColumn, check if any tracked range is useless and |
| * can be removed. If it is a RangeTombstone, add it to this tracker. |
| */ |
| public void update(OnDiskAtom atom) |
| { |
| if (atom instanceof RangeTombstone) |
| { |
| RangeTombstone t = (RangeTombstone)atom; |
| // This could be a repeated marker already. If so, we already have a range in which it is |
| // fully included. While keeping both would be ok functionaly, we could end up with a lot of |
| // useless marker after a few compaction, so avoid this. |
| for (RangeTombstone tombstone : maxOrderingSet.tailSet(t)) |
| { |
| // We only care about tombstone have the same max than t |
| if (comparator.compare(t.max, tombstone.max) > 0) |
| break; |
| |
| // Since it is assume tombstones are passed to this method in growing min order, it's enough to |
| // check for the data to know is the current tombstone is included in a previous one |
| if (tombstone.data.equals(t.data)) |
| return; |
| } |
| ranges.addLast(t); |
| maxOrderingSet.add(t); |
| } |
| else |
| { |
| assert atom instanceof IColumn; |
| Iterator<RangeTombstone> iter = maxOrderingSet.iterator(); |
| while (iter.hasNext()) |
| { |
| RangeTombstone tombstone = iter.next(); |
| if (comparator.compare(atom.name(), tombstone.max) > 0) |
| { |
| // That tombstone is now useless |
| iter.remove(); |
| ranges.remove(tombstone); |
| } |
| else |
| { |
| // Since we're iterating by growing end bound, if the current range |
| // includes the column, so does all the next ones |
| return; |
| } |
| } |
| } |
| } |
| |
| public boolean isDeleted(IColumn column) |
| { |
| for (RangeTombstone tombstone : ranges) |
| { |
| if (comparator.compare(column.name(), tombstone.max) <= 0 && tombstone.data.isDeleted(column)) |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| public static class Serializer implements ISSTableSerializer<RangeTombstone> |
| { |
| public void serializeForSSTable(RangeTombstone t, DataOutput dos) throws IOException |
| { |
| ByteBufferUtil.writeWithShortLength(t.min, dos); |
| dos.writeByte(ColumnSerializer.RANGE_TOMBSTONE_MASK); |
| ByteBufferUtil.writeWithShortLength(t.max, dos); |
| DeletionTime.serializer.serialize(t.data, dos); |
| } |
| |
| public RangeTombstone deserializeFromSSTable(DataInput dis, Descriptor.Version version) throws IOException |
| { |
| ByteBuffer min = ByteBufferUtil.readWithShortLength(dis); |
| if (min.remaining() <= 0) |
| throw ColumnSerializer.CorruptColumnException.create(dis, min); |
| |
| int b = dis.readUnsignedByte(); |
| assert (b & ColumnSerializer.RANGE_TOMBSTONE_MASK) != 0; |
| return deserializeBody(dis, min, version); |
| } |
| |
| public RangeTombstone deserializeBody(DataInput dis, ByteBuffer min, Descriptor.Version version) throws IOException |
| { |
| ByteBuffer max = ByteBufferUtil.readWithShortLength(dis); |
| if (max.remaining() <= 0) |
| throw ColumnSerializer.CorruptColumnException.create(dis, max); |
| |
| DeletionTime dt = DeletionTime.serializer.deserialize(dis); |
| return new RangeTombstone(min, max, dt); |
| } |
| } |
| } |