| /* |
| * 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)); |
| } |
| } |
| |
| } |