diff --git a/cql3/Cql.g b/cql3/Cql.g index b5b5d01e94..2063f5cab4 100644 --- a/cql3/Cql.g +++ b/cql3/Cql.g @@ -1465,7 +1465,11 @@ collectionLiteral returns [shared_ptr value] @init{ std::vector> l; } : '[' ( t1=term { l.push_back(t1); } ( ',' tn=term { l.push_back(tn); } )* )? - ']' { $value = ::make_shared(std::move(l)); } + ']' { $value = cql3::expr::as_term_raw( + cql3::expr::collection_constructor{ + cql3::expr::collection_constructor::style_type::list, + boost::copy_range>(std::move(l) | boost::adaptors::transformed(cql3::expr::as_expression)) + }); } | '{' t=term v=setOrMapLiteral[t] { $value = v; } '}' // Note that we have an ambiguity between maps and set for "{}". So we force it to a set literal, // and deal with it later based on the type of the column (SetLiteral.java). diff --git a/cql3/expr/expression.cc b/cql3/expr/expression.cc index 0f484f42f7..6af5997107 100644 --- a/cql3/expr/expression.cc +++ b/cql3/expr/expression.cc @@ -566,6 +566,9 @@ bool is_satisfied_by(const binary_operator& opr, const column_value_eval_bag& ba [] (const tuple_constructor&) -> bool { on_internal_error(expr_logger, "is_satisified_by: tuple_constructor cannot serve as the LHS of a binary expression (yet!)"); }, + [] (const collection_constructor&) -> bool { + on_internal_error(expr_logger, "is_satisified_by: collection_constructor cannot serve as the LHS of a binary expression"); + }, }, *opr.lhs); } @@ -617,6 +620,9 @@ bool is_satisfied_by(const expression& restr, const column_value_eval_bag& bag) [] (const tuple_constructor&) -> bool { on_internal_error(expr_logger, "is_satisfied_by: a tuple constructor cannot serve as a restriction by itself"); }, + [] (const collection_constructor&) -> bool { + on_internal_error(expr_logger, "is_satisfied_by: a collection constructor cannot serve as a restriction by itself"); + }, }, restr); } @@ -868,6 +874,9 @@ value_set possible_lhs_values(const column_definition* cdef, const expression& e [] (const tuple_constructor&) -> value_set { on_internal_error(expr_logger, "possible_lhs_values: tuple constructors are not supported as the LHS of a binary expression yet"); }, + [] (const collection_constructor&) -> value_set { + on_internal_error(expr_logger, "possible_lhs_values: collection constructors are not supported as the LHS of a binary expression"); + }, }, *oper.lhs); }, [] (const column_value&) -> value_set { @@ -909,6 +918,9 @@ value_set possible_lhs_values(const column_definition* cdef, const expression& e [] (const tuple_constructor&) -> value_set { on_internal_error(expr_logger, "possible_lhs_values: an tuple constructor cannot serve as a restriction by itself"); }, + [] (const collection_constructor&) -> value_set { + on_internal_error(expr_logger, "possible_lhs_values: a collection constructor cannot serve as a restriction by itself"); + }, }, expr); } @@ -982,6 +994,9 @@ bool is_supported_by(const expression& expr, const secondary_index::index& idx) [&] (const tuple_constructor&) -> bool { on_internal_error(expr_logger, "is_supported_by: tuple constructors are not supported as the LHS of a binary expression yet"); }, + [&] (const collection_constructor&) -> bool { + on_internal_error(expr_logger, "is_supported_by: collection constructors are not supported as the LHS of a binary expression"); + }, }, *oper.lhs); }, [] (const auto& default_case) { return false; } @@ -1074,6 +1089,12 @@ std::ostream& operator<<(std::ostream& os, const expression& expr) { [&] (const tuple_constructor& tc) { fmt::print(os, "({})", join(", ", tc.elements)); }, + [&] (const collection_constructor& cc) { + switch (cc.style) { + case collection_constructor::style_type::list: fmt::print(os, "{}", std::to_string(cc.elements)); return; + } + on_internal_error(expr_logger, fmt::format("unexpected collection_constructor style {}", static_cast(cc.style))); + }, }, expr); return os; } @@ -1128,6 +1149,16 @@ expression replace_column_def(const expression& expr, const column_definition* n } }; }, + [&] (const collection_constructor& c) { + return expression{ + collection_constructor{ + c.style, + boost::copy_range>( + c.elements | boost::adaptors::transformed(std::bind(replace_column_def, std::placeholders::_1, new_cdef)) + ) + } + }; + }, }, expr); } @@ -1186,6 +1217,13 @@ expression replace_token(const expression& expr, const column_definition* new_cd tc.elements | boost::adaptors::transformed(std::bind(replace_token, std::placeholders::_1, new_cdef)) )}; }, + [&] (const collection_constructor& c) -> expression { + return collection_constructor{ + c.style, + boost::copy_range>( + c.elements | boost::adaptors::transformed(std::bind(replace_token, std::placeholders::_1, new_cdef))) + }; + }, }, expr); } @@ -1277,6 +1315,7 @@ std::vector extract_single_column_restrictions_for_column(const expr void operator()(const bind_variable&) {} void operator()(const untyped_constant&) {} void operator()(const tuple_constructor&) {} + void operator()(const collection_constructor&) {} }; visitor v { diff --git a/cql3/expr/expression.hh b/cql3/expr/expression.hh index 22efdc237d..5cc2ebac2b 100644 --- a/cql3/expr/expression.hh +++ b/cql3/expr/expression.hh @@ -86,12 +86,13 @@ struct null; struct bind_variable; struct untyped_constant; struct tuple_constructor; +struct collection_constructor; /// A CQL expression -- union of all possible expression types. bool means a Boolean constant. using expression = std::variant; + tuple_constructor, collection_constructor>; template concept ExpressionElement = utils::VariantElement; @@ -221,6 +222,13 @@ struct tuple_constructor { std::vector elements; }; +// Constructs a collection of same-typed elements +struct collection_constructor { + enum class style_type { list }; + style_type style; + std::vector elements; +}; + /// Creates a conjunction of a and b. If either a or b is itself a conjunction, its children are inserted /// directly into the resulting conjunction's children, flattening the expression tree. extern expression make_conjunction(expression a, expression b); @@ -332,6 +340,14 @@ const binary_operator* find_atom(const expression& e, Fn f) { } return nullptr; }, + [&] (const collection_constructor& c) -> const binary_operator* { + for (auto& e : c.elements) { + if (auto found = find_atom(e, f)) { + return found; + } + } + return nullptr; + }, }, e); } @@ -376,6 +392,10 @@ size_t count_if(const expression& e, Fn f) { return std::accumulate(t.elements.cbegin(), t.elements.cend(), size_t{0}, [&] (size_t acc, const expression& e) { return acc + count_if(e, f); }); }, + [&] (const collection_constructor& c) -> size_t { + return std::accumulate(c.elements.cbegin(), c.elements.cend(), size_t{0}, + [&] (size_t acc, const expression& e) { return acc + count_if(e, f); }); + }, }, e); } diff --git a/cql3/expr/term_expr.cc b/cql3/expr/term_expr.cc index ace89766aa..b5fb5bf875 100644 --- a/cql3/expr/term_expr.cc +++ b/cql3/expr/term_expr.cc @@ -30,73 +30,72 @@ #include "cql3/tuples.hh" #include "types/list.hh" -namespace cql3 { - -sstring -lists::literal::to_string() const { - return std::to_string(_elements); -} +namespace cql3::expr { +static lw_shared_ptr -lists::value_spec_of(const column_specification& column) { +list_value_spec_of(const column_specification& column) { return make_lw_shared(column.ks_name, column.cf_name, ::make_shared(format("value({})", *column.name), true), dynamic_cast(column.type->without_reversed()).get_elements_type()); } +static void -lists::literal::validate_assignable_to(database& db, const sstring keyspace, const column_specification& receiver) const { +list_validate_assignable_to(const collection_constructor& c, database& db, const sstring keyspace, const column_specification& receiver) { if (!receiver.type->without_reversed().is_list()) { throw exceptions::invalid_request_exception(format("Invalid list literal for {} of type {}", *receiver.name, receiver.type->as_cql3_type())); } - auto&& value_spec = value_spec_of(receiver); - for (auto rt : _elements) { - if (!is_assignable(rt->test_assignment(db, keyspace, *value_spec))) { + auto&& value_spec = list_value_spec_of(receiver); + for (auto& rt : c.elements) { + if (!is_assignable(as_term_raw(rt)->test_assignment(db, keyspace, *value_spec))) { throw exceptions::invalid_request_exception(format("Invalid list literal for {}: value {} is not of type {}", - *receiver.name, *rt, value_spec->type->as_cql3_type())); + *receiver.name, rt, value_spec->type->as_cql3_type())); } } } +static assignment_testable::test_result -lists::literal::test_assignment(database& db, const sstring& keyspace, const column_specification& receiver) const { +list_test_assignment(const collection_constructor& c, database& db, const sstring& keyspace, const column_specification& receiver) { if (!dynamic_pointer_cast(receiver.type)) { return assignment_testable::test_result::NOT_ASSIGNABLE; } // If there is no elements, we can't say it's an exact match (an empty list if fundamentally polymorphic). - if (_elements.empty()) { + if (c.elements.empty()) { return assignment_testable::test_result::WEAKLY_ASSIGNABLE; } - auto&& value_spec = value_spec_of(receiver); + auto&& value_spec = list_value_spec_of(receiver); std::vector> to_test; - to_test.reserve(_elements.size()); - std::copy(_elements.begin(), _elements.end(), std::back_inserter(to_test)); + to_test.reserve(c.elements.size()); + boost::copy(c.elements | boost::adaptors::transformed(as_term_raw), std::back_inserter(to_test)); return assignment_testable::test_all(db, keyspace, *value_spec, to_test); } +static shared_ptr -lists::literal::prepare(database& db, const sstring& keyspace, const column_specification_or_tuple& receiver_) const { +list_prepare_term(const collection_constructor& c, database& db, const sstring& keyspace, const column_specification_or_tuple& receiver_) { auto receiver = std::get>(receiver_); - validate_assignable_to(db, keyspace, *receiver); + list_validate_assignable_to(c, db, keyspace, *receiver); // In Cassandra, an empty (unfrozen) map/set/list is equivalent to the column being null. In // other words a non-frozen collection only exists if it has elements. Return nullptr right // away to simplify predicate evaluation. See also // https://issues.apache.org/jira/browse/CASSANDRA-5141 - if (receiver->type->is_multi_cell() && _elements.empty()) { + if (receiver->type->is_multi_cell() && c.elements.empty()) { return cql3::constants::NULL_VALUE; } - auto&& value_spec = value_spec_of(*receiver); + auto&& value_spec = list_value_spec_of(*receiver); std::vector> values; - values.reserve(_elements.size()); + values.reserve(c.elements.size()); bool all_terminal = true; - for (auto rt : _elements) { - auto&& t = rt->prepare(db, keyspace, value_spec); + for (auto& rt : c.elements) { + auto&& t = as_term_raw(rt)->prepare(db, keyspace, value_spec); if (t->contains_bind_marker()) { throw exceptions::invalid_request_exception(format("Invalid list literal for {}: bind variables are not supported inside collection literals", *receiver->name)); @@ -106,18 +105,14 @@ lists::literal::prepare(database& db, const sstring& keyspace, const column_spec } values.push_back(std::move(t)); } - delayed_value value(values); + lists::delayed_value value(values); if (all_terminal) { return value.bind(query_options::DEFAULT); } else { - return make_shared(std::move(value)); + return make_shared(std::move(value)); } } -} - -namespace cql3::expr { - static lw_shared_ptr component_spec_of(const column_specification& column, size_t component) { @@ -571,6 +566,12 @@ term_raw_expr::prepare(database& db, const sstring& keyspace, const column_speci [&] (const tuple_constructor& tc) -> ::shared_ptr { return tuple_constructor_prepare_term(tc, db, keyspace, receiver); }, + [&] (const collection_constructor& c) -> ::shared_ptr { + switch (c.style) { + case collection_constructor::style_type::list: return list_prepare_term(c, db, keyspace, receiver); + } + on_internal_error(expr_logger, fmt::format("unexpected collection_constructor style {}", static_cast(c.style))); + }, }, _expr); } @@ -626,6 +627,12 @@ term_raw_expr::test_assignment(database& db, const sstring& keyspace, const colu [&] (const tuple_constructor& tc) -> test_result { return tuple_constructor_test_assignment(tc, db, keyspace, receiver); }, + [&] (const collection_constructor& c) -> test_result { + switch (c.style) { + case collection_constructor::style_type::list: return list_test_assignment(c, db, keyspace, receiver); + } + on_internal_error(expr_logger, fmt::format("unexpected collection_constructor style {}", static_cast(c.style))); + }, }, _expr); } @@ -650,4 +657,13 @@ term_raw_expr::assignment_testable_source_context() const { } -} \ No newline at end of file +} + +namespace cql3 { + +lw_shared_ptr +lists::value_spec_of(const column_specification& column) { + return cql3::expr::list_value_spec_of(column); +} + +} diff --git a/cql3/lists.hh b/cql3/lists.hh index 8cf4d43d0a..e8f76221b9 100644 --- a/cql3/lists.hh +++ b/cql3/lists.hh @@ -58,20 +58,6 @@ public: static lw_shared_ptr value_spec_of(const column_specification&); static lw_shared_ptr uuid_index_spec_of(const column_specification&); - class literal : public term::raw { - const std::vector> _elements; - public: - explicit literal(std::vector> elements) - : _elements(std::move(elements)) { - } - virtual shared_ptr prepare(database& db, const sstring& keyspace, const column_specification_or_tuple& receiver) const override; - private: - void validate_assignable_to(database& db, const sstring keyspace, const column_specification& receiver) const; - public: - virtual assignment_testable::test_result test_assignment(database& db, const sstring& keyspace, const column_specification& receiver) const override; - virtual sstring to_string() const override; - }; - class value : public multi_item_terminal, collection_terminal { public: utils::chunked_vector _elements; diff --git a/cql3/restrictions/statement_restrictions.cc b/cql3/restrictions/statement_restrictions.cc index b0138ac8c3..e531629d63 100644 --- a/cql3/restrictions/statement_restrictions.cc +++ b/cql3/restrictions/statement_restrictions.cc @@ -246,6 +246,10 @@ static std::vector extract_partition_range( void operator()(const tuple_constructor&) { on_internal_error(rlogger, "extract_partition_range(tuple_constructor)"); } + + void operator()(const collection_constructor&) { + on_internal_error(rlogger, "extract_partition_range(collection_constructor)"); + } } v; std::visit(v, where_clause); if (v.tokens) { @@ -351,6 +355,10 @@ static std::vector extract_clustering_prefix_restrictions( void operator()(const tuple_constructor&) { on_internal_error(rlogger, "extract_clustering_prefix_restrictions(tuple_constructor)"); } + + void operator()(const collection_constructor&) { + on_internal_error(rlogger, "extract_clustering_prefix_restrictions(collection_constructor)"); + } } v; std::visit(v, where_clause); @@ -1068,6 +1076,10 @@ struct multi_column_range_accumulator { on_internal_error(rlogger, "tuple constructor encountered outside binary operator"); } + void operator()(const collection_constructor&) { + on_internal_error(rlogger, "collection constructor encountered outside binary operator"); + } + /// Intersects each range with v. If any intersection is empty, clears ranges. void intersect_all(const query::clustering_range& v) { for (auto& r : ranges) { diff --git a/cql3/selection/selectable.cc b/cql3/selection/selectable.cc index 1339e53169..83868db4c0 100644 --- a/cql3/selection/selectable.cc +++ b/cql3/selection/selectable.cc @@ -223,6 +223,9 @@ prepare_selectable(const schema& s, const expr::expression& raw_selectable) { [&] (const expr::tuple_constructor&) -> shared_ptr { on_internal_error(slogger, "tuple_constructor found its way to selector context"); }, + [&] (const expr::collection_constructor&) -> shared_ptr { + on_internal_error(slogger, "collection_constructor found its way to selector context"); + }, }, raw_selectable); } @@ -281,6 +284,9 @@ selectable_processes_selection(const expr::expression& raw_selectable) { [&] (const expr::tuple_constructor&) -> bool { on_internal_error(slogger, "tuple_constructor found its way to selector context"); }, + [&] (const expr::collection_constructor&) -> bool { + on_internal_error(slogger, "collection_constructor found its way to selector context"); + }, }, raw_selectable); };