/*
* 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.util.*;
import com.google.common.base.Objects;
import com.google.common.collect.Iterators;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.IVersionedSerializer;
public class DeletionInfo
{
private static final Serializer serializer = new Serializer();
// We don't have way to represent the full interval of keys (Interval don't support the minimum token as the right bound),
// so we keep the topLevel deletion info separatly. This also slightly optimize the case of full row deletion which is rather common.
private DeletionTime topLevel;
private RangeTombstoneList ranges; // null if no range tombstones (to save an allocation since it's a common case).
public DeletionInfo(long markedForDeleteAt, int localDeletionTime)
{
// Pre-1.1 node may return MIN_VALUE for non-deleted container, but the new default is MAX_VALUE
// (see CASSANDRA-3872)
this(new DeletionTime(markedForDeleteAt, localDeletionTime == Integer.MIN_VALUE ? Integer.MAX_VALUE : localDeletionTime));
}
public DeletionInfo(DeletionTime topLevel)
{
this(topLevel, null);
}
public DeletionInfo(ByteBuffer start, ByteBuffer end, Comparator<ByteBuffer> comparator, long markedForDeleteAt, int localDeletionTime)
{
this(DeletionTime.LIVE, new RangeTombstoneList(comparator, 1));
ranges.add(start, end, markedForDeleteAt, localDeletionTime);
}
public DeletionInfo(RangeTombstone rangeTombstone, Comparator<ByteBuffer> comparator)
{
this(rangeTombstone.min, rangeTombstone.max, comparator, rangeTombstone.data.markedForDeleteAt, rangeTombstone.data.localDeletionTime);
}
public static DeletionInfo live()
{
return new DeletionInfo(DeletionTime.LIVE);
}
private DeletionInfo(DeletionTime topLevel, RangeTombstoneList ranges)
{
this.topLevel = topLevel;
this.ranges = ranges;
}
public static Serializer serializer()
{
return serializer;
}
public DeletionInfo copy()
{
return new DeletionInfo(topLevel, ranges == null ? null : ranges.copy());
}
/**
* Returns whether this DeletionInfo is live, that is deletes no columns.
*/
public boolean isLive()
{
return topLevel.markedForDeleteAt == Long.MIN_VALUE
&& topLevel.localDeletionTime == Integer.MAX_VALUE
&& (ranges == null || ranges.isEmpty());
}
/**
* Return whether a given column is deleted by the container having this
* deletion info.
*
* @param column the column to check.
* @return true if the column is deleted, false otherwise
*/
public boolean isDeleted(Column column)
{
return isDeleted(column.name(), column.timestamp());
}
public boolean isDeleted(ByteBuffer name, long timestamp)
{
// We do rely on this test: if topLevel.markedForDeleteAt is MIN_VALUE, we should not
// consider the column deleted even if timestamp=MIN_VALUE, otherwise this break QueryFilter.isRelevant
if (isLive())
return false;
if (timestamp <= topLevel.markedForDeleteAt)
return true;
return ranges != null && ranges.isDeleted(name, timestamp);
}
/**
* Returns a new {@link InOrderTester} in forward order.
*/
InOrderTester inOrderTester()
{
return inOrderTester(false);
}
/**
* Returns a new {@link InOrderTester} given the order in which
* columns will be passed to it.
*/
public InOrderTester inOrderTester(boolean reversed)
{
return new InOrderTester(reversed);
}
/**
* Purge every tombstones that are older than {@code gcbefore}.
*
* @param gcBefore timestamp (in seconds) before which tombstones should
* be purged
*/
public void purge(int gcBefore)
{
topLevel = topLevel.localDeletionTime < gcBefore ? DeletionTime.LIVE : topLevel;
if (ranges != null)
{
ranges.purge(gcBefore);
if (ranges.isEmpty())
ranges = null;
}
}
public boolean hasIrrelevantData(int gcBefore)
{
if (topLevel.localDeletionTime < gcBefore)
return true;
return ranges != null && ranges.hasIrrelevantData(gcBefore);
}
public void add(DeletionTime newInfo)
{
if (topLevel.markedForDeleteAt < newInfo.markedForDeleteAt)
topLevel = newInfo;
}
public void add(RangeTombstone tombstone, Comparator<ByteBuffer> comparator)
{
if (ranges == null)
ranges = new RangeTombstoneList(comparator, 1);
ranges.add(tombstone);
}
/**
* Adds the provided deletion infos to the current ones.
*
* @return this object.
*/
public DeletionInfo add(DeletionInfo newInfo)
{
add(newInfo.topLevel);
if (ranges == null)
ranges = newInfo.ranges == null ? null : newInfo.ranges.copy();
else if (newInfo.ranges != null)
ranges.addAll(newInfo.ranges);
return this;
}
public long minTimestamp()
{
return ranges == null
? topLevel.markedForDeleteAt
: Math.min(topLevel.markedForDeleteAt, ranges.minMarkedAt());
}
/**
* The maximum timestamp mentioned by this DeletionInfo.
*/
public long maxTimestamp()
{
return ranges == null
? topLevel.markedForDeleteAt
: Math.max(topLevel.markedForDeleteAt, ranges.maxMarkedAt());
}
public DeletionTime getTopLevelDeletion()
{
return topLevel;
}
// Use sparingly, not the most efficient thing
public Iterator<RangeTombstone> rangeIterator()
{
return ranges == null ? Iterators.<RangeTombstone>emptyIterator() : ranges.iterator();
}
public DeletionTime rangeCovering(ByteBuffer name)
{
return ranges == null ? null : ranges.search(name);
}
public int dataSize()
{
int size = TypeSizes.NATIVE.sizeof(topLevel.markedForDeleteAt);
return size + (ranges == null ? 0 : ranges.dataSize());
}
public boolean hasRanges()
{
return ranges != null && !ranges.isEmpty();
}
@Override
public String toString()
{
if (ranges == null || ranges.isEmpty())
return String.format("{%s}", topLevel);
else
return String.format("{%s, ranges=%s}", topLevel, rangesAsString());
}
private String rangesAsString()
{
assert !ranges.isEmpty();
StringBuilder sb = new StringBuilder();
AbstractType at = (AbstractType)ranges.comparator();
assert at != null;
Iterator<RangeTombstone> iter = rangeIterator();
while (iter.hasNext())
{
RangeTombstone i = iter.next();
sb.append("[");
sb.append(at.getString(i.min)).append("-");
sb.append(at.getString(i.max)).append(", ");
sb.append(i.data);
sb.append("]");
}
return sb.toString();
}
// Updates all the timestamp of the deletion contained in this DeletionInfo to be {@code timestamp}.
public void updateAllTimestamp(long timestamp)
{
if (topLevel.markedForDeleteAt != Long.MIN_VALUE)
topLevel = new DeletionTime(timestamp, topLevel.localDeletionTime);
if (ranges != null)
ranges.updateAllTimestamp(timestamp);
}
@Override
public boolean equals(Object o)
{
if(!(o instanceof DeletionInfo))
return false;
DeletionInfo that = (DeletionInfo)o;
return topLevel.equals(that.topLevel) && Objects.equal(ranges, that.ranges);
}
@Override
public final int hashCode()
{
return Objects.hashCode(topLevel, ranges);
}
public static class Serializer implements IVersionedSerializer<DeletionInfo>
{
public void serialize(DeletionInfo info, DataOutput out, int version) throws IOException
{
DeletionTime.serializer.serialize(info.topLevel, out);
RangeTombstoneList.serializer.serialize(info.ranges, out, version);
}
/*
* Range tombstones internally depend on the column family serializer, but it is not serialized.
* Thus deserialize(DataInput, int, Comparator<ByteBuffer>) should be used instead of this method.
*/
public DeletionInfo deserialize(DataInput in, int version) throws IOException
{
throw new UnsupportedOperationException();
}
public DeletionInfo deserialize(DataInput in, int version, Comparator<ByteBuffer> comparator) throws IOException
{
DeletionTime topLevel = DeletionTime.serializer.deserialize(in);
RangeTombstoneList ranges = RangeTombstoneList.serializer.deserialize(in, version, comparator);
return new DeletionInfo(topLevel, ranges);
}
public long serializedSize(DeletionInfo info, TypeSizes typeSizes, int version)
{
long size = DeletionTime.serializer.serializedSize(info.topLevel, typeSizes);
return size + RangeTombstoneList.serializer.serializedSize(info.ranges, typeSizes, version);
}
public long serializedSize(DeletionInfo info, int version)
{
return serializedSize(info, TypeSizes.NATIVE, version);
}
}
/**
* This object allow testing whether a given column (name/timestamp) is deleted
* or not by this DeletionInfo, assuming that the column given to this
* object are passed in forward or reversed comparator sorted order.
*
* This is more efficient that calling DeletionInfo.isDeleted() repeatedly
* in that case.
*/
public class InOrderTester
{
/*
* Note that because because range tombstone are added to this DeletionInfo while we iterate,
* ranges may be null initially and we need to wait the first range to create the tester (once
* created the test will pick up new tombstones however). We do are guaranteed that a range tombstone
* will be added *before* we test any column that it may delete so this is ok.
*/
private RangeTombstoneList.InOrderTester tester;
private final boolean reversed;
private InOrderTester(boolean reversed)
{
this.reversed = reversed;
}
public boolean isDeleted(Column column)
{
return isDeleted(column.name(), column.timestamp());
}
public boolean isDeleted(ByteBuffer name, long timestamp)
{
if (timestamp <= topLevel.markedForDeleteAt)
return true;
/*
* We don't optimize the reversed case for now because RangeTombstoneList
* is always in forward sorted order.
*/
if (reversed)
return DeletionInfo.this.isDeleted(name, timestamp);
// Maybe create the tester if we hadn't yet and we now have some ranges (see above).
if (tester == null && ranges != null)
tester = ranges.inOrderTester();
return tester != null && tester.isDeleted(name, timestamp);
}
}
}