-
Notifications
You must be signed in to change notification settings - Fork 3.7k
CASSANDRA-20667: Accord: BEGIN TRANSACTIONs IF condition logic does not properly support meaningless emptiness and null values #4175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Conversation
…ot properly support meaningless emptiness and null values
@Override | ||
public boolean isNull(Row row) | ||
{ | ||
return column.type.isNull(rowValue(row)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isNull
has the following semantics
- for non-meangingless emptyness
null
andempty
are bothnull
- for meangingless emptyness
null
andempty
are bothnull
- for types that support empty, only
null
isnull
The main difference between 1/2 is that in 1 empty
is rejected during validation, so normally means you passed a tombstone here by mistake (historic bug on the read path in RowFilter
). 2 allows empty
during validation
{ | ||
case MAP: | ||
{ | ||
exists = !type.nameComparator().isNull(cell.path().get(0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im still not 100% sold on this, here is the test that hits this
map<int, int>
IF row.map[<empty>] IS NULL -- returns true as the key is "null". The data has map[<empty>] -> 42
IF row.map[42] IS NULL -- returns true if the value is <empty>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we have a particularly good principled behaviour we can implement here, but I think treating empty as NULL wherever it is safe to do so, and unsafe to treat it as a raw value, is perhaps as good as we can get.
@@ -532,7 +589,13 @@ else if (type.isUDT()) | |||
@Override | |||
public boolean applies(TxnData data) | |||
{ | |||
return getBounds(data).appliesTo(reference.getRow(data)); | |||
Bound bounds = getBounds(data); | |||
if (reference.column().type.unwrap().isNull(bounds.value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
semantic difference with CAS IF. if input or value are null, applies is
reference.column().type.unwrap().isNull(bounds.value) == bounds.isNull(row)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is OK, because bound cannot be an IS NULL or IS NOT NULL check.
@@ -351,4 +407,100 @@ public long serializedSize(TxnReference reference, TableMetadatas tables, Versio | |||
return size; | |||
} | |||
}; | |||
|
|||
private static class ClusteringColumnData extends AbstractCell<ByteBuffer> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is kinda a hack... it was this or refactor the code more. I could also special case clustering, but felt that this was best as it kept the logic simpler
private final ByteBuffer value; | ||
|
||
public Constant(ByteBuffer value) | ||
public Constant(@Nullable ByteBuffer value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should add a test to show this, but the value can be null which leads to NPE later on, so marked this nullable and made it handled
@@ -115,19 +118,24 @@ void collect(TableMetadatas.Collector collector) | |||
@Override | |||
public void serialize(Constant constant, TableMetadatas tables, DataOutputPlus out, Version version) throws IOException | |||
{ | |||
ByteBufferUtil.writeWithVIntLength(constant.value, out); | |||
out.writeBoolean(constant.value != null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not backwards or mixed compatible
import org.apache.cassandra.utils.memory.Cloner; | ||
import org.apache.cassandra.utils.memory.HeapCloner; | ||
|
||
public class SimplePartition extends AbstractBTreePartition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
creating the RowIterator
i need for the test was a lot of boiler plate, so figured could make this class to make writing tests less verbose
} | ||
} | ||
|
||
private class Updater implements UpdateFunction<Row, Row>, ColumnData.PostReconciliationFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
honestly copied a ton from atomic btree partition, the issue i faced with reusing is having an allocator... I could try to make that logic reusable but didn't want to touch a critical section of code so I forked
…etect anything else missing
@Override | ||
public boolean isNull(Row row) | ||
{ | ||
return row == null || row.getComplexColumnData(column) == null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is all filtered data, so can this data be non-live?
@@ -300,7 +300,7 @@ public Setter(ColumnMetadata column, Term t) | |||
@Override | |||
public boolean requiresTimestamp() | |||
{ | |||
return true; | |||
return column.type.isMultiCell(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bug found in fuzz testing, frozen lists were requiring timestamp which made it hit this block
public void add(Operation operation, TableMetadata tableMetadata)
{
if (isForTxn && (operation.requiresRead() || operation.requiresTimestamp()))
{
add(operation.column, ReferenceOperation.create(operation, tableMetadata));
return;
}
this is a immutable bb so can avoid converting to a reference (i think it fails as a reference but forgot)
@@ -142,6 +143,8 @@ else if (isTxnRequest && coordinationFailed instanceof TopologyMismatch) | |||
} | |||
else | |||
{ | |||
logger.error("Unexpected exception", fail); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was seeing that logs were clean but exceptions were happening, and found that this code block was hiding all these exceptions. The error isn't a CoordinationFailed
so its not expected, so we should be loud! I found several unexpected exceptions by adding this in
else if (reference.isElementSelection()) | ||
{ | ||
// This is frozen, so check if the Cell is a tombstone and that the element is present. | ||
Cell<?> cell = (Cell<?>) columnData; | ||
exists = exists(cell, reference.column().type); | ||
if (exists) | ||
{ | ||
ByteBuffer element = reference.getFrozenCollectionElement(cell); | ||
exists = !reference.getFrozenCollectionElementType().isNull(element); | ||
} | ||
} | ||
else if (reference.isFieldSelection()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im not sure if this is allowed in CQL, but it was already here in the code so i made sure it worked
No description provided.