Simplify some 8099's implementations

patch by slebresne; reviewed by iamalesky for CASSANDRA-9705
This commit is contained in:
Sylvain Lebresne 2015-06-30 15:58:02 +02:00
parent 7659ae2eef
commit 2457599427
193 changed files with 7276 additions and 10887 deletions

View File

@ -715,7 +715,7 @@ public final class CFMetaData
// it means that it's a dropped column from before 3.0, and in that case using
// BytesType is fine for what we'll be using it for, even if that's a hack.
AbstractType<?> type = dropped.type == null ? BytesType.instance : dropped.type;
return ColumnDefinition.regularDef(this, name, type, null);
return ColumnDefinition.regularDef(this, name, type);
}
@Override

View File

@ -70,7 +70,6 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
private final Integer componentIndex;
private final Comparator<CellPath> cellPathComparator;
private final Comparator<Cell> cellComparator;
/**
* These objects are compared frequently, so we encode several of their comparison components
@ -103,19 +102,19 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
return new ColumnDefinition(ksName, cfName, ColumnIdentifier.getInterned(name, true), validator, null, null, null, componentIndex, Kind.CLUSTERING);
}
public static ColumnDefinition regularDef(CFMetaData cfm, ByteBuffer name, AbstractType<?> validator, Integer componentIndex)
public static ColumnDefinition regularDef(CFMetaData cfm, ByteBuffer name, AbstractType<?> validator)
{
return new ColumnDefinition(cfm, name, validator, componentIndex, Kind.REGULAR);
return new ColumnDefinition(cfm, name, validator, null, Kind.REGULAR);
}
public static ColumnDefinition regularDef(String ksName, String cfName, String name, AbstractType<?> validator, Integer componentIndex)
public static ColumnDefinition regularDef(String ksName, String cfName, String name, AbstractType<?> validator)
{
return new ColumnDefinition(ksName, cfName, ColumnIdentifier.getInterned(name, true), validator, null, null, null, componentIndex, Kind.REGULAR);
return new ColumnDefinition(ksName, cfName, ColumnIdentifier.getInterned(name, true), validator, null, null, null, null, Kind.REGULAR);
}
public static ColumnDefinition staticDef(CFMetaData cfm, ByteBuffer name, AbstractType<?> validator, Integer componentIndex)
public static ColumnDefinition staticDef(CFMetaData cfm, ByteBuffer name, AbstractType<?> validator)
{
return new ColumnDefinition(cfm, name, validator, componentIndex, Kind.STATIC);
return new ColumnDefinition(cfm, name, validator, null, Kind.STATIC);
}
public ColumnDefinition(CFMetaData cfm, ByteBuffer name, AbstractType<?> validator, Integer componentIndex, Kind kind)
@ -150,12 +149,13 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
super(ksName, cfName, name, validator);
assert name != null && validator != null && kind != null;
assert name.isInterned();
assert componentIndex == null || kind.isPrimaryKeyKind(); // The componentIndex really only make sense for partition and clustering columns,
// so make sure we don't sneak it for something else since it'd breaks equals()
this.kind = kind;
this.indexName = indexName;
this.componentIndex = componentIndex;
this.setIndexType(indexType, indexOptions);
this.cellPathComparator = makeCellPathComparator(kind, validator);
this.cellComparator = makeCellComparator(cellPathComparator);
this.comparisonOrder = comparisonOrder(kind, isComplex(), position());
}
@ -185,21 +185,6 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
};
}
private static Comparator<Cell> makeCellComparator(final Comparator<CellPath> cellPathComparator)
{
return new Comparator<Cell>()
{
public int compare(Cell c1, Cell c2)
{
int cmp = c1.column().compareTo(c2.column());
if (cmp != 0 || cellPathComparator == null)
return cmp;
return cellPathComparator.compare(c1.path(), c2.path());
}
};
}
public ColumnDefinition copy()
{
return new ColumnDefinition(ksName, cfName, name, type, indexType, indexOptions, indexName, componentIndex, kind);
@ -422,16 +407,16 @@ public class ColumnDefinition extends ColumnSpecification implements Comparable<
return cellPathComparator;
}
public Comparator<Cell> cellComparator()
{
return cellComparator;
}
public boolean isComplex()
{
return cellPathComparator != null;
}
public boolean isSimple()
{
return !isComplex();
}
public CellPath.Serializer cellPathSerializer()
{
// Collections are our only complex so far, so keep it simple

View File

@ -38,7 +38,6 @@ import static com.google.common.collect.Lists.newArrayList;
*/
public class ColumnCondition
{
public final ColumnDefinition column;
// For collection, when testing the equality of a specific element, null otherwise.
@ -210,7 +209,11 @@ public class ColumnCondition
{
// If we're asking for a complex cells, and we didn't got any row from our read, it's
// the same as not having any cells for that column.
return row == null ? Collections.<Cell>emptyIterator() : row.getCells(column);
if (row == null)
return Collections.<Cell>emptyIterator();
ComplexColumnData complexData = row.getComplexColumnData(column);
return complexData == null ? Collections.<Cell>emptyIterator() : complexData.iterator();
}
/**

View File

@ -23,7 +23,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.BytesType;
import org.apache.cassandra.db.marshal.CounterColumnType;
@ -318,13 +317,13 @@ public abstract class Constants
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
ByteBuffer value = t.bindAndGet(params.options);
if (value == null)
params.addTombstone(column, writer);
params.addTombstone(column);
else if (value != ByteBufferUtil.UNSET_BYTE_BUFFER) // use reference equality and not object equality
params.addCell(clustering, column, writer, value);
params.addCell(column, value);
}
}
@ -335,7 +334,7 @@ public abstract class Constants
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
ByteBuffer bytes = t.bindAndGet(params.options);
if (bytes == null)
@ -344,7 +343,7 @@ public abstract class Constants
return;
long increment = ByteBufferUtil.toLong(bytes);
params.addCounter(column, writer, increment);
params.addCounter(column, increment);
}
}
@ -355,7 +354,7 @@ public abstract class Constants
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
ByteBuffer bytes = t.bindAndGet(params.options);
if (bytes == null)
@ -367,7 +366,7 @@ public abstract class Constants
if (increment == Long.MIN_VALUE)
throw new InvalidRequestException("The negation of " + increment + " overflows supported counter precision (signed 8 bytes integer)");
params.addCounter(column, writer, -increment);
params.addCounter(column, -increment);
}
}
@ -380,12 +379,12 @@ public abstract class Constants
super(column, null);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
if (column.type.isMultiCell())
params.setComplexDeletionTime(column, writer);
params.setComplexDeletionTime(column);
else
params.addTombstone(column, writer);
params.addTombstone(column);
}
};
}
}

View File

@ -21,12 +21,9 @@ import static org.apache.cassandra.cql3.Constants.UNSET_VALUE;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import com.google.common.collect.Iterators;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.*;
@ -72,7 +69,7 @@ public abstract class Lists
validateAssignableTo(keyspace, receiver);
ColumnSpecification valueSpec = Lists.valueSpecOf(receiver);
List<Term> values = new ArrayList<Term>(elements.size());
List<Term> values = new ArrayList<>(elements.size());
boolean allTerminal = true;
for (Term.Raw rt : elements)
{
@ -300,7 +297,7 @@ public abstract class Lists
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
Term.Terminal value = t.bind(params.options);
if (value == UNSET_VALUE)
@ -308,8 +305,8 @@ public abstract class Lists
// delete + append
if (column.type.isMultiCell())
params.setComplexDeletionTimeForOverwrite(column, writer);
Appender.doAppend(value, clustering, writer, column, params);
params.setComplexDeletionTimeForOverwrite(column);
Appender.doAppend(value, column, params);
}
}
@ -318,17 +315,8 @@ public abstract class Lists
if (row == null)
return 0;
Iterator<Cell> cells = row.getCells(column);
return cells == null ? 0 : Iterators.size(cells);
}
private static Cell existingElement(Row row, ColumnDefinition column, int idx)
{
assert row != null;
Iterator<Cell> cells = row.getCells(column);
assert cells != null;
return Iterators.get(cells, idx);
ComplexColumnData complexData = row.getComplexColumnData(column);
return complexData == null ? 0 : complexData.cellsCount();
}
public static class SetterByIndex extends Operation
@ -354,7 +342,7 @@ public abstract class Lists
idx.collectMarkerSpecification(boundNames);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
// we should not get here for frozen lists
assert column.type.isMultiCell() : "Attempted to set an individual element on a frozen list";
@ -367,7 +355,7 @@ public abstract class Lists
if (index == ByteBufferUtil.UNSET_BYTE_BUFFER)
throw new InvalidRequestException("Invalid unset value for list index");
Row existingRow = params.getPrefetchedRow(partitionKey, clustering);
Row existingRow = params.getPrefetchedRow(partitionKey, params.currentClustering());
int existingSize = existingSize(existingRow, column);
int idx = ByteBufferUtil.toInt(index);
if (existingSize == 0)
@ -375,10 +363,10 @@ public abstract class Lists
if (idx < 0 || idx >= existingSize)
throw new InvalidRequestException(String.format("List index %d out of bound, list has size %d", idx, existingSize));
CellPath elementPath = existingElement(existingRow, column, idx).path();
CellPath elementPath = existingRow.getComplexColumnData(column).getCellByIndex(idx).path();
if (value == null)
{
params.addTombstone(column, writer);
params.addTombstone(column);
}
else if (value != ByteBufferUtil.UNSET_BYTE_BUFFER)
{
@ -388,7 +376,7 @@ public abstract class Lists
FBUtilities.MAX_UNSIGNED_SHORT,
value.remaining()));
params.addCell(clustering, column, writer, elementPath, value);
params.addCell(column, elementPath, value);
}
}
}
@ -400,14 +388,14 @@ public abstract class Lists
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to append to a frozen list";
Term.Terminal value = t.bind(params.options);
doAppend(value, clustering, writer, column, params);
doAppend(value, column, params);
}
static void doAppend(Term.Terminal value, Clustering clustering, Row.Writer writer, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
static void doAppend(Term.Terminal value, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
{
if (column.type.isMultiCell())
{
@ -419,16 +407,16 @@ public abstract class Lists
for (ByteBuffer buffer : ((Value) value).elements)
{
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes());
params.addCell(clustering, column, writer, CellPath.create(uuid), buffer);
params.addCell(column, CellPath.create(uuid), buffer);
}
}
else
{
// for frozen lists, we're overwriting the whole cell value
if (value == null)
params.addTombstone(column, writer);
params.addTombstone(column);
else
params.addCell(clustering, column, writer, value.get(Server.CURRENT_VERSION));
params.addCell(column, value.get(Server.CURRENT_VERSION));
}
}
}
@ -440,7 +428,7 @@ public abstract class Lists
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to prepend to a frozen list";
Term.Terminal value = t.bind(params.options);
@ -454,7 +442,7 @@ public abstract class Lists
{
PrecisionTime pt = PrecisionTime.getNext(time);
ByteBuffer uuid = ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes(pt.millis, pt.nanos));
params.addCell(clustering, column, writer, CellPath.create(uuid), toAdd.get(i));
params.addCell(column, CellPath.create(uuid), toAdd.get(i));
}
}
}
@ -472,16 +460,16 @@ public abstract class Lists
return true;
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to delete from a frozen list";
// We want to call bind before possibly returning to reject queries where the value provided is not a list.
Term.Terminal value = t.bind(params.options);
Row existingRow = params.getPrefetchedRow(partitionKey, clustering);
Iterator<Cell> cells = existingRow == null ? null : existingRow.getCells(column);
if (value == null || value == UNSET_VALUE || cells == null)
Row existingRow = params.getPrefetchedRow(partitionKey, params.currentClustering());
ComplexColumnData complexData = existingRow == null ? null : existingRow.getComplexColumnData(column);
if (value == null || value == UNSET_VALUE || complexData == null)
return;
// Note: below, we will call 'contains' on this toDiscard list for each element of existingList.
@ -489,11 +477,10 @@ public abstract class Lists
// the read-before-write this operation requires limits its usefulness on big lists, so in practice
// toDiscard will be small and keeping a list will be more efficient.
List<ByteBuffer> toDiscard = ((Value)value).elements;
while (cells.hasNext())
for (Cell cell : complexData)
{
Cell cell = cells.next();
if (toDiscard.contains(cell.value()))
params.addTombstone(column, writer, cell.path());
params.addTombstone(column, cell.path());
}
}
}
@ -511,7 +498,7 @@ public abstract class Lists
return true;
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to delete an item by index from a frozen list";
Term.Terminal index = t.bind(params.options);
@ -520,7 +507,7 @@ public abstract class Lists
if (index == Constants.UNSET_VALUE)
return;
Row existingRow = params.getPrefetchedRow(partitionKey, clustering);
Row existingRow = params.getPrefetchedRow(partitionKey, params.currentClustering());
int existingSize = existingSize(existingRow, column);
int idx = ByteBufferUtil.toInt(index.get(params.options.getProtocolVersion()));
if (existingSize == 0)
@ -528,7 +515,7 @@ public abstract class Lists
if (idx < 0 || idx >= existingSize)
throw new InvalidRequestException(String.format("List index %d out of bound, list has size %d", idx, existingSize));
params.addTombstone(column, writer, existingElement(existingRow, column, idx).path());
params.addTombstone(column, existingRow.getComplexColumnData(column).getCellByIndex(idx).path());
}
}
}

View File

@ -26,7 +26,6 @@ import com.google.common.collect.Iterables;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.marshal.MapType;
@ -290,7 +289,7 @@ public abstract class Maps
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
Term.Terminal value = t.bind(params.options);
if (value == UNSET_VALUE)
@ -298,8 +297,8 @@ public abstract class Maps
// delete + put
if (column.type.isMultiCell())
params.setComplexDeletionTimeForOverwrite(column, writer);
Putter.doPut(value, clustering, writer, column, params);
params.setComplexDeletionTimeForOverwrite(column);
Putter.doPut(value, column, params);
}
}
@ -320,7 +319,7 @@ public abstract class Maps
k.collectMarkerSpecification(boundNames);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to set a value for a single key on a frozen map";
ByteBuffer key = k.bindAndGet(params.options);
@ -334,7 +333,7 @@ public abstract class Maps
if (value == null)
{
params.addTombstone(column, writer, path);
params.addTombstone(column, path);
}
else if (value != ByteBufferUtil.UNSET_BYTE_BUFFER)
{
@ -344,7 +343,7 @@ public abstract class Maps
FBUtilities.MAX_UNSIGNED_SHORT,
value.remaining()));
params.addCell(clustering, column, writer, path, value);
params.addCell(column, path, value);
}
}
}
@ -356,15 +355,15 @@ public abstract class Maps
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to add items to a frozen map";
Term.Terminal value = t.bind(params.options);
if (value != UNSET_VALUE)
doPut(value, clustering, writer, column, params);
doPut(value, column, params);
}
static void doPut(Term.Terminal value, Clustering clustering, Row.Writer writer, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
static void doPut(Term.Terminal value, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
{
if (column.type.isMultiCell())
{
@ -373,15 +372,15 @@ public abstract class Maps
Map<ByteBuffer, ByteBuffer> elements = ((Value) value).map;
for (Map.Entry<ByteBuffer, ByteBuffer> entry : elements.entrySet())
params.addCell(clustering, column, writer, CellPath.create(entry.getKey()), entry.getValue());
params.addCell(column, CellPath.create(entry.getKey()), entry.getValue());
}
else
{
// for frozen maps, we're overwriting the whole cell
if (value == null)
params.addTombstone(column, writer);
params.addTombstone(column);
else
params.addCell(clustering, column, writer, value.get(Server.CURRENT_VERSION));
params.addCell(column, value.get(Server.CURRENT_VERSION));
}
}
}
@ -393,7 +392,7 @@ public abstract class Maps
super(column, k);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to delete a single key in a frozen map";
Term.Terminal key = t.bind(params.options);
@ -402,7 +401,7 @@ public abstract class Maps
if (key == Constants.UNSET_VALUE)
throw new InvalidRequestException("Invalid unset map key");
params.addTombstone(column, writer, CellPath.create(key.get(params.options.getProtocolVersion())));
params.addTombstone(column, CellPath.create(key.get(params.options.getProtocolVersion())));
}
}
}

View File

@ -21,9 +21,7 @@ import java.util.Collections;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.InvalidRequestException;
@ -87,11 +85,9 @@ public abstract class Operation
* Execute the operation.
*
* @param partitionKey partition key for the update.
* @param clustering the clustering for the row on which the operation applies
* @param writer the row update to which to add the updates generated by this operation.
* @param params parameters of the update.
*/
public abstract void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException;
public abstract void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException;
/**
* A parsed raw UPDATE operation.

View File

@ -26,7 +26,6 @@ import com.google.common.base.Joiner;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.marshal.*;
@ -257,7 +256,7 @@ public abstract class Sets
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
Term.Terminal value = t.bind(params.options);
if (value == UNSET_VALUE)
@ -265,8 +264,8 @@ public abstract class Sets
// delete + add
if (column.type.isMultiCell())
params.setComplexDeletionTimeForOverwrite(column, writer);
Adder.doAdd(value, clustering, writer, column, params);
params.setComplexDeletionTimeForOverwrite(column);
Adder.doAdd(value, column, params);
}
}
@ -277,15 +276,15 @@ public abstract class Sets
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to add items to a frozen set";
Term.Terminal value = t.bind(params.options);
if (value != UNSET_VALUE)
doAdd(value, clustering, writer, column, params);
doAdd(value, column, params);
}
static void doAdd(Term.Terminal value, Clustering clustering, Row.Writer writer, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
static void doAdd(Term.Terminal value, ColumnDefinition column, UpdateParameters params) throws InvalidRequestException
{
if (column.type.isMultiCell())
{
@ -297,16 +296,16 @@ public abstract class Sets
if (bb == ByteBufferUtil.UNSET_BYTE_BUFFER)
continue;
params.addCell(clustering, column, writer, CellPath.create(bb), ByteBufferUtil.EMPTY_BYTE_BUFFER);
params.addCell(column, CellPath.create(bb), ByteBufferUtil.EMPTY_BYTE_BUFFER);
}
}
else
{
// for frozen sets, we're overwriting the whole cell
if (value == null)
params.addTombstone(column, writer);
params.addTombstone(column);
else
params.addCell(clustering, column, writer, value.get(Server.CURRENT_VERSION));
params.addCell(column, value.get(Server.CURRENT_VERSION));
}
}
}
@ -319,7 +318,7 @@ public abstract class Sets
super(column, t);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to remove items from a frozen set";
@ -333,7 +332,7 @@ public abstract class Sets
: Collections.singleton(value.get(params.options.getProtocolVersion()));
for (ByteBuffer bb : toDiscard)
params.addTombstone(column, writer, CellPath.create(bb));
params.addTombstone(column, CellPath.create(bb));
}
}
@ -344,14 +343,14 @@ public abstract class Sets
super(column, k);
}
public void execute(DecoratedKey partitionKey, Clustering clustering, Row.Writer writer, UpdateParameters params) throws InvalidRequestException
public void execute(DecoratedKey partitionKey, UpdateParameters params) throws InvalidRequestException
{
assert column.type.isMultiCell() : "Attempted to delete a single element in a frozen set";
Term.Terminal elt = t.bind(params.options);
if (elt == null)
throw new InvalidRequestException("Invalid null set element");
params.addTombstone(column, writer, CellPath.create(elt.get(params.options.getProtocolVersion())));
params.addTombstone(column, CellPath.create(elt.get(params.options.getProtocolVersion())));
}
}
}

View File

@ -234,18 +234,18 @@ public abstract class UntypedResultSet implements Iterable<UntypedResultSet.Row>
for (ColumnDefinition def : metadata.partitionColumns())
{
if (def.isComplex())
{
Iterator<Cell> cells = row.getCells(def);
if (cells != null)
data.put(def.name.toString(), ((CollectionType)def.type).serializeForNativeProtocol(def, cells, Server.VERSION_3));
}
else
if (def.isSimple())
{
Cell cell = row.getCell(def);
if (cell != null)
data.put(def.name.toString(), cell.value());
}
else
{
ComplexColumnData complexData = row.getComplexColumnData(def);
if (complexData != null)
data.put(def.name.toString(), ((CollectionType)def.type).serializeForNativeProtocol(def, complexData.iterator(), Server.VERSION_3));
}
}
return new Row(data);

View File

@ -29,19 +29,21 @@ import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.index.SecondaryIndexManager;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
/**
* A simple container that simplify passing parameters for collections methods.
* Groups the parameters of an update query, and make building updates easier.
*/
public class UpdateParameters
{
public final CFMetaData metadata;
public final PartitionColumns updatedColumns;
public final QueryOptions options;
private final LivenessInfo defaultLiveness;
private final LivenessInfo deletionLiveness;
private final int nowInSec;
private final long timestamp;
private final int ttl;
private final DeletionTime deletionTime;
private final SecondaryIndexManager indexManager;
@ -49,16 +51,30 @@ public class UpdateParameters
// For lists operation that require a read-before-write. Will be null otherwise.
private final Map<DecoratedKey, Partition> prefetchedRows;
public UpdateParameters(CFMetaData metadata, QueryOptions options, long timestamp, int ttl, Map<DecoratedKey, Partition> prefetchedRows, boolean validateIndexedColumns)
private Row.Builder staticBuilder;
private Row.Builder regularBuilder;
// The builder currently in use. Will alias either staticBuilder or regularBuilder, which are themselves built lazily.
private Row.Builder builder;
public UpdateParameters(CFMetaData metadata,
PartitionColumns updatedColumns,
QueryOptions options,
long timestamp,
int ttl,
Map<DecoratedKey, Partition> prefetchedRows,
boolean validateIndexedColumns)
throws InvalidRequestException
{
this.metadata = metadata;
this.updatedColumns = updatedColumns;
this.options = options;
int nowInSec = FBUtilities.nowInSeconds();
this.defaultLiveness = SimpleLivenessInfo.forUpdate(timestamp, ttl, nowInSec, metadata);
this.deletionLiveness = SimpleLivenessInfo.forDeletion(timestamp, nowInSec);
this.deletionTime = new SimpleDeletionTime(timestamp, nowInSec);
this.nowInSec = FBUtilities.nowInSeconds();
this.timestamp = timestamp;
this.ttl = ttl;
this.deletionTime = new DeletionTime(timestamp, nowInSec);
this.prefetchedRows = prefetchedRows;
@ -85,7 +101,7 @@ public class UpdateParameters
indexManager.validate(partitionKey);
}
public void writeClustering(Clustering clustering, Row.Writer writer) throws InvalidRequestException
public void newRow(Clustering clustering) throws InvalidRequestException
{
if (indexManager != null)
indexManager.validate(clustering);
@ -101,66 +117,93 @@ public class UpdateParameters
throw new InvalidRequestException("Invalid empty or null value for column " + metadata.clusteringColumns().get(0).name);
}
Rows.writeClustering(clustering, writer);
if (clustering == Clustering.STATIC_CLUSTERING)
{
if (staticBuilder == null)
staticBuilder = ArrayBackedRow.unsortedBuilder(updatedColumns.statics, nowInSec);
builder = staticBuilder;
}
else
{
if (regularBuilder == null)
regularBuilder = ArrayBackedRow.unsortedBuilder(updatedColumns.regulars, nowInSec);
builder = regularBuilder;
}
builder.newRow(clustering);
}
public void writePartitionKeyLivenessInfo(Row.Writer writer)
public Clustering currentClustering()
{
writer.writePartitionKeyLivenessInfo(defaultLiveness);
return builder.clustering();
}
public void writeRowDeletion(Row.Writer writer)
public void addPrimaryKeyLivenessInfo()
{
writer.writeRowDeletion(deletionTime);
builder.addPrimaryKeyLivenessInfo(LivenessInfo.create(metadata, timestamp, ttl, nowInSec));
}
public void addTombstone(ColumnDefinition column, Row.Writer writer) throws InvalidRequestException
public void addRowDeletion()
{
addTombstone(column, writer, null);
builder.addRowDeletion(deletionTime);
}
public void addTombstone(ColumnDefinition column, Row.Writer writer, CellPath path) throws InvalidRequestException
public void addTombstone(ColumnDefinition column) throws InvalidRequestException
{
writer.writeCell(column, false, ByteBufferUtil.EMPTY_BYTE_BUFFER, deletionLiveness, path);
addTombstone(column, null);
}
public void addCell(Clustering clustering, ColumnDefinition column, Row.Writer writer, ByteBuffer value) throws InvalidRequestException
public void addTombstone(ColumnDefinition column, CellPath path) throws InvalidRequestException
{
addCell(clustering, column, writer, null, value);
builder.addCell(BufferCell.tombstone(column, timestamp, nowInSec, path));
}
public void addCell(Clustering clustering, ColumnDefinition column, Row.Writer writer, CellPath path, ByteBuffer value) throws InvalidRequestException
public void addCell(ColumnDefinition column, ByteBuffer value) throws InvalidRequestException
{
addCell(column, null, value);
}
public void addCell(ColumnDefinition column, CellPath path, ByteBuffer value) throws InvalidRequestException
{
if (indexManager != null)
indexManager.validate(column, value, path);
writer.writeCell(column, false, value, defaultLiveness, path);
Cell cell = ttl == LivenessInfo.NO_TTL
? BufferCell.live(metadata, column, timestamp, value, path)
: BufferCell.expiring(column, timestamp, ttl, nowInSec, value, path);
builder.addCell(cell);
}
public void addCounter(ColumnDefinition column, Row.Writer writer, long increment) throws InvalidRequestException
public void addCounter(ColumnDefinition column, long increment) throws InvalidRequestException
{
assert defaultLiveness.ttl() == LivenessInfo.NO_TTL;
assert ttl == LivenessInfo.NO_TTL;
// In practice, the actual CounterId (and clock really) that we use doesn't matter, because we will
// actually ignore it in CounterMutation when we do the read-before-write to create the actual value
// that is applied. In other words, this is not the actual value that will be written to the memtable
// ignore it in CounterMutation when we do the read-before-write to create the actual value that is
// applied. In other words, this is not the actual value that will be written to the memtable
// because this will be replaced in CounterMutation.updateWithCurrentValue().
// As an aside, since we don't care about the CounterId/clock, we used to only send the incremement,
// but that makes things a bit more complex as this means we need to be able to distinguish inside
// PartitionUpdate between counter updates that has been processed by CounterMutation and those that
// haven't.
ByteBuffer value = CounterContext.instance().createLocal(increment);
writer.writeCell(column, true, value, defaultLiveness, null);
builder.addCell(BufferCell.live(metadata, column, timestamp, CounterContext.instance().createLocal(increment)));
}
public void setComplexDeletionTime(ColumnDefinition column, Row.Writer writer)
public void setComplexDeletionTime(ColumnDefinition column)
{
writer.writeComplexDeletion(column, deletionTime);
builder.addComplexDeletion(column, deletionTime);
}
public void setComplexDeletionTimeForOverwrite(ColumnDefinition column, Row.Writer writer)
public void setComplexDeletionTimeForOverwrite(ColumnDefinition column)
{
writer.writeComplexDeletion(column, new SimpleDeletionTime(deletionTime.markedForDeleteAt() - 1, deletionTime.localDeletionTime()));
builder.addComplexDeletion(column, new DeletionTime(deletionTime.markedForDeleteAt() - 1, deletionTime.localDeletionTime()));
}
public Row buildRow()
{
Row built = builder.build();
builder = null; // Resetting to null just so we quickly bad usage where we forget to call newRow() after that.
return built;
}
public DeletionTime deletionTime()

View File

@ -307,10 +307,19 @@ public abstract class Selection
current.add(value(c));
if (timestamps != null)
timestamps[current.size() - 1] = c.livenessInfo().timestamp();
timestamps[current.size() - 1] = c.timestamp();
if (ttls != null)
ttls[current.size() - 1] = c.livenessInfo().remainingTTL(nowInSec);
ttls[current.size() - 1] = remainingTTL(c, nowInSec);
}
private int remainingTTL(Cell c, int nowInSec)
{
if (!c.isExpiring())
return -1;
int remaining = c.localDeletionTime() - nowInSec;
return remaining >= 0 ? remaining : -1;
}
private ByteBuffer value(Cell c)

View File

@ -141,10 +141,9 @@ public class AlterTableStatement extends SchemaAlteringStatement
}
}
Integer componentIndex = cfm.isCompound() ? cfm.comparator.size() : null;
cfm.addColumnDefinition(isStatic
? ColumnDefinition.staticDef(cfm, columnName.bytes, type, componentIndex)
: ColumnDefinition.regularDef(cfm, columnName.bytes, type, componentIndex));
? ColumnDefinition.staticDef(cfm, columnName.bytes, type)
: ColumnDefinition.regularDef(cfm, columnName.bytes, type));
break;
case ALTER:

View File

@ -200,7 +200,7 @@ public class CQL3CasRequest implements CASRequest
public void applyUpdates(FilteredPartition current, PartitionUpdate updates) throws InvalidRequestException
{
Map<DecoratedKey, Partition> map = stmt.requiresRead() ? Collections.<DecoratedKey, Partition>singletonMap(key, current) : null;
UpdateParameters params = new UpdateParameters(cfm, options, timestamp, stmt.getTimeToLive(options), map, true);
UpdateParameters params = new UpdateParameters(cfm, updates.columns(), options, timestamp, stmt.getTimeToLive(options), map, true);
stmt.addUpdateForKey(updates, cbuilder, params);
}
}

View File

@ -26,7 +26,6 @@ import org.apache.cassandra.cql3.restrictions.Restriction;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.utils.Pair;
@ -62,16 +61,14 @@ public class DeleteStatement extends ModificationStatement
// ... or a row deletion ...
else if (cbuilder.remainingCount() == 0)
{
Clustering clustering = cbuilder.build();
Row.Writer writer = update.writer();
params.writeClustering(clustering, writer);
params.writeRowDeletion(writer);
writer.endOfRow();
params.newRow(cbuilder.build());
params.addRowDeletion();
update.add(params.buildRow());
}
// ... or a range of rows deletion.
else
{
update.addRangeTombstone(params.makeRangeTombstone(cbuilder));
update.add(params.makeRangeTombstone(cbuilder));
}
}
else
@ -82,20 +79,18 @@ public class DeleteStatement extends ModificationStatement
if (cbuilder.remainingCount() > 0)
throw new InvalidRequestException(String.format("Primary key column '%s' must be specified in order to delete column '%s'", getFirstEmptyKey().name, regularDeletions.get(0).column.name));
Clustering clustering = cbuilder.build();
Row.Writer writer = update.writer();
params.writeClustering(clustering, writer);
params.newRow(cbuilder.build());
for (Operation op : regularDeletions)
op.execute(update.partitionKey(), clustering, writer, params);
writer.endOfRow();
op.execute(update.partitionKey(), params);
update.add(params.buildRow());
}
if (!staticDeletions.isEmpty())
{
Row.Writer writer = update.staticWriter();
params.newRow(Clustering.STATIC_CLUSTERING);
for (Operation op : staticDeletions)
op.execute(update.partitionKey(), Clustering.STATIC_CLUSTERING, writer, params);
writer.endOfRow();
op.execute(update.partitionKey(), params);
update.add(params.buildRow());
}
}
}

View File

@ -816,7 +816,7 @@ public abstract class ModificationStatement implements CQLStatement
{
// Some lists operation requires reading
Map<DecoratedKey, Partition> lists = readRequiredLists(keys, clustering, local, options.getConsistency());
return new UpdateParameters(cfm, options, getTimestamp(now, options), getTimeToLive(options), lists, true);
return new UpdateParameters(cfm, updatedColumns(), options, getTimestamp(now, options), getTimeToLive(options), lists, true);
}
/**

View File

@ -594,7 +594,7 @@ public class SelectStatement implements CQLStatement
ByteBuffer[] keyComponents = getComponents(cfm, partition.partitionKey());
Row staticRow = partition.staticRow().takeAlias();
Row staticRow = partition.staticRow();
// If there is no rows, then provided the select was a full partition selection
// (i.e. not a 2ndary index search and there was no condition on clustering columns),
// we want to include static columns and we're done.
@ -653,11 +653,11 @@ public class SelectStatement implements CQLStatement
{
// Collections are the only complex types we have so far
assert def.type.isCollection() && def.type.isMultiCell();
Iterator<Cell> cells = row.getCells(def);
if (cells == null)
ComplexColumnData complexData = row.getComplexColumnData(def);
if (complexData == null)
result.add((ByteBuffer)null);
else
result.add(((CollectionType)def.type).serializeForNativeProtocol(def, cells, protocolVersion));
result.add(((CollectionType)def.type).serializeForNativeProtocol(def, complexData.iterator(), protocolVersion));
}
else
{

View File

@ -23,7 +23,6 @@ import org.apache.cassandra.cql3.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.PartitionUpdate;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.utils.ByteBufferUtil;
@ -54,16 +53,13 @@ public class UpdateStatement extends ModificationStatement
if (updatesRegularRows())
{
Clustering clustering = cbuilder.build();
Row.Writer writer = update.writer();
params.writeClustering(clustering, writer);
params.newRow(cbuilder.build());
// We update the row timestamp (ex-row marker) only on INSERT (#6782)
// Further, COMPACT tables semantic differs from "CQL3" ones in that a row exists only if it has
// a non-null column, so we don't want to set the row timestamp for them.
if (type == StatementType.INSERT && cfm.isCQLTable())
params.writePartitionKeyLivenessInfo(writer);
params.addPrimaryKeyLivenessInfo();
List<Operation> updates = getRegularOperations();
@ -82,17 +78,17 @@ public class UpdateStatement extends ModificationStatement
}
for (Operation op : updates)
op.execute(update.partitionKey(), clustering, writer, params);
op.execute(update.partitionKey(), params);
writer.endOfRow();
update.add(params.buildRow());
}
if (updatesStaticRow())
{
Row.Writer writer = update.staticWriter();
params.newRow(Clustering.STATIC_CLUSTERING);
for (Operation op : getStaticOperations())
op.execute(update.partitionKey(), Clustering.STATIC_CLUSTERING, writer, params);
writer.endOfRow();
op.execute(update.partitionKey(), params);
update.add(params.buildRow());
}
}

View File

@ -22,14 +22,48 @@ import java.security.MessageDigest;
import java.util.Objects;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.ObjectSizes;
public abstract class AbstractClusteringPrefix implements ClusteringPrefix
{
protected static final ByteBuffer[] EMPTY_VALUES_ARRAY = new ByteBuffer[0];
private static final long EMPTY_SIZE = ObjectSizes.measure(new Clustering(EMPTY_VALUES_ARRAY));
protected final Kind kind;
protected final ByteBuffer[] values;
protected AbstractClusteringPrefix(Kind kind, ByteBuffer[] values)
{
this.kind = kind;
this.values = values;
}
public Kind kind()
{
return kind;
}
public ClusteringPrefix clustering()
{
return this;
}
public int size()
{
return values.length;
}
public ByteBuffer get(int i)
{
return values[i];
}
public ByteBuffer[] getRawValues()
{
return values;
}
public int dataSize()
{
int size = 0;
@ -47,22 +81,19 @@ public abstract class AbstractClusteringPrefix implements ClusteringPrefix
{
ByteBuffer bb = get(i);
if (bb != null)
digest.update(bb.duplicate());
digest.update(bb.duplicate());
}
FBUtilities.updateWithByte(digest, kind().ordinal());
}
public void writeTo(Writer writer)
{
for (int i = 0; i < size(); i++)
writer.writeClusteringValue(get(i));
}
public long unsharedHeapSize()
{
// unsharedHeapSize is used inside the cache and in memtables. Implementations that are
// safe to use there (SimpleClustering, Slice.Bound.SimpleBound and MemtableRow.* classes) overwrite this.
throw new UnsupportedOperationException();
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(values);
}
public long unsharedHeapSizeExcludingData()
{
return EMPTY_SIZE + ObjectSizes.sizeOnHeapExcludingData(values);
}
@Override

View File

@ -1,164 +0,0 @@
/*
* 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.Objects;
import java.security.MessageDigest;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.FBUtilities;
/**
* Base abstract class for {@code LivenessInfo} implementations.
*
* All {@code LivenessInfo} should extends this class unless it has a very
* good reason not to.
*/
public abstract class AbstractLivenessInfo implements LivenessInfo
{
public boolean hasTimestamp()
{
return timestamp() != NO_TIMESTAMP;
}
public boolean hasTTL()
{
return ttl() != NO_TTL;
}
public boolean hasLocalDeletionTime()
{
return localDeletionTime() != NO_DELETION_TIME;
}
public int remainingTTL(int nowInSec)
{
if (!hasTTL())
return -1;
int remaining = localDeletionTime() - nowInSec;
return remaining >= 0 ? remaining : -1;
}
public boolean isLive(int nowInSec)
{
// Note that we don't rely on localDeletionTime() only because if we were to, we
// could potentially consider a tombstone as a live cell (due to time skew). So
// if a cell has a local deletion time and no ttl, it's a tombstone and consider
// dead no matter what it's actual local deletion value is.
return hasTimestamp() && (!hasLocalDeletionTime() || (hasTTL() && nowInSec < localDeletionTime()));
}
public void digest(MessageDigest digest)
{
FBUtilities.updateWithLong(digest, timestamp());
FBUtilities.updateWithInt(digest, localDeletionTime());
FBUtilities.updateWithInt(digest, ttl());
}
public void validate()
{
if (ttl() < 0)
throw new MarshalException("A TTL should not be negative");
if (localDeletionTime() < 0)
throw new MarshalException("A local deletion time should not be negative");
if (hasTTL() && !hasLocalDeletionTime())
throw new MarshalException("Shoud not have a TTL without an associated local deletion time");
}
public int dataSize()
{
int size = 0;
if (hasTimestamp())
size += TypeSizes.sizeof(timestamp());
if (hasTTL())
size += TypeSizes.sizeof(ttl());
if (hasLocalDeletionTime())
size += TypeSizes.sizeof(localDeletionTime());
return size;
}
public boolean supersedes(LivenessInfo other)
{
return timestamp() > other.timestamp();
}
public LivenessInfo mergeWith(LivenessInfo other)
{
return supersedes(other) ? this : other;
}
public LivenessInfo takeAlias()
{
return new SimpleLivenessInfo(timestamp(), ttl(), localDeletionTime());
};
public LivenessInfo withUpdatedTimestamp(long newTimestamp)
{
if (!hasTimestamp())
return this;
return new SimpleLivenessInfo(newTimestamp, ttl(), localDeletionTime());
}
public boolean isPurgeable(long maxPurgeableTimestamp, int gcBefore)
{
return timestamp() < maxPurgeableTimestamp && localDeletionTime() < gcBefore;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append('[');
boolean needSpace = false;
if (hasTimestamp())
{
sb.append("ts=").append(timestamp());
needSpace = true;
}
if (hasTTL())
{
sb.append(needSpace ? ' ' : "").append("ttl=").append(ttl());
needSpace = true;
}
if (hasLocalDeletionTime())
sb.append(needSpace ? ' ' : "").append("ldt=").append(localDeletionTime());
sb.append(']');
return sb.toString();
}
@Override
public boolean equals(Object other)
{
if(!(other instanceof LivenessInfo))
return false;
LivenessInfo that = (LivenessInfo)other;
return this.timestamp() == that.timestamp()
&& this.ttl() == that.ttl()
&& this.localDeletionTime() == that.localDeletionTime();
}
@Override
public int hashCode()
{
return Objects.hash(timestamp(), ttl(), localDeletionTime());
}
}

View File

@ -1,62 +0,0 @@
/*
* 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;
/**
* This interface marks objects that are only valid in a restricted scope and
* shouldn't be simply aliased outside of this scope (in other words, you should
* not keep a reference to the object that escaped said scope as the object will
* likely become invalid).
*
* For instance, most {@link RowIterator} implementation reuse the same {@link
* Row} object during iteration. This means that the following code would be
* incorrect.
* <pre>
* RowIterator iter = ...;
* Row someRow = null;
* while (iter.hasNext())
* {
* Row row = iter.next();
* if (someCondition(row))
* someRow = row; // This isn't safe
* doSomethingElse();
* }
* useRow(someRow);
* </pre>
* The problem being that, because the row iterator reuse the same object,
* {@code someRow} will not point to the row that had met {@code someCondition}
* at the end of the iteration ({@code someRow} will point to the last iterated
* row in practice).
*
* When code do need to alias such {@code Aliasable} object, it should call the
* {@code takeAlias} method that will make a copy of the object if necessary.
*
* Of course, the {@code takeAlias} should not be abused, as it defeat the purpose
* of sharing objects in the first place.
*
* Also note that some implementation of an {@code Aliasable} object may be
* safe to alias, in which case its {@code takeAlias} method will be a no-op.
*/
public interface Aliasable<T>
{
/**
* Returns either this object (if it's safe to alias) or a copy of it
* (it it isn't safe to alias).
*/
public T takeAlias();
}

View File

@ -163,7 +163,7 @@ public abstract class CBuilder
built = true;
// Currently, only dense table can leave some clustering column out (see #7990)
return size == 0 ? Clustering.EMPTY : new SimpleClustering(values);
return size == 0 ? Clustering.EMPTY : new Clustering(values);
}
public Slice.Bound buildBound(boolean isStart, boolean isInclusive)
@ -197,7 +197,7 @@ public abstract class CBuilder
ByteBuffer[] newValues = Arrays.copyOf(values, size+1);
newValues[size] = value;
return new SimpleClustering(newValues);
return new Clustering(newValues);
}
public Clustering buildWith(List<ByteBuffer> newValues)
@ -208,7 +208,7 @@ public abstract class CBuilder
for (ByteBuffer value : newValues)
buffers[newSize++] = value;
return new SimpleClustering(buffers);
return new Clustering(buffers);
}
public Slice.Bound buildBoundWith(ByteBuffer value, boolean isStart, boolean isInclusive)

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@ -25,7 +24,9 @@ import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.memory.AbstractAllocator;
/**
* The clustering column values for a row.
@ -39,7 +40,7 @@ import org.apache.cassandra.io.util.DataOutputPlus;
* all of the following ones will be too because that's what thrift allows, but it's never assumed by the
* code so we could start generally allowing nulls for clustering columns if we wanted to).
*/
public abstract class Clustering extends AbstractClusteringPrefix
public class Clustering extends AbstractClusteringPrefix
{
public static final Serializer serializer = new Serializer();
@ -47,7 +48,7 @@ public abstract class Clustering extends AbstractClusteringPrefix
* The special cased clustering used by all static rows. It is a special case in the
* sense that it's always empty, no matter how many clustering columns the table has.
*/
public static final Clustering STATIC_CLUSTERING = new EmptyClustering()
public static final Clustering STATIC_CLUSTERING = new Clustering(EMPTY_VALUES_ARRAY)
{
@Override
public Kind kind()
@ -63,19 +64,35 @@ public abstract class Clustering extends AbstractClusteringPrefix
};
/** Empty clustering for tables having no clustering columns. */
public static final Clustering EMPTY = new EmptyClustering();
public static final Clustering EMPTY = new Clustering(EMPTY_VALUES_ARRAY)
{
@Override
public String toString(CFMetaData metadata)
{
return "EMPTY";
}
};
public Clustering(ByteBuffer... values)
{
super(Kind.CLUSTERING, values);
}
public Kind kind()
{
return Kind.CLUSTERING;
}
public Clustering takeAlias()
public Clustering copy(AbstractAllocator allocator)
{
ByteBuffer[] values = new ByteBuffer[size()];
// Important for STATIC_CLUSTERING (but no point in being wasteful in general).
if (size() == 0)
return this;
ByteBuffer[] newValues = new ByteBuffer[size()];
for (int i = 0; i < size(); i++)
values[i] = get(i);
return new SimpleClustering(values);
newValues[i] = values[i] == null ? null : allocator.clone(values[i]);
return new Clustering(newValues);
}
public String toString(CFMetaData metadata)
@ -84,7 +101,7 @@ public abstract class Clustering extends AbstractClusteringPrefix
for (int i = 0; i < size(); i++)
{
ColumnDefinition c = metadata.clusteringColumns().get(i);
sb.append(i == 0 ? "" : ", ").append(c.name).append("=").append(get(i) == null ? "null" : c.type.getString(get(i)));
sb.append(i == 0 ? "" : ", ").append(c.name).append('=').append(get(i) == null ? "null" : c.type.getString(get(i)));
}
return sb.toString();
}
@ -100,44 +117,6 @@ public abstract class Clustering extends AbstractClusteringPrefix
return sb.toString();
}
private static class EmptyClustering extends Clustering
{
private static final ByteBuffer[] EMPTY_VALUES_ARRAY = new ByteBuffer[0];
public int size()
{
return 0;
}
public ByteBuffer get(int i)
{
throw new UnsupportedOperationException();
}
public ByteBuffer[] getRawValues()
{
return EMPTY_VALUES_ARRAY;
}
@Override
public Clustering takeAlias()
{
return this;
}
@Override
public long unsharedHeapSize()
{
return 0;
}
@Override
public String toString(CFMetaData metadata)
{
return "EMPTY";
}
}
/**
* Serializer for Clustering object.
* <p>
@ -148,6 +127,7 @@ public abstract class Clustering extends AbstractClusteringPrefix
{
public void serialize(Clustering clustering, DataOutputPlus out, int version, List<AbstractType<?>> types) throws IOException
{
assert clustering != STATIC_CLUSTERING : "We should never serialize a static clustering";
ClusteringPrefix.serializer.serializeValuesWithoutSize(clustering, out, version, types);
}
@ -156,16 +136,13 @@ public abstract class Clustering extends AbstractClusteringPrefix
return ClusteringPrefix.serializer.valuesWithoutSizeSerializedSize(clustering, version, types);
}
public void deserialize(DataInput in, int version, List<AbstractType<?>> types, Writer writer) throws IOException
public Clustering deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{
ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, types.size(), version, types, writer);
}
if (types.isEmpty())
return EMPTY;
public Clustering deserialize(DataInput in, int version, List<AbstractType<?>> types) throws IOException
{
SimpleClustering.Builder builder = SimpleClustering.builder(types.size());
deserialize(in, version, types, builder);
return builder.build();
ByteBuffer[] values = ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, types.size(), version, types);
return new Clustering(values);
}
}
}

View File

@ -25,7 +25,9 @@ import java.util.Objects;
import com.google.common.base.Joiner;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.io.sstable.IndexHelper.IndexInfo;
@ -46,9 +48,11 @@ public class ClusteringComparator implements Comparator<Clusterable>
private final Comparator<IndexInfo> indexReverseComparator;
private final Comparator<Clusterable> reverseComparator;
private final Comparator<Row> rowComparator = (r1, r2) -> compare(r1.clustering(), r2.clustering());
public ClusteringComparator(AbstractType<?>... clusteringTypes)
{
this(Arrays.<AbstractType<?>>asList(clusteringTypes));
this(Arrays.asList(clusteringTypes));
}
public ClusteringComparator(List<AbstractType<?>> clusteringTypes)
@ -56,27 +60,9 @@ public class ClusteringComparator implements Comparator<Clusterable>
this.clusteringTypes = clusteringTypes;
this.isByteOrderComparable = isByteOrderComparable(clusteringTypes);
this.indexComparator = new Comparator<IndexInfo>()
{
public int compare(IndexInfo o1, IndexInfo o2)
{
return ClusteringComparator.this.compare(o1.lastName, o2.lastName);
}
};
this.indexReverseComparator = new Comparator<IndexInfo>()
{
public int compare(IndexInfo o1, IndexInfo o2)
{
return ClusteringComparator.this.compare(o1.firstName, o2.firstName);
}
};
this.reverseComparator = new Comparator<Clusterable>()
{
public int compare(Clusterable c1, Clusterable c2)
{
return ClusteringComparator.this.compare(c2, c1);
}
};
this.indexComparator = (o1, o2) -> ClusteringComparator.this.compare(o1.lastName, o2.lastName);
this.indexReverseComparator = (o1, o2) -> ClusteringComparator.this.compare(o1.firstName, o2.firstName);
this.reverseComparator = (c1, c2) -> ClusteringComparator.this.compare(c2, c1);
}
private static boolean isByteOrderComparable(Iterable<AbstractType<?>> types)
@ -130,11 +116,10 @@ public class ClusteringComparator implements Comparator<Clusterable>
throw new IllegalArgumentException(String.format("Invalid number of components, expecting %d but got %d", size(), values.length));
CBuilder builder = CBuilder.create(this);
for (int i = 0; i < values.length; i++)
for (Object val : values)
{
Object val = values[i];
if (val instanceof ByteBuffer)
builder.add((ByteBuffer)val);
builder.add((ByteBuffer) val);
else
builder.add(val);
}
@ -179,7 +164,7 @@ public class ClusteringComparator implements Comparator<Clusterable>
public int compareComponent(int i, ByteBuffer v1, ByteBuffer v2)
{
if (v1 == null)
return v1 == null ? 0 : -1;
return v2 == null ? 0 : -1;
if (v2 == null)
return 1;
@ -233,6 +218,19 @@ public class ClusteringComparator implements Comparator<Clusterable>
}
}
/**
* A comparator for rows.
*
* A {@code Row} is a {@code Clusterable} so {@code ClusteringComparator} can be used
* to compare rows directly, but when we know we deal with rows (and not {@code Clusterable} in
* general), this is a little faster because by knowing we compare {@code Clustering} objects,
* we know that 1) they all have the same size and 2) they all have the same kind.
*/
public Comparator<Row> rowComparator()
{
return rowComparator;
}
public Comparator<IndexInfo> indexComparator(boolean reversed)
{
return reversed ? indexReverseComparator : indexComparator;
@ -243,27 +241,6 @@ public class ClusteringComparator implements Comparator<Clusterable>
return reverseComparator;
}
/**
* Whether the two provided clustering prefix are on the same clustering values.
*
* @param c1 the first prefix.
* @param c2 the second prefix.
* @return whether {@code c1} and {@code c2} have the same clustering values (but not necessarily
* the same "kind") or not.
*/
public boolean isOnSameClustering(ClusteringPrefix c1, ClusteringPrefix c2)
{
if (c1.size() != c2.size())
return false;
for (int i = 0; i < c1.size(); i++)
{
if (compareComponent(i, c1.get(i), c2.get(i)) != 0)
return false;
}
return true;
}
@Override
public String toString()
{

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
@ -27,29 +26,31 @@ import org.apache.cassandra.cache.IMeasurableMemory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* A clustering prefix is basically the unit of what a {@link ClusteringComparator} can compare.
* A clustering prefix is the unit of what a {@link ClusteringComparator} can compare.
* <p>
* It holds values for the clustering columns of a table (potentially only a prefix of all of them) and it has
* It holds values for the clustering columns of a table (potentially only a prefix of all of them) and has
* a "kind" that allows us to implement slices with inclusive and exclusive bounds.
* <p>
* In practice, {@code ClusteringPrefix} is just the common parts to its 2 main subtype: {@link Clustering} and
* {@link Slice.Bound}, where:
* In practice, {@code ClusteringPrefix} is just the common parts to its 3 main subtype: {@link Clustering} and
* {@link Slice.Bound}/{@link RangeTombstone.Bound}, where:
* 1) {@code Clustering} represents the clustering values for a row, i.e. the values for it's clustering columns.
* 2) {@code Slice.Bound} represents a bound (start or end) of a slice (of rows).
* 3) {@code RangeTombstoneBoundMarker.Bound} represents a range tombstone marker "bound".
* See those classes for more details.
*/
public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurableMemory, Clusterable
public interface ClusteringPrefix extends IMeasurableMemory, Clusterable
{
public static final Serializer serializer = new Serializer();
/**
* The kind of clustering prefix this actually is.
*
* The kind {@code STATIC_CLUSTERING} is only implemented by {@link Clustering.STATIC_CLUSTERING} and {@code CLUSTERING} is
* The kind {@code STATIC_CLUSTERING} is only implemented by {@link Clustering#STATIC_CLUSTERING} and {@code CLUSTERING} is
* implemented by the {@link Clustering} class. The rest is used by {@link Slice.Bound} and {@link RangeTombstone.Bound}.
*/
public enum Kind
@ -166,12 +167,12 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
public boolean isOpen(boolean reversed)
{
return reversed ? isEnd() : isStart();
return isBoundary() || (reversed ? isEnd() : isStart());
}
public boolean isClose(boolean reversed)
{
return reversed ? isStart() : isEnd();
return isBoundary() || (reversed ? isStart() : isEnd());
}
public Kind closeBoundOfBoundary(boolean reversed)
@ -211,15 +212,29 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
*/
public ByteBuffer get(int i);
/**
* Adds the data of this clustering prefix to the provided digest.
*
* @param digest the digest to which to add this prefix.
*/
public void digest(MessageDigest digest);
// Used to verify if batches goes over a given size
/**
* The size of the data hold by this prefix.
*
* @return the size of the data hold by this prefix (this is not the size of the object in memory, just
* the size of the data it stores).
*/
public int dataSize();
/**
* Generates a proper string representation of the prefix.
*
* @param metadata the metadata for the table the clustering prefix is of.
* @return a human-readable string representation fo this prefix.
*/
public String toString(CFMetaData metadata);
public void writeTo(Writer writer);
/**
* The values of this prefix as an array.
* <p>
@ -231,21 +246,6 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
*/
public ByteBuffer[] getRawValues();
/**
* Interface for writing a clustering prefix.
* <p>
* Each value for the prefix should simply be written in order.
*/
public interface Writer
{
/**
* Write the next value to the writer.
*
* @param value the value to write.
*/
public void writeClusteringValue(ByteBuffer value);
}
public static class Serializer
{
public void serialize(ClusteringPrefix clustering, DataOutputPlus out, int version, List<AbstractType<?>> types) throws IOException
@ -263,7 +263,7 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
}
}
public ClusteringPrefix deserialize(DataInput in, int version, List<AbstractType<?>> types) throws IOException
public ClusteringPrefix deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{
Kind kind = Kind.values()[in.readByte()];
// We shouldn't serialize static clusterings
@ -317,21 +317,20 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
return size;
}
void deserializeValuesWithoutSize(DataInput in, int size, int version, List<AbstractType<?>> types, ClusteringPrefix.Writer writer) throws IOException
ByteBuffer[] deserializeValuesWithoutSize(DataInputPlus in, int size, int version, List<AbstractType<?>> types) throws IOException
{
if (size == 0)
return;
// Callers of this method should handle the case where size = 0 (in all case we want to return a special value anyway).
assert size > 0;
ByteBuffer[] values = new ByteBuffer[size];
int[] header = readHeader(size, in);
for (int i = 0; i < size; i++)
{
if (isNull(header, i))
writer.writeClusteringValue(null);
else if (isEmpty(header, i))
writer.writeClusteringValue(ByteBufferUtil.EMPTY_BYTE_BUFFER);
else
writer.writeClusteringValue(types.get(i).readValue(in));
values[i] = isNull(header, i)
? null
: (isEmpty(header, i) ? ByteBufferUtil.EMPTY_BYTE_BUFFER : types.get(i).readValue(in));
}
return values;
}
private int headerBytesCount(int size)
@ -369,7 +368,7 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
}
}
private int[] readHeader(int size, DataInput in) throws IOException
private int[] readHeader(int size, DataInputPlus in) throws IOException
{
int nbBytes = headerBytesCount(size);
int[] header = new int[nbBytes];
@ -378,14 +377,14 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
return header;
}
private boolean isNull(int[] header, int i)
private static boolean isNull(int[] header, int i)
{
int b = header[i / 4];
int mask = 1 << ((i % 4) * 2) + 1;
return (b & mask) != 0;
}
private boolean isEmpty(int[] header, int i)
private static boolean isEmpty(int[] header, int i)
{
int b = header[i / 4];
int mask = 1 << ((i % 4) * 2);
@ -405,7 +404,7 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
public static class Deserializer
{
private final ClusteringComparator comparator;
private final DataInput in;
private final DataInputPlus in;
private final SerializationHeader serializationHeader;
private boolean nextIsRow;
@ -414,14 +413,13 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
private int nextSize;
private ClusteringPrefix.Kind nextKind;
private int deserializedSize;
private final ByteBuffer[] nextValues;
private ByteBuffer[] nextValues;
public Deserializer(ClusteringComparator comparator, DataInput in, SerializationHeader header)
public Deserializer(ClusteringComparator comparator, DataInputPlus in, SerializationHeader header)
{
this.comparator = comparator;
this.in = in;
this.serializationHeader = header;
this.nextValues = new ByteBuffer[comparator.size()];
}
public void prepare(int flags) throws IOException
@ -432,6 +430,14 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
this.nextSize = nextIsRow ? comparator.size() : in.readUnsignedShort();
this.nextHeader = serializer.readHeader(nextSize, in);
this.deserializedSize = 0;
// The point of the deserializer is that some of the clustering prefix won't actually be used (because they are not
// within the bounds of the query), and we want to reduce allocation for them. So we only reuse the values array
// between elements if 1) we haven't returned the previous element (if we have, nextValues will be null) and 2)
// nextValues is of the proper size. Note that the 2nd condition may not hold for range tombstone bounds, but all
// rows have a fixed size clustering, so we'll still save in the common case.
if (nextValues == null || nextValues.length != nextSize)
this.nextValues = new ByteBuffer[nextSize];
}
public int compareNextTo(Slice.Bound bound) throws IOException
@ -473,9 +479,9 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
return false;
int i = deserializedSize++;
nextValues[i] = serializer.isNull(nextHeader, i)
nextValues[i] = Serializer.isNull(nextHeader, i)
? null
: (serializer.isEmpty(nextHeader, i) ? ByteBufferUtil.EMPTY_BYTE_BUFFER : serializationHeader.clusteringTypes().get(i).readValue(in));
: (Serializer.isEmpty(nextHeader, i) ? ByteBufferUtil.EMPTY_BYTE_BUFFER : serializationHeader.clusteringTypes().get(i).readValue(in));
return true;
}
@ -485,29 +491,31 @@ public interface ClusteringPrefix extends Aliasable<ClusteringPrefix>, IMeasurab
continue;
}
public RangeTombstone.Bound.Kind deserializeNextBound(RangeTombstone.Bound.Writer writer) throws IOException
public RangeTombstone.Bound deserializeNextBound() throws IOException
{
assert !nextIsRow;
deserializeAll();
for (int i = 0; i < nextSize; i++)
writer.writeClusteringValue(nextValues[i]);
writer.writeBoundKind(nextKind);
return nextKind;
RangeTombstone.Bound bound = new RangeTombstone.Bound(nextKind, nextValues);
nextValues = null;
return bound;
}
public void deserializeNextClustering(Clustering.Writer writer) throws IOException
public Clustering deserializeNextClustering() throws IOException
{
assert nextIsRow && nextSize == nextValues.length;
assert nextIsRow;
deserializeAll();
for (int i = 0; i < nextSize; i++)
writer.writeClusteringValue(nextValues[i]);
Clustering clustering = new Clustering(nextValues);
nextValues = null;
return clustering;
}
public ClusteringPrefix.Kind skipNext() throws IOException
{
for (int i = deserializedSize; i < nextSize; i++)
if (!serializer.isNull(nextHeader, i) && !serializer.isEmpty(nextHeader, i))
{
if (!Serializer.isNull(nextHeader, i) && !Serializer.isEmpty(nextHeader, i))
serializationHeader.clusteringTypes().get(i).skipValue(in);
}
return nextKind;
}
}

View File

@ -74,7 +74,7 @@ public class ColumnIndex
private int written;
private ClusteringPrefix firstClustering;
private final ReusableClusteringPrefix lastClustering;
private ClusteringPrefix lastClustering;
private DeletionTime openMarker;
@ -90,7 +90,6 @@ public class ColumnIndex
this.result = new ColumnIndex(new ArrayList<IndexHelper.IndexInfo>());
this.initialPosition = writer.getFilePointer();
this.lastClustering = new ReusableClusteringPrefix(iterator.metadata().clusteringColumns().size());
}
private void writePartitionHeader(UnfilteredRowIterator iterator) throws IOException
@ -119,7 +118,7 @@ public class ColumnIndex
private void addIndexBlock()
{
IndexHelper.IndexInfo cIndexInfo = new IndexHelper.IndexInfo(firstClustering,
lastClustering.get().takeAlias(),
lastClustering,
startPosition,
currentPosition() - startPosition,
openMarker);
@ -129,28 +128,27 @@ public class ColumnIndex
private void add(Unfiltered unfiltered) throws IOException
{
lastClustering.copy(unfiltered.clustering());
boolean isMarker = unfiltered.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER;
if (firstClustering == null)
{
// Beginning of an index block. Remember the start and position
firstClustering = lastClustering.get().takeAlias();
firstClustering = unfiltered.clustering();
startPosition = currentPosition();
}
UnfilteredSerializer.serializer.serialize(unfiltered, header, writer.stream, version);
lastClustering = unfiltered.clustering();
++written;
if (isMarker)
if (unfiltered.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
{
RangeTombstoneMarker marker = (RangeTombstoneMarker) unfiltered;
RangeTombstoneMarker marker = (RangeTombstoneMarker)unfiltered;
openMarker = marker.isOpen(false) ? marker.openDeletionTime(false) : null;
}
// if we hit the column index size that we have to index after, go ahead and index it.
if (currentPosition() - startPosition >= DatabaseDescriptor.getColumnIndexSize())
addIndexBlock();
}
private ColumnIndex close() throws IOException

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
@ -329,9 +330,9 @@ public class Columns implements Iterable<ColumnDefinition>
}
/**
* Whether this object is a subset of the provided other {@code Columns object}.
* Whether this object is a superset of the provided other {@code Columns object}.
*
* @param other the othere object to test for inclusion in this object.
* @param other the other object to test for inclusion in this object.
*
* @return whether all the columns of {@code other} are contained by this object.
*/
@ -439,6 +440,34 @@ public class Columns implements Iterable<ColumnDefinition>
return new Columns(newColumns);
}
/**
* Returns a predicate to test whether columns are included in this {@code Columns} object,
* assuming that tes tested columns are passed to the predicate in sorted order.
*
* @return a predicate to test the inclusion of sorted columns in this object.
*/
public Predicate<ColumnDefinition> inOrderInclusionTester()
{
return new Predicate<ColumnDefinition>()
{
private int i = 0;
public boolean test(ColumnDefinition column)
{
while (i < columns.length)
{
int cmp = column.compareTo(columns[i]);
if (cmp < 0)
return false;
i++;
if (cmp == 0)
return true;
}
return false;
}
};
}
public void digest(MessageDigest digest)
{
for (ColumnDefinition c : this)

View File

@ -155,7 +155,7 @@ public class CounterMutation implements IMutation
/**
* Returns a wrapper for the Striped#bulkGet() call (via Keyspace#counterLocksFor())
* Striped#bulkGet() depends on Object#hashCode(), so here we make sure that the cf id and the partition key
* all get to be part of the hashCode() calculation, not just the cell name.
* all get to be part of the hashCode() calculation.
*/
private Iterable<Object> getCounterLockKeys()
{
@ -167,11 +167,11 @@ public class CounterMutation implements IMutation
{
public Iterable<Object> apply(final Row row)
{
return Iterables.concat(Iterables.transform(row, new Function<Cell, Object>()
return Iterables.concat(Iterables.transform(row, new Function<ColumnData, Object>()
{
public Object apply(final Cell cell)
public Object apply(final ColumnData data)
{
return Objects.hashCode(update.metadata().cfId, key(), row.clustering(), cell.column(), cell.path());
return Objects.hashCode(update.metadata().cfId, key(), row.clustering(), data.column());
}
}));
}
@ -238,7 +238,7 @@ public class CounterMutation implements IMutation
BTreeSet.Builder<Clustering> names = BTreeSet.builder(cfs.metadata.comparator);
for (PartitionUpdate.CounterMark mark : marks)
{
names.add(mark.clustering().takeAlias());
names.add(mark.clustering());
if (mark.path() == null)
builder.add(mark.column());
else

View File

@ -16,7 +16,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
@ -26,6 +25,7 @@ import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.dht.*;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.net.MessagingService;
@ -372,7 +372,7 @@ public class DataRange
}
}
public DataRange deserialize(DataInput in, int version, CFMetaData metadata) throws IOException
public DataRange deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
AbstractBounds<PartitionPosition> range = AbstractBounds.rowPositionSerializer.deserialize(in, MessagingService.globalPartitioner(), version);
ClusteringIndexFilter filter = ClusteringIndexFilter.serializer.deserialize(in, version, metadata);

View File

@ -19,255 +19,56 @@ package org.apache.cassandra.db;
import java.util.Iterator;
import com.google.common.base.Objects;
import com.google.common.collect.Iterators;
import org.apache.cassandra.cache.IMeasurableMemory;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.utils.ObjectSizes;
import org.apache.cassandra.db.rows.RowStats;
import org.apache.cassandra.utils.memory.AbstractAllocator;
/**
* A combination of a top-level (partition) tombstone and range tombstones describing the deletions
* within a partition.
* <p>
* Note that in practice {@link MutableDeletionInfo} is the only concrete implementation of this, however
* different parts of the code will return either {@code DeletionInfo} or {@code MutableDeletionInfo} based
* on whether it can/should be mutated or not.
* <p>
* <b>Warning:</b> do not ever cast a {@code DeletionInfo} into a {@code MutableDeletionInfo} to mutate it!!!
* TODO: it would be safer to have 2 actual implementation of DeletionInfo, one mutable and one that isn't (I'm
* just lazy right this minute).
*/
public class DeletionInfo implements IMeasurableMemory
public interface DeletionInfo extends IMeasurableMemory
{
private static final long EMPTY_SIZE = ObjectSizes.measure(new DeletionInfo(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 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 SimpleDeletionTime(markedForDeleteAt, localDeletionTime == Integer.MIN_VALUE ? Integer.MAX_VALUE : localDeletionTime));
}
public DeletionInfo(DeletionTime partitionDeletion)
{
this(partitionDeletion, null);
}
public DeletionInfo(ClusteringComparator comparator, Slice slice, long markedForDeleteAt, int localDeletionTime)
{
this(DeletionTime.LIVE, new RangeTombstoneList(comparator, 1));
ranges.add(slice.start(), slice.end(), markedForDeleteAt, localDeletionTime);
}
public DeletionInfo(DeletionTime partitionDeletion, RangeTombstoneList ranges)
{
this.partitionDeletion = partitionDeletion.takeAlias();
this.ranges = ranges;
}
/**
* Returns a new DeletionInfo that has no top-level tombstone or any range tombstones.
*/
public static DeletionInfo live()
{
return new DeletionInfo(DeletionTime.LIVE);
}
public DeletionInfo copy()
{
return new DeletionInfo(partitionDeletion, ranges == null ? null : ranges.copy());
}
public DeletionInfo copy(AbstractAllocator allocator)
{
RangeTombstoneList rangesCopy = null;
if (ranges != null)
rangesCopy = ranges.copy(allocator);
return new DeletionInfo(partitionDeletion, rangesCopy);
}
// Note that while MutableDeletionInfo.live() is mutable, we expose it here as a non-mutable DeletionInfo so sharing is fine.
public static final DeletionInfo LIVE = MutableDeletionInfo.live();
/**
* Returns whether this DeletionInfo is live, that is deletes no columns.
*/
public boolean isLive()
{
return partitionDeletion.isLive() && (ranges == null || ranges.isEmpty());
}
public boolean isLive();
/**
* Return whether a given cell is deleted by this deletion info.
*
* @param clustering the clustering for the cell to check.
* @param cell the cell to check.
* @return true if the cell is deleted, false otherwise
*/
private boolean isDeleted(Clustering clustering, Cell cell)
{
// If we're live, don't consider anything deleted, even if the cell ends up having as timestamp Long.MIN_VALUE
// (which shouldn't happen in practice, but it would invalid to consider it deleted if it does).
if (isLive())
return false;
if (cell.livenessInfo().timestamp() <= partitionDeletion.markedForDeleteAt())
return true;
// No matter what the counter cell's timestamp is, a tombstone always takes precedence. See CASSANDRA-7346.
if (!partitionDeletion.isLive() && cell.isCounterCell())
return true;
return ranges != null && ranges.isDeleted(clustering, cell);
}
/**
* Potentially replaces the top-level tombstone with another, keeping whichever has the higher markedForDeleteAt
* timestamp.
* @param newInfo
*/
public void add(DeletionTime newInfo)
{
if (newInfo.supersedes(partitionDeletion))
partitionDeletion = newInfo;
}
public void add(RangeTombstone tombstone, ClusteringComparator comparator)
{
if (ranges == null)
ranges = new RangeTombstoneList(comparator, 1);
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.partitionDeletion);
if (ranges == null)
ranges = newInfo.ranges == null ? null : newInfo.ranges.copy();
else if (newInfo.ranges != null)
ranges.addAll(newInfo.ranges);
return this;
}
public DeletionTime getPartitionDeletion()
{
return partitionDeletion;
}
public DeletionTime getPartitionDeletion();
// Use sparingly, not the most efficient thing
public Iterator<RangeTombstone> rangeIterator(boolean reversed)
{
return ranges == null ? Iterators.<RangeTombstone>emptyIterator() : ranges.iterator(reversed);
}
public Iterator<RangeTombstone> rangeIterator(boolean reversed);
public Iterator<RangeTombstone> rangeIterator(Slice slice, boolean reversed)
{
return ranges == null ? Iterators.<RangeTombstone>emptyIterator() : ranges.iterator(slice, reversed);
}
public Iterator<RangeTombstone> rangeIterator(Slice slice, boolean reversed);
public RangeTombstone rangeCovering(Clustering name)
{
return ranges == null ? null : ranges.search(name);
}
public RangeTombstone rangeCovering(Clustering name);
public int dataSize()
{
int size = TypeSizes.sizeof(partitionDeletion.markedForDeleteAt());
return size + (ranges == null ? 0 : ranges.dataSize());
}
public void collectStats(RowStats.Collector collector);
public boolean hasRanges()
{
return ranges != null && !ranges.isEmpty();
}
public int dataSize();
public int rangeCount()
{
return hasRanges() ? ranges.size() : 0;
}
public boolean hasRanges();
public int rangeCount();
public long maxTimestamp();
/**
* Whether this deletion info may modify the provided one if added to it.
*/
public boolean mayModify(DeletionInfo delInfo)
{
return partitionDeletion.compareTo(delInfo.partitionDeletion) > 0 || hasRanges();
}
public boolean mayModify(DeletionInfo delInfo);
@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 SimpleDeletionTime(timestamp, partitionDeletion.localDeletionTime());
if (ranges != null)
ranges.updateAllTimestamp(timestamp);
return this;
}
@Override
public boolean equals(Object o)
{
if(!(o instanceof DeletionInfo))
return false;
DeletionInfo that = (DeletionInfo)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 MutableDeletionInfo mutableCopy();
public DeletionInfo copy(AbstractAllocator allocator);
}

View File

@ -0,0 +1,35 @@
/*
* 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;
public interface DeletionPurger
{
public static final DeletionPurger PURGE_ALL = (ts, ldt) -> true;
public boolean shouldPurge(long timestamp, int localDeletionTime);
public default boolean shouldPurge(DeletionTime dt)
{
return !dt.isLive() && shouldPurge(dt.markedForDeleteAt(), dt.localDeletionTime());
}
public default boolean shouldPurge(LivenessInfo liveness, int nowInSec)
{
return !liveness.isLive(nowInSec) && shouldPurge(liveness.timestamp(), liveness.localExpirationTime());
}
}

View File

@ -17,13 +17,13 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.security.MessageDigest;
import com.google.common.base.Objects;
import org.apache.cassandra.cache.IMeasurableMemory;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
@ -34,29 +34,44 @@ import org.apache.cassandra.utils.ObjectSizes;
/**
* Information on deletion of a storage engine object.
*/
public abstract class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory, Aliasable<DeletionTime>
public class DeletionTime implements Comparable<DeletionTime>, IMeasurableMemory
{
private static final long EMPTY_SIZE = ObjectSizes.measure(new SimpleDeletionTime(0, 0));
private static final long EMPTY_SIZE = ObjectSizes.measure(new DeletionTime(0, 0));
/**
* A special DeletionTime that signifies that there is no top-level (row) tombstone.
*/
public static final DeletionTime LIVE = new SimpleDeletionTime(Long.MIN_VALUE, Integer.MAX_VALUE);
public static final DeletionTime LIVE = new DeletionTime(Long.MIN_VALUE, Integer.MAX_VALUE);
public static final Serializer serializer = new Serializer();
private final long markedForDeleteAt;
private final int localDeletionTime;
public DeletionTime(long markedForDeleteAt, int localDeletionTime)
{
this.markedForDeleteAt = markedForDeleteAt;
this.localDeletionTime = localDeletionTime;
}
/**
* A timestamp (typically in microseconds since the unix epoch, although this is not enforced) after which
* data should be considered deleted. If set to Long.MIN_VALUE, this implies that the data has not been marked
* for deletion at all.
*/
public abstract long markedForDeleteAt();
public long markedForDeleteAt()
{
return markedForDeleteAt;
}
/**
* The local server timestamp, in seconds since the unix epoch, at which this tombstone was created. This is
* only used for purposes of purging the tombstone after gc_grace_seconds have elapsed.
*/
public abstract int localDeletionTime();
public int localDeletionTime()
{
return localDeletionTime;
}
/**
* Returns whether this DeletionTime is live, that is deletes no columns.
@ -112,16 +127,16 @@ public abstract class DeletionTime implements Comparable<DeletionTime>, IMeasura
return markedForDeleteAt() > dt.markedForDeleteAt() || (markedForDeleteAt() == dt.markedForDeleteAt() && localDeletionTime() > dt.localDeletionTime());
}
public boolean isPurgeable(long maxPurgeableTimestamp, int gcBefore)
{
return markedForDeleteAt() < maxPurgeableTimestamp && localDeletionTime() < gcBefore;
}
public boolean deletes(LivenessInfo info)
{
return deletes(info.timestamp());
}
public boolean deletes(Cell cell)
{
return deletes(cell.timestamp());
}
public boolean deletes(long timestamp)
{
return timestamp <= markedForDeleteAt();
@ -151,10 +166,10 @@ public abstract class DeletionTime implements Comparable<DeletionTime>, IMeasura
long mfda = in.readLong();
return mfda == Long.MIN_VALUE && ldt == Integer.MAX_VALUE
? LIVE
: new SimpleDeletionTime(mfda, ldt);
: new DeletionTime(mfda, ldt);
}
public void skip(DataInput in) throws IOException
public void skip(DataInputPlus in) throws IOException
{
FileUtils.skipBytesFully(in, 4 + 8);
}

View File

@ -1,153 +0,0 @@
/*
* 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.Arrays;
import org.apache.cassandra.utils.ObjectSizes;
/**
* Utility class to store an array of deletion times a bit efficiently.
*/
public class DeletionTimeArray
{
private long[] markedForDeleteAts;
private int[] delTimes;
public DeletionTimeArray(int initialCapacity)
{
this.markedForDeleteAts = new long[initialCapacity];
this.delTimes = new int[initialCapacity];
clear();
}
public void clear(int i)
{
markedForDeleteAts[i] = Long.MIN_VALUE;
delTimes[i] = Integer.MAX_VALUE;
}
public void set(int i, DeletionTime dt)
{
this.markedForDeleteAts[i] = dt.markedForDeleteAt();
this.delTimes[i] = dt.localDeletionTime();
}
public int size()
{
return markedForDeleteAts.length;
}
public void resize(int newSize)
{
int prevSize = size();
markedForDeleteAts = Arrays.copyOf(markedForDeleteAts, newSize);
delTimes = Arrays.copyOf(delTimes, newSize);
Arrays.fill(markedForDeleteAts, prevSize, newSize, Long.MIN_VALUE);
Arrays.fill(delTimes, prevSize, newSize, Integer.MAX_VALUE);
}
public boolean supersedes(int i, DeletionTime dt)
{
return markedForDeleteAts[i] > dt.markedForDeleteAt();
}
public boolean supersedes(int i, int j)
{
return markedForDeleteAts[i] > markedForDeleteAts[j];
}
public void swap(int i, int j)
{
long m = markedForDeleteAts[j];
int l = delTimes[j];
move(i, j);
markedForDeleteAts[i] = m;
delTimes[i] = l;
}
public void move(int i, int j)
{
markedForDeleteAts[j] = markedForDeleteAts[i];
delTimes[j] = delTimes[i];
}
public boolean isLive(int i)
{
return markedForDeleteAts[i] > Long.MIN_VALUE;
}
public void clear()
{
Arrays.fill(markedForDeleteAts, Long.MIN_VALUE);
Arrays.fill(delTimes, Integer.MAX_VALUE);
}
public int dataSize()
{
return 12 * markedForDeleteAts.length;
}
public long unsharedHeapSize()
{
return ObjectSizes.sizeOfArray(markedForDeleteAts)
+ ObjectSizes.sizeOfArray(delTimes);
}
public void copy(DeletionTimeArray other)
{
assert size() == other.size();
for (int i = 0; i < size(); i++)
{
markedForDeleteAts[i] = other.markedForDeleteAts[i];
delTimes[i] = other.delTimes[i];
}
}
public static class Cursor extends DeletionTime
{
private DeletionTimeArray array;
private int i;
public Cursor setTo(DeletionTimeArray array, int i)
{
this.array = array;
this.i = i;
return this;
}
public long markedForDeleteAt()
{
return array.markedForDeleteAts[i];
}
public int localDeletionTime()
{
return array.delTimes[i];
}
public DeletionTime takeAlias()
{
return new SimpleDeletionTime(markedForDeleteAt(), localDeletionTime());
}
}
}

View File

@ -132,19 +132,13 @@ public class HintedHandOffManager implements HintedHandOffManagerMBean
UUID hintId = UUIDGen.getTimeUUID();
// serialize the hint with id and version as a composite column name
PartitionUpdate upd = new PartitionUpdate(SystemKeyspace.Hints,
StorageService.getPartitioner().decorateKey(UUIDType.instance.decompose(targetId)),
PartitionColumns.of(hintColumn),
1);
Row.Writer writer = upd.writer();
Rows.writeClustering(SystemKeyspace.Hints.comparator.make(hintId, MessagingService.current_version), writer);
DecoratedKey key = StorageService.getPartitioner().decorateKey(UUIDType.instance.decompose(targetId));
Clustering clustering = SystemKeyspace.Hints.comparator.make(hintId, MessagingService.current_version);
ByteBuffer value = ByteBuffer.wrap(FBUtilities.serialize(mutation, Mutation.serializer, MessagingService.current_version));
writer.writeCell(hintColumn, false, value, SimpleLivenessInfo.forUpdate(now, ttl, FBUtilities.nowInSeconds(), SystemKeyspace.Hints), null);
writer.endOfRow();
Cell cell = BufferCell.expiring(hintColumn, now, ttl, FBUtilities.nowInSeconds(), value);
return new Mutation(upd);
return new Mutation(PartitionUpdate.singleRowUpdate(SystemKeyspace.Hints, key, ArrayBackedRow.singleCellRow(clustering, cell)));
}
/*
@ -187,13 +181,8 @@ public class HintedHandOffManager implements HintedHandOffManagerMBean
private static void deleteHint(ByteBuffer tokenBytes, Clustering clustering, long timestamp)
{
DecoratedKey dk = StorageService.getPartitioner().decorateKey(tokenBytes);
PartitionUpdate upd = new PartitionUpdate(SystemKeyspace.Hints, dk, PartitionColumns.of(hintColumn), 1);
Row.Writer writer = upd.writer();
Rows.writeClustering(clustering, writer);
Cells.writeTombstone(writer, hintColumn, timestamp, FBUtilities.nowInSeconds());
Cell cell = BufferCell.tombstone(hintColumn, timestamp, FBUtilities.nowInSeconds());
PartitionUpdate upd = PartitionUpdate.singleRowUpdate(SystemKeyspace.Hints, dk, ArrayBackedRow.singleCellRow(clustering, cell));
new Mutation(upd).applyUnsafe(); // don't bother with commitlog since we're going to flush as soon as we're done with delivery
}
@ -420,7 +409,7 @@ public class HintedHandOffManager implements HintedHandOffManagerMBean
int version = Int32Type.instance.compose(hint.clustering().get(1));
Cell cell = hint.getCell(hintColumn);
final long timestamp = cell.livenessInfo().timestamp();
final long timestamp = cell.timestamp();
DataInputPlus in = new NIODataInputStream(cell.value(), true);
Mutation mutation;
try

View File

@ -31,15 +31,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.thrift.ColumnDef;
import org.apache.cassandra.utils.*;
import org.apache.hadoop.io.serializer.Serialization;
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
@ -103,7 +102,7 @@ public abstract class LegacyLayout
if (metadata.isSuper())
{
assert superColumnName != null;
return decodeForSuperColumn(metadata, new SimpleClustering(superColumnName), cellname);
return decodeForSuperColumn(metadata, new Clustering(superColumnName), cellname);
}
assert superColumnName == null;
@ -152,7 +151,7 @@ public abstract class LegacyLayout
{
// If it's a compact table, it means the column is in fact a "dynamic" one
if (metadata.isCompactTable())
return new LegacyCellName(new SimpleClustering(column), metadata.compactValueColumn(), null);
return new LegacyCellName(new Clustering(column), metadata.compactValueColumn(), null);
throw new UnknownColumnException(metadata, column);
}
@ -242,7 +241,7 @@ public abstract class LegacyLayout
? CompositeType.splitName(value)
: Collections.singletonList(value);
return new SimpleClustering(components.subList(0, Math.min(csize, components.size())).toArray(new ByteBuffer[csize]));
return new Clustering(components.subList(0, Math.min(csize, components.size())).toArray(new ByteBuffer[csize]));
}
public static ByteBuffer encodeClustering(CFMetaData metadata, Clustering clustering)
@ -276,7 +275,7 @@ public abstract class LegacyLayout
DeletionInfo delInfo,
Iterator<LegacyCell> cells)
{
SerializationHelper helper = new SerializationHelper(0, SerializationHelper.Flag.LOCAL);
SerializationHelper helper = new SerializationHelper(metadata, 0, SerializationHelper.Flag.LOCAL);
return toUnfilteredRowIterator(metadata, key, LegacyDeletionInfo.from(delInfo), cells, false, helper);
}
@ -320,22 +319,16 @@ public abstract class LegacyLayout
Iterator<Row> rows = convertToRows(new CellGrouper(metadata, helper), iter, delInfo);
Iterator<RangeTombstone> ranges = delInfo.deletionInfo.rangeIterator(reversed);
final Iterator<Unfiltered> atoms = new RowAndTombstoneMergeIterator(metadata.comparator, reversed)
.setTo(rows, ranges);
return new AbstractUnfilteredRowIterator(metadata,
key,
delInfo.deletionInfo.getPartitionDeletion(),
metadata.partitionColumns(),
staticRow,
reversed,
RowStats.NO_STATS)
{
protected Unfiltered computeNext()
{
return atoms.hasNext() ? atoms.next() : endOfData();
}
};
return new RowAndDeletionMergeIterator(metadata,
key,
delInfo.deletionInfo.getPartitionDeletion(),
ColumnFilter.all(metadata),
staticRow,
reversed,
RowStats.NO_STATS,
rows,
ranges,
true);
}
public static Row extractStaticColumns(CFMetaData metadata, DataInputPlus in, Columns statics) throws IOException
@ -351,7 +344,7 @@ public abstract class LegacyLayout
for (ColumnDefinition column : statics)
columnsToFetch.add(column.name.bytes);
StaticRow.Builder builder = StaticRow.builder(statics, false, metadata.isCounter());
Row.Builder builder = ArrayBackedRow.unsortedBuilder(statics, FBUtilities.nowInSeconds());
boolean foundOne = false;
LegacyAtom atom;
@ -364,7 +357,7 @@ public abstract class LegacyLayout
continue;
foundOne = true;
builder.writeCell(cell.name.column, cell.isCounter(), cell.value, livenessInfo(metadata, cell), null);
builder.addCell(new BufferCell(cell.name.column, cell.timestamp, cell.ttl, cell.localDeletionTime, cell.value, null));
}
else
{
@ -469,7 +462,7 @@ public abstract class LegacyLayout
{
return new AbstractIterator<LegacyCell>()
{
private final Iterator<Cell> cells = row.iterator();
private final Iterator<Cell> cells = row.cells().iterator();
// we don't have (and shouldn't have) row markers for compact tables.
private boolean hasReturnedRowMarker = metadata.isCompactTable();
@ -480,7 +473,7 @@ public abstract class LegacyLayout
hasReturnedRowMarker = true;
LegacyCellName cellName = new LegacyCellName(row.clustering(), null, null);
LivenessInfo info = row.primaryKeyLivenessInfo();
return new LegacyCell(LegacyCell.Kind.REGULAR, cellName, ByteBufferUtil.EMPTY_BYTE_BUFFER, info.timestamp(), info.localDeletionTime(), info.ttl());
return new LegacyCell(LegacyCell.Kind.REGULAR, cellName, ByteBufferUtil.EMPTY_BYTE_BUFFER, info.timestamp(), info.localExpirationTime(), info.ttl());
}
if (!cells.hasNext())
@ -507,8 +500,7 @@ public abstract class LegacyLayout
CellPath path = cell.path();
assert path == null || path.size() == 1;
LegacyCellName name = new LegacyCellName(clustering, cell.column(), path == null ? null : path.get(0));
LivenessInfo info = cell.livenessInfo();
return new LegacyCell(kind, name, cell.value(), info.timestamp(), info.localDeletionTime(), info.ttl());
return new LegacyCell(kind, name, cell.value(), cell.timestamp(), cell.localDeletionTime(), cell.ttl());
}
public static RowIterator toRowIterator(final CFMetaData metadata,
@ -516,17 +508,10 @@ public abstract class LegacyLayout
final Iterator<LegacyCell> cells,
final int nowInSec)
{
SerializationHelper helper = new SerializationHelper(0, SerializationHelper.Flag.LOCAL);
SerializationHelper helper = new SerializationHelper(metadata, 0, SerializationHelper.Flag.LOCAL);
return UnfilteredRowIterators.filter(toUnfilteredRowIterator(metadata, key, LegacyDeletionInfo.live(), cells, false, helper), nowInSec);
}
private static LivenessInfo livenessInfo(CFMetaData metadata, LegacyCell cell)
{
return cell.isTombstone()
? SimpleLivenessInfo.forDeletion(cell.timestamp, cell.localDeletionTime)
: SimpleLivenessInfo.forUpdate(cell.timestamp, cell.ttl, cell.localDeletionTime, metadata);
}
public static Comparator<LegacyCell> legacyCellComparator(CFMetaData metadata)
{
return legacyCellComparator(metadata, false);
@ -662,7 +647,7 @@ public abstract class LegacyLayout
ByteBuffer value = ByteBufferUtil.readWithLength(in);
if (flag == SerializationHelper.Flag.FROM_REMOTE || (flag == SerializationHelper.Flag.LOCAL && CounterContext.instance().shouldClearLocal(value)))
value = CounterContext.instance().clearAllLocal(value);
return new LegacyCell(LegacyCell.Kind.COUNTER, decodeCellName(metadata, cellname, readAllAsDynamic), value, ts, LivenessInfo.NO_DELETION_TIME, LivenessInfo.NO_TTL);
return new LegacyCell(LegacyCell.Kind.COUNTER, decodeCellName(metadata, cellname, readAllAsDynamic), value, ts, Cell.NO_DELETION_TIME, Cell.NO_TTL);
}
else if ((mask & EXPIRATION_MASK) != 0)
{
@ -678,10 +663,10 @@ public abstract class LegacyLayout
ByteBuffer value = ByteBufferUtil.readWithLength(in);
LegacyCellName name = decodeCellName(metadata, cellname, readAllAsDynamic);
return (mask & COUNTER_UPDATE_MASK) != 0
? new LegacyCell(LegacyCell.Kind.COUNTER, name, CounterContext.instance().createLocal(ByteBufferUtil.toLong(value)), ts, LivenessInfo.NO_DELETION_TIME, LivenessInfo.NO_TTL)
? new LegacyCell(LegacyCell.Kind.COUNTER, name, CounterContext.instance().createLocal(ByteBufferUtil.toLong(value)), ts, Cell.NO_DELETION_TIME, Cell.NO_TTL)
: ((mask & DELETION_MASK) == 0
? new LegacyCell(LegacyCell.Kind.REGULAR, name, value, ts, LivenessInfo.NO_DELETION_TIME, LivenessInfo.NO_TTL)
: new LegacyCell(LegacyCell.Kind.DELETED, name, ByteBufferUtil.EMPTY_BYTE_BUFFER, ts, ByteBufferUtil.toInt(value), LivenessInfo.NO_TTL));
? new LegacyCell(LegacyCell.Kind.REGULAR, name, value, ts, Cell.NO_DELETION_TIME, Cell.NO_TTL)
: new LegacyCell(LegacyCell.Kind.DELETED, name, ByteBufferUtil.EMPTY_BYTE_BUFFER, ts, ByteBufferUtil.toInt(value), Cell.NO_TTL));
}
}
@ -741,10 +726,9 @@ public abstract class LegacyLayout
public static class CellGrouper
{
public final CFMetaData metadata;
private final ReusableRow row;
private final boolean isStatic;
private final SerializationHelper helper;
private Row.Writer writer;
private Row.Builder builder;
private Clustering clustering;
private LegacyRangeTombstone rowDeletion;
@ -760,10 +744,7 @@ public abstract class LegacyLayout
this.metadata = metadata;
this.isStatic = isStatic;
this.helper = helper;
this.row = isStatic ? null : new ReusableRow(metadata.clusteringColumns().size(), metadata.partitionColumns().regulars, false, metadata.isCounter());
if (isStatic)
this.writer = StaticRow.builder(metadata.partitionColumns().statics, false, metadata.isCounter());
this.builder = ArrayBackedRow.sortedBuilder(isStatic ? metadata.partitionColumns().statics : metadata.partitionColumns().regulars);
}
public static CellGrouper staticGrouper(CFMetaData metadata, SerializationHelper helper)
@ -776,9 +757,6 @@ public abstract class LegacyLayout
this.clustering = null;
this.rowDeletion = null;
this.collectionDeletion = null;
if (!isStatic)
this.writer = row.writer();
}
public boolean addAtom(LegacyAtom atom)
@ -797,8 +775,8 @@ public abstract class LegacyLayout
}
else if (clustering == null)
{
clustering = cell.name.clustering.takeAlias();
clustering.writeTo(writer);
clustering = cell.name.clustering;
builder.newRow(clustering);
}
else if (!clustering.equals(cell.name.clustering))
{
@ -809,14 +787,12 @@ public abstract class LegacyLayout
if (rowDeletion != null && rowDeletion.deletionTime.deletes(cell.timestamp))
return true;
LivenessInfo info = livenessInfo(metadata, cell);
ColumnDefinition column = cell.name.column;
if (column == null)
{
// It's the row marker
assert !cell.value.hasRemaining();
helper.writePartitionKeyLivenessInfo(writer, info.timestamp(), info.ttl(), info.localDeletionTime());
builder.addPrimaryKeyLivenessInfo(LivenessInfo.create(cell.timestamp, cell.ttl, cell.localDeletionTime));
}
else
{
@ -833,11 +809,15 @@ public abstract class LegacyLayout
// practice and 2) is only used during upgrade, it's probably worth keeping things simple.
helper.startOfComplexColumn(column);
path = cell.name.collectionElement == null ? null : CellPath.create(cell.name.collectionElement);
if (!helper.includes(path))
return true;
}
helper.writeCell(writer, column, cell.isCounter(), cell.value, info.timestamp(), info.localDeletionTime(), info.ttl(), path);
Cell c = new BufferCell(column, cell.timestamp, cell.ttl, cell.localDeletionTime, cell.value, path);
if (!helper.isDropped(c, column.isComplex()))
builder.addCell(c);
if (column.isComplex())
{
helper.endOfComplexColumn(column);
helper.endOfComplexColumn();
}
}
}
@ -852,9 +832,9 @@ public abstract class LegacyLayout
if (clustering != null)
return false;
clustering = tombstone.start.getAsClustering(metadata).takeAlias();
clustering.writeTo(writer);
writer.writeRowDeletion(tombstone.deletionTime);
clustering = tombstone.start.getAsClustering(metadata);
builder.newRow(clustering);
builder.addRowDeletion(tombstone.deletionTime);
rowDeletion = tombstone;
return true;
}
@ -863,15 +843,15 @@ public abstract class LegacyLayout
{
if (clustering == null)
{
clustering = tombstone.start.getAsClustering(metadata).takeAlias();
clustering.writeTo(writer);
clustering = tombstone.start.getAsClustering(metadata);
builder.newRow(clustering);
}
else if (!clustering.equals(tombstone.start.getAsClustering(metadata)))
{
return false;
}
writer.writeComplexDeletion(tombstone.start.collectionName, tombstone.deletionTime);
builder.addComplexDeletion(tombstone.start.collectionName, tombstone.deletionTime);
if (rowDeletion == null || tombstone.deletionTime.supersedes(rowDeletion.deletionTime))
collectionDeletion = tombstone;
return true;
@ -881,8 +861,7 @@ public abstract class LegacyLayout
public Row getRow()
{
writer.endOfRow();
return isStatic ? ((StaticRow.Builder)writer).build() : row;
return builder.build();
}
}
@ -947,7 +926,7 @@ public abstract class LegacyLayout
ByteBuffer[] values = new ByteBuffer[bound.size()];
for (int i = 0; i < bound.size(); i++)
values[i] = bound.get(i);
return new SimpleClustering(values);
return new Clustering(values);
}
@Override
@ -1005,13 +984,13 @@ public abstract class LegacyLayout
public static LegacyCell regular(CFMetaData metadata, ByteBuffer superColumnName, ByteBuffer name, ByteBuffer value, long timestamp)
throws UnknownColumnException
{
return new LegacyCell(Kind.REGULAR, decodeCellName(metadata, superColumnName, name), value, timestamp, LivenessInfo.NO_DELETION_TIME, LivenessInfo.NO_TTL);
return new LegacyCell(Kind.REGULAR, decodeCellName(metadata, superColumnName, name), value, timestamp, Cell.NO_DELETION_TIME, Cell.NO_TTL);
}
public static LegacyCell expiring(CFMetaData metadata, ByteBuffer superColumnName, ByteBuffer name, ByteBuffer value, long timestamp, int ttl, int nowInSec)
throws UnknownColumnException
{
return new LegacyCell(Kind.EXPIRING, decodeCellName(metadata, superColumnName, name), value, timestamp, nowInSec, ttl);
return new LegacyCell(Kind.EXPIRING, decodeCellName(metadata, superColumnName, name), value, timestamp, nowInSec + ttl, ttl);
}
public static LegacyCell tombstone(CFMetaData metadata, ByteBuffer superColumnName, ByteBuffer name, long timestamp, int nowInSec)
@ -1030,7 +1009,7 @@ public abstract class LegacyLayout
public static LegacyCell counter(LegacyCellName name, ByteBuffer value)
{
return new LegacyCell(Kind.COUNTER, name, value, FBUtilities.timestampMicros(), LivenessInfo.NO_DELETION_TIME, LivenessInfo.NO_TTL);
return new LegacyCell(Kind.COUNTER, name, value, FBUtilities.timestampMicros(), Cell.NO_DELETION_TIME, Cell.NO_TTL);
}
public ClusteringPrefix clustering()
@ -1205,7 +1184,7 @@ public abstract class LegacyLayout
public static LegacyDeletionInfo live()
{
return from(DeletionInfo.live());
return from(DeletionInfo.LIVE);
}
public Iterator<LegacyRangeTombstone> inRowRangeTombstones()
@ -1228,7 +1207,7 @@ public abstract class LegacyLayout
int rangeCount = in.readInt();
if (rangeCount == 0)
return from(new DeletionInfo(topLevel));
return from(new MutableDeletionInfo(topLevel));
RangeTombstoneList ranges = new RangeTombstoneList(metadata.comparator, rangeCount);
List<LegacyRangeTombstone> inRowTombsones = new ArrayList<>();
@ -1239,13 +1218,13 @@ public abstract class LegacyLayout
int delTime = in.readInt();
long markedAt = in.readLong();
LegacyRangeTombstone tombstone = new LegacyRangeTombstone(start, end, new SimpleDeletionTime(markedAt, delTime));
LegacyRangeTombstone tombstone = new LegacyRangeTombstone(start, end, new DeletionTime(markedAt, delTime));
if (tombstone.isCollectionTombstone() || tombstone.isRowDeletion(metadata))
inRowTombsones.add(tombstone);
else
ranges.add(start.bound, end.bound, markedAt, delTime);
}
return new LegacyDeletionInfo(new DeletionInfo(topLevel, ranges), inRowTombsones);
return new LegacyDeletionInfo(new MutableDeletionInfo(topLevel, ranges), inRowTombsones);
}
public long serializedSize(CFMetaData metadata, LegacyDeletionInfo info, TypeSizes typeSizes, int version)

View File

@ -17,128 +17,154 @@
*/
package org.apache.cassandra.db;
import java.util.Objects;
import java.security.MessageDigest;
import org.apache.cassandra.db.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.FBUtilities;
/**
* Groups the informations necessary to decide the liveness of a given piece of
* column data.
* Stores the information relating to the liveness of the primary key columns of a row.
* <p>
* In practice, a {@code LivenessInfo} groups 3 informations:
* 1) the data timestamp. It is sometimes allowed for a given piece of data to have
* no timestamp (for {@link Row#partitionKeyLivenessInfo} more precisely), but if that
* is the case it means the data has no liveness info at all.
* 2) the data ttl if relevant.
* 2) the data local deletion time if relevant (that is, if either the data has a ttl or is deleted).
* A {@code LivenessInfo} can first be empty. If it isn't, it contains at least a timestamp,
* which is the timestamp for the row primary key columns. On top of that, the info can be
* ttl'ed, in which case the {@code LivenessInfo} also has both a ttl and a local expiration time.
*/
public interface LivenessInfo extends Aliasable<LivenessInfo>
public class LivenessInfo
{
public static final long NO_TIMESTAMP = Long.MIN_VALUE;
public static final int NO_TTL = 0;
public static final int NO_DELETION_TIME = Integer.MAX_VALUE;
public static final int NO_EXPIRATION_TIME = Integer.MAX_VALUE;
public static final LivenessInfo NONE = new SimpleLivenessInfo(NO_TIMESTAMP, NO_TTL, NO_DELETION_TIME);
public static final LivenessInfo EMPTY = new LivenessInfo(NO_TIMESTAMP);
protected final long timestamp;
protected LivenessInfo(long timestamp)
{
this.timestamp = timestamp;
}
public static LivenessInfo create(CFMetaData metadata, long timestamp, int nowInSec)
{
int defaultTTL = metadata.getDefaultTimeToLive();
if (defaultTTL != NO_TTL)
return expiring(timestamp, defaultTTL, nowInSec);
return new LivenessInfo(timestamp);
}
public static LivenessInfo expiring(long timestamp, int ttl, int nowInSec)
{
return new ExpiringLivenessInfo(timestamp, ttl, nowInSec + ttl);
}
public static LivenessInfo create(CFMetaData metadata, long timestamp, int ttl, int nowInSec)
{
return ttl == NO_TTL
? create(metadata, timestamp, nowInSec)
: expiring(timestamp, ttl, nowInSec);
}
// Note that this ctor ignores the default table ttl and takes the expiration time, not the current time.
// Use when you know that's what you want.
public static LivenessInfo create(long timestamp, int ttl, int localExpirationTime)
{
return ttl == NO_TTL ? new LivenessInfo(timestamp) : new ExpiringLivenessInfo(timestamp, ttl, localExpirationTime);
}
/**
* The timestamp at which the data was inserted or {@link NO_TIMESTAMP}
* if it has no timestamp (which may or may not be allowed).
* Whether this liveness info is empty (has no timestamp).
*
* @return the liveness info timestamp.
* @return whether this liveness info is empty or not.
*/
public long timestamp();
public boolean isEmpty()
{
return timestamp == NO_TIMESTAMP;
}
/**
* Whether this liveness info has a timestamp or not.
* <p>
* Note that if this return {@code false}, then both {@link #hasTTL} and
* {@link #hasLocalDeletionTime} must return {@code false} too.
* The timestamp for this liveness info.
*
* @return whether this liveness info has a timestamp or not.
* @return the liveness info timestamp (or {@link #NO_TIMESTAMP} if the info is empty).
*/
public boolean hasTimestamp();
public long timestamp()
{
return timestamp;
}
/**
* The ttl (if any) on the data or {@link NO_TTL} if the data is not
* Whether the info has a ttl.
*/
public boolean isExpiring()
{
return false;
}
/**
* The ttl (if any) on the row primary key columns or {@link #NO_TTL} if it is not
* expiring.
*
* Please note that this value is the TTL that was set originally and is thus not
* changing. If you want to figure out how much time the data has before it expires,
* then you should use {@link #remainingTTL}.
* changing.
*/
public int ttl();
public int ttl()
{
return NO_TTL;
}
/**
* Whether this liveness info has a TTL or not.
* The expiration time (in seconds) if the info is expiring ({@link #NO_EXPIRATION_TIME} otherwise).
*
* @return whether this liveness info has a TTL or not.
*/
public boolean hasTTL();
public int localExpirationTime()
{
return NO_EXPIRATION_TIME;
}
/**
* The deletion time (in seconds) on the data if applicable ({@link NO_DELETION}
* otherwise).
* Whether that info is still live.
*
* There is 3 cases in practice:
* 1) the data is neither deleted nor expiring: it then has neither {@code ttl()}
* nor {@code localDeletionTime()}.
* 2) the data is expiring/expired: it then has both a {@code ttl()} and a
* {@code localDeletionTime()}. Whether it's still live or is expired depends
* on the {@code localDeletionTime()}.
* 3) the data is deleted: it has no {@code ttl()} but has a
* {@code localDeletionTime()}.
*/
public int localDeletionTime();
/**
* Whether this liveness info has a local deletion time or not.
*
* @return whether this liveness info has a local deletion time or not.
*/
public boolean hasLocalDeletionTime();
/**
* The actual remaining time to live (in seconds) for the data this is
* the liveness information of.
*
* {@code #ttl} returns the initial TTL sets on the piece of data while this
* method computes how much time the data actually has to live given the
* current time.
* A {@code LivenessInfo} is live if it is either not expiring, or if its expiration time if after
* {@code nowInSec}.
*
* @param nowInSec the current time in seconds.
* @return the remaining time to live (in seconds) the data has, or
* {@code -1} if the data is either expired or not expiring.
* @return whether this liveness info is live or not.
*/
public int remainingTTL(int nowInSec);
/**
* Checks whether a given piece of data is live given the current time.
*
* @param nowInSec the current time in seconds.
* @return whether the data having this liveness info is live or not.
*/
public boolean isLive(int nowInSec);
public boolean isLive(int nowInSec)
{
return !isEmpty();
}
/**
* Adds this liveness information to the provided digest.
*
* @param digest the digest to add this liveness information to.
*/
public void digest(MessageDigest digest);
public void digest(MessageDigest digest)
{
FBUtilities.updateWithLong(digest, timestamp());
}
/**
* Validate the data contained by this liveness information.
*
* @throws MarshalException if some of the data is corrupted.
*/
public void validate();
public void validate()
{
}
/**
* The size of the (useful) data this liveness information contains.
*
* @return the size of the data this liveness information contains.
*/
public int dataSize();
public int dataSize()
{
return TypeSizes.sizeof(timestamp());
}
/**
* Whether this liveness information supersedes another one (that is
@ -148,31 +174,10 @@ public interface LivenessInfo extends Aliasable<LivenessInfo>
*
* @return whether this {@code LivenessInfo} supersedes {@code other}.
*/
public boolean supersedes(LivenessInfo other);
/**
* Returns the result of merging this info to another one (that is, it
* return this info if it supersedes the other one, or the other one
* otherwise).
*/
public LivenessInfo mergeWith(LivenessInfo other);
/**
* Whether this liveness information can be purged.
* <p>
* A liveness info can be purged if it is not live and hasn't been so
* for longer than gcGrace (or more precisely, it's local deletion time
* is smaller than gcBefore, which is itself "now - gcGrace").
*
* @param maxPurgeableTimestamp the biggest timestamp that can be purged.
* A liveness info will not be considered purgeable if its timestamp is
* greater than this value, even if it mets the other criteria for purging.
* @param gcBefore the local deletion time before which deleted/expired
* liveness info can be purged.
*
* @return whether this liveness information can be purged.
*/
public boolean isPurgeable(long maxPurgeableTimestamp, int gcBefore);
public boolean supersedes(LivenessInfo other)
{
return timestamp > other.timestamp;
}
/**
* Returns a copy of this liveness info updated with the provided timestamp.
@ -182,5 +187,108 @@ public interface LivenessInfo extends Aliasable<LivenessInfo>
* as timestamp. If it has no timestamp however, this liveness info is returned
* unchanged.
*/
public LivenessInfo withUpdatedTimestamp(long newTimestamp);
public LivenessInfo withUpdatedTimestamp(long newTimestamp)
{
return new LivenessInfo(newTimestamp);
}
@Override
public String toString()
{
return String.format("[ts=%d]", timestamp);
}
@Override
public boolean equals(Object other)
{
if(!(other instanceof LivenessInfo))
return false;
LivenessInfo that = (LivenessInfo)other;
return this.timestamp() == that.timestamp()
&& this.ttl() == that.ttl()
&& this.localExpirationTime() == that.localExpirationTime();
}
@Override
public int hashCode()
{
return Objects.hash(timestamp(), ttl(), localExpirationTime());
}
private static class ExpiringLivenessInfo extends LivenessInfo
{
private final int ttl;
private final int localExpirationTime;
private ExpiringLivenessInfo(long timestamp, int ttl, int localExpirationTime)
{
super(timestamp);
assert ttl != NO_TTL && localExpirationTime != NO_EXPIRATION_TIME;
this.ttl = ttl;
this.localExpirationTime = localExpirationTime;
}
@Override
public int ttl()
{
return ttl;
}
@Override
public int localExpirationTime()
{
return localExpirationTime;
}
@Override
public boolean isExpiring()
{
return true;
}
@Override
public boolean isLive(int nowInSec)
{
return nowInSec < localExpirationTime;
}
@Override
public void digest(MessageDigest digest)
{
super.digest(digest);
FBUtilities.updateWithInt(digest, localExpirationTime);
FBUtilities.updateWithInt(digest, ttl);
}
@Override
public void validate()
{
if (ttl < 0)
throw new MarshalException("A TTL should not be negative");
if (localExpirationTime < 0)
throw new MarshalException("A local expiration time should not be negative");
}
@Override
public int dataSize()
{
return super.dataSize()
+ TypeSizes.sizeof(ttl)
+ TypeSizes.sizeof(localExpirationTime);
}
@Override
public LivenessInfo withUpdatedTimestamp(long newTimestamp)
{
return new ExpiringLivenessInfo(newTimestamp, ttl, localExpirationTime);
}
@Override
public String toString()
{
return String.format("[ts=%d ttl=%d, let=%d]", timestamp, ttl, localExpirationTime);
}
}
}

View File

@ -1,174 +0,0 @@
/*
* 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.Arrays;
import org.apache.cassandra.utils.ObjectSizes;
/**
* Utility class to store an array of liveness info efficiently.
*/
public class LivenessInfoArray
{
private long[] timestamps;
private int[] delTimesAndTTLs;
public LivenessInfoArray(int initialCapacity)
{
this.timestamps = new long[initialCapacity];
this.delTimesAndTTLs = new int[initialCapacity * 2];
clear();
}
public void clear(int i)
{
timestamps[i] = LivenessInfo.NO_TIMESTAMP;
delTimesAndTTLs[2 * i] = LivenessInfo.NO_DELETION_TIME;
delTimesAndTTLs[2 * i + 1] = LivenessInfo.NO_TTL;
}
public void set(int i, LivenessInfo info)
{
set(i, info.timestamp(), info.ttl(), info.localDeletionTime());
}
public void set(int i, long timestamp, int ttl, int localDeletionTime)
{
this.timestamps[i] = timestamp;
this.delTimesAndTTLs[2 * i] = localDeletionTime;
this.delTimesAndTTLs[2 * i + 1] = ttl;
}
public long timestamp(int i)
{
return timestamps[i];
}
public int localDeletionTime(int i)
{
return delTimesAndTTLs[2 * i];
}
public int ttl(int i)
{
return delTimesAndTTLs[2 * i + 1];
}
public boolean isLive(int i, int nowInSec)
{
// See AbstractLivenessInfo.isLive().
return localDeletionTime(i) == LivenessInfo.NO_DELETION_TIME
|| (ttl(i) != LivenessInfo.NO_TTL && nowInSec < localDeletionTime(i));
}
public int size()
{
return timestamps.length;
}
public void resize(int newSize)
{
int prevSize = size();
timestamps = Arrays.copyOf(timestamps, newSize);
delTimesAndTTLs = Arrays.copyOf(delTimesAndTTLs, newSize * 2);
clear(prevSize, newSize);
}
public void swap(int i, int j)
{
long ts = timestamps[j];
int ldt = delTimesAndTTLs[2 * j];
int ttl = delTimesAndTTLs[2 * j + 1];
move(i, j);
timestamps[i] = ts;
delTimesAndTTLs[2 * i] = ldt;
delTimesAndTTLs[2 * i + 1] = ttl;
}
public void move(int i, int j)
{
timestamps[j] = timestamps[i];
delTimesAndTTLs[2 * j] = delTimesAndTTLs[2 * i];
delTimesAndTTLs[2 * j + 1] = delTimesAndTTLs[2 * i + 1];
}
public void clear()
{
clear(0, size());
}
private void clear(int from, int to)
{
Arrays.fill(timestamps, from, to, LivenessInfo.NO_TIMESTAMP);
for (int i = from; i < to; i++)
{
delTimesAndTTLs[2 * i] = LivenessInfo.NO_DELETION_TIME;
delTimesAndTTLs[2 * i + 1] = LivenessInfo.NO_TTL;
}
}
public int dataSize()
{
return 16 * size();
}
public long unsharedHeapSize()
{
return ObjectSizes.sizeOfArray(timestamps)
+ ObjectSizes.sizeOfArray(delTimesAndTTLs);
}
public static Cursor newCursor()
{
return new Cursor();
}
public static class Cursor extends AbstractLivenessInfo
{
private LivenessInfoArray array;
private int i;
public Cursor setTo(LivenessInfoArray array, int i)
{
this.array = array;
this.i = i;
return this;
}
public long timestamp()
{
return array.timestamps[i];
}
public int localDeletionTime()
{
return array.delTimesAndTTLs[2 * i];
}
public int ttl()
{
return array.delTimesAndTTLs[2 * i + 1];
}
}
}

View File

@ -0,0 +1,311 @@
/*
* 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.Iterator;
import com.google.common.base.Objects;
import com.google.common.collect.Iterators;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.rows.RowStats;
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)
ranges = new RangeTombstoneList(comparator, 1);
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 ? Iterators.<RangeTombstone>emptyIterator() : ranges.iterator(reversed);
}
public Iterator<RangeTombstone> rangeIterator(Slice slice, boolean reversed)
{
return ranges == null ? Iterators.<RangeTombstone>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(RowStats.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);
Slice.Bound open = openMarker.openBound(reversed);
Slice.Bound 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;
}
}
}

View File

@ -66,6 +66,11 @@ public class PartitionColumns implements Iterable<ColumnDefinition>
return statics.isEmpty() && regulars.isEmpty();
}
public Columns columns(boolean isStatic)
{
return isStatic ? statics : regulars;
}
public boolean contains(ColumnDefinition column)
{
return column.isStatic() ? statics.contains(column) : regulars.contains(column);

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@ -32,6 +31,7 @@ import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.metrics.TableMetrics;
@ -278,11 +278,11 @@ public class PartitionRangeReadCommand extends ReadCommand
private static class Deserializer extends SelectionDeserializer
{
public ReadCommand deserialize(DataInput in, int version, boolean isDigest, boolean isForThrift, CFMetaData metadata, int nowInSec, ColumnFilter columnFilter, RowFilter rowFilter, DataLimits limits)
public ReadCommand deserialize(DataInputPlus in, int version, boolean isDigest, boolean isForThrift, CFMetaData metadata, int nowInSec, ColumnFilter columnFilter, RowFilter rowFilter, DataLimits limits)
throws IOException
{
DataRange range = DataRange.serializer.deserialize(in, version, metadata);
return new PartitionRangeReadCommand(isDigest, isForThrift, metadata, nowInSec, columnFilter, rowFilter, limits, range);
}
};
}
}

View File

@ -17,20 +17,15 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.ISSTableSerializer;
import org.apache.cassandra.io.sstable.format.Version;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.db.rows.RangeTombstoneMarker;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.Interval;
import org.apache.cassandra.utils.memory.AbstractAllocator;
/**
* A range tombstone is a tombstone that covers a slice/range of rows.
@ -48,7 +43,7 @@ public class RangeTombstone
public RangeTombstone(Slice slice, DeletionTime deletion)
{
this.slice = slice;
this.deletion = deletion.takeAlias();
this.deletion = deletion;
}
/**
@ -73,7 +68,7 @@ public class RangeTombstone
public String toString(ClusteringComparator comparator)
{
return slice.toString(comparator) + "@" + deletion;
return slice.toString(comparator) + '@' + deletion;
}
@Override
@ -108,9 +103,30 @@ public class RangeTombstone
{
public static final Serializer serializer = new Serializer();
/** The smallest start bound, i.e. the one that starts before any row. */
public static final Bound BOTTOM = new Bound(Kind.INCL_START_BOUND, EMPTY_VALUES_ARRAY);
/** The biggest end bound, i.e. the one that ends after any row. */
public static final Bound TOP = new Bound(Kind.INCL_END_BOUND, EMPTY_VALUES_ARRAY);
public Bound(Kind kind, ByteBuffer[] values)
{
super(kind, values);
assert values.length > 0 || !kind.isBoundary();
}
public boolean isBoundary()
{
return kind.isBoundary();
}
public boolean isOpen(boolean reversed)
{
return kind.isOpen(reversed);
}
public boolean isClose(boolean reversed)
{
return kind.isClose(reversed);
}
public static RangeTombstone.Bound inclusiveOpen(boolean reversed, ByteBuffer[] boundValues)
@ -143,6 +159,19 @@ public class RangeTombstone
return new Bound(reversed ? Kind.INCL_END_EXCL_START_BOUNDARY : Kind.EXCL_END_INCL_START_BOUNDARY, boundValues);
}
public static RangeTombstone.Bound fromSliceBound(Slice.Bound sliceBound)
{
return new RangeTombstone.Bound(sliceBound.kind(), sliceBound.getRawValues());
}
public RangeTombstone.Bound copy(AbstractAllocator allocator)
{
ByteBuffer[] newValues = new ByteBuffer[size()];
for (int i = 0; i < size(); i++)
newValues[i] = allocator.clone(get(i));
return new Bound(kind(), newValues);
}
@Override
public Bound withNewKind(Kind kind)
{
@ -165,13 +194,15 @@ public class RangeTombstone
+ ClusteringPrefix.serializer.valuesWithoutSizeSerializedSize(bound, version, types);
}
public Kind deserialize(DataInput in, int version, List<AbstractType<?>> types, Writer writer) throws IOException
public RangeTombstone.Bound deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{
Kind kind = Kind.values()[in.readByte()];
writer.writeBoundKind(kind);
int size = in.readUnsignedShort();
ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, size, version, types, writer);
return kind;
if (size == 0)
return kind.isStart() ? BOTTOM : TOP;
ByteBuffer[] values = ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, size, version, types);
return new RangeTombstone.Bound(kind, values);
}
}
}

View File

@ -244,7 +244,7 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
{
int idx = searchInternal(clustering, 0, size);
// No matter what the counter cell's timestamp is, a tombstone always takes precedence. See CASSANDRA-7346.
return idx >= 0 && (cell.isCounterCell() || markedAts[idx] >= cell.livenessInfo().timestamp());
return idx >= 0 && (cell.isCounterCell() || markedAts[idx] >= cell.timestamp());
}
/**
@ -254,7 +254,7 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
public DeletionTime searchDeletionTime(Clustering name)
{
int idx = searchInternal(name, 0, size);
return idx < 0 ? null : new SimpleDeletionTime(markedAts[idx], delTimes[idx]);
return idx < 0 ? null : new DeletionTime(markedAts[idx], delTimes[idx]);
}
public RangeTombstone search(Clustering name)
@ -300,6 +300,23 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
return dataSize;
}
public long maxMarkedAt()
{
long max = Long.MIN_VALUE;
for (int i = 0; i < size; i++)
max = Math.max(max, markedAts[i]);
return max;
}
public void collectStats(RowStats.Collector collector)
{
for (int i = 0; i < size; i++)
{
collector.updateTimestamp(markedAts[i]);
collector.updateLocalDeletionTime(delTimes[i]);
}
}
public void updateAllTimestamp(long timestamp)
{
for (int i = 0; i < size; i++)
@ -308,22 +325,22 @@ public class RangeTombstoneList implements Iterable<RangeTombstone>, IMeasurable
private RangeTombstone rangeTombstone(int idx)
{
return new RangeTombstone(Slice.make(starts[idx], ends[idx]), new SimpleDeletionTime(markedAts[idx], delTimes[idx]));
return new RangeTombstone(Slice.make(starts[idx], ends[idx]), new DeletionTime(markedAts[idx], delTimes[idx]));
}
private RangeTombstone rangeTombstoneWithNewStart(int idx, Slice.Bound newStart)
{
return new RangeTombstone(Slice.make(newStart, ends[idx]), new SimpleDeletionTime(markedAts[idx], delTimes[idx]));
return new RangeTombstone(Slice.make(newStart, ends[idx]), new DeletionTime(markedAts[idx], delTimes[idx]));
}
private RangeTombstone rangeTombstoneWithNewEnd(int idx, Slice.Bound newEnd)
{
return new RangeTombstone(Slice.make(starts[idx], newEnd), new SimpleDeletionTime(markedAts[idx], delTimes[idx]));
return new RangeTombstone(Slice.make(starts[idx], newEnd), new DeletionTime(markedAts[idx], delTimes[idx]));
}
private RangeTombstone rangeTombstoneWithNewBounds(int idx, Slice.Bound newStart, Slice.Bound newEnd)
{
return new RangeTombstone(Slice.make(newStart, newEnd), new SimpleDeletionTime(markedAts[idx], delTimes[idx]));
return new RangeTombstone(Slice.make(newStart, newEnd), new DeletionTime(markedAts[idx], delTimes[idx]));
}
public Iterator<RangeTombstone> iterator()

View File

@ -17,11 +17,12 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.config.Schema;
@ -63,7 +64,7 @@ public abstract class ReadCommand implements ReadQuery
protected static abstract class SelectionDeserializer
{
public abstract ReadCommand deserialize(DataInput in, int version, boolean isDigest, boolean isForThrift, CFMetaData metadata, int nowInSec, ColumnFilter columnFilter, RowFilter rowFilter, DataLimits limits) throws IOException;
public abstract ReadCommand deserialize(DataInputPlus in, int version, boolean isDigest, boolean isForThrift, CFMetaData metadata, int nowInSec, ColumnFilter columnFilter, RowFilter rowFilter, DataLimits limits) throws IOException;
}
protected enum Kind
@ -268,22 +269,13 @@ public abstract class ReadCommand implements ReadQuery
try
{
resultIterator = UnfilteredPartitionIterators.convertExpiredCellsToTombstones(resultIterator, nowInSec);
resultIterator = withMetricsRecording(withoutExpiredTombstones(resultIterator, cfs), cfs.metric, startTimeNanos);
// TODO: we should push the dropping of columns down the layers because
// 1) it'll be more efficient
// 2) it could help us solve #6276
// But there is not reason not to do this as a followup so keeping it here for now (we'll have
// to be wary of cached row if we move this down the layers)
if (!metadata().getDroppedColumns().isEmpty())
resultIterator = UnfilteredPartitionIterators.removeDroppedColumns(resultIterator, metadata().getDroppedColumns());
resultIterator = withMetricsRecording(withoutPurgeableTombstones(resultIterator, cfs), cfs.metric, startTimeNanos);
// If we've used a 2ndary index, we know the result already satisfy the primary expression used, so
// no point in checking it again.
RowFilter updatedFilter = searcher == null
? rowFilter()
: rowFilter().without(searcher.primaryClause(this));
? rowFilter()
: rowFilter().without(searcher.primaryClause(this));
// TODO: We'll currently do filtering by the rowFilter here because it's convenient. However,
// we'll probably want to optimize by pushing it down the layer (like for dropped columns) as it
@ -333,26 +325,33 @@ public abstract class ReadCommand implements ReadQuery
{
currentKey = iter.partitionKey();
return new WrappingUnfilteredRowIterator(iter)
return new AlteringUnfilteredRowIterator(iter)
{
public Unfiltered next()
@Override
protected Row computeNextStatic(Row row)
{
Unfiltered unfiltered = super.next();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
{
Row row = (Row) unfiltered;
if (row.hasLiveData(ReadCommand.this.nowInSec()))
++liveRows;
for (Cell cell : row)
if (!cell.isLive(ReadCommand.this.nowInSec()))
countTombstone(row.clustering());
}
else
{
countTombstone(unfiltered.clustering());
}
return computeNext(row);
}
return unfiltered;
@Override
protected Row computeNext(Row row)
{
if (row.hasLiveData(ReadCommand.this.nowInSec()))
++liveRows;
for (Cell cell : row.cells())
{
if (!cell.isLive(ReadCommand.this.nowInSec()))
countTombstone(row.clustering());
}
return row;
}
@Override
protected RangeTombstoneMarker computeNext(RangeTombstoneMarker marker)
{
countTombstone(marker.clustering());
return marker;
}
private void countTombstone(ClusteringPrefix clustering)
@ -407,12 +406,12 @@ public abstract class ReadCommand implements ReadQuery
protected abstract void appendCQLWhereClause(StringBuilder sb);
// Skip expired tombstones. We do this because it's safe to do (post-merge of the memtable and sstable at least), it
// can save us some bandwith, and avoid making us throw a TombstoneOverwhelmingException for expired tombstones (which
// Skip purgeable tombstones. We do this because it's safe to do (post-merge of the memtable and sstable at least), it
// can save us some bandwith, and avoid making us throw a TombstoneOverwhelmingException for purgeable tombstones (which
// are to some extend an artefact of compaction lagging behind and hence counting them is somewhat unintuitive).
protected UnfilteredPartitionIterator withoutExpiredTombstones(UnfilteredPartitionIterator iterator, ColumnFamilyStore cfs)
protected UnfilteredPartitionIterator withoutPurgeableTombstones(UnfilteredPartitionIterator iterator, ColumnFamilyStore cfs)
{
return new TombstonePurgingPartitionIterator(iterator, cfs.gcBefore(nowInSec()))
return new PurgingPartitionIterator(iterator, cfs.gcBefore(nowInSec()))
{
protected long getMaxPurgeableTimestamp()
{

View File

@ -1,82 +0,0 @@
/*
* 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.nio.ByteBuffer;
import java.util.Arrays;
import org.apache.cassandra.utils.ObjectSizes;
public class ReusableClustering extends Clustering
{
private static final long EMPTY_SIZE = ObjectSizes.measure(new ReusableClustering(0));
protected final ByteBuffer[] values;
protected ReusableWriter writer;
public ReusableClustering(int size)
{
this.values = new ByteBuffer[size];
}
public int size()
{
return values.length;
}
public ByteBuffer get(int i)
{
return values[i];
}
public ByteBuffer[] getRawValues()
{
return values;
}
public Writer writer()
{
if (writer == null)
writer = new ReusableWriter();
return writer;
}
public void reset()
{
Arrays.fill(values, null);
if (writer != null)
writer.reset();
}
protected class ReusableWriter implements Writer
{
int idx;
public void writeClusteringValue(ByteBuffer value)
{
values[idx++] = value;
}
private void reset()
{
idx = 0;
}
}
}

View File

@ -1,57 +0,0 @@
/*
* 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.nio.ByteBuffer;
import java.util.Arrays;
import org.apache.cassandra.utils.ObjectSizes;
// Note that we abuse a bit ReusableClustering to store Slice.Bound infos, but it's convenient so ...
public class ReusableClusteringPrefix extends ReusableClustering
{
private Kind kind;
private int size;
public ReusableClusteringPrefix(int size)
{
super(size);
}
public ClusteringPrefix get()
{
// We use ReusableClusteringPrefix when writing sstables (in ColumnIndex) and we
// don't write static clustering there.
assert kind != Kind.STATIC_CLUSTERING;
if (kind == Kind.CLUSTERING)
{
assert values.length == size;
return this;
}
return Slice.Bound.create(kind, Arrays.copyOfRange(values, 0, size));
}
public void copy(ClusteringPrefix clustering)
{
kind = clustering.kind();
for (int i = 0; i < clustering.size(); i++)
values[i] = clustering.get(i);
size = clustering.size();
}
}

View File

@ -1,65 +0,0 @@
/*
* 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;
public class ReusableLivenessInfo extends AbstractLivenessInfo
{
private long timestamp;
private int ttl;
private int localDeletionTime;
public ReusableLivenessInfo()
{
reset();
}
public LivenessInfo setTo(LivenessInfo info)
{
return setTo(info.timestamp(), info.ttl(), info.localDeletionTime());
}
public LivenessInfo setTo(long timestamp, int ttl, int localDeletionTime)
{
this.timestamp = timestamp;
this.ttl = ttl;
this.localDeletionTime = localDeletionTime;
return this;
}
public long timestamp()
{
return timestamp;
}
public int ttl()
{
return ttl;
}
public int localDeletionTime()
{
return localDeletionTime;
}
public void reset()
{
this.timestamp = LivenessInfo.NO_TIMESTAMP;
this.ttl = LivenessInfo.NO_TTL;
this.localDeletionTime = LivenessInfo.NO_DELETION_TIME;
}
}

View File

@ -43,25 +43,27 @@ public class RowUpdateBuilder
{
private final PartitionUpdate update;
private final LivenessInfo defaultLiveness;
private final LivenessInfo deletionLiveness;
private final long timestamp;
private final int ttl;
private final int localDeletionTime;
private final DeletionTime deletionTime;
private final Mutation mutation;
private Row.Writer regularWriter;
private Row.Writer staticWriter;
private Row.Builder regularBuilder;
private Row.Builder staticBuilder;
private boolean hasSetClustering;
private boolean useRowMarker = true;
private RowUpdateBuilder(PartitionUpdate update, long timestamp, int ttl, int localDeletionTime, Mutation mutation)
{
this.update = update;
this.defaultLiveness = SimpleLivenessInfo.forUpdate(timestamp, ttl, localDeletionTime, update.metadata());
this.deletionLiveness = SimpleLivenessInfo.forDeletion(timestamp, localDeletionTime);
this.deletionTime = new SimpleDeletionTime(timestamp, localDeletionTime);
this.timestamp = timestamp;
this.ttl = ttl;
this.localDeletionTime = localDeletionTime;
this.deletionTime = new DeletionTime(timestamp, localDeletionTime);
// note that the created mutation may get further update later on, so we don't use the ctor that create a singletonMap
// underneath (this class if for convenience, not performance)
@ -73,31 +75,45 @@ public class RowUpdateBuilder
this(update, timestamp, ttl, FBUtilities.nowInSeconds(), mutation);
}
private Row.Writer writer()
private void startRow(Clustering clustering)
{
assert staticWriter == null : "Cannot update both static and non-static columns with the same RowUpdateBuilder object";
if (regularWriter == null)
assert staticBuilder == null : "Cannot update both static and non-static columns with the same RowUpdateBuilder object";
assert regularBuilder == null : "Cannot add the clustering twice to the same row";
regularBuilder = ArrayBackedRow.unsortedBuilder(update.columns().regulars, FBUtilities.nowInSeconds());
regularBuilder.newRow(clustering);
// If a CQL table, add the "row marker"
if (update.metadata().isCQLTable() && useRowMarker)
regularBuilder.addPrimaryKeyLivenessInfo(LivenessInfo.create(update.metadata(), timestamp, ttl, localDeletionTime));
}
private Row.Builder builder()
{
assert staticBuilder == null : "Cannot update both static and non-static columns with the same RowUpdateBuilder object";
if (regularBuilder == null)
{
regularWriter = update.writer();
// If a CQL table, add the "row marker"
if (update.metadata().isCQLTable() && useRowMarker)
regularWriter.writePartitionKeyLivenessInfo(defaultLiveness);
// we don't force people to call clustering() if the table has no clustering, so call it ourselves
assert update.metadata().comparator.size() == 0 : "Missing call to clustering()";
startRow(Clustering.EMPTY);
}
return regularWriter;
return regularBuilder;
}
private Row.Writer staticWriter()
private Row.Builder staticBuilder()
{
assert regularWriter == null : "Cannot update both static and non-static columns with the same RowUpdateBuilder object";
if (staticWriter == null)
staticWriter = update.staticWriter();
return staticWriter;
assert regularBuilder == null : "Cannot update both static and non-static columns with the same RowUpdateBuilder object";
if (staticBuilder == null)
{
staticBuilder = ArrayBackedRow.unsortedBuilder(update.columns().statics, FBUtilities.nowInSeconds());
staticBuilder.newRow(Clustering.STATIC_CLUSTERING);
}
return staticBuilder;
}
private Row.Writer writer(ColumnDefinition c)
private Row.Builder builder(ColumnDefinition c)
{
return c.isStatic() ? staticWriter() : writer();
return c.isStatic() ? staticBuilder() : builder();
}
public RowUpdateBuilder(CFMetaData metadata, long timestamp, Object partitionKey)
@ -145,18 +161,17 @@ public class RowUpdateBuilder
public RowUpdateBuilder clustering(Object... clusteringValues)
{
assert clusteringValues.length == update.metadata().comparator.size()
: "Invalid clustering values length. Expected: " + update.metadata().comparator.size() + " got: " + clusteringValues.length;
hasSetClustering = true;
if (clusteringValues.length > 0)
Rows.writeClustering(update.metadata().comparator.make(clusteringValues), writer());
: "Invalid clustering values length. Expected: " + update.metadata().comparator.size() + " got: " + clusteringValues.length;
startRow(clusteringValues.length == 0 ? Clustering.EMPTY : update.metadata().comparator.make(clusteringValues));
return this;
}
public Mutation build()
{
Row.Writer writer = regularWriter == null ? staticWriter : regularWriter;
if (writer != null)
writer.endOfRow();
Row.Builder builder = regularBuilder == null ? staticBuilder : regularBuilder;
if (builder != null)
update.add(builder.build());
return mutation;
}
@ -170,14 +185,16 @@ public class RowUpdateBuilder
{
assert clusteringValues.length == update.metadata().comparator.size() || (clusteringValues.length == 0 && !update.columns().statics.isEmpty());
Row.Writer writer = clusteringValues.length == update.metadata().comparator.size()
? update.writer()
: update.staticWriter();
boolean isStatic = clusteringValues.length != update.metadata().comparator.size();
Row.Builder builder = ArrayBackedRow.sortedBuilder(isStatic ? update.columns().statics : update.columns().regulars);
if (clusteringValues.length > 0)
Rows.writeClustering(update.metadata().comparator.make(clusteringValues), writer);
writer.writeRowDeletion(new SimpleDeletionTime(timestamp, FBUtilities.nowInSeconds()));
writer.endOfRow();
if (isStatic)
builder.newRow(Clustering.STATIC_CLUSTERING);
else
builder.newRow(clusteringValues.length == 0 ? Clustering.EMPTY : update.metadata().comparator.make(clusteringValues));
builder.addRowDeletion(new DeletionTime(timestamp, FBUtilities.nowInSeconds()));
update.add(builder.build());
}
public static Mutation deleteRow(CFMetaData metadata, long timestamp, Mutation mutation, Object... clusteringValues)
@ -219,22 +236,21 @@ public class RowUpdateBuilder
{
ColumnDefinition c = getDefinition(columnName);
assert c != null : "Cannot find column " + columnName;
assert c.isStatic() || update.metadata().comparator.size() == 0 || hasSetClustering : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.isStatic() || update.metadata().comparator.size() == 0 || regularBuilder != null : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.type.isCollection() && c.type.isMultiCell();
writer(c).writeComplexDeletion(c, new SimpleDeletionTime(defaultLiveness.timestamp() - 1, deletionTime.localDeletionTime()));
builder(c).addComplexDeletion(c, new DeletionTime(timestamp - 1, localDeletionTime));
return this;
}
public RowUpdateBuilder addRangeTombstone(RangeTombstone rt)
{
update.addRangeTombstone(rt);
update.add(rt);
return this;
}
public RowUpdateBuilder addRangeTombstone(Slice slice)
{
update.addRangeTombstone(slice, deletionTime);
return this;
return addRangeTombstone(new RangeTombstone(slice, deletionTime));
}
public RowUpdateBuilder addRangeTombstone(Object start, Object end)
@ -251,13 +267,17 @@ public class RowUpdateBuilder
return add(c, value);
}
private Cell makeCell(ColumnDefinition c, ByteBuffer value, CellPath path)
{
return value == null
? BufferCell.tombstone(c, timestamp, localDeletionTime)
: (ttl == LivenessInfo.NO_TTL ? BufferCell.live(update.metadata(), c, timestamp, value, path) : BufferCell.expiring(c, timestamp, ttl, localDeletionTime, value, path));
}
public RowUpdateBuilder add(ColumnDefinition columnDefinition, Object value)
{
assert columnDefinition.isStatic() || update.metadata().comparator.size() == 0 || hasSetClustering : "Cannot set non static column " + columnDefinition + " since no clustering hasn't been provided";
if (value == null)
writer(columnDefinition).writeCell(columnDefinition, false, ByteBufferUtil.EMPTY_BYTE_BUFFER, deletionLiveness, null);
else
writer(columnDefinition).writeCell(columnDefinition, false, bb(value, columnDefinition.type), defaultLiveness, null);
assert columnDefinition.isStatic() || update.metadata().comparator.size() == 0 || regularBuilder != null : "Cannot set non static column " + columnDefinition + " since no clustering hasn't been provided";
builder(columnDefinition).addCell(makeCell(columnDefinition, bb(value, columnDefinition.type), null));
return this;
}
@ -273,8 +293,11 @@ public class RowUpdateBuilder
return add(columnDefinition, null);
}
private ByteBuffer bb(Object value, AbstractType<?> type)
private static ByteBuffer bb(Object value, AbstractType<?> type)
{
if (value == null)
return null;
if (value instanceof ByteBuffer)
return (ByteBuffer)value;
@ -306,30 +329,30 @@ public class RowUpdateBuilder
public RowUpdateBuilder addMapEntry(String columnName, Object key, Object value)
{
ColumnDefinition c = getDefinition(columnName);
assert c.isStatic() || update.metadata().comparator.size() == 0 || hasSetClustering : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.isStatic() || update.metadata().comparator.size() == 0 || regularBuilder != null : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.type instanceof MapType && c.type.isMultiCell();
MapType mt = (MapType)c.type;
writer(c).writeCell(c, false, bb(value, mt.getValuesType()), defaultLiveness, CellPath.create(bb(key, mt.getKeysType())));
builder(c).addCell(makeCell(c, bb(value, mt.getValuesType()), CellPath.create(bb(key, mt.getKeysType()))));
return this;
}
public RowUpdateBuilder addListEntry(String columnName, Object value)
{
ColumnDefinition c = getDefinition(columnName);
assert c.isStatic() || hasSetClustering : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.isStatic() || regularBuilder != null : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.type instanceof ListType && c.type.isMultiCell();
ListType lt = (ListType)c.type;
writer(c).writeCell(c, false, bb(value, lt.getElementsType()), defaultLiveness, CellPath.create(ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes())));
builder(c).addCell(makeCell(c, bb(value, lt.getElementsType()), CellPath.create(ByteBuffer.wrap(UUIDGen.getTimeUUIDBytes()))));
return this;
}
public RowUpdateBuilder addSetEntry(String columnName, Object value)
{
ColumnDefinition c = getDefinition(columnName);
assert c.isStatic() || hasSetClustering : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.isStatic() || regularBuilder != null : "Cannot set non static column " + c + " since no clustering has been provided";
assert c.type instanceof SetType && c.type.isMultiCell();
SetType st = (SetType)c.type;
writer(c).writeCell(c, false, ByteBufferUtil.EMPTY_BYTE_BUFFER, defaultLiveness, CellPath.create(bb(value, st.getElementsType())));
builder(c).addCell(makeCell(c, ByteBufferUtil.EMPTY_BYTE_BUFFER, CellPath.create(bb(value, st.getElementsType()))));
return this;
}

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@ -97,7 +96,7 @@ public class SerializationHeader
// We always use a dense layout for the static row. Having very many static columns with only a few set at
// any given time doesn't feel very common at all (and we already optimize the case where no static at all
// are provided).
return isStatic ? false : useSparseColumnLayout;
return !isStatic && useSparseColumnLayout;
}
public static SerializationHeader forKeyCache(CFMetaData metadata)
@ -159,13 +158,7 @@ public class SerializationHeader
private static List<AbstractType<?>> typesOf(List<ColumnDefinition> columns)
{
return ImmutableList.copyOf(Lists.transform(columns, new Function<ColumnDefinition, AbstractType<?>>()
{
public AbstractType<?> apply(ColumnDefinition column)
{
return column.type;
}
}));
return ImmutableList.copyOf(Lists.transform(columns, column -> column.type));
}
public PartitionColumns columns()
@ -365,7 +358,7 @@ public class SerializationHeader
Columns.serializer.serialize(header.columns.regulars, out);
}
public SerializationHeader deserializeForMessaging(DataInput in, CFMetaData metadata, boolean hasStatic) throws IOException
public SerializationHeader deserializeForMessaging(DataInputPlus in, CFMetaData metadata, boolean hasStatic) throws IOException
{
RowStats stats = RowStats.serializer.deserialize(in);
@ -458,7 +451,7 @@ public class SerializationHeader
return size;
}
private void readColumnsWithType(DataInput in, Map<ByteBuffer, AbstractType<?>> typeMap) throws IOException
private void readColumnsWithType(DataInputPlus in, Map<ByteBuffer, AbstractType<?>> typeMap) throws IOException
{
int length = in.readUnsignedShort();
for (int i = 0; i < length; i++)
@ -474,7 +467,7 @@ public class SerializationHeader
ByteBufferUtil.writeWithLength(UTF8Type.instance.decompose(type.toString()), out);
}
private AbstractType<?> readType(DataInput in) throws IOException
private AbstractType<?> readType(DataInputPlus in) throws IOException
{
ByteBuffer raw = ByteBufferUtil.readWithLength(in);
return TypeParser.parse(UTF8Type.instance.compose(raw));

View File

@ -1,93 +0,0 @@
/*
* 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.nio.ByteBuffer;
import org.apache.cassandra.utils.ObjectSizes;
public class SimpleClustering extends Clustering
{
private static final long EMPTY_SIZE = ObjectSizes.measure(new SimpleClustering(new ByteBuffer[0]));
private final ByteBuffer[] values;
public SimpleClustering(ByteBuffer... values)
{
this.values = values;
}
public SimpleClustering(ByteBuffer value)
{
this(new ByteBuffer[]{ value });
}
public int size()
{
return values.length;
}
public ByteBuffer get(int i)
{
return values[i];
}
public ByteBuffer[] getRawValues()
{
return values;
}
@Override
public long unsharedHeapSize()
{
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(values);
}
@Override
public Clustering takeAlias()
{
return this;
}
public static Builder builder(int size)
{
return new Builder(size);
}
public static class Builder implements Writer
{
private final ByteBuffer[] values;
private int idx;
private Builder(int size)
{
this.values = new ByteBuffer[size];
}
public void writeClusteringValue(ByteBuffer value)
{
values[idx++] = value;
}
public SimpleClustering build()
{
assert idx == values.length;
return new SimpleClustering(values);
}
}
}

View File

@ -1,61 +0,0 @@
/*
* 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.IOException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import org.apache.cassandra.cache.IMeasurableMemory;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.ObjectSizes;
/**
* Simple implementation of DeletionTime.
*/
public class SimpleDeletionTime extends DeletionTime
{
public final long markedForDeleteAt;
public final int localDeletionTime;
@VisibleForTesting
public SimpleDeletionTime(long markedForDeleteAt, int localDeletionTime)
{
this.markedForDeleteAt = markedForDeleteAt;
this.localDeletionTime = localDeletionTime;
}
public long markedForDeleteAt()
{
return markedForDeleteAt;
}
public int localDeletionTime()
{
return localDeletionTime;
}
public DeletionTime takeAlias()
{
return this;
}
}

View File

@ -1,75 +0,0 @@
/*
* 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.Objects;
import java.security.MessageDigest;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.FBUtilities;
public class SimpleLivenessInfo extends AbstractLivenessInfo
{
private final long timestamp;
private final int ttl;
private final int localDeletionTime;
// Note that while some code use this ctor, the two following static creation methods
// are usually less error prone.
SimpleLivenessInfo(long timestamp, int ttl, int localDeletionTime)
{
this.timestamp = timestamp;
this.ttl = ttl;
this.localDeletionTime = localDeletionTime;
}
public static SimpleLivenessInfo forUpdate(long timestamp, int ttl, int nowInSec, CFMetaData metadata)
{
if (ttl == NO_TTL)
ttl = metadata.getDefaultTimeToLive();
return new SimpleLivenessInfo(timestamp, ttl, ttl == NO_TTL ? NO_DELETION_TIME : nowInSec + ttl);
}
public static SimpleLivenessInfo forDeletion(long timestamp, int localDeletionTime)
{
return new SimpleLivenessInfo(timestamp, NO_TTL, localDeletionTime);
}
public long timestamp()
{
return timestamp;
}
public int ttl()
{
return ttl;
}
public int localDeletionTime()
{
return localDeletionTime;
}
@Override
public LivenessInfo takeAlias()
{
return this;
}
}

View File

@ -146,7 +146,7 @@ public class SinglePartitionNamesCommand extends SinglePartitionReadCommand<Clus
try (UnfilteredRowIterator iter = result.unfilteredIterator(columnFilter(), Slices.ALL, false))
{
final Mutation mutation = new Mutation(UnfilteredRowIterators.toUpdate(iter));
final Mutation mutation = new Mutation(PartitionUpdate.fromIterator(iter));
StageManager.getStage(Stage.MUTATION).execute(new Runnable()
{
public void run()
@ -232,7 +232,7 @@ public class SinglePartitionNamesCommand extends SinglePartitionReadCommand<Clus
// We can remove a row if it has data that is more recent that the next sstable to consider for the data that the query
// cares about. And the data we care about is 1) the row timestamp (since every query cares if the row exists or not)
// and 2) the requested columns.
if (!row.primaryKeyLivenessInfo().hasTimestamp() || row.primaryKeyLivenessInfo().timestamp() <= sstableTimestamp)
if (row.primaryKeyLivenessInfo().isEmpty() || row.primaryKeyLivenessInfo().timestamp() <= sstableTimestamp)
return false;
for (ColumnDefinition column : requestedColumns)
@ -242,7 +242,7 @@ public class SinglePartitionNamesCommand extends SinglePartitionReadCommand<Clus
return false;
Cell cell = row.getCell(column);
if (cell == null || cell.livenessInfo().timestamp() <= sstableTimestamp)
if (cell == null || cell.timestamp() <= sstableTimestamp)
return false;
}
return true;

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.util.*;
@ -29,6 +28,7 @@ import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.filter.*;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.metrics.TableMetrics;
import org.apache.cassandra.service.*;
@ -483,7 +483,7 @@ public abstract class SinglePartitionReadCommand<F extends ClusteringIndexFilter
private static class Deserializer extends SelectionDeserializer
{
public ReadCommand deserialize(DataInput in, int version, boolean isDigest, boolean isForThrift, CFMetaData metadata, int nowInSec, ColumnFilter columnFilter, RowFilter rowFilter, DataLimits limits)
public ReadCommand deserialize(DataInputPlus in, int version, boolean isDigest, boolean isForThrift, CFMetaData metadata, int nowInSec, ColumnFilter columnFilter, RowFilter rowFilter, DataLimits limits)
throws IOException
{
DecoratedKey key = StorageService.getPartitioner().decorateKey(metadata.getKeyValidator().readValue(in));

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
@ -25,6 +24,7 @@ import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.ObjectSizes;
@ -68,8 +68,8 @@ public class Slice
private Slice(Bound start, Bound end)
{
assert start.isStart() && end.isEnd();
this.start = start.takeAlias();
this.end = end.takeAlias();
this.start = start;
this.end = end;
}
public static Slice make(Bound start, Bound end)
@ -331,7 +331,7 @@ public class Slice
+ Bound.serializer.serializedSize(slice.end, version, types);
}
public Slice deserialize(DataInput in, int version, List<AbstractType<?>> types) throws IOException
public Slice deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{
Bound start = Bound.serializer.deserialize(in, version, types);
Bound end = Bound.serializer.deserialize(in, version, types);
@ -346,21 +346,19 @@ public class Slice
*/
public static class Bound extends AbstractClusteringPrefix
{
private static final long EMPTY_SIZE = ObjectSizes.measure(new Bound(Kind.INCL_START_BOUND, new ByteBuffer[0]));
public static final Serializer serializer = new Serializer();
/** The smallest start bound, i.e. the one that starts before any row. */
public static final Bound BOTTOM = inclusiveStartOf();
/** The biggest end bound, i.e. the one that ends after any row. */
public static final Bound TOP = inclusiveEndOf();
protected final Kind kind;
protected final ByteBuffer[] values;
/**
* The smallest and biggest bound. Note that as range tomstone bounds are (special case) of slice bounds,
* we want the BOTTOM and TOP to be the same object, but we alias them here because it's cleaner when dealing
* with slices to refer to Slice.Bound.BOTTOM and Slice.Bound.TOP.
*/
public static final Bound BOTTOM = RangeTombstone.Bound.BOTTOM;
public static final Bound TOP = RangeTombstone.Bound.TOP;
protected Bound(Kind kind, ByteBuffer[] values)
{
this.kind = kind;
this.values = values;
super(kind, values);
}
public static Bound create(Kind kind, ByteBuffer[] values)
@ -396,22 +394,6 @@ public class Slice
return create(Kind.EXCL_END_BOUND, values);
}
public static Bound exclusiveStartOf(ClusteringPrefix prefix)
{
ByteBuffer[] values = new ByteBuffer[prefix.size()];
for (int i = 0; i < prefix.size(); i++)
values[i] = prefix.get(i);
return exclusiveStartOf(values);
}
public static Bound inclusiveEndOf(ClusteringPrefix prefix)
{
ByteBuffer[] values = new ByteBuffer[prefix.size()];
for (int i = 0; i < prefix.size(); i++)
values[i] = prefix.get(i);
return inclusiveEndOf(values);
}
public static Bound create(ClusteringComparator comparator, boolean isStart, boolean isInclusive, Object... values)
{
CBuilder builder = CBuilder.create(comparator);
@ -426,21 +408,6 @@ public class Slice
return builder.buildBound(isStart, isInclusive);
}
public Kind kind()
{
return kind;
}
public int size()
{
return values.length;
}
public ByteBuffer get(int i)
{
return values[i];
}
public Bound withNewKind(Kind kind)
{
assert !kind.isBoundary();
@ -480,24 +447,6 @@ public class Slice
return withNewKind(kind().invert());
}
public ByteBuffer[] getRawValues()
{
return values;
}
public void digest(MessageDigest digest)
{
for (int i = 0; i < size(); i++)
digest.update(get(i).duplicate());
FBUtilities.updateWithByte(digest, kind().ordinal());
}
public void writeTo(Slice.Bound.Writer writer)
{
super.writeTo(writer);
writer.writeBoundKind(kind());
}
// For use by intersects, it's called with the sstable bound opposite to the slice bound
// (so if the slice bound is a start, it's call with the max sstable bound)
private int compareTo(ClusteringComparator comparator, List<ByteBuffer> sstableBound)
@ -544,66 +493,10 @@ public class Slice
return sb.append(")").toString();
}
// Overriding to get a more precise type
@Override
public Bound takeAlias()
{
return this;
}
@Override
public long unsharedHeapSize()
{
return EMPTY_SIZE + ObjectSizes.sizeOnHeapOf(values);
}
public long unsharedHeapSizeExcludingData()
{
return EMPTY_SIZE + ObjectSizes.sizeOnHeapExcludingData(values);
}
public static Builder builder(int size)
{
return new Builder(size);
}
public interface Writer extends ClusteringPrefix.Writer
{
public void writeBoundKind(Kind kind);
}
public static class Builder implements Writer
{
private final ByteBuffer[] values;
private Kind kind;
private int idx;
private Builder(int size)
{
this.values = new ByteBuffer[size];
}
public void writeClusteringValue(ByteBuffer value)
{
values[idx++] = value;
}
public void writeBoundKind(Kind kind)
{
this.kind = kind;
}
public Slice.Bound build()
{
assert idx == values.length;
return Slice.Bound.create(kind, values);
}
}
/**
* Serializer for slice bounds.
* <p>
* Contrarily to {@code Clustering}, a slice bound can only be a true prefix of the full clustering, so we actually record
* Contrarily to {@code Clustering}, a slice bound can be a true prefix of the full clustering, so we actually record
* its size.
*/
public static class Serializer
@ -622,31 +515,21 @@ public class Slice
+ ClusteringPrefix.serializer.valuesWithoutSizeSerializedSize(bound, version, types);
}
public Slice.Bound deserialize(DataInput in, int version, List<AbstractType<?>> types) throws IOException
public Slice.Bound deserialize(DataInputPlus in, int version, List<AbstractType<?>> types) throws IOException
{
Kind kind = Kind.values()[in.readByte()];
return deserializeValues(in, kind, version, types);
}
public Slice.Bound deserializeValues(DataInput in, Kind kind, int version, List<AbstractType<?>> types) throws IOException
public Slice.Bound deserializeValues(DataInputPlus in, Kind kind, int version, List<AbstractType<?>> types) throws IOException
{
int size = in.readUnsignedShort();
if (size == 0)
return kind.isStart() ? BOTTOM : TOP;
Builder builder = builder(size);
ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, size, version, types, builder);
builder.writeBoundKind(kind);
return builder.build();
ByteBuffer[] values = ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, size, version, types);
return Slice.Bound.create(kind, values);
}
public void deserializeValues(DataInput in, Bound.Kind kind, int version, List<AbstractType<?>> types, Writer writer) throws IOException
{
int size = in.readUnsignedShort();
ClusteringPrefix.serializer.deserializeValuesWithoutSize(in, size, version, types, writer);
writer.writeBoundKind(kind);
}
}
}
}

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
@ -28,6 +27,7 @@ import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
/**
@ -318,7 +318,7 @@ public abstract class Slices implements Iterable<Slice>
return size;
}
public Slices deserialize(DataInput in, int version, CFMetaData metadata) throws IOException
public Slices deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
int size = in.readInt();

View File

@ -1160,7 +1160,7 @@ public final class SystemKeyspace
// delete all previous values with a single range tombstone.
int nowInSec = FBUtilities.nowInSeconds();
update.addRangeTombstone(Slice.make(SizeEstimates.comparator, table), new SimpleDeletionTime(timestamp - 1, nowInSec));
update.add(new RangeTombstone(Slice.make(SizeEstimates.comparator, table), new DeletionTime(timestamp - 1, nowInSec)));
// add a CQL row for each primary token range.
for (Map.Entry<Range<Token>, Pair<Long, Long>> entry : estimates.entrySet())

View File

@ -68,6 +68,11 @@ public final class TypeSizes
return sizeof(value.remaining()) + value.remaining();
}
public static int sizeofWithVIntLength(ByteBuffer value)
{
return sizeofVInt(value.remaining()) + value.remaining();
}
public static int sizeof(boolean value)
{
return BOOL_SIZE;

View File

@ -111,8 +111,7 @@ public abstract class UnfilteredDeserializer
private boolean isReady;
private boolean isDone;
private final ReusableRow row;
private final RangeTombstoneMarker.Builder markerBuilder;
private final Row.Builder builder;
private CurrentDeserializer(CFMetaData metadata,
DataInputPlus in,
@ -122,8 +121,7 @@ public abstract class UnfilteredDeserializer
super(metadata, in, helper);
this.header = header;
this.clusteringDeserializer = new ClusteringPrefix.Deserializer(metadata.comparator, in, header);
this.row = new ReusableRow(metadata.clusteringColumns().size(), header.columns().regulars, true, metadata.isCounter());
this.markerBuilder = new RangeTombstoneMarker.Builder(metadata.clusteringColumns().size());
this.builder = ArrayBackedRow.sortedBuilder(helper.fetchedRegularColumns(header));
}
public boolean hasNext() throws IOException
@ -181,17 +179,13 @@ public abstract class UnfilteredDeserializer
isReady = false;
if (UnfilteredSerializer.kind(nextFlags) == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
{
markerBuilder.reset();
RangeTombstone.Bound.Kind kind = clusteringDeserializer.deserializeNextBound(markerBuilder);
UnfilteredSerializer.serializer.deserializeMarkerBody(in, header, kind.isBoundary(), markerBuilder);
return markerBuilder.build();
RangeTombstone.Bound bound = clusteringDeserializer.deserializeNextBound();
return UnfilteredSerializer.serializer.deserializeMarkerBody(in, header, bound);
}
else
{
Row.Writer writer = row.writer();
clusteringDeserializer.deserializeNextClustering(writer);
UnfilteredSerializer.serializer.deserializeRowBody(in, header, helper, nextFlags, writer);
return row;
builder.newRow(clusteringDeserializer.deserializeNextClustering());
return UnfilteredSerializer.serializer.deserializeRowBody(in, header, helper, nextFlags, builder);
}
}
@ -205,7 +199,7 @@ public abstract class UnfilteredDeserializer
}
else
{
UnfilteredSerializer.serializer.skipRowBody(in, header, helper, nextFlags);
UnfilteredSerializer.serializer.skipRowBody(in, header, nextFlags);
}
}
@ -221,7 +215,6 @@ public abstract class UnfilteredDeserializer
private final boolean readAllAsDynamic;
private boolean skipStatic;
private int nextFlags;
private boolean isDone;
private boolean isStart = true;
@ -254,13 +247,7 @@ public abstract class UnfilteredDeserializer
public boolean hasNext() throws IOException
{
if (nextAtom != null)
return true;
if (isDone)
return false;
return deserializeNextAtom();
return nextAtom != null || (!isDone && deserializeNextAtom());
}
private boolean deserializeNextAtom() throws IOException
@ -392,6 +379,7 @@ public abstract class UnfilteredDeserializer
grouper.addAtom(nextAtom);
while (deserializeNextAtom() && grouper.addAtom(nextAtom))
{
// Nothing to do, deserializeNextAtom() changes nextAtom and it's then added to the grouper
}
// if this was the first static row, we're done with it. Otherwise, we're also done with static.

View File

@ -38,8 +38,6 @@ import org.apache.cassandra.utils.ByteBufferUtil;
abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
{
private static final Logger logger = LoggerFactory.getLogger(AbstractSSTableIterator.class);
protected final SSTableReader sstable;
protected final DecoratedKey key;
protected final DeletionTime partitionLevelDeletion;
@ -65,7 +63,7 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
this.sstable = sstable;
this.key = key;
this.columns = columnFilter;
this.helper = new SerializationHelper(sstable.descriptor.version.correspondingMessagingVersion(), SerializationHelper.Flag.LOCAL, columnFilter);
this.helper = new SerializationHelper(sstable.metadata, sstable.descriptor.version.correspondingMessagingVersion(), SerializationHelper.Flag.LOCAL, columnFilter);
this.isForThrift = isForThrift;
if (indexEntry == null)
@ -81,7 +79,7 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
{
// We seek to the beginning to the partition if either:
// - the partition is not indexed; we then have a single block to read anyway
// and we need to read the partition deletion time.
// (and we need to read the partition deletion time).
// - we're querying static columns.
boolean needSeekAtPartitionStart = !indexEntry.isIndexed() || !columns.fetchedColumns().statics.isEmpty();
@ -104,24 +102,24 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
// Note that this needs to be called after file != null and after the partitionDeletion has been set, but before readStaticRow
// (since it uses it) so we can't move that up (but we'll be able to simplify as soon as we drop support for the old file format).
this.reader = needsReader ? createReader(indexEntry, file, needSeekAtPartitionStart, shouldCloseFile) : null;
this.reader = needsReader ? createReader(indexEntry, file, true, shouldCloseFile) : null;
this.staticRow = readStaticRow(sstable, file, helper, columns.fetchedColumns().statics, isForThrift, reader == null ? null : reader.deserializer);
}
else
{
this.partitionLevelDeletion = indexEntry.deletionTime();
this.staticRow = Rows.EMPTY_STATIC_ROW;
this.reader = needsReader ? createReader(indexEntry, file, needSeekAtPartitionStart, shouldCloseFile) : null;
this.reader = needsReader ? createReader(indexEntry, file, false, shouldCloseFile) : null;
}
if (reader == null && shouldCloseFile)
if (reader == null && file != null && shouldCloseFile)
file.close();
}
catch (IOException e)
{
sstable.markSuspect();
String filePath = file.getPath();
if (shouldCloseFile && file != null)
if (shouldCloseFile)
{
try
{
@ -164,7 +162,7 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
if (statics.isEmpty() || isForThrift)
return Rows.EMPTY_STATIC_ROW;
assert sstable.metadata.isStaticCompactTable() && !isForThrift;
assert sstable.metadata.isStaticCompactTable();
// As said above, if it's a CQL query and the table is a "static compact", the only exposed columns are the
// static ones. So we don't have to mark the position to seek back later.
@ -221,45 +219,13 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
public boolean hasNext()
{
try
{
return reader != null && reader.hasNext();
}
catch (IOException e)
{
try
{
closeInternal();
}
catch (IOException suppressed)
{
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
}
return reader != null && reader.hasNext();
}
public Unfiltered next()
{
try
{
assert reader != null;
return reader.next();
}
catch (IOException e)
{
try
{
closeInternal();
}
catch (IOException suppressed)
{
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
}
assert reader != null;
return reader.next();
}
public Iterator<Unfiltered> slice(Slice slice)
@ -269,7 +235,8 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
if (reader == null)
return Collections.emptyIterator();
return reader.slice(slice);
reader.setForSlice(slice);
return reader;
}
catch (IOException e)
{
@ -317,7 +284,7 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
}
}
protected abstract class Reader
protected abstract class Reader implements Iterator<Unfiltered>
{
private final boolean shouldCloseFile;
public FileDataInput file;
@ -327,12 +294,19 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
// Records the currently open range tombstone (if any)
protected DeletionTime openMarker = null;
protected Reader(FileDataInput file, boolean shouldCloseFile)
// !isInit means we have never seeked in the file and thus should seek before reading anything
protected boolean isInit;
protected Reader(FileDataInput file, boolean isInit, boolean shouldCloseFile)
{
this.file = file;
this.isInit = isInit;
this.shouldCloseFile = shouldCloseFile;
if (file != null)
createDeserializer();
else
assert !isInit;
}
private void createDeserializer()
@ -369,9 +343,62 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
return toReturn;
}
public abstract boolean hasNext() throws IOException;
public abstract Unfiltered next() throws IOException;
public abstract Iterator<Unfiltered> slice(Slice slice) throws IOException;
public boolean hasNext()
{
try
{
if (!isInit)
{
init();
isInit = true;
}
return hasNextInternal();
}
catch (IOException e)
{
try
{
closeInternal();
}
catch (IOException suppressed)
{
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
}
}
public Unfiltered next()
{
try
{
return nextInternal();
}
catch (IOException e)
{
try
{
closeInternal();
}
catch (IOException suppressed)
{
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, reader.file.getPath());
}
}
// Called is hasNext() is called but we haven't been yet initialized
protected abstract void init() throws IOException;
// Set the reader so its hasNext/next methods return values within the provided slice
public abstract void setForSlice(Slice slice) throws IOException;
protected abstract boolean hasNextInternal() throws IOException;
protected abstract Unfiltered nextInternal() throws IOException;
public void close() throws IOException
{
@ -380,35 +407,61 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
}
}
protected abstract class IndexedReader extends Reader
// Used by indexed readers to store where they are of the index.
protected static class IndexState
{
protected final RowIndexEntry indexEntry;
protected final List<IndexHelper.IndexInfo> indexes;
private final Reader reader;
private final ClusteringComparator comparator;
protected int currentIndexIdx = -1;
private final RowIndexEntry indexEntry;
private final List<IndexHelper.IndexInfo> indexes;
private final boolean reversed;
private int currentIndexIdx = -1;
// Marks the beginning of the block corresponding to currentIndexIdx.
protected FileMark mark;
private FileMark mark;
// !isInit means we have never seeked in the file and thus shouldn't read as we could be anywhere
protected boolean isInit;
protected IndexedReader(FileDataInput file, boolean shouldCloseFile, RowIndexEntry indexEntry, boolean isInit)
public IndexState(Reader reader, ClusteringComparator comparator, RowIndexEntry indexEntry, boolean reversed)
{
super(file, shouldCloseFile);
this.reader = reader;
this.comparator = comparator;
this.indexEntry = indexEntry;
this.indexes = indexEntry.columnsIndex();
this.isInit = isInit;
this.reversed = reversed;
this.currentIndexIdx = reversed ? indexEntry.columnsIndex().size() : -1;
}
// Should be called when we're at the beginning of blockIdx.
protected void updateBlock(int blockIdx) throws IOException
public boolean isDone()
{
seekToPosition(indexEntry.position + indexes.get(blockIdx).offset);
return reversed ? currentIndexIdx < 0 : currentIndexIdx >= indexes.size();
}
// Sets the reader to the beginning of blockIdx.
public void setToBlock(int blockIdx) throws IOException
{
if (blockIdx >= 0 && blockIdx < indexes.size())
reader.seekToPosition(indexEntry.position + indexes.get(blockIdx).offset);
currentIndexIdx = blockIdx;
openMarker = blockIdx > 0 ? indexes.get(blockIdx - 1).endOpenMarker : null;
mark = file.mark();
reader.openMarker = blockIdx > 0 ? indexes.get(blockIdx - 1).endOpenMarker : null;
mark = reader.file.mark();
}
public int blocksCount()
{
return indexes.size();
}
// Check if we've crossed an index boundary (based on the mark on the beginning of the index block).
public boolean isPastCurrentBlock()
{
return currentIndexIdx < indexes.size() && reader.file.bytesPastMark(mark) >= currentIndex().width;
}
public int currentBlockIdx()
{
return currentIndexIdx;
}
public IndexHelper.IndexInfo currentIndex()
@ -416,9 +469,16 @@ abstract class AbstractSSTableIterator implements SliceableUnfilteredRowIterator
return indexes.get(currentIndexIdx);
}
public IndexHelper.IndexInfo previousIndex()
// Finds the index of the first block containing the provided bound, starting at the current index.
// Will be -1 if the bound is before any block, and blocksCount() if it is after every block.
public int findBlockIndex(Slice.Bound bound)
{
return currentIndexIdx <= 1 ? null : indexes.get(currentIndexIdx - 1);
if (bound == Slice.Bound.BOTTOM)
return -1;
if (bound == Slice.Bound.TOP)
return blocksCount();
return IndexHelper.indexFor(bound, indexes, comparator, reversed, currentIndexIdx);
}
}
}

View File

@ -18,30 +18,19 @@
package org.apache.cassandra.db.columniterator;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.google.common.collect.AbstractIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.NoSuchElementException;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.sstable.IndexHelper;
import org.apache.cassandra.io.util.FileDataInput;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* A Cell Iterator over SSTable
*/
public class SSTableIterator extends AbstractSSTableIterator
{
private static final Logger logger = LoggerFactory.getLogger(SSTableIterator.class);
public SSTableIterator(SSTableReader sstable, DecoratedKey key, ColumnFilter columns, boolean isForThrift)
{
this(sstable, null, key, sstable.getPosition(key, SSTableReader.Operator.EQ), columns, isForThrift);
@ -71,222 +60,215 @@ public class SSTableIterator extends AbstractSSTableIterator
private class ForwardReader extends Reader
{
// The start of the current slice. This will be null as soon as we know we've passed that bound.
protected Slice.Bound start;
// The end of the current slice. Will never be null.
protected Slice.Bound end = Slice.Bound.TOP;
protected Unfiltered next; // the next element to return: this is computed by hasNextInternal().
protected boolean sliceDone; // set to true once we know we have no more result for the slice. This is in particular
// used by the indexed reader when we know we can't have results based on the index.
private ForwardReader(FileDataInput file, boolean isAtPartitionStart, boolean shouldCloseFile)
{
super(file, shouldCloseFile);
assert isAtPartitionStart;
super(file, isAtPartitionStart, shouldCloseFile);
}
public boolean hasNext() throws IOException
protected void init() throws IOException
{
assert deserializer != null;
return deserializer.hasNext();
// We should always have been initialized (at the beginning of the partition). Only indexed readers may
// have to initialize.
throw new IllegalStateException();
}
public Unfiltered next() throws IOException
public void setForSlice(Slice slice) throws IOException
{
return deserializer.readNext();
start = slice.start() == Slice.Bound.BOTTOM ? null : slice.start();
end = slice.end();
sliceDone = false;
next = null;
}
public Iterator<Unfiltered> slice(final Slice slice) throws IOException
// Skip all data that comes before the currently set slice.
// Return what should be returned at the end of this, or null if nothing should.
private Unfiltered handlePreSliceData() throws IOException
{
return new AbstractIterator<Unfiltered>()
// Note that the following comparison is not strict. The reason is that the only cases
// where it can be == is if the "next" is a RT start marker (either a '[' of a ')[' boundary),
// and if we had a strict inequality and an open RT marker before this, we would issue
// the open marker first, and then return then next later, which would send in the
// stream both '[' (or '(') and then ')[' for the same clustering value, which is wrong.
// By using a non-strict inequality, we avoid that problem (if we do get ')[' for the same
// clustering value than the slice, we'll simply record it in 'openMarker').
while (deserializer.hasNext() && deserializer.compareNextTo(start) <= 0)
{
private boolean beforeStart = true;
if (deserializer.nextIsRow())
deserializer.skipNext();
else
updateOpenMarker((RangeTombstoneMarker)deserializer.readNext());
}
protected Unfiltered computeNext()
Slice.Bound sliceStart = start;
start = null;
// We've reached the beginning of our queried slice. If we have an open marker
// we should return that first.
if (openMarker != null)
return new RangeTombstoneBoundMarker(sliceStart, openMarker);
return null;
}
// Compute the next element to return, assuming we're in the middle to the slice
// and the next element is either in the slice, or just after it. Returns null
// if we're done with the slice.
protected Unfiltered computeNext() throws IOException
{
if (!deserializer.hasNext() || deserializer.compareNextTo(end) > 0)
return null;
Unfiltered next = deserializer.readNext();
if (next.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
updateOpenMarker((RangeTombstoneMarker)next);
return next;
}
protected boolean hasNextInternal() throws IOException
{
if (next != null)
return true;
if (sliceDone)
return false;
assert deserializer != null;
if (start != null)
{
Unfiltered unfiltered = handlePreSliceData();
if (unfiltered != null)
{
try
{
// While we're before the start of the slice, we can skip row but we should keep
// track of open range tombstones
if (beforeStart)
{
// Note that the following comparison is not strict. The reason is that the only cases
// where it can be == is if the "next" is a RT start marker (either a '[' of a ')[' boundary),
// and if we had a strict inequality and an open RT marker before this, we would issue
// the open marker first, and then return then next later, which would yet in the
// stream both '[' (or '(') and then ')[' for the same clustering value, which is wrong.
// By using a non-strict inequality, we avoid that problem (if we do get ')[' for the same
// clustering value than the slice, we'll simply record it in 'openMarker').
while (deserializer.hasNext() && deserializer.compareNextTo(slice.start()) <= 0)
{
if (deserializer.nextIsRow())
deserializer.skipNext();
else
updateOpenMarker((RangeTombstoneMarker)deserializer.readNext());
}
beforeStart = false;
// We've reached the beginning of our queried slice. If we have an open marker
// we should return that first.
if (openMarker != null)
return new RangeTombstoneBoundMarker(slice.start(), openMarker);
}
if (deserializer.hasNext() && deserializer.compareNextTo(slice.end()) <= 0)
{
Unfiltered next = deserializer.readNext();
if (next.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
updateOpenMarker((RangeTombstoneMarker)next);
return next;
}
// If we have an open marker, we should close it before finishing
if (openMarker != null)
return new RangeTombstoneBoundMarker(slice.end(), getAndClearOpenMarker());
return endOfData();
}
catch (IOException e)
{
try
{
close();
}
catch (IOException suppressed)
{
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, file.getPath());
}
next = unfiltered;
return true;
}
};
}
next = computeNext();
if (next != null)
return true;
// If we have an open marker, we should close it before finishing
if (openMarker != null)
{
next = new RangeTombstoneBoundMarker(end, getAndClearOpenMarker());
return true;
}
sliceDone = true; // not absolutely necessary but accurate and cheap
return false;
}
protected Unfiltered nextInternal() throws IOException
{
if (!hasNextInternal())
throw new NoSuchElementException();
Unfiltered toReturn = next;
next = null;
return toReturn;
}
}
private class ForwardIndexedReader extends IndexedReader
private class ForwardIndexedReader extends ForwardReader
{
private final IndexState indexState;
private int lastBlockIdx; // the last index block that has data for the current query
private ForwardIndexedReader(RowIndexEntry indexEntry, FileDataInput file, boolean isAtPartitionStart, boolean shouldCloseFile)
{
super(file, shouldCloseFile, indexEntry, isAtPartitionStart);
super(file, isAtPartitionStart, shouldCloseFile);
this.indexState = new IndexState(this, sstable.metadata.comparator, indexEntry, false);
this.lastBlockIdx = indexState.blocksCount(); // if we never call setForSlice, that's where we want to stop
}
public boolean hasNext() throws IOException
@Override
protected void init() throws IOException
{
// If it's called before we've created the file, create it. This then mean
// we're reading from the beginning of the partition.
if (!isInit)
{
seekToPosition(indexEntry.position);
ByteBufferUtil.skipShortLength(file); // partition key
DeletionTime.serializer.skip(file); // partition deletion
if (sstable.header.hasStatic())
UnfilteredSerializer.serializer.skipStaticRow(file, sstable.header, helper);
isInit = true;
}
return deserializer.hasNext();
// If this is called, it means we're calling hasNext() before any call to setForSlice. Which means
// we're reading everything from the beginning. So just set us up at the beginning of the first block.
indexState.setToBlock(0);
}
public Unfiltered next() throws IOException
@Override
public void setForSlice(Slice slice) throws IOException
{
return deserializer.readNext();
}
super.setForSlice(slice);
public Iterator<Unfiltered> slice(final Slice slice) throws IOException
{
final List<IndexHelper.IndexInfo> indexes = indexEntry.columnsIndex();
isInit = true;
// if our previous slicing already got us the biggest row in the sstable, we're done
if (currentIndexIdx >= indexes.size())
return Collections.emptyIterator();
if (indexState.isDone())
{
sliceDone = true;
return;
}
// Find the first index block we'll need to read for the slice.
final int startIdx = IndexHelper.indexFor(slice.start(), indexes, sstable.metadata.comparator, false, currentIndexIdx);
if (startIdx >= indexes.size())
return Collections.emptyIterator();
int startIdx = indexState.findBlockIndex(slice.start());
if (startIdx >= indexState.blocksCount())
{
sliceDone = true;
return;
}
// If that's the last block we were reading, we're already where we want to be. Otherwise,
// seek to that first block
if (startIdx != currentIndexIdx)
updateBlock(startIdx);
if (startIdx != indexState.currentBlockIdx())
indexState.setToBlock(startIdx);
// Find the last index block we'll need to read for the slice.
final int endIdx = IndexHelper.indexFor(slice.end(), indexes, sstable.metadata.comparator, false, startIdx);
final IndexHelper.IndexInfo startIndex = currentIndex();
lastBlockIdx = indexState.findBlockIndex(slice.end());
// The index search is based on the last name of the index blocks, so at that point we have that:
// 1) indexes[startIdx - 1].lastName < slice.start <= indexes[startIdx].lastName
// 2) indexes[endIdx - 1].lastName < slice.end <= indexes[endIdx].lastName
// so if startIdx == endIdx and slice.end < indexes[startIdx].firstName, we're guaranteed that the
// whole slice is between the previous block end and this bloc start, and thus has no corresponding
// 1) indexes[currentIdx - 1].lastName < slice.start <= indexes[currentIdx].lastName
// 2) indexes[lastBlockIdx - 1].lastName < slice.end <= indexes[lastBlockIdx].lastName
// so if currentIdx == lastBlockIdx and slice.end < indexes[currentIdx].firstName, we're guaranteed that the
// whole slice is between the previous block end and this block start, and thus has no corresponding
// data. One exception is if the previous block ends with an openMarker as it will cover our slice
// and we need to return it.
if (startIdx == endIdx && metadata().comparator.compare(slice.end(), startIndex.firstName) < 0 && openMarker == null && sstable.descriptor.version.storeRows())
return Collections.emptyIterator();
return new AbstractIterator<Unfiltered>()
if (indexState.currentBlockIdx() == lastBlockIdx
&& metadata().comparator.compare(slice.end(), indexState.currentIndex().firstName) < 0
&& openMarker == null
&& sstable.descriptor.version.storeRows())
{
private boolean beforeStart = true;
private int currentIndexIdx = startIdx;
sliceDone = true;
}
}
protected Unfiltered computeNext()
{
try
{
// While we're before the start of the slice, we can skip row but we should keep
// track of open range tombstones
if (beforeStart)
{
// See ForwardReader equivalent method to see why this inequality is not strict.
while (deserializer.hasNext() && deserializer.compareNextTo(slice.start()) <= 0)
{
if (deserializer.nextIsRow())
deserializer.skipNext();
else
updateOpenMarker((RangeTombstoneMarker)deserializer.readNext());
}
@Override
protected Unfiltered computeNext() throws IOException
{
// Our previous read might have made us cross an index block boundary. If so, update our informations.
if (indexState.isPastCurrentBlock())
indexState.setToBlock(indexState.currentBlockIdx() + 1);
beforeStart = false;
// Return the next unfiltered unless we've reached the end, or we're beyond our slice
// end (note that unless we're on the last block for the slice, there is no point
// in checking the slice end).
if (indexState.isDone()
|| indexState.currentBlockIdx() > lastBlockIdx
|| !deserializer.hasNext()
|| (indexState.currentBlockIdx() == lastBlockIdx && deserializer.compareNextTo(end) > 0))
return null;
// We've reached the beginning of our queried slice. If we have an open marker
// we should return that first.
if (openMarker != null)
return new RangeTombstoneBoundMarker(slice.start(), openMarker);
}
// If we've crossed an index block boundary, update our informations
if (currentIndexIdx < indexes.size() && file.bytesPastMark(mark) >= currentIndex().width)
updateBlock(++currentIndexIdx);
// Return the next atom unless we've reached the end, or we're beyond our slice
// end (note that unless we're on the last block for the slice, there is no point
// in checking the slice end).
if (currentIndexIdx < indexes.size()
&& currentIndexIdx <= endIdx
&& deserializer.hasNext()
&& (currentIndexIdx != endIdx || deserializer.compareNextTo(slice.end()) <= 0))
{
Unfiltered next = deserializer.readNext();
if (next.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
updateOpenMarker((RangeTombstoneMarker)next);
return next;
}
// If we have an open marker, we should close it before finishing
if (openMarker != null)
return new RangeTombstoneBoundMarker(slice.end(), getAndClearOpenMarker());
return endOfData();
}
catch (IOException e)
{
try
{
close();
}
catch (IOException suppressed)
{
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, file.getPath());
}
}
};
Unfiltered next = deserializer.readNext();
if (next.kind() == Unfiltered.Kind.RANGE_TOMBSTONE_MARKER)
updateOpenMarker((RangeTombstoneMarker)next);
return next;
}
}
}

View File

@ -20,29 +20,19 @@ package org.apache.cassandra.db.columniterator;
import java.io.IOException;
import java.util.*;
import com.google.common.collect.AbstractIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.AbstractPartitionData;
import org.apache.cassandra.db.partitions.AbstractThreadUnsafePartition;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.sstable.CorruptSSTableException;
import org.apache.cassandra.io.sstable.IndexHelper;
import org.apache.cassandra.io.util.FileDataInput;
import org.apache.cassandra.io.util.FileMark;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* A Cell Iterator in reversed clustering order over SSTable
*/
public class SSTableReversedIterator extends AbstractSSTableIterator
{
private static final Logger logger = LoggerFactory.getLogger(SSTableReversedIterator.class);
public SSTableReversedIterator(SSTableReader sstable, DecoratedKey key, ColumnFilter columns, boolean isForThrift)
{
this(sstable, null, key, sstable.getPosition(key, SSTableReader.Operator.EQ), columns, isForThrift);
@ -70,319 +60,290 @@ public class SSTableReversedIterator extends AbstractSSTableIterator
return true;
}
private ReusablePartitionData createBuffer(int blocksCount)
{
int estimatedRowCount = 16;
int columnCount = metadata().partitionColumns().regulars.columnCount();
if (columnCount == 0 || metadata().clusteringColumns().size() == 0)
{
estimatedRowCount = 1;
}
else
{
try
{
// To avoid wasted resizing we guess-estimate the number of rows we're likely to read. For that
// we use the stats on the number of rows per partition for that sstable.
// FIXME: so far we only keep stats on cells, so to get a rough estimate on the number of rows,
// we divide by the number of regular columns the table has. We should fix once we collect the
// stats on rows
int estimatedRowsPerPartition = (int)(sstable.getEstimatedColumnCount().percentile(0.75) / columnCount);
estimatedRowCount = Math.max(estimatedRowsPerPartition / blocksCount, 1);
}
catch (IllegalStateException e)
{
// The EstimatedHistogram mean() method can throw this (if it overflows). While such overflow
// shouldn't happen, it's not worth taking the risk of letting the exception bubble up.
}
}
return new ReusablePartitionData(metadata(), partitionKey(), DeletionTime.LIVE, columns(), estimatedRowCount);
}
private class ReverseReader extends Reader
{
private ReusablePartitionData partition;
private UnfilteredRowIterator iterator;
protected ReusablePartitionData buffer;
protected Iterator<Unfiltered> iterator;
private ReverseReader(FileDataInput file, boolean isAtPartitionStart, boolean shouldCloseFile)
{
super(file, shouldCloseFile);
assert isAtPartitionStart;
super(file, isAtPartitionStart, shouldCloseFile);
}
public boolean hasNext() throws IOException
protected ReusablePartitionData createBuffer(int blocksCount)
{
if (partition == null)
int estimatedRowCount = 16;
int columnCount = metadata().partitionColumns().regulars.columnCount();
if (columnCount == 0 || metadata().clusteringColumns().isEmpty())
{
partition = createBuffer(1);
partition.populateFrom(this, null, null, new Tester()
{
public boolean isDone()
{
return false;
}
});
iterator = partition.unfilteredIterator(columns, Slices.ALL, true);
estimatedRowCount = 1;
}
return iterator.hasNext();
}
public Unfiltered next() throws IOException
{
if (!hasNext())
throw new NoSuchElementException();
return iterator.next();
}
public Iterator<Unfiltered> slice(final Slice slice) throws IOException
{
if (partition == null)
{
partition = createBuffer(1);
partition.populateFrom(this, slice.start(), slice.end(), new Tester()
{
public boolean isDone()
{
return false;
}
});
}
return partition.unfilteredIterator(columns, Slices.with(metadata().comparator, slice), true);
}
}
private class ReverseIndexedReader extends IndexedReader
{
private ReusablePartitionData partition;
private UnfilteredRowIterator iterator;
private ReverseIndexedReader(RowIndexEntry indexEntry, FileDataInput file, boolean isAtPartitionStart, boolean shouldCloseFile)
{
super(file, shouldCloseFile, indexEntry, isAtPartitionStart);
this.currentIndexIdx = indexEntry.columnsIndex().size();
}
public boolean hasNext() throws IOException
{
// If it's called before we've created the file, create it. This then mean
// we're reading from the end of the partition.
if (!isInit)
{
seekToPosition(indexEntry.position);
ByteBufferUtil.skipShortLength(file); // partition key
DeletionTime.serializer.skip(file); // partition deletion
if (sstable.header.hasStatic())
UnfilteredSerializer.serializer.skipStaticRow(file, sstable.header, helper);
isInit = true;
}
if (partition == null)
{
partition = createBuffer(indexes.size());
partition.populateFrom(this, null, null, new Tester()
{
public boolean isDone()
{
return false;
}
});
iterator = partition.unfilteredIterator(columns, Slices.ALL, true);
}
return iterator.hasNext();
}
public Unfiltered next() throws IOException
{
if (!hasNext())
throw new NoSuchElementException();
return iterator.next();
}
private void prepareBlock(int blockIdx, Slice.Bound start, Slice.Bound end) throws IOException
{
updateBlock(blockIdx);
if (partition == null)
partition = createBuffer(indexes.size());
else
partition.clear();
final FileMark fileMark = mark;
final long width = currentIndex().width;
partition.populateFrom(this, start, end, new Tester()
{
public boolean isDone()
try
{
return file.bytesPastMark(fileMark) >= width;
// To avoid wasted resizing we guess-estimate the number of rows we're likely to read. For that
// we use the stats on the number of rows per partition for that sstable.
// FIXME: so far we only keep stats on cells, so to get a rough estimate on the number of rows,
// we divide by the number of regular columns the table has. We should fix once we collect the
// stats on rows
int estimatedRowsPerPartition = (int)(sstable.getEstimatedColumnCount().percentile(0.75) / columnCount);
estimatedRowCount = Math.max(estimatedRowsPerPartition / blocksCount, 1);
}
});
}
@Override
public Iterator<Unfiltered> slice(final Slice slice) throws IOException
{
// if our previous slicing already got us the smallest row in the sstable, we're done
if (currentIndexIdx < 0)
return Collections.emptyIterator();
final List<IndexHelper.IndexInfo> indexes = indexEntry.columnsIndex();
// Find the first index block we'll need to read for the slice.
final int startIdx = IndexHelper.indexFor(slice.end(), indexes, sstable.metadata.comparator, true, currentIndexIdx);
if (startIdx < 0)
return Collections.emptyIterator();
// Find the last index block we'll need to read for the slice.
int lastIdx = IndexHelper.indexFor(slice.start(), indexes, sstable.metadata.comparator, true, startIdx);
// The index search is by firstname and so lastIdx is such that
// indexes[lastIdx].firstName < slice.start <= indexes[lastIdx + 1].firstName
// However, if indexes[lastIdx].lastName < slice.start we can bump lastIdx.
if (lastIdx >= 0 && metadata().comparator.compare(indexes.get(lastIdx).lastName, slice.start()) < 0)
++lastIdx;
final int endIdx = lastIdx;
// Because we're reversed, even if it is our current block, we should re-prepare the block since we would
// have skipped anything not in the previous slice.
prepareBlock(startIdx, slice.start(), slice.end());
return new AbstractIterator<Unfiltered>()
{
private Iterator<Unfiltered> currentBlockIterator = partition.unfilteredIterator(columns, Slices.with(metadata().comparator, slice), true);
protected Unfiltered computeNext()
catch (IllegalStateException e)
{
try
{
if (currentBlockIterator.hasNext())
return currentBlockIterator.next();
--currentIndexIdx;
if (currentIndexIdx < 0 || currentIndexIdx < endIdx)
return endOfData();
// Note that since we know we're read blocks backward, there is no point in checking the slice end, so we pass null
prepareBlock(currentIndexIdx, slice.start(), null);
currentBlockIterator = partition.unfilteredIterator(columns, Slices.with(metadata().comparator, slice), true);
return computeNext();
}
catch (IOException e)
{
try
{
close();
}
catch (IOException suppressed)
{
e.addSuppressed(suppressed);
}
sstable.markSuspect();
throw new CorruptSSTableException(e, file.getPath());
}
// The EstimatedHistogram mean() method can throw this (if it overflows). While such overflow
// shouldn't happen, it's not worth taking the risk of letting the exception bubble up.
}
};
}
}
private abstract class Tester
{
public abstract boolean isDone();
}
private class ReusablePartitionData extends AbstractPartitionData
{
private final Writer rowWriter;
private final RangeTombstoneCollector markerWriter;
private ReusablePartitionData(CFMetaData metadata,
DecoratedKey partitionKey,
DeletionTime deletionTime,
PartitionColumns columns,
int initialRowCapacity)
{
super(metadata, partitionKey, deletionTime, columns, initialRowCapacity, false);
this.rowWriter = new Writer(true);
// Note that even though the iterator handles the reverse case, this object holds the data for a single index bock, and we read index blocks in
// forward clustering order.
this.markerWriter = new RangeTombstoneCollector(false);
}
return new ReusablePartitionData(metadata(), partitionKey(), columns(), estimatedRowCount);
}
// Note that this method is here rather than in the readers because we want to use it for both readers and they
// don't extend one another
private void populateFrom(Reader reader, Slice.Bound start, Slice.Bound end, Tester tester) throws IOException
protected void init() throws IOException
{
// If we have a start bound, skip everything that comes before it.
while (reader.deserializer.hasNext() && start != null && reader.deserializer.compareNextTo(start) <= 0 && !tester.isDone())
// We should always have been initialized (at the beginning of the partition). Only indexed readers may
// have to initialize.
throw new IllegalStateException();
}
public void setForSlice(Slice slice) throws IOException
{
// If we have read the data, just create the iterator for the slice. Otherwise, read the data.
if (buffer == null)
{
if (reader.deserializer.nextIsRow())
reader.deserializer.skipNext();
else
reader.updateOpenMarker((RangeTombstoneMarker)reader.deserializer.readNext());
buffer = createBuffer(1);
// Note that we can reuse that buffer between slices (we could alternatively re-read from disk
// every time, but that feels more wasteful) so we want to include everything from the beginning.
// We can stop at the slice end however since any following slice will be before that.
loadFromDisk(null, slice.end());
}
setIterator(slice);
}
protected void setIterator(Slice slice)
{
assert buffer != null;
iterator = buffer.unfilteredIterator(columns, Slices.with(metadata().comparator, slice), true);
}
protected boolean hasNextInternal() throws IOException
{
// If we've never called setForSlice, we're reading everything
if (iterator == null)
setForSlice(Slice.ALL);
return iterator.hasNext();
}
protected Unfiltered nextInternal() throws IOException
{
if (!hasNext())
throw new NoSuchElementException();
return iterator.next();
}
protected boolean stopReadingDisk()
{
return false;
}
// Reads the unfiltered from disk and load them into the reader buffer. It stops reading when either the partition
// is fully read, or when stopReadingDisk() returns true.
protected void loadFromDisk(Slice.Bound start, Slice.Bound end) throws IOException
{
buffer.reset();
// If the start might be in this block, skip everything that comes before it.
if (start != null)
{
while (deserializer.hasNext() && deserializer.compareNextTo(start) <= 0 && !stopReadingDisk())
{
if (deserializer.nextIsRow())
deserializer.skipNext();
else
updateOpenMarker((RangeTombstoneMarker)deserializer.readNext());
}
}
// If we have an open marker, it's either one from what we just skipped (if start != null), or it's from the previous index block.
if (reader.openMarker != null)
if (openMarker != null)
{
// If we have no start but still an openMarker, this means we're indexed and it's coming from the previous block
Slice.Bound markerStart = start;
if (start == null)
{
ClusteringPrefix c = ((IndexedReader)reader).previousIndex().lastName;
markerStart = Slice.Bound.exclusiveStartOf(c);
}
writeMarker(markerStart, reader.openMarker);
RangeTombstone.Bound markerStart = start == null ? RangeTombstone.Bound.BOTTOM : RangeTombstone.Bound.fromSliceBound(start);
buffer.add(new RangeTombstoneBoundMarker(markerStart, openMarker));
}
// Now deserialize everything until we reach our requested end (if we have one)
while (reader.deserializer.hasNext()
&& (end == null || reader.deserializer.compareNextTo(end) <= 0)
&& !tester.isDone())
while (deserializer.hasNext()
&& (end == null || deserializer.compareNextTo(end) <= 0)
&& !stopReadingDisk())
{
Unfiltered unfiltered = reader.deserializer.readNext();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
{
((Row) unfiltered).copyTo(rowWriter);
}
else
{
RangeTombstoneMarker marker = (RangeTombstoneMarker) unfiltered;
reader.updateOpenMarker(marker);
marker.copyTo(markerWriter);
}
Unfiltered unfiltered = deserializer.readNext();
buffer.add(unfiltered);
if (unfiltered.isRangeTombstoneMarker())
updateOpenMarker((RangeTombstoneMarker)unfiltered);
}
// If we have an open marker, we should close it before finishing
if (reader.openMarker != null)
if (openMarker != null)
{
// If we no end and still an openMarker, this means we're indexed and the marker can be close using the blocks end
Slice.Bound markerEnd = end;
if (end == null)
{
ClusteringPrefix c = ((IndexedReader)reader).currentIndex().lastName;
markerEnd = Slice.Bound.inclusiveEndOf(c);
}
writeMarker(markerEnd, reader.getAndClearOpenMarker());
// If we have no end and still an openMarker, this means we're indexed and the marker is closed in a following block.
RangeTombstone.Bound markerEnd = end == null ? RangeTombstone.Bound.TOP : RangeTombstone.Bound.fromSliceBound(end);
buffer.add(new RangeTombstoneBoundMarker(markerEnd, getAndClearOpenMarker()));
}
buffer.build();
}
}
private class ReverseIndexedReader extends ReverseReader
{
private final IndexState indexState;
// The slice we're currently iterating over
private Slice slice;
// The last index block to consider for the slice
private int lastBlockIdx;
private ReverseIndexedReader(RowIndexEntry indexEntry, FileDataInput file, boolean isAtPartitionStart, boolean shouldCloseFile)
{
super(file, isAtPartitionStart, shouldCloseFile);
this.indexState = new IndexState(this, sstable.metadata.comparator, indexEntry, true);
}
private void writeMarker(Slice.Bound bound, DeletionTime dt)
protected void init() throws IOException
{
bound.writeTo(markerWriter);
markerWriter.writeBoundDeletion(dt);
markerWriter.endOfMarker();
// If this is called, it means we're calling hasNext() before any call to setForSlice. Which means
// we're reading everything from the end. So just set us up on the last block.
indexState.setToBlock(indexState.blocksCount() - 1);
}
@Override
public void clear()
public void setForSlice(Slice slice) throws IOException
{
super.clear();
rowWriter.reset();
markerWriter.reset();
this.slice = slice;
isInit = true;
// if our previous slicing already got us pas the beginning of the sstable, we're done
if (indexState.isDone())
{
iterator = Collections.emptyIterator();
return;
}
// Find the first index block we'll need to read for the slice.
int startIdx = indexState.findBlockIndex(slice.end());
if (startIdx < 0)
{
iterator = Collections.emptyIterator();
return;
}
boolean isCurrentBlock = startIdx == indexState.currentBlockIdx();
if (!isCurrentBlock)
indexState.setToBlock(startIdx);
lastBlockIdx = indexState.findBlockIndex(slice.start());
if (!isCurrentBlock)
readCurrentBlock(true);
setIterator(slice);
}
@Override
protected boolean hasNextInternal() throws IOException
{
if (super.hasNextInternal())
return true;
// We have nothing more for our current block, move the previous one.
int previousBlockIdx = indexState.currentBlockIdx() - 1;
if (previousBlockIdx < 0 || previousBlockIdx < lastBlockIdx)
return false;
// The slice start can be in
indexState.setToBlock(previousBlockIdx);
readCurrentBlock(false);
setIterator(slice);
// since that new block is within the bounds we've computed in setToSlice(), we know there will
// always be something matching the slice unless we're on the lastBlockIdx (in which case there
// may or may not be results, but if there isn't, we're done for the slice).
return iterator.hasNext();
}
/**
* Reads the current block, the last one we've set.
*
* @param canIncludeSliceEnd whether the block can include the slice end.
*/
private void readCurrentBlock(boolean canIncludeSliceEnd) throws IOException
{
if (buffer == null)
buffer = createBuffer(indexState.blocksCount());
boolean canIncludeSliceStart = indexState.currentBlockIdx() == lastBlockIdx;
loadFromDisk(canIncludeSliceStart ? slice.start() : null, canIncludeSliceEnd ? slice.end() : null);
}
@Override
protected boolean stopReadingDisk()
{
return indexState.isPastCurrentBlock();
}
}
private class ReusablePartitionData extends AbstractThreadUnsafePartition
{
private MutableDeletionInfo.Builder deletionBuilder;
private MutableDeletionInfo deletionInfo;
private ReusablePartitionData(CFMetaData metadata,
DecoratedKey partitionKey,
PartitionColumns columns,
int initialRowCapacity)
{
super(metadata, partitionKey, columns, new ArrayList<>(initialRowCapacity));
}
public DeletionInfo deletionInfo()
{
return deletionInfo;
}
protected boolean canHaveShadowedData()
{
return false;
}
public Row staticRow()
{
return Rows.EMPTY_STATIC_ROW; // we don't actually use that
}
public RowStats stats()
{
return RowStats.NO_STATS; // we don't actually use that
}
public void add(Unfiltered unfiltered)
{
if (unfiltered.isRow())
rows.add((Row)unfiltered);
else
deletionBuilder.add((RangeTombstoneMarker)unfiltered);
}
public void reset()
{
rows.clear();
deletionBuilder = MutableDeletionInfo.builder(partitionLevelDeletion, metadata().comparator, false);
}
public void build()
{
deletionInfo = deletionBuilder.build();
deletionBuilder = null;
}
}
}

View File

@ -26,7 +26,6 @@ import org.apache.cassandra.db.index.SecondaryIndexManager;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.io.sstable.ISSTableScanner;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.metrics.CompactionMetrics;
/**
@ -34,7 +33,7 @@ import org.apache.cassandra.metrics.CompactionMetrics;
* <p>
* On top of the actual merging the source iterators, this class:
* <ul>
* <li>purge gc-able tombstones if possible (see PurgingPartitionIterator below).</li>
* <li>purge gc-able tombstones if possible (see PurgeIterator below).</li>
* <li>update 2ndary indexes if necessary (as we don't read-before-write on index updates, index entries are
* not deleted on deletion of the base table data, which is ok because we'll fix index inconsistency
* on reads. This however mean that potentially obsolete index entries could be kept a long time for
@ -65,12 +64,9 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
*/
private final long[] mergeCounters;
private final UnfilteredPartitionIterator mergedIterator;
private final UnfilteredPartitionIterator compacted;
private final CompactionMetrics metrics;
// The number of row/RT merged by the iterator
private int merged;
public CompactionIterator(OperationType type, List<ISSTableScanner> scanners, CompactionController controller, int nowInSec, UUID compactionId)
{
this(type, scanners, controller, nowInSec, compactionId, null);
@ -96,9 +92,9 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
if (metrics != null)
metrics.beginCompaction(this);
this.mergedIterator = scanners.isEmpty()
? UnfilteredPartitionIterators.EMPTY
: UnfilteredPartitionIterators.convertExpiredCellsToTombstones(new PurgingPartitionIterator(UnfilteredPartitionIterators.merge(scanners, nowInSec, listener()), controller), nowInSec);
this.compacted = scanners.isEmpty()
? UnfilteredPartitionIterators.EMPTY
: new PurgeIterator(UnfilteredPartitionIterators.merge(scanners, nowInSec, listener()), controller);
}
public boolean isForThrift()
@ -143,57 +139,46 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
CompactionIterator.this.updateCounterFor(merged);
/*
* The row level listener does 2 things:
* - It updates 2ndary indexes for deleted/shadowed cells
* - It updates progress regularly (every UNFILTERED_TO_UPDATE_PROGRESS)
*/
final SecondaryIndexManager.Updater indexer = type == OperationType.COMPACTION
? controller.cfs.indexManager.gcUpdaterFor(partitionKey, nowInSec)
: SecondaryIndexManager.nullUpdater;
if (type != OperationType.COMPACTION || !controller.cfs.indexManager.hasIndexes())
return null;
// If we have a 2ndary index, we must update it with deleted/shadowed cells.
// TODO: this should probably be done asynchronously and batched.
final SecondaryIndexManager.Updater indexer = controller.cfs.indexManager.gcUpdaterFor(partitionKey, nowInSec);
final RowDiffListener diffListener = new RowDiffListener()
{
public void onPrimaryKeyLivenessInfo(int i, Clustering clustering, LivenessInfo merged, LivenessInfo original)
{
}
public void onDeletion(int i, Clustering clustering, DeletionTime merged, DeletionTime original)
{
}
public void onComplexDeletion(int i, Clustering clustering, ColumnDefinition column, DeletionTime merged, DeletionTime original)
{
}
public void onCell(int i, Clustering clustering, Cell merged, Cell original)
{
if (original != null && (merged == null || !merged.isLive(nowInSec)))
indexer.remove(clustering, original);
}
};
return new UnfilteredRowIterators.MergeListener()
{
private Clustering clustering;
public void onMergePartitionLevelDeletion(DeletionTime mergedDeletion, DeletionTime[] versions)
public void onMergedPartitionLevelDeletion(DeletionTime mergedDeletion, DeletionTime[] versions)
{
}
public void onMergingRows(Clustering clustering, LivenessInfo mergedInfo, DeletionTime mergedDeletion, Row[] versions)
public void onMergedRows(Row merged, Columns columns, Row[] versions)
{
this.clustering = clustering;
}
public void onMergedComplexDeletion(ColumnDefinition c, DeletionTime mergedCompositeDeletion, DeletionTime[] versions)
{
}
public void onMergedCells(Cell mergedCell, Cell[] versions)
{
if (indexer == SecondaryIndexManager.nullUpdater)
return;
for (int i = 0; i < versions.length; i++)
{
Cell version = versions[i];
if (version != null && (mergedCell == null || !mergedCell.equals(version)))
indexer.remove(clustering, version);
}
}
public void onRowDone()
{
int merged = ++CompactionIterator.this.merged;
if (merged % UNFILTERED_TO_UPDATE_PROGRESS == 0)
updateBytesRead();
Rows.diff(merged, columns, versions, diffListener);
}
public void onMergedRangeTombstoneMarkers(RangeTombstoneMarker mergedMarker, RangeTombstoneMarker[] versions)
{
int merged = ++CompactionIterator.this.merged;
if (merged % UNFILTERED_TO_UPDATE_PROGRESS == 0)
updateBytesRead();
}
public void close()
@ -218,12 +203,12 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
public boolean hasNext()
{
return mergedIterator.hasNext();
return compacted.hasNext();
}
public UnfilteredRowIterator next()
{
return mergedIterator.next();
return compacted.next();
}
public void remove()
@ -235,7 +220,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
{
try
{
mergedIterator.close();
compacted.close();
}
finally
{
@ -249,7 +234,7 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
return this.getCompactionInfo().toString();
}
private class PurgingPartitionIterator extends TombstonePurgingPartitionIterator
private class PurgeIterator extends PurgingPartitionIterator
{
private final CompactionController controller;
@ -257,28 +242,33 @@ public class CompactionIterator extends CompactionInfo.Holder implements Unfilte
private long maxPurgeableTimestamp;
private boolean hasCalculatedMaxPurgeableTimestamp;
private PurgingPartitionIterator(UnfilteredPartitionIterator toPurge, CompactionController controller)
private long compactedUnfiltered;
private PurgeIterator(UnfilteredPartitionIterator toPurge, CompactionController controller)
{
super(toPurge, controller.gcBefore);
this.controller = controller;
}
@Override
protected void onEmpty(DecoratedKey key)
protected void onEmptyPartitionPostPurge(DecoratedKey key)
{
if (type == OperationType.COMPACTION)
controller.cfs.invalidateCachedPartition(key);
}
@Override
protected boolean shouldFilter(UnfilteredRowIterator iterator)
protected void onNewPartition(DecoratedKey key)
{
currentKey = iterator.partitionKey();
currentKey = key;
hasCalculatedMaxPurgeableTimestamp = false;
}
// TODO: we could be able to skip filtering if UnfilteredRowIterator was giving us some stats
// (like the smallest local deletion time).
return true;
@Override
protected void updateProgress()
{
if ((++compactedUnfiltered) % UNFILTERED_TO_UPDATE_PROGRESS == 0)
updateBytesRead();
}
/*

View File

@ -17,13 +17,13 @@
*/
package org.apache.cassandra.db.filter;
import java.io.DataInput;
import java.io.IOException;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.ReversedType;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
public abstract class AbstractClusteringIndexFilter implements ClusteringIndexFilter
@ -68,7 +68,7 @@ public abstract class AbstractClusteringIndexFilter implements ClusteringIndexFi
int i = 0;
for (ColumnDefinition column : metadata.clusteringColumns())
sb.append(i++ == 0 ? "" : ", ").append(column.name).append(column.type instanceof ReversedType ? " ASC" : " DESC");
sb.append(")");
sb.append(')');
}
}
@ -84,7 +84,7 @@ public abstract class AbstractClusteringIndexFilter implements ClusteringIndexFi
filter.serializeInternal(out, version);
}
public ClusteringIndexFilter deserialize(DataInput in, int version, CFMetaData metadata) throws IOException
public ClusteringIndexFilter deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
Kind kind = Kind.values()[in.readUnsignedByte()];
boolean reversed = in.readBoolean();
@ -104,6 +104,6 @@ public abstract class AbstractClusteringIndexFilter implements ClusteringIndexFi
protected static abstract class InternalDeserializer
{
public abstract ClusteringIndexFilter deserialize(DataInput in, int version, CFMetaData metadata, boolean reversed) throws IOException;
public abstract ClusteringIndexFilter deserialize(DataInputPlus in, int version, CFMetaData metadata, boolean reversed) throws IOException;
}
}

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db.filter;
import java.io.DataInput;
import java.io.IOException;
import org.apache.cassandra.config.CFMetaData;
@ -26,6 +25,7 @@ import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
/**
@ -146,7 +146,7 @@ public interface ClusteringIndexFilter
public interface Serializer
{
public void serialize(ClusteringIndexFilter filter, DataOutputPlus out, int version) throws IOException;
public ClusteringIndexFilter deserialize(DataInput in, int version, CFMetaData metadata) throws IOException;
public ClusteringIndexFilter deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException;
public long serializedSize(ClusteringIndexFilter filter, int version);
}
}

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db.filter;
import java.io.DataInput;
import java.io.IOException;
import java.util.*;
@ -27,6 +26,7 @@ import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.*;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.SearchIterator;
import org.apache.cassandra.utils.btree.BTreeSet;
@ -94,6 +94,9 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
public boolean isFullyCoveredBy(CachedPartition partition)
{
if (partition.isEmpty())
return false;
// 'partition' contains all columns, so it covers our filter if our last clusterings
// is smaller than the last in the cache
return clusterings.comparator().compare(clusterings.last(), partition.lastRow().clustering()) <= 0;
@ -109,18 +112,18 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
{
// Note that we don't filter markers because that's a bit trickier (we don't know in advance until when
// the range extend) and it's harmless to left them.
return new FilteringRowIterator(iterator)
return new AlteringUnfilteredRowIterator(iterator)
{
@Override
public FilteringRow makeRowFilter()
public Row computeNextStatic(Row row)
{
return FilteringRow.columnsFilteringRow(columnFilter);
return columnFilter.fetchedColumns().statics.isEmpty() ? null : row.filter(columnFilter, iterator.metadata());
}
@Override
protected boolean includeRow(Row row)
public Row computeNext(Row row)
{
return clusterings.contains(row.clustering());
return clusterings.contains(row.clustering()) ? row.filter(columnFilter, iterator.metadata()) : null;
}
};
}
@ -214,7 +217,7 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
sb.append(i++ == 0 ? "" : ", ").append(clustering.toString(metadata));
if (reversed)
sb.append(", reversed");
return sb.append(")").toString();
return sb.append(')').toString();
}
public String toCQLString(CFMetaData metadata)
@ -223,7 +226,7 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
return "";
StringBuilder sb = new StringBuilder();
sb.append("(").append(ColumnDefinition.toCQLString(metadata.clusteringColumns())).append(")");
sb.append('(').append(ColumnDefinition.toCQLString(metadata.clusteringColumns())).append(')');
sb.append(clusterings.size() == 1 ? " = " : " IN (");
int i = 0;
for (Clustering clustering : clusterings)
@ -258,13 +261,13 @@ public class ClusteringIndexNamesFilter extends AbstractClusteringIndexFilter
private static class NamesDeserializer extends InternalDeserializer
{
public ClusteringIndexFilter deserialize(DataInput in, int version, CFMetaData metadata, boolean reversed) throws IOException
public ClusteringIndexFilter deserialize(DataInputPlus in, int version, CFMetaData metadata, boolean reversed) throws IOException
{
ClusteringComparator comparator = metadata.comparator;
BTreeSet.Builder<Clustering> clusterings = BTreeSet.builder(comparator);
int size = in.readInt();
for (int i = 0; i < size; i++)
clusterings.add(Clustering.serializer.deserialize(in, version, comparator.subtypes()).takeAlias());
clusterings.add(Clustering.serializer.deserialize(in, version, comparator.subtypes()));
return new ClusteringIndexNamesFilter(clusterings.build(), reversed);
}

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db.filter;
import java.io.DataInput;
import java.io.IOException;
import java.util.List;
import java.nio.ByteBuffer;
@ -28,6 +27,7 @@ import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.partitions.CachedPartition;
import org.apache.cassandra.db.partitions.Partition;
import org.apache.cassandra.io.sstable.format.SSTableReader;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
/**
@ -91,25 +91,25 @@ public class ClusteringIndexSliceFilter extends AbstractClusteringIndexFilter
// Note that we don't filter markers because that's a bit trickier (we don't know in advance until when
// the range extend) and it's harmless to leave them.
return new FilteringRowIterator(iterator)
return new AlteringUnfilteredRowIterator(iterator)
{
@Override
public FilteringRow makeRowFilter()
{
return FilteringRow.columnsFilteringRow(columnFilter);
}
@Override
protected boolean includeRow(Row row)
{
return tester.includes(row.clustering());
}
@Override
public boolean hasNext()
{
return !tester.isDone() && super.hasNext();
}
@Override
public Row computeNextStatic(Row row)
{
return columnFilter.fetchedColumns().statics.isEmpty() ? null : row.filter(columnFilter, iterator.metadata());
}
@Override
public Row computeNext(Row row)
{
return tester.includes(row.clustering()) ? row.filter(columnFilter, iterator.metadata()) : null;
}
};
}
@ -170,7 +170,7 @@ public class ClusteringIndexSliceFilter extends AbstractClusteringIndexFilter
private static class SliceDeserializer extends InternalDeserializer
{
public ClusteringIndexFilter deserialize(DataInput in, int version, CFMetaData metadata, boolean reversed) throws IOException
public ClusteringIndexFilter deserialize(DataInputPlus in, int version, CFMetaData metadata, boolean reversed) throws IOException
{
Slices slices = Slices.serializer.deserialize(in, version, metadata);
return new ClusteringIndexSliceFilter(slices, reversed);

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db.filter;
import java.io.DataInput;
import java.io.IOException;
import java.util.*;
@ -30,8 +29,8 @@ import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.ByteBufferUtil;
/**
* Represents which (non-PK) columns (and optionally which sub-part of a column for complex columns) are selected
@ -52,15 +51,6 @@ public class ColumnFilter
{
public static final Serializer serializer = new Serializer();
private static final Comparator<ColumnSubselection> valueComparator = new Comparator<ColumnSubselection>()
{
public int compare(ColumnSubselection s1, ColumnSubselection s2)
{
assert s1.column().name.equals(s2.column().name);
return s1.column().cellPathComparator().compare(s1.minIncludedPath(), s2.minIncludedPath());
}
};
// Distinguish between the 2 cases described above: if 'isFetchAll' is true, then all columns will be retrieved
// by the query, but the values for column/cells not selected by 'selection' and 'subSelections' will be skipped.
// Otherwise, only the column/cells returned by 'selection' and 'subSelections' will be returned at all.
@ -115,6 +105,11 @@ public class ColumnFilter
return isFetchAll ? metadata.partitionColumns() : selection;
}
public boolean includesAllColumns()
{
return isFetchAll;
}
/**
* Whether the provided column is selected by this selection.
*/
@ -144,7 +139,7 @@ public class ColumnFilter
return true;
for (ColumnSubselection subSel : s)
if (subSel.includes(cell.path()))
if (subSel.compareInclusionOf(cell.path()) == 0)
return true;
return false;
@ -163,7 +158,7 @@ public class ColumnFilter
return false;
for (ColumnSubselection subSel : s)
if (subSel.includes(path))
if (subSel.compareInclusionOf(path) == 0)
return false;
return true;
@ -182,7 +177,7 @@ public class ColumnFilter
if (s.isEmpty())
return null;
return new Tester(s.iterator());
return new Tester(isFetchAll, s.iterator());
}
/**
@ -205,46 +200,43 @@ public class ColumnFilter
public static class Tester
{
private final boolean isFetchAll;
private ColumnSubselection current;
private final Iterator<ColumnSubselection> iterator;
private Tester(Iterator<ColumnSubselection> iterator)
private Tester(boolean isFetchAll, Iterator<ColumnSubselection> iterator)
{
this.isFetchAll = isFetchAll;
this.iterator = iterator;
}
public boolean includes(CellPath path)
{
while (current == null)
{
if (!iterator.hasNext())
return false;
current = iterator.next();
if (current.includes(path))
return true;
if (current.column().cellPathComparator().compare(current.maxIncludedPath(), path) < 0)
current = null;
}
return false;
return isFetchAll || includedBySubselection(path);
}
public boolean canSkipValue(CellPath path)
{
while (current == null)
return isFetchAll && !includedBySubselection(path);
}
private boolean includedBySubselection(CellPath path)
{
while (current != null || iterator.hasNext())
{
if (!iterator.hasNext())
if (current == null)
current = iterator.next();
int cmp = current.compareInclusionOf(path);
if (cmp == 0) // The path is included
return true;
else if (cmp < 0) // The path is before this sub-selection, it's not included by any
return false;
current = iterator.next();
if (current.includes(path))
return false;
if (current.column().cellPathComparator().compare(current.maxIncludedPath(), path) < 0)
current = null;
// the path is after this sub-selection, we need to check the next one.
current = null;
}
return true;
return false;
}
}
@ -302,7 +294,7 @@ public class ColumnFilter
SortedSetMultimap<ColumnIdentifier, ColumnSubselection> s = null;
if (subSelections != null)
{
s = TreeMultimap.create(Comparator.<ColumnIdentifier>naturalOrder(), valueComparator);
s = TreeMultimap.create(Comparator.<ColumnIdentifier>naturalOrder(), Comparator.<ColumnSubselection>naturalOrder());
for (ColumnSubselection subSelection : subSelections)
s.put(subSelection.column().name, subSelection);
}
@ -317,6 +309,9 @@ public class ColumnFilter
if (selection == null)
return "*";
if (selection.isEmpty())
return "";
Iterator<ColumnDefinition> defs = selection.selectOrderIterator();
StringBuilder sb = new StringBuilder();
appendColumnDef(sb, defs.next());
@ -351,7 +346,7 @@ public class ColumnFilter
private static final int HAS_SELECTION_MASK = 0x02;
private static final int HAS_SUB_SELECTIONS_MASK = 0x04;
private int makeHeaderByte(ColumnFilter selection)
private static int makeHeaderByte(ColumnFilter selection)
{
return (selection.isFetchAll ? IS_FETCH_ALL_MASK : 0)
| (selection.selection != null ? HAS_SELECTION_MASK : 0)
@ -376,7 +371,7 @@ public class ColumnFilter
}
}
public ColumnFilter deserialize(DataInput in, int version, CFMetaData metadata) throws IOException
public ColumnFilter deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
int header = in.readUnsignedByte();
boolean isFetchAll = (header & IS_FETCH_ALL_MASK) != 0;
@ -394,7 +389,7 @@ public class ColumnFilter
SortedSetMultimap<ColumnIdentifier, ColumnSubselection> subSelections = null;
if (hasSubSelections)
{
subSelections = TreeMultimap.create(Comparator.<ColumnIdentifier>naturalOrder(), valueComparator);
subSelections = TreeMultimap.create(Comparator.<ColumnIdentifier>naturalOrder(), Comparator.<ColumnSubselection>naturalOrder());
int size = in.readUnsignedShort();
for (int i = 0; i < size; i++)
{

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db.filter;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Comparator;
@ -29,6 +28,7 @@ import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.utils.ByteBufferUtil;
@ -38,7 +38,7 @@ import org.apache.cassandra.utils.ByteBufferUtil;
* This only make sense for complex column. For those, this allow for instance
* to select only a slice of a map.
*/
public abstract class ColumnSubselection
public abstract class ColumnSubselection implements Comparable<ColumnSubselection>
{
public static final Serializer serializer = new Serializer();
@ -72,9 +72,19 @@ public abstract class ColumnSubselection
protected abstract Kind kind();
public abstract CellPath minIncludedPath();
public abstract CellPath maxIncludedPath();
public abstract boolean includes(CellPath path);
protected abstract CellPath comparisonPath();
public int compareTo(ColumnSubselection other)
{
assert other.column().name.equals(column().name);
return column().cellPathComparator().compare(comparisonPath(), other.comparisonPath());
}
/**
* Given a path, return -1 if the path is before anything selected by this subselection, 0 if it is selected by this
* subselection and 1 if the path is after anything selected by this subselection.
*/
public abstract int compareInclusionOf(CellPath path);
private static class Slice extends ColumnSubselection
{
@ -93,20 +103,20 @@ public abstract class ColumnSubselection
return Kind.SLICE;
}
public CellPath minIncludedPath()
public CellPath comparisonPath()
{
return from;
}
public CellPath maxIncludedPath()
{
return to;
}
public boolean includes(CellPath path)
public int compareInclusionOf(CellPath path)
{
Comparator<CellPath> cmp = column.cellPathComparator();
return cmp.compare(from, path) <= 0 && cmp.compare(path, to) <= 0;
if (cmp.compare(path, from) < 0)
return -1;
else if (cmp.compare(to, path) < 0)
return 1;
else
return 0;
}
@Override
@ -133,20 +143,14 @@ public abstract class ColumnSubselection
return Kind.ELEMENT;
}
public CellPath minIncludedPath()
public CellPath comparisonPath()
{
return element;
}
public CellPath maxIncludedPath()
public int compareInclusionOf(CellPath path)
{
return element;
}
public boolean includes(CellPath path)
{
Comparator<CellPath> cmp = column.cellPathComparator();
return cmp.compare(element, path) == 0;
return column.cellPathComparator().compare(path, element);
}
@Override
@ -180,7 +184,7 @@ public abstract class ColumnSubselection
throw new AssertionError();
}
public ColumnSubselection deserialize(DataInput in, int version, CFMetaData metadata) throws IOException
public ColumnSubselection deserialize(DataInputPlus in, int version, CFMetaData metadata) throws IOException
{
ByteBuffer name = ByteBufferUtil.readWithShortLength(in);
ColumnDefinition column = metadata.getColumnDefinition(name);

View File

@ -115,8 +115,7 @@ public abstract class DataLimits
* The max number of results this limits enforces.
* <p>
* Note that the actual definition of "results" depends a bit: for CQL, it's always rows, but for
* thrift, it means cells. The {@link #countsCells} allows to distinguish between the two cases if
* needed.
* thrift, it means cells.
*
* @return the maximum number of results this limits enforces.
*/
@ -124,8 +123,6 @@ public abstract class DataLimits
public abstract int perPartitionCount();
public abstract boolean countsCells();
public UnfilteredPartitionIterator filter(UnfilteredPartitionIterator iter, int nowInSec)
{
return new CountingUnfilteredPartitionIterator(iter, newCounter(nowInSec, false));
@ -269,11 +266,6 @@ public abstract class DataLimits
return perPartitionLimit;
}
public boolean countsCells()
{
return false;
}
public float estimateTotalResults(ColumnFamilyStore cfs)
{
// TODO: we should start storing stats on the number of rows (instead of the number of cells, which
@ -353,7 +345,7 @@ public abstract class DataLimits
{
sb.append("LIMIT ").append(rowLimit);
if (perPartitionLimit != Integer.MAX_VALUE)
sb.append(" ");
sb.append(' ');
}
if (perPartitionLimit != Integer.MAX_VALUE)
@ -511,11 +503,6 @@ public abstract class DataLimits
return cellPerPartitionLimit;
}
public boolean countsCells()
{
return true;
}
public float estimateTotalResults(ColumnFamilyStore cfs)
{
// remember that getMeansColumns returns a number of cells: we should clean nomenclature
@ -572,7 +559,7 @@ public abstract class DataLimits
public void newRow(Row row)
{
for (Cell cell : row)
for (Cell cell : row.cells())
{
if (assumeLiveData || cell.isLive(nowInSec))
{

View File

@ -137,11 +137,11 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
if (metadata.isCompound())
{
List<ByteBuffer> values = CompositeType.splitName(name);
return new SimpleClustering(values.toArray(new ByteBuffer[metadata.comparator.size()]));
return new Clustering(values.toArray(new ByteBuffer[metadata.comparator.size()]));
}
else
{
return new SimpleClustering(name);
return new Clustering(name);
}
}
@ -165,28 +165,18 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
super(expressions);
}
public UnfilteredPartitionIterator filter(UnfilteredPartitionIterator iter, final int nowInSec)
public UnfilteredPartitionIterator filter(UnfilteredPartitionIterator iter, int nowInSec)
{
if (expressions.isEmpty())
return iter;
return new WrappingUnfilteredPartitionIterator(iter)
return new AlteringUnfilteredPartitionIterator(iter)
{
@Override
public UnfilteredRowIterator computeNext(final UnfilteredRowIterator iter)
protected Row computeNext(DecoratedKey partitionKey, Row row)
{
return new FilteringRowIterator(iter)
{
// We filter tombstones when passing the row to isSatisfiedBy so that the method doesn't have to bother with them.
// (we should however not filter them in the output of the method, hence it's not used as row filter for the
// FilteringRowIterator)
private final TombstoneFilteringRow filter = new TombstoneFilteringRow(nowInSec);
protected boolean includeRow(Row row)
{
return CQLFilter.this.isSatisfiedBy(iter.partitionKey(), filter.setTo(row));
}
};
// We filter tombstones when passing the row to isSatisfiedBy so that the method doesn't have to bother with them.
Row purged = row.purge(DeletionPurger.PURGE_ALL, nowInSec);
return purged != null && CQLFilter.this.isSatisfiedBy(partitionKey, purged) ? row : null;
}
};
}
@ -515,10 +505,9 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
CollectionType<?> type = (CollectionType<?>)column.type;
if (column.isComplex())
{
Iterator<Cell> iter = row.getCells(column);
while (iter.hasNext())
ComplexColumnData complexData = row.getComplexColumnData(column);
for (Cell cell : complexData)
{
Cell cell = iter.next();
if (type.kind == CollectionType.Kind.SET)
{
if (type.nameComparator().compare(cell.path().get(0), value) == 0)
@ -720,7 +709,7 @@ public abstract class RowFilter implements Iterable<RowFilter.Expression>
// In thrift, we actually allow expression on non-defined columns for the sake of filtering. To accomodate
// this we create a "fake" definition. This is messy but it works so is probably good enough.
return ColumnDefinition.regularDef(metadata, name, metadata.compactValueColumn().type, null);
return ColumnDefinition.regularDef(metadata, name, metadata.compactValueColumn().type);
}
public boolean isSatisfiedBy(DecoratedKey partitionKey, Row row)

View File

@ -108,17 +108,16 @@ public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSec
public void deleteForCleanup(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup, int nowInSec)
{
delete(rowKey, clustering, cell.value(), cell.path(), new SimpleDeletionTime(cell.livenessInfo().timestamp(), nowInSec), opGroup);
delete(rowKey, clustering, cell.value(), cell.path(), new DeletionTime(cell.timestamp(), nowInSec), opGroup);
}
public void delete(ByteBuffer rowKey, Clustering clustering, ByteBuffer cellValue, CellPath path, DeletionTime deletion, OpOrder.Group opGroup)
{
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey, clustering, cellValue, path));
PartitionUpdate upd = new PartitionUpdate(indexCfs.metadata, valueKey, PartitionColumns.NONE, 1);
Row.Writer writer = upd.writer();
Rows.writeClustering(makeIndexClustering(rowKey, clustering, path), writer);
writer.writeRowDeletion(deletion);
writer.endOfRow();
Row row = ArrayBackedRow.emptyDeletedRow(makeIndexClustering(rowKey, clustering, path), deletion);
PartitionUpdate upd = PartitionUpdate.singleRowUpdate(indexCfs.metadata, valueKey, row);
indexCfs.apply(upd, SecondaryIndexManager.nullUpdater, opGroup, null);
if (logger.isDebugEnabled())
logger.debug("removed index entry for cleaned-up value {}:{}", valueKey, upd);
@ -126,18 +125,16 @@ public abstract class AbstractSimplePerColumnSecondaryIndex extends PerColumnSec
public void insert(ByteBuffer rowKey, Clustering clustering, Cell cell, OpOrder.Group opGroup)
{
insert(rowKey, clustering, cell, cell.livenessInfo(), opGroup);
insert(rowKey, clustering, cell, LivenessInfo.create(cell.timestamp(), cell.ttl(), cell.localDeletionTime()), opGroup);
}
public void insert(ByteBuffer rowKey, Clustering clustering, Cell cell, LivenessInfo info, OpOrder.Group opGroup)
{
DecoratedKey valueKey = getIndexKeyFor(getIndexedValue(rowKey, clustering, cell));
PartitionUpdate upd = new PartitionUpdate(indexCfs.metadata, valueKey, PartitionColumns.NONE, 1);
Row.Writer writer = upd.writer();
Rows.writeClustering(makeIndexClustering(rowKey, clustering, cell), writer);
writer.writePartitionKeyLivenessInfo(info);
writer.endOfRow();
Row row = ArrayBackedRow.noCellLiveRow(makeIndexClustering(rowKey, clustering, cell), info);
PartitionUpdate upd = PartitionUpdate.singleRowUpdate(indexCfs.metadata, valueKey, row);
if (logger.isDebugEnabled())
logger.debug("applying index row {} in {}", indexCfs.metadata.getKeyValidator().getString(valueKey.getKey()), upd);

View File

@ -18,6 +18,7 @@
package org.apache.cassandra.db.index;
import java.nio.ByteBuffer;
import java.util.Iterator;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
@ -87,17 +88,18 @@ public abstract class PerColumnSecondaryIndex extends SecondaryIndex
long timestamp = row.primaryKeyLivenessInfo().timestamp();
int ttl = row.primaryKeyLivenessInfo().ttl();
for (Cell cell : row)
for (Cell cell : row.cells())
{
if (cell.isLive(nowInSec) && cell.livenessInfo().timestamp() > timestamp)
if (cell.isLive(nowInSec) && cell.timestamp() > timestamp)
{
timestamp = cell.livenessInfo().timestamp();
ttl = cell.livenessInfo().ttl();
timestamp = cell.timestamp();
ttl = cell.ttl();
}
}
maybeIndex(key.getKey(), clustering, timestamp, ttl, opGroup, nowInSec);
}
for (Cell cell : row)
for (Cell cell : row.cells())
{
if (!indexes(cell.column()))
continue;

View File

@ -18,15 +18,7 @@
package org.apache.cassandra.db.index;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
@ -469,7 +461,8 @@ public class SecondaryIndexManager
if (!row.deletion().isLive())
for (PerColumnSecondaryIndex index : indexes)
index.maybeDelete(key, clustering, row.deletion(), opGroup);
for (Cell cell : row)
for (Cell cell : row.cells())
{
for (PerColumnSecondaryIndex index : indexes)
{
@ -636,8 +629,7 @@ public class SecondaryIndexManager
// Completely identical cells (including expiring columns with
// identical ttl & localExpirationTime) will not get this far due
// to the oldCell.equals(newCell) in StandardUpdater.update
return !oldCell.value().equals(newCell.value())
|| oldCell.livenessInfo().timestamp() != newCell.livenessInfo().timestamp();
return !oldCell.value().equals(newCell.value()) || oldCell.timestamp() != newCell.timestamp();
}
private Set<String> filterByColumn(Set<String> idxNames)

View File

@ -112,7 +112,7 @@ public abstract class SecondaryIndexSearcher
NavigableSet<Clustering> requested = ((ClusteringIndexNamesFilter)filter).requestedRows();
BTreeSet.Builder<Clustering> clusterings = BTreeSet.builder(index.getIndexComparator());
for (Clustering c : requested)
clusterings.add(index.makeIndexClustering(pk, c, (Cell)null).takeAlias());
clusterings.add(index.makeIndexClustering(pk, c, (Cell)null));
return new ClusteringIndexNamesFilter(clusterings.build(), filter.isReversed());
}
else

View File

@ -112,11 +112,8 @@ public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIn
public void delete(IndexedEntry entry, OpOrder.Group opGroup, int nowInSec)
{
PartitionUpdate upd = new PartitionUpdate(indexCfs.metadata, entry.indexValue, PartitionColumns.NONE, 1);
Row.Writer writer = upd.writer();
Rows.writeClustering(entry.indexClustering, writer);
writer.writeRowDeletion(new SimpleDeletionTime(entry.timestamp, nowInSec));
writer.endOfRow();
Row row = ArrayBackedRow.emptyDeletedRow(entry.indexClustering, new DeletionTime(entry.timestamp, nowInSec));
PartitionUpdate upd = PartitionUpdate.singleRowUpdate(indexCfs.metadata, entry.indexValue, row);
indexCfs.apply(upd, SecondaryIndexManager.nullUpdater, opGroup, null);
if (logger.isDebugEnabled())
@ -159,10 +156,10 @@ public abstract class CompositesIndex extends AbstractSimplePerColumnSecondaryIn
public IndexedEntry(DecoratedKey indexValue, Clustering indexClustering, long timestamp, ByteBuffer indexedKey, Clustering indexedEntryClustering)
{
this.indexValue = indexValue;
this.indexClustering = indexClustering.takeAlias();
this.indexClustering = indexClustering;
this.timestamp = timestamp;
this.indexedKey = indexedKey;
this.indexedEntryClustering = indexedEntryClustering.takeAlias();
this.indexedEntryClustering = indexedEntryClustering;
}
}
}

View File

@ -118,7 +118,7 @@ public class CompositesIndexOnClusteringKey extends CompositesIndex
public void maybeIndex(ByteBuffer partitionKey, Clustering clustering, long timestamp, int ttl, OpOrder.Group opGroup, int nowInSec)
{
if (clustering != Clustering.STATIC_CLUSTERING && clustering.get(columnDef.position()) != null)
insert(partitionKey, clustering, null, SimpleLivenessInfo.forUpdate(timestamp, ttl, nowInSec, indexCfs.metadata), opGroup);
insert(partitionKey, clustering, null, LivenessInfo.create(indexCfs.metadata, timestamp, ttl, nowInSec), opGroup);
}
@Override

View File

@ -90,10 +90,9 @@ public class CompositesIndexOnCollectionValue extends CompositesIndex
public boolean isStale(Row data, ByteBuffer indexValue, int nowInSec)
{
Iterator<Cell> iter = data.getCells(columnDef);
while (iter.hasNext())
ComplexColumnData complexData = data.getComplexColumnData(columnDef);
for (Cell cell : complexData)
{
Cell cell = iter.next();
if (cell.isLive(nowInSec) && ((CollectionType) columnDef.type).valueComparator().compare(indexValue, cell.value()) == 0)
return false;
}

View File

@ -93,7 +93,7 @@ public class CompositesIndexOnPartitionKey extends CompositesIndex
@Override
public void maybeIndex(ByteBuffer partitionKey, Clustering clustering, long timestamp, int ttl, OpOrder.Group opGroup, int nowInSec)
{
insert(partitionKey, clustering, null, SimpleLivenessInfo.forUpdate(timestamp, ttl, nowInSec, indexCfs.metadata), opGroup);
insert(partitionKey, clustering, null, LivenessInfo.create(indexCfs.metadata, timestamp, ttl, nowInSec), opGroup);
}
@Override

View File

@ -171,49 +171,20 @@ public class CompositesSearcher extends SecondaryIndexSearcher
final OpOrder.Group writeOp,
final int nowInSec)
{
return new WrappingUnfilteredRowIterator(dataIter)
return new AlteringUnfilteredRowIterator(dataIter)
{
private int entriesIdx;
private Unfiltered next;
@Override
public boolean hasNext()
protected Row computeNext(Row row)
{
return prepareNext();
}
CompositesIndex.IndexedEntry entry = findEntry(row.clustering(), writeOp, nowInSec);
if (!index.isStale(row, indexValue, nowInSec))
return row;
@Override
public Unfiltered next()
{
if (next == null)
prepareNext();
Unfiltered toReturn = next;
next = null;
return toReturn;
}
private boolean prepareNext()
{
if (next != null)
return true;
while (super.hasNext())
{
next = super.next();
if (next.kind() != Unfiltered.Kind.ROW)
return true;
Row row = (Row)next;
CompositesIndex.IndexedEntry entry = findEntry(row.clustering(), writeOp, nowInSec);
if (!index.isStale(row, indexValue, nowInSec))
return true;
// The entry is stale: delete the entry and ignore otherwise
index.delete(entry, writeOp, nowInSec);
next = null;
}
return false;
// The entry is stale: delete the entry and ignore otherwise
index.delete(entry, writeOp, nowInSec);
return null;
}
private CompositesIndex.IndexedEntry findEntry(Clustering clustering, OpOrder.Group writeOp, int nowInSec)

View File

@ -138,7 +138,7 @@ public class KeysSearcher extends SecondaryIndexSearcher
// is the indexed name. Ans so we need to materialize the partition.
ArrayBackedPartition result = ArrayBackedPartition.create(iterator);
iterator.close();
Row data = result.getRow(new SimpleClustering(index.indexedColumn().name.bytes));
Row data = result.getRow(new Clustering(index.indexedColumn().name.bytes));
Cell cell = data == null ? null : data.getCell(baseCfs.metadata.compactValueColumn());
return deleteIfStale(iterator.partitionKey(), cell, index, indexHit, indexedValue, writeOp, nowInSec)
? null
@ -173,10 +173,10 @@ public class KeysSearcher extends SecondaryIndexSearcher
{
// Index is stale, remove the index entry and ignore
index.delete(partitionKey.getKey(),
new SimpleClustering(index.indexedColumn().name.bytes),
new Clustering(index.indexedColumn().name.bytes),
indexedValue,
null,
new SimpleDeletionTime(indexHit.primaryKeyLivenessInfo().timestamp(), nowInSec),
new DeletionTime(indexHit.primaryKeyLivenessInfo().timestamp(), nowInSec),
writeOp);
return true;
}

View File

@ -17,7 +17,6 @@
*/
package org.apache.cassandra.db.marshal;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -36,6 +35,7 @@ import org.apache.cassandra.serializers.MarshalException;
import org.github.jamm.Unmetered;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.ByteBufferUtil;
@ -325,7 +325,7 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>
if (valueLengthIfFixed() >= 0)
out.write(value);
else
ByteBufferUtil.writeWithLength(value, out);
ByteBufferUtil.writeWithVIntLength(value, out);
}
public long writtenLength(ByteBuffer value)
@ -333,25 +333,25 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>
assert value.hasRemaining();
return valueLengthIfFixed() >= 0
? value.remaining()
: TypeSizes.sizeofWithLength(value);
: TypeSizes.sizeofWithVIntLength(value);
}
public ByteBuffer readValue(DataInput in) throws IOException
public ByteBuffer readValue(DataInputPlus in) throws IOException
{
int length = valueLengthIfFixed();
if (length >= 0)
return ByteBufferUtil.read(in, length);
else
return ByteBufferUtil.readWithLength(in);
return ByteBufferUtil.readWithVIntLength(in);
}
public void skipValue(DataInput in) throws IOException
public void skipValue(DataInputPlus in) throws IOException
{
int length = valueLengthIfFixed();
if (length < 0)
length = in.readInt();
FileUtils.skipBytesFully(in, length);
if (length >= 0)
FileUtils.skipBytesFully(in, length);
else
ByteBufferUtil.skipWithVIntLength(in);
}
/**

View File

@ -18,7 +18,6 @@
package org.apache.cassandra.db.marshal;
import java.nio.ByteBuffer;
import java.io.DataInput;
import java.io.IOException;
import java.util.List;
import java.util.Iterator;
@ -34,6 +33,7 @@ import org.apache.cassandra.cql3.Sets;
import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.serializers.CollectionSerializer;
@ -236,23 +236,22 @@ public abstract class CollectionType<T> extends AbstractType<T>
{
public void serialize(CellPath path, DataOutputPlus out) throws IOException
{
ByteBufferUtil.writeWithLength(path.get(0), out);
ByteBufferUtil.writeWithVIntLength(path.get(0), out);
}
public CellPath deserialize(DataInput in) throws IOException
public CellPath deserialize(DataInputPlus in) throws IOException
{
return CellPath.create(ByteBufferUtil.readWithLength(in));
return CellPath.create(ByteBufferUtil.readWithVIntLength(in));
}
public long serializedSize(CellPath path)
{
return TypeSizes.sizeofWithLength(path.get(0));
return ByteBufferUtil.serializedSizeWithVIntLength(path.get(0));
}
public void skip(DataInput in) throws IOException
public void skip(DataInputPlus in) throws IOException
{
int length = in.readInt();
FileUtils.skipBytesFully(in, length);
ByteBufferUtil.skipWithVIntLength(in);
}
}
}

View File

@ -1,850 +0,0 @@
/*
* 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.partitions;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.UnmodifiableIterator;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.utils.SearchIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract common class for all non-thread safe Partition implementations.
*/
public abstract class AbstractPartitionData implements Partition, Iterable<Row>
{
private static final Logger logger = LoggerFactory.getLogger(AbstractPartitionData.class);
protected final CFMetaData metadata;
protected final DecoratedKey key;
protected final DeletionInfo deletionInfo;
protected final PartitionColumns columns;
protected Row staticRow;
protected int rows;
// The values for the clustering columns of the rows contained in this partition object. If
// clusteringSize is the size of the clustering comparator for this table, clusterings has size
// clusteringSize * rows where rows is the number of rows stored, and row i has it's clustering
// column values at indexes [clusteringSize * i, clusteringSize * (i + 1)).
protected ByteBuffer[] clusterings;
// The partition key column liveness infos for the rows of this partition (row i has its liveness info at index i).
protected final LivenessInfoArray livenessInfos;
// The row deletion for the rows of this partition (row i has its row deletion at index i).
protected final DeletionTimeArray deletions;
// The row data (cells data + complex deletions for complex columns) for the rows contained in this partition.
protected final RowDataBlock data;
// Stats over the rows stored in this partition.
private final RowStats.Collector statsCollector = new RowStats.Collector();
// The maximum timestamp for any data contained in this partition.
protected long maxTimestamp = Long.MIN_VALUE;
private AbstractPartitionData(CFMetaData metadata,
DecoratedKey key,
DeletionInfo deletionInfo,
ByteBuffer[] clusterings,
LivenessInfoArray livenessInfos,
DeletionTimeArray deletions,
PartitionColumns columns,
RowDataBlock data)
{
this.metadata = metadata;
this.key = key;
this.deletionInfo = deletionInfo;
this.clusterings = clusterings;
this.livenessInfos = livenessInfos;
this.deletions = deletions;
this.columns = columns;
this.data = data;
collectStats(deletionInfo.getPartitionDeletion());
Iterator<RangeTombstone> iter = deletionInfo.rangeIterator(false);
while (iter.hasNext())
collectStats(iter.next().deletionTime());
}
protected AbstractPartitionData(CFMetaData metadata,
DecoratedKey key,
DeletionInfo deletionInfo,
PartitionColumns columns,
RowDataBlock data,
int initialRowCapacity)
{
this(metadata,
key,
deletionInfo,
new ByteBuffer[initialRowCapacity * metadata.clusteringColumns().size()],
new LivenessInfoArray(initialRowCapacity),
new DeletionTimeArray(initialRowCapacity),
columns,
data);
}
protected AbstractPartitionData(CFMetaData metadata,
DecoratedKey key,
DeletionTime partitionDeletion,
PartitionColumns columns,
int initialRowCapacity,
boolean sortable)
{
this(metadata,
key,
new DeletionInfo(partitionDeletion.takeAlias()),
columns,
new RowDataBlock(columns.regulars, initialRowCapacity, sortable, metadata.isCounter()),
initialRowCapacity);
}
private void collectStats(DeletionTime dt)
{
statsCollector.updateDeletionTime(dt);
maxTimestamp = Math.max(maxTimestamp, dt.markedForDeleteAt());
}
private void collectStats(LivenessInfo info)
{
statsCollector.updateTimestamp(info.timestamp());
statsCollector.updateTTL(info.ttl());
statsCollector.updateLocalDeletionTime(info.localDeletionTime());
maxTimestamp = Math.max(maxTimestamp, info.timestamp());
}
public CFMetaData metadata()
{
return metadata;
}
public DecoratedKey partitionKey()
{
return key;
}
public DeletionTime partitionLevelDeletion()
{
return deletionInfo.getPartitionDeletion();
}
public PartitionColumns columns()
{
return columns;
}
public Row staticRow()
{
return staticRow == null ? Rows.EMPTY_STATIC_ROW : staticRow;
}
public RowStats stats()
{
return statsCollector.get();
}
/**
* The deletion info for the partition update.
*
* <b>warning:</b> the returned object should be used in a read-only fashion. In particular,
* it should not be used to add new range tombstones to this deletion. For that,
* {@link addRangeTombstone} should be used instead. The reason being that adding directly to
* the returned object would bypass some stats collection that {@code addRangeTombstone} does.
*
* @return the deletion info for the partition update for use as read-only.
*/
public DeletionInfo deletionInfo()
{
// TODO: it is a tad fragile that deletionInfo can be but shouldn't be modified. We
// could add the option of providing a read-only view of a DeletionInfo instead.
return deletionInfo;
}
public void addPartitionDeletion(DeletionTime deletionTime)
{
collectStats(deletionTime);
deletionInfo.add(deletionTime);
}
public void addRangeTombstone(Slice deletedSlice, DeletionTime deletion)
{
addRangeTombstone(new RangeTombstone(deletedSlice, deletion.takeAlias()));
}
public void addRangeTombstone(RangeTombstone range)
{
collectStats(range.deletionTime());
deletionInfo.add(range, metadata.comparator);
}
/**
* Swap row i and j.
*
* This is only used when we need to reorder rows because those were not added in clustering order,
* which happens in {@link PartitionUpdate#sort} and {@link ArrayBackedPartition#create}. This method
* is public only because {@code PartitionUpdate} needs to implement {@link Sorting.Sortable}, but
* it should really only be used by subclasses (and with care) in practice.
*/
public void swap(int i, int j)
{
int cs = metadata.clusteringColumns().size();
for (int k = 0; k < cs; k++)
{
ByteBuffer tmp = clusterings[j * cs + k];
clusterings[j * cs + k] = clusterings[i * cs + k];
clusterings[i * cs + k] = tmp;
}
livenessInfos.swap(i, j);
deletions.swap(i, j);
data.swap(i, j);
}
protected void merge(int i, int j, int nowInSec)
{
data.merge(i, j, nowInSec);
if (livenessInfos.timestamp(i) > livenessInfos.timestamp(j))
livenessInfos.move(i, j);
if (deletions.supersedes(i, j))
deletions.move(i, j);
}
protected void move(int i, int j)
{
int cs = metadata.clusteringColumns().size();
for (int k = 0; k < cs; k++)
clusterings[j * cs + k] = clusterings[i * cs + k];
data.move(i, j);
livenessInfos.move(i, j);
deletions.move(i, j);
}
public int rowCount()
{
return rows;
}
public boolean isEmpty()
{
return deletionInfo.isLive() && rows == 0 && staticRow().isEmpty();
}
protected void clear()
{
rows = 0;
Arrays.fill(clusterings, null);
livenessInfos.clear();
deletions.clear();
data.clear();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
CFMetaData metadata = metadata();
sb.append(String.format("Partition[%s.%s] key=%s columns=%s deletion=%s",
metadata.ksName,
metadata.cfName,
metadata.getKeyValidator().getString(partitionKey().getKey()),
columns(),
deletionInfo));
if (staticRow() != Rows.EMPTY_STATIC_ROW)
sb.append("\n ").append(staticRow().toString(metadata, true));
// We use createRowIterator() directly instead of iterator() because that avoids
// sorting for PartitionUpdate (which inherit this method) and that is useful because
// 1) it can help with debugging and 2) we can't write after sorting but we want to
// be able to print an update while we build it (again for debugging)
Iterator<Row> iterator = createRowIterator(null, false);
while (iterator.hasNext())
sb.append("\n ").append(iterator.next().toString(metadata, true));
return sb.toString();
}
protected void reverse()
{
for (int i = 0; i < rows / 2; i++)
swap(i, rows - 1 - i);
}
public Row getRow(Clustering clustering)
{
Row row = searchIterator(ColumnFilter.selection(columns()), false).next(clustering);
// Note that for statics, this will never return null, this will return an empty row. However,
// it's more consistent for this method to return null if we don't really have a static row.
return row == null || (clustering == Clustering.STATIC_CLUSTERING && row.isEmpty()) ? null : row;
}
/**
* Returns an iterator that iterators over the rows of this update in clustering order.
*
* @return an iterator over the rows of this update.
*/
public Iterator<Row> iterator()
{
return createRowIterator(null, false);
}
public SearchIterator<Clustering, Row> searchIterator(final ColumnFilter columns, boolean reversed)
{
final RowIterator iter = createRowIterator(columns, reversed);
return new SearchIterator<Clustering, Row>()
{
public boolean hasNext()
{
return iter.hasNext();
}
public Row next(Clustering key)
{
if (key == Clustering.STATIC_CLUSTERING)
{
if (columns.fetchedColumns().statics.isEmpty() || staticRow().isEmpty())
return Rows.EMPTY_STATIC_ROW;
return FilteringRow.columnsFilteringRow(columns).setTo(staticRow());
}
return iter.seekTo(key) ? iter.next() : null;
}
};
}
public UnfilteredRowIterator unfilteredIterator()
{
return unfilteredIterator(ColumnFilter.selection(columns()), Slices.ALL, false);
}
public UnfilteredRowIterator unfilteredIterator(ColumnFilter columns, Slices slices, boolean reversed)
{
return slices.makeSliceIterator(sliceableUnfilteredIterator(columns, reversed));
}
protected SliceableUnfilteredRowIterator sliceableUnfilteredIterator()
{
return sliceableUnfilteredIterator(ColumnFilter.selection(columns()), false);
}
protected SliceableUnfilteredRowIterator sliceableUnfilteredIterator(final ColumnFilter selection, final boolean reversed)
{
return new AbstractSliceableIterator(this, selection.fetchedColumns(), reversed)
{
private final RowIterator rowIterator = createRowIterator(selection, reversed);
private RowAndTombstoneMergeIterator mergeIterator = new RowAndTombstoneMergeIterator(metadata.comparator, reversed);
protected Unfiltered computeNext()
{
if (!mergeIterator.isSet())
mergeIterator.setTo(rowIterator, deletionInfo.rangeIterator(reversed));
return mergeIterator.hasNext() ? mergeIterator.next() : endOfData();
}
public Iterator<Unfiltered> slice(Slice slice)
{
return mergeIterator.setTo(rowIterator.slice(slice), deletionInfo.rangeIterator(slice, reversed));
}
};
}
private RowIterator createRowIterator(ColumnFilter columns, boolean reversed)
{
return reversed ? new ReverseRowIterator(columns) : new ForwardRowIterator(columns);
}
/**
* An iterator over the rows of this partition that reuse the same row object.
*/
private abstract class RowIterator extends UnmodifiableIterator<Row>
{
protected final InternalReusableClustering clustering = new InternalReusableClustering();
protected final InternalReusableRow reusableRow;
protected final FilteringRow filter;
protected int next;
protected RowIterator(final ColumnFilter columns)
{
this.reusableRow = new InternalReusableRow(clustering);
this.filter = columns == null ? null : FilteringRow.columnsFilteringRow(columns);
}
/*
* Move the iterator so that row {@code name} is returned next by {@code next} if that
* row exists. Otherwise the first row sorting after {@code name} will be returned.
* Returns whether {@code name} was found or not.
*/
public abstract boolean seekTo(Clustering name);
public abstract Iterator<Row> slice(Slice slice);
protected Row setRowTo(int row)
{
reusableRow.setTo(row);
return filter == null ? reusableRow : filter.setTo(reusableRow);
}
/**
* Simple binary search.
*/
protected int binarySearch(ClusteringPrefix name, int fromIndex, int toIndex)
{
int low = fromIndex;
int mid = toIndex;
int high = mid - 1;
int result = -1;
while (low <= high)
{
mid = (low + high) >> 1;
if ((result = metadata.comparator.compare(name, clustering.setTo(mid))) > 0)
low = mid + 1;
else if (result == 0)
return mid;
else
high = mid - 1;
}
return -mid - (result < 0 ? 1 : 2);
}
}
private class ForwardRowIterator extends RowIterator
{
private ForwardRowIterator(ColumnFilter columns)
{
super(columns);
this.next = 0;
}
public boolean hasNext()
{
return next < rows;
}
public Row next()
{
return setRowTo(next++);
}
public boolean seekTo(Clustering name)
{
if (next >= rows)
return false;
int idx = binarySearch(name, next, rows);
next = idx >= 0 ? idx : -idx - 1;
return idx >= 0;
}
public Iterator<Row> slice(Slice slice)
{
int sidx = binarySearch(slice.start(), next, rows);
final int start = sidx >= 0 ? sidx : -sidx - 1;
if (start >= rows)
return Collections.emptyIterator();
int eidx = binarySearch(slice.end(), start, rows);
// The insertion point is the first element greater than slice.end(), so we want the previous index
final int end = eidx >= 0 ? eidx : -eidx - 2;
// Remember the end to speed up potential further slice search
next = end;
if (start > end)
return Collections.emptyIterator();
return new AbstractIterator<Row>()
{
private int i = start;
protected Row computeNext()
{
if (i >= rows || i > end)
return endOfData();
return setRowTo(i++);
}
};
}
}
private class ReverseRowIterator extends RowIterator
{
private ReverseRowIterator(ColumnFilter columns)
{
super(columns);
this.next = rows - 1;
}
public boolean hasNext()
{
return next >= 0;
}
public Row next()
{
return setRowTo(next--);
}
public boolean seekTo(Clustering name)
{
// We only use that method with forward iterators.
throw new UnsupportedOperationException();
}
public Iterator<Row> slice(Slice slice)
{
int sidx = binarySearch(slice.end(), 0, next + 1);
// The insertion point is the first element greater than slice.end(), so we want the previous index
final int start = sidx >= 0 ? sidx : -sidx - 2;
if (start < 0)
return Collections.emptyIterator();
int eidx = binarySearch(slice.start(), 0, start + 1);
final int end = eidx >= 0 ? eidx : -eidx - 1;
// Remember the end to speed up potential further slice search
next = end;
if (start < end)
return Collections.emptyIterator();
return new AbstractIterator<Row>()
{
private int i = start;
protected Row computeNext()
{
if (i < 0 || i < end)
return endOfData();
return setRowTo(i--);
}
};
}
}
/**
* A reusable view over the clustering of this partition.
*/
protected class InternalReusableClustering extends Clustering
{
final int size = metadata.clusteringColumns().size();
private int base;
public int size()
{
return size;
}
public Clustering setTo(int row)
{
base = row * size;
return this;
}
public ByteBuffer get(int i)
{
return clusterings[base + i];
}
public ByteBuffer[] getRawValues()
{
ByteBuffer[] values = new ByteBuffer[size];
for (int i = 0; i < size; i++)
values[i] = get(i);
return values;
}
};
/**
* A reusable view over the rows of this partition.
*/
protected class InternalReusableRow extends AbstractReusableRow
{
private final LivenessInfoArray.Cursor liveness = new LivenessInfoArray.Cursor();
private final DeletionTimeArray.Cursor deletion = new DeletionTimeArray.Cursor();
private final InternalReusableClustering clustering;
private int row;
public InternalReusableRow()
{
this(new InternalReusableClustering());
}
public InternalReusableRow(InternalReusableClustering clustering)
{
this.clustering = clustering;
}
protected RowDataBlock data()
{
return data;
}
public Row setTo(int row)
{
this.clustering.setTo(row);
this.liveness.setTo(livenessInfos, row);
this.deletion.setTo(deletions, row);
this.row = row;
return this;
}
protected int row()
{
return row;
}
public Clustering clustering()
{
return clustering;
}
public LivenessInfo primaryKeyLivenessInfo()
{
return liveness;
}
public DeletionTime deletion()
{
return deletion;
}
};
private static abstract class AbstractSliceableIterator extends AbstractUnfilteredRowIterator implements SliceableUnfilteredRowIterator
{
private AbstractSliceableIterator(AbstractPartitionData data, PartitionColumns columns, boolean isReverseOrder)
{
super(data.metadata, data.key, data.partitionLevelDeletion(), columns, data.staticRow(), isReverseOrder, data.stats());
}
}
/**
* A row writer to add rows to this partition.
*/
protected class Writer extends RowDataBlock.Writer
{
private int clusteringBase;
private int simpleColumnsSetInRow;
private final Set<ColumnDefinition> complexColumnsSetInRow = new HashSet<>();
public Writer(boolean inOrderCells)
{
super(data, inOrderCells);
}
public void writeClusteringValue(ByteBuffer value)
{
ensureCapacity(row);
clusterings[clusteringBase++] = value;
}
public void writePartitionKeyLivenessInfo(LivenessInfo info)
{
ensureCapacity(row);
livenessInfos.set(row, info);
collectStats(info);
}
public void writeRowDeletion(DeletionTime deletion)
{
ensureCapacity(row);
if (!deletion.isLive())
deletions.set(row, deletion);
collectStats(deletion);
}
@Override
public void writeCell(ColumnDefinition column, boolean isCounter, ByteBuffer value, LivenessInfo info, CellPath path)
{
ensureCapacity(row);
collectStats(info);
if (column.isComplex())
complexColumnsSetInRow.add(column);
else
++simpleColumnsSetInRow;
super.writeCell(column, isCounter, value, info, path);
}
@Override
public void writeComplexDeletion(ColumnDefinition c, DeletionTime complexDeletion)
{
ensureCapacity(row);
collectStats(complexDeletion);
super.writeComplexDeletion(c, complexDeletion);
}
@Override
public void endOfRow()
{
super.endOfRow();
++rows;
statsCollector.updateColumnSetPerRow(simpleColumnsSetInRow + complexColumnsSetInRow.size());
simpleColumnsSetInRow = 0;
complexColumnsSetInRow.clear();
}
public int currentRow()
{
return row;
}
private void ensureCapacity(int rowToSet)
{
int originalCapacity = livenessInfos.size();
if (rowToSet < originalCapacity)
return;
int newCapacity = RowDataBlock.computeNewCapacity(originalCapacity, rowToSet);
int clusteringSize = metadata.clusteringColumns().size();
clusterings = Arrays.copyOf(clusterings, newCapacity * clusteringSize);
livenessInfos.resize(newCapacity);
deletions.resize(newCapacity);
}
@Override
public Writer reset()
{
super.reset();
clusteringBase = 0;
simpleColumnsSetInRow = 0;
complexColumnsSetInRow.clear();
return this;
}
}
/**
* A range tombstone marker writer to add range tombstone markers to this partition.
*/
protected class RangeTombstoneCollector implements RangeTombstoneMarker.Writer
{
private final boolean reversed;
private final ByteBuffer[] nextValues = new ByteBuffer[metadata().comparator.size()];
private int size;
private RangeTombstone.Bound.Kind nextKind;
private Slice.Bound openBound;
private DeletionTime openDeletion;
public RangeTombstoneCollector(boolean reversed)
{
this.reversed = reversed;
}
public void writeClusteringValue(ByteBuffer value)
{
nextValues[size++] = value;
}
public void writeBoundKind(RangeTombstone.Bound.Kind kind)
{
nextKind = kind;
}
private ByteBuffer[] getValues()
{
return Arrays.copyOfRange(nextValues, 0, size);
}
private void open(RangeTombstone.Bound.Kind kind, DeletionTime deletion)
{
openBound = Slice.Bound.create(kind, getValues());
openDeletion = deletion.takeAlias();
}
private void close(RangeTombstone.Bound.Kind kind, DeletionTime deletion)
{
assert deletion.equals(openDeletion) : "Expected " + openDeletion + " but was " + deletion;
Slice.Bound closeBound = Slice.Bound.create(kind, getValues());
Slice slice = reversed
? Slice.make(closeBound, openBound)
: Slice.make(openBound, closeBound);
addRangeTombstone(slice, openDeletion);
}
public void writeBoundDeletion(DeletionTime deletion)
{
assert !nextKind.isBoundary();
if (nextKind.isOpen(reversed))
open(nextKind, deletion);
else
close(nextKind, deletion);
}
public void writeBoundaryDeletion(DeletionTime endDeletion, DeletionTime startDeletion)
{
assert nextKind.isBoundary();
DeletionTime closeTime = reversed ? startDeletion : endDeletion;
DeletionTime openTime = reversed ? endDeletion : startDeletion;
close(nextKind.closeBoundOfBoundary(reversed), closeTime);
open(nextKind.openBoundOfBoundary(reversed), openTime);
}
public void endOfMarker()
{
clear();
}
private void addRangeTombstone(Slice deletionSlice, DeletionTime dt)
{
AbstractPartitionData.this.addRangeTombstone(deletionSlice, dt);
}
private void clear()
{
size = 0;
Arrays.fill(nextValues, null);
nextKind = null;
}
public void reset()
{
openBound = null;
openDeletion = null;
clear();
}
}
}

View File

@ -0,0 +1,393 @@
/*
* 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.partitions;
import java.util.*;
import com.google.common.collect.Lists;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.utils.SearchIterator;
/**
* Abstract common class for all non-thread safe Partition implementations.
*/
public abstract class AbstractThreadUnsafePartition implements Partition, Iterable<Row>
{
protected final CFMetaData metadata;
protected final DecoratedKey key;
protected final PartitionColumns columns;
protected final List<Row> rows;
protected AbstractThreadUnsafePartition(CFMetaData metadata,
DecoratedKey key,
PartitionColumns columns,
List<Row> rows)
{
this.metadata = metadata;
this.key = key;
this.columns = columns;
this.rows = rows;
}
public CFMetaData metadata()
{
return metadata;
}
public DecoratedKey partitionKey()
{
return key;
}
public DeletionTime partitionLevelDeletion()
{
return deletionInfo().getPartitionDeletion();
}
public PartitionColumns columns()
{
return columns;
}
public abstract Row staticRow();
protected abstract boolean canHaveShadowedData();
/**
* The deletion info for the partition update.
*
* Note: do not cast the result to a {@code MutableDeletionInfo} to modify it!
*
* @return the deletion info for the partition update for use as read-only.
*/
public abstract DeletionInfo deletionInfo();
public int rowCount()
{
return rows.size();
}
public boolean isEmpty()
{
return deletionInfo().isLive() && rows.isEmpty() && staticRow().isEmpty();
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
CFMetaData metadata = metadata();
sb.append(String.format("Partition[%s.%s] key=%s columns=%s%s",
metadata().ksName,
metadata().cfName,
metadata().getKeyValidator().getString(partitionKey().getKey()),
columns(),
deletionInfo().isLive() ? "" : " " + deletionInfo()));
if (staticRow() != Rows.EMPTY_STATIC_ROW)
sb.append("\n ").append(staticRow().toString(metadata, true));
// We use createRowIterator() directly instead of iterator() because that avoids
// sorting for PartitionUpdate (which inherit this method) and that is useful because
// 1) it can help with debugging and 2) we can't write after sorting but we want to
// be able to print an update while we build it (again for debugging)
for (Row row : this)
sb.append("\n ").append(row.toString(metadata, true));
return sb.toString();
}
public Row getRow(Clustering clustering)
{
Row row = searchIterator(ColumnFilter.selection(columns()), false).next(clustering);
// Note that for statics, this will never return null, this will return an empty row. However,
// it's more consistent for this method to return null if we don't really have a static row.
return row == null || (clustering == Clustering.STATIC_CLUSTERING && row.isEmpty()) ? null : row;
}
/**
* Returns an iterator that iterators over the rows of this update in clustering order.
*
* @return an iterator over the rows of this partition.
*/
public Iterator<Row> iterator()
{
return rows.iterator();
}
public SearchIterator<Clustering, Row> searchIterator(final ColumnFilter columns, boolean reversed)
{
final RowSearcher searcher = reversed ? new ReverseRowSearcher() : new ForwardRowSearcher();
return new SearchIterator<Clustering, Row>()
{
public boolean hasNext()
{
return !searcher.isDone();
}
public Row next(Clustering clustering)
{
if (clustering == Clustering.STATIC_CLUSTERING)
{
Row staticRow = staticRow();
return staticRow.isEmpty() || columns.fetchedColumns().statics.isEmpty()
? Rows.EMPTY_STATIC_ROW
: staticRow.filter(columns, partitionLevelDeletion(), true, metadata);
}
Row row = searcher.search(clustering);
RangeTombstone rt = deletionInfo().rangeCovering(clustering);
// A search iterator only return a row, so it doesn't allow to directly account for deletion that should apply to to row
// (the partition deletion or the deletion of a range tombstone that covers it). So if needs be, reuse the row deletion
// to carry the proper deletion on the row.
DeletionTime activeDeletion = partitionLevelDeletion();
if (rt != null && rt.deletionTime().supersedes(activeDeletion))
activeDeletion = rt.deletionTime();
if (row == null)
return activeDeletion.isLive() ? null : ArrayBackedRow.emptyDeletedRow(clustering, activeDeletion);
return row.filter(columns, activeDeletion, true, metadata);
}
};
}
public UnfilteredRowIterator unfilteredIterator()
{
return unfilteredIterator(ColumnFilter.all(metadata()), Slices.ALL, false);
}
public UnfilteredRowIterator unfilteredIterator(ColumnFilter columns, Slices slices, boolean reversed)
{
return slices.makeSliceIterator(sliceableUnfilteredIterator(columns, reversed));
}
protected SliceableUnfilteredRowIterator sliceableUnfilteredIterator()
{
return sliceableUnfilteredIterator(ColumnFilter.all(metadata()), false);
}
protected SliceableUnfilteredRowIterator sliceableUnfilteredIterator(ColumnFilter selection, boolean reversed)
{
return new SliceableIterator(this, selection, reversed);
}
/**
* Simple binary search for a given row (in the rows list).
*
* The return value has the exact same meaning that the one of Collections.binarySearch() but
* we don't use the later because we're searching for a 'Clustering' in an array of 'Row' (and while
* both are Clusterable, it's slightly faster to use the 'Clustering' comparison (see comment on
* ClusteringComparator.rowComparator())).
*/
private int binarySearch(Clustering clustering, int fromIndex, int toIndex)
{
ClusteringComparator comparator = metadata().comparator;
int low = fromIndex;
int mid = toIndex;
int high = mid - 1;
int result = -1;
while (low <= high)
{
mid = (low + high) >> 1;
if ((result = comparator.compare(clustering, rows.get(mid).clustering())) > 0)
low = mid + 1;
else if (result == 0)
return mid;
else
high = mid - 1;
}
return -mid - (result < 0 ? 1 : 2);
}
private class SliceableIterator extends AbstractUnfilteredRowIterator implements SliceableUnfilteredRowIterator
{
private final ColumnFilter columns;
private RowSearcher searcher;
private Iterator<Unfiltered> iterator;
private SliceableIterator(AbstractThreadUnsafePartition partition, ColumnFilter columns, boolean isReverseOrder)
{
super(partition.metadata(),
partition.partitionKey(),
partition.partitionLevelDeletion(),
columns.fetchedColumns(),
partition.staticRow().isEmpty() ? Rows.EMPTY_STATIC_ROW : partition.staticRow().filter(columns, partition.partitionLevelDeletion(), false, partition.metadata()),
isReverseOrder,
partition.stats());
this.columns = columns;
}
protected Unfiltered computeNext()
{
if (iterator == null)
iterator = merge(isReverseOrder ? Lists.reverse(rows).iterator(): iterator(), deletionInfo().rangeIterator(isReverseOrder()));
return iterator.hasNext() ? iterator.next() : endOfData();
}
public Iterator<Unfiltered> slice(Slice slice)
{
if (searcher == null)
searcher = isReverseOrder() ? new ReverseRowSearcher() : new ForwardRowSearcher();
return merge(searcher.slice(slice), deletionInfo().rangeIterator(slice, isReverseOrder()));
}
private Iterator<Unfiltered> merge(Iterator<Row> rows, Iterator<RangeTombstone> ranges)
{
return new RowAndDeletionMergeIterator(metadata,
partitionKey,
partitionLevelDeletion,
columns,
staticRow(),
isReverseOrder(),
stats(),
rows,
ranges,
canHaveShadowedData());
}
}
/**
* Utility class to search for rows or slice of rows in order.
*/
private abstract class RowSearcher
{
public abstract boolean isDone();
public abstract Row search(Clustering name);
public abstract Iterator<Row> slice(Slice slice);
protected int search(Clustering clustering, int from, int to)
{
return binarySearch(clustering, from, to);
}
protected int search(Slice.Bound bound, int from, int to)
{
return Collections.binarySearch(rows.subList(from, to), bound, metadata.comparator);
}
}
private class ForwardRowSearcher extends RowSearcher
{
private int nextIdx = 0;
public boolean isDone()
{
return nextIdx >= rows.size();
}
public Row search(Clustering name)
{
if (isDone())
return null;
int idx = search(name, nextIdx, rows.size());
if (idx < 0)
{
nextIdx = -idx - 1;
return null;
}
else
{
nextIdx = idx + 1;
return rows.get(idx);
}
}
public Iterator<Row> slice(Slice slice)
{
// Note that because a Slice.Bound can never sort equally to a Clustering, we know none of the search will
// be a match, so we save from testing for it.
final int start = -search(slice.start(), nextIdx, rows.size()) - 1; // First index to include
if (start >= rows.size())
return Collections.emptyIterator();
final int end = -search(slice.end(), start, rows.size()) - 1; // First index to exclude
// Remember the end to speed up potential further slice search
nextIdx = end;
if (start >= end)
return Collections.emptyIterator();
return rows.subList(start, end).iterator();
}
}
private class ReverseRowSearcher extends RowSearcher
{
private int nextIdx = rows.size() - 1;
public boolean isDone()
{
return nextIdx < 0;
}
public Row search(Clustering name)
{
if (isDone())
return null;
int idx = search(name, 0, nextIdx);
if (idx < 0)
{
// The insertion point is the first element greater than name, so we want start from the previous one next time
nextIdx = -idx - 2;
return null;
}
else
{
nextIdx = idx - 1;
return rows.get(idx);
}
}
public Iterator<Row> slice(Slice slice)
{
// Note that because a Slice.Bound can never sort equally to a Clustering, we know none of the search will
// be a match, so we save from testing for it.
// The insertion point is the first element greater than slice.end(), so we want the previous index
final int start = -search(slice.end(), 0, nextIdx + 1) - 2; // First index to include
if (start < 0)
return Collections.emptyIterator();
final int end = -search(slice.start(), 0, start + 1) - 2; // First index to exclude
// Remember the end to speed up potential further slice search
nextIdx = end;
if (start < end)
return Collections.emptyIterator();
return Lists.reverse(rows.subList(end+1, start+1)).iterator();
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.partitions;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.db.rows.*;
/**
* A partition iterator that allows to filter/modify the unfiltered from the
* underlying iterators.
*/
public abstract class AlteringUnfilteredPartitionIterator extends WrappingUnfilteredPartitionIterator
{
protected AlteringUnfilteredPartitionIterator(UnfilteredPartitionIterator wrapped)
{
super(wrapped);
}
protected Row computeNextStatic(DecoratedKey partitionKey, Row row)
{
return row;
}
protected Row computeNext(DecoratedKey partitionKey, Row row)
{
return row;
}
protected RangeTombstoneMarker computeNext(DecoratedKey partitionKey, RangeTombstoneMarker marker)
{
return marker;
}
@Override
protected UnfilteredRowIterator computeNext(UnfilteredRowIterator iter)
{
final DecoratedKey partitionKey = iter.partitionKey();
return new AlteringUnfilteredRowIterator(iter)
{
protected Row computeNextStatic(Row row)
{
return AlteringUnfilteredPartitionIterator.this.computeNextStatic(partitionKey, row);
}
protected Row computeNext(Row row)
{
return AlteringUnfilteredPartitionIterator.this.computeNext(partitionKey, row);
}
protected RangeTombstoneMarker computeNext(RangeTombstoneMarker marker)
{
return AlteringUnfilteredPartitionIterator.this.computeNext(partitionKey, marker);
}
};
}
}

View File

@ -18,11 +18,11 @@
package org.apache.cassandra.db.partitions;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.DataLimits;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.util.DataInputPlus;
@ -33,24 +33,31 @@ public class ArrayBackedCachedPartition extends ArrayBackedPartition implements
{
private final int createdAtInSec;
// Note that those fields are really immutable, but we can't easily pass their values to
// the ctor so they are not final.
private int cachedLiveRows;
private int rowsWithNonExpiringCells;
private final int cachedLiveRows;
private final int rowsWithNonExpiringCells;
private int nonTombstoneCellCount;
private int nonExpiringLiveCells;
private final int nonTombstoneCellCount;
private final int nonExpiringLiveCells;
private ArrayBackedCachedPartition(CFMetaData metadata,
DecoratedKey partitionKey,
DeletionTime deletionTime,
PartitionColumns columns,
int initialRowCapacity,
boolean sortable,
int createdAtInSec)
Row staticRow,
List<Row> rows,
DeletionInfo deletionInfo,
RowStats stats,
int createdAtInSec,
int cachedLiveRows,
int rowsWithNonExpiringCells,
int nonTombstoneCellCount,
int nonExpiringLiveCells)
{
super(metadata, partitionKey, deletionTime, columns, initialRowCapacity, sortable);
super(metadata, partitionKey, columns, staticRow, rows, deletionInfo, stats);
this.createdAtInSec = createdAtInSec;
this.cachedLiveRows = cachedLiveRows;
this.rowsWithNonExpiringCells = rowsWithNonExpiringCells;
this.nonTombstoneCellCount = nonTombstoneCellCount;
this.nonExpiringLiveCells = nonExpiringLiveCells;
}
/**
@ -65,7 +72,7 @@ public class ArrayBackedCachedPartition extends ArrayBackedPartition implements
*/
public static ArrayBackedCachedPartition create(UnfilteredRowIterator iterator, int nowInSec)
{
return create(iterator, 4, nowInSec);
return create(iterator, 16, nowInSec);
}
/**
@ -82,30 +89,76 @@ public class ArrayBackedCachedPartition extends ArrayBackedPartition implements
*/
public static ArrayBackedCachedPartition create(UnfilteredRowIterator iterator, int initialRowCapacity, int nowInSec)
{
ArrayBackedCachedPartition partition = new ArrayBackedCachedPartition(iterator.metadata(),
iterator.partitionKey(),
iterator.partitionLevelDeletion(),
iterator.columns(),
initialRowCapacity,
iterator.isReverseOrder(),
nowInSec);
CFMetaData metadata = iterator.metadata();
boolean reversed = iterator.isReverseOrder();
partition.staticRow = iterator.staticRow().takeAlias();
List<Row> rows = new ArrayList<>(initialRowCapacity);
MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(iterator.partitionLevelDeletion(), metadata.comparator, reversed);
Writer writer = partition.new Writer(nowInSec);
RangeTombstoneCollector markerCollector = partition.new RangeTombstoneCollector(iterator.isReverseOrder());
int cachedLiveRows = 0;
int rowsWithNonExpiringCells = 0;
copyAll(iterator, writer, markerCollector, partition);
int nonTombstoneCellCount = 0;
int nonExpiringLiveCells = 0;
return partition;
while (iterator.hasNext())
{
Unfiltered unfiltered = iterator.next();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
{
Row row = (Row)unfiltered;
rows.add(row);
// Collect stats
if (row.hasLiveData(nowInSec))
++cachedLiveRows;
boolean hasNonExpiringCell = false;
for (Cell cell : row.cells())
{
if (!cell.isTombstone())
{
++nonTombstoneCellCount;
if (!cell.isExpiring())
{
hasNonExpiringCell = true;
++nonExpiringLiveCells;
}
}
}
if (hasNonExpiringCell)
++rowsWithNonExpiringCells;
}
else
{
deletionBuilder.add((RangeTombstoneMarker)unfiltered);
}
}
if (reversed)
Collections.reverse(rows);
return new ArrayBackedCachedPartition(metadata,
iterator.partitionKey(),
iterator.columns(),
iterator.staticRow(),
rows,
deletionBuilder.build(),
iterator.stats(),
nowInSec,
cachedLiveRows,
rowsWithNonExpiringCells,
nonTombstoneCellCount,
nonExpiringLiveCells);
}
public Row lastRow()
{
if (rows == 0)
if (rows.isEmpty())
return null;
return new InternalReusableRow().setTo(rows - 1);
return rows.get(rows.size() - 1);
}
/**
@ -146,62 +199,6 @@ public class ArrayBackedCachedPartition extends ArrayBackedPartition implements
return nonExpiringLiveCells;
}
// Writers that collect the values for 'cachedLiveRows', 'rowsWithNonExpiringCells', 'nonTombstoneCellCount'
// and 'nonExpiringLiveCells'.
protected class Writer extends AbstractPartitionData.Writer
{
private final int nowInSec;
private boolean hasLiveData;
private boolean hasNonExpiringCell;
protected Writer(int nowInSec)
{
super(true);
this.nowInSec = nowInSec;
}
@Override
public void writePartitionKeyLivenessInfo(LivenessInfo info)
{
super.writePartitionKeyLivenessInfo(info);
if (info.isLive(nowInSec))
hasLiveData = true;
}
@Override
public void writeCell(ColumnDefinition column, boolean isCounter, ByteBuffer value, LivenessInfo info, CellPath path)
{
super.writeCell(column, isCounter, value, info, path);
if (info.isLive(nowInSec))
{
hasLiveData = true;
if (!info.hasTTL())
{
hasNonExpiringCell = true;
++ArrayBackedCachedPartition.this.nonExpiringLiveCells;
}
}
if (!info.hasLocalDeletionTime() || info.hasTTL())
++ArrayBackedCachedPartition.this.nonTombstoneCellCount;
}
@Override
public void endOfRow()
{
super.endOfRow();
if (hasLiveData)
++ArrayBackedCachedPartition.this.cachedLiveRows;
if (hasNonExpiringCell)
++ArrayBackedCachedPartition.this.rowsWithNonExpiringCells;
hasLiveData = false;
hasNonExpiringCell = false;
}
}
static class Serializer implements ISerializer<CachedPartition>
{
public void serialize(CachedPartition partition, DataOutputPlus out) throws IOException
@ -210,9 +207,13 @@ public class ArrayBackedCachedPartition extends ArrayBackedPartition implements
ArrayBackedCachedPartition p = (ArrayBackedCachedPartition)partition;
out.writeInt(p.createdAtInSec);
out.writeInt(p.cachedLiveRows);
out.writeInt(p.rowsWithNonExpiringCells);
out.writeInt(p.nonTombstoneCellCount);
out.writeInt(p.nonExpiringLiveCells);
try (UnfilteredRowIterator iter = p.sliceableUnfilteredIterator())
{
UnfilteredRowIteratorSerializer.serializer.serialize(iter, out, MessagingService.current_version, p.rows);
UnfilteredRowIteratorSerializer.serializer.serialize(iter, out, MessagingService.current_version, p.rowCount());
}
}
@ -226,18 +227,42 @@ public class ArrayBackedCachedPartition extends ArrayBackedPartition implements
// is slightly faster.
int createdAtInSec = in.readInt();
int cachedLiveRows = in.readInt();
int rowsWithNonExpiringCells = in.readInt();
int nonTombstoneCellCount = in.readInt();
int nonExpiringLiveCells = in.readInt();
UnfilteredRowIteratorSerializer.Header h = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(in, MessagingService.current_version, SerializationHelper.Flag.LOCAL);
assert !h.isReversed && h.rowEstimate >= 0;
UnfilteredRowIteratorSerializer.Header header = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(in, MessagingService.current_version, SerializationHelper.Flag.LOCAL);
assert !header.isReversed && header.rowEstimate >= 0;
ArrayBackedCachedPartition partition = new ArrayBackedCachedPartition(h.metadata, h.key, h.partitionDeletion, h.sHeader.columns(), h.rowEstimate, false, createdAtInSec);
partition.staticRow = h.staticRow;
MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(header.partitionDeletion, header.metadata.comparator, false);
List<Row> rows = new ArrayList<>(header.rowEstimate);
Writer writer = partition.new Writer(createdAtInSec);
RangeTombstoneMarker.Writer markerWriter = partition.new RangeTombstoneCollector(false);
try (UnfilteredRowIterator partition = UnfilteredRowIteratorSerializer.serializer.deserialize(in, MessagingService.current_version, SerializationHelper.Flag.LOCAL, header))
{
while (partition.hasNext())
{
Unfiltered unfiltered = partition.next();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
rows.add((Row)unfiltered);
else
deletionBuilder.add((RangeTombstoneMarker)unfiltered);
}
}
return new ArrayBackedCachedPartition(header.metadata,
header.key,
header.sHeader.columns(),
header.staticRow,
rows,
deletionBuilder.build(),
header.sHeader.stats(),
createdAtInSec,
cachedLiveRows,
rowsWithNonExpiringCells,
nonTombstoneCellCount,
nonExpiringLiveCells);
UnfilteredRowIteratorSerializer.serializer.deserialize(in, new SerializationHelper(MessagingService.current_version, SerializationHelper.Flag.LOCAL), h.sHeader, writer, markerWriter);
return partition;
}
public long serializedSize(CachedPartition partition)
@ -248,7 +273,11 @@ public class ArrayBackedCachedPartition extends ArrayBackedPartition implements
try (UnfilteredRowIterator iter = p.sliceableUnfilteredIterator())
{
return TypeSizes.sizeof(p.createdAtInSec)
+ UnfilteredRowIteratorSerializer.serializer.serializedSize(iter, MessagingService.current_version, p.rows);
+ TypeSizes.sizeof(p.cachedLiveRows)
+ TypeSizes.sizeof(p.rowsWithNonExpiringCells)
+ TypeSizes.sizeof(p.nonTombstoneCellCount)
+ TypeSizes.sizeof(p.nonExpiringLiveCells)
+ UnfilteredRowIteratorSerializer.serializer.serializedSize(iter, MessagingService.current_version, p.rowCount());
}
}
}

View File

@ -17,28 +17,30 @@
*/
package org.apache.cassandra.db.partitions;
import java.io.DataInput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.io.ISerializer;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.net.MessagingService;
public class ArrayBackedPartition extends AbstractPartitionData
public class ArrayBackedPartition extends AbstractThreadUnsafePartition
{
private final Row staticRow;
private final DeletionInfo deletionInfo;
private final RowStats stats;
protected ArrayBackedPartition(CFMetaData metadata,
DecoratedKey partitionKey,
DeletionTime deletionTime,
PartitionColumns columns,
int initialRowCapacity,
boolean sortable)
Row staticRow,
List<Row> rows,
DeletionInfo deletionInfo,
RowStats stats)
{
super(metadata, partitionKey, deletionTime, columns, initialRowCapacity, sortable);
super(metadata, partitionKey, columns, rows);
this.staticRow = staticRow;
this.deletionInfo = deletionInfo;
this.stats = stats;
}
/**
@ -52,7 +54,7 @@ public class ArrayBackedPartition extends AbstractPartitionData
*/
public static ArrayBackedPartition create(UnfilteredRowIterator iterator)
{
return create(iterator, 4);
return create(iterator, 16);
}
/**
@ -68,37 +70,45 @@ public class ArrayBackedPartition extends AbstractPartitionData
*/
public static ArrayBackedPartition create(UnfilteredRowIterator iterator, int initialRowCapacity)
{
ArrayBackedPartition partition = new ArrayBackedPartition(iterator.metadata(),
iterator.partitionKey(),
iterator.partitionLevelDeletion(),
iterator.columns(),
initialRowCapacity,
iterator.isReverseOrder());
CFMetaData metadata = iterator.metadata();
boolean reversed = iterator.isReverseOrder();
partition.staticRow = iterator.staticRow().takeAlias();
List<Row> rows = new ArrayList<>(initialRowCapacity);
MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(iterator.partitionLevelDeletion(), metadata.comparator, reversed);
Writer writer = partition.new Writer(true);
RangeTombstoneCollector markerCollector = partition.new RangeTombstoneCollector(iterator.isReverseOrder());
copyAll(iterator, writer, markerCollector, partition);
return partition;
}
protected static void copyAll(UnfilteredRowIterator iterator, Writer writer, RangeTombstoneCollector markerCollector, ArrayBackedPartition partition)
{
while (iterator.hasNext())
{
Unfiltered unfiltered = iterator.next();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
((Row) unfiltered).copyTo(writer);
rows.add((Row)unfiltered);
else
((RangeTombstoneMarker) unfiltered).copyTo(markerCollector);
deletionBuilder.add((RangeTombstoneMarker)unfiltered);
}
// A Partition (or more precisely AbstractPartitionData) always assumes that its data is in clustering
// order. So if we've just added them in reverse clustering order, reverse them.
if (iterator.isReverseOrder())
partition.reverse();
if (reversed)
Collections.reverse(rows);
return new ArrayBackedPartition(metadata, iterator.partitionKey(), iterator.columns(), iterator.staticRow(), rows, deletionBuilder.build(), iterator.stats());
}
protected boolean canHaveShadowedData()
{
// We only create instances from UnfilteredRowIterator that don't have shadowed data
return false;
}
public Row staticRow()
{
return staticRow;
}
public DeletionInfo deletionInfo()
{
return deletionInfo;
}
public RowStats stats()
{
return stats;
}
}

View File

@ -26,11 +26,8 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Iterators;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.filter.ColumnFilter;
@ -89,10 +86,7 @@ public class AtomicBTreePartition implements Partition
private static final AtomicIntegerFieldUpdater<AtomicBTreePartition> wasteTrackerUpdater = AtomicIntegerFieldUpdater.newUpdater(AtomicBTreePartition.class, "wasteTracker");
private static final DeletionInfo LIVE = DeletionInfo.live();
// This is a small optimization: DeletionInfo is mutable, but we know that we will always copy it in that class,
// so we can safely alias one DeletionInfo.live() reference and avoid some allocations.
private static final Holder EMPTY = new Holder(BTree.empty(), LIVE, null, RowStats.NO_STATS);
private static final Holder EMPTY = new Holder(BTree.empty(), DeletionInfo.LIVE, Rows.EMPTY_STATIC_ROW, RowStats.NO_STATS);
private final CFMetaData metadata;
private final DecoratedKey partitionKey;
@ -154,146 +148,56 @@ public class AtomicBTreePartition implements Partition
return row == null || (clustering == Clustering.STATIC_CLUSTERING && row.isEmpty()) ? null : row;
}
private Row staticRow(Holder current, ColumnFilter columns, boolean setActiveDeletionToRow)
{
DeletionTime partitionDeletion = current.deletionInfo.getPartitionDeletion();
if (columns.fetchedColumns().statics.isEmpty() || (current.staticRow.isEmpty() && partitionDeletion.isLive()))
return Rows.EMPTY_STATIC_ROW;
Row row = current.staticRow.filter(columns, partitionDeletion, setActiveDeletionToRow, metadata);
return row == null ? Rows.EMPTY_STATIC_ROW : row;
}
public SearchIterator<Clustering, Row> searchIterator(final ColumnFilter columns, final boolean reversed)
{
// TODO: we could optimize comparison for "NativeRow" à la #6755
final Holder current = ref;
return new SearchIterator<Clustering, Row>()
{
private final SearchIterator<Clustering, MemtableRowData> rawIter = new BTreeSearchIterator<>(current.tree, metadata.comparator, !reversed);
private final MemtableRowData.ReusableRow row = allocator.newReusableRow();
private final ReusableFilteringRow filter = new ReusableFilteringRow(columns.fetchedColumns().regulars, columns);
private final long partitionDeletion = current.deletionInfo.getPartitionDeletion().markedForDeleteAt();
private final SearchIterator<Clustering, Row> rawIter = new BTreeSearchIterator<>(current.tree, metadata.comparator, !reversed);
private final DeletionTime partitionDeletion = current.deletionInfo.getPartitionDeletion();
public boolean hasNext()
{
return rawIter.hasNext();
}
public Row next(Clustering key)
public Row next(Clustering clustering)
{
if (key == Clustering.STATIC_CLUSTERING)
return makeStatic(columns, current, allocator);
if (clustering == Clustering.STATIC_CLUSTERING)
return staticRow(current, columns, true);
MemtableRowData data = rawIter.next(key);
// We also need to find if there is a range tombstone covering this key
RangeTombstone rt = current.deletionInfo.rangeCovering(key);
Row row = rawIter.next(clustering);
RangeTombstone rt = current.deletionInfo.rangeCovering(clustering);
if (data == null)
{
// If we have a range tombstone but not data, "fake" the RT by return a row deletion
// corresponding to the tombstone.
if (rt != null && rt.deletionTime().markedForDeleteAt() > partitionDeletion)
return filter.setRowDeletion(rt.deletionTime()).setTo(emptyDeletedRow(key, rt.deletionTime()));
return null;
}
// A search iterator only return a row, so it doesn't allow to directly account for deletion that should apply to to row
// (the partition deletion or the deletion of a range tombstone that covers it). So if needs be, reuse the row deletion
// to carry the proper deletion on the row.
DeletionTime activeDeletion = partitionDeletion;
if (rt != null && rt.deletionTime().supersedes(activeDeletion))
activeDeletion = rt.deletionTime();
row.setTo(data);
if (row == null)
return activeDeletion.isLive() ? null : ArrayBackedRow.emptyDeletedRow(clustering, activeDeletion);
filter.setRowDeletion(null);
if (rt == null || rt.deletionTime().markedForDeleteAt() < partitionDeletion)
{
filter.setDeletionTimestamp(partitionDeletion);
}
else
{
filter.setDeletionTimestamp(rt.deletionTime().markedForDeleteAt());
// If we have a range tombstone covering that row and it's bigger than the row deletion itself, then
// we replace the row deletion by the tombstone deletion as a way to return the tombstone.
if (rt.deletionTime().supersedes(row.deletion()))
filter.setRowDeletion(rt.deletionTime());
}
return filter.setTo(row);
}
};
}
private static Row emptyDeletedRow(Clustering clustering, DeletionTime deletion)
{
return new AbstractRow()
{
public Columns columns()
{
return Columns.NONE;
}
public LivenessInfo primaryKeyLivenessInfo()
{
return LivenessInfo.NONE;
}
public DeletionTime deletion()
{
return deletion;
}
public boolean isEmpty()
{
return true;
}
public boolean hasComplexDeletion()
{
return false;
}
public Clustering clustering()
{
return clustering;
}
public Cell getCell(ColumnDefinition c)
{
return null;
}
public Cell getCell(ColumnDefinition c, CellPath path)
{
return null;
}
public Iterator<Cell> getCells(ColumnDefinition c)
{
return null;
}
public DeletionTime getDeletion(ColumnDefinition c)
{
return DeletionTime.LIVE;
}
public Iterator<Cell> iterator()
{
return Iterators.<Cell>emptyIterator();
}
public SearchIterator<ColumnDefinition, ColumnData> searchIterator()
{
return new SearchIterator<ColumnDefinition, ColumnData>()
{
public boolean hasNext()
{
return false;
}
public ColumnData next(ColumnDefinition column)
{
return null;
}
};
}
public Row takeAlias()
{
return this;
return row.filter(columns, activeDeletion, true, metadata);
}
};
}
public UnfilteredRowIterator unfilteredIterator()
{
return unfilteredIterator(ColumnFilter.selection(columns()), Slices.ALL, false);
return unfilteredIterator(ColumnFilter.all(metadata()), Slices.ALL, false);
}
public UnfilteredRowIterator unfilteredIterator(ColumnFilter selection, Slices slices, boolean reversed)
@ -309,7 +213,7 @@ public class AtomicBTreePartition implements Partition
partitionKey,
partitionDeletion,
selection.fetchedColumns(),
makeStatic(selection, current, allocator),
staticRow(current, selection, false),
reversed,
current.stats)
{
@ -320,189 +224,51 @@ public class AtomicBTreePartition implements Partition
};
}
Holder current = ref;
Row staticRow = staticRow(current, selection, false);
return slices.size() == 1
? new SingleSliceIterator(metadata, partitionKey, ref, selection, slices.get(0), reversed, allocator)
: new SlicesIterator(metadata, partitionKey, ref, selection, slices, reversed, allocator);
? sliceIterator(selection, slices.get(0), reversed, current, staticRow)
: new SlicesIterator(metadata, partitionKey, selection, slices, reversed, current, staticRow);
}
private static Row makeStatic(ColumnFilter selection, Holder holder, MemtableAllocator allocator)
private UnfilteredRowIterator sliceIterator(ColumnFilter selection, Slice slice, boolean reversed, Holder current, Row staticRow)
{
Columns statics = selection.fetchedColumns().statics;
if (statics.isEmpty() || holder.staticRow == null)
return Rows.EMPTY_STATIC_ROW;
Slice.Bound start = slice.start() == Slice.Bound.BOTTOM ? null : slice.start();
Slice.Bound end = slice.end() == Slice.Bound.TOP ? null : slice.end();
Iterator<Row> rowIter = BTree.slice(current.tree, metadata.comparator, start, true, end, true, !reversed);
return new ReusableFilteringRow(statics, selection)
.setDeletionTimestamp(holder.deletionInfo.getPartitionDeletion().markedForDeleteAt())
.setTo(allocator.newReusableRow().setTo(holder.staticRow));
return new RowAndDeletionMergeIterator(metadata,
partitionKey,
current.deletionInfo.getPartitionDeletion(),
selection,
staticRow,
reversed,
current.stats,
rowIter,
current.deletionInfo.rangeIterator(slice, reversed),
true);
}
private static class ReusableFilteringRow extends FilteringRow
public class SlicesIterator extends AbstractUnfilteredRowIterator
{
private final Columns columns;
private final ColumnFilter selection;
private ColumnFilter.Tester tester;
private long deletionTimestamp;
// Used by searchIterator in case the row is covered by a tombstone.
private DeletionTime rowDeletion;
public ReusableFilteringRow(Columns columns, ColumnFilter selection)
{
this.columns = columns;
this.selection = selection;
}
public ReusableFilteringRow setDeletionTimestamp(long timestamp)
{
this.deletionTimestamp = timestamp;
return this;
}
public ReusableFilteringRow setRowDeletion(DeletionTime rowDeletion)
{
this.rowDeletion = rowDeletion;
return this;
}
@Override
public DeletionTime deletion()
{
return rowDeletion == null ? super.deletion() : rowDeletion;
}
@Override
protected boolean include(LivenessInfo info)
{
return info.timestamp() > deletionTimestamp;
}
@Override
protected boolean include(ColumnDefinition def)
{
return columns.contains(def);
}
@Override
protected boolean include(DeletionTime dt)
{
return dt.markedForDeleteAt() > deletionTimestamp;
}
@Override
protected boolean include(ColumnDefinition c, DeletionTime dt)
{
return dt.markedForDeleteAt() > deletionTimestamp;
}
@Override
protected boolean include(Cell cell)
{
return selection.includes(cell);
}
}
private static class SingleSliceIterator extends AbstractUnfilteredRowIterator
{
private final Iterator<Unfiltered> iterator;
private final ReusableFilteringRow row;
private SingleSliceIterator(CFMetaData metadata,
DecoratedKey key,
Holder holder,
ColumnFilter selection,
Slice slice,
boolean isReversed,
MemtableAllocator allocator)
{
super(metadata,
key,
holder.deletionInfo.getPartitionDeletion(),
selection.fetchedColumns(),
makeStatic(selection, holder, allocator),
isReversed,
holder.stats);
Iterator<Row> rowIter = rowIter(metadata,
holder,
slice,
!isReversed,
allocator);
this.iterator = new RowAndTombstoneMergeIterator(metadata.comparator, isReversed)
.setTo(rowIter, holder.deletionInfo.rangeIterator(slice, isReversed));
this.row = new ReusableFilteringRow(selection.fetchedColumns().regulars, selection)
.setDeletionTimestamp(partitionLevelDeletion.markedForDeleteAt());
}
private Iterator<Row> rowIter(CFMetaData metadata,
Holder holder,
Slice slice,
boolean forwards,
final MemtableAllocator allocator)
{
Slice.Bound start = slice.start() == Slice.Bound.BOTTOM ? null : slice.start();
Slice.Bound end = slice.end() == Slice.Bound.TOP ? null : slice.end();
final Iterator<MemtableRowData> dataIter = BTree.slice(holder.tree, metadata.comparator, start, true, end, true, forwards);
return new AbstractIterator<Row>()
{
private final MemtableRowData.ReusableRow row = allocator.newReusableRow();
protected Row computeNext()
{
return dataIter.hasNext() ? row.setTo(dataIter.next()) : endOfData();
}
};
}
protected Unfiltered computeNext()
{
while (iterator.hasNext())
{
Unfiltered next = iterator.next();
if (next.kind() == Unfiltered.Kind.ROW)
{
row.setTo((Row)next);
if (!row.isEmpty())
return row;
}
else
{
RangeTombstoneMarker marker = (RangeTombstoneMarker)next;
long deletion = partitionLevelDeletion().markedForDeleteAt();
if (marker.isOpen(isReverseOrder()))
deletion = Math.max(deletion, marker.openDeletionTime(isReverseOrder()).markedForDeleteAt());
row.setDeletionTimestamp(deletion);
return marker;
}
}
return endOfData();
}
}
public static class SlicesIterator extends AbstractUnfilteredRowIterator
{
private final Holder holder;
private final MemtableAllocator allocator;
private final Holder current;
private final ColumnFilter selection;
private final Slices slices;
private int idx;
private UnfilteredRowIterator currentSlice;
private Iterator<Unfiltered> currentSlice;
private SlicesIterator(CFMetaData metadata,
DecoratedKey key,
Holder holder,
ColumnFilter selection,
Slices slices,
boolean isReversed,
MemtableAllocator allocator)
Holder holder,
Row staticRow)
{
super(metadata, key, holder.deletionInfo.getPartitionDeletion(), selection.fetchedColumns(), makeStatic(selection, holder, allocator), isReversed, holder.stats);
this.holder = holder;
super(metadata, key, holder.deletionInfo.getPartitionDeletion(), selection.fetchedColumns(), staticRow, isReversed, holder.stats);
this.current = holder;
this.selection = selection;
this.allocator = allocator;
this.slices = slices;
}
@ -516,13 +282,7 @@ public class AtomicBTreePartition implements Partition
return endOfData();
int sliceIdx = isReverseOrder ? slices.size() - idx - 1 : idx;
currentSlice = new SingleSliceIterator(metadata,
partitionKey,
holder,
selection,
slices.get(sliceIdx),
isReverseOrder,
allocator);
currentSlice = sliceIterator(selection, slices.get(sliceIdx), isReverseOrder, current, Rows.EMPTY_STATIC_ROW);
idx++;
}
@ -565,7 +325,7 @@ public class AtomicBTreePartition implements Partition
if (inputDeletionInfoCopy == null)
inputDeletionInfoCopy = update.deletionInfo().copy(HeapAllocator.instance);
deletionInfo = current.deletionInfo.copy().add(inputDeletionInfoCopy);
deletionInfo = current.deletionInfo.mutableCopy().add(inputDeletionInfoCopy);
updater.allocated(deletionInfo.unsharedHeapSize() - current.deletionInfo.unsharedHeapSize());
}
else
@ -574,9 +334,9 @@ public class AtomicBTreePartition implements Partition
}
Row newStatic = update.staticRow();
MemtableRowData staticRow = newStatic == Rows.EMPTY_STATIC_ROW
? current.staticRow
: (current.staticRow == null ? updater.apply(newStatic) : updater.apply(current.staticRow, newStatic));
Row staticRow = newStatic.isEmpty()
? current.staticRow
: (current.staticRow.isEmpty() ? updater.apply(newStatic) : updater.apply(current.staticRow, newStatic));
Object[] tree = BTree.update(current.tree, update.metadata().comparator, update, update.rowCount(), updater);
RowStats newStats = current.stats.mergeWith(update.stats());
@ -661,10 +421,10 @@ public class AtomicBTreePartition implements Partition
final DeletionInfo deletionInfo;
// the btree of rows
final Object[] tree;
final MemtableRowData staticRow;
final Row staticRow;
final RowStats stats;
Holder(Object[] tree, DeletionInfo deletionInfo, MemtableRowData staticRow, RowStats stats)
Holder(Object[] tree, DeletionInfo deletionInfo, Row staticRow, RowStats stats)
{
this.tree = tree;
this.deletionInfo = deletionInfo;
@ -679,7 +439,7 @@ public class AtomicBTreePartition implements Partition
}
// the function we provide to the btree utilities to perform any column replacements
private static final class RowUpdater implements UpdateFunction<Row, MemtableRowData>
private static final class RowUpdater implements UpdateFunction<Row, Row>
{
final AtomicBTreePartition updating;
final MemtableAllocator allocator;
@ -687,13 +447,13 @@ public class AtomicBTreePartition implements Partition
final Updater indexer;
final int nowInSec;
Holder ref;
Row.Builder regularBuilder;
long dataSize;
long heapSize;
long colUpdateTimeDelta = Long.MAX_VALUE;
final MemtableRowData.ReusableRow row;
final MemtableAllocator.DataReclaimer reclaimer;
final MemtableAllocator.RowAllocator rowAllocator;
List<MemtableRowData> inserted; // TODO: replace with walk of aborted BTree
List<Row> inserted; // TODO: replace with walk of aborted BTree
private RowUpdater(AtomicBTreePartition updating, MemtableAllocator allocator, OpOrder.Group writeOp, Updater indexer)
{
@ -702,18 +462,25 @@ public class AtomicBTreePartition implements Partition
this.writeOp = writeOp;
this.indexer = indexer;
this.nowInSec = FBUtilities.nowInSeconds();
this.row = allocator.newReusableRow();
this.reclaimer = allocator.reclaimer();
this.rowAllocator = allocator.newRowAllocator(updating.metadata(), writeOp);
}
public MemtableRowData apply(Row insert)
private Row.Builder builder(Clustering clustering)
{
rowAllocator.allocateNewRow(insert.clustering().size(), insert.columns(), insert.isStatic());
insert.copyTo(rowAllocator);
MemtableRowData data = rowAllocator.allocatedRowData();
boolean isStatic = clustering == Clustering.STATIC_CLUSTERING;
// We know we only insert/update one static per PartitionUpdate, so no point in saving the builder
if (isStatic)
return allocator.rowBuilder(updating.metadata(), writeOp, true);
insertIntoIndexes(insert);
if (regularBuilder == null)
regularBuilder = allocator.rowBuilder(updating.metadata(), writeOp, false);
return regularBuilder;
}
public Row apply(Row insert)
{
Row data = Rows.copy(insert, builder(insert.clustering())).build();
insertIntoIndexes(data);
this.dataSize += data.dataSize();
this.heapSize += data.unsharedHeapSizeExcludingData();
@ -723,14 +490,14 @@ public class AtomicBTreePartition implements Partition
return data;
}
public MemtableRowData apply(MemtableRowData existing, Row update)
public Row apply(Row existing, Row update)
{
Columns mergedColumns = existing.columns().mergeTo(update.columns());
rowAllocator.allocateNewRow(update.clustering().size(), mergedColumns, update.isStatic());
colUpdateTimeDelta = Math.min(colUpdateTimeDelta, Rows.merge(row.setTo(existing), update, mergedColumns, rowAllocator, nowInSec, indexer));
Row.Builder builder = builder(existing.clustering());
colUpdateTimeDelta = Math.min(colUpdateTimeDelta, Rows.merge(existing, update, mergedColumns, builder, nowInSec, indexer));
MemtableRowData reconciled = rowAllocator.allocatedRowData();
Row reconciled = builder.build();
dataSize += reconciled.dataSize() - existing.dataSize();
heapSize += reconciled.unsharedHeapSizeExcludingData() - existing.unsharedHeapSizeExcludingData();
@ -749,7 +516,7 @@ public class AtomicBTreePartition implements Partition
maybeIndexPrimaryKeyColumns(toInsert);
Clustering clustering = toInsert.clustering();
for (Cell cell : toInsert)
for (Cell cell : toInsert.cells())
indexer.insert(clustering, cell);
}
@ -761,15 +528,15 @@ public class AtomicBTreePartition implements Partition
long timestamp = row.primaryKeyLivenessInfo().timestamp();
int ttl = row.primaryKeyLivenessInfo().ttl();
for (Cell cell : row)
for (Cell cell : row.cells())
{
long cellTimestamp = cell.livenessInfo().timestamp();
long cellTimestamp = cell.timestamp();
if (cell.isLive(nowInSec))
{
if (cellTimestamp > timestamp)
{
timestamp = cellTimestamp;
ttl = cell.livenessInfo().ttl();
ttl = cell.ttl();
}
}
}
@ -783,19 +550,19 @@ public class AtomicBTreePartition implements Partition
this.heapSize = 0;
if (inserted != null)
{
for (MemtableRowData row : inserted)
for (Row row : inserted)
abort(row);
inserted.clear();
}
reclaimer.cancel();
}
protected void abort(MemtableRowData abort)
protected void abort(Row abort)
{
reclaimer.reclaimImmediately(abort);
}
protected void discard(MemtableRowData discard)
protected void discard(Row discard)
{
reclaimer.reclaim(discard);
}

View File

@ -49,10 +49,10 @@ public class CountingUnfilteredRowIterator extends WrappingUnfilteredRowIterator
@Override
public Unfiltered next()
{
Unfiltered unfiltered = super.next();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
counter.newRow((Row) unfiltered);
return unfiltered;
Unfiltered next = super.next();
if (next.isRow())
counter.newRow((Row)next);
return next;
}
@Override

View File

@ -17,21 +17,24 @@
*/
package org.apache.cassandra.db.partitions;
import java.util.Iterator;
import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
public class FilteredPartition extends AbstractPartitionData implements Iterable<Row>
public class FilteredPartition extends AbstractThreadUnsafePartition
{
private final Row staticRow;
private FilteredPartition(CFMetaData metadata,
DecoratedKey partitionKey,
PartitionColumns columns,
int initialRowCapacity,
boolean sortable)
Row staticRow,
List<Row> rows)
{
super(metadata, partitionKey, DeletionTime.LIVE, columns, initialRowCapacity, sortable);
super(metadata, partitionKey, columns, rows);
this.staticRow = staticRow;
}
/**
@ -42,25 +45,43 @@ public class FilteredPartition extends AbstractPartitionData implements Iterable
*/
public static FilteredPartition create(RowIterator iterator)
{
FilteredPartition partition = new FilteredPartition(iterator.metadata(),
iterator.partitionKey(),
iterator.columns(),
4,
iterator.isReverseOrder());
CFMetaData metadata = iterator.metadata();
boolean reversed = iterator.isReverseOrder();
partition.staticRow = iterator.staticRow().takeAlias();
Writer writer = partition.new Writer(true);
List<Row> rows = new ArrayList<>();
while (iterator.hasNext())
iterator.next().copyTo(writer);
{
Unfiltered unfiltered = iterator.next();
if (unfiltered.isRow())
rows.add((Row)unfiltered);
}
// A Partition (or more precisely AbstractPartitionData) always assumes that its data is in clustering
// order. So if we've just added them in reverse clustering order, reverse them.
if (iterator.isReverseOrder())
partition.reverse();
if (reversed)
Collections.reverse(rows);
return partition;
return new FilteredPartition(metadata, iterator.partitionKey(), iterator.columns(), iterator.staticRow(), rows);
}
protected boolean canHaveShadowedData()
{
// We only create instances from RowIterator that don't have shadowed data (nor deletion info really)
return false;
}
public Row staticRow()
{
return staticRow;
}
public DeletionInfo deletionInfo()
{
return DeletionInfo.LIVE;
}
public RowStats stats()
{
return RowStats.NO_STATS;
}
public RowIterator rowIterator()
@ -90,7 +111,7 @@ public class FilteredPartition extends AbstractPartitionData implements Iterable
public Row staticRow()
{
return staticRow == null ? Rows.EMPTY_STATIC_ROW : staticRow;
return FilteredPartition.this.staticRow();
}
public boolean hasNext()
@ -117,26 +138,20 @@ public class FilteredPartition extends AbstractPartitionData implements Iterable
@Override
public String toString()
{
try (RowIterator iterator = rowIterator())
{
StringBuilder sb = new StringBuilder();
CFMetaData metadata = iterator.metadata();
PartitionColumns columns = iterator.columns();
StringBuilder sb = new StringBuilder();
sb.append(String.format("[%s.%s] key=%s columns=%s reversed=%b",
metadata.ksName,
metadata.cfName,
metadata.getKeyValidator().getString(iterator.partitionKey().getKey()),
columns,
iterator.isReverseOrder()));
sb.append(String.format("[%s.%s] key=%s columns=%s",
metadata.ksName,
metadata.cfName,
metadata.getKeyValidator().getString(partitionKey().getKey()),
columns));
if (iterator.staticRow() != Rows.EMPTY_STATIC_ROW)
sb.append("\n ").append(iterator.staticRow().toString(metadata));
if (staticRow() != Rows.EMPTY_STATIC_ROW)
sb.append("\n ").append(staticRow().toString(metadata));
while (iterator.hasNext())
sb.append("\n ").append(iterator.next().toString(metadata));
for (Row row : this)
sb.append("\n ").append(row.toString(metadata));
return sb.toString();
}
return sb.toString();
}
}

View File

@ -1,146 +0,0 @@
/*
* 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.partitions;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
/**
* Abstract class to make it easier to write iterators that filter some
* parts of another iterator (used for purging tombstones and removing dropped columns).
*/
public abstract class FilteringPartitionIterator extends WrappingUnfilteredPartitionIterator
{
private UnfilteredRowIterator next;
protected FilteringPartitionIterator(UnfilteredPartitionIterator iter)
{
super(iter);
}
// The filter to use for filtering row contents. Is null by default to mean no particular filtering
// but can be overriden by subclasses. Please see FilteringAtomIterator for details on how this is used.
protected FilteringRow makeRowFilter()
{
return null;
}
// Whether or not we should bother filtering the provided rows iterator. This
// exists mainly for preformance
protected boolean shouldFilter(UnfilteredRowIterator iterator)
{
return true;
}
protected boolean includeRangeTombstoneMarker(RangeTombstoneMarker marker)
{
return true;
}
protected boolean includePartitionDeletion(DeletionTime dt)
{
return true;
}
// Allows to modify the range tombstone returned. This is called *after* includeRangeTombstoneMarker has been called.
protected RangeTombstoneMarker filterRangeTombstoneMarker(RangeTombstoneMarker marker, boolean reversed)
{
return marker;
}
// Called when a particular partition is skipped due to being empty post filtering
protected void onEmpty(DecoratedKey key)
{
}
public boolean hasNext()
{
while (next == null && super.hasNext())
{
UnfilteredRowIterator iterator = super.next();
if (shouldFilter(iterator))
{
next = new FilteringIterator(iterator);
if (!isForThrift() && next.isEmpty())
{
onEmpty(iterator.partitionKey());
iterator.close();
next = null;
}
}
else
{
next = iterator;
}
}
return next != null;
}
public UnfilteredRowIterator next()
{
UnfilteredRowIterator toReturn = next;
next = null;
return toReturn;
}
@Override
public void close()
{
try
{
super.close();
}
finally
{
if (next != null)
next.close();
}
}
private class FilteringIterator extends FilteringRowIterator
{
private FilteringIterator(UnfilteredRowIterator iterator)
{
super(iterator);
}
@Override
protected FilteringRow makeRowFilter()
{
return FilteringPartitionIterator.this.makeRowFilter();
}
@Override
protected boolean includeRangeTombstoneMarker(RangeTombstoneMarker marker)
{
return FilteringPartitionIterator.this.includeRangeTombstoneMarker(marker);
}
@Override
protected RangeTombstoneMarker filterRangeTombstoneMarker(RangeTombstoneMarker marker, boolean reversed)
{
return FilteringPartitionIterator.this.filterRangeTombstoneMarker(marker, reversed);
}
@Override
protected boolean includePartitionDeletion(DeletionTime dt)
{
return FilteringPartitionIterator.this.includePartitionDeletion(dt);
}
}
}

View File

@ -15,41 +15,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.db.rows;
package org.apache.cassandra.db.partitions;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.Cell;
public class TombstoneFilteringRow extends FilteringRow
public interface PartitionStatisticsCollector
{
private final int nowInSec;
public TombstoneFilteringRow(int nowInSec)
{
this.nowInSec = nowInSec;
}
@Override
protected boolean include (LivenessInfo info)
{
return info.isLive(nowInSec);
}
@Override
protected boolean include(DeletionTime dt)
{
return false;
}
@Override
protected boolean include(Cell cell)
{
return cell.isLive(nowInSec);
}
@Override
protected boolean include(ColumnDefinition c, DeletionTime dt)
{
return false;
}
public void update(LivenessInfo info);
public void update(DeletionTime deletionTime);
public void update(Cell cell);
public void updateColumnSetPerRow(long columnSetInRow);
public void updateHasLegacyCounterShards(boolean hasLegacyCounterShards);
}

View File

@ -17,12 +17,12 @@
*/
package org.apache.cassandra.db.partitions;
import java.io.DataInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -31,77 +31,67 @@ import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnFilter;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.db.index.SecondaryIndexManager;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.NIODataInputStream;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.Sorting;
import org.apache.cassandra.utils.MergeIterator;
/**
* Stores updates made on a partition.
* <p>
* A PartitionUpdate object requires that all writes are performed before we
* try to read the updates (attempts to write to the PartitionUpdate after a
* read method has been called will result in an exception being thrown).
* In other words, a Partition is mutable while we do a write and become
* immutable as soon as it is read.
* A PartitionUpdate object requires that all writes/additions are performed before we
* try to read the updates (attempts to write to the PartitionUpdate after a read method
* has been called will result in an exception being thrown). In other words, a Partition
* is mutable while it's written but becomes immutable as soon as it is read.
* <p>
* Row updates are added to this update through the {@link #writer} method which
* returns a {@link Row.Writer}. Multiple rows can be added to this writer as required and
* those row do not have to added in (clustering) order, and the same row can be added
* multiple times. Further, for a given row, the writer actually supports intermingling
* the writing of cells for different complex cells (note that this is usually not supported
* by {@code Row.Writer} implementations, but is supported here because
* {@code ModificationStatement} requires that (because we could have multiple {@link Operation}
* on the same column in a given statement)).
* A typical usage is to create a new update ({@code new PartitionUpdate(metadata, key, columns, capacity)})
* and then add rows and range tombstones through the {@code add()} methods (the partition
* level deletion time can also be set with {@code addPartitionDeletion()}). However, there
* is also a few static helper constructor methods for special cases ({@code emptyUpdate()},
* {@code fullPartitionDelete} and {@code singleRowUpdate}).
*/
public class PartitionUpdate extends AbstractPartitionData implements Sorting.Sortable
public class PartitionUpdate extends AbstractThreadUnsafePartition
{
protected static final Logger logger = LoggerFactory.getLogger(PartitionUpdate.class);
// Records whether the partition update has been sorted (it is the rows contained in the partition
// that are sorted since we don't require rows to be added in order). Sorting happens when the
// update is read, and writting is rejected as soon as the update is sorted (it's actually possible
// to manually allow new update by using allowNewUpdates(), and we could make that more implicit, but
// as only triggers really requires it, we keep it simple for now).
private boolean isSorted;
public static final PartitionUpdateSerializer serializer = new PartitionUpdateSerializer();
private final Writer writer;
private final int createdAtInSec = FBUtilities.nowInSeconds();
// Used by compare for the sake of implementing the Sorting.Sortable interface (which is in turn used
// to sort the rows of this update).
private final InternalReusableClustering p1 = new InternalReusableClustering();
private final InternalReusableClustering p2 = new InternalReusableClustering();
// Records whether this update is "built", i.e. if the build() method has been called, which
// happens when the update is read. Further writing is then rejected though a manual call
// to allowNewUpdates() allow new writes. We could make that more implicit but only triggers
// really requires that so we keep it simple for now).
private boolean isBuilt;
private boolean canReOpen = true;
private final MutableDeletionInfo deletionInfo;
private RowStats stats; // will be null if isn't built
private Row staticRow = Rows.EMPTY_STATIC_ROW;
private final boolean canHaveShadowedData;
private PartitionUpdate(CFMetaData metadata,
DecoratedKey key,
DeletionInfo delInfo,
RowDataBlock data,
PartitionColumns columns,
int initialRowCapacity)
Row staticRow,
List<Row> rows,
MutableDeletionInfo deletionInfo,
RowStats stats,
boolean isBuilt,
boolean canHaveShadowedData)
{
super(metadata, key, delInfo, columns, data, initialRowCapacity);
this.writer = createWriter();
}
public PartitionUpdate(CFMetaData metadata,
DecoratedKey key,
DeletionInfo delInfo,
PartitionColumns columns,
int initialRowCapacity)
{
this(metadata,
key,
delInfo,
new RowDataBlock(columns.regulars, initialRowCapacity, true, metadata.isCounter()),
columns,
initialRowCapacity);
super(metadata, key, columns, rows);
this.staticRow = staticRow;
this.deletionInfo = deletionInfo;
this.stats = stats;
this.isBuilt = isBuilt;
this.canHaveShadowedData = canHaveShadowedData;
}
public PartitionUpdate(CFMetaData metadata,
@ -109,21 +99,117 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
PartitionColumns columns,
int initialRowCapacity)
{
this(metadata,
key,
DeletionInfo.live(),
columns,
initialRowCapacity);
this(metadata, key, columns, Rows.EMPTY_STATIC_ROW, new ArrayList<>(initialRowCapacity), MutableDeletionInfo.live(), null, false, true);
}
protected Writer createWriter()
/**
* Creates a empty immutable partition update.
*
* @param metadata the metadata for the created update.
* @param key the partition key for the created update.
*
* @return the newly created empty (and immutable) update.
*/
public static PartitionUpdate emptyUpdate(CFMetaData metadata, DecoratedKey key)
{
return new RegularWriter();
return new PartitionUpdate(metadata, key, PartitionColumns.NONE, Rows.EMPTY_STATIC_ROW, Collections.<Row>emptyList(), MutableDeletionInfo.live(), RowStats.NO_STATS, true, false);
}
protected StaticWriter createStaticWriter()
/**
* Creates an immutable partition update that entirely deletes a given partition.
*
* @param metadata the metadata for the created update.
* @param key the partition key for the partition that the created update should delete.
* @param timestamp the timestamp for the deletion.
* @param nowInSec the current time in seconds to use as local deletion time for the partition deletion.
*
* @return the newly created partition deletion update.
*/
public static PartitionUpdate fullPartitionDelete(CFMetaData metadata, DecoratedKey key, long timestamp, int nowInSec)
{
return new StaticWriter();
return new PartitionUpdate(metadata, key, PartitionColumns.NONE, Rows.EMPTY_STATIC_ROW, Collections.<Row>emptyList(), new MutableDeletionInfo(timestamp, nowInSec), RowStats.NO_STATS, true, false);
}
/**
* Creates an immutable partition update that contains a single row update.
*
* @param metadata the metadata for the created update.
* @param key the partition key for the partition that the created update should delete.
* @param row the row for the update.
*
* @return the newly created partition update containing only {@code row}.
*/
public static PartitionUpdate singleRowUpdate(CFMetaData metadata, DecoratedKey key, Row row)
{
return row.isStatic()
? new PartitionUpdate(metadata, key, new PartitionColumns(row.columns(), Columns.NONE), row, Collections.<Row>emptyList(), MutableDeletionInfo.live(), RowStats.NO_STATS, true, false)
: new PartitionUpdate(metadata, key, new PartitionColumns(Columns.NONE, row.columns()), Rows.EMPTY_STATIC_ROW, Collections.singletonList(row), MutableDeletionInfo.live(), RowStats.NO_STATS, true, false);
}
/**
* Turns the given iterator into an update.
*
* Warning: this method does not close the provided iterator, it is up to
* the caller to close it.
*/
public static PartitionUpdate fromIterator(UnfilteredRowIterator iterator)
{
CFMetaData metadata = iterator.metadata();
boolean reversed = iterator.isReverseOrder();
List<Row> rows = new ArrayList<>();
MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(iterator.partitionLevelDeletion(), metadata.comparator, reversed);
while (iterator.hasNext())
{
Unfiltered unfiltered = iterator.next();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
rows.add((Row)unfiltered);
else
deletionBuilder.add((RangeTombstoneMarker)unfiltered);
}
if (reversed)
Collections.reverse(rows);
return new PartitionUpdate(metadata, iterator.partitionKey(), iterator.columns(), iterator.staticRow(), rows, deletionBuilder.build(), iterator.stats(), true, false);
}
public static PartitionUpdate fromIterator(RowIterator iterator)
{
CFMetaData metadata = iterator.metadata();
boolean reversed = iterator.isReverseOrder();
List<Row> rows = new ArrayList<>();
RowStats.Collector collector = new RowStats.Collector();
while (iterator.hasNext())
{
Row row = iterator.next();
rows.add(row);
Rows.collectStats(row, collector);
}
if (reversed)
Collections.reverse(rows);
return new PartitionUpdate(metadata, iterator.partitionKey(), iterator.columns(), iterator.staticRow(), rows, MutableDeletionInfo.live(), collector.get(), true, false);
}
protected boolean canHaveShadowedData()
{
return canHaveShadowedData;
}
public Row staticRow()
{
return staticRow;
}
public DeletionInfo deletionInfo()
{
return deletionInfo;
}
/**
@ -166,7 +252,7 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
{
try (DataOutputBuffer out = new DataOutputBuffer())
{
serializer.serialize(update, out, MessagingService.current_version);
serializer.serialize(update, out, version);
return ByteBuffer.wrap(out.getData(), 0, out.getLength());
}
catch (IOException e)
@ -175,60 +261,6 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
}
}
/**
* Creates a empty immutable partition update.
*
* @param metadata the metadata for the created update.
* @param key the partition key for the created update.
*
* @return the newly created empty (and immutable) update.
*/
public static PartitionUpdate emptyUpdate(CFMetaData metadata, DecoratedKey key)
{
return new PartitionUpdate(metadata, key, PartitionColumns.NONE, 0)
{
public Row.Writer staticWriter()
{
throw new UnsupportedOperationException();
}
public Row.Writer writer()
{
throw new UnsupportedOperationException();
}
public void addPartitionDeletion(DeletionTime deletionTime)
{
throw new UnsupportedOperationException();
}
public void addRangeTombstone(RangeTombstone range)
{
throw new UnsupportedOperationException();
}
};
}
/**
* Creates a partition update that entirely deletes a given partition.
*
* @param metadata the metadata for the created update.
* @param key the partition key for the partition that the created update should delete.
* @param timestamp the timestamp for the deletion.
* @param nowInSec the current time in seconds to use as local deletion time for the partition deletion.
*
* @return the newly created partition deletion update.
*/
public static PartitionUpdate fullPartitionDelete(CFMetaData metadata, DecoratedKey key, long timestamp, int nowInSec)
{
return new PartitionUpdate(metadata,
key,
new DeletionInfo(timestamp, nowInSec),
new RowDataBlock(Columns.NONE, 0, true, metadata.isCounter()),
PartitionColumns.NONE,
0);
}
/**
* Merges the provided updates, yielding a new update that incorporates all those updates.
*
@ -239,17 +271,30 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
public static PartitionUpdate merge(Collection<PartitionUpdate> updates)
{
assert !updates.isEmpty();
if (updates.size() == 1)
final int size = updates.size();
if (size == 1)
return Iterables.getOnlyElement(updates);
int totalSize = 0;
// Used when merging row to decide of liveness
int nowInSec = FBUtilities.nowInSeconds();
PartitionColumns.Builder builder = PartitionColumns.builder();
DecoratedKey key = null;
CFMetaData metadata = null;
MutableDeletionInfo deletion = MutableDeletionInfo.live();
Row staticRow = Rows.EMPTY_STATIC_ROW;
List<Iterator<Row>> updateRowIterators = new ArrayList<>(size);
RowStats stats = RowStats.NO_STATS;
for (PartitionUpdate update : updates)
{
totalSize += update.rows;
builder.addAll(update.columns());
deletion.add(update.deletionInfo());
if (!update.staticRow().isEmpty())
staticRow = staticRow == Rows.EMPTY_STATIC_ROW ? update.staticRow() : Rows.merge(staticRow, update.staticRow(), nowInSec);
updateRowIterators.add(update.iterator());
stats = stats.mergeWith(update.stats());
if (key == null)
key = update.partitionKey();
@ -262,23 +307,70 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
assert metadata.cfId.equals(update.metadata().cfId);
}
// Used when merging row to decide of liveness
int nowInSec = FBUtilities.nowInSeconds();
PartitionUpdate newUpdate = new PartitionUpdate(metadata, key, builder.build(), totalSize);
for (PartitionUpdate update : updates)
PartitionColumns columns = builder.build();
final Row.Merger merger = new Row.Merger(size, nowInSec, columns.regulars);
Iterator<Row> merged = MergeIterator.get(updateRowIterators, metadata.comparator, new MergeIterator.Reducer<Row, Row>()
{
newUpdate.deletionInfo.add(update.deletionInfo);
if (!update.staticRow().isEmpty())
@Override
public boolean trivialReduceIsTrivial()
{
if (newUpdate.staticRow().isEmpty())
newUpdate.staticRow = update.staticRow().takeAlias();
else
Rows.merge(newUpdate.staticRow(), update.staticRow(), newUpdate.columns().statics, newUpdate.staticWriter(), nowInSec, SecondaryIndexManager.nullUpdater);
return true;
}
for (Row row : update)
row.copyTo(newUpdate.writer);
}
return newUpdate;
public void reduce(int idx, Row current)
{
merger.add(idx, current);
}
protected Row getReduced()
{
// Note that while merger.getRow() can theoretically return null, it won't in this case because
// we don't pass an "activeDeletion".
return merger.merge(DeletionTime.LIVE);
}
@Override
protected void onKeyChange()
{
merger.clear();
}
});
List<Row> rows = new ArrayList<>();
Iterators.addAll(rows, merged);
return new PartitionUpdate(metadata, key, columns, staticRow, rows, deletion, stats, true, true);
}
/**
* Modify this update to set every timestamp for live data to {@code newTimestamp} and
* every deletion timestamp to {@code newTimestamp - 1}.
*
* There is no reason to use that expect on the Paxos code path, where we need ensure that
* anything inserted use the ballot timestamp (to respect the order of update decided by
* the Paxos algorithm). We use {@code newTimestamp - 1} for deletions because tombstones
* always win on timestamp equality and we don't want to delete our own insertions
* (typically, when we overwrite a collection, we first set a complex deletion to delete the
* previous collection before adding new elements. If we were to set that complex deletion
* to the same timestamp that the new elements, it would delete those elements). And since
* tombstones always wins on timestamp equality, using -1 guarantees our deletion will still
* delete anything from a previous update.
*/
public void updateAllTimestamp(long newTimestamp)
{
// We know we won't be updating that update again after this call, and doing is post built is potentially
// slightly more efficient (things are more "compact"). So force a build if it hasn't happened yet.
maybeBuild();
deletionInfo.updateAllTimestamp(newTimestamp - 1);
if (!staticRow.isEmpty())
staticRow = staticRow.updateAllTimestamp(newTimestamp);
for (int i = 0; i < rows.size(); i++)
rows.set(i, rows.get(i).updateAllTimestamp(newTimestamp));
}
/**
@ -291,7 +383,7 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
*/
public int operationCount()
{
return rowCount()
return rows.size()
+ deletionInfo.rangeCount()
+ (deletionInfo.getPartitionDeletion().isLive() ? 0 : 1);
}
@ -303,17 +395,29 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
*/
public int dataSize()
{
int clusteringSize = metadata().comparator.size();
int size = 0;
for (Row row : this)
{
size += row.clustering().dataSize();
for (Cell cell : row)
size += cell.dataSize();
for (ColumnData cd : row)
size += cd.dataSize();
}
return size;
}
@Override
public int rowCount()
{
maybeBuild();
return super.rowCount();
}
public RowStats stats()
{
maybeBuild();
return stats;
}
/**
* If a partition update has been read (and is thus unmodifiable), a call to this method
* makes the update modifiable again.
@ -325,21 +429,17 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
*/
public synchronized void allowNewUpdates()
{
if (!canReOpen)
throw new IllegalStateException("You cannot do more updates on collectCounterMarks has been called");
// This is synchronized to make extra sure things work properly even if this is
// called concurrently with sort() (which should be avoided in the first place, but
// better safe than sorry).
isSorted = false;
}
@Override
public int rowCount()
{
maybeSort();
return super.rowCount();
isBuilt = false;
}
/**
* Returns an iterator that iterators over the rows of this update in clustering order.
* Returns an iterator that iterates over the rows of this update in clustering order.
* <p>
* Note that this might trigger a sorting of the update, and as such the update will not
* be modifiable anymore after this call.
@ -349,14 +449,14 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
@Override
public Iterator<Row> iterator()
{
maybeSort();
maybeBuild();
return super.iterator();
}
@Override
protected SliceableUnfilteredRowIterator sliceableUnfilteredIterator(ColumnFilter columns, boolean reversed)
{
maybeSort();
maybeBuild();
return super.sliceableUnfilteredIterator(columns, reversed);
}
@ -370,8 +470,8 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
for (Row row : this)
{
metadata().comparator.validate(row.clustering());
for (Cell cell : row)
cell.validate();
for (ColumnData cd : row)
cd.validate();
}
}
@ -382,6 +482,27 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
*/
public long maxTimestamp()
{
maybeBuild();
long maxTimestamp = deletionInfo.maxTimestamp();
for (Row row : this)
{
maxTimestamp = Math.max(maxTimestamp, row.primaryKeyLivenessInfo().timestamp());
for (ColumnData cd : row)
{
if (cd.column().isSimple())
{
maxTimestamp = Math.max(maxTimestamp, ((Cell)cd).timestamp());
}
else
{
ComplexColumnData complexData = (ComplexColumnData)cd;
maxTimestamp = Math.max(maxTimestamp, complexData.complexDeletion().markedForDeleteAt());
for (Cell cell : complexData)
maxTimestamp = Math.max(maxTimestamp, cell.timestamp());
}
}
}
return maxTimestamp;
}
@ -394,62 +515,73 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
public List<CounterMark> collectCounterMarks()
{
assert metadata().isCounter();
maybeBuild();
// We will take aliases on the rows of this update, and update them in-place. So we should be sure the
// update is no immutable for all intent and purposes.
canReOpen = false;
InternalReusableClustering clustering = new InternalReusableClustering();
List<CounterMark> l = new ArrayList<>();
int i = 0;
for (Row row : this)
for (Row row : rows)
{
for (Cell cell : row)
for (Cell cell : row.cells())
{
if (cell.isCounterCell())
l.add(new CounterMark(clustering, i, cell.column(), cell.path()));
i++;
l.add(new CounterMark(row, cell.column(), cell.path()));
}
}
return l;
}
/**
* Returns a row writer for the static row of this partition update.
*
* @return a row writer for the static row of this partition update. A partition
* update contains only one static row so only one row should be written through
* this writer (but if multiple rows are added, the latest written one wins).
*/
public Row.Writer staticWriter()
private void assertNotBuilt()
{
return createStaticWriter();
if (isBuilt)
throw new IllegalStateException("An update should not be written again once it has been read");
}
public void addPartitionDeletion(DeletionTime deletionTime)
{
assertNotBuilt();
deletionInfo.add(deletionTime);
}
public void add(RangeTombstone range)
{
assertNotBuilt();
deletionInfo.add(range, metadata.comparator);
}
/**
* Returns a row writer to add (non-static) rows to this partition update.
* Adds a row to this update.
*
* @return a row writer to add (non-static) rows to this partition update.
* Multiple rows can be successively added this way and the rows added do not have
* to be in clustering order. Further, the same row can be added multiple time.
* There is no particular assumption made on the order of row added to a partition update. It is further
* allowed to add the same row (more precisely, multiple row objects for the same clustering).
*
* Note however that the columns contained in the added row must be a subset of the columns used when
* creating this update.
*
* @param row the row to add.
*/
public Row.Writer writer()
public void add(Row row)
{
if (isSorted)
throw new IllegalStateException("An update should not written again once it has been read");
if (row.isEmpty())
return;
return writer;
}
assertNotBuilt();
/**
* Returns a range tombstone marker writer to add range tombstones to this
* partition update.
* <p>
* Note that if more convenient, range tombstones can also be added using
* {@link addRangeTombstone}.
*
* @param isReverseOrder whether the range tombstone marker will be provided to the returned writer
* in clustering order or in reverse clustering order.
* @return a range tombstone marker writer to add range tombstones to this update.
*/
public RangeTombstoneMarker.Writer markerWriter(boolean isReverseOrder)
{
return new RangeTombstoneCollector(isReverseOrder);
if (row.isStatic())
{
// We test for == first because in most case it'll be true and that is faster
assert columns().statics == row.columns() || columns().statics.contains(row.columns());
staticRow = staticRow.isEmpty()
? row
: Rows.merge(staticRow, row, createdAtInSec);
}
else
{
// We test for == first because in most case it'll be true and that is faster
assert columns().regulars == row.columns() || columns().regulars.contains(row.columns());
rows.add(row);
}
}
/**
@ -459,160 +591,70 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
*/
public int size()
{
return rows;
return rows.size();
}
private void maybeSort()
private void maybeBuild()
{
if (isSorted)
if (isBuilt)
return;
sort();
build();
}
private synchronized void sort()
private synchronized void build()
{
if (isSorted)
if (isBuilt)
return;
if (rows <= 1)
if (rows.size() <= 1)
{
isSorted = true;
finishBuild();
return;
}
// Sort the rows - will still potentially contain duplicate (non-reconciled) rows
Sorting.sort(this);
Comparator<Row> comparator = metadata.comparator.rowComparator();
// Sort the rows. Because the same row can have been added multiple times, we can still have duplicates after that
Collections.sort(rows, comparator);
// Now find duplicates and merge them together
// Now find the duplicates and merge them together
int previous = 0; // The last element that was set
int nowInSec = FBUtilities.nowInSeconds();
for (int current = 1; current < rows; current++)
for (int current = 1; current < rows.size(); current++)
{
// There is really only 2 possible comparison: < 0 or == 0 since we've sorted already
int cmp = compare(previous, current);
Row previousRow = rows.get(previous);
Row currentRow = rows.get(current);
int cmp = comparator.compare(previousRow, currentRow);
if (cmp == 0)
{
// current and previous are the same row. Merge current into previous
// (and so previous + 1 will be "free").
merge(current, previous, nowInSec);
rows.set(previous, Rows.merge(previousRow, currentRow, createdAtInSec));
}
else
{
// data[current] != [previous], so move current just after previous if needs be
// current != previous, so move current just after previous if needs be
++previous;
if (previous != current)
move(current, previous);
rows.set(previous, currentRow);
}
}
// previous is on the last value to keep
rows = previous + 1;
for (int j = rows.size() - 1; j > previous; j--)
rows.remove(j);
isSorted = true;
finishBuild();
}
/**
* This method is note meant to be used externally: it is only public so this
* update conform to the {@link Sorting.Sortable} interface.
*/
public int compare(int i, int j)
private void finishBuild()
{
return metadata.comparator.compare(p1.setTo(i), p2.setTo(j));
}
protected class StaticWriter extends StaticRow.Builder
{
protected StaticWriter()
{
super(columns.statics, false, metadata().isCounter());
}
@Override
public void endOfRow()
{
super.endOfRow();
if (staticRow == null)
{
staticRow = build();
}
else
{
StaticRow.Builder builder = StaticRow.builder(columns.statics, true, metadata().isCounter());
Rows.merge(staticRow, build(), columns.statics, builder, FBUtilities.nowInSeconds());
staticRow = builder.build();
}
}
}
protected class RegularWriter extends Writer
{
// For complex column, the writer assumptions is that for a given row, cells of different
// complex columns are not intermingled (they also should be in cellPath order). We however
// don't yet guarantee that this will be the case for updates (both UpdateStatement and
// RowUpdateBuilder can potentially break that assumption; we could change those classes but
// that's non trivial, at least for UpdateStatement).
// To deal with that problem, we record which complex columns have been updated (for the current
// row) and if we detect a violation of our assumption, we switch the row we're writing
// into (which is ok because everything will be sorted and merged in maybeSort()).
private final Set<ColumnDefinition> updatedComplex = new HashSet();
private ColumnDefinition lastUpdatedComplex;
private CellPath lastUpdatedComplexPath;
public RegularWriter()
{
super(false);
}
@Override
public void writeCell(ColumnDefinition column, boolean isCounter, ByteBuffer value, LivenessInfo info, CellPath path)
{
if (column.isComplex())
{
if (updatedComplex.contains(column)
&& (!column.equals(lastUpdatedComplex) || (column.cellPathComparator().compare(path, lastUpdatedComplexPath)) <= 0))
{
// We've updated that complex already, but we've either updated another complex or it's not in order: as this
// break the writer assumption, switch rows.
endOfRow();
// Copy the clustering values from the previous row
int clusteringSize = metadata.clusteringColumns().size();
int base = (row - 1) * clusteringSize;
for (int i = 0; i < clusteringSize; i++)
writer.writeClusteringValue(clusterings[base + i]);
updatedComplex.clear();
}
lastUpdatedComplex = column;
lastUpdatedComplexPath = path;
updatedComplex.add(column);
}
super.writeCell(column, isCounter, value, info, path);
}
@Override
public void endOfRow()
{
super.endOfRow();
clear();
}
@Override
public Writer reset()
{
super.reset();
clear();
return this;
}
private void clear()
{
updatedComplex.clear();
lastUpdatedComplex = null;
lastUpdatedComplexPath = null;
}
RowStats.Collector collector = new RowStats.Collector();
deletionInfo.collectStats(collector);
for (Row row : rows)
Rows.collectStats(row, collector);
stats = collector.get();
isBuilt = true;
}
public static class PartitionUpdateSerializer
@ -648,7 +690,7 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
try (UnfilteredRowIterator iter = update.sliceableUnfilteredIterator())
{
assert !iter.isReverseOrder();
UnfilteredRowIteratorSerializer.serializer.serialize(iter, out, version, update.rows);
UnfilteredRowIteratorSerializer.serializer.serialize(iter, out, version, update.rows.size());
}
}
@ -666,37 +708,46 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
LegacyLayout.LegacyDeletionInfo info = LegacyLayout.LegacyDeletionInfo.serializer.deserialize(metadata, in, version);
int size = in.readInt();
Iterator<LegacyLayout.LegacyCell> cells = LegacyLayout.deserializeCells(metadata, in, flag, size);
SerializationHelper helper = new SerializationHelper(version, flag);
SerializationHelper helper = new SerializationHelper(metadata, version, flag);
try (UnfilteredRowIterator iterator = LegacyLayout.onWireCellstoUnfilteredRowIterator(metadata, key, info, cells, false, helper))
{
return UnfilteredRowIterators.toUpdate(iterator);
return PartitionUpdate.fromIterator(iterator);
}
}
assert key == null; // key is only there for the old format
UnfilteredRowIteratorSerializer.Header h = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(in, version, flag);
if (h.isEmpty)
return emptyUpdate(h.metadata, h.key);
UnfilteredRowIteratorSerializer.Header header = UnfilteredRowIteratorSerializer.serializer.deserializeHeader(in, version, flag);
if (header.isEmpty)
return emptyUpdate(header.metadata, header.key);
assert !h.isReversed;
assert h.rowEstimate >= 0;
PartitionUpdate upd = new PartitionUpdate(h.metadata,
h.key,
new DeletionInfo(h.partitionDeletion),
new RowDataBlock(h.sHeader.columns().regulars, h.rowEstimate, false, h.metadata.isCounter()),
h.sHeader.columns(),
h.rowEstimate);
assert !header.isReversed;
assert header.rowEstimate >= 0;
upd.staticRow = h.staticRow;
MutableDeletionInfo.Builder deletionBuilder = MutableDeletionInfo.builder(header.partitionDeletion, header.metadata.comparator, false);
List<Row> rows = new ArrayList<>(header.rowEstimate);
RangeTombstoneMarker.Writer markerWriter = upd.markerWriter(false);
UnfilteredRowIteratorSerializer.serializer.deserialize(in, new SerializationHelper(version, flag), h.sHeader, upd.writer(), markerWriter);
try (UnfilteredRowIterator partition = UnfilteredRowIteratorSerializer.serializer.deserialize(in, version, flag, header))
{
while (partition.hasNext())
{
Unfiltered unfiltered = partition.next();
if (unfiltered.kind() == Unfiltered.Kind.ROW)
rows.add((Row)unfiltered);
else
deletionBuilder.add((RangeTombstoneMarker)unfiltered);
}
}
// Mark sorted after we're read it all since that's what we use in the writer() method to detect bad uses
upd.isSorted = true;
return upd;
return new PartitionUpdate(header.metadata,
header.key,
header.sHeader.columns(),
header.staticRow,
rows,
deletionBuilder.build(),
header.sHeader.stats(),
true,
false);
}
public long serializedSize(PartitionUpdate update, int version)
@ -719,7 +770,7 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
try (UnfilteredRowIterator iter = update.sliceableUnfilteredIterator())
{
return UnfilteredRowIteratorSerializer.serializer.serializedSize(iter, version, update.rows);
return UnfilteredRowIteratorSerializer.serializer.serializedSize(iter, version, update.rows.size());
}
}
}
@ -729,16 +780,14 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
* us to update the counter value based on the pre-existing value read during the read-before-write that counters
* do. See {@link CounterMutation} to understand how this is used.
*/
public class CounterMark
public static class CounterMark
{
private final InternalReusableClustering clustering;
private final int row;
private final Row row;
private final ColumnDefinition column;
private final CellPath path;
private CounterMark(InternalReusableClustering clustering, int row, ColumnDefinition column, CellPath path)
private CounterMark(Row row, ColumnDefinition column, CellPath path)
{
this.clustering = clustering;
this.row = row;
this.column = column;
this.path = path;
@ -746,7 +795,7 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
public Clustering clustering()
{
return clustering.setTo(row);
return row.clustering();
}
public ColumnDefinition column()
@ -761,12 +810,17 @@ public class PartitionUpdate extends AbstractPartitionData implements Sorting.So
public ByteBuffer value()
{
return data.getValue(row, column, path);
return path == null
? row.getCell(column).value()
: row.getCell(column, path).value();
}
public void setValue(ByteBuffer value)
{
data.setValue(row, column, path, value);
// This is a bit of a giant hack as this is the only place where we mutate a Row object. This makes it more efficient
// for counters however and this won't be needed post-#6506 so that's probably fine.
assert row instanceof ArrayBackedRow;
((ArrayBackedRow)row).setValue(column, path, value);
}
}
}

View File

@ -0,0 +1,150 @@
/*
* 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.partitions;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
public abstract class PurgingPartitionIterator extends WrappingUnfilteredPartitionIterator
{
private final DeletionPurger purger;
private final int gcBefore;
private UnfilteredRowIterator next;
public PurgingPartitionIterator(UnfilteredPartitionIterator iterator, int gcBefore)
{
super(iterator);
this.gcBefore = gcBefore;
this.purger = new DeletionPurger()
{
public boolean shouldPurge(long timestamp, int localDeletionTime)
{
return timestamp < getMaxPurgeableTimestamp() && localDeletionTime < gcBefore;
}
};
}
protected abstract long getMaxPurgeableTimestamp();
// Called at the beginning of each new partition
protected void onNewPartition(DecoratedKey partitionKey)
{
}
// Called for each partition that had only purged infos and are empty post-purge.
protected void onEmptyPartitionPostPurge(DecoratedKey partitionKey)
{
}
// Called for every unfiltered. Meant for CompactionIterator to update progress
protected void updateProgress()
{
}
@Override
public boolean hasNext()
{
while (next == null && super.hasNext())
{
UnfilteredRowIterator iterator = super.next();
onNewPartition(iterator.partitionKey());
UnfilteredRowIterator purged = purge(iterator);
if (isForThrift() || !purged.isEmpty())
{
next = purged;
return true;
}
onEmptyPartitionPostPurge(purged.partitionKey());
}
return next != null;
}
@Override
public UnfilteredRowIterator next()
{
UnfilteredRowIterator toReturn = next;
next = null;
return toReturn;
}
private UnfilteredRowIterator purge(final UnfilteredRowIterator iter)
{
return new AlteringUnfilteredRowIterator(iter)
{
@Override
public DeletionTime partitionLevelDeletion()
{
DeletionTime dt = iter.partitionLevelDeletion();
return purger.shouldPurge(dt) ? DeletionTime.LIVE : dt;
}
@Override
public Row computeNextStatic(Row row)
{
return row.purge(purger, gcBefore);
}
@Override
public Row computeNext(Row row)
{
return row.purge(purger, gcBefore);
}
@Override
public RangeTombstoneMarker computeNext(RangeTombstoneMarker marker)
{
boolean reversed = isReverseOrder();
if (marker.isBoundary())
{
// We can only skip the whole marker if both deletion time are purgeable.
// If only one of them is, filterTombstoneMarker will deal with it.
RangeTombstoneBoundaryMarker boundary = (RangeTombstoneBoundaryMarker)marker;
boolean shouldPurgeClose = purger.shouldPurge(boundary.closeDeletionTime(reversed));
boolean shouldPurgeOpen = purger.shouldPurge(boundary.openDeletionTime(reversed));
if (shouldPurgeClose)
{
if (shouldPurgeOpen)
return null;
return boundary.createCorrespondingOpenMarker(reversed);
}
return shouldPurgeOpen
? boundary.createCorrespondingCloseMarker(reversed)
: marker;
}
else
{
return purger.shouldPurge(((RangeTombstoneBoundMarker)marker).deletionTime()) ? null : marker;
}
}
@Override
public Unfiltered next()
{
Unfiltered next = super.next();
updateProgress();
return next;
}
};
}
};

View File

@ -1,103 +0,0 @@
/*
* 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.partitions;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
public abstract class TombstonePurgingPartitionIterator extends FilteringPartitionIterator
{
private final int gcBefore;
public TombstonePurgingPartitionIterator(UnfilteredPartitionIterator iterator, int gcBefore)
{
super(iterator);
this.gcBefore = gcBefore;
}
protected abstract long getMaxPurgeableTimestamp();
protected FilteringRow makeRowFilter()
{
return new FilteringRow()
{
@Override
protected boolean include(LivenessInfo info)
{
return !info.hasLocalDeletionTime() || !info.isPurgeable(getMaxPurgeableTimestamp(), gcBefore);
}
@Override
protected boolean include(DeletionTime dt)
{
return includeDelTime(dt);
}
@Override
protected boolean include(ColumnDefinition c, DeletionTime dt)
{
return includeDelTime(dt);
}
};
}
private boolean includeDelTime(DeletionTime dt)
{
return dt.isLive() || !dt.isPurgeable(getMaxPurgeableTimestamp(), gcBefore);
}
@Override
protected boolean includePartitionDeletion(DeletionTime dt)
{
return includeDelTime(dt);
}
@Override
protected boolean includeRangeTombstoneMarker(RangeTombstoneMarker marker)
{
if (marker.isBoundary())
{
// We can only skip the whole marker if both deletion time are purgeable.
// If only one of them is, filterTombstoneMarker will deal with it.
RangeTombstoneBoundaryMarker boundary = (RangeTombstoneBoundaryMarker)marker;
return includeDelTime(boundary.endDeletionTime()) || includeDelTime(boundary.startDeletionTime());
}
else
{
return includeDelTime(((RangeTombstoneBoundMarker)marker).deletionTime());
}
}
@Override
protected RangeTombstoneMarker filterRangeTombstoneMarker(RangeTombstoneMarker marker, boolean reversed)
{
if (!marker.isBoundary())
return marker;
// Note that we know this is called after includeRangeTombstoneMarker. So if one of the deletion time is
// purgeable, we know the other one isn't.
RangeTombstoneBoundaryMarker boundary = (RangeTombstoneBoundaryMarker)marker;
if (!(includeDelTime(boundary.closeDeletionTime(reversed))))
return boundary.createCorrespondingCloseBound(reversed);
else if (!(includeDelTime(boundary.openDeletionTime(reversed))))
return boundary.createCorrespondingOpenBound(reversed);
return boundary;
}
};

View File

@ -19,13 +19,10 @@ package org.apache.cassandra.db.partitions;
import java.io.IOError;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.*;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.rows.*;
import org.apache.cassandra.io.util.DataInputPlus;
@ -33,25 +30,14 @@ import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.utils.MergeIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Static methods to work with partition iterators.
*/
public abstract class UnfilteredPartitionIterators
{
private static final Logger logger = LoggerFactory.getLogger(UnfilteredPartitionIterators.class);
private static final Serializer serializer = new Serializer();
private static final Comparator<UnfilteredRowIterator> partitionComparator = new Comparator<UnfilteredRowIterator>()
{
public int compare(UnfilteredRowIterator p1, UnfilteredRowIterator p2)
{
return p1.partitionKey().compareTo(p2.partitionKey());
}
};
private static final Comparator<UnfilteredRowIterator> partitionComparator = (p1, p2) -> p1.partitionKey().compareTo(p2.partitionKey());
public static final UnfilteredPartitionIterator EMPTY = new AbstractUnfilteredPartitionIterator()
{
@ -242,28 +228,6 @@ public abstract class UnfilteredPartitionIterators
};
}
/**
* Convert all expired cells to equivalent tombstones.
* <p>
* See {@link UnfilteredRowIterators#convertExpiredCellsToTombstones} for details.
*
* @param iterator the iterator in which to conver expired cells.
* @param nowInSec the current time to use to decide if a cell is expired.
* @return an iterator that returns the same data than {@code iterator} but with all expired cells converted
* to equivalent tombstones.
*/
public static UnfilteredPartitionIterator convertExpiredCellsToTombstones(UnfilteredPartitionIterator iterator, final int nowInSec)
{
return new WrappingUnfilteredPartitionIterator(iterator)
{
@Override
protected UnfilteredRowIterator computeNext(UnfilteredRowIterator iter)
{
return UnfilteredRowIterators.convertExpiredCellsToTombstones(iter, nowInSec);
}
};
}
public static UnfilteredPartitionIterator mergeLazily(final List<? extends UnfilteredPartitionIterator> iterators, final int nowInSec)
{
assert !iterators.isEmpty();
@ -330,52 +294,6 @@ public abstract class UnfilteredPartitionIterators
};
}
public static UnfilteredPartitionIterator removeDroppedColumns(UnfilteredPartitionIterator iterator, final Map<ByteBuffer, CFMetaData.DroppedColumn> droppedColumns)
{
return new FilteringPartitionIterator(iterator)
{
@Override
protected FilteringRow makeRowFilter()
{
return new FilteringRow()
{
@Override
protected boolean include(Cell cell)
{
return include(cell.column(), cell.livenessInfo().timestamp());
}
@Override
protected boolean include(ColumnDefinition c, DeletionTime dt)
{
return include(c, dt.markedForDeleteAt());
}
private boolean include(ColumnDefinition column, long timestamp)
{
CFMetaData.DroppedColumn dropped = droppedColumns.get(column.name.bytes);
return dropped == null || timestamp > dropped.droppedTime;
}
};
}
@Override
protected boolean shouldFilter(UnfilteredRowIterator iterator)
{
// TODO: We could have row iterators return the smallest timestamp they might return
// (which we can get from sstable stats), and ignore any dropping if that smallest
// timestamp is bigger that the biggest droppedColumns timestamp.
// If none of the dropped columns is part of the columns that the iterator actually returns, there is nothing to do;
for (ColumnDefinition c : iterator.columns())
if (droppedColumns.containsKey(c.name.bytes))
return true;
return false;
}
};
}
public static void digest(UnfilteredPartitionIterator iterator, MessageDigest digest)
{
try (UnfilteredPartitionIterator iter = iterator)

View File

@ -34,30 +34,12 @@ import org.apache.cassandra.utils.FBUtilities;
*/
public abstract class AbstractCell implements Cell
{
public boolean isLive(int nowInSec)
{
return livenessInfo().isLive(nowInSec);
}
public boolean isTombstone()
{
return livenessInfo().hasLocalDeletionTime() && !livenessInfo().hasTTL();
}
public boolean isExpiring()
{
return livenessInfo().hasTTL();
}
public void writeTo(Row.Writer writer)
{
writer.writeCell(column(), isCounterCell(), value(), livenessInfo(), path());
}
public void digest(MessageDigest digest)
{
digest.update(value().duplicate());
livenessInfo().digest(digest);
FBUtilities.updateWithLong(digest, timestamp());
FBUtilities.updateWithInt(digest, localDeletionTime());
FBUtilities.updateWithInt(digest, ttl());
FBUtilities.updateWithBoolean(digest, isCounterCell());
if (path() != null)
path().digest(digest);
@ -67,7 +49,12 @@ public abstract class AbstractCell implements Cell
{
column().validateCellValue(value());
livenessInfo().validate();
if (ttl() < 0)
throw new MarshalException("A TTL should not be negative");
if (localDeletionTime() < 0)
throw new MarshalException("A local deletion time should not be negative");
if (isExpiring() && localDeletionTime() == NO_DELETION_TIME)
throw new MarshalException("Shoud not have a TTL without an associated local deletion time");
// If cell is a tombstone, it shouldn't have a value.
if (isTombstone() && value().hasRemaining())
@ -77,59 +64,58 @@ public abstract class AbstractCell implements Cell
column().validateCellPath(path());
}
public int dataSize()
{
int size = value().remaining() + livenessInfo().dataSize();
if (path() != null)
size += path().dataSize();
return size;
}
@Override
public boolean equals(Object other)
{
if (this == other)
return true;
if(!(other instanceof Cell))
return false;
Cell that = (Cell)other;
return this.column().equals(that.column())
&& this.isCounterCell() == that.isCounterCell()
&& this.timestamp() == that.timestamp()
&& this.ttl() == that.ttl()
&& this.localDeletionTime() == that.localDeletionTime()
&& Objects.equals(this.value(), that.value())
&& Objects.equals(this.livenessInfo(), that.livenessInfo())
&& Objects.equals(this.path(), that.path());
}
@Override
public int hashCode()
{
return Objects.hash(column(), isCounterCell(), value(), livenessInfo(), path());
return Objects.hash(column(), isCounterCell(), timestamp(), ttl(), localDeletionTime(), value(), path());
}
@Override
public String toString()
{
if (isCounterCell())
return String.format("[%s=%d ts=%d]", column().name, CounterContext.instance().total(value()), livenessInfo().timestamp());
return String.format("[%s=%d ts=%d]", column().name, CounterContext.instance().total(value()), timestamp());
AbstractType<?> type = column().type;
if (type instanceof CollectionType && type.isMultiCell())
{
CollectionType ct = (CollectionType)type;
return String.format("[%s[%s]=%s info=%s]",
return String.format("[%s[%s]=%s %s]",
column().name,
ct.nameComparator().getString(path().get(0)),
ct.valueComparator().getString(value()),
livenessInfo());
livenessInfoString());
}
return String.format("[%s=%s info=%s]", column().name, type.getString(value()), livenessInfo());
return String.format("[%s=%s %s]", column().name, type.getString(value()), livenessInfoString());
}
public Cell takeAlias()
private String livenessInfoString()
{
// Cell is always used as an Aliasable object but as the code currently
// never need to alias a cell outside of its valid scope, we don't yet
// need that.
throw new UnsupportedOperationException();
if (isExpiring())
return String.format("ts=%d ttl=%d ldt=%d", timestamp(), ttl(), localDeletionTime());
else if (isTombstone())
return String.format("ts=%d ldt=%d", timestamp(), localDeletionTime());
else
return String.format("ts=%d", timestamp());
}
}

View File

@ -41,6 +41,21 @@ public abstract class AbstractRangeTombstoneMarker implements RangeTombstoneMark
return Unfiltered.Kind.RANGE_TOMBSTONE_MARKER;
}
public boolean isBoundary()
{
return bound.isBoundary();
}
public boolean isOpen(boolean reversed)
{
return bound.isOpen(reversed);
}
public boolean isClose(boolean reversed)
{
return bound.isClose(reversed);
}
public void validateData(CFMetaData metadata)
{
Slice.Bound bound = clustering();
@ -56,16 +71,4 @@ public abstract class AbstractRangeTombstoneMarker implements RangeTombstoneMark
{
return toString(metadata);
}
protected void copyBoundTo(RangeTombstoneMarker.Writer writer)
{
for (int i = 0; i < bound.size(); i++)
writer.writeClusteringValue(bound.get(i));
writer.writeBoundKind(bound.kind());
}
public Unfiltered takeAlias()
{
return this;
}
}

View File

@ -1,207 +0,0 @@
/*
* 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.util.Iterator;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.utils.SearchIterator;
public abstract class AbstractReusableRow extends AbstractRow
{
private CellData.ReusableCell simpleCell;
private ComplexRowDataBlock.ReusableIterator complexCells;
private DeletionTimeArray.Cursor complexDeletionCursor;
private RowDataBlock.ReusableIterator iterator;
public AbstractReusableRow()
{
}
protected abstract int row();
protected abstract RowDataBlock data();
private CellData.ReusableCell simpleCell()
{
if (simpleCell == null)
simpleCell = SimpleRowDataBlock.reusableCell();
return simpleCell;
}
private ComplexRowDataBlock.ReusableIterator complexCells()
{
if (complexCells == null)
complexCells = ComplexRowDataBlock.reusableComplexCells();
return complexCells;
}
private DeletionTimeArray.Cursor complexDeletionCursor()
{
if (complexDeletionCursor == null)
complexDeletionCursor = ComplexRowDataBlock.complexDeletionCursor();
return complexDeletionCursor;
}
private RowDataBlock.ReusableIterator reusableIterator()
{
if (iterator == null)
iterator = RowDataBlock.reusableIterator();
return iterator;
}
public Columns columns()
{
return data().columns();
}
public Cell getCell(ColumnDefinition c)
{
assert !c.isComplex();
if (data().simpleData == null)
return null;
int idx = columns().simpleIdx(c, 0);
if (idx < 0)
return null;
return simpleCell().setTo(data().simpleData.data, c, (row() * columns().simpleColumnCount()) + idx);
}
public Cell getCell(ColumnDefinition c, CellPath path)
{
assert c.isComplex();
ComplexRowDataBlock data = data().complexData;
if (data == null)
return null;
int idx = data.cellIdx(row(), c, path);
if (idx < 0)
return null;
return simpleCell().setTo(data.cellData(row()), c, idx);
}
public Iterator<Cell> getCells(ColumnDefinition c)
{
assert c.isComplex();
return complexCells().setTo(data().complexData, row(), c);
}
public boolean hasComplexDeletion()
{
return data().hasComplexDeletion(row());
}
public DeletionTime getDeletion(ColumnDefinition c)
{
assert c.isComplex();
if (data().complexData == null)
return DeletionTime.LIVE;
int idx = data().complexData.complexDeletionIdx(row(), c);
return idx < 0
? DeletionTime.LIVE
: complexDeletionCursor().setTo(data().complexData.complexDelTimes, idx);
}
public Iterator<Cell> iterator()
{
return reusableIterator().setTo(data(), row());
}
public SearchIterator<ColumnDefinition, ColumnData> searchIterator()
{
return new SearchIterator<ColumnDefinition, ColumnData>()
{
private int simpleIdx = 0;
public boolean hasNext()
{
// TODO: we can do better, but we expect users to no rely on this anyway
return true;
}
public ColumnData next(ColumnDefinition column)
{
if (column.isComplex())
{
// TODO: this is sub-optimal
Iterator<Cell> cells = getCells(column);
return cells == null ? null : new ColumnData(column, null, cells, getDeletion(column));
}
else
{
int idx = columns().simpleIdx(column, simpleIdx);
if (idx < 0)
return null;
Cell cell = simpleCell().setTo(data().simpleData.data, column, (row() * columns().simpleColumnCount()) + idx);
simpleIdx = idx + 1;
return cell == null ? null : new ColumnData(column, cell, null, null);
}
}
};
}
public Row takeAlias()
{
final Clustering clustering = clustering().takeAlias();
final LivenessInfo info = primaryKeyLivenessInfo().takeAlias();
final DeletionTime deletion = deletion().takeAlias();
final RowDataBlock data = data();
final int row = row();
return new AbstractReusableRow()
{
protected RowDataBlock data()
{
return data;
}
protected int row()
{
return row;
}
public Clustering clustering()
{
return clustering;
}
public LivenessInfo primaryKeyLivenessInfo()
{
return info;
}
public DeletionTime deletion()
{
return deletion;
}
@Override
public Row takeAlias()
{
return this;
}
};
}
}

View File

@ -18,11 +18,11 @@ 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.collect.Iterables;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.CollectionType;
import org.apache.cassandra.serializers.MarshalException;
@ -46,19 +46,14 @@ public abstract class AbstractRow implements Row
if (primaryKeyLivenessInfo().isLive(nowInSec))
return true;
for (Cell cell : this)
if (cell.isLive(nowInSec))
return true;
return false;
return Iterables.any(cells(), cell -> cell.isLive(nowInSec));
}
public boolean isEmpty()
{
return !primaryKeyLivenessInfo().hasTimestamp()
return primaryKeyLivenessInfo().isEmpty()
&& deletion().isLive()
&& !iterator().hasNext()
&& !hasComplexDeletion();
&& !iterator().hasNext();
}
public boolean isStatic()
@ -74,36 +69,8 @@ public abstract class AbstractRow implements Row
deletion().digest(digest);
primaryKeyLivenessInfo().digest(digest);
Iterator<ColumnDefinition> iter = columns().complexColumns();
while (iter.hasNext())
getDeletion(iter.next()).digest(digest);
for (Cell cell : this)
cell.digest(digest);
}
/**
* Copy this row to the provided writer.
*
* @param writer the row writer to write this row to.
*/
public void copyTo(Row.Writer writer)
{
Rows.writeClustering(clustering(), writer);
writer.writePartitionKeyLivenessInfo(primaryKeyLivenessInfo());
writer.writeRowDeletion(deletion());
for (Cell cell : this)
cell.writeTo(writer);
for (int i = 0; i < columns().complexColumnCount(); i++)
{
ColumnDefinition c = columns().getComplex(i);
DeletionTime dt = getDeletion(c);
if (!dt.isLive())
writer.writeComplexDeletion(c, dt);
}
writer.endOfRow();
for (ColumnData cd : this)
cd.digest(digest);
}
public void validateData(CFMetaData metadata)
@ -120,8 +87,8 @@ public abstract class AbstractRow implements Row
if (deletion().localDeletionTime() < 0)
throw new MarshalException("A local deletion time should not be negative");
for (Cell cell : this)
cell.validate();
for (ColumnData cd : this)
cd.validate();
}
public String toString(CFMetaData metadata)
@ -142,33 +109,43 @@ public abstract class AbstractRow implements Row
}
sb.append(": ").append(clustering().toString(metadata)).append(" | ");
boolean isFirst = true;
ColumnDefinition prevColumn = null;
for (Cell cell : this)
for (ColumnData cd : this)
{
if (isFirst) isFirst = false; else sb.append(", ");
if (fullDetails)
{
if (cell.column().isComplex() && !cell.column().equals(prevColumn))
if (cd.column().isSimple())
{
DeletionTime complexDel = getDeletion(cell.column());
if (!complexDel.isLive())
sb.append("del(").append(cell.column().name).append(")=").append(complexDel).append(", ");
}
sb.append(cell);
prevColumn = cell.column();
}
else
{
sb.append(cell.column().name);
if (cell.column().type instanceof CollectionType)
{
CollectionType ct = (CollectionType)cell.column().type;
sb.append("[").append(ct.nameComparator().getString(cell.path().get(0))).append("]");
sb.append("=").append(ct.valueComparator().getString(cell.value()));
sb.append(cd);
}
else
{
sb.append("=").append(cell.column().type.getString(cell.value()));
ComplexColumnData complexData = (ComplexColumnData)cd;
if (!complexData.complexDeletion().isLive())
sb.append("del(").append(cd.column().name).append(")=").append(complexData.complexDeletion());
for (Cell cell : complexData)
sb.append(", ").append(cell);
}
}
else
{
if (cd.column().isSimple())
{
Cell cell = (Cell)cd;
sb.append(cell.column().name).append('=').append(cell.column().type.getString(cell.value()));
}
else
{
ComplexColumnData complexData = (ComplexColumnData)cd;
CollectionType ct = (CollectionType)cd.column().type;
sb.append(cd.column().name).append("={");
int i = 0;
for (Cell cell : complexData)
{
sb.append(i++ == 0 ? "" : ", ");
sb.append(ct.nameComparator().getString(cell.path().get(0))).append("->").append(ct.valueComparator().getString(cell.value()));
}
sb.append('}');
}
}
}
@ -188,22 +165,15 @@ public abstract class AbstractRow implements Row
|| !this.deletion().equals(that.deletion()))
return false;
Iterator<Cell> thisCells = this.iterator();
Iterator<Cell> thatCells = that.iterator();
while (thisCells.hasNext())
{
if (!thatCells.hasNext() || !thisCells.next().equals(thatCells.next()))
return false;
}
return !thatCells.hasNext();
return Iterables.elementsEqual(this, that);
}
@Override
public int hashCode()
{
int hash = Objects.hash(clustering(), columns(), primaryKeyLivenessInfo(), deletion());
for (Cell cell : this)
hash += 31 * cell.hashCode();
for (ColumnData cd : this)
hash += 31 * cd.hashCode();
return hash;
}
}

View File

@ -90,18 +90,4 @@ public abstract class AbstractUnfilteredRowIterator extends AbstractIterator<Unf
public void close()
{
}
public static boolean equal(UnfilteredRowIterator a, UnfilteredRowIterator b)
{
return Objects.equals(a.columns(), b.columns())
&& Objects.equals(a.metadata(), b.metadata())
&& Objects.equals(a.isReverseOrder(), b.isReverseOrder())
&& Objects.equals(a.partitionKey(), b.partitionKey())
&& Objects.equals(a.partitionLevelDeletion(), b.partitionLevelDeletion())
&& Objects.equals(a.staticRow(), b.staticRow())
&& Objects.equals(a.stats(), b.stats())
&& Objects.equals(a.metadata(), b.metadata())
&& Iterators.elementsEqual(a, b);
}
}

Some files were not shown because too many files have changed in this diff Show More