diff --git a/CHANGES.txt b/CHANGES.txt index 2f3543f51e..9b6a6dc051 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5,6 +5,7 @@ * Reduce thread contention in CommitLogSegment and HintsBuffer (CASSANDRA-16072) * Avoid sending CDC column if not enabled (CASSANDRA-16770) Merged from 3.0: + * Fix secondary indexes on primary key columns skipping some writes (CASSANDRA-16868) * Fix incorrect error message in LegacyLayout (CASSANDRA-15136) * Use JMX to validate nodetool --jobs parameter (CASSANDRA-16104) * Handle properly UnsatisfiedLinkError in NativeLibrary#getProcessID() (CASSANDRA-16578) diff --git a/src/java/org/apache/cassandra/index/internal/CassandraIndex.java b/src/java/org/apache/cassandra/index/internal/CassandraIndex.java index e23882f30e..27b60e5ccf 100644 --- a/src/java/org/apache/cassandra/index/internal/CassandraIndex.java +++ b/src/java/org/apache/cassandra/index/internal/CassandraIndex.java @@ -427,7 +427,7 @@ public abstract class CassandraIndex implements Index if (isPrimaryKeyIndex()) indexPrimaryKey(newRow.clustering(), - newRow.primaryKeyLivenessInfo(), + getPrimaryKeyIndexLiveness(newRow), newRow.deletion()); if (indexedColumn.isComplex()) @@ -505,10 +505,7 @@ public abstract class CassandraIndex implements Index if (cell.isLive(nowInSec)) { if (cellTimestamp > timestamp) - { timestamp = cellTimestamp; - ttl = cell.ttl(); - } } } return LivenessInfo.create(timestamp, ttl, nowInSec); diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java index ce77081efb..adb69113b0 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/SecondaryIndexTest.java @@ -30,7 +30,6 @@ import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.ColumnIdentifier; -import org.apache.cassandra.cql3.Duration; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.cql3.restrictions.StatementRestrictions; import org.apache.cassandra.cql3.statements.IndexTarget; @@ -1537,46 +1536,180 @@ public class SecondaryIndexTest extends CQLTester @Test public void testIndexOnPartitionKeyInsertExpiringColumn() throws Throwable + { + testIndexOnPartitionKeyInsertExpiringColumn(false); + } + + @Test + public void testIndexOnPartitionKeyInsertExpiringColumnWithFlush() throws Throwable + { + testIndexOnPartitionKeyInsertExpiringColumn(true); + } + + private void testIndexOnPartitionKeyInsertExpiringColumn(boolean flushBeforeUpdate) throws Throwable { createTable("CREATE TABLE %s (k1 int, k2 int, a int, b int, PRIMARY KEY ((k1, k2)))"); - createIndex("CREATE INDEX on %s(k1)"); + createIndex("CREATE INDEX ON %s(k1)"); execute("INSERT INTO %s (k1, k2, a, b) VALUES (1, 2, 3, 4)"); assertRows(execute("SELECT * FROM %s WHERE k1 = 1"), row(1, 2, 3, 4)); + + if (flushBeforeUpdate) + flush(); + execute("UPDATE %s USING TTL 1 SET b = 10 WHERE k1 = 1 AND k2 = 2"); Thread.sleep(1000); assertRows(execute("SELECT * FROM %s WHERE k1 = 1"), row(1, 2, 3, null)); } + @Test + public void testIndexOnPartitionKeyOverridingExpiredRow() throws Throwable + { + createTable("CREATE TABLE %s (k1 int, k2 int, v int, PRIMARY KEY ((k1, k2)))"); + createIndex("CREATE INDEX ON %s(k1)"); + + execute("UPDATE %s USING TTL 1 SET v = 3 WHERE k1 = 1 AND k2 = 2"); + Thread.sleep(1000); + + assertEmpty(execute("SELECT * FROM %s WHERE k1 = 1")); + + execute("UPDATE %s SET v = 3 WHERE k1 = 1 AND k2 = 2"); + assertRows(execute("SELECT * FROM %s WHERE k1 = 1"), row(1, 2, 3)); + } + + @Test + public void testIndexOnPartitionKeyOverridingDeletedRow() throws Throwable + { + createTable("CREATE TABLE %s (k1 int, k2 int, c int, v int, PRIMARY KEY ((k1, k2), c))"); + createIndex("CREATE INDEX ON %s(k1)"); + + execute("INSERT INTO %s(k1, k2, c, v) VALUES (1, 2, 3, 4)"); + execute("DELETE FROM %s WHERE k1 = 1 AND k2 = 2 AND c = 3"); + execute("UPDATE %s SET v = 4 WHERE k1 = 1 AND k2 = 2 AND c = 3"); + + assertRows(execute("SELECT * FROM %s WHERE k1 = 1 AND k2 = 2 AND c = 3"), row(1, 2, 3, 4)); + assertRows(execute("SELECT * FROM %s WHERE k1 = 1"), row(1, 2, 3, 4)); + } + @Test public void testIndexOnClusteringKeyInsertExpiringColumn() throws Throwable + { + testIndexOnClusteringKeyInsertExpiringColumn(false); + } + + @Test + public void testIndexOnClusteringKeyInsertExpiringColumnWithFlush() throws Throwable + { + testIndexOnClusteringKeyInsertExpiringColumn(true); + } + + private void testIndexOnClusteringKeyInsertExpiringColumn(boolean flushBeforeUpdate) throws Throwable { createTable("CREATE TABLE %s (pk int, ck int, a int, b int, PRIMARY KEY (pk, ck))"); - createIndex("CREATE INDEX on %s(ck)"); + createIndex("CREATE INDEX ON %s(ck)"); execute("INSERT INTO %s (pk, ck, a, b) VALUES (1, 2, 3, 4)"); assertRows(execute("SELECT * FROM %s WHERE ck = 2"), row(1, 2, 3, 4)); + + if (flushBeforeUpdate) + flush(); + execute("UPDATE %s USING TTL 1 SET b = 10 WHERE pk = 1 AND ck = 2"); Thread.sleep(1000); assertRows(execute("SELECT * FROM %s WHERE ck = 2"), row(1, 2, 3, null)); } + @Test + public void testIndexOnClusteringKeyOverridingExpiredRow() throws Throwable + { + createTable("CREATE TABLE %s (pk int, ck int, v int, PRIMARY KEY (pk, ck))"); + createIndex("CREATE INDEX ON %s(ck)"); + + execute("UPDATE %s USING TTL 1 SET v = 3 WHERE pk = 1 AND ck = 2"); + Thread.sleep(1000); + + assertEmpty(execute("SELECT * FROM %s WHERE ck = 2")); + + execute("UPDATE %s SET v = 3 WHERE pk = 1 AND ck = 2"); + assertRows(execute("SELECT * FROM %s WHERE ck = 2"), row(1, 2, 3)); + } + + @Test + public void testIndexOnClusteringKeyOverridingDeletedRow() throws Throwable + { + createTable("CREATE TABLE %s (pk int, ck int, v int, PRIMARY KEY (pk, ck))"); + createIndex("CREATE INDEX ON %s(ck)"); + + execute("INSERT INTO %s(pk, ck, v) VALUES (1, 2, 3)"); + execute("DELETE FROM %s WHERE pk = 1 AND ck = 2"); + execute("UPDATE %s SET v = 3 WHERE pk = 1 AND ck = 2"); + + assertRows(execute("SELECT * FROM %s WHERE pk = 1 AND ck = 2"), row(1, 2, 3)); + assertRows(execute("SELECT * FROM %s WHERE ck = 2"), row(1, 2, 3)); + } + @Test public void testIndexOnRegularColumnInsertExpiringColumn() throws Throwable + { + testIndexOnRegularColumnInsertExpiringColumn(false); + } + + @Test + public void testIndexOnRegularColumnInsertExpiringColumnWithFlush() throws Throwable + { + testIndexOnRegularColumnInsertExpiringColumn(true); + } + + private void testIndexOnRegularColumnInsertExpiringColumn(boolean flushBeforeUpdate) throws Throwable { createTable("CREATE TABLE %s (pk int, ck int, a int, b int, PRIMARY KEY (pk, ck))"); - createIndex("CREATE INDEX on %s(a)"); + createIndex("CREATE INDEX ON %s(a)"); execute("INSERT INTO %s (pk, ck, a, b) VALUES (1, 2, 3, 4)"); assertRows(execute("SELECT * FROM %s WHERE a = 3"), row(1, 2, 3, 4)); + if (flushBeforeUpdate) + flush(); + execute("UPDATE %s USING TTL 1 SET b = 10 WHERE pk = 1 AND ck = 2"); Thread.sleep(1000); assertRows(execute("SELECT * FROM %s WHERE a = 3"), row(1, 2, 3, null)); + if (flushBeforeUpdate) + flush(); + execute("UPDATE %s USING TTL 1 SET a = 5 WHERE pk = 1 AND ck = 2"); Thread.sleep(1000); assertEmpty(execute("SELECT * FROM %s WHERE a = 3")); assertEmpty(execute("SELECT * FROM %s WHERE a = 5")); } + @Test + public void testIndexOnRegularColumnOverridingExpiredRow() throws Throwable + { + createTable("CREATE TABLE %s (pk int, ck int, v int, PRIMARY KEY (pk, ck))"); + createIndex("CREATE INDEX ON %s(v)"); + + execute("UPDATE %s USING TTL 1 SET v = 3 WHERE pk = 1 AND ck = 2"); + Thread.sleep(1000); + + assertEmpty(execute("SELECT * FROM %s WHERE v = 3")); + + execute("UPDATE %s SET v = 3 WHERE pk = 1 AND ck = 2"); + assertRows(execute("SELECT * FROM %s WHERE v = 3"), row(1, 2, 3)); + } + + @Test + public void testIndexOnRegularColumnOverridingDeletedRow() throws Throwable + { + createTable("CREATE TABLE %s (pk int, ck int, v int, PRIMARY KEY (pk, ck))"); + createIndex("CREATE INDEX ON %s(v)"); + + execute("INSERT INTO %s(pk, ck, v) VALUES (1, 2, 3)"); + execute("DELETE FROM %s WHERE pk=1 AND ck=2"); + execute("UPDATE %s SET v=3 WHERE pk=1 AND ck=2"); + + assertRows(execute("SELECT * FROM %s WHERE pk=1 AND ck=2"), row(1, 2, 3)); + assertRows(execute("SELECT * FROM %s WHERE v=3"), row(1, 2, 3)); + } + @Test public void testIndicesOnCompactTable() throws Throwable {