Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions src/java/org/apache/cassandra/db/rows/AbstractRow.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,6 @@ public Unfiltered.Kind kind()
return Unfiltered.Kind.ROW;
}

@Override
public boolean hasLiveData(long nowInSec, boolean enforceStrictLiveness)
{
if (primaryKeyLivenessInfo().isLive(nowInSec))
return true;
else if (enforceStrictLiveness)
return false;
return Iterables.any(cells(), cell -> cell.isLive(nowInSec));
}

public boolean isStatic()
{
return clustering() == Clustering.STATIC_CLUSTERING;
Expand Down
15 changes: 15 additions & 0 deletions src/java/org/apache/cassandra/db/rows/BTreeRow.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.primitives.Ints;

Expand Down Expand Up @@ -198,6 +199,20 @@ private static long minDeletionTime(ColumnData cd)
return cd.column().isSimple() ? minDeletionTime((Cell<?>) cd) : minDeletionTime((ComplexColumnData)cd);
}

@Override
public boolean hasLiveData(long nowInSec, boolean enforceStrictLiveness)
{
if (primaryKeyLivenessInfo().isLive(nowInSec))
return true;
else if (enforceStrictLiveness)
return false;
// Fast path to avoid cell iteration
// if there are no deleted cells then we can just check if we have at least one cell
if (!hasDeletion(nowInSec))
return !BTree.isEmpty(btree);
Comment thread
netudima marked this conversation as resolved.
return Iterables.any(cells(), cell -> cell.isLive(nowInSec));
}

public void apply(Consumer<ColumnData> function)
{
BTree.apply(btree, function);
Expand Down
224 changes: 224 additions & 0 deletions test/unit/org/apache/cassandra/db/rows/BTreeRowHasLiveDataTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/*
* 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.math.BigInteger;
import java.nio.ByteBuffer;

import org.junit.Assert;
import org.junit.Test;

import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.db.Clustering;
import org.apache.cassandra.db.DeletionTime;
import org.apache.cassandra.db.LivenessInfo;
import org.apache.cassandra.db.marshal.IntegerType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.utils.ByteBufferUtil;

public class BTreeRowHasLiveDataTest
{
private static final TableMetadata metadata;
private static final ColumnMetadata intColumn;
private static final ColumnMetadata mapColumn;
private static final Clustering<?> clusteringKey;

static
{
metadata = TableMetadata.builder("ks", "tbl")
.addPartitionKeyColumn("k", IntegerType.instance)
.addClusteringColumn("c", IntegerType.instance)
.addRegularColumn("v", IntegerType.instance)
.addRegularColumn("m", MapType.getInstance(IntegerType.instance, IntegerType.instance, true))
.build();
intColumn = metadata.getColumn(new ColumnIdentifier("v", false));
mapColumn = metadata.getColumn(new ColumnIdentifier("m", false));
clusteringKey = metadata.comparator.make(BigInteger.valueOf(1));
}

private static final ByteBuffer KEY1 = ByteBufferUtil.bytes(1);
private static final ByteBuffer KEY2 = ByteBufferUtil.bytes(2);

private static final ByteBuffer VAL = ByteBufferUtil.bytes(10);

private static Row.Builder newBuilder()
{
Row.Builder b = BTreeRow.unsortedBuilder();
b.newRow(clusteringKey);
return b;
}

// TRIGGER: row with one live regular cell and an EMPTY primary key liveness.
@Test
public void rowWithOneLiveRegularCellAndEmptyPrimaryKeyLiveness_returnsTrue()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
// No addPrimaryKeyLivenessInfo => LivenessInfo.EMPTY
b.addCell(BufferCell.live(intColumn, ts, VAL));
Row row = b.build();

Assert.assertTrue(row.hasLiveData(nowInSec, false));
}

// TRIGGER: same shape as above, but enforceStrictLiveness = true.
@Test
public void rowWithOneLiveRegularCellAndEmptyPrimaryKeyLivenessEnforcedStrictLiveness_returnsFalse()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addCell(BufferCell.live(intColumn, ts, VAL));
Row row = b.build();

Assert.assertFalse(row.hasLiveData(nowInSec, true));
}

// TRIGGER: row with a TTL'd-but-not-yet-expired cell, empty PK liveness.
@Test
public void unexpiredTtlCell_returnsTrue()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addCell(BufferCell.expiring(intColumn, ts, /*ttl*/ 3600, /*writeNow*/ nowInSec, VAL));
Row row = b.build();

Assert.assertTrue(row.hasLiveData(nowInSec, false));
}

// TRIGGER: row whose only cell is an expired TTL cell, empty PK liveness.
@Test
public void expiredTtlCell_returnsFalse()
{
long writeTime = nowInSec();
long ts = timestampMicro(writeTime);
long nowInSec = writeTime + 7200; // well past the TTL

Row.Builder b = newBuilder();
b.addCell(BufferCell.expiring(intColumn, ts, /*ttl*/ 3600, /*writeNow*/ writeTime, VAL));
Row row = b.build();

Assert.assertFalse(row.hasLiveData(nowInSec, false));
}

// TRIGGER: row whose only cell is a tombstone, empty PK liveness.
@Test
public void tombstoneCell_returnsFalse()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addCell(BufferCell.tombstone(intColumn, ts, nowInSec));
Row row = b.build();

Assert.assertFalse(row.hasLiveData(nowInSec, false));
}

// TRIGGER: row with a live PK liveness info (not strict).
@Test
public void livePK_returnsTrue()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addPrimaryKeyLivenessInfo(LivenessInfo.create(ts, nowInSec));
// No cells.
Row row = b.build();

Assert.assertTrue(row.hasLiveData(nowInSec, false));
Assert.assertTrue(row.hasLiveData(nowInSec, true));
}

// TRIGGER: row whose only data is a row deletion (no PK liveness, no cells).
@Test
public void rowDeletionOnly_returnsFalse()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addRowDeletion(new Row.Deletion(DeletionTime.build(ts, nowInSec), false));
Row row = b.build();

Assert.assertFalse(row.hasLiveData(nowInSec, false));
}

// TRIGGER: row with a live cell inside a complex (collection) column, empty PK liveness
@Test
public void liveComplexCell_returnsTrue()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addCell(BufferCell.live(mapColumn, ts, VAL, org.apache.cassandra.db.rows.CellPath.create(KEY1)));
Row row = b.build();

Assert.assertTrue(row.hasLiveData(nowInSec, false));
}

// TRIGGER: row with a tombstone alongside a live regular cell.
@Test
public void mixedTombstoneAndLiveCell_returnsTrue()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addCell(BufferCell.live(intColumn, ts, VAL));
b.addCell(BufferCell.tombstone(mapColumn, ts, nowInSec, org.apache.cassandra.db.rows.CellPath.create(KEY1)));
Row row = b.build();

Assert.assertTrue(row.hasLiveData(nowInSec, false));
}

// TRIGGER: row with a tombstone alongside a live regular cell.
@Test
public void mixedTombstoneAndLiveComplexCells_returnsTrue()
{
long nowInSec = nowInSec();
long ts = timestampMicro(nowInSec);

Row.Builder b = newBuilder();
b.addCell(BufferCell.live(mapColumn, ts, VAL, org.apache.cassandra.db.rows.CellPath.create(KEY1)));
b.addCell(BufferCell.tombstone(mapColumn, ts, nowInSec, org.apache.cassandra.db.rows.CellPath.create(KEY2)));
Row row = b.build();

Assert.assertTrue(row.hasLiveData(nowInSec, false));
}

private static long nowInSec()
{
return 1_000_000L;
}

private static long timestampMicro(long nowInSec)
{
return nowInSec * 1_000_000L;
}
}