blob: 1395782c9af28352734949bf5479f7f7180d5f83 [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.rows;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Iterator;
import java.util.Objects;
import com.google.common.base.Function;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.DeletionPurger;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.LivenessInfo;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.marshal.ByteType;
import org.apache.cassandra.db.marshal.SetType;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.utils.btree.BTree;
/**
* The data for a complex column, that is it's cells and potential complex
* deletion time.
*/
public class ComplexColumnData extends ColumnData implements Iterable<Cell>
{
static final Cell[] NO_CELLS = new Cell[0];
private static final long EMPTY_SIZE = ObjectSizes.measure(new ComplexColumnData(ColumnDefinition.regularDef("", "", "", SetType.getInstance(ByteType.instance, true)), NO_CELLS, new DeletionTime(0, 0)));
// The cells for 'column' sorted by cell path.
private final Object[] cells;
private final DeletionTime complexDeletion;
// Only ArrayBackedRow should call this.
ComplexColumnData(ColumnDefinition column, Object[] cells, DeletionTime complexDeletion)
{
super(column);
assert column.isComplex();
assert cells.length > 0 || !complexDeletion.isLive();
this.cells = cells;
this.complexDeletion = complexDeletion;
}
public boolean hasCells()
{
return !BTree.isEmpty(cells);
}
public int cellsCount()
{
return BTree.size(cells);
}
public Cell getCell(CellPath path)
{
return (Cell) BTree.<Object>find(cells, column.asymmetricCellPathComparator(), path);
}
public Cell getCellByIndex(int idx)
{
return BTree.findByIndex(cells, idx);
}
/**
* The complex deletion time of the complex column.
* <p>
* The returned "complex deletion" is a deletion of all the cells of the column. For instance,
* for a collection, this correspond to a full collection deletion.
* Please note that this deletion says nothing about the individual cells of the complex column:
* there can be no complex deletion but some of the individual cells can be deleted.
*
* @return the complex deletion time for the column this is the data of or {@code DeletionTime.LIVE}
* if the column is not deleted.
*/
public DeletionTime complexDeletion()
{
return complexDeletion;
}
public Iterator<Cell> iterator()
{
return BTree.iterator(cells);
}
public Iterator<Cell> reverseIterator()
{
return BTree.iterator(cells, BTree.Dir.DESC);
}
public int dataSize()
{
int size = complexDeletion.dataSize();
for (Cell cell : this)
size += cell.dataSize();
return size;
}
public long unsharedHeapSizeExcludingData()
{
long heapSize = EMPTY_SIZE + ObjectSizes.sizeOfArray(cells);
// TODO: this can be turned into a simple multiplication, at least while we have only one Cell implementation
for (Cell cell : this)
heapSize += cell.unsharedHeapSizeExcludingData();
return heapSize;
}
public void validate()
{
for (Cell cell : this)
cell.validate();
}
public void digest(MessageDigest digest)
{
if (!complexDeletion.isLive())
complexDeletion.digest(digest);
for (Cell cell : this)
cell.digest(digest);
}
public ComplexColumnData markCounterLocalToBeCleared()
{
return transformAndFilter(complexDeletion, Cell::markCounterLocalToBeCleared);
}
public ComplexColumnData filter(ColumnFilter filter, DeletionTime activeDeletion, CFMetaData.DroppedColumn dropped, LivenessInfo rowLiveness)
{
ColumnFilter.Tester cellTester = filter.newTester(column);
if (cellTester == null && activeDeletion.isLive() && dropped == null)
return this;
DeletionTime newDeletion = activeDeletion.supersedes(complexDeletion) ? DeletionTime.LIVE : complexDeletion;
return transformAndFilter(newDeletion, (cell) ->
{
boolean isForDropped = dropped != null && cell.timestamp() <= dropped.droppedTime;
boolean isShadowed = activeDeletion.deletes(cell);
boolean isSkippable = cellTester != null && (!cellTester.fetches(cell.path())
|| (!cellTester.fetchedCellIsQueried(cell.path()) && cell.timestamp() < rowLiveness.timestamp()));
return isForDropped || isShadowed || isSkippable ? null : cell;
});
}
public ComplexColumnData purge(DeletionPurger purger, int nowInSec)
{
DeletionTime newDeletion = complexDeletion.isLive() || purger.shouldPurge(complexDeletion) ? DeletionTime.LIVE : complexDeletion;
return transformAndFilter(newDeletion, (cell) -> cell.purge(purger, nowInSec));
}
public ComplexColumnData withOnlyQueriedData(ColumnFilter filter)
{
return transformAndFilter(complexDeletion, (cell) -> filter.fetchedCellIsQueried(column, cell.path()) ? null : cell);
}
private ComplexColumnData transformAndFilter(DeletionTime newDeletion, Function<? super Cell, ? extends Cell> function)
{
Object[] transformed = BTree.transformAndFilter(cells, function);
if (cells == transformed && newDeletion == complexDeletion)
return this;
if (newDeletion == DeletionTime.LIVE && BTree.isEmpty(transformed))
return null;
return new ComplexColumnData(column, transformed, newDeletion);
}
public ComplexColumnData updateAllTimestamp(long newTimestamp)
{
DeletionTime newDeletion = complexDeletion.isLive() ? complexDeletion : new DeletionTime(newTimestamp - 1, complexDeletion.localDeletionTime());
return transformAndFilter(newDeletion, (cell) -> (Cell) cell.updateAllTimestamp(newTimestamp));
}
public long maxTimestamp()
{
long timestamp = complexDeletion.markedForDeleteAt();
for (Cell cell : this)
timestamp = Math.max(timestamp, cell.timestamp());
return timestamp;
}
// This is the partner in crime of ArrayBackedRow.setValue. The exact warning apply. The short
// version is: "don't use that method".
void setValue(CellPath path, ByteBuffer value)
{
Cell current = (Cell) BTree.<Object>find(cells, column.asymmetricCellPathComparator(), path);
BTree.replaceInSitu(cells, column.cellComparator(), current, current.withUpdatedValue(value));
}
@Override
public boolean equals(Object other)
{
if (this == other)
return true;
if(!(other instanceof ComplexColumnData))
return false;
ComplexColumnData that = (ComplexColumnData)other;
return this.column().equals(that.column())
&& this.complexDeletion().equals(that.complexDeletion)
&& BTree.equals(this.cells, that.cells);
}
@Override
public int hashCode()
{
return Objects.hash(column(), complexDeletion(), BTree.hashCode(cells));
}
public static Builder builder()
{
return new Builder();
}
public static class Builder
{
private DeletionTime complexDeletion;
private ColumnDefinition column;
private BTree.Builder<Cell> builder;
public void newColumn(ColumnDefinition column)
{
this.column = column;
this.complexDeletion = DeletionTime.LIVE; // default if writeComplexDeletion is not called
if (builder == null)
builder = BTree.builder(column.cellComparator());
else
builder.reuse(column.cellComparator());
}
public void addComplexDeletion(DeletionTime complexDeletion)
{
this.complexDeletion = complexDeletion;
}
public void addCell(Cell cell)
{
builder.add(cell);
}
public ComplexColumnData build()
{
if (complexDeletion.isLive() && builder.isEmpty())
return null;
return new ComplexColumnData(column, builder.build(), complexDeletion);
}
}
}