mirror of https://github.com/apache/cassandra
Fix AssertionError while flushing memtable due to materialized views incorrectly inserting empty rows
Patch by thobbs; reviewed by tjake for CASSANDRA-10614
This commit is contained in:
parent
7fd2eaa727
commit
2998bb3cc4
|
|
@ -1,4 +1,6 @@
|
|||
3.0
|
||||
* Fix AssertionError while flushing memtable due to materialized views
|
||||
incorrectly inserting empty rows (CASSANDRA-10614)
|
||||
* Store UDA initcond as CQL literal in the schema table, instead of a blob (CASSANDRA-10650)
|
||||
* Don't use -1 for the position of partition key in schema (CASSANDRA-10491)
|
||||
* Fix distinct queries in mixed version cluster (CASSANDRA-10573)
|
||||
|
|
|
|||
|
|
@ -243,6 +243,16 @@ public class TemporalRow
|
|||
row.addColumnValue(column, path, newCell.timestamp, newCell.ttl,
|
||||
newCell.localDeletionTime, newCell.value, newCell.isNew);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("numSet", numSet)
|
||||
.add("existingCell", existingCell)
|
||||
.add("newCell", newCell)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +295,22 @@ public class TemporalRow
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("table", baseCfs.keyspace.getName() + "." + baseCfs.getTableName())
|
||||
.add("basePartitionKey", ByteBufferUtil.bytesToHex(basePartitionKey))
|
||||
.add("startRow", startRow.toString(baseCfs.metadata))
|
||||
.add("startIsNew", startIsNew)
|
||||
.add("nowInSec", nowInSec)
|
||||
.add("viewClusteringTtl", viewClusteringTtl)
|
||||
.add("viewClusteringTimestamp", viewClusteringTimestamp)
|
||||
.add("viewClusteringLocalDeletionTime", viewClusteringLocalDeletionTime)
|
||||
.add("columnValues", columnValues)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o)
|
||||
{
|
||||
|
|
@ -459,6 +485,11 @@ public class TemporalRow
|
|||
return builder;
|
||||
}
|
||||
|
||||
public Clustering baseClustering()
|
||||
{
|
||||
return startRow.clustering();
|
||||
}
|
||||
|
||||
static class Set implements Iterable<TemporalRow>
|
||||
{
|
||||
private final ColumnFamilyStore baseCfs;
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public class View
|
|||
|
||||
private Columns columns;
|
||||
|
||||
private final boolean viewHasAllPrimaryKeys;
|
||||
private final boolean viewPKIncludesOnlyBasePKColumns;
|
||||
private final boolean includeAllColumns;
|
||||
private ViewBuilder builder;
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ public class View
|
|||
name = definition.viewName;
|
||||
includeAllColumns = definition.includeAllColumns;
|
||||
|
||||
viewHasAllPrimaryKeys = updateDefinition(definition);
|
||||
viewPKIncludesOnlyBasePKColumns = updateDefinition(definition);
|
||||
this.rawSelect = definition.select;
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +210,7 @@ public class View
|
|||
if (!selectQuery.selectsClustering(partition.partitionKey(), row.clustering()))
|
||||
continue;
|
||||
|
||||
if (includeAllColumns || viewHasAllPrimaryKeys || !row.deletion().isLive())
|
||||
if (includeAllColumns || !row.deletion().isLive())
|
||||
return true;
|
||||
|
||||
if (row.primaryKeyLivenessInfo().isLive(FBUtilities.nowInSeconds()))
|
||||
|
|
@ -313,7 +313,7 @@ public class View
|
|||
private PartitionUpdate createRangeTombstoneForRow(TemporalRow temporalRow)
|
||||
{
|
||||
// Primary Key and Clustering columns do not generate tombstones
|
||||
if (viewHasAllPrimaryKeys)
|
||||
if (viewPKIncludesOnlyBasePKColumns)
|
||||
return null;
|
||||
|
||||
boolean hasUpdate = false;
|
||||
|
|
@ -375,7 +375,14 @@ public class View
|
|||
}
|
||||
}
|
||||
|
||||
return PartitionUpdate.singleRowUpdate(viewCfm, partitionKey, regularBuilder.build());
|
||||
Row row = regularBuilder.build();
|
||||
|
||||
// although we check for empty rows in updateAppliesToView(), if there are any good rows in the PartitionUpdate,
|
||||
// all rows in the partition will be processed, and we need to handle empty/non-live rows here (CASSANDRA-10614)
|
||||
if (row.isEmpty())
|
||||
return null;
|
||||
|
||||
return PartitionUpdate.singleRowUpdate(viewCfm, partitionKey, row);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -650,9 +657,16 @@ public class View
|
|||
if (!updateAffectsView(partition))
|
||||
return null;
|
||||
|
||||
ReadQuery selectQuery = getReadQuery();
|
||||
Collection<Mutation> mutations = null;
|
||||
for (TemporalRow temporalRow : rowSet)
|
||||
{
|
||||
// In updateAffectsView, we check the partition to see if there is at least one row that matches the
|
||||
// filters and is live. If there is more than one row in the partition, we need to re-check each one
|
||||
// invididually.
|
||||
if (partition.rowCount() != 1 && !selectQuery.selectsClustering(partition.partitionKey(), temporalRow.baseClustering()))
|
||||
continue;
|
||||
|
||||
// If we are building, there is no need to check for partition tombstones; those values will not be present
|
||||
// in the partition data
|
||||
if (!isBuilding)
|
||||
|
|
|
|||
|
|
@ -1078,6 +1078,20 @@ public class ViewFilteringTest extends CQLTester
|
|||
row(0, 1, 1, 0)
|
||||
);
|
||||
|
||||
// insert a partition with one matching and one non-matching row using a batch (CASSANDRA-10614)
|
||||
String tableName = KEYSPACE + "." + currentTable();
|
||||
execute("BEGIN BATCH " +
|
||||
"INSERT INTO " + tableName + " (a, b, c, d) VALUES (?, ?, ?, ?); " +
|
||||
"INSERT INTO " + tableName + " (a, b, c, d) VALUES (?, ?, ?, ?); " +
|
||||
"APPLY BATCH",
|
||||
4, 4, 0, 0,
|
||||
4, 4, 1, 1);
|
||||
assertRowsIgnoringOrder(execute("SELECT a, b, c, d FROM mv_test" + i),
|
||||
row(0, 0, 1, 0),
|
||||
row(0, 1, 1, 0),
|
||||
row(4, 4, 1, 1)
|
||||
);
|
||||
|
||||
dropView("mv_test" + i);
|
||||
dropTable("DROP TABLE %s");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import org.apache.cassandra.concurrent.StageManager;
|
|||
import org.apache.cassandra.config.CFMetaData;
|
||||
import org.apache.cassandra.config.ColumnDefinition;
|
||||
import org.apache.cassandra.config.Schema;
|
||||
import org.apache.cassandra.db.ColumnFamilyStore;
|
||||
import org.apache.cassandra.db.Keyspace;
|
||||
import org.apache.cassandra.db.SystemKeyspace;
|
||||
import org.apache.cassandra.serializers.SimpleDateSerializer;
|
||||
|
|
@ -810,6 +811,47 @@ public class ViewTest extends CQLTester
|
|||
assertRows(execute("SELECT k, intval from mv WHERE intval = ?", 1), row(0, 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreUpdate() throws Throwable
|
||||
{
|
||||
// regression test for CASSANDRA-10614
|
||||
|
||||
createTable("CREATE TABLE %s (" +
|
||||
"a int, " +
|
||||
"b int, " +
|
||||
"c int, " +
|
||||
"d int, " +
|
||||
"PRIMARY KEY (a, b))");
|
||||
|
||||
execute("USE " + keyspace());
|
||||
executeNet(protocolVersion, "USE " + keyspace());
|
||||
|
||||
createView("mv", "CREATE MATERIALIZED VIEW %s AS SELECT a, b, c FROM %%s WHERE a IS NOT NULL AND b IS NOT NULL PRIMARY KEY (b, a)");
|
||||
|
||||
updateView("INSERT INTO %s (a, b, c) VALUES (?, ?, ?)", 0, 0, 0);
|
||||
assertRows(execute("SELECT a, b, c from mv WHERE b = ?", 0), row(0, 0, 0));
|
||||
|
||||
updateView("UPDATE %s SET d = ? WHERE a = ? AND b = ?", 0, 0, 0);
|
||||
assertRows(execute("SELECT a, b, c from mv WHERE b = ?", 0), row(0, 0, 0));
|
||||
|
||||
// Note: errors here may result in the test hanging when the memtables are flushed as part of the table drop,
|
||||
// because empty rows in the memtable will cause the flush to fail. This will result in a test timeout that
|
||||
// should not be ignored.
|
||||
String table = KEYSPACE + "." + currentTable();
|
||||
updateView("BEGIN BATCH " +
|
||||
"INSERT INTO " + table + " (a, b, c, d) VALUES (?, ?, ?, ?); " + // should be accepted
|
||||
"UPDATE " + table + " SET d = ? WHERE a = ? AND b = ?; " + // should be ignored
|
||||
"APPLY BATCH",
|
||||
0, 0, 0, 0,
|
||||
1, 0, 1);
|
||||
assertRows(execute("SELECT a, b, c from mv WHERE b = ?", 0), row(0, 0, 0));
|
||||
assertEmpty(execute("SELECT a, b, c from mv WHERE b = ?", 1));
|
||||
|
||||
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore("mv");
|
||||
cfs.forceBlockingFlush();
|
||||
Assert.assertEquals(1, cfs.getLiveSSTables().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoTablesOneView() throws Throwable
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue