mirror of https://github.com/apache/cassandra
Fix not returning live subcolumns of deleted supercolumns
patch by jbellis; reviewed by junrao for CASSANDRA-583 git-svn-id: https://svn.apache.org/repos/asf/incubator/cassandra/trunk@886886 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
9cc162d776
commit
83cce240bf
|
|
@ -7,6 +7,9 @@
|
|||
* fix cleanup of local "system" keyspace (CASSANDRA-576)
|
||||
* improve computation of cluster load balance (CASSANDRA-554)
|
||||
* added count and column/row delete to cassandra-cli (CASSANDRA-594)
|
||||
* fix returning live subcolumns of deleted supercolumns (CASSANDRA-583)
|
||||
* respect JAVA_HOME in bin/ scripts (several tickets)
|
||||
* add StorageService.initClient for fat clients on the JVM (CASSANDRA-535)
|
||||
|
||||
|
||||
0.5.0 beta
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ public final class Column implements IColumn
|
|||
return timestamp;
|
||||
}
|
||||
|
||||
public long mostRecentChangeAt()
|
||||
public long mostRecentLiveChangeAt()
|
||||
{
|
||||
return timestamp;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1117,7 +1117,7 @@ public final class ColumnFamilyStore implements ColumnFamilyStoreMBean
|
|||
if (filter.path.superColumnName != null)
|
||||
{
|
||||
QueryFilter nameFilter = new NamesQueryFilter(filter.key, new QueryPath(columnFamily_), filter.path.superColumnName);
|
||||
ColumnFamily cf = getColumnFamilyInternal(nameFilter, getDefaultGCBefore());
|
||||
ColumnFamily cf = getColumnFamilyInternal(nameFilter, gcBefore);
|
||||
if (cf == null || cf.getColumnCount() == 0)
|
||||
return cf;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ public interface IColumn
|
|||
|
||||
public boolean isMarkedForDelete();
|
||||
public long getMarkedForDeleteAt();
|
||||
public long mostRecentChangeAt();
|
||||
public long mostRecentLiveChangeAt();
|
||||
public byte[] name();
|
||||
public int size();
|
||||
public int serializedSize();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ package org.apache.cassandra.db;
|
|||
|
||||
import java.io.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.security.MessageDigest;
|
||||
|
|
@ -158,14 +157,14 @@ public final class SuperColumn implements IColumn, IColumnContainer
|
|||
throw new IllegalArgumentException("Timestamp was requested for a column that does not exist.");
|
||||
}
|
||||
|
||||
public long mostRecentChangeAt()
|
||||
public long mostRecentLiveChangeAt()
|
||||
{
|
||||
long max = Long.MIN_VALUE;
|
||||
for (IColumn column : columns_.values())
|
||||
{
|
||||
if (column.mostRecentChangeAt() > max)
|
||||
if (!column.isMarkedForDelete() && column.timestamp() > max)
|
||||
{
|
||||
max = column.mostRecentChangeAt();
|
||||
max = column.timestamp();
|
||||
}
|
||||
}
|
||||
return max;
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ public class NamesQueryFilter extends QueryFilter
|
|||
while (reducedColumns.hasNext())
|
||||
{
|
||||
IColumn column = reducedColumns.next();
|
||||
if (!column.isMarkedForDelete() || column.getLocalDeletionTime() > gcBefore)
|
||||
if (QueryFilter.isRelevant(column, container, gcBefore))
|
||||
container.addColumn(column);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,4 +111,14 @@ public abstract class QueryFilter
|
|||
{
|
||||
return path.columnFamilyName;
|
||||
}
|
||||
|
||||
public static boolean isRelevant(IColumn column, IColumnContainer container, int gcBefore)
|
||||
{
|
||||
// the column itself must be not gc-able (it is live, or a still relevant tombstone, or has live subcolumns), (1)
|
||||
// and if its container is deleted, the column must be changed more recently than the container tombstone (2)
|
||||
// (since otherwise, the only thing repair cares about is the container tombstone)
|
||||
long maxChange = column.mostRecentLiveChangeAt();
|
||||
return (!column.isMarkedForDelete() || column.getLocalDeletionTime() > gcBefore || maxChange > column.getMarkedForDeleteAt()) // (1)
|
||||
&& (!container.isMarkedForDelete() || maxChange > container.getMarkedForDeleteAt()); // (2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import org.apache.commons.collections.IteratorUtils;
|
|||
import org.apache.cassandra.io.SSTableReader;
|
||||
import org.apache.cassandra.db.*;
|
||||
import org.apache.cassandra.db.marshal.AbstractType;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
|
||||
public class SliceQueryFilter extends QueryFilter
|
||||
{
|
||||
|
|
@ -124,20 +123,14 @@ public class SliceQueryFilter extends QueryFilter
|
|||
// only count live columns towards the `count` criteria
|
||||
if (!column.isMarkedForDelete()
|
||||
&& (!container.isMarkedForDelete()
|
||||
|| column.mostRecentChangeAt() > container.getMarkedForDeleteAt()))
|
||||
|| column.mostRecentLiveChangeAt() > container.getMarkedForDeleteAt()))
|
||||
{
|
||||
liveColumns++;
|
||||
}
|
||||
|
||||
// but we need to add all non-gc-able columns to the result for read repair:
|
||||
// the column itself must be not gc-able, (1)
|
||||
// and if its container is deleted, the column must be changed more recently than the container tombstone (2)
|
||||
// (since otherwise, the only thing repair cares about is the container tombstone)
|
||||
if ((!column.isMarkedForDelete() || column.getLocalDeletionTime() > gcBefore) // (1)
|
||||
&& (!container.isMarkedForDelete() || column.mostRecentChangeAt() > container.getMarkedForDeleteAt())) // (2)
|
||||
{
|
||||
if (QueryFilter.isRelevant(column, container, gcBefore))
|
||||
container.addColumn(column);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -452,7 +452,7 @@ class TestMutations(CassandraTester):
|
|||
|
||||
# Test resurrection. First, re-insert the value w/ older timestamp,
|
||||
# and make sure it stays removed:
|
||||
client.insert('Keyspace1', 'key1', ColumnPath('Super1', 'sc2', _i64(5)), 'value5', 0, ConsistencyLevel.ONE)
|
||||
client.insert('Keyspace1', 'key1', ColumnPath('Super1', 'sc2', _i64(5)), 'value5', 1, ConsistencyLevel.ONE)
|
||||
super_columns = [result.super_column
|
||||
for result in _big_slice('Keyspace1', 'key1', ColumnParent('Super1'))]
|
||||
assert super_columns == super_columns_expected, super_columns
|
||||
|
|
@ -465,6 +465,12 @@ class TestMutations(CassandraTester):
|
|||
SuperColumn(name='sc2', columns=[Column(_i64(5), 'value5', 6)])]
|
||||
assert super_columns == super_columns_expected, super_columns
|
||||
|
||||
# check slicing at the subcolumn level too
|
||||
p = SlicePredicate(slice_range=SliceRange('', '', False, 1000))
|
||||
columns = [result.column
|
||||
for result in client.get_slice('Keyspace1', 'key1', ColumnParent('Super1', 'sc2'), p, ConsistencyLevel.ONE)]
|
||||
assert columns == [Column(_i64(5), 'value5', 6)], columns
|
||||
|
||||
|
||||
def test_empty_range(self):
|
||||
assert client.get_key_range('Keyspace1', 'Standard1', '', '', 1000, ConsistencyLevel.ONE) == []
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ public class RemoveSuperColumnTest
|
|||
private void validateRemoveWithNewData() throws IOException
|
||||
{
|
||||
ColumnFamilyStore store = Table.open("Keyspace1").getColumnFamilyStore("Super2");
|
||||
ColumnFamily resolved = store.getColumnFamily(new NamesQueryFilter("key1", new QueryPath("Super2", "SC1".getBytes()), getBytes(2)));
|
||||
ColumnFamily resolved = store.getColumnFamily(new NamesQueryFilter("key1", new QueryPath("Super2", "SC1".getBytes()), getBytes(2)), Integer.MAX_VALUE);
|
||||
Collection<IColumn> subColumns = resolved.getSortedColumns().iterator().next().getSubColumns();
|
||||
assert subColumns.size() == 1;
|
||||
assert subColumns.iterator().next().timestamp() == 2;
|
||||
|
|
|
|||
Loading…
Reference in New Issue