mirror of https://github.com/apache/cassandra
313 lines
10 KiB
Java
313 lines
10 KiB
Java
/*
|
|
* 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.util.Collections;
|
|
import java.util.Iterator;
|
|
|
|
import com.google.common.base.Objects;
|
|
|
|
import org.apache.cassandra.config.DatabaseDescriptor;
|
|
import org.apache.cassandra.db.rows.*;
|
|
import org.apache.cassandra.db.rows.EncodingStats;
|
|
import org.apache.cassandra.utils.ObjectSizes;
|
|
import org.apache.cassandra.utils.memory.AbstractAllocator;
|
|
|
|
/**
|
|
* A mutable implementation of {@code DeletionInfo}.
|
|
*/
|
|
public class MutableDeletionInfo implements DeletionInfo
|
|
{
|
|
private static final long EMPTY_SIZE = ObjectSizes.measure(new MutableDeletionInfo(0, 0));
|
|
|
|
/**
|
|
* This represents a deletion of the entire partition. We can't represent this within the RangeTombstoneList, so it's
|
|
* kept separately. This also slightly optimizes the common case of a full partition deletion.
|
|
*/
|
|
private DeletionTime partitionDeletion;
|
|
|
|
/**
|
|
* A list of range tombstones within the partition. This is left as null if there are no range tombstones
|
|
* (to save an allocation (since it's a common case).
|
|
*/
|
|
private RangeTombstoneList ranges;
|
|
|
|
/**
|
|
* Creates a DeletionInfo with only a top-level (row) tombstone.
|
|
* @param markedForDeleteAt the time after which the entire row should be considered deleted
|
|
* @param localDeletionTime what time the deletion write was applied locally (for purposes of
|
|
* purging the tombstone after gc_grace_seconds).
|
|
*/
|
|
public MutableDeletionInfo(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 MutableDeletionInfo(DeletionTime partitionDeletion)
|
|
{
|
|
this(partitionDeletion, null);
|
|
}
|
|
|
|
public MutableDeletionInfo(DeletionTime partitionDeletion, RangeTombstoneList ranges)
|
|
{
|
|
this.partitionDeletion = partitionDeletion;
|
|
this.ranges = ranges;
|
|
}
|
|
|
|
/**
|
|
* Returns a new DeletionInfo that has no top-level tombstone or any range tombstones.
|
|
*/
|
|
public static MutableDeletionInfo live()
|
|
{
|
|
return new MutableDeletionInfo(DeletionTime.LIVE);
|
|
}
|
|
|
|
public MutableDeletionInfo mutableCopy()
|
|
{
|
|
return new MutableDeletionInfo(partitionDeletion, ranges == null ? null : ranges.copy());
|
|
}
|
|
|
|
public MutableDeletionInfo copy(AbstractAllocator allocator)
|
|
{
|
|
RangeTombstoneList rangesCopy = null;
|
|
if (ranges != null)
|
|
rangesCopy = ranges.copy(allocator);
|
|
|
|
return new MutableDeletionInfo(partitionDeletion, rangesCopy);
|
|
}
|
|
|
|
/**
|
|
* Returns whether this DeletionInfo is live, that is deletes no columns.
|
|
*/
|
|
public boolean isLive()
|
|
{
|
|
return partitionDeletion.isLive() && (ranges == null || ranges.isEmpty());
|
|
}
|
|
|
|
/**
|
|
* Potentially replaces the top-level tombstone with another, keeping whichever has the higher markedForDeleteAt
|
|
* timestamp.
|
|
* @param newInfo the deletion time to add to this deletion info.
|
|
*/
|
|
public void add(DeletionTime newInfo)
|
|
{
|
|
if (newInfo.supersedes(partitionDeletion))
|
|
partitionDeletion = newInfo;
|
|
}
|
|
|
|
public void add(RangeTombstone tombstone, ClusteringComparator comparator)
|
|
{
|
|
if (ranges == null) // Introduce getInitialRangeTombstoneAllocationSize
|
|
ranges = new RangeTombstoneList(comparator, DatabaseDescriptor.getInitialRangeTombstoneListAllocationSize());
|
|
|
|
ranges.add(tombstone);
|
|
}
|
|
|
|
/**
|
|
* Combines another DeletionInfo with this one and returns the result. Whichever top-level tombstone
|
|
* has the higher markedForDeleteAt timestamp will be kept, along with its localDeletionTime. The
|
|
* range tombstones will be combined.
|
|
*
|
|
* @return this object.
|
|
*/
|
|
public DeletionInfo add(DeletionInfo newInfo)
|
|
{
|
|
add(newInfo.getPartitionDeletion());
|
|
|
|
// We know MutableDeletionInfo is the only impelementation and we're not mutating it, it's just to get access to the
|
|
// RangeTombstoneList directly.
|
|
assert newInfo instanceof MutableDeletionInfo;
|
|
RangeTombstoneList newRanges = ((MutableDeletionInfo)newInfo).ranges;
|
|
|
|
if (ranges == null)
|
|
ranges = newRanges == null ? null : newRanges.copy();
|
|
else if (newRanges != null)
|
|
ranges.addAll(newRanges);
|
|
|
|
return this;
|
|
}
|
|
|
|
public DeletionTime getPartitionDeletion()
|
|
{
|
|
return partitionDeletion;
|
|
}
|
|
|
|
// Use sparingly, not the most efficient thing
|
|
public Iterator<RangeTombstone> rangeIterator(boolean reversed)
|
|
{
|
|
return ranges == null ? Collections.emptyIterator() : ranges.iterator(reversed);
|
|
}
|
|
|
|
public Iterator<RangeTombstone> rangeIterator(Slice slice, boolean reversed)
|
|
{
|
|
return ranges == null ? Collections.emptyIterator() : ranges.iterator(slice, reversed);
|
|
}
|
|
|
|
public RangeTombstone rangeCovering(Clustering<?> name)
|
|
{
|
|
return ranges == null ? null : ranges.search(name);
|
|
}
|
|
|
|
public int dataSize()
|
|
{
|
|
int size = TypeSizes.sizeof(partitionDeletion.markedForDeleteAt());
|
|
return size + (ranges == null ? 0 : ranges.dataSize());
|
|
}
|
|
|
|
public boolean hasRanges()
|
|
{
|
|
return ranges != null && !ranges.isEmpty();
|
|
}
|
|
|
|
public int rangeCount()
|
|
{
|
|
return hasRanges() ? ranges.size() : 0;
|
|
}
|
|
|
|
public long maxTimestamp()
|
|
{
|
|
return ranges == null ? partitionDeletion.markedForDeleteAt() : Math.max(partitionDeletion.markedForDeleteAt(), ranges.maxMarkedAt());
|
|
}
|
|
|
|
/**
|
|
* Whether this deletion info may modify the provided one if added to it.
|
|
*/
|
|
public boolean mayModify(DeletionInfo delInfo)
|
|
{
|
|
return partitionDeletion.compareTo(delInfo.getPartitionDeletion()) > 0 || hasRanges();
|
|
}
|
|
|
|
@Override
|
|
public String toString()
|
|
{
|
|
if (ranges == null || ranges.isEmpty())
|
|
return String.format("{%s}", partitionDeletion);
|
|
else
|
|
return String.format("{%s, ranges=%s}", partitionDeletion, rangesAsString());
|
|
}
|
|
|
|
private String rangesAsString()
|
|
{
|
|
assert !ranges.isEmpty();
|
|
StringBuilder sb = new StringBuilder();
|
|
ClusteringComparator cc = ranges.comparator();
|
|
Iterator<RangeTombstone> iter = rangeIterator(false);
|
|
while (iter.hasNext())
|
|
{
|
|
RangeTombstone i = iter.next();
|
|
sb.append(i.deletedSlice().toString(cc));
|
|
sb.append('@');
|
|
sb.append(i.deletionTime());
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
// Updates all the timestamp of the deletion contained in this DeletionInfo to be {@code timestamp}.
|
|
public DeletionInfo updateAllTimestamp(long timestamp)
|
|
{
|
|
if (partitionDeletion.markedForDeleteAt() != Long.MIN_VALUE)
|
|
partitionDeletion = new DeletionTime(timestamp, partitionDeletion.localDeletionTime());
|
|
|
|
if (ranges != null)
|
|
ranges.updateAllTimestamp(timestamp);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o)
|
|
{
|
|
if(!(o instanceof MutableDeletionInfo))
|
|
return false;
|
|
MutableDeletionInfo that = (MutableDeletionInfo)o;
|
|
return partitionDeletion.equals(that.partitionDeletion) && Objects.equal(ranges, that.ranges);
|
|
}
|
|
|
|
@Override
|
|
public final int hashCode()
|
|
{
|
|
return Objects.hashCode(partitionDeletion, ranges);
|
|
}
|
|
|
|
@Override
|
|
public long unsharedHeapSize()
|
|
{
|
|
return EMPTY_SIZE + partitionDeletion.unsharedHeapSize() + (ranges == null ? 0 : ranges.unsharedHeapSize());
|
|
}
|
|
|
|
public void collectStats(EncodingStats.Collector collector)
|
|
{
|
|
collector.update(partitionDeletion);
|
|
if (ranges != null)
|
|
ranges.collectStats(collector);
|
|
}
|
|
|
|
public static Builder builder(DeletionTime partitionLevelDeletion, ClusteringComparator comparator, boolean reversed)
|
|
{
|
|
return new Builder(partitionLevelDeletion, comparator, reversed);
|
|
}
|
|
|
|
/**
|
|
* Builds DeletionInfo object from (in order) range tombstone markers.
|
|
*/
|
|
public static class Builder
|
|
{
|
|
private final MutableDeletionInfo deletion;
|
|
private final ClusteringComparator comparator;
|
|
|
|
private final boolean reversed;
|
|
|
|
private RangeTombstoneMarker openMarker;
|
|
|
|
private Builder(DeletionTime partitionLevelDeletion, ClusteringComparator comparator, boolean reversed)
|
|
{
|
|
this.deletion = new MutableDeletionInfo(partitionLevelDeletion);
|
|
this.comparator = comparator;
|
|
this.reversed = reversed;
|
|
}
|
|
|
|
public void add(RangeTombstoneMarker marker)
|
|
{
|
|
// We need to start by the close case in case that's a boundary
|
|
|
|
if (marker.isClose(reversed))
|
|
{
|
|
DeletionTime openDeletion = openMarker.openDeletionTime(reversed);
|
|
assert marker.closeDeletionTime(reversed).equals(openDeletion);
|
|
|
|
ClusteringBound<?> open = openMarker.openBound(reversed);
|
|
ClusteringBound<?> close = marker.closeBound(reversed);
|
|
|
|
Slice slice = reversed ? Slice.make(close, open) : Slice.make(open, close);
|
|
deletion.add(new RangeTombstone(slice, openDeletion), comparator);
|
|
}
|
|
|
|
if (marker.isOpen(reversed))
|
|
{
|
|
openMarker = marker;
|
|
}
|
|
}
|
|
|
|
public MutableDeletionInfo build()
|
|
{
|
|
return deletion;
|
|
}
|
|
}
|
|
}
|