cql3: prepare and evaluate WRITETIME/TTL on collection elements and UDT fields
Complete the implementation of SELECT WRITETIME(col[key])/TTL(col[key])
and WRITETIME(col.field)/TTL(col.field), building on the grammar (commit 1),
wire format (commit 2), and selection-layer (commit 3) changes in the
preceding patches.
* prepare_column_mutation_attribute() (prepare_expr.cc) now handles the
subscript and field_selection nodes that the grammar produces:
- For subscripts, it validates that the inner column is a non-frozen
map or set and checks the 'writetime_ttl_individual_element' feature
flag so the feature is rejected during rolling upgrades.
- For field selections, it validates that the inner column is a
non-frozen UDT, with the same feature-flag check.
* do_evaluate(column_mutation_attribute) (expression.cc) handles the
same two cases. For a field selection it serializes the field index as
a key and looks it up in collection_element_metadata; for a subscript
it evaluates the subscript key and looks it up in the same map.
A missing key (element not found or expired) returns NULL, matching
Cassandra behavior.
Together with the preceding three patches, this finally fixes #15427.
The next three patches will add tests and documentation for the new
feature, and the final eighth patch will fix the implementation of
UDT fields in LWT expressions - which the first patch made the grammar
allow but is still not implemented correctly.
This commit is contained in:
@@ -1206,6 +1206,58 @@ cql3::raw_value do_evaluate(const field_selection& field_select, const evaluatio
|
||||
static
|
||||
cql3::raw_value
|
||||
do_evaluate(const column_mutation_attribute& cma, const evaluation_inputs& inputs) {
|
||||
// Helper for WRITETIME/TTL on a collection element or UDT field: given the
|
||||
// inner column and the serialized element key, validate the index and look
|
||||
// up the per-element timestamp or TTL in collection_element_metadata.
|
||||
auto lookup_element_attribute = [&](const column_value* inner_col, std::string_view context, bytes key) -> cql3::raw_value {
|
||||
int32_t index = inputs.selection->index_of(*inner_col->col);
|
||||
if (inputs.collection_element_metadata.empty() || index < 0 || size_t(index) >= inputs.collection_element_metadata.size()) {
|
||||
on_internal_error(expr_logger, fmt::format("evaluating column_mutation_attribute {}: column {} is not in selection",
|
||||
context, inner_col->col->name_as_text()));
|
||||
}
|
||||
const auto& meta = inputs.collection_element_metadata[index];
|
||||
switch (cma.kind) {
|
||||
case column_mutation_attribute::attribute_kind::writetime: {
|
||||
const auto it = meta.timestamps.find(key);
|
||||
if (it == meta.timestamps.end()) {
|
||||
return cql3::raw_value::make_null();
|
||||
}
|
||||
return raw_value::make_value(data_value(it->second).serialize());
|
||||
}
|
||||
case column_mutation_attribute::attribute_kind::ttl: {
|
||||
const auto it = meta.ttls.find(key);
|
||||
// The test it->second <= 0 (rather than < 0) matches the
|
||||
// single-TTL check ttl_v <= 0 below.
|
||||
if (it == meta.ttls.end() || it->second <= 0) {
|
||||
return cql3::raw_value::make_null();
|
||||
}
|
||||
return raw_value::make_value(data_value(it->second).serialize());
|
||||
}
|
||||
}
|
||||
on_internal_error(expr_logger, fmt::format("evaluating column_mutation_attribute {} with unexpected kind", context));
|
||||
};
|
||||
// Handle WRITETIME(x.field) / TTL(x.field) on a UDT field
|
||||
if (auto fs = expr::as_if<field_selection>(&cma.column)) {
|
||||
auto inner_col = expr::as_if<column_value>(&fs->structure);
|
||||
if (!inner_col) {
|
||||
on_internal_error(expr_logger, fmt::format("evaluating column_mutation_attribute field_selection: inner expression is not a column: {}", fs->structure));
|
||||
}
|
||||
return lookup_element_attribute(inner_col, "field_selection", serialize_field_index(fs->field_idx));
|
||||
}
|
||||
// Handle WRITETIME(m[key]) / TTL(m[key]) on a map element
|
||||
if (auto sub = expr::as_if<subscript>(&cma.column)) {
|
||||
auto inner_col = expr::as_if<column_value>(&sub->val);
|
||||
if (!inner_col) {
|
||||
on_internal_error(expr_logger, fmt::format("evaluating column_mutation_attribute subscript: inner expression is not a column: {}", sub->val));
|
||||
}
|
||||
auto evaluated_key = evaluate(sub->sub, inputs);
|
||||
if (evaluated_key.is_null()) {
|
||||
return cql3::raw_value::make_null();
|
||||
}
|
||||
return evaluated_key.view().with_linearized([&] (bytes_view key_bv) {
|
||||
return lookup_element_attribute(inner_col, "subscript", bytes(key_bv));
|
||||
});
|
||||
}
|
||||
auto col = expr::as_if<column_value>(&cma.column);
|
||||
if (!col) {
|
||||
on_internal_error(expr_logger, fmt::format("evaluating column_mutation_attribute of non-column {}", cma.column));
|
||||
|
||||
@@ -1259,6 +1259,40 @@ prepare_column_mutation_attribute(
|
||||
receiver->type->name(), receiver->name->text()));
|
||||
}
|
||||
auto column = prepare_expression(cma.column, db, keyspace, schema_opt, nullptr);
|
||||
// Helper for the subscript and field-selection cases below: validates that
|
||||
// inner_expr is a column, not a primary key column, that its type satisfies
|
||||
// type_allowed, and that the cluster feature flag is on.
|
||||
auto validate_and_return =
|
||||
[&](const expression& inner_expr, std::string_view context,
|
||||
auto type_allowed, std::string_view type_allowed_str) -> std::optional<expression> {
|
||||
auto inner_cval = expr::as_if<column_value>(&inner_expr);
|
||||
if (!inner_cval) {
|
||||
throw exceptions::invalid_request_exception(fmt::format("{} on a {} expects a column, got {}", cma.kind, context, inner_expr));
|
||||
}
|
||||
if (inner_cval->col->is_primary_key()) {
|
||||
throw exceptions::invalid_request_exception(fmt::format("{} is not legal on primary key component {}", cma.kind, inner_cval->col->name_as_text()));
|
||||
}
|
||||
if (!type_allowed(inner_cval->col->type)) {
|
||||
throw exceptions::invalid_request_exception(fmt::format("{} on a {} is only valid for {}", cma.kind, context, type_allowed_str));
|
||||
}
|
||||
if (!db.features().writetime_ttl_individual_element) {
|
||||
throw exceptions::invalid_request_exception(fmt::format(
|
||||
"{} on a {} is not supported until all nodes in the cluster are upgraded", cma.kind, context));
|
||||
}
|
||||
return column_mutation_attribute{.kind = cma.kind, .column = std::move(column)};
|
||||
};
|
||||
// Handle WRITETIME(m[key]) / TTL(m[key]) - a subscript into a non-frozen map or set column
|
||||
if (auto sub = expr::as_if<subscript>(&column)) {
|
||||
return validate_and_return(sub->val, "subscript",
|
||||
[](const data_type& t) { return (t->is_map() || t->is_set()) && t->is_multi_cell(); },
|
||||
"non-frozen map or set columns");
|
||||
}
|
||||
// Handle WRITETIME(x.field) / TTL(x.field) - a field selection into a non-frozen UDT column
|
||||
if (auto fs = expr::as_if<field_selection>(&column)) {
|
||||
return validate_and_return(fs->structure, "field selection",
|
||||
[](const data_type& t) { return t->is_user_type() && t->is_multi_cell(); },
|
||||
"non-frozen UDT columns");
|
||||
}
|
||||
auto cval = expr::as_if<column_value>(&column);
|
||||
if (!cval) {
|
||||
throw exceptions::invalid_request_exception(fmt::format("{} expects a column, but {} is a general expression", cma.kind, column));
|
||||
|
||||
Reference in New Issue
Block a user