diff --git a/configure.py b/configure.py index 42099dc4cf..4a5afd0a50 100755 --- a/configure.py +++ b/configure.py @@ -266,7 +266,10 @@ urchin_core = (['database.cc', 'cql3/cql3.cc', 'cql3/cql3_type.cc', 'cql3/operation.cc', + 'cql3/lists.cc', + 'cql3/sets.cc', 'cql3/functions/functions.cc', + 'cql3/statements/cf_prop_defs.cc', 'cql3/statements/schema_altering_statement.cc', 'cql3/statements/modification_statement.cc', 'cql3/statements/update_statement.cc', diff --git a/cql3/Cql.g b/cql3/Cql.g index 68b1b63a4f..da05aaa268 100644 --- a/cql3/Cql.g +++ b/cql3/Cql.g @@ -31,6 +31,7 @@ options { @parser::includes { #include "cql3/statements/create_keyspace_statement.hh" +#include "cql3/statements/create_table_statement.hh" #include "cql3/statements/property_definitions.hh" #include "cql3/statements/select_statement.hh" #include "cql3/statements/update_statement.hh" @@ -280,8 +281,8 @@ cqlStatement returns [shared_ptr stmt] | st7= truncateStatement { $stmt = st7; } #endif | st8= createKeyspaceStatement { $stmt = st8; } -#if 0 | st9= createTableStatement { $stmt = st9; } +#if 0 | st10=createIndexStatement { $stmt = st10; } | st11=dropKeyspaceStatement { $stmt = st11; } | st12=dropTableStatement { $stmt = st12; } @@ -667,7 +668,6 @@ createKeyspaceStatement returns [shared_ptr(ks, attrs, if_not_exists); } ; -#if 0 /** * CREATE COLUMNFAMILY [IF NOT EXISTS] ( * , @@ -675,41 +675,44 @@ createKeyspaceStatement returns [shared_ptr * ) WITH = AND ...; */ -createTableStatement returns [CreateTableStatement.RawStatement expr] - @init { boolean ifNotExists = false; } - : K_CREATE K_COLUMNFAMILY (K_IF K_NOT K_EXISTS { ifNotExists = true; } )? - cf=columnFamilyName { $expr = new CreateTableStatement.RawStatement(cf, ifNotExists); } +createTableStatement returns [shared_ptr expr] + @init { bool if_not_exists = false; } + : K_CREATE K_COLUMNFAMILY (K_IF K_NOT K_EXISTS { if_not_exists = true; } )? + cf=columnFamilyName { $expr = make_shared(cf, if_not_exists); } cfamDefinition[expr] ; -cfamDefinition[CreateTableStatement.RawStatement expr] +cfamDefinition[shared_ptr expr] : '(' cfamColumns[expr] ( ',' cfamColumns[expr]? )* ')' ( K_WITH cfamProperty[expr] ( K_AND cfamProperty[expr] )*)? ; -cfamColumns[CreateTableStatement.RawStatement expr] - : k=ident v=comparatorType { boolean isStatic=false; } (K_STATIC {isStatic = true;})? { $expr.addDefinition(k, v, isStatic); } - (K_PRIMARY K_KEY { $expr.addKeyAliases(Collections.singletonList(k)); })? - | K_PRIMARY K_KEY '(' pkDef[expr] (',' c=ident { $expr.addColumnAlias(c); } )* ')' +cfamColumns[shared_ptr expr] + @init { bool is_static=false; } + : k=ident v=comparatorType (K_STATIC {is_static = true;})? { $expr->add_definition(k, v, is_static); } + (K_PRIMARY K_KEY { $expr->add_key_aliases(std::vector>{k}); })? + | K_PRIMARY K_KEY '(' pkDef[expr] (',' c=ident { $expr->add_column_alias(c); } )* ')' ; -pkDef[CreateTableStatement.RawStatement expr] - : k=ident { $expr.addKeyAliases(Collections.singletonList(k)); } - | '(' { List l = new ArrayList(); } k1=ident { l.add(k1); } ( ',' kn=ident { l.add(kn); } )* ')' { $expr.addKeyAliases(l); } +pkDef[shared_ptr expr] + @init { std::vector> l; } + : k=ident { $expr->add_key_aliases(std::vector>{k}); } + | '(' k1=ident { l.push_back(k1); } ( ',' kn=ident { l.push_back(kn); } )* ')' { $expr->add_key_aliases(l); } ; -cfamProperty[CreateTableStatement.RawStatement expr] - : property[expr.properties] - | K_COMPACT K_STORAGE { $expr.setCompactStorage(); } +cfamProperty[shared_ptr expr] + : property[expr->properties] + | K_COMPACT K_STORAGE { $expr->set_compact_storage(); } | K_CLUSTERING K_ORDER K_BY '(' cfamOrdering[expr] (',' cfamOrdering[expr])* ')' ; -cfamOrdering[CreateTableStatement.RawStatement expr] - @init{ boolean reversed=false; } - : k=ident (K_ASC | K_DESC { reversed=true;} ) { $expr.setOrdering(k, reversed); } +cfamOrdering[shared_ptr expr] + @init{ bool reversed=false; } + : k=ident (K_ASC | K_DESC { reversed=true;} ) { $expr->set_ordering(k, reversed); } ; +#if 0 /** * CREATE TYPE foo ( * , diff --git a/cql3/column_condition.cc b/cql3/column_condition.cc index 952188ab23..839acdbb4d 100644 --- a/cql3/column_condition.cc +++ b/cql3/column_condition.cc @@ -58,7 +58,7 @@ void column_condition::collect_marker_specificaton(::shared_ptr -column_condition::raw::prepare(const sstring& keyspace, column_definition& receiver) { +column_condition::raw::prepare(const sstring& keyspace, const column_definition& receiver) { if (receiver.type->is_counter()) { throw exceptions::invalid_request_exception("Conditions on counters are not supported"); } diff --git a/cql3/column_condition.hh b/cql3/column_condition.hh index edad9c4147..86e21b9b72 100644 --- a/cql3/column_condition.hh +++ b/cql3/column_condition.hh @@ -56,7 +56,7 @@ namespace cql3 { */ class column_condition final { public: - column_definition& column; + const column_definition& column; private: // For collection, when testing the equality of a specific element, nullptr otherwise. ::shared_ptr _collection_element; @@ -64,7 +64,7 @@ private: std::vector<::shared_ptr> _in_values; const operator_type& _op; public: - column_condition(column_definition& column, ::shared_ptr collection_element, + column_condition(const column_definition& column, ::shared_ptr collection_element, ::shared_ptr value, std::vector<::shared_ptr> in_values, const operator_type& op) : column(column) , _collection_element(std::move(collection_element)) @@ -77,33 +77,33 @@ public: } } - static ::shared_ptr condition(column_definition& def, ::shared_ptr value, const operator_type& op) { + static ::shared_ptr condition(const column_definition& def, ::shared_ptr value, const operator_type& op) { return ::make_shared(def, ::shared_ptr{}, std::move(value), std::vector<::shared_ptr>{}, op); } - static ::shared_ptr condition(column_definition& def, ::shared_ptr collection_element, + static ::shared_ptr condition(const column_definition& def, ::shared_ptr collection_element, ::shared_ptr value, const operator_type& op) { return ::make_shared(def, std::move(collection_element), std::move(value), std::vector<::shared_ptr>{}, op); } - static ::shared_ptr in_condition(column_definition& def, std::vector<::shared_ptr> in_values) { + static ::shared_ptr in_condition(const column_definition& def, std::vector<::shared_ptr> in_values) { return ::make_shared(def, ::shared_ptr{}, ::shared_ptr{}, std::move(in_values), operator_type::IN); } - static ::shared_ptr in_condition(column_definition& def, ::shared_ptr collection_element, + static ::shared_ptr in_condition(const column_definition& def, ::shared_ptr collection_element, std::vector<::shared_ptr> in_values) { return ::make_shared(def, std::move(collection_element), ::shared_ptr{}, std::move(in_values), operator_type::IN); } - static ::shared_ptr in_condition(column_definition& def, ::shared_ptr in_marker) { + static ::shared_ptr in_condition(const column_definition& def, ::shared_ptr in_marker) { return ::make_shared(def, ::shared_ptr{}, std::move(in_marker), std::vector<::shared_ptr>{}, operator_type::IN); } - static ::shared_ptr in_condition(column_definition& def, ::shared_ptr collection_element, + static ::shared_ptr in_condition(const column_definition& def, ::shared_ptr collection_element, ::shared_ptr in_marker) { return ::make_shared(def, std::move(collection_element), std::move(in_marker), std::vector<::shared_ptr>{}, operator_type::IN); @@ -758,7 +758,7 @@ public: std::move(collection_element), operator_type::IN); } - ::shared_ptr prepare(const sstring& keyspace, column_definition& receiver); + ::shared_ptr prepare(const sstring& keyspace, const column_definition& receiver); }; }; diff --git a/cql3/column_identifier.hh b/cql3/column_identifier.hh index acde217542..c38fdb8648 100644 --- a/cql3/column_identifier.hh +++ b/cql3/column_identifier.hh @@ -178,7 +178,7 @@ public: }; static inline -column_definition* get_column_definition(schema_ptr schema, column_identifier& id) { +const column_definition* get_column_definition(schema_ptr schema, const column_identifier& id) { return schema->get_column_definition(id.bytes_); } diff --git a/cql3/lists.cc b/cql3/lists.cc new file mode 100644 index 0000000000..eb65a26141 --- /dev/null +++ b/cql3/lists.cc @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2015 Cloudius Systems, Ltd. + */ + +#include "lists.hh" +#include "update_parameters.hh" +#include "column_identifier.hh" +#include "cql3_type.hh" + +namespace cql3 { + +shared_ptr +lists::index_spec_of(shared_ptr column) { + return make_shared(column->ks_name, column->cf_name, + ::make_shared(sprint("idx(%s)", *column->name), true), int32_type); +} + +shared_ptr +lists::value_spec_of(shared_ptr column) { + return make_shared(column->ks_name, column->cf_name, + ::make_shared(sprint("value(%s)", *column->name), true), + dynamic_pointer_cast(column->type)->get_elements_type()); +} + +shared_ptr +lists::literal::prepare(const sstring& keyspace, shared_ptr receiver) { + validate_assignable_to(keyspace, receiver); + + auto&& value_spec = value_spec_of(receiver); + std::vector> values; + values.reserve(_elements.size()); + bool all_terminal = true; + for (auto rt : _elements) { + auto&& t = rt->prepare(keyspace, value_spec); + + if (t->contains_bind_marker()) { + throw exceptions::invalid_request_exception(sprint("Invalid list literal for %s: bind variables are not supported inside collection literals", *receiver->name)); + } + if (dynamic_pointer_cast(t)) { + all_terminal = false; + } + values.push_back(std::move(t)); + } + delayed_value value(values); + if (all_terminal) { + return value.bind(query_options::DEFAULT); + } else { + return make_shared(std::move(value)); + } +} + +void +lists::literal::validate_assignable_to(const sstring keyspace, shared_ptr receiver) { + if (!dynamic_pointer_cast(receiver->type)) { + throw exceptions::invalid_request_exception(sprint("Invalid list literal for %s of type %s", + *receiver->name, *receiver->type->as_cql3_type())); + } + auto&& value_spec = value_spec_of(receiver); + for (auto rt : _elements) { + if (!is_assignable(rt->test_assignment(keyspace, value_spec))) { + throw exceptions::invalid_request_exception(sprint("Invalid list literal for %s: value %s is not of type %s", + *receiver->name, *rt, *value_spec->type->as_cql3_type())); + } + } +} + +assignment_testable::test_result +lists::literal::test_assignment(const sstring& keyspace, shared_ptr 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()) { + return assignment_testable::test_result::WEAKLY_ASSIGNABLE; + } + + auto&& value_spec = value_spec_of(receiver); + std::vector> to_test; + to_test.reserve(_elements.size()); + std::copy(_elements.begin(), _elements.end(), std::back_inserter(to_test)); + return assignment_testable::test_all(keyspace, value_spec, to_test); +} + +sstring +lists::literal::to_string() const { + return ::to_string(_elements); +} + +lists::value +lists::value::from_serialized(bytes_view v, shared_ptr type, serialization_format sf) { + try { + // Collections have this small hack that validate cannot be called on a serialized object, + // but compose does the validation (so we're fine). + // FIXME: deserializeForNativeProtocol()?! + auto&& l = boost::any_cast(type->deserialize(v, sf)); + std::vector elements; + elements.reserve(l.size()); + for (auto&& element : l) { + // elements can be null in lists that represent a set of IN values + // FIXME: assumes that empty bytes is equivalent to null element + elements.push_back(element.empty() ? bytes() : type->get_elements_type()->decompose(element)); + } + return value(std::move(elements)); + } catch (marshal_exception& e) { + throw exceptions::invalid_request_exception(e.what()); + } +} + +bytes_opt +lists::value::get(const query_options& options) { + return get_with_protocol_version(options.get_serialization_format()); +} + +bytes +lists::value::get_with_protocol_version(serialization_format sf) { + return collection_type_impl::pack(_elements.begin(), _elements.end(), _elements.size(), sf); +} + +bool +lists::value::equals(shared_ptr lt, const value& v) { + if (_elements.size() != v._elements.size()) { + return false; + } + return std::equal(_elements.begin(), _elements.end(), + v._elements.begin(), + [t = lt->get_elements_type()] (bytes_view e1, bytes_view e2) { return t->equal(e1, e2); }); +} + +std::vector +lists::value::get_elements() { + return _elements; +} + +sstring +lists::value::to_string() const { + std::ostringstream os; + os << "["; + bool is_first = true; + for (auto&& e : _elements) { + if (!is_first) { + os << ", "; + } + is_first = false; + os << to_hex(e); + } + os << "]"; + return os.str(); +} + +bool +lists::delayed_value::contains_bind_marker() const { + // False since we don't support them in collection + return false; +} + +void +lists::delayed_value::collect_marker_specification(shared_ptr bound_names) { +} + +shared_ptr +lists::delayed_value::bind(const query_options& options) { + std::vector buffers; + buffers.reserve(_elements.size()); + for (auto&& t : _elements) { + bytes_opt bo = t->bind_and_get(options); + + if (!bo) { + throw exceptions::invalid_request_exception("null is not supported inside collections"); + } + + // We don't support value > 64K because the serialization format encode the length as an unsigned short. + if (bo->size() > std::numeric_limits::max()) { + throw exceptions::invalid_request_exception(sprint("List value is too long. List values are limited to %d bytes but %d bytes value provided", + std::numeric_limits::max(), + bo->size())); + } + + buffers.push_back(std::move(*bo)); + } + return ::make_shared(buffers); +} + +::shared_ptr +lists::marker::bind(const query_options& options) { + throw std::runtime_error(""); +} +#if 0 + public Value bind(QueryOptions options) throws InvalidRequestException + { + ByteBuffer value = options.getValues().get(bindIndex); + return value == null ? null : Value.fromSerialized(value, (ListType)receiver.type, options.getProtocolVersion()); + } +#endif + +void +lists::setter::execute(mutation& m, const exploded_clustering_prefix& prefix, const update_parameters& params) { + tombstone ts; + if (column.type->is_multi_cell()) { + // delete + append + ts = params.make_tombstone_just_before(); + } + do_append(_t, m, prefix, column, params, ts); +} + +bool +lists::setter_by_index::requires_read() { + return true; +} + +void +lists::setter_by_index::collect_marker_specification(shared_ptr bound_names) { + operation::collect_marker_specification(bound_names); + _idx->collect_marker_specification(std::move(bound_names)); +} + +void +lists::setter_by_index::execute(mutation& m, const exploded_clustering_prefix& prefix, const update_parameters& params) { + // we should not get here for frozen lists + assert(column.type->is_multi_cell()); // "Attempted to set an individual element on a frozen list"; + + auto row_key = clustering_key::from_clustering_prefix(*params._schema, prefix); + + bytes_opt index = _idx->bind_and_get(params._options); + bytes_opt value = _t->bind_and_get(params._options); + + if (!index) { + throw exceptions::invalid_request_exception("Invalid null value for list index"); + } + + collection_mutation::view existing_list_ser = params.get_prefetched_list(m.key, row_key, column); + auto ltype = dynamic_pointer_cast(column.type); + collection_type_impl::mutation_view existing_list = ltype->deserialize_mutation_form(existing_list_ser.data); + // we verified that index is an int32_type + auto idx = net::ntoh(int32_t(*unaligned_cast(index->begin()))); + if (idx < 0 || size_t(idx) >= existing_list.cells.size()) { + throw exceptions::invalid_request_exception(sprint("List index %d out of bound, list has size %d", + idx, existing_list.cells.size())); + } + + bytes_view eidx = existing_list.cells[idx].first; + list_type_impl::mutation mut; + mut.cells.reserve(1); + if (!value) { + mut.cells.emplace_back(to_bytes(eidx), params.make_dead_cell()); + } else { + if (value->size() > std::numeric_limits::max()) { + throw exceptions::invalid_request_exception( + sprint("List value is too long. List values are limited to %d bytes but %d bytes value provided", + std::numeric_limits::max(), value->size())); + } + mut.cells.emplace_back(to_bytes(eidx), params.make_cell(*value)); + } + auto smut = ltype->serialize_mutation_form(mut); + m.set_cell(prefix, column, atomic_cell_or_collection::from_collection_mutation(std::move(smut))); +} + +void +lists::do_append(shared_ptr t, + mutation& m, + const exploded_clustering_prefix& prefix, + const column_definition& column, + const update_parameters& params, + tombstone ts) { + auto&& value = t->bind(params._options); + auto&& list_value = dynamic_pointer_cast(value); + auto&& ltype = dynamic_pointer_cast(column.type); + if (column.type->is_multi_cell()) { + // If we append null, do nothing. Note that for Setter, we've + // already removed the previous value so we're good here too + if (!value) { + return; + } + + auto&& to_add = list_value->_elements; + collection_type_impl::mutation appended; + appended.tomb = ts; + appended.cells.reserve(to_add.size()); + for (auto&& e : to_add) { + auto uuid1 = utils::UUID_gen::get_time_UUID_bytes(); + auto uuid = bytes(reinterpret_cast(uuid1.data()), uuid1.size()); + appended.cells.emplace_back(std::move(uuid), params.make_cell(e)); + } + m.set_cell(prefix, column, ltype->serialize_mutation_form(appended)); + } else { + // for frozen lists, we're overwriting the whole cell value + if (!value) { + m.set_cell(prefix, column, params.make_dead_cell()); + } else { + auto&& to_add = list_value->_elements; + auto&& newv = collection_mutation::one{list_type_impl::pack(to_add.begin(), to_add.end(), to_add.size(), + serialization_format::internal())}; + m.set_cell(prefix, column, atomic_cell_or_collection::from_collection_mutation(std::move(newv))); + } + } +} + +} diff --git a/cql3/lists.hh b/cql3/lists.hh index 96eb88d82a..01a90f6070 100644 --- a/cql3/lists.hh +++ b/cql3/lists.hh @@ -28,6 +28,7 @@ #include "cql3/abstract_marker.hh" #include "to_string.hh" #include "utils/UUID_gen.hh" +#include "operation.hh" namespace cql3 { @@ -63,16 +64,8 @@ import org.slf4j.LoggerFactory; class lists { lists() = delete; public: - static shared_ptr index_spec_of(shared_ptr column) { - return make_shared(column->ks_name, column->cf_name, - ::make_shared(sprint("idx(%s)", *column->name), true), int32_type); - } - - static shared_ptr value_spec_of(shared_ptr column) { - return make_shared(column->ks_name, column->cf_name, - ::make_shared(sprint("value(%s)", *column->name), true), - dynamic_pointer_cast(column->type)->get_elements_type()); - } + static shared_ptr index_spec_of(shared_ptr column); + static shared_ptr value_spec_of(shared_ptr column); class literal : public term::raw { const std::vector> _elements; @@ -80,67 +73,12 @@ public: explicit literal(std::vector> elements) : _elements(std::move(elements)) { } - - shared_ptr prepare(const sstring& keyspace, shared_ptr receiver) { - validate_assignable_to(keyspace, receiver); - - auto&& value_spec = value_spec_of(receiver); - std::vector> values; - values.reserve(_elements.size()); - bool all_terminal = true; - for (auto rt : _elements) { - auto&& t = rt->prepare(keyspace, value_spec); - - if (t->contains_bind_marker()) { - throw exceptions::invalid_request_exception(sprint("Invalid list literal for %s: bind variables are not supported inside collection literals", *receiver->name)); - } - if (dynamic_pointer_cast(t)) { - all_terminal = false; - } - values.push_back(std::move(t)); - } - delayed_value value(values); - if (all_terminal) { - return value.bind(query_options::DEFAULT); - } else { - return make_shared(std::move(value)); - } - } + shared_ptr prepare(const sstring& keyspace, shared_ptr receiver); private: - void validate_assignable_to(const sstring keyspace, shared_ptr receiver) { - if (!dynamic_pointer_cast(receiver->type)) { - throw exceptions::invalid_request_exception(sprint("Invalid list literal for %s of type %s", - *receiver->name, *receiver->type->as_cql3_type())); - } - auto&& value_spec = value_spec_of(receiver); - for (auto rt : _elements) { - if (!is_assignable(rt->test_assignment(keyspace, value_spec))) { - throw exceptions::invalid_request_exception(sprint("Invalid list literal for %s: value %s is not of type %s", - *receiver->name, *rt, *value_spec->type->as_cql3_type())); - } - } - } + void validate_assignable_to(const sstring keyspace, shared_ptr receiver); public: - virtual assignment_testable::test_result test_assignment(const sstring& keyspace, shared_ptr receiver) override { - 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()) { - return assignment_testable::test_result::WEAKLY_ASSIGNABLE; - } - - auto&& value_spec = value_spec_of(receiver); - std::vector> to_test; - to_test.reserve(_elements.size()); - std::copy(_elements.begin(), _elements.end(), std::back_inserter(to_test)); - return assignment_testable::test_all(keyspace, value_spec, to_test); - } - - virtual sstring to_string() const override { - return ::to_string(_elements); - } + virtual assignment_testable::test_result test_assignment(const sstring& keyspace, shared_ptr receiver) override; + virtual sstring to_string() const override; }; class value : public multi_item_terminal, collection_terminal { @@ -149,61 +87,12 @@ public: explicit value(std::vector elements) : _elements(std::move(elements)) { } - - static value from_serialized(bytes_view v, shared_ptr type, serialization_format sf) { - try { - // Collections have this small hack that validate cannot be called on a serialized object, - // but compose does the validation (so we're fine). - // FIXME: deserializeForNativeProtocol()?! - auto&& l = boost::any_cast(type->deserialize(v, sf)); - std::vector elements; - elements.reserve(l.size()); - for (auto&& element : l) { - // elements can be null in lists that represent a set of IN values - // FIXME: assumes that empty bytes is equivalent to null element - elements.push_back(element.empty() ? bytes() : type->get_elements_type()->decompose(element)); - } - return value(std::move(elements)); - } catch (marshal_exception& e) { - throw exceptions::invalid_request_exception(e.what()); - } - } - - virtual bytes_opt get(const query_options& options) override { - return get_with_protocol_version(options.get_serialization_format()); - } - - virtual bytes get_with_protocol_version(serialization_format sf) override { - return collection_type_impl::pack(_elements.begin(), _elements.end(), _elements.size(), sf); - } - - bool equals(shared_ptr lt, const value& v) { - if (_elements.size() != v._elements.size()) { - return false; - } - return std::equal(_elements.begin(), _elements.end(), - v._elements.begin(), - [t = lt->get_elements_type()] (bytes_view e1, bytes_view e2) { return t->equal(e1, e2); }); - } - - virtual std::vector get_elements() override { - return _elements; - } - - virtual sstring to_string() const { - std::ostringstream os; - os << "["; - bool is_first = true; - for (auto&& e : _elements) { - if (!is_first) { - os << ", "; - } - is_first = false; - os << to_hex(e); - } - os << "]"; - return os.str(); - } + static value from_serialized(bytes_view v, shared_ptr type, serialization_format sf); + virtual bytes_opt get(const query_options& options) override; + virtual bytes get_with_protocol_version(serialization_format sf) override; + bool equals(shared_ptr lt, const value& v); + virtual std::vector get_elements() override; + virtual sstring to_string() const; friend class lists; }; /** @@ -221,36 +110,9 @@ public: explicit delayed_value(std::vector> elements) : _elements(std::move(elements)) { } - - virtual bool contains_bind_marker() const override { - // False since we don't support them in collection - return false; - } - - virtual void collect_marker_specification(shared_ptr bound_names) { - } - - virtual shared_ptr bind(const query_options& options) override { - std::vector buffers; - buffers.reserve(_elements.size()); - for (auto&& t : _elements) { - bytes_opt bo = t->bind_and_get(options); - - if (!bo) { - throw exceptions::invalid_request_exception("null is not supported inside collections"); - } - - // We don't support value > 64K because the serialization format encode the length as an unsigned short. - if (bo->size() > std::numeric_limits::max()) { - throw exceptions::invalid_request_exception(sprint("List value is too long. List values are limited to %d bytes but %d bytes value provided", - std::numeric_limits::max(), - bo->size())); - } - - buffers.push_back(std::move(*bo)); - } - return ::make_shared(buffers); - } + virtual bool contains_bind_marker() const override; + virtual void collect_marker_specification(shared_ptr bound_names); + virtual shared_ptr bind(const query_options& options) override; }; /** @@ -268,9 +130,7 @@ public: assert receiver.type instanceof ListType; } #endif - virtual ::shared_ptr bind(const query_options& options) override { - throw std::runtime_error(""); - } + virtual ::shared_ptr bind(const query_options& options) override; #if 0 public Value bind(QueryOptions options) throws InvalidRequestException { @@ -323,75 +183,21 @@ public: class setter : public operation { public: - setter(column_definition& column, shared_ptr t) + setter(const column_definition& column, shared_ptr t) : operation(column, std::move(t)) { } - - virtual void execute(mutation& m, const exploded_clustering_prefix& prefix, const update_parameters& params) override { - tombstone ts; - if (column.type->is_multi_cell()) { - // delete + append - ts = params.make_tombstone_just_before(); - } - do_append(_t, m, prefix, column, params, ts); - } + virtual void execute(mutation& m, const exploded_clustering_prefix& prefix, const update_parameters& params) override; }; class setter_by_index : public operation { shared_ptr _idx; public: - setter_by_index(column_definition& column, shared_ptr idx, shared_ptr t) + setter_by_index(const column_definition& column, shared_ptr idx, shared_ptr t) : operation(column, std::move(t)), _idx(std::move(idx)) { } - - virtual bool requires_read() override { - return true; - } - - virtual void collect_marker_specification(shared_ptr bound_names) override { - operation::collect_marker_specification(bound_names); - _idx->collect_marker_specification(std::move(bound_names)); - } - - virtual void execute(mutation& m, const exploded_clustering_prefix& prefix, const update_parameters& params) override { - // we should not get here for frozen lists - assert(column.type->is_multi_cell()); // "Attempted to set an individual element on a frozen list"; - - auto row_key = clustering_key::from_clustering_prefix(*params._schema, prefix); - - bytes_opt index = _idx->bind_and_get(params._options); - bytes_opt value = _t->bind_and_get(params._options); - - if (!index) { - throw exceptions::invalid_request_exception("Invalid null value for list index"); - } - - collection_mutation::view existing_list_ser = params.get_prefetched_list(m.key, row_key, column); - auto ltype = dynamic_pointer_cast(column.type); - collection_type_impl::mutation_view existing_list = ltype->deserialize_mutation_form(existing_list_ser.data); - // we verified that index is an int32_type - auto idx = net::ntoh(int32_t(*unaligned_cast(index->begin()))); - if (idx < 0 || size_t(idx) >= existing_list.cells.size()) { - throw exceptions::invalid_request_exception(sprint("List index %d out of bound, list has size %d", - idx, existing_list.cells.size())); - } - - bytes_view eidx = existing_list.cells[idx].first; - list_type_impl::mutation mut; - mut.cells.reserve(1); - if (!value) { - mut.cells.emplace_back(to_bytes(eidx), params.make_dead_cell()); - } else { - if (value->size() > std::numeric_limits::max()) { - throw exceptions::invalid_request_exception( - sprint("List value is too long. List values are limited to %d bytes but %d bytes value provided", - std::numeric_limits::max(), value->size())); - } - mut.cells.emplace_back(to_bytes(eidx), params.make_cell(*value)); - } - auto smut = ltype->serialize_mutation_form(mut); - m.set_cell(prefix, column, atomic_cell_or_collection::from_collection_mutation(std::move(smut))); - } + virtual bool requires_read() override; + virtual void collect_marker_specification(shared_ptr bound_names); + virtual void execute(mutation& m, const exploded_clustering_prefix& prefix, const update_parameters& params) override; }; #if 0 @@ -415,39 +221,7 @@ public: const exploded_clustering_prefix& prefix, const column_definition& column, const update_parameters& params, - tombstone ts = {}) { - auto&& value = t->bind(params._options); - auto&& list_value = dynamic_pointer_cast(value); - auto&& ltype = dynamic_pointer_cast(column.type); - if (column.type->is_multi_cell()) { - // If we append null, do nothing. Note that for Setter, we've - // already removed the previous value so we're good here too - if (!value) { - return; - } - - auto&& to_add = list_value->_elements; - collection_type_impl::mutation appended; - appended.tomb = ts; - appended.cells.reserve(to_add.size()); - for (auto&& e : to_add) { - auto uuid1 = utils::UUID_gen::get_time_UUID_bytes(); - auto uuid = bytes(reinterpret_cast(uuid1.data()), uuid1.size()); - appended.cells.emplace_back(std::move(uuid), params.make_cell(e)); - } - m.set_cell(prefix, column, ltype->serialize_mutation_form(appended)); - } else { - // for frozen lists, we're overwriting the whole cell value - if (!value) { - m.set_cell(prefix, column, params.make_dead_cell()); - } else { - auto&& to_add = list_value->_elements; - auto&& newv = collection_mutation::one{list_type_impl::pack(to_add.begin(), to_add.end(), to_add.size(), - serialization_format::internal())}; - m.set_cell(prefix, column, atomic_cell_or_collection::from_collection_mutation(std::move(newv))); - } - } - } + tombstone ts = {}); #if 0 public static class Prepender extends Operation diff --git a/cql3/maps.hh b/cql3/maps.hh index 7b9e2542e6..b0cb80ce7d 100644 --- a/cql3/maps.hh +++ b/cql3/maps.hh @@ -288,7 +288,7 @@ public: class setter : public operation { public: - setter(column_definition& column, shared_ptr t) + setter(const column_definition& column, shared_ptr t) : operation(column, std::move(t)) { } @@ -308,7 +308,7 @@ public: class setter_by_key : public operation { const shared_ptr _k; public: - setter_by_key(column_definition& column, shared_ptr k, shared_ptr t) + setter_by_key(const column_definition& column, shared_ptr k, shared_ptr t) : operation(column, std::move(t)), _k(std::move(k)) { } @@ -342,7 +342,7 @@ public: class putter : public operation { public: - putter(column_definition& column, shared_ptr t) + putter(const column_definition& column, shared_ptr t) : operation(column, std::move(t)) { } diff --git a/cql3/operation.cc b/cql3/operation.cc index dbe06fe440..8f3c6846ef 100644 --- a/cql3/operation.cc +++ b/cql3/operation.cc @@ -30,7 +30,7 @@ namespace cql3 { shared_ptr -operation::set_element::prepare(const sstring& keyspace, column_definition& receiver) { +operation::set_element::prepare(const sstring& keyspace, const column_definition& receiver) { using exceptions::invalid_request_exception; auto rtype = dynamic_pointer_cast(receiver.type); if (!rtype) { @@ -61,7 +61,7 @@ operation::set_element::is_compatible_with(shared_ptr other) { } shared_ptr -operation::addition::prepare(const sstring& keyspace, column_definition& receiver) { +operation::addition::prepare(const sstring& keyspace, const column_definition& receiver) { auto v = _value->prepare(keyspace, receiver.column_specification); auto ctype = dynamic_pointer_cast(receiver.type); @@ -97,7 +97,7 @@ operation::addition::is_compatible_with(shared_ptr other) { } shared_ptr -operation::subtraction::prepare(const sstring& keyspace, column_definition& receiver) { +operation::subtraction::prepare(const sstring& keyspace, const column_definition& receiver) { warn(unimplemented::cause::COLLECTIONS); throw exceptions::invalid_request_exception("unimplemented, go away"); // FIXME: @@ -135,7 +135,7 @@ operation::subtraction::is_compatible_with(shared_ptr other) { } shared_ptr -operation::prepend::prepare(const sstring& keyspace, column_definition& receiver) { +operation::prepend::prepare(const sstring& keyspace, const column_definition& receiver) { warn(unimplemented::cause::COLLECTIONS); throw exceptions::invalid_request_exception("unimplemented, go away"); // FIXME: @@ -158,7 +158,7 @@ operation::prepend::is_compatible_with(shared_ptr other) { ::shared_ptr -operation::set_value::prepare(const sstring& keyspace, column_definition& receiver) { +operation::set_value::prepare(const sstring& keyspace, const column_definition& receiver) { auto v = _value->prepare(keyspace, receiver.column_specification); if (receiver.type->is_counter()) { diff --git a/cql3/operation.hh b/cql3/operation.hh index 1fadb020b4..49d36668e7 100644 --- a/cql3/operation.hh +++ b/cql3/operation.hh @@ -75,7 +75,7 @@ protected: const ::shared_ptr _t; public: - operation(column_definition& column_, ::shared_ptr t) + operation(const column_definition& column_, ::shared_ptr t) : column{column_} , _t{t} { } @@ -136,7 +136,7 @@ public: * be a true column. * @return the prepared update operation. */ - virtual ::shared_ptr prepare(const sstring& keyspace, column_definition& receiver) = 0; + virtual ::shared_ptr prepare(const sstring& keyspace, const column_definition& receiver) = 0; /** * @return whether this operation can be applied alongside the {@code @@ -172,7 +172,7 @@ public: * @param receiver the "column" this operation applies to. * @return the prepared delete operation. */ - virtual ::shared_ptr prepare(const sstring& keyspace, column_definition& receiver) = 0; + virtual ::shared_ptr prepare(const sstring& keyspace, const column_definition& receiver) = 0; }; class set_value; @@ -185,7 +185,7 @@ public: : _selector(std::move(selector)), _value(std::move(value)) { } - virtual shared_ptr prepare(const sstring& keyspace, column_definition& receiver); + virtual shared_ptr prepare(const sstring& keyspace, const column_definition& receiver); #if 0 protected String toString(ColumnSpecification column) { @@ -203,7 +203,7 @@ public: : _value(value) { } - virtual shared_ptr prepare(const sstring& keyspace, column_definition& receiver) override; + virtual shared_ptr prepare(const sstring& keyspace, const column_definition& receiver) override; #if 0 protected String toString(ColumnSpecification column) @@ -222,7 +222,7 @@ public: : _value(value) { } - virtual shared_ptr prepare(const sstring& keyspace, column_definition& receiver) override; + virtual shared_ptr prepare(const sstring& keyspace, const column_definition& receiver) override; #if 0 protected String toString(ColumnSpecification column) @@ -241,7 +241,7 @@ public: : _value(std::move(value)) { } - virtual shared_ptr prepare(const sstring& keyspace, column_definition& receiver) override; + virtual shared_ptr prepare(const sstring& keyspace, const column_definition& receiver) override; #if 0 protected String toString(ColumnSpecification column) diff --git a/cql3/operation_impl.hh b/cql3/operation_impl.hh index 5fafa16e89..8852d87281 100644 --- a/cql3/operation_impl.hh +++ b/cql3/operation_impl.hh @@ -38,7 +38,7 @@ private: public: set_value(::shared_ptr value) : _value(std::move(value)) {} - virtual ::shared_ptr prepare(const sstring& keyspace, column_definition& receiver) override; + virtual ::shared_ptr prepare(const sstring& keyspace, const column_definition& receiver) override; #if 0 protected String toString(ColumnSpecification column) diff --git a/cql3/relation.cc b/cql3/relation.cc index eeba699eb8..4b9dd48ca6 100644 --- a/cql3/relation.cc +++ b/cql3/relation.cc @@ -27,7 +27,7 @@ namespace cql3 { -column_definition& +const column_definition& relation::to_column_definition(schema_ptr schema, ::shared_ptr entity) { auto id = entity->prepare_column_identifier(schema); auto def = get_column_definition(schema, *id); diff --git a/cql3/relation.hh b/cql3/relation.hh index 650f1fdd9f..d21af415cf 100644 --- a/cql3/relation.hh +++ b/cql3/relation.hh @@ -252,7 +252,7 @@ protected: * @return the column definition corresponding to the specified entity * @throws InvalidRequestException if the entity cannot be recognized */ - virtual column_definition& to_column_definition(schema_ptr schema, ::shared_ptr entity) final; + virtual const column_definition& to_column_definition(schema_ptr schema, ::shared_ptr entity) final; }; using relation_ptr = ::shared_ptr; diff --git a/cql3/restrictions/single_column_restriction.hh b/cql3/restrictions/single_column_restriction.hh index 1692dab876..29b31d5db3 100644 --- a/cql3/restrictions/single_column_restriction.hh +++ b/cql3/restrictions/single_column_restriction.hh @@ -180,7 +180,7 @@ class single_column_restriction::EQ final : public single_column_restriction { private: ::shared_ptr _value; public: - EQ(column_definition& column_def, ::shared_ptr value) + EQ(const column_definition& column_def, ::shared_ptr value) : single_column_restriction(column_def) , _value(std::move(value)) { } @@ -310,7 +310,7 @@ private: std::vector<::shared_ptr> _entry_keys; std::vector<::shared_ptr> _entry_values; public: - contains(column_definition& column_def, ::shared_ptr t, bool is_key) + contains(const column_definition& column_def, ::shared_ptr t, bool is_key) : single_column_restriction(column_def) { if (is_key) { _keys.emplace_back(std::move(t)); @@ -319,7 +319,7 @@ public: } } - contains(column_definition& column_def, ::shared_ptr map_key, ::shared_ptr map_value) + contains(const column_definition& column_def, ::shared_ptr map_key, ::shared_ptr map_value) : single_column_restriction(column_def) { _entry_keys.emplace_back(std::move(map_key)); _entry_values.emplace_back(std::move(map_value)); diff --git a/cql3/sets.cc b/cql3/sets.cc new file mode 100644 index 0000000000..bc99a309aa --- /dev/null +++ b/cql3/sets.cc @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2015 Cloudius Systems, Ltd. + */ + +#include "sets.hh" + +namespace cql3 { + +shared_ptr +sets::value_spec_of(shared_ptr column) { + return make_shared(column->ks_name, column->cf_name, + ::make_shared(sprint("value(%s)", *column->name), true), + dynamic_pointer_cast(column->type)->get_elements_type()); +} + +shared_ptr +sets::literal::prepare(const sstring& keyspace, shared_ptr receiver) { + validate_assignable_to(keyspace, receiver); + + // We've parsed empty maps as a set literal to break the ambiguity so + // handle that case now + if (_elements.empty() && dynamic_pointer_cast(receiver->type)) { + // use empty_type for comparator, set is empty anyway. + std::map m(empty_type->as_less_comparator()); + return ::make_shared(std::move(m)); + } + + auto value_spec = value_spec_of(receiver); + std::vector> values; + values.reserve(_elements.size()); + bool all_terminal = true; + for (shared_ptr rt : _elements) + { + auto t = rt->prepare(keyspace, value_spec); + + if (t->contains_bind_marker()) { + throw exceptions::invalid_request_exception(sprint("Invalid set literal for %s: bind variables are not supported inside collection literals", *receiver->name)); + } + + if (dynamic_pointer_cast(t)) { + all_terminal = false; + } + + values.push_back(std::move(t)); + } + auto compare = dynamic_pointer_cast(receiver->type)->get_elements_type()->as_less_comparator(); + + auto value = ::make_shared(compare, std::move(values)); + if (all_terminal) { + return value->bind(query_options::DEFAULT); + } else { + return value; + } +} + +void +sets::literal::validate_assignable_to(const sstring& keyspace, shared_ptr receiver) { + if (!dynamic_pointer_cast(receiver->type)) { + // We've parsed empty maps as a set literal to break the ambiguity so + // handle that case now + if (dynamic_pointer_cast(receiver->type) && _elements.empty()) { + return; + } + + throw exceptions::invalid_request_exception(sprint("Invalid set literal for %s of type %s", *receiver->name, *receiver->type->as_cql3_type())); + } + + auto&& value_spec = value_spec_of(receiver); + for (shared_ptr rt : _elements) { + if (!is_assignable(rt->test_assignment(keyspace, value_spec))) { + throw exceptions::invalid_request_exception(sprint("Invalid set literal for %s: value %s is not of type %s", *receiver->name, *rt, *value_spec->type->as_cql3_type())); + } + } +} + +assignment_testable::test_result +sets::literal::test_assignment(const sstring& keyspace, shared_ptr receiver) { + if (!dynamic_pointer_cast(receiver->type)) { + // We've parsed empty maps as a set literal to break the ambiguity so handle that case now + if (dynamic_pointer_cast(receiver->type) && _elements.empty()) { + return assignment_testable::test_result::WEAKLY_ASSIGNABLE; + } + + return assignment_testable::test_result::NOT_ASSIGNABLE; + } + + // If there is no elements, we can't say it's an exact match (an empty set if fundamentally polymorphic). + if (_elements.empty()) { + return assignment_testable::test_result::WEAKLY_ASSIGNABLE; + } + + auto&& value_spec = value_spec_of(receiver); + // FIXME: make assignment_testable::test_all() accept ranges + std::vector> to_test(_elements.begin(), _elements.end()); + return assignment_testable::test_all(keyspace, value_spec, to_test); +} + +sstring +sets::literal::to_string() const { + return "{" + join(", ", _elements) + "}"; +} + +sets::value +sets::value::from_serialized(bytes_view v, set_type type, serialization_format sf) { + try { + // Collections have this small hack that validate cannot be called on a serialized object, + // but compose does the validation (so we're fine). + // FIXME: deserializeForNativeProtocol?! + auto s = boost::any_cast(type->deserialize(v, sf)); + std::set elements(type->as_less_comparator()); + for (auto&& element : s) { + elements.insert(elements.end(), type->get_elements_type()->decompose(element)); + } + return value(std::move(elements)); + } catch (marshal_exception& e) { + throw exceptions::invalid_request_exception(e.why()); + } +} + +bytes_opt +sets::value::get(const query_options& options) { + return get_with_protocol_version(options.get_serialization_format()); +} + +bytes +sets::value::get_with_protocol_version(serialization_format sf) { + return collection_type_impl::pack(_elements.begin(), _elements.end(), + _elements.size(), sf); +} + +bool +sets::value::equals(set_type st, const value& v) { + if (_elements.size() != v._elements.size()) { + return false; + } + auto&& elements_type = st->get_elements_type(); + return std::equal(_elements.begin(), _elements.end(), + v._elements.begin(), + [elements_type] (bytes_view v1, bytes_view v2) { + return elements_type->equal(v1, v2); + }); +} + +sstring +sets::value::to_string() const { + sstring result = "{"; + bool first = true; + for (auto&& e : _elements) { + if (!first) { + result += ", "; + } + first = true; + result += to_hex(e); + } + result += "}"; + return result; +} + +bool +sets::delayed_value::contains_bind_marker() const { + // False since we don't support them in collection + return false; +} + +void +sets::delayed_value::collect_marker_specification(shared_ptr bound_names) { +} + +shared_ptr +sets::delayed_value::bind(const query_options& options) { + std::set buffers(_comparator); + for (auto&& t : _elements) { + bytes_opt b = t->bind_and_get(options); + + if (!b) { + throw exceptions::invalid_request_exception("null is not supported inside collections"); + } + + // We don't support value > 64K because the serialization format encode the length as an unsigned short. + if (b->size() > std::numeric_limits::max()) { + throw exceptions::invalid_request_exception(sprint("Set value is too long. Set values are limited to %d bytes but %d bytes value provided", + std::numeric_limits::max(), + b->size())); + } + + buffers.insert(buffers.end(), std::move(*b)); + } + return ::make_shared(std::move(buffers)); +} + + +::shared_ptr +sets::marker::bind(const query_options& options) { + throw std::runtime_error(""); +} +#if 0 +public Value bind(QueryOptions options) throws InvalidRequestException +{ + ByteBuffer value = options.getValues().get(bindIndex); + return value == null ? null : Value.fromSerialized(value, (SetType)receiver.type, options.getProtocolVersion()); +} +#endif + +void +sets::setter::execute(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params) { + if (column.type->is_multi_cell()) { + unimplemented::warn(unimplemented::cause::COLLECTION_RANGE_TOMBSTONES); + // FIXME: implement + // delete + add +#if 0 + CellName name = cf.getComparator().create(prefix, column); + cf.addAtom(params.makeTombstoneForOverwrite(name.slice())); +#endif + } + adder::do_add(m, row_key, params, _t, column); +} + +void +sets::adder::execute(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params) { + assert(column.type->is_multi_cell()); // "Attempted to add items to a frozen set"; + do_add(m, row_key, params, _t, column); +} + +void +sets::adder::do_add(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params, + shared_ptr t, const column_definition& column) { + auto&& value = t->bind(params._options); + auto set_value = dynamic_pointer_cast(std::move(value)); + auto set_type = dynamic_pointer_cast(column.type); + if (column.type->is_multi_cell()) { + if (!set_value || set_value->_elements.empty()) { + return; + } + + // FIXME: mutation_view? not compatible with params.make_cell(). + collection_type_impl::mutation mut; + for (auto&& e : set_value->_elements) { + mut.cells.emplace_back(e, params.make_cell({})); + } + auto smut = set_type->serialize_mutation_form(mut); + + m.set_cell(row_key, column, std::move(smut)); + } else { + // for frozen sets, we're overwriting the whole cell + auto v = set_type->serialize_partially_deserialized_form( + {set_value->_elements.begin(), set_value->_elements.end()}, + serialization_format::internal()); + if (set_value->_elements.empty()) { + m.set_cell(row_key, column, params.make_dead_cell()); + } else { + m.set_cell(row_key, column, params.make_cell(std::move(v))); + } + } +} + +} diff --git a/cql3/sets.hh b/cql3/sets.hh index 67a401a079..d0560ed0f5 100644 --- a/cql3/sets.hh +++ b/cql3/sets.hh @@ -63,11 +63,7 @@ import org.apache.cassandra.utils.FBUtilities; class sets { sets() = delete; public: - static shared_ptr value_spec_of(shared_ptr column) { - return make_shared(column->ks_name, column->cf_name, - ::make_shared(sprint("value(%s)", *column->name), true), - dynamic_pointer_cast(column->type)->get_elements_type()); - } + static shared_ptr value_spec_of(shared_ptr column); class literal : public term::raw { std::vector> _elements; @@ -75,90 +71,11 @@ public: explicit literal(std::vector> elements) : _elements(std::move(elements)) { } - - shared_ptr prepare(const sstring& keyspace, shared_ptr receiver) { - validate_assignable_to(keyspace, receiver); - - // We've parsed empty maps as a set literal to break the ambiguity so - // handle that case now - if (_elements.empty() && dynamic_pointer_cast(receiver->type)) { - // use empty_type for comparator, set is empty anyway. - std::map m(empty_type->as_less_comparator()); - return ::make_shared(std::move(m)); - } - - auto value_spec = value_spec_of(receiver); - std::vector> values; - values.reserve(_elements.size()); - bool all_terminal = true; - for (shared_ptr rt : _elements) - { - auto t = rt->prepare(keyspace, value_spec); - - if (t->contains_bind_marker()) { - throw exceptions::invalid_request_exception(sprint("Invalid set literal for %s: bind variables are not supported inside collection literals", *receiver->name)); - } - - if (dynamic_pointer_cast(t)) { - all_terminal = false; - } - - values.push_back(std::move(t)); - } - auto compare = dynamic_pointer_cast(receiver->type)->get_elements_type()->as_less_comparator(); - - auto value = ::make_shared(compare, std::move(values)); - if (all_terminal) { - return value->bind(query_options::DEFAULT); - } else { - return value; - } - } - - void validate_assignable_to(const sstring& keyspace, shared_ptr receiver) { - if (!dynamic_pointer_cast(receiver->type)) { - // We've parsed empty maps as a set literal to break the ambiguity so - // handle that case now - if (dynamic_pointer_cast(receiver->type) && _elements.empty()) { - return; - } - - throw exceptions::invalid_request_exception(sprint("Invalid set literal for %s of type %s", *receiver->name, *receiver->type->as_cql3_type())); - } - - auto&& value_spec = value_spec_of(receiver); - for (shared_ptr rt : _elements) { - if (!is_assignable(rt->test_assignment(keyspace, value_spec))) { - throw exceptions::invalid_request_exception(sprint("Invalid set literal for %s: value %s is not of type %s", *receiver->name, *rt, *value_spec->type->as_cql3_type())); - } - } - } - + shared_ptr prepare(const sstring& keyspace, shared_ptr receiver); + void validate_assignable_to(const sstring& keyspace, shared_ptr receiver); assignment_testable::test_result - test_assignment(const sstring& keyspace, shared_ptr receiver) { - if (!dynamic_pointer_cast(receiver->type)) { - // We've parsed empty maps as a set literal to break the ambiguity so handle that case now - if (dynamic_pointer_cast(receiver->type) && _elements.empty()) { - return assignment_testable::test_result::WEAKLY_ASSIGNABLE; - } - - return assignment_testable::test_result::NOT_ASSIGNABLE; - } - - // If there is no elements, we can't say it's an exact match (an empty set if fundamentally polymorphic). - if (_elements.empty()) { - return assignment_testable::test_result::WEAKLY_ASSIGNABLE; - } - - auto&& value_spec = value_spec_of(receiver); - // FIXME: make assignment_testable::test_all() accept ranges - std::vector> to_test(_elements.begin(), _elements.end()); - return assignment_testable::test_all(keyspace, value_spec, to_test); - } - - virtual sstring to_string() const override { - return "{" + join(", ", _elements) + "}"; - } + test_assignment(const sstring& keyspace, shared_ptr receiver); + virtual sstring to_string() const override; }; class value : public terminal, collection_terminal { @@ -168,57 +85,11 @@ public: value(std::set elements) : _elements(std::move(elements)) { } - - static value from_serialized(bytes_view v, set_type type, serialization_format sf) { - try { - // Collections have this small hack that validate cannot be called on a serialized object, - // but compose does the validation (so we're fine). - // FIXME: deserializeForNativeProtocol?! - auto s = boost::any_cast(type->deserialize(v, sf)); - std::set elements(type->as_less_comparator()); - for (auto&& element : s) { - elements.insert(elements.end(), type->get_elements_type()->decompose(element)); - } - return value(std::move(elements)); - } catch (marshal_exception& e) { - throw exceptions::invalid_request_exception(e.why()); - } - } - - virtual bytes_opt get(const query_options& options) override { - return get_with_protocol_version(options.get_serialization_format()); - } - - virtual bytes get_with_protocol_version(serialization_format sf) override { - return collection_type_impl::pack(_elements.begin(), _elements.end(), - _elements.size(), sf); - } - - bool equals(set_type st, const value& v) { - if (_elements.size() != v._elements.size()) { - return false; - } - auto&& elements_type = st->get_elements_type(); - return std::equal(_elements.begin(), _elements.end(), - v._elements.begin(), - [elements_type] (bytes_view v1, bytes_view v2) { - return elements_type->equal(v1, v2); - }); - } - - virtual sstring to_string() const override { - sstring result = "{"; - bool first = true; - for (auto&& e : _elements) { - if (!first) { - result += ", "; - } - first = true; - result += to_hex(e); - } - result += "}"; - return result; - } + static value from_serialized(bytes_view v, set_type type, serialization_format sf); + virtual bytes_opt get(const query_options& options) override; + virtual bytes get_with_protocol_version(serialization_format sf) override; + bool equals(set_type st, const value& v); + virtual sstring to_string() const override; }; // See Lists.DelayedValue @@ -229,35 +100,9 @@ public: delayed_value(serialized_compare comparator, std::vector> elements) : _comparator(std::move(comparator)), _elements(std::move(elements)) { } - - virtual bool contains_bind_marker() const override { - // False since we don't support them in collection - return false; - } - - virtual void collect_marker_specification(shared_ptr bound_names) override { - } - - virtual shared_ptr bind(const query_options& options) { - std::set buffers(_comparator); - for (auto&& t : _elements) { - bytes_opt b = t->bind_and_get(options); - - if (!b) { - throw exceptions::invalid_request_exception("null is not supported inside collections"); - } - - // We don't support value > 64K because the serialization format encode the length as an unsigned short. - if (b->size() > std::numeric_limits::max()) { - throw exceptions::invalid_request_exception(sprint("Set value is too long. Set values are limited to %d bytes but %d bytes value provided", - std::numeric_limits::max(), - b->size())); - } - - buffers.insert(buffers.end(), std::move(*b)); - } - return ::make_shared(std::move(buffers)); - } + virtual bool contains_bind_marker() const override; + virtual void collect_marker_specification(shared_ptr bound_names) override; + virtual shared_ptr bind(const query_options& options); }; class marker : public abstract_marker { @@ -273,9 +118,7 @@ public: } #endif - virtual ::shared_ptr bind(const query_options& options) override { - throw std::runtime_error(""); - } + virtual ::shared_ptr bind(const query_options& options) override; #if 0 public Value bind(QueryOptions options) throws InvalidRequestException { @@ -287,65 +130,20 @@ public: class setter : public operation { public: - setter(column_definition& column, shared_ptr t) + setter(const column_definition& column, shared_ptr t) : operation(column, std::move(t)) { } - - virtual void execute(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params) override { - if (column.type->is_multi_cell()) { - unimplemented::warn(unimplemented::cause::COLLECTION_RANGE_TOMBSTONES); - // FIXME: implement - // delete + add -#if 0 - CellName name = cf.getComparator().create(prefix, column); - cf.addAtom(params.makeTombstoneForOverwrite(name.slice())); -#endif - } - adder::do_add(m, row_key, params, _t, column); - } + virtual void execute(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params) override; }; class adder : public operation { public: - adder(column_definition& column, shared_ptr t) + adder(const column_definition& column, shared_ptr t) : operation(column, std::move(t)) { } - - virtual void execute(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params) override { - assert(column.type->is_multi_cell()); // "Attempted to add items to a frozen set"; - do_add(m, row_key, params, _t, column); - } - + virtual void execute(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params) override; static void do_add(mutation& m, const exploded_clustering_prefix& row_key, const update_parameters& params, - shared_ptr t, const column_definition& column) { - auto&& value = t->bind(params._options); - auto set_value = dynamic_pointer_cast(std::move(value)); - auto set_type = dynamic_pointer_cast(column.type); - if (column.type->is_multi_cell()) { - if (!set_value || set_value->_elements.empty()) { - return; - } - - // FIXME: mutation_view? not compatible with params.make_cell(). - collection_type_impl::mutation mut; - for (auto&& e : set_value->_elements) { - mut.cells.emplace_back(e, params.make_cell({})); - } - auto smut = set_type->serialize_mutation_form(mut); - - m.set_cell(row_key, column, std::move(smut)); - } else { - // for frozen sets, we're overwriting the whole cell - auto v = set_type->serialize_partially_deserialized_form( - {set_value->_elements.begin(), set_value->_elements.end()}, - serialization_format::internal()); - if (set_value->_elements.empty()) { - m.set_cell(row_key, column, params.make_dead_cell()); - } else { - m.set_cell(row_key, column, params.make_cell(std::move(v))); - } - } - } + shared_ptr t, const column_definition& column); }; #if 0 diff --git a/cql3/single_column_relation.cc b/cql3/single_column_relation.cc index f2fc887a0a..74e8186b29 100644 --- a/cql3/single_column_relation.cc +++ b/cql3/single_column_relation.cc @@ -46,7 +46,7 @@ single_column_relation::to_term(std::vector<::shared_ptr> ::shared_ptr single_column_relation::new_EQ_restriction(schema_ptr schema, ::shared_ptr bound_names) { - column_definition& column_def = to_column_definition(schema, _entity); + const column_definition& column_def = to_column_definition(schema, _entity); if (!_map_key) { auto term = to_term(to_receivers(schema, column_def), _value, schema->ks_name, bound_names); return ::make_shared(column_def, std::move(term)); @@ -61,7 +61,7 @@ single_column_relation::new_EQ_restriction(schema_ptr schema, ::shared_ptr> -single_column_relation::to_receivers(schema_ptr schema, column_definition& column_def) +single_column_relation::to_receivers(schema_ptr schema, const column_definition& column_def) { auto receiver = column_def.column_specification; diff --git a/cql3/single_column_relation.hh b/cql3/single_column_relation.hh index c46bf5c9d0..8b9fc5cfe5 100644 --- a/cql3/single_column_relation.hh +++ b/cql3/single_column_relation.hh @@ -175,7 +175,7 @@ private: * @return the receivers for the specified relation. * @throws exceptions::invalid_request_exception if the relation is invalid */ - std::vector<::shared_ptr> to_receivers(schema_ptr schema, column_definition& column_def); + std::vector<::shared_ptr> to_receivers(schema_ptr schema, const column_definition& column_def); #if 0 private ColumnSpecification makeCollectionReceiver(ColumnSpecification receiver, bool forKey) diff --git a/cql3/statements/CreateTableStatement.java b/cql3/statements/CreateTableStatement.java deleted file mode 100644 index c8c24743e4..0000000000 --- a/cql3/statements/CreateTableStatement.java +++ /dev/null @@ -1,442 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.cassandra.cql3.statements; - -import java.nio.ByteBuffer; -import java.util.*; - -import org.apache.cassandra.exceptions.*; -import org.apache.commons.lang3.StringUtils; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; - -import org.apache.cassandra.auth.Permission; -import org.apache.cassandra.config.ColumnDefinition; -import org.apache.cassandra.config.CFMetaData; -import org.apache.cassandra.config.Schema; -import org.apache.cassandra.cql3.*; -import org.apache.cassandra.db.composites.*; -import org.apache.cassandra.db.ColumnFamilyType; -import org.apache.cassandra.db.marshal.*; -import org.apache.cassandra.exceptions.AlreadyExistsException; -import org.apache.cassandra.io.compress.CompressionParameters; -import org.apache.cassandra.service.ClientState; -import org.apache.cassandra.service.MigrationManager; -import org.apache.cassandra.transport.Event; -import org.apache.cassandra.utils.ByteBufferUtil; - -/** A CREATE TABLE parsed from a CQL query statement. */ -public class CreateTableStatement extends SchemaAlteringStatement -{ - public CellNameType comparator; - private AbstractType defaultValidator; - private AbstractType keyValidator; - - private final List keyAliases = new ArrayList(); - private final List columnAliases = new ArrayList(); - private ByteBuffer valueAlias; - - private boolean isDense; - - private final Map columns = new HashMap(); - private final Set staticColumns; - private final CFPropDefs properties; - private final boolean ifNotExists; - - public CreateTableStatement(CFName name, CFPropDefs properties, boolean ifNotExists, Set staticColumns) - { - super(name); - this.properties = properties; - this.ifNotExists = ifNotExists; - this.staticColumns = staticColumns; - - try - { - if (!this.properties.hasProperty(CFPropDefs.KW_COMPRESSION) && CFMetaData.DEFAULT_COMPRESSOR != null) - this.properties.addProperty(CFPropDefs.KW_COMPRESSION, - new HashMap() - {{ - put(CompressionParameters.SSTABLE_COMPRESSION, CFMetaData.DEFAULT_COMPRESSOR); - }}); - } - catch (SyntaxException e) - { - throw new AssertionError(e); - } - } - - public void checkAccess(ClientState state) throws UnauthorizedException, InvalidRequestException - { - state.hasKeyspaceAccess(keyspace(), Permission.CREATE); - } - - public void validate(ClientState state) - { - // validated in announceMigration() - } - - // Column definitions - private List getColumns(CFMetaData cfm) - { - List columnDefs = new ArrayList<>(columns.size()); - Integer componentIndex = comparator.isCompound() ? comparator.clusteringPrefixSize() : null; - for (Map.Entry col : columns.entrySet()) - { - ColumnIdentifier id = col.getKey(); - columnDefs.add(staticColumns.contains(id) - ? ColumnDefinition.staticDef(cfm, col.getKey().bytes, col.getValue(), componentIndex) - : ColumnDefinition.regularDef(cfm, col.getKey().bytes, col.getValue(), componentIndex)); - } - - return columnDefs; - } - - public boolean announceMigration(boolean isLocalOnly) throws RequestValidationException - { - try - { - MigrationManager.announceNewColumnFamily(getCFMetaData(), isLocalOnly); - return true; - } - catch (AlreadyExistsException e) - { - if (ifNotExists) - return false; - throw e; - } - } - - public Event.SchemaChange changeEvent() - { - return new Event.SchemaChange(Event.SchemaChange.Change.CREATED, Event.SchemaChange.Target.TABLE, keyspace(), columnFamily()); - } - - /** - * Returns a CFMetaData instance based on the parameters parsed from this - * CREATE statement, or defaults where applicable. - * - * @return a CFMetaData instance corresponding to the values parsed from this statement - * @throws InvalidRequestException on failure to validate parsed parameters - */ - public CFMetaData getCFMetaData() throws RequestValidationException - { - CFMetaData newCFMD; - newCFMD = new CFMetaData(keyspace(), - columnFamily(), - ColumnFamilyType.Standard, - comparator); - applyPropertiesTo(newCFMD); - return newCFMD; - } - - public void applyPropertiesTo(CFMetaData cfmd) throws RequestValidationException - { - cfmd.defaultValidator(defaultValidator) - .keyValidator(keyValidator) - .addAllColumnDefinitions(getColumns(cfmd)) - .isDense(isDense); - - addColumnMetadataFromAliases(cfmd, keyAliases, keyValidator, ColumnDefinition.Kind.PARTITION_KEY); - addColumnMetadataFromAliases(cfmd, columnAliases, comparator.asAbstractType(), ColumnDefinition.Kind.CLUSTERING_COLUMN); - if (valueAlias != null) - addColumnMetadataFromAliases(cfmd, Collections.singletonList(valueAlias), defaultValidator, ColumnDefinition.Kind.COMPACT_VALUE); - - properties.applyToCFMetadata(cfmd); - } - - private void addColumnMetadataFromAliases(CFMetaData cfm, List aliases, AbstractType comparator, ColumnDefinition.Kind kind) - { - if (comparator instanceof CompositeType) - { - CompositeType ct = (CompositeType)comparator; - for (int i = 0; i < aliases.size(); ++i) - if (aliases.get(i) != null) - cfm.addOrReplaceColumnDefinition(new ColumnDefinition(cfm, aliases.get(i), ct.types.get(i), i, kind)); - } - else - { - assert aliases.size() <= 1; - if (!aliases.isEmpty() && aliases.get(0) != null) - cfm.addOrReplaceColumnDefinition(new ColumnDefinition(cfm, aliases.get(0), comparator, null, kind)); - } - } - - - public static class RawStatement extends CFStatement - { - private final Map definitions = new HashMap<>(); - public final CFPropDefs properties = new CFPropDefs(); - - private final List> keyAliases = new ArrayList>(); - private final List columnAliases = new ArrayList(); - private final Map definedOrdering = new LinkedHashMap(); // Insertion ordering is important - private final Set staticColumns = new HashSet(); - - private boolean useCompactStorage; - private final Multiset definedNames = HashMultiset.create(1); - - private final boolean ifNotExists; - - public RawStatement(CFName name, boolean ifNotExists) - { - super(name); - this.ifNotExists = ifNotExists; - } - - /** - * Transform this raw statement into a CreateTableStatement. - */ - public ParsedStatement.Prepared prepare() throws RequestValidationException - { - // Column family name - if (!columnFamily().matches("\\w+")) - throw new InvalidRequestException(String.format("\"%s\" is not a valid table name (must be alphanumeric character only: [0-9A-Za-z]+)", columnFamily())); - if (columnFamily().length() > Schema.NAME_LENGTH) - throw new InvalidRequestException(String.format("Table names shouldn't be more than %s characters long (got \"%s\")", Schema.NAME_LENGTH, columnFamily())); - - for (Multiset.Entry entry : definedNames.entrySet()) - if (entry.getCount() > 1) - throw new InvalidRequestException(String.format("Multiple definition of identifier %s", entry.getElement())); - - properties.validate(); - - CreateTableStatement stmt = new CreateTableStatement(cfName, properties, ifNotExists, staticColumns); - - Map definedMultiCellCollections = null; - for (Map.Entry entry : definitions.entrySet()) - { - ColumnIdentifier id = entry.getKey(); - CQL3Type pt = entry.getValue().prepare(keyspace()); - if (pt.isCollection() && ((CollectionType) pt.getType()).isMultiCell()) - { - if (definedMultiCellCollections == null) - definedMultiCellCollections = new HashMap<>(); - definedMultiCellCollections.put(id.bytes, (CollectionType) pt.getType()); - } - stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below - } - - if (keyAliases.isEmpty()) - throw new InvalidRequestException("No PRIMARY KEY specifed (exactly one required)"); - else if (keyAliases.size() > 1) - throw new InvalidRequestException("Multiple PRIMARY KEYs specifed (exactly one required)"); - - List kAliases = keyAliases.get(0); - - List> keyTypes = new ArrayList>(kAliases.size()); - for (ColumnIdentifier alias : kAliases) - { - stmt.keyAliases.add(alias.bytes); - AbstractType t = getTypeAndRemove(stmt.columns, alias); - if (t instanceof CounterColumnType) - throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", alias)); - if (staticColumns.contains(alias)) - throw new InvalidRequestException(String.format("Static column %s cannot be part of the PRIMARY KEY", alias)); - keyTypes.add(t); - } - stmt.keyValidator = keyTypes.size() == 1 ? keyTypes.get(0) : CompositeType.getInstance(keyTypes); - - // Dense means that no part of the comparator stores a CQL column name. This means - // COMPACT STORAGE with at least one columnAliases (otherwise it's a thrift "static" CF). - stmt.isDense = useCompactStorage && !columnAliases.isEmpty(); - - // Handle column aliases - if (columnAliases.isEmpty()) - { - if (useCompactStorage) - { - // There should remain some column definition since it is a non-composite "static" CF - if (stmt.columns.isEmpty()) - throw new InvalidRequestException("No definition found that is not part of the PRIMARY KEY"); - - if (definedMultiCellCollections != null) - throw new InvalidRequestException("Non-frozen collection types are not supported with COMPACT STORAGE"); - - stmt.comparator = new SimpleSparseCellNameType(UTF8Type.instance); - } - else - { - stmt.comparator = definedMultiCellCollections == null - ? new CompoundSparseCellNameType(Collections.>emptyList()) - : new CompoundSparseCellNameType.WithCollection(Collections.>emptyList(), ColumnToCollectionType.getInstance(definedMultiCellCollections)); - } - } - else - { - // If we use compact storage and have only one alias, it is a - // standard "dynamic" CF, otherwise it's a composite - if (useCompactStorage && columnAliases.size() == 1) - { - if (definedMultiCellCollections != null) - throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE"); - - ColumnIdentifier alias = columnAliases.get(0); - if (staticColumns.contains(alias)) - throw new InvalidRequestException(String.format("Static column %s cannot be part of the PRIMARY KEY", alias)); - - stmt.columnAliases.add(alias.bytes); - AbstractType at = getTypeAndRemove(stmt.columns, alias); - if (at instanceof CounterColumnType) - throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", stmt.columnAliases.get(0))); - stmt.comparator = new SimpleDenseCellNameType(at); - } - else - { - List> types = new ArrayList>(columnAliases.size() + 1); - for (ColumnIdentifier t : columnAliases) - { - stmt.columnAliases.add(t.bytes); - - AbstractType type = getTypeAndRemove(stmt.columns, t); - if (type instanceof CounterColumnType) - throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", t)); - if (staticColumns.contains(t)) - throw new InvalidRequestException(String.format("Static column %s cannot be part of the PRIMARY KEY", t)); - types.add(type); - } - - if (useCompactStorage) - { - if (definedMultiCellCollections != null) - throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE"); - - stmt.comparator = new CompoundDenseCellNameType(types); - } - else - { - stmt.comparator = definedMultiCellCollections == null - ? new CompoundSparseCellNameType(types) - : new CompoundSparseCellNameType.WithCollection(types, ColumnToCollectionType.getInstance(definedMultiCellCollections)); - } - } - } - - if (!staticColumns.isEmpty()) - { - // Only CQL3 tables can have static columns - if (useCompactStorage) - throw new InvalidRequestException("Static columns are not supported in COMPACT STORAGE tables"); - // Static columns only make sense if we have at least one clustering column. Otherwise everything is static anyway - if (columnAliases.isEmpty()) - throw new InvalidRequestException("Static columns are only useful (and thus allowed) if the table has at least one clustering column"); - } - - if (useCompactStorage && !stmt.columnAliases.isEmpty()) - { - if (stmt.columns.isEmpty()) - { - // The only value we'll insert will be the empty one, so the default validator don't matter - stmt.defaultValidator = BytesType.instance; - // We need to distinguish between - // * I'm upgrading from thrift so the valueAlias is null - // * I've defined my table with only a PK (and the column value will be empty) - // So, we use an empty valueAlias (rather than null) for the second case - stmt.valueAlias = ByteBufferUtil.EMPTY_BYTE_BUFFER; - } - else - { - if (stmt.columns.size() > 1) - throw new InvalidRequestException(String.format("COMPACT STORAGE with composite PRIMARY KEY allows no more than one column not part of the PRIMARY KEY (got: %s)", StringUtils.join(stmt.columns.keySet(), ", "))); - - Map.Entry lastEntry = stmt.columns.entrySet().iterator().next(); - stmt.defaultValidator = lastEntry.getValue(); - stmt.valueAlias = lastEntry.getKey().bytes; - stmt.columns.remove(lastEntry.getKey()); - } - } - else - { - // For compact, we are in the "static" case, so we need at least one column defined. For non-compact however, having - // just the PK is fine since we have CQL3 row marker. - if (useCompactStorage && stmt.columns.isEmpty()) - throw new InvalidRequestException("COMPACT STORAGE with non-composite PRIMARY KEY require one column not part of the PRIMARY KEY, none given"); - - // There is no way to insert/access a column that is not defined for non-compact storage, so - // the actual validator don't matter much (except that we want to recognize counter CF as limitation apply to them). - stmt.defaultValidator = !stmt.columns.isEmpty() && (stmt.columns.values().iterator().next() instanceof CounterColumnType) - ? CounterColumnType.instance - : BytesType.instance; - } - - - // If we give a clustering order, we must explicitly do so for all aliases and in the order of the PK - if (!definedOrdering.isEmpty()) - { - if (definedOrdering.size() > columnAliases.size()) - throw new InvalidRequestException("Only clustering key columns can be defined in CLUSTERING ORDER directive"); - - int i = 0; - for (ColumnIdentifier id : definedOrdering.keySet()) - { - ColumnIdentifier c = columnAliases.get(i); - if (!id.equals(c)) - { - if (definedOrdering.containsKey(c)) - throw new InvalidRequestException(String.format("The order of columns in the CLUSTERING ORDER directive must be the one of the clustering key (%s must appear before %s)", c, id)); - else - throw new InvalidRequestException(String.format("Missing CLUSTERING ORDER for column %s", c)); - } - ++i; - } - } - - return new ParsedStatement.Prepared(stmt); - } - - private AbstractType getTypeAndRemove(Map columns, ColumnIdentifier t) throws InvalidRequestException - { - AbstractType type = columns.get(t); - if (type == null) - throw new InvalidRequestException(String.format("Unknown definition %s referenced in PRIMARY KEY", t)); - if (type.isCollection() && type.isMultiCell()) - throw new InvalidRequestException(String.format("Invalid collection type for PRIMARY KEY component %s", t)); - - columns.remove(t); - Boolean isReversed = definedOrdering.get(t); - return isReversed != null && isReversed ? ReversedType.getInstance(type) : type; - } - - public void addDefinition(ColumnIdentifier def, CQL3Type.Raw type, boolean isStatic) - { - definedNames.add(def); - definitions.put(def, type); - if (isStatic) - staticColumns.add(def); - } - - public void addKeyAliases(List aliases) - { - keyAliases.add(aliases); - } - - public void addColumnAlias(ColumnIdentifier alias) - { - columnAliases.add(alias); - } - - public void setOrdering(ColumnIdentifier alias, boolean reversed) - { - definedOrdering.put(alias, reversed); - } - - public void setCompactStorage() - { - useCompactStorage = true; - } - } -} diff --git a/cql3/statements/cf_prop_defs.cc b/cql3/statements/cf_prop_defs.cc new file mode 100644 index 0000000000..afc580af2b --- /dev/null +++ b/cql3/statements/cf_prop_defs.cc @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2015 Cloudius Systems + * + * Modified by Cloudius Systems + */ + +#include "cql3/statements/cf_prop_defs.hh" + +namespace cql3 { + +namespace statements { + +const sstring cf_prop_defs::KW_COMMENT = "comment"; +const sstring cf_prop_defs::KW_READREPAIRCHANCE = "read_repair_chance"; +const sstring cf_prop_defs::KW_DCLOCALREADREPAIRCHANCE = "dclocal_read_repair_chance"; +const sstring cf_prop_defs::KW_GCGRACESECONDS = "gc_grace_seconds"; +const sstring cf_prop_defs::KW_MINCOMPACTIONTHRESHOLD = "min_threshold"; +const sstring cf_prop_defs::KW_MAXCOMPACTIONTHRESHOLD = "max_threshold"; +const sstring cf_prop_defs::KW_CACHING = "caching"; +const sstring cf_prop_defs::KW_DEFAULT_TIME_TO_LIVE = "default_time_to_live"; +const sstring cf_prop_defs::KW_MIN_INDEX_INTERVAL = "min_index_interval"; +const sstring cf_prop_defs::KW_MAX_INDEX_INTERVAL = "max_index_interval"; +const sstring cf_prop_defs::KW_SPECULATIVE_RETRY = "speculative_retry"; +const sstring cf_prop_defs::KW_BF_FP_CHANCE = "bloom_filter_fp_chance"; +const sstring cf_prop_defs::KW_MEMTABLE_FLUSH_PERIOD = "memtable_flush_period_in_ms"; + +const sstring cf_prop_defs::KW_COMPACTION = "compaction"; +const sstring cf_prop_defs::KW_COMPRESSION = "compression"; + +const sstring cf_prop_defs::COMPACTION_STRATEGY_CLASS_KEY = "class"; + +} + +} diff --git a/cql3/statements/CFPropDefs.java b/cql3/statements/cf_prop_defs.hh similarity index 85% rename from cql3/statements/CFPropDefs.java rename to cql3/statements/cf_prop_defs.hh index d6d3f72911..10b47561e7 100644 --- a/cql3/statements/CFPropDefs.java +++ b/cql3/statements/cf_prop_defs.hh @@ -15,6 +15,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +/* + * Copyright 2015 Cloudius Systems + * + * Modified by Cloudius Systems + */ + +#pragma once + +#include "cql3/statements/property_definitions.hh" + +#include "schema.hh" + +#if 0 package org.apache.cassandra.cql3.statements; import java.util.Collections; @@ -30,28 +44,34 @@ import org.apache.cassandra.db.compaction.AbstractCompactionStrategy; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.io.compress.CompressionParameters; +#endif -public class CFPropDefs extends PropertyDefinitions -{ - public static final String KW_COMMENT = "comment"; - public static final String KW_READREPAIRCHANCE = "read_repair_chance"; - public static final String KW_DCLOCALREADREPAIRCHANCE = "dclocal_read_repair_chance"; - public static final String KW_GCGRACESECONDS = "gc_grace_seconds"; - public static final String KW_MINCOMPACTIONTHRESHOLD = "min_threshold"; - public static final String KW_MAXCOMPACTIONTHRESHOLD = "max_threshold"; - public static final String KW_CACHING = "caching"; - public static final String KW_DEFAULT_TIME_TO_LIVE = "default_time_to_live"; - public static final String KW_MIN_INDEX_INTERVAL = "min_index_interval"; - public static final String KW_MAX_INDEX_INTERVAL = "max_index_interval"; - public static final String KW_SPECULATIVE_RETRY = "speculative_retry"; - public static final String KW_BF_FP_CHANCE = "bloom_filter_fp_chance"; - public static final String KW_MEMTABLE_FLUSH_PERIOD = "memtable_flush_period_in_ms"; +namespace cql3 { - public static final String KW_COMPACTION = "compaction"; - public static final String KW_COMPRESSION = "compression"; +namespace statements { - public static final String COMPACTION_STRATEGY_CLASS_KEY = "class"; +class cf_prop_defs : public property_definitions { +public: + static const sstring KW_COMMENT; + static const sstring KW_READREPAIRCHANCE; + static const sstring KW_DCLOCALREADREPAIRCHANCE; + static const sstring KW_GCGRACESECONDS; + static const sstring KW_MINCOMPACTIONTHRESHOLD; + static const sstring KW_MAXCOMPACTIONTHRESHOLD; + static const sstring KW_CACHING; + static const sstring KW_DEFAULT_TIME_TO_LIVE; + static const sstring KW_MIN_INDEX_INTERVAL; + static const sstring KW_MAX_INDEX_INTERVAL; + static const sstring KW_SPECULATIVE_RETRY; + static const sstring KW_BF_FP_CHANCE; + static const sstring KW_MEMTABLE_FLUSH_PERIOD; + static const sstring KW_COMPACTION; + static const sstring KW_COMPRESSION; + + static const sstring COMPACTION_STRATEGY_CLASS_KEY; + +#if 0 public static final Set keywords = new HashSet<>(); public static final Set obsoleteKeywords = new HashSet<>(); @@ -77,9 +97,11 @@ public class CFPropDefs extends PropertyDefinitions } private Class compactionStrategyClass = null; - - public void validate() throws ConfigurationException, SyntaxException - { +#endif +public: + void validate() const { + // FIXME +#if 0 // Skip validation if the comapction strategy class is already set as it means we've alreayd // prepared (and redoing it would set strategyClass back to null, which we don't want) if (compactionStrategyClass != null) @@ -128,8 +150,10 @@ public class CFPropDefs extends PropertyDefinitions throw new ConfigurationException(KW_MAX_INDEX_INTERVAL + " must be greater than " + KW_MIN_INDEX_INTERVAL); SpeculativeRetry.fromString(getString(KW_SPECULATIVE_RETRY, SpeculativeRetry.RetryType.NONE.name())); +#endif } +#if 0 public Class getCompactionStrategy() { return compactionStrategyClass; @@ -165,12 +189,14 @@ public class CFPropDefs extends PropertyDefinitions } return options; } +#endif - public void applyToCFMetadata(CFMetaData cfm) throws ConfigurationException, SyntaxException - { - if (hasProperty(KW_COMMENT)) - cfm.comment(getString(KW_COMMENT, "")); + void apply_to_schema(schema_ptr s) { + if (has_property(KW_COMMENT)) { + s->set_comment(get_string(KW_COMMENT, "")); + } +#if 0 cfm.readRepairChance(getDouble(KW_READREPAIRCHANCE, cfm.getReadRepairChance())); cfm.dcLocalReadRepairChance(getDouble(KW_DCLOCALREADREPAIRCHANCE, cfm.getDcLocalReadRepairChance())); cfm.gcGraceSeconds(getInt(KW_GCGRACESECONDS, cfm.getGcGraceSeconds())); @@ -199,8 +225,10 @@ public class CFPropDefs extends PropertyDefinitions CachingOptions cachingOptions = getCachingOptions(); if (cachingOptions != null) cfm.caching(cachingOptions); +#endif } +#if 0 @Override public String toString() { @@ -215,4 +243,9 @@ public class CFPropDefs extends PropertyDefinitions field, minimumValue, defaultValue)); } +#endif +}; + +} + } diff --git a/cql3/statements/create_table_statement.hh b/cql3/statements/create_table_statement.hh new file mode 100644 index 0000000000..f6cc29364d --- /dev/null +++ b/cql3/statements/create_table_statement.hh @@ -0,0 +1,488 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2015 Cloudius Systems + * + * Modified by Cloudius Systems + */ + +#pragma once + +#include "cql3/statements/schema_altering_statement.hh" +#include "cql3/statements/cf_prop_defs.hh" +#include "cql3/statements/cf_statement.hh" +#include "cql3/cql3_type.hh" + +#include "service/migration_manager.hh" +#include "schema.hh" + +#include "core/shared_ptr.hh" + +#include +#include +#include +#include + +#if 0 +package org.apache.cassandra.cql3.statements; + +import java.nio.ByteBuffer; +import java.util.*; + +import org.apache.cassandra.exceptions.*; +import org.apache.commons.lang3.StringUtils; +import com.google.common.collect.HashMultiset; +import com.google.common.collect.Multiset; + +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.config.ColumnDefinition; +import org.apache.cassandra.config.CFMetaData; +import org.apache.cassandra.config.Schema; +import org.apache.cassandra.cql3.*; +import org.apache.cassandra.db.composites.*; +import org.apache.cassandra.db.ColumnFamilyType; +import org.apache.cassandra.db.marshal.*; +import org.apache.cassandra.exceptions.AlreadyExistsException; +import org.apache.cassandra.io.compress.CompressionParameters; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.MigrationManager; +import org.apache.cassandra.transport.Event; +import org.apache.cassandra.utils.ByteBufferUtil; +#endif + +namespace cql3 { + +namespace statements { + +/** A CREATE TABLE parsed from a CQL query statement. */ +class create_table_statement : public schema_altering_statement { +#if 0 + public CellNameType comparator; +#endif +private: +#if 0 + private AbstractType defaultValidator; + private AbstractType keyValidator; + + private final List keyAliases = new ArrayList(); + private final List columnAliases = new ArrayList(); + private ByteBuffer valueAlias; + + private boolean isDense; + + private final Map columns = new HashMap(); +#endif + const std::set<::shared_ptr> _static_columns; + const ::shared_ptr _properties; + const bool _if_not_exists; +public: + create_table_statement(::shared_ptr name, ::shared_ptr properties, bool if_not_exists, std::set<::shared_ptr> static_columns) + : schema_altering_statement{name} + , _static_columns{static_columns} + , _properties{properties} + , _if_not_exists{if_not_exists} + { +#if 0 + try + { + if (!this.properties.hasProperty(CFPropDefs.KW_COMPRESSION) && CFMetaData.DEFAULT_COMPRESSOR != null) + this.properties.addProperty(CFPropDefs.KW_COMPRESSION, + new HashMap() + {{ + put(CompressionParameters.SSTABLE_COMPRESSION, CFMetaData.DEFAULT_COMPRESSOR); + }}); + } + catch (SyntaxException e) + { + throw new AssertionError(e); + } +#endif + } + + virtual void check_access(const service::client_state& state) override { + warn(unimplemented::cause::PERMISSIONS); +#if 0 + state.hasKeyspaceAccess(keyspace(), Permission.CREATE); +#endif + } + + virtual void validate(const service::client_state& state) override { + // validated in announceMigration() + } + +#if 0 + // Column definitions + private List getColumns(CFMetaData cfm) + { + List columnDefs = new ArrayList<>(columns.size()); + Integer componentIndex = comparator.isCompound() ? comparator.clusteringPrefixSize() : null; + for (Map.Entry col : columns.entrySet()) + { + ColumnIdentifier id = col.getKey(); + columnDefs.add(staticColumns.contains(id) + ? ColumnDefinition.staticDef(cfm, col.getKey().bytes, col.getValue(), componentIndex) + : ColumnDefinition.regularDef(cfm, col.getKey().bytes, col.getValue(), componentIndex)); + } + + return columnDefs; + } +#endif + + virtual bool announce_migration(bool is_local_only) override { + try { + service::migration_manager::announce_new_column_family(get_cf_meta_data(), is_local_only); + return true; + } catch (const exceptions::already_exists_exception& e) { + if (_if_not_exists) { + return false; + } + throw e; + } + } + + virtual shared_ptr change_event() override { + return make_shared(transport::event::schema_change::change_type::CREATED, transport::event::schema_change::target_type::TABLE, keyspace(), column_family()); + } + + /** + * Returns a CFMetaData instance based on the parameters parsed from this + * CREATE statement, or defaults where applicable. + * + * @return a CFMetaData instance corresponding to the values parsed from this statement + * @throws InvalidRequestException on failure to validate parsed parameters + */ + schema_ptr get_cf_meta_data() { + auto s = make_lw_shared(schema(keyspace(), column_family(), + // partition key + {}, + // clustering key + {}, + // regular columns + {}, + // static columns + {}, + // regular column name type + utf8_type, + // comment + "" + )); + apply_properties_to(s); + return s; + } + + void apply_properties_to(schema_ptr s) { +#if 0 + cfmd.defaultValidator(defaultValidator) + .keyValidator(keyValidator) + .addAllColumnDefinitions(getColumns(cfmd)) + .isDense(isDense); + + addColumnMetadataFromAliases(cfmd, keyAliases, keyValidator, ColumnDefinition.Kind.PARTITION_KEY); + addColumnMetadataFromAliases(cfmd, columnAliases, comparator.asAbstractType(), ColumnDefinition.Kind.CLUSTERING_COLUMN); + if (valueAlias != null) + addColumnMetadataFromAliases(cfmd, Collections.singletonList(valueAlias), defaultValidator, ColumnDefinition.Kind.COMPACT_VALUE); +#endif + + _properties->apply_to_schema(s); + } + +#if 0 + private void addColumnMetadataFromAliases(CFMetaData cfm, List aliases, AbstractType comparator, ColumnDefinition.Kind kind) + { + if (comparator instanceof CompositeType) + { + CompositeType ct = (CompositeType)comparator; + for (int i = 0; i < aliases.size(); ++i) + if (aliases.get(i) != null) + cfm.addOrReplaceColumnDefinition(new ColumnDefinition(cfm, aliases.get(i), ct.types.get(i), i, kind)); + } + else + { + assert aliases.size() <= 1; + if (!aliases.isEmpty() && aliases.get(0) != null) + cfm.addOrReplaceColumnDefinition(new ColumnDefinition(cfm, aliases.get(0), comparator, null, kind)); + } + } +#endif + class raw_statement; +}; + +class create_table_statement::raw_statement : public cf_statement { +private: + std::unordered_map<::shared_ptr, ::shared_ptr> _definitions; +public: + const ::shared_ptr properties = ::make_shared(); +private: + std::vector>> _key_aliases; + std::vector<::shared_ptr> _column_aliases; + std::vector, bool>> defined_ordering; // Insertion ordering is important + std::set<::shared_ptr> _static_columns; + + bool _use_compact_storage = false; + std::multiset<::shared_ptr> _defined_names; + bool _if_not_exists; +public: + raw_statement(::shared_ptr name, bool if_not_exists) + : cf_statement{std::move(name)} + , _if_not_exists{if_not_exists} + { } + + virtual ::shared_ptr prepare(database& db) override { +#if 0 + // Column family name + if (!columnFamily().matches("\\w+")) + throw new InvalidRequestException(String.format("\"%s\" is not a valid table name (must be alphanumeric character only: [0-9A-Za-z]+)", columnFamily())); + if (columnFamily().length() > Schema.NAME_LENGTH) + throw new InvalidRequestException(String.format("Table names shouldn't be more than %s characters long (got \"%s\")", Schema.NAME_LENGTH, columnFamily())); + + for (Multiset.Entry entry : definedNames.entrySet()) + if (entry.getCount() > 1) + throw new InvalidRequestException(String.format("Multiple definition of identifier %s", entry.getElement())); +#endif + + properties->validate(); + + auto stmt = ::make_shared(_cf_name, properties, _if_not_exists, _static_columns); + +#if 0 + Map definedMultiCellCollections = null; + for (Map.Entry entry : definitions.entrySet()) + { + ColumnIdentifier id = entry.getKey(); + CQL3Type pt = entry.getValue().prepare(keyspace()); + if (pt.isCollection() && ((CollectionType) pt.getType()).isMultiCell()) + { + if (definedMultiCellCollections == null) + definedMultiCellCollections = new HashMap<>(); + definedMultiCellCollections.put(id.bytes, (CollectionType) pt.getType()); + } + stmt.columns.put(id, pt.getType()); // we'll remove what is not a column below + } + + if (keyAliases.isEmpty()) + throw new InvalidRequestException("No PRIMARY KEY specifed (exactly one required)"); + else if (keyAliases.size() > 1) + throw new InvalidRequestException("Multiple PRIMARY KEYs specifed (exactly one required)"); + + List kAliases = keyAliases.get(0); + + List> keyTypes = new ArrayList>(kAliases.size()); + for (ColumnIdentifier alias : kAliases) + { + stmt.keyAliases.add(alias.bytes); + AbstractType t = getTypeAndRemove(stmt.columns, alias); + if (t instanceof CounterColumnType) + throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", alias)); + if (staticColumns.contains(alias)) + throw new InvalidRequestException(String.format("Static column %s cannot be part of the PRIMARY KEY", alias)); + keyTypes.add(t); + } + stmt.keyValidator = keyTypes.size() == 1 ? keyTypes.get(0) : CompositeType.getInstance(keyTypes); + + // Dense means that no part of the comparator stores a CQL column name. This means + // COMPACT STORAGE with at least one columnAliases (otherwise it's a thrift "static" CF). + stmt.isDense = useCompactStorage && !columnAliases.isEmpty(); + + // Handle column aliases + if (columnAliases.isEmpty()) + { + if (useCompactStorage) + { + // There should remain some column definition since it is a non-composite "static" CF + if (stmt.columns.isEmpty()) + throw new InvalidRequestException("No definition found that is not part of the PRIMARY KEY"); + + if (definedMultiCellCollections != null) + throw new InvalidRequestException("Non-frozen collection types are not supported with COMPACT STORAGE"); + + stmt.comparator = new SimpleSparseCellNameType(UTF8Type.instance); + } + else + { + stmt.comparator = definedMultiCellCollections == null + ? new CompoundSparseCellNameType(Collections.>emptyList()) + : new CompoundSparseCellNameType.WithCollection(Collections.>emptyList(), ColumnToCollectionType.getInstance(definedMultiCellCollections)); + } + } + else + { + // If we use compact storage and have only one alias, it is a + // standard "dynamic" CF, otherwise it's a composite + if (useCompactStorage && columnAliases.size() == 1) + { + if (definedMultiCellCollections != null) + throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE"); + + ColumnIdentifier alias = columnAliases.get(0); + if (staticColumns.contains(alias)) + throw new InvalidRequestException(String.format("Static column %s cannot be part of the PRIMARY KEY", alias)); + + stmt.columnAliases.add(alias.bytes); + AbstractType at = getTypeAndRemove(stmt.columns, alias); + if (at instanceof CounterColumnType) + throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", stmt.columnAliases.get(0))); + stmt.comparator = new SimpleDenseCellNameType(at); + } + else + { + List> types = new ArrayList>(columnAliases.size() + 1); + for (ColumnIdentifier t : columnAliases) + { + stmt.columnAliases.add(t.bytes); + + AbstractType type = getTypeAndRemove(stmt.columns, t); + if (type instanceof CounterColumnType) + throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", t)); + if (staticColumns.contains(t)) + throw new InvalidRequestException(String.format("Static column %s cannot be part of the PRIMARY KEY", t)); + types.add(type); + } + + if (useCompactStorage) + { + if (definedMultiCellCollections != null) + throw new InvalidRequestException("Collection types are not supported with COMPACT STORAGE"); + + stmt.comparator = new CompoundDenseCellNameType(types); + } + else + { + stmt.comparator = definedMultiCellCollections == null + ? new CompoundSparseCellNameType(types) + : new CompoundSparseCellNameType.WithCollection(types, ColumnToCollectionType.getInstance(definedMultiCellCollections)); + } + } + } + + if (!staticColumns.isEmpty()) + { + // Only CQL3 tables can have static columns + if (useCompactStorage) + throw new InvalidRequestException("Static columns are not supported in COMPACT STORAGE tables"); + // Static columns only make sense if we have at least one clustering column. Otherwise everything is static anyway + if (columnAliases.isEmpty()) + throw new InvalidRequestException("Static columns are only useful (and thus allowed) if the table has at least one clustering column"); + } + + if (useCompactStorage && !stmt.columnAliases.isEmpty()) + { + if (stmt.columns.isEmpty()) + { + // The only value we'll insert will be the empty one, so the default validator don't matter + stmt.defaultValidator = BytesType.instance; + // We need to distinguish between + // * I'm upgrading from thrift so the valueAlias is null + // * I've defined my table with only a PK (and the column value will be empty) + // So, we use an empty valueAlias (rather than null) for the second case + stmt.valueAlias = ByteBufferUtil.EMPTY_BYTE_BUFFER; + } + else + { + if (stmt.columns.size() > 1) + throw new InvalidRequestException(String.format("COMPACT STORAGE with composite PRIMARY KEY allows no more than one column not part of the PRIMARY KEY (got: %s)", StringUtils.join(stmt.columns.keySet(), ", "))); + + Map.Entry lastEntry = stmt.columns.entrySet().iterator().next(); + stmt.defaultValidator = lastEntry.getValue(); + stmt.valueAlias = lastEntry.getKey().bytes; + stmt.columns.remove(lastEntry.getKey()); + } + } + else + { + // For compact, we are in the "static" case, so we need at least one column defined. For non-compact however, having + // just the PK is fine since we have CQL3 row marker. + if (useCompactStorage && stmt.columns.isEmpty()) + throw new InvalidRequestException("COMPACT STORAGE with non-composite PRIMARY KEY require one column not part of the PRIMARY KEY, none given"); + + // There is no way to insert/access a column that is not defined for non-compact storage, so + // the actual validator don't matter much (except that we want to recognize counter CF as limitation apply to them). + stmt.defaultValidator = !stmt.columns.isEmpty() && (stmt.columns.values().iterator().next() instanceof CounterColumnType) + ? CounterColumnType.instance + : BytesType.instance; + } + + + // If we give a clustering order, we must explicitly do so for all aliases and in the order of the PK + if (!definedOrdering.isEmpty()) + { + if (definedOrdering.size() > columnAliases.size()) + throw new InvalidRequestException("Only clustering key columns can be defined in CLUSTERING ORDER directive"); + + int i = 0; + for (ColumnIdentifier id : definedOrdering.keySet()) + { + ColumnIdentifier c = columnAliases.get(i); + if (!id.equals(c)) + { + if (definedOrdering.containsKey(c)) + throw new InvalidRequestException(String.format("The order of columns in the CLUSTERING ORDER directive must be the one of the clustering key (%s must appear before %s)", c, id)); + else + throw new InvalidRequestException(String.format("Missing CLUSTERING ORDER for column %s", c)); + } + ++i; + } + } +#endif + + return ::make_shared(stmt); + } + +#if 0 + private AbstractType getTypeAndRemove(Map columns, ColumnIdentifier t) throws InvalidRequestException + { + AbstractType type = columns.get(t); + if (type == null) + throw new InvalidRequestException(String.format("Unknown definition %s referenced in PRIMARY KEY", t)); + if (type.isCollection() && type.isMultiCell()) + throw new InvalidRequestException(String.format("Invalid collection type for PRIMARY KEY component %s", t)); + + columns.remove(t); + Boolean isReversed = definedOrdering.get(t); + return isReversed != null && isReversed ? ReversedType.getInstance(type) : type; + } +#endif + + void add_definition(::shared_ptr def, ::shared_ptr type, bool is_static) { + _defined_names.emplace(def); + _definitions.emplace(def, type); + if (is_static) { + _static_columns.emplace(def); + } + } + + void add_key_aliases(const std::vector<::shared_ptr> aliases) { + _key_aliases.emplace_back(aliases); + } + + void add_column_alias(::shared_ptr alias) { + _column_aliases.emplace_back(alias); + } + + void set_ordering(::shared_ptr alias, bool reversed) { + defined_ordering.emplace_back(alias, reversed); + } + + void set_compact_storage() { + _use_compact_storage = true; + } +}; + +} + +} diff --git a/cql3/statements/modification_statement.cc b/cql3/statements/modification_statement.cc index 8f509da747..48801b6540 100644 --- a/cql3/statements/modification_statement.cc +++ b/cql3/statements/modification_statement.cc @@ -338,7 +338,7 @@ modification_statement::execute_with_condition(service::storage_proxy& proxy, se } void -modification_statement::add_key_values(column_definition& def, ::shared_ptr values) { +modification_statement::add_key_values(const column_definition& def, ::shared_ptr values) { if (def.is_clustering_key()) { _has_no_clustering_columns = false; } @@ -350,7 +350,7 @@ modification_statement::add_key_values(column_definition& def, ::shared_ptr value) { +modification_statement::add_key_value(const column_definition& def, ::shared_ptr value) { add_key_values(def, ::make_shared(def, value)); } @@ -421,7 +421,7 @@ modification_statement::parsed::prepare(database& db, ::shared_ptrprepare_column_identifier(schema); - column_definition* def = get_column_definition(schema, *id); + const column_definition* def = get_column_definition(schema, *id); if (!def) { throw exceptions::invalid_request_exception(sprint("Unknown identifier %s", *id)); } diff --git a/cql3/statements/modification_statement.hh b/cql3/statements/modification_statement.hh index d104b074ef..eabc691857 100644 --- a/cql3/statements/modification_statement.hh +++ b/cql3/statements/modification_statement.hh @@ -116,8 +116,8 @@ private: bool _sets_static_columns = false; bool _sets_regular_columns = false; - const std::function)> get_column_for_condition = - [](::shared_ptr cond) -> column_definition& { + const std::function)> get_column_for_condition = + [](::shared_ptr cond) -> const column_definition& { return cond->column; }; @@ -250,10 +250,10 @@ public: } private: - void add_key_values(column_definition& def, ::shared_ptr values); + void add_key_values(const column_definition& def, ::shared_ptr values); public: - void add_key_value(column_definition& def, ::shared_ptr value); + void add_key_value(const column_definition& def, ::shared_ptr value); void process_where_clause(std::vector where_clause, ::shared_ptr names); std::vector build_partition_keys(const query_options& options); diff --git a/cql3/statements/property_definitions.hh b/cql3/statements/property_definitions.hh index 5117adb9f5..e3e53910d4 100644 --- a/cql3/statements/property_definitions.hh +++ b/cql3/statements/property_definitions.hh @@ -33,6 +33,7 @@ #include #include #include +#include #include diff --git a/cql3/statements/schema_altering_statement.hh b/cql3/statements/schema_altering_statement.hh index d574b49cb9..0388b7985f 100644 --- a/cql3/statements/schema_altering_statement.hh +++ b/cql3/statements/schema_altering_statement.hh @@ -52,7 +52,7 @@ namespace messages = transport::messages; /** * Abstract class for statements that alter the schema. */ -class schema_altering_statement : public cf_statement, public virtual cql_statement, public ::enable_shared_from_this { +class schema_altering_statement : public cf_statement, public cql_statement, public ::enable_shared_from_this { private: const bool _is_column_family_level; diff --git a/database.cc b/database.cc index b08e004080..6976d781bd 100644 --- a/database.cc +++ b/database.cc @@ -25,7 +25,7 @@ get_column_types(const Sequence& column_definitions) { } ::shared_ptr -schema::make_column_specification(column_definition& def) { +schema::make_column_specification(const column_definition& def) { auto id = ::make_shared(def.name(), column_name_type(def)); return ::make_shared(ks_name, cf_name, std::move(id), def.type); } @@ -39,11 +39,23 @@ schema::build_columns(const std::vector& columns, column_definition::col auto& col = columns[i]; dst.emplace_back(std::move(col.name), std::move(col.type), i, kind); column_definition& def = dst.back(); - _columns_by_name[def.name()] = &def; def.column_specification = make_column_specification(def); } } +void schema::rehash_columns() { + _columns_by_name.clear(); + _regular_columns_by_name.clear(); + + for (const column_definition& def : all_columns_in_select_order()) { + _columns_by_name[def.name()] = &def; + } + + for (const column_definition& def : _regular_columns) { + _regular_columns_by_name[def.name()] = &def; + } +} + schema::schema(sstring ks_name, sstring cf_name, std::vector partition_key, std::vector clustering_key, std::vector regular_columns, @@ -51,14 +63,15 @@ schema::schema(sstring ks_name, sstring cf_name, std::vector partition_k data_type regular_column_name_type, sstring comment) : _regular_columns_by_name(serialized_compare(regular_column_name_type)) - , _comment(comment) - , ks_name(std::move(ks_name)) - , cf_name(std::move(cf_name)) - , partition_key_type(::make_lw_shared>(get_column_types(partition_key))) - , clustering_key_type(::make_lw_shared>(get_column_types(clustering_key))) - , clustering_key_prefix_type(::make_lw_shared(clustering_key_type->as_prefix())) - , regular_column_name_type(regular_column_name_type) { + this->_comment = std::move(comment); + this->ks_name = std::move(ks_name); + this->cf_name = std::move(cf_name); + this->partition_key_type = ::make_lw_shared>(get_column_types(partition_key)); + this->clustering_key_type = ::make_lw_shared>(get_column_types(clustering_key)); + this->clustering_key_prefix_type = ::make_lw_shared(clustering_key_type->as_prefix()); + this->regular_column_name_type = regular_column_name_type; + if (partition_key.size() == 1) { thrift.partition_key_type = partition_key[0].type; } else { @@ -71,12 +84,17 @@ schema::schema(sstring ks_name, sstring cf_name, std::vector partition_k std::sort(regular_columns.begin(), regular_columns.end(), column::name_compare(regular_column_name_type)); build_columns(regular_columns, column_definition::column_kind::REGULAR, _regular_columns); - for (column_definition& def : _regular_columns) { - _regular_columns_by_name[def.name()] = &def; - } std::sort(static_columns.begin(), static_columns.end(), column::name_compare(utf8_type)); build_columns(static_columns, column_definition::column_kind::STATIC, _static_columns); + + rehash_columns(); +} + +schema::schema(const schema& o) + : raw_schema(o) + , _regular_columns_by_name(serialized_compare(regular_column_name_type)) { + rehash_columns(); } column_family::column_family(schema_ptr schema) @@ -275,7 +293,7 @@ column_definition::column_definition(bytes name, data_type type, column_id id, c , kind(kind) { } -column_definition* schema::get_column_definition(const bytes& name) { +const column_definition* schema::get_column_definition(const bytes& name) { auto i = _columns_by_name.find(name); if (i == _columns_by_name.end()) { return nullptr; diff --git a/schema.hh b/schema.hh index f887964835..16c072b1c9 100644 --- a/schema.hh +++ b/schema.hh @@ -62,18 +62,33 @@ struct thrift_schema { shared_ptr partition_key_type; }; -/* - * Keep this effectively immutable. - */ -class schema final { -private: - std::unordered_map _columns_by_name; - std::map _regular_columns_by_name; +// Schema fields which can be safely default-copied +// FIXME: encapsulate public fields so that we can make this a private inner structure of schema +class raw_schema { +protected: std::vector _partition_key; std::vector _clustering_key; std::vector _regular_columns; // sorted by name std::vector _static_columns; // sorted by name, present only when there's any clustering column sstring _comment; +public: + gc_clock::duration default_time_to_live = gc_clock::duration::zero(); + sstring ks_name; + sstring cf_name; + lw_shared_ptr> partition_key_type; + lw_shared_ptr> clustering_key_type; + lw_shared_ptr clustering_key_prefix_type; + data_type regular_column_name_type; + thrift_schema thrift; +}; + +/* + * Keep this effectively immutable. + */ +class schema final : public raw_schema { +private: + std::unordered_map _columns_by_name; + std::map _regular_columns_by_name; public: struct column { bytes name; @@ -88,16 +103,8 @@ public: }; private: void build_columns(const std::vector& columns, column_definition::column_kind kind, std::vector& dst); - ::shared_ptr make_column_specification(column_definition& def); -public: - gc_clock::duration default_time_to_live = gc_clock::duration::zero(); - const sstring ks_name; - const sstring cf_name; - lw_shared_ptr> partition_key_type; - lw_shared_ptr> clustering_key_type; - lw_shared_ptr clustering_key_prefix_type; - data_type regular_column_name_type; - thrift_schema thrift; + ::shared_ptr make_column_specification(const column_definition& def); + void rehash_columns(); public: schema(sstring ks_name, sstring cf_name, std::vector partition_key, @@ -106,13 +113,17 @@ public: std::vector static_columns, shared_ptr regular_column_name_type, sstring comment = {}); + schema(const schema&); + void set_comment(const sstring& comment) { + _comment = comment; + } bool is_dense() const { return false; } bool is_counter() const { return false; } - column_definition* get_column_definition(const bytes& name); + const column_definition* get_column_definition(const bytes& name); auto regular_begin() { return _regular_columns.begin(); } @@ -137,7 +148,7 @@ public: return _regular_columns.begin() + i->second->id; } } - data_type column_name_type(column_definition& def) { + data_type column_name_type(const column_definition& def) { return def.kind == column_definition::column_kind::REGULAR ? regular_column_name_type : utf8_type; } column_definition& regular_column_at(column_id id) { @@ -146,7 +157,7 @@ public: column_definition& static_column_at(column_id id) { return _static_columns[id]; } - bool is_last_partition_key(column_definition& def) { + bool is_last_partition_key(const column_definition& def) { return &_partition_key[_partition_key.size() - 1] == &def; } bool has_collections() { diff --git a/service/migration_manager.hh b/service/migration_manager.hh index 36454c636f..45684a6f9c 100644 --- a/service/migration_manager.hh +++ b/service/migration_manager.hh @@ -294,9 +294,11 @@ public: { announceNewColumnFamily(cfm, false); } +#endif - public static void announceNewColumnFamily(CFMetaData cfm, boolean announceLocally) throws ConfigurationException - { + static void announce_new_column_family(schema_ptr cfm, bool announce_locally) { + warn(unimplemented::cause::MIGRATIONS); +#if 0 cfm.validate(); KSMetaData ksm = Schema.instance.getKSMetaData(cfm.ksName); @@ -307,8 +309,10 @@ public: logger.info(String.format("Create new table: %s", cfm)); announce(LegacySchemaTables.makeCreateTableMutation(ksm, cfm, FBUtilities.timestampMicros()), announceLocally); +#endif } +#if 0 public static void announceNewType(UserType newType, boolean announceLocally) { KSMetaData ksm = Schema.instance.getKSMetaData(newType.keyspace); diff --git a/tests/perf/perf_mutation.cc b/tests/perf/perf_mutation.cc index a75c771245..50435c2a61 100644 --- a/tests/perf/perf_mutation.cc +++ b/tests/perf/perf_mutation.cc @@ -19,7 +19,7 @@ int main(int argc, char* argv[]) { time_it([&] { mutation m(key, s); - column_definition& col = *s->get_column_definition("r1"); + const column_definition& col = *s->get_column_definition("r1"); m.set_clustered_cell(c_key, col, make_atomic_cell(value)); cf.apply(std::move(m)); }); diff --git a/tests/urchin/cql_query_test.cc b/tests/urchin/cql_query_test.cc index c80c65a044..32f508370d 100644 --- a/tests/urchin/cql_query_test.cc +++ b/tests/urchin/cql_query_test.cc @@ -93,6 +93,17 @@ SEASTAR_TEST_CASE(test_create_keyspace_statement) { }); } +SEASTAR_TEST_CASE(test_create_table_statement) { + auto db = make_shared>(); + auto state = make_shared(*db, ks_name); + + return db->start().then([state] { + return state->execute_cql("create table users (user_name varchar PRIMARY KEY, birth_year bigint);").discard_result(); + }).finally([db] { + return db->stop().finally([db] {}); + }); +} + SEASTAR_TEST_CASE(test_insert_statement) { auto db = make_shared>(); auto state = make_shared(*db, ks_name); diff --git a/tests/urchin/mutation_test.cc b/tests/urchin/mutation_test.cc index cf48dee98f..bc82f07204 100644 --- a/tests/urchin/mutation_test.cc +++ b/tests/urchin/mutation_test.cc @@ -23,7 +23,7 @@ BOOST_AUTO_TEST_CASE(test_mutation_is_applied) { column_family cf(s); - column_definition& r1_col = *s->get_column_definition("r1"); + const column_definition& r1_col = *s->get_column_definition("r1"); auto key = partition_key::from_exploded(*s, {to_bytes("key1")}); auto c_key = clustering_key::from_exploded(*s, {int32_type->decompose(2)});