diff --git a/test/unit/org/apache/cassandra/io/sstable/format/SSTableReaderTest.java b/test/unit/org/apache/cassandra/io/sstable/format/SSTableReaderTest.java index 6d07f1c3ed9f..bc581a453e3c 100644 --- a/test/unit/org/apache/cassandra/io/sstable/format/SSTableReaderTest.java +++ b/test/unit/org/apache/cassandra/io/sstable/format/SSTableReaderTest.java @@ -27,15 +27,21 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; +import java.util.Random; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; +import java.util.stream.Collectors; import com.google.common.collect.Sets; import org.apache.cassandra.cache.CachingOptions; import org.apache.cassandra.config.KSMetaData; +import org.apache.cassandra.db.Cell; +import org.apache.cassandra.db.ReadCommand; +import org.apache.cassandra.db.filter.SliceQueryFilter; import org.apache.cassandra.io.sstable.Component; import org.apache.cassandra.io.sstable.Descriptor; import org.apache.cassandra.io.sstable.ISSTableScanner; @@ -587,6 +593,76 @@ public void testIndexSummaryDownsampleAndReload() throws Exception } } + /** + * This test reproduces a bug where range scans resurrect columns when column indices are being used. + *
+ * It does the following. + * 1. Write column e + * 2. Flush + * 3. Write column b + * 4. Delete with range tombstone from c to z + * 5. Flush + * 6. Range scan with filter (d, "") + * 7. Assert the data scanned contains the deleted column e + * 8. Range scan with filter (c, "") + * 9. Assert the data scanned does not contain the deleted column + *
+ * The bug has to do with how column indices work. For regular columns, they work as expected indexing off the
+ * column name. For range tombstones, however, they only index off the "start" meaning the beginning of the interval.
+ * This can cause a read on column range (X, Y) to ignore tombstones that start before X, even if the tombstone
+ * looks like (X - 1, Y + 1), which covers X.
+ */
+ @Test
+ public void testIndexIgnoresRangeTombstone() throws IOException {
+ Keyspace keyspace = Keyspace.open(KEYSPACE1);
+ ColumnFamilyStore store = keyspace.getColumnFamilyStore("Standard1");
+ CompactionManager.instance.disableAutoCompaction();
+
+ ByteBuffer key = ByteBufferUtil.bytes("row-key");
+
+ // create large enough columns for column indices to kick in
+ int columnSizeBytes = DatabaseDescriptor.getColumnIndexSize() + 1;
+ ByteBuffer buffer = ByteBuffer.allocate(columnSizeBytes);
+ byte[] randomBytes = new byte[columnSizeBytes];
+ new Random().nextBytes(randomBytes);
+ buffer.put(randomBytes);
+ buffer.rewind();
+
+ // insert e
+ Mutation rm = new Mutation(KEYSPACE1, key);
+ rm.add("Standard1", cellname("e"), buffer.duplicate(), System.currentTimeMillis());
+ rm.applyUnsafe();
+ store.forceBlockingFlush();
+
+ // insert b
+ // insert range tombstone (c, z)
+ rm.add("Standard1", cellname("b"), buffer.duplicate(), System.currentTimeMillis());
+ rm.deleteRange("Standard1", cellname("c"), cellname("z"), System.currentTimeMillis());
+ rm.applyUnsafe();
+ store.forceBlockingFlush();
+
+ // read (d, "")
+ SliceQueryFilter sliceQueryFilterD = new SliceQueryFilter(cellname("d"), Composites.EMPTY, false, 100);
+ ReadCommand readCommandD =
+ ReadCommand.create(KEYSPACE1, key, "Standard1", System.currentTimeMillis(), sliceQueryFilterD);
+ Row rowD = readCommandD.getRow(keyspace);
+
+ // assert non empty
+ assert !rowD.cf.getSortedColumns().isEmpty();
+ List