mirror of https://github.com/apache/cassandra
Simplify some 8099's implementations
patch by slebresne; reviewed by iamalesky for CASSANDRA-9705
This commit is contained in:
parent
7659ae2eef
commit
2457599427
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue