diff --git a/cql3/statements/alter_table_statement.cc b/cql3/statements/alter_table_statement.cc index bf1ce67365..77348bb98c 100644 --- a/cql3/statements/alter_table_statement.cc +++ b/cql3/statements/alter_table_statement.cc @@ -79,214 +79,187 @@ void alter_table_statement::validate(distributed& proxy, future alter_table_statement::announce_migration(distributed& proxy, bool is_local_only) { - throw std::runtime_error(sprint("%s not implemented", __PRETTY_FUNCTION__)); -#if 0 - CFMetaData meta = validateColumnFamily(keyspace(), columnFamily()); - CFMetaData cfm = meta.copy(); + auto& db = proxy.local().get_db().local(); + auto schema = validation::validate_column_family(db, keyspace(), column_family()); + auto cfm = schema_builder(schema); - CQL3Type validator = this.validator == null ? null : this.validator.prepare(keyspace()); - ColumnIdentifier columnName = null; - ColumnDefinition def = null; - if (rawColumnName != null) - { - columnName = rawColumnName.prepare(cfm); - def = cfm.getColumnDefinition(columnName); + shared_ptr validator; + if (_validator) { + validator = _validator->prepare(db, keyspace()); + } + shared_ptr column_name; + const column_definition* def = nullptr; + if (_raw_column_name) { + column_name = _raw_column_name->prepare_column_identifier(schema); + def = get_column_definition(schema, *column_name); } - switch (oType) + switch (_type) { + case alter_table_statement::type::add: { - case ADD: - assert columnName != null; - if (cfm.comparator.isDense()) - throw new InvalidRequestException("Cannot add new column to a COMPACT STORAGE table"); + assert(column_name); + if (schema->is_dense()) { + throw exceptions::invalid_request_exception("Cannot add new column to a COMPACT STORAGE table"); + } - if (isStatic) - { - if (!cfm.comparator.isCompound()) - throw new InvalidRequestException("Static columns are not allowed in COMPACT STORAGE tables"); - if (cfm.clusteringColumns().isEmpty()) - throw new InvalidRequestException("Static columns are only useful (and thus allowed) if the table has at least one clustering column"); + if (_is_static) { + if (!schema->is_compound()) { + throw exceptions::invalid_request_exception("Static columns are not allowed in COMPACT STORAGE tables"); + } + if (!schema->clustering_key_size()) { + throw exceptions::invalid_request_exception("Static columns are only useful (and thus allowed) if the table has at least one clustering column"); + } + } + + if (def) { + if (def->is_partition_key()) { + throw exceptions::invalid_request_exception(sprint("Invalid column name %s because it conflicts with a PRIMARY KEY part", column_name)); + } else { + throw exceptions::invalid_request_exception(sprint("Invalid column name %s because it conflicts with an existing column", column_name)); + } + } + + // Cannot re-add a dropped counter column. See #7831. + if (schema->is_counter() && schema->dropped_columns().count(column_name->text())) { + throw exceptions::invalid_request_exception(sprint("Cannot re-add previously dropped counter column %s", column_name)); + } + + auto type = validator->get_type(); + if (type->is_collection() && type->is_multi_cell()) { + if (!schema->is_compound()) { + throw exceptions::invalid_request_exception("Cannot use non-frozen collections with a non-composite PRIMARY KEY"); + } + if (schema->is_super()) { + throw exceptions::invalid_request_exception("Cannot use non-frozen collections with super column families"); + } + } + + cfm.with_column(column_name->name(), type, _is_static ? column_kind::static_column : column_kind::regular_column); + break; + } + case alter_table_statement::type::alter: + { + assert(column_name); + if (!def) { + throw exceptions::invalid_request_exception(sprint("Column %s was not found in table %s", column_name, column_family())); + } + + auto type = validator->get_type(); + switch (def->kind) { + case column_kind::partition_key: + if (type->is_counter()) { + throw exceptions::invalid_request_exception(sprint("counter type is not supported for PRIMARY KEY part %s", column_name)); } - if (def != null) - { - switch (def.kind) - { - case PARTITION_KEY: - case CLUSTERING_COLUMN: - throw new InvalidRequestException(String.format("Invalid column name %s because it conflicts with a PRIMARY KEY part", columnName)); - default: - throw new InvalidRequestException(String.format("Invalid column name %s because it conflicts with an existing column", columnName)); + if (!type->is_value_compatible_with(*def->type)) { + throw exceptions::configuration_exception(sprint("Cannot change %s from type %s to type %s: types are incompatible.", + column_name, + def->type->as_cql3_type(), + validator)); + } + break; + + case column_kind::clustering_key: + if (!schema->is_cql3_table()) { + throw exceptions::invalid_request_exception(sprint("Cannot alter clustering column %s in a non-CQL3 table", column_name)); + } + + // Note that CFMetaData.validateCompatibility already validate the change we're about to do. However, the error message it + // sends is a bit cryptic for a CQL3 user, so validating here for a sake of returning a better error message + // Do note that we need isCompatibleWith here, not just isValueCompatibleWith. + if (!type->is_compatible_with(*def->type)) { + throw exceptions::configuration_exception(sprint("Cannot change %s from type %s to type %s: types are not order-compatible.", + column_name, + def->type->as_cql3_type(), + validator)); + } + break; + + case column_kind::compact_column: + case column_kind::regular_column: + case column_kind::static_column: + // Thrift allows to change a column validator so CFMetaData.validateCompatibility will let it slide + // if we change to an incompatible type (contrarily to the comparator case). But we don't want to + // allow it for CQL3 (see #5882) so validating it explicitly here. We only care about value compatibility + // though since we won't compare values (except when there is an index, but that is validated by + // ColumnDefinition already). + if (!type->is_value_compatible_with(*def->type)) { + throw exceptions::configuration_exception(sprint("Cannot change %s from type %s to type %s: types are incompatible.", + column_name, + def->type->as_cql3_type(), + validator)); + } + break; + } + // In any case, we update the column definition + cfm.with_altered_column_type(column_name->name(), type); + break; + } + case alter_table_statement::type::drop: + assert(column_name); + if (!schema->is_cql3_table()) { + throw exceptions::invalid_request_exception("Cannot drop columns from a non-CQL3 table"); + } + if (!def) { + throw exceptions::invalid_request_exception(sprint("Column %s was not found in table %s", column_name, column_family())); + } + + if (def->is_primary_key()) { + throw exceptions::invalid_request_exception(sprint("Cannot drop PRIMARY KEY part %s", column_name)); + } else { + for (auto&& column_def : boost::range::join(schema->static_columns(), schema->regular_columns())) { // find + if (column_def.name() == column_name->name()) { + cfm.without_column(column_name->name()); + break; } } + } + break; - // Cannot re-add a dropped counter column. See #7831. - if (meta.isCounter() && meta.getDroppedColumns().containsKey(columnName)) - throw new InvalidRequestException(String.format("Cannot re-add previously dropped counter column %s", columnName)); + case alter_table_statement::type::opts: + if (!_properties) { + throw exceptions::invalid_request_exception("ALTER COLUMNFAMILY WITH invoked, but no parameters found"); + } - AbstractType type = validator.getType(); - if (type.isCollection() && type.isMultiCell()) - { - if (!cfm.comparator.supportCollections()) - throw new InvalidRequestException("Cannot use non-frozen collections with a non-composite PRIMARY KEY"); - if (cfm.isSuper()) - throw new InvalidRequestException("Cannot use non-frozen collections with super column families"); + _properties->validate(); - // If there used to be a collection column with the same name (that has been dropped), it will - // still be appear in the ColumnToCollectionType because or reasons explained on #6276. The same - // reason mean that we can't allow adding a new collection with that name (see the ticket for details). - if (cfm.comparator.hasCollections()) - { - CollectionType previous = cfm.comparator.collectionType() == null ? null : cfm.comparator.collectionType().defined.get(columnName.bytes); - if (previous != null && !type.isCompatibleWith(previous)) - throw new InvalidRequestException(String.format("Cannot add a collection with the name %s " + - "because a collection with the same name and a different type has already been used in the past", columnName)); - } + if (schema->is_counter() && _properties->get_default_time_to_live() > 0) { + throw exceptions::invalid_request_exception("Cannot set default_time_to_live on a table with counters"); + } - cfm.comparator = cfm.comparator.addOrUpdateCollection(columnName, (CollectionType)type); + _properties->apply_to_builder(cfm); + break; + + case alter_table_statement::type::rename: + for (auto&& entry : _renames) { + auto from = entry.first->prepare_column_identifier(schema); + auto to = entry.second->prepare_column_identifier(schema); + + auto def = schema->get_column_definition(from->name()); + if (!def) { + throw exceptions::invalid_request_exception(sprint("Cannot rename unknown column %s in table %s", from, column_family())); } - Integer componentIndex = cfm.comparator.isCompound() ? cfm.comparator.clusteringPrefixSize() : null; - cfm.addColumnDefinition(isStatic - ? ColumnDefinition.staticDef(cfm, columnName.bytes, type, componentIndex) - : ColumnDefinition.regularDef(cfm, columnName.bytes, type, componentIndex)); - break; - - case ALTER: - assert columnName != null; - if (def == null) - throw new InvalidRequestException(String.format("Column %s was not found in table %s", columnName, columnFamily())); - - AbstractType validatorType = validator.getType(); - switch (def.kind) - { - case PARTITION_KEY: - if (validatorType instanceof CounterColumnType) - throw new InvalidRequestException(String.format("counter type is not supported for PRIMARY KEY part %s", columnName)); - if (cfm.getKeyValidator() instanceof CompositeType) - { - List> oldTypes = ((CompositeType) cfm.getKeyValidator()).types; - if (!validatorType.isValueCompatibleWith(oldTypes.get(def.position()))) - throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.", - columnName, - oldTypes.get(def.position()).asCQL3Type(), - validator)); - - List> newTypes = new ArrayList>(oldTypes); - newTypes.set(def.position(), validatorType); - cfm.keyValidator(CompositeType.getInstance(newTypes)); - } - else - { - if (!validatorType.isValueCompatibleWith(cfm.getKeyValidator())) - throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.", - columnName, - cfm.getKeyValidator().asCQL3Type(), - validator)); - cfm.keyValidator(validatorType); - } - break; - case CLUSTERING_COLUMN: - if (!cfm.isCQL3Table()) - throw new InvalidRequestException(String.format("Cannot alter clustering column %s in a non-CQL3 table", columnName)); - - AbstractType oldType = cfm.comparator.subtype(def.position()); - // Note that CFMetaData.validateCompatibility already validate the change we're about to do. However, the error message it - // sends is a bit cryptic for a CQL3 user, so validating here for a sake of returning a better error message - // Do note that we need isCompatibleWith here, not just isValueCompatibleWith. - if (!validatorType.isCompatibleWith(oldType)) - throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are not order-compatible.", - columnName, - oldType.asCQL3Type(), - validator)); - - cfm.comparator = cfm.comparator.setSubtype(def.position(), validatorType); - break; - case COMPACT_VALUE: - // See below - if (!validatorType.isValueCompatibleWith(cfm.getDefaultValidator())) - throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.", - columnName, - cfm.getDefaultValidator().asCQL3Type(), - validator)); - cfm.defaultValidator(validatorType); - break; - case REGULAR: - case STATIC: - // Thrift allows to change a column validator so CFMetaData.validateCompatibility will let it slide - // if we change to an incompatible type (contrarily to the comparator case). But we don't want to - // allow it for CQL3 (see #5882) so validating it explicitly here. We only care about value compatibility - // though since we won't compare values (except when there is an index, but that is validated by - // ColumnDefinition already). - if (!validatorType.isValueCompatibleWith(def.type)) - throw new ConfigurationException(String.format("Cannot change %s from type %s to type %s: types are incompatible.", - columnName, - def.type.asCQL3Type(), - validator)); - - // For collections, if we alter the type, we need to update the comparator too since it includes - // the type too (note that isValueCompatibleWith above has validated that the new type doesn't - // change the underlying sorting order, but we still don't want to have a discrepancy between the type - // in the comparator and the one in the ColumnDefinition as that would be dodgy). - if (validatorType.isCollection() && validatorType.isMultiCell()) - cfm.comparator = cfm.comparator.addOrUpdateCollection(def.name, (CollectionType)validatorType); - - break; + if (schema->get_column_definition(to->name())) { + throw exceptions::invalid_request_exception(sprint("Cannot rename column %s to %s in table %s; another column of that name already exist", from, to, column_family())); } - // In any case, we update the column definition - cfm.addOrReplaceColumnDefinition(def.withNewType(validatorType)); - break; - case DROP: - assert columnName != null; - if (!cfm.isCQL3Table()) - throw new InvalidRequestException("Cannot drop columns from a non-CQL3 table"); - if (def == null) - throw new InvalidRequestException(String.format("Column %s was not found in table %s", columnName, columnFamily())); - - switch (def.kind) - { - case PARTITION_KEY: - case CLUSTERING_COLUMN: - throw new InvalidRequestException(String.format("Cannot drop PRIMARY KEY part %s", columnName)); - case REGULAR: - case STATIC: - ColumnDefinition toDelete = null; - for (ColumnDefinition columnDef : cfm.regularAndStaticColumns()) - { - if (columnDef.name.equals(columnName)) - toDelete = columnDef; - } - assert toDelete != null; - cfm.removeColumnDefinition(toDelete); - cfm.recordColumnDrop(toDelete); - break; + if (def->is_part_of_cell_name()) { + throw exceptions::invalid_request_exception(sprint("Cannot rename non PRIMARY KEY part %s", from)); } - break; - case OPTS: - if (cfProps == null) - throw new InvalidRequestException(String.format("ALTER COLUMNFAMILY WITH invoked, but no parameters found")); - cfProps.validate(); - - if (meta.isCounter() && cfProps.getDefaultTimeToLive() > 0) - throw new InvalidRequestException("Cannot set default_time_to_live on a table with counters"); - - cfProps.applyToCFMetadata(cfm); - break; - case RENAME: - for (Map.Entry entry : renames.entrySet()) - { - ColumnIdentifier from = entry.getKey().prepare(cfm); - ColumnIdentifier to = entry.getValue().prepare(cfm); - cfm.renameColumn(from, to); + if (def->is_indexed()) { + throw exceptions::invalid_request_exception(sprint("Cannot rename column %s because it is secondary indexed", from)); } - break; + + cfm.with_column_rename(from->name(), to->name()); + } + break; } - MigrationManager.announceColumnFamilyUpdate(cfm, false, isLocalOnly); - return true; -#endif + return service::get_local_migration_manager().announce_column_family_update(cfm.build(), false, is_local_only).then([] { + return true; + }); } shared_ptr alter_table_statement::change_event()