expression: add vector style type

Motivation for this changes is to provide a distinguishable interface
for vector type expressions.

The square bracket literal is ambigious for lists and vectors,
so that we need to perform a distinction not using CQL layer.
At first we should use the collection constructor to manage
both lists and vectors (although a vector is not a collection).
Later during preparation of expressions we should be able to get
to know the exact type using given receiver (column specification).

Knowing the type of expression we may use their respective style type
(in this case the vector style type being introduced),
which would make the implementation more precise and allow us to
evaluate the expressions properly.

This commit introduces vector style type and functions making use of it.

However vector style type is not yet used anywhere,
the next commit should adjust collection constructor and make use
of the new vector style type and it's features.
This commit is contained in:
Dawid Pawlik
2024-12-28 15:56:00 +01:00
parent 7554e55c2c
commit 69c754f0d4
3 changed files with 111 additions and 4 deletions

View File

@@ -811,6 +811,10 @@ auto fmt::formatter<cql3::expr::expression::printer>::format(const cql3::expr::e
out = fmt::format_to(out, "}}");
return;
}
case collection_constructor::style_type::vector: {
out = fmt::format_to(out, "[{}]", fmt::join(cc.elements | std::views::transform(to_printer), ", "));
return;
}
}
on_internal_error(expr_logger, fmt::format("unexpected collection_constructor style {}", static_cast<unsigned>(cc.style)));
},
@@ -1495,6 +1499,22 @@ static cql3::raw_value evaluate_list(const collection_constructor& collection,
return raw_value::make_value(std::move(collection_bytes));
}
static cql3::raw_value evaluate_vector(const collection_constructor& vector, const evaluation_inputs& inputs) {
std::vector<managed_bytes> vector_elements;
vector_elements.reserve(vector.elements.size());
for (const expression& element : vector.elements) {
cql3::raw_value elem_val = evaluate(element, inputs);
if (elem_val.is_null()) {
throw exceptions::invalid_request_exception("null is not supported inside vectors");
}
vector_elements.emplace_back(std::move(elem_val).to_managed_bytes());
}
managed_bytes vector_bytes = vector_type_impl::build_value_fragmented(std::move(vector_elements), vector.type->value_length_if_fixed());
return raw_value::make_value(std::move(vector_bytes));
}
static cql3::raw_value evaluate_set(const collection_constructor& collection, const evaluation_inputs& inputs) {
const set_type_impl& stype = dynamic_cast<const set_type_impl&>(collection.type->without_reversed());
std::set<managed_bytes, serialized_compare> evaluated_elements(stype.get_elements_type()->as_less_comparator());
@@ -1579,6 +1599,9 @@ static cql3::raw_value do_evaluate(const collection_constructor& collection, con
case collection_constructor::style_type::map:
return evaluate_map(collection, inputs);
case collection_constructor::style_type::vector:
return evaluate_vector(collection, inputs);
}
std::abort();
}

View File

@@ -417,7 +417,7 @@ struct tuple_constructor {
// For example: "[1, 2, ?]", "{5, 6, 7}", {1: 2, 3: 4}"
// During preparation collection constructors with constant values are converted to expr::constant.
struct collection_constructor {
enum class style_type { list, set, map };
enum class style_type { list, set, map, vector };
style_type style;
// For map constructors, elements is a list of key-pair tuples.
@@ -460,8 +460,8 @@ struct expression::impl final {
using variant_type = std::variant<
conjunction, binary_operator, column_value, unresolved_identifier,
column_mutation_attribute, function_call, cast, field_selection,
bind_variable, untyped_constant, constant, tuple_constructor, collection_constructor,
usertype_constructor, subscript, temporary>;
bind_variable, untyped_constant, constant, tuple_constructor,
collection_constructor, usertype_constructor, subscript, temporary>;
variant_type v;
impl(variant_type v) : v(std::move(v)) {}
};

View File

@@ -20,6 +20,7 @@
#include "types/list.hh"
#include "types/set.hh"
#include "types/map.hh"
#include "types/vector.hh"
#include "types/user.hh"
#include "exceptions/unrecognized_entity_exception.hh"
#include "utils/like_matcher.hh"
@@ -492,6 +493,85 @@ list_prepare_expression(const collection_constructor& c, data_dictionary::databa
}
}
static
lw_shared_ptr<column_specification>
vector_value_spec_of(const column_specification& column) {
return make_lw_shared<column_specification>(column.ks_name, column.cf_name,
::make_shared<column_identifier>(format("value({})", *column.name), true),
dynamic_cast<const vector_type_impl&>(column.type->without_reversed()).get_elements_type());
}
static
void
vector_validate_assignable_to(const collection_constructor& c, data_dictionary::database db, const sstring keyspace, const schema* schema_opt, const column_specification& receiver) {
auto vt = dynamic_pointer_cast<const vector_type_impl>(receiver.type->underlying_type());
if (!vt) {
throw exceptions::invalid_request_exception(format("Invalid vector type literal for {} of type {}", *receiver.name, receiver.type->as_cql3_type()));
}
size_t expected_size = vt->get_dimension();
if (!expected_size) {
throw exceptions::invalid_request_exception(format("Invalid vector type literal for {}: type {} expects at least one element",
*receiver.name, receiver.type->as_cql3_type()));
}
size_t received_size = c.elements.size();
if (expected_size != received_size) {
throw exceptions::invalid_request_exception(format("Invalid vector literal for {}: type {} expects {:d} elements but got {:d}",
*receiver.name, receiver.type->as_cql3_type(), expected_size, received_size));
}
auto&& value_spec = vector_value_spec_of(receiver);
for (auto& e : c.elements) {
if (!is_assignable(test_assignment(e, db, keyspace, schema_opt, *value_spec))) {
throw exceptions::invalid_request_exception(format("Invalid vector literal for {}: value {} is not of type {}",
*receiver.name, e, value_spec->type->as_cql3_type()));
}
}
}
static
assignment_testable::test_result
vector_test_assignment(const collection_constructor& c, data_dictionary::database db, const sstring& keyspace, const schema* schema_opt, const column_specification& receiver) {
// If there is no elements, we can't say it's an exact match (an empty vector if fundamentally polymorphic).
if (c.elements.empty()) {
return assignment_testable::test_result::WEAKLY_ASSIGNABLE;
}
auto&& value_spec = vector_value_spec_of(receiver);
return test_assignment_all(c.elements, db, keyspace, schema_opt, *value_spec);
}
static
std::optional<expression>
vector_prepare_expression(const collection_constructor& c, data_dictionary::database db, const sstring& keyspace, const schema* schema_opt, lw_shared_ptr<column_specification> receiver) {
vector_validate_assignable_to(c, db, keyspace, schema_opt, *receiver);
auto&& value_spec = vector_value_spec_of(*receiver);
std::vector<expression> values;
values.reserve(c.elements.size());
bool all_terminal = true;
for (auto& e : c.elements) {
expression elem = prepare_expression(e, db, keyspace, schema_opt, value_spec);
if (!is<constant>(elem)) {
all_terminal = false;
}
values.push_back(std::move(elem));
}
// Here we convert from list_or_vector to vector style, as we know that we received a vector type.
collection_constructor value {
.style = collection_constructor::style_type::vector,
.elements = std::move(values),
.type = receiver->type
};
if (all_terminal) {
return constant(evaluate(value, query_options::DEFAULT), value.type);
} else {
return value;
}
}
static
lw_shared_ptr<column_specification>
component_spec_of(const column_specification& column, size_t component) {
@@ -624,7 +704,7 @@ untyped_constant_test_assignment(const untyped_constant& uc, data_dictionary::da
{
bool uc_is_null = uc.partial_type == untyped_constant::type_class::null;
auto receiver_type = receiver.type->as_cql3_type();
if ((receiver_type.is_collection() || receiver_type.is_user_type()) && !uc_is_null) {
if ((receiver_type.is_collection() || receiver_type.is_user_type() || receiver_type.is_vector()) && !uc_is_null) {
return assignment_testable::test_result::NOT_ASSIGNABLE;
}
if (!receiver_type.is_native()) {
@@ -1243,6 +1323,8 @@ try_prepare_expression(const expression& expr, data_dictionary::database db, con
case collection_constructor::style_type::list: return list_prepare_expression(c, db, keyspace, schema_opt, receiver);
case collection_constructor::style_type::set: return set_prepare_expression(c, db, keyspace, schema_opt, receiver);
case collection_constructor::style_type::map: return map_prepare_expression(c, db, keyspace, schema_opt, receiver);
case collection_constructor::style_type::vector:
on_internal_error(expr_logger, "vector style type found during prepare, should have been introduced post-prepare");
}
on_internal_error(expr_logger, fmt::format("unexpected collection_constructor style {}", static_cast<unsigned>(c.style)));
},
@@ -1318,6 +1400,8 @@ test_assignment(const expression& expr, data_dictionary::database db, const sstr
case collection_constructor::style_type::list: return list_test_assignment(c, db, keyspace, schema_opt, receiver);
case collection_constructor::style_type::set: return set_test_assignment(c, db, keyspace, schema_opt, receiver);
case collection_constructor::style_type::map: return map_test_assignment(c, db, keyspace, schema_opt, receiver);
case collection_constructor::style_type::vector:
on_internal_error(expr_logger, "vector style type found in test_assignment, should have been introduced post-prepare");
}
on_internal_error(expr_logger, fmt::format("unexpected collection_constructor style {}", static_cast<unsigned>(c.style)));
},