From 4fc8bb29fcda935728d8863a4499fa0e9d924b82 Mon Sep 17 00:00:00 2001 From: Sunil Ramchandra Pawar Date: Tue, 7 Jan 2025 08:58:48 -0800 Subject: [PATCH] IndexOutOfBoundsException when accessing partition where the column was deleted patch by Sunil Ramchandra Pawar; reviewed by Caleb Rackliffe, David Capwell for CASSANDRA-20108 --- CHANGES.txt | 1 + .../apache/cassandra/db/filter/RowFilter.java | 32 ++++++++++--------- .../validation/operations/SelectTest.java | 30 +++++++++++++++++ .../index/sasi/plan/OperationTest.java | 2 +- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 23dcc154f8..7eb0068d79 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ 4.0.16 * Fix gossip issue with gossip-only and bootstrapping nodes missing DC/Rack/Host ID endpoint state (CASSANDRA-19983) + * IndexOutOfBoundsException when accessing partition where the column was deleted (CASSANDRA-20108) 4.0.15 diff --git a/src/java/org/apache/cassandra/db/filter/RowFilter.java b/src/java/org/apache/cassandra/db/filter/RowFilter.java index 68a1d57fe1..69a57918cf 100644 --- a/src/java/org/apache/cassandra/db/filter/RowFilter.java +++ b/src/java/org/apache/cassandra/db/filter/RowFilter.java @@ -175,7 +175,7 @@ public abstract class RowFilter implements Iterable for (Expression e : expressions) { - if (!e.isSatisfiedBy(metadata, partitionKey, purged)) + if (!e.isSatisfiedBy(metadata, partitionKey, purged, nowInSec)) return false; } return true; @@ -301,7 +301,7 @@ public abstract class RowFilter implements Iterable // Short-circuit all partitions that won't match based on static and partition keys for (Expression e : partitionLevelExpressions) - if (!e.isSatisfiedBy(metadata, partition.partitionKey(), partition.staticRow())) + if (!e.isSatisfiedBy(metadata, partition.partitionKey(), partition.staticRow(), nowInSec)) { partition.close(); return null; @@ -327,7 +327,7 @@ public abstract class RowFilter implements Iterable return null; for (Expression e : rowLevelExpressions) - if (!e.isSatisfiedBy(metadata, pk, purged)) + if (!e.isSatisfiedBy(metadata, pk, purged, nowInSec)) return null; return row; @@ -437,9 +437,9 @@ public abstract class RowFilter implements Iterable * (i.e. it should come from a RowIterator). * @return whether the row is satisfied by this expression. */ - public abstract boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row); + public abstract boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row, int nowInSec); - protected ByteBuffer getValue(TableMetadata metadata, DecoratedKey partitionKey, Row row) + protected ByteBuffer getValue(TableMetadata metadata, DecoratedKey partitionKey, Row row, int nowInSec) { switch (column.kind) { @@ -451,7 +451,7 @@ public abstract class RowFilter implements Iterable return row.clustering().bufferAt(column.position()); default: Cell cell = row.getCell(column); - return cell == null ? null : cell.buffer(); + return cell == null || cell.isTombstone() || !cell.isLive(nowInSec) ? null : cell.buffer(); } } @@ -594,7 +594,7 @@ public abstract class RowFilter implements Iterable super(column, operator, value); } - public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row) + public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row, int nowInSec) { // We support null conditions for LWT (in ColumnCondition) but not for RowFilter. // TODO: we should try to merge both code someday. @@ -614,7 +614,7 @@ public abstract class RowFilter implements Iterable // representation. See CASSANDRA-11629 if (column.type.isCounter()) { - ByteBuffer foundValue = getValue(metadata, partitionKey, row); + ByteBuffer foundValue = getValue(metadata, partitionKey, row, nowInSec); if (foundValue == null) return false; @@ -624,7 +624,7 @@ public abstract class RowFilter implements Iterable else { // Note that CQL expression are always of the form 'x < 4', i.e. the tested value is on the left. - ByteBuffer foundValue = getValue(metadata, partitionKey, row); + ByteBuffer foundValue = getValue(metadata, partitionKey, row, nowInSec); return foundValue != null && operator.isSatisfiedBy(column.type, foundValue, value); } } @@ -635,7 +635,7 @@ public abstract class RowFilter implements Iterable case LIKE_MATCHES: { assert !column.isComplex() : "Only CONTAINS and CONTAINS_KEY are supported for 'complex' types"; - ByteBuffer foundValue = getValue(metadata, partitionKey, row); + ByteBuffer foundValue = getValue(metadata, partitionKey, row, nowInSec); // Note that CQL expression are always of the form 'x < 4', i.e. the tested value is on the left. return foundValue != null && operator.isSatisfiedBy(column.type, foundValue, value); } @@ -665,7 +665,7 @@ public abstract class RowFilter implements Iterable } else { - ByteBuffer foundValue = getValue(metadata, partitionKey, row); + ByteBuffer foundValue = getValue(metadata, partitionKey, row, nowInSec); if (foundValue == null) return false; @@ -692,7 +692,7 @@ public abstract class RowFilter implements Iterable } else { - ByteBuffer foundValue = getValue(metadata, partitionKey, row); + ByteBuffer foundValue = getValue(metadata, partitionKey, row, nowInSec); return foundValue != null && mapType.getSerializer().getSerializedValue(foundValue, value, mapType.getKeysType()) != null; } @@ -765,7 +765,8 @@ public abstract class RowFilter implements Iterable return CompositeType.build(ByteBufferAccessor.instance, key, value); } - public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row) + @Override + public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row, int nowInSec) { assert key != null; // We support null conditions for LWT (in ColumnCondition) but not for RowFilter. @@ -783,7 +784,7 @@ public abstract class RowFilter implements Iterable } else { - ByteBuffer serializedMap = getValue(metadata, partitionKey, row); + ByteBuffer serializedMap = getValue(metadata, partitionKey, row, nowInSec); if (serializedMap == null) return false; @@ -879,7 +880,8 @@ public abstract class RowFilter implements Iterable } // Filtering by custom expressions isn't supported yet, so just accept any row - public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row) + @Override + public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row, int nowInSec) { return true; } diff --git a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java index d0493cf8fc..0a7b507df1 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/operations/SelectTest.java @@ -2301,6 +2301,36 @@ public class SelectTest extends CQLTester }); } + @Test + public void filteringOnDeletedStaticColumnValue() throws Throwable + { + // Create table with int-only columns + createTable("CREATE TABLE %s (pk0 int, pk1 int, ck0 int, ck1 int, s0 tinyint static, v0 int, v1 int, PRIMARY KEY ((pk0, pk1), ck0, ck1))"); + + // Insert rows + execute("INSERT INTO %s (pk0, pk1, s0, ck0, ck1, v0, v1) VALUES (?, ?, ?, ?, ?, ?, ?)", 1000, 2000, (byte) 126, 100, 1, 20, 30); + execute("INSERT INTO %s (pk0, pk1, s0, ck0, ck1, v0, v1) VALUES (?, ?, ?, ?, ?, ?, ?)", 1000, 2000, (byte) 125, 200, 2, 40, 50); + execute("INSERT INTO %s (pk0, pk1, s0, ck0, ck1, v0, v1) VALUES (?, ?, ?, ?, ?, ?, ?)", 1000, 3000, (byte) 122, 300, 3, 60, 70); + execute("DELETE s0,v0,v1 FROM %s WHERE pk0=1000 AND pk1=2000 and ck0=100 and ck1=1"); + + beforeAndAfterFlush(() -> { + // Verify the columns are deleted + assertRows(execute("SELECT pk0, pk1, s0, ck0, ck1, v0, v1 FROM %s WHERE s0=? ALLOW FILTERING", (byte) 122), + row(1000, 3000, (byte) 122, 300, 3, 60, 70)); + }); + + execute("DELETE v0 FROM %s WHERE pk0=1000 AND pk1=3000 AND ck0=300 AND ck1=3"); + + beforeAndAfterFlush(() -> { + assertRows(execute("SELECT pk0, pk1, s0, ck0, ck1, v0, v1 FROM %s WHERE s0=? ALLOW FILTERING", (byte) 122), + row(1000, 3000, (byte) 122, 300, 3, null, 70)); + + assertRows(execute("SELECT pk0, pk1, s0, ck0, ck1, v0, v1 FROM %s WHERE pk0=1000 AND pk1=3000 AND ck0=300 AND ck1=3"), + row(1000, 3000, (byte) 122, 300, 3, null, 70)); + }); + + } + @Test public void containsFilteringOnNonClusteringColumn() throws Throwable { createTable("CREATE TABLE %s (a int, b int, c int, d list, PRIMARY KEY (a, b, c))"); diff --git a/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java b/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java index 4468f2c219..e80e68fb76 100644 --- a/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java +++ b/test/unit/org/apache/cassandra/index/sasi/plan/OperationTest.java @@ -647,7 +647,7 @@ public class OperationTest extends SchemaLoader } @Override - public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row) + public boolean isSatisfiedBy(TableMetadata metadata, DecoratedKey partitionKey, Row row, int nowInSec) { throw new UnsupportedOperationException(); }