blob: a7243a2fbaf4a6ed3e90f1a67425e4140833761c [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;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.cassandra.cache.IRowCacheEntry;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.db.composites.CellName;
import org.apache.cassandra.db.composites.CellNameType;
import org.apache.cassandra.db.composites.CellNames;
import org.apache.cassandra.db.filter.ColumnCounter;
import org.apache.cassandra.db.filter.ColumnSlice;
import org.apache.cassandra.io.sstable.ColumnNameHelper;
import org.apache.cassandra.io.sstable.ColumnStats;
import org.apache.cassandra.io.sstable.SSTable;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.*;
/**
* A sorted map of columns.
* This represents the backing map of a colum family.
*
* Whether the implementation is thread safe or not is left to the
* implementing classes.
*/
public abstract class ColumnFamily implements Iterable<Cell>, IRowCacheEntry
{
/* The column serializer for this Column Family. Create based on config. */
public static final ColumnFamilySerializer serializer = new ColumnFamilySerializer();
protected final CFMetaData metadata;
protected ColumnFamily(CFMetaData metadata)
{
assert metadata != null;
this.metadata = metadata;
}
public <T extends ColumnFamily> T cloneMeShallow(ColumnFamily.Factory<T> factory, boolean reversedInsertOrder)
{
T cf = factory.create(metadata, reversedInsertOrder);
cf.delete(this);
return cf;
}
public ColumnFamily cloneMeShallow()
{
return cloneMeShallow(false);
}
public ColumnFamily cloneMeShallow(boolean reversed)
{
return cloneMeShallow(getFactory(), reversed);
}
public ColumnFamilyType getType()
{
return metadata.cfType;
}
public int liveCQL3RowCount(long now)
{
ColumnCounter counter = getComparator().isDense()
? new ColumnCounter(now)
: new ColumnCounter.GroupByPrefix(now, getComparator(), metadata.clusteringColumns().size());
return counter.countAll(this).live();
}
/**
* Clones the column map.
*/
public abstract ColumnFamily cloneMe();
public UUID id()
{
return metadata.cfId;
}
/**
* @return The CFMetaData for this row
*/
public CFMetaData metadata()
{
return metadata;
}
public void addColumn(CellName name, ByteBuffer value, long timestamp)
{
addColumn(name, value, timestamp, 0);
}
public void addColumn(CellName name, ByteBuffer value, long timestamp, int timeToLive)
{
assert !metadata().isCounter();
Cell cell = AbstractCell.create(name, value, timestamp, timeToLive, metadata());
addColumn(cell);
}
public void addCounter(CellName name, long value)
{
addColumn(new BufferCounterUpdateCell(name, value, FBUtilities.timestampMicros()));
}
public void addTombstone(CellName name, ByteBuffer localDeletionTime, long timestamp)
{
addColumn(new BufferDeletedCell(name, localDeletionTime, timestamp));
}
public void addTombstone(CellName name, int localDeletionTime, long timestamp)
{
addColumn(new BufferDeletedCell(name, localDeletionTime, timestamp));
}
public void addAtom(OnDiskAtom atom)
{
if (atom instanceof Cell)
{
addColumn((Cell)atom);
}
else
{
assert atom instanceof RangeTombstone;
delete((RangeTombstone)atom);
}
}
/**
* Clear this column family, removing all columns and deletion info.
*/
public abstract void clear();
/**
* Returns a {@link DeletionInfo.InOrderTester} for the deletionInfo() of
* this column family. Please note that for ThreadSafe implementation of ColumnFamily,
* this tester will remain valid even if new tombstones are added to this ColumnFamily
* *as long as said addition is done in comparator order*. For AtomicSortedColumns,
* the tester will correspond to the state of when this method is called.
*/
public DeletionInfo.InOrderTester inOrderDeletionTester()
{
return deletionInfo().inOrderTester();
}
/**
* Returns the factory used for this ISortedColumns implementation.
*/
public abstract Factory getFactory();
public abstract DeletionInfo deletionInfo();
public abstract void setDeletionInfo(DeletionInfo info);
public abstract void delete(DeletionInfo info);
public abstract void delete(DeletionTime deletionTime);
protected abstract void delete(RangeTombstone tombstone);
public abstract SearchIterator<CellName, Cell> searchIterator();
/**
* Purges top-level and range tombstones whose localDeletionTime is older than gcBefore.
* @param gcBefore a timestamp (in seconds) before which tombstones should be purged
*/
public abstract void purgeTombstones(int gcBefore);
/**
* Adds a cell to this cell map.
* If a cell with the same name is already present in the map, it will
* be replaced by the newly added cell.
*/
public abstract void addColumn(Cell cell);
/**
* Adds a cell if it's non-gc-able and isn't shadowed by a partition/range tombstone with a higher timestamp.
* Requires that the cell to add is sorted strictly after the last cell in the container.
*/
public abstract void maybeAppendColumn(Cell cell, DeletionInfo.InOrderTester tester, int gcBefore);
/**
* Appends a cell. Requires that the cell to add is sorted strictly after the last cell in the container.
*/
public abstract void appendColumn(Cell cell);
/**
* Adds all the columns of a given column map to this column map.
* This is equivalent to:
* <code>
* for (Cell c : cm)
* addColumn(c, ...);
* </code>
* but is potentially faster.
*/
public abstract void addAll(ColumnFamily cm);
/**
* Get a column given its name, returning null if the column is not
* present.
*/
public abstract Cell getColumn(CellName name);
/**
* Returns an iterable with the names of columns in this column map in the same order
* as the underlying columns themselves.
*/
public abstract Iterable<CellName> getColumnNames();
/**
* Returns the columns of this column map as a collection.
* The columns in the returned collection should be sorted as the columns
* in this map.
*/
public abstract Collection<Cell> getSortedColumns();
/**
* Returns the columns of this column map as a collection.
* The columns in the returned collection should be sorted in reverse
* order of the columns in this map.
*/
public abstract Collection<Cell> getReverseSortedColumns();
/**
* Returns the number of columns in this map.
*/
public abstract int getColumnCount();
/**
* Returns whether or not there are any columns present.
*/
public abstract boolean hasColumns();
/**
* Returns true if this contains no columns or deletion info
*/
public boolean isEmpty()
{
return deletionInfo().isLive() && !hasColumns();
}
/**
* Returns an iterator over the columns of this map that returns only the matching @param slices.
* The provided slices must be in order and must be non-overlapping.
*/
public abstract Iterator<Cell> iterator(ColumnSlice[] slices);
/**
* Returns a reversed iterator over the columns of this map that returns only the matching @param slices.
* The provided slices must be in reversed order and must be non-overlapping.
*/
public abstract Iterator<Cell> reverseIterator(ColumnSlice[] slices);
/**
* Returns if this map only support inserts in reverse order.
*/
public abstract boolean isInsertReversed();
/**
* If `columns` has any tombstones (top-level or range tombstones), they will be applied to this set of columns.
*/
public void delete(ColumnFamily columns)
{
delete(columns.deletionInfo());
}
/*
* This function will calculate the difference between 2 column families.
* The external input is assumed to be a superset of internal.
*/
public ColumnFamily diff(ColumnFamily cfComposite)
{
assert cfComposite.id().equals(id());
ColumnFamily cfDiff = ArrayBackedSortedColumns.factory.create(metadata);
cfDiff.delete(cfComposite.deletionInfo());
// (don't need to worry about cfNew containing Columns that are shadowed by
// the delete tombstone, since cfNew was generated by CF.resolve, which
// takes care of those for us.)
for (Cell cellExternal : cfComposite)
{
CellName cName = cellExternal.name();
Cell cellInternal = getColumn(cName);
if (cellInternal == null)
{
cfDiff.addColumn(cellExternal);
}
else
{
Cell cellDiff = cellInternal.diff(cellExternal);
if (cellDiff != null)
{
cfDiff.addColumn(cellDiff);
}
}
}
cfDiff.setDeletionInfo(deletionInfo().diff(cfComposite.deletionInfo()));
if (!cfDiff.isEmpty())
return cfDiff;
return null;
}
public long dataSize()
{
long size = 0;
for (Cell cell : this)
size += cell.cellDataSize();
return size;
}
public long maxTimestamp()
{
long maxTimestamp = deletionInfo().maxTimestamp();
for (Cell cell : this)
maxTimestamp = Math.max(maxTimestamp, cell.timestamp());
return maxTimestamp;
}
@Override
public int hashCode()
{
HashCodeBuilder builder = new HashCodeBuilder(373, 75437)
.append(metadata)
.append(deletionInfo());
for (Cell cell : this)
builder.append(cell);
return builder.toHashCode();
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || !(o instanceof ColumnFamily))
return false;
ColumnFamily comparison = (ColumnFamily) o;
return metadata.equals(comparison.metadata)
&& deletionInfo().equals(comparison.deletionInfo())
&& ByteBufferUtil.compareUnsigned(digest(this), digest(comparison)) == 0;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("ColumnFamily(");
sb.append(metadata.cfName);
if (isMarkedForDelete())
sb.append(" -").append(deletionInfo()).append("-");
sb.append(" [").append(CellNames.getColumnsString(getComparator(), this)).append("])");
return sb.toString();
}
public static ByteBuffer digest(ColumnFamily cf)
{
MessageDigest digest = FBUtilities.threadLocalMD5Digest();
if (cf != null)
cf.updateDigest(digest);
return ByteBuffer.wrap(digest.digest());
}
public void updateDigest(MessageDigest digest)
{
for (Cell cell : this)
cell.updateDigest(digest);
deletionInfo().updateDigest(digest);
}
public static ColumnFamily diff(ColumnFamily cf1, ColumnFamily cf2)
{
if (cf1 == null)
return cf2;
return cf1.diff(cf2);
}
public ColumnStats getColumnStats()
{
// note that we default to MIN_VALUE/MAX_VALUE here to be able to override them later in this method
// we are checking row/range tombstones and actual cells - there should always be data that overrides
// these with actual values
ColumnStats.MinLongTracker minTimestampTracker = new ColumnStats.MinLongTracker(Long.MIN_VALUE);
ColumnStats.MaxLongTracker maxTimestampTracker = new ColumnStats.MaxLongTracker(Long.MAX_VALUE);
StreamingHistogram tombstones = new StreamingHistogram(SSTable.TOMBSTONE_HISTOGRAM_BIN_SIZE);
ColumnStats.MaxIntTracker maxDeletionTimeTracker = new ColumnStats.MaxIntTracker(Integer.MAX_VALUE);
List<ByteBuffer> minColumnNamesSeen = Collections.emptyList();
List<ByteBuffer> maxColumnNamesSeen = Collections.emptyList();
boolean hasLegacyCounterShards = false;
if (deletionInfo().getTopLevelDeletion().localDeletionTime < Integer.MAX_VALUE)
{
tombstones.update(deletionInfo().getTopLevelDeletion().localDeletionTime);
maxDeletionTimeTracker.update(deletionInfo().getTopLevelDeletion().localDeletionTime);
minTimestampTracker.update(deletionInfo().getTopLevelDeletion().markedForDeleteAt);
maxTimestampTracker.update(deletionInfo().getTopLevelDeletion().markedForDeleteAt);
}
Iterator<RangeTombstone> it = deletionInfo().rangeIterator();
while (it.hasNext())
{
RangeTombstone rangeTombstone = it.next();
tombstones.update(rangeTombstone.getLocalDeletionTime());
minTimestampTracker.update(rangeTombstone.timestamp());
maxTimestampTracker.update(rangeTombstone.timestamp());
maxDeletionTimeTracker.update(rangeTombstone.getLocalDeletionTime());
minColumnNamesSeen = ColumnNameHelper.minComponents(minColumnNamesSeen, rangeTombstone.min, metadata.comparator);
maxColumnNamesSeen = ColumnNameHelper.maxComponents(maxColumnNamesSeen, rangeTombstone.max, metadata.comparator);
}
for (Cell cell : this)
{
minTimestampTracker.update(cell.timestamp());
maxTimestampTracker.update(cell.timestamp());
maxDeletionTimeTracker.update(cell.getLocalDeletionTime());
int deletionTime = cell.getLocalDeletionTime();
if (deletionTime < Integer.MAX_VALUE)
tombstones.update(deletionTime);
minColumnNamesSeen = ColumnNameHelper.minComponents(minColumnNamesSeen, cell.name(), metadata.comparator);
maxColumnNamesSeen = ColumnNameHelper.maxComponents(maxColumnNamesSeen, cell.name(), metadata.comparator);
if (cell instanceof CounterCell)
hasLegacyCounterShards = hasLegacyCounterShards || ((CounterCell) cell).hasLegacyShards();
}
return new ColumnStats(getColumnCount(),
minTimestampTracker.get(),
maxTimestampTracker.get(),
maxDeletionTimeTracker.get(),
tombstones,
minColumnNamesSeen,
maxColumnNamesSeen,
hasLegacyCounterShards);
}
public boolean isMarkedForDelete()
{
return !deletionInfo().isLive();
}
/**
* @return the comparator whose sorting order the contained columns conform to
*/
public CellNameType getComparator()
{
return metadata.comparator;
}
public boolean hasOnlyTombstones(long now)
{
for (Cell cell : this)
if (cell.isLive(now))
return false;
return true;
}
public Iterator<Cell> iterator()
{
return getSortedColumns().iterator();
}
public Iterator<Cell> reverseIterator()
{
return getReverseSortedColumns().iterator();
}
public Map<CellName, ByteBuffer> asMap()
{
ImmutableMap.Builder<CellName, ByteBuffer> builder = ImmutableMap.builder();
for (Cell cell : this)
builder.put(cell.name(), cell.value());
return builder.build();
}
public static ColumnFamily fromBytes(ByteBuffer bytes)
{
if (bytes == null)
return null;
try
{
return serializer.deserialize(new DataInputStream(ByteBufferUtil.inputStream(bytes)),
ArrayBackedSortedColumns.factory,
ColumnSerializer.Flag.LOCAL,
MessagingService.current_version);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
public ByteBuffer toBytes()
{
try (DataOutputBuffer out = new DataOutputBuffer())
{
serializer.serialize(this, out, MessagingService.current_version);
return ByteBuffer.wrap(out.getData(), 0, out.getLength());
}
}
/**
* @return an iterator where the removes are carried out once everything has been iterated
*/
public abstract BatchRemoveIterator<Cell> batchRemoveIterator();
public abstract static class Factory <T extends ColumnFamily>
{
/**
* Returns a (initially empty) column map whose columns are sorted
* according to the provided comparator.
* The {@code insertReversed} flag is an hint on how we expect insertion to be perfomed,
* either in sorted or reverse sorted order. This is used by ArrayBackedSortedColumns to
* allow optimizing for both forward and reversed slices. This does not matter for ThreadSafeSortedColumns.
* Note that this is only an hint on how we expect to do insertion, this does not change the map sorting.
*/
public abstract T create(CFMetaData metadata, boolean insertReversed, int initialCapacity);
public T create(CFMetaData metadata, boolean insertReversed)
{
return create(metadata, insertReversed, 0);
}
public T create(CFMetaData metadata)
{
return create(metadata, false);
}
public T create(String keyspace, String cfName)
{
return create(Schema.instance.getCFMetaData(keyspace, cfName));
}
}
}