cql3: support UDT fields in LWT expressions

In an earlier patch, we used the CQL grammar's "subscriptExpr" in
the rule for WRITETIME() and TTL(). But since we also wanted these
to support UDT fields (x.a), not just collection subscripts (x[3]),
we expanded subscriptExpr to also support the field syntax.

But LWT expressions already used this subscriptExpr, which meant
that LWT expressions unintentionally gained support for UDT fields.
Missing support for UDT fields in LWT is a long-standing known
Cassandra-compatibility bug (#13624), and now our grammar finally
supports the missing syntax.

But supporting the syntax is not enough for correct implementation
of this feature - we also need to fix the expression handling:

Two bugs prevented expressions like `v.a = 0` from working in LWT IF
clauses, where `v` is a column of user-defined type.

The first bug was in get_lhs_receiver() in prepare_expr.cc: it lacked
a handler for field_selection nodes, causing an "unexpected expression"
internal error when preparing a condition like `IF v.a = 0`. The fix
adds a handler that returns a column_specification whose type is taken
from the prepared field_selection's type field.

The second bug was in search_and_replace() in expression.cc: when
recursing into a field_selection node it reconstructed it with only
`structure` and `field`, silently dropping the `field_idx` and `type`
fields that are set during preparation. As a result, any transformation
that uses search_and_replace() on a prepared expression containing a
field_selection — such as adjust_for_collection_as_maps() called from
column_condition_prepare() — would zero out those fields. At evaluation
time, type_of() on the field_selection returned a null data_type
pointer, causing a segmentation fault when the comparison operator tried
to call ->equal() through it. The fix preserves field_idx and type when
reconstructing the node.

Fixes #13624.
This commit is contained in:
Nadav Har'El
2026-03-26 11:26:32 +02:00
parent bb2fb810bb
commit 33dbb63aef
3 changed files with 8 additions and 2 deletions

View File

@@ -1031,7 +1031,7 @@ expression search_and_replace(const expression& e,
return cast{c.style, recurse(c.arg), c.type};
},
[&] (const field_selection& fs) -> expression {
return field_selection{recurse(fs.structure), fs.field};
return field_selection{recurse(fs.structure), fs.field, fs.field_idx, fs.type};
},
[&] (const subscript& s) -> expression {
return subscript {

View File

@@ -1688,6 +1688,12 @@ static lw_shared_ptr<column_specification> get_lhs_receiver(const expression& pr
return list_value_spec_of(*sub_col.col->column_specification);
}
},
[&](const field_selection& fs) -> lw_shared_ptr<column_specification> {
return make_lw_shared<column_specification>(
schema.ks_name(), schema.cf_name(),
::make_shared<column_identifier>(fs.field->text(), true),
fs.type);
},
[&](const tuple_constructor& tup) -> lw_shared_ptr<column_specification> {
std::ostringstream tuple_name;
tuple_name << "(";

View File

@@ -266,7 +266,7 @@ def checkInvalidUDT(cql, table, condition, value, expected):
assertInvalidThrow(cql, table, expected, "DELETE FROM %s WHERE k = 0 IF " + condition)
assertRows(execute(cql, table, "SELECT * FROM %s"), row(0, value))
@pytest.mark.xfail(reason="Issue #13624")
# Reproduces #13624
def testUDTField(cql, test_keyspace):
with create_type(cql, test_keyspace, "(a int, b text)") as typename:
for frozen in [False, True]: