yifan-c commented on code in PR #4411: URL: https://github.com/apache/cassandra/pull/4411#discussion_r2463049669
########## doc/modules/cassandra/pages/developing/cql/ddl.adoc: ########## @@ -801,3 +801,195 @@ statements. However, tables are the only object that can be truncated currently, and the `TABLE` keyword can be omitted. Truncating a table permanently removes all existing data from the table, but without removing the table itself. + +[[comment-statement]] +== COMMENT ON + +The `COMMENT ON` statement allows you to add descriptive comments to schema elements for documentation purposes. +Comments are stored in the schema metadata and displayed when using `DESCRIBE` statements. + +=== COMMENT ON KEYSPACE + +Add or modify a comment on a keyspace: + +[source,cql] +---- +COMMENT ON KEYSPACE keyspace_name IS 'comment text'; +COMMENT ON KEYSPACE keyspace_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON KEYSPACE cycling IS 'Keyspace for cycling application data'; +---- + +=== COMMENT ON TABLE + +Add or modify a comment on a table: + +[source,cql] +---- +COMMENT ON TABLE [keyspace_name.]table_name IS 'comment text'; +COMMENT ON TABLE [keyspace_name.]table_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON TABLE cycling.cyclist_name IS 'Table storing cyclist names and basic information'; +---- + +=== COMMENT ON COLUMN + +Add or modify a comment on a column: + +[source,cql] +---- +COMMENT ON COLUMN [keyspace_name.]table_name.column_name IS 'comment text'; +COMMENT ON COLUMN [keyspace_name.]table_name.column_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON COLUMN cycling.cyclist_name.id IS 'Unique identifier for each cyclist'; +---- + +=== COMMENT ON TYPE + +Add or modify a comment on a user-defined type: + +[source,cql] +---- +COMMENT ON TYPE [keyspace_name.]type_name IS 'comment text'; +COMMENT ON TYPE [keyspace_name.]type_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON TYPE cycling.address IS 'User-defined type for storing address information'; +---- + +=== COMMENT ON FIELD + +Add or modify a comment on a field within a user-defined type: + +[source,cql] +---- +COMMENT ON FIELD [keyspace_name.]type_name.field_name IS 'comment text'; +COMMENT ON FIELD [keyspace_name.]type_name.field_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +include::cassandra:example$CQL/comment_on_field.cql[] Review Comment: I have two comments: 1. Some examples include inline CQL statements, while others link to external files. Since those external files only contain a single line of CQL, is it necessary to reference them externally? 2. The `[keyspace_name.]` notation might be confusing. I assume the intent is to indicate that `keyspace_name` can be omitted when a `USE` statement has been issued earlier. However, it might not be necessary to mention that scenario here. It’s probably clearer to focus on demonstrating the full syntax of the new CQL. ########## doc/modules/cassandra/pages/developing/cql/ddl.adoc: ########## @@ -801,3 +801,195 @@ statements. However, tables are the only object that can be truncated currently, and the `TABLE` keyword can be omitted. Truncating a table permanently removes all existing data from the table, but without removing the table itself. + +[[comment-statement]] +== COMMENT ON + +The `COMMENT ON` statement allows you to add descriptive comments to schema elements for documentation purposes. +Comments are stored in the schema metadata and displayed when using `DESCRIBE` statements. + +=== COMMENT ON KEYSPACE + +Add or modify a comment on a keyspace: + +[source,cql] +---- +COMMENT ON KEYSPACE keyspace_name IS 'comment text'; +COMMENT ON KEYSPACE keyspace_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON KEYSPACE cycling IS 'Keyspace for cycling application data'; +---- + +=== COMMENT ON TABLE + +Add or modify a comment on a table: + +[source,cql] +---- +COMMENT ON TABLE [keyspace_name.]table_name IS 'comment text'; +COMMENT ON TABLE [keyspace_name.]table_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON TABLE cycling.cyclist_name IS 'Table storing cyclist names and basic information'; +---- + +=== COMMENT ON COLUMN + +Add or modify a comment on a column: + +[source,cql] +---- +COMMENT ON COLUMN [keyspace_name.]table_name.column_name IS 'comment text'; +COMMENT ON COLUMN [keyspace_name.]table_name.column_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON COLUMN cycling.cyclist_name.id IS 'Unique identifier for each cyclist'; +---- + +=== COMMENT ON TYPE + +Add or modify a comment on a user-defined type: + +[source,cql] +---- +COMMENT ON TYPE [keyspace_name.]type_name IS 'comment text'; +COMMENT ON TYPE [keyspace_name.]type_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON TYPE cycling.address IS 'User-defined type for storing address information'; +---- + +=== COMMENT ON FIELD + +Add or modify a comment on a field within a user-defined type: + +[source,cql] +---- +COMMENT ON FIELD [keyspace_name.]type_name.field_name IS 'comment text'; +COMMENT ON FIELD [keyspace_name.]type_name.field_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +include::cassandra:example$CQL/comment_on_field.cql[] +---- + +NOTE: Comments can be removed by setting them to `NULL`. Comments are displayed when using `DESCRIBE` statements +and are useful for documenting the purpose and structure of your schema elements. + +[[security-label-statement]] +== SECURITY LABEL ON Review Comment: Should this be `SECURITY LABEL`? I thought the correct term was “security label statement.” Similarly, I believe it’s “comment statement,” not “comment on statement.” ########## src/java/org/apache/cassandra/cql3/statements/schema/CommentOnTableStatement.java: ########## @@ -0,0 +1,140 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.QualifiedName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.schema.TableParams; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +/** + * Handles the execution of COMMENT ON TABLE CQL statements. + * <p> + * This statement allows users to add descriptive comments to tables for documentation purposes. + * Comments are stored in the table metadata and displayed when using DESCRIBE TABLE. + * </p> + * <p> + * Syntax: {@code COMMENT ON TABLE [keyspace_name.]table_name IS 'comment text';} + * </p> + * <p> + * Comments can be removed by setting them to an empty string or null. + * </p> + * + * @see CommentOnKeyspaceStatement + * @see CommentOnColumnStatement + * @see CommentOnTypeStatement + */ +public final class CommentOnTableStatement extends AlterSchemaStatement +{ + private final String tableName; + private final String comment; + + public CommentOnTableStatement(String keyspaceName, String tableName, String comment) + { + super(keyspaceName); + this.tableName = tableName; + this.comment = comment; + } + + @Override + public void validate(ClientState state) + { + super.validate(state); + SchemaDescriptionsUtil.validateComment(comment); + } + + @Override + public Keyspaces apply(ClusterMetadata metadata) + { + Keyspaces schema = metadata.schema.getKeyspaces(); + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + + if (null == keyspace) + throw ire("Keyspace '%s' doesn't exist", keyspaceName); + + TableMetadata table = keyspace.getTableOrViewNullable(tableName); + if (null == table) + throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName); + + if (table.isView()) + throw ire("Cannot set comment on materialized view '%s.%s'. Comments should be set on the base table.", keyspaceName, tableName); + Review Comment: To @smiklosovic 's point, I think you only want to allow comment on regular table, i.e. reject if `table.kind != TableMetadata.Kind.REGULAR` ########## src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java: ########## @@ -305,17 +335,19 @@ public CQLStatement prepare(ClientState state) { String oldKeyspace = oldName.hasKeyspace() ? oldName.getKeyspace() : state.getKeyspace(); String newKeyspace = newName.hasKeyspace() ? newName.getKeyspace() : state.getKeyspace(); - return new CopyTableStatement(oldKeyspace, newKeyspace, oldName.getName(), newName.getName(), ifNotExists, createLikeOption, attrs); + return new CopyTableStatement(oldKeyspace, newKeyspace, oldName.getName(), newName.getName(), ifNotExists, createLikeOptions, attrs); } - public void withLikeOption(CreateLikeOption option) + public void addLikeOption(CreateLikeOption option) { - this.createLikeOption = option; + this.createLikeOptions.add(option); } } public enum CreateLikeOption { - INDEXES; + INDEXES, + COMMENTS, + SECURITY_LABELS; Review Comment: Not a fan of the if-else logic in the `maybeCopyComments` method and others. I would like to suggest a refactoring that follows OOP. The callsites would be simplified. ```java interface CreateLikeHandler { // TODO: add javadoc please void copy(TableMetadata.Builder tagetTableBuilder, String targetKeyspace, String targetTableName, TableMetadata sourceTableMeta); } public enum CreateLikeOption implements CreateLikeHandler { INDEXES { @Override public void copy(TableMetadata.Builder tagetTableBuilder, String targetKeyspace, String targetTableName, TableMetadata sourceTableMeta) { String sourceTableName = sourceTableMeta.name; String sourceKeyspace = sourceTableMeta.keyspace; KeyspaceMetadata targetKeyspaceMeta = Schema.instance.getKeyspaceMetadata(targetKeyspace); Set<String> customIndexes = Sets.newTreeSet(); List<IndexMetadata> indexesToCopy = new ArrayList<>(); for (IndexMetadata indexMetadata : sourceTableMeta.indexes) { // only sai and legacy secondary index is supported if (indexMetadata.isCustom() && !StorageAttachedIndex.class.getCanonicalName().equals(indexMetadata.getIndexClassName())) { customIndexes.add(indexMetadata.name); continue; } ColumnMetadata targetColumn = sourceTableMeta.getColumn(UTF8Type.instance.decompose(indexMetadata.options.get("target"))); String indexName; // The rules for generating the index names of the target table are: // (1) If the source table's index names follow the pattern sourcetablename_columnname_idx_number, the index names are considered to be generated by the system, // then we directly replace the name of source table with the name of target table, and increment the number after idx to avoid index name conflicts. // (2) Index names that do not follow the above pattern are considered user-defined, so the index names are retained and increment the number after idx to avoid conflicts. if (indexMetadata.name.startsWith(sourceTableName + "_" + targetColumn.name + "_idx")) { String baseName = IndexMetadata.generateDefaultIndexName(targetTableName, targetColumn.name); indexName = targetKeyspaceMeta.findAvailableIndexName(baseName, indexesToCopy, targetKeyspaceMeta); } else { indexName = targetKeyspaceMeta.findAvailableIndexName(indexMetadata.name, indexesToCopy, targetKeyspaceMeta); } indexesToCopy.add(IndexMetadata.fromSchemaMetadata(indexName, indexMetadata.kind, indexMetadata.options)); } if (!indexesToCopy.isEmpty()) tagetTableBuilder.indexes(Indexes.builder().add(indexesToCopy).build()); if (!customIndexes.isEmpty()) ClientWarn.instance.warn(String.format("Source table %s.%s to copy indexes from to %s.%s has custom indexes. These indexes were not copied: %s", sourceKeyspace, sourceTableName, targetKeyspace, targetTableName, customIndexes)); } }, COMMENTS { @Override public void copy(TableMetadata.Builder tagetTableBuilder, String targetKeyspace, String targetTableName, TableMetadata sourceTableMeta) { if (!StringUtils.isEmpty(sourceTableMeta.params.comment)) tagetTableBuilder.comment(sourceTableMeta.params.comment); for (ColumnMetadata columnMetadata : sourceTableMeta.columns()) tagetTableBuilder.alterColumnComment(columnMetadata.name, columnMetadata.comment); } }, SECURITY_LABELS { @Override public void copy(TableMetadata.Builder tagetTableBuilder, String targetKeyspace, String targetTableName, TableMetadata sourceTableMeta) { if (!StringUtils.isEmpty(sourceTableMeta.params.securityLabel)) tagetTableBuilder.securityLabel(sourceTableMeta.params.securityLabel); for (ColumnMetadata columnMetadata : sourceTableMeta.columns()) tagetTableBuilder.alterColumnSecurityLabel(columnMetadata.name, columnMetadata.securityLabel); } } } ``` ########## src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnTableStatement.java: ########## @@ -0,0 +1,150 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.QualifiedName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.schema.TableParams; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +/** + * Handles the execution of SECURITY LABEL ON TABLE CQL statements. + * <p> + * This statement allows users to add security classification labels to tables. + * Security labels are stored in the table metadata and displayed when using DESCRIBE TABLE. + * </p> + * <p> + * Syntax: {@code SECURITY LABEL ON TABLE [keyspace_name.]table_name IS 'label';} + * </p> + * <p> + * Security labels can be used to mark data sensitivity levels or compliance requirements. + * Labels can be removed by setting them to an empty string or null. + * </p> + * + * @see SecurityLabelOnKeyspaceStatement + * @see SecurityLabelOnColumnStatement + * @see SecurityLabelOnTypeStatement + */ +public final class SecurityLabelOnTableStatement extends AlterSchemaStatement +{ + private final String tableName; + private final String securityLabel; + private final String provider; + + public SecurityLabelOnTableStatement(String keyspaceName, String tableName, String securityLabel, String provider) + { + super(keyspaceName); + this.tableName = tableName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public void validate(ClientState state) + { + super.validate(state); + SchemaDescriptionsUtil.validateSecurityLabel(securityLabel); + } + + @Override + public Keyspaces apply(ClusterMetadata metadata) + { + Keyspaces schema = metadata.schema.getKeyspaces(); + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + + if (null == keyspace) + throw ire("Keyspace '%s' doesn't exist", keyspaceName); + + TableMetadata table = keyspace.getTableOrViewNullable(tableName); + if (null == table) + throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName); + + if (table.isView()) + throw ire("Cannot set security label on materialized view '%s.%s'. Security labels should be set on the base table.", keyspaceName, tableName); + + if (provider != null) + ClientWarn.instance.warn("Provider is not yet implemented but will proceed with adding the security label"); + + TableParams newParams = table.params.unbuild().securityLabel(securityLabel).build(); + TableMetadata newTable = table.withSwapped(newParams); + KeyspaceMetadata newKeyspace = keyspace.withSwapped(keyspace.tables.withSwapped(newTable)); + + return schema.withAddedOrUpdated(newKeyspace); + } + + @Override + SchemaChange schemaChangeEvent(KeyspacesDiff diff) + { + return new SchemaChange(Change.UPDATED, Target.TABLE, keyspaceName, tableName); + } + + @Override + public void authorize(ClientState client) + { + client.ensureTablePermission(keyspaceName, tableName, Permission.ALTER); + } + + @Override + public AuditLogContext getAuditLogContext() + { + return new AuditLogContext(AuditLogEntryType.SECURITY_LABEL_TABLE, keyspaceName, tableName); + } + + @Override + public String toString() + { + return String.format("%s (%s.%s, %s)", getClass().getSimpleName(), keyspaceName, tableName, securityLabel); + } + + public static final class Raw extends CQLStatement.Raw + { + private final QualifiedName tableName; + private final String securityLabel; + private final String provider; + + public Raw(QualifiedName tableName, String securityLabel, String provider) + { + this.tableName = tableName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public SecurityLabelOnTableStatement prepare(ClientState state) + { + String keyspaceName = tableName.hasKeyspace() ? tableName.getKeyspace() : state.getKeyspace(); + return new SecurityLabelOnTableStatement(keyspaceName, + tableName.getName(), + securityLabel, + provider); Review Comment: not aligned; please check the other files. I am not going to making more comments about alignment :p ########## src/java/org/apache/cassandra/cql3/statements/SchemaDescriptionsUtil.java: ########## @@ -0,0 +1,292 @@ +/* + * 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 org.apache.commons.lang3.StringUtils; + +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.TableMetadata; + +/** + * Utility class for appending COMMENT and SECURITY LABEL statements to schema element descriptions. + * <p> + * This class provides methods to append CQL statements for comments and security labels on various + * schema elements (keyspaces, tables, columns, and types) to their CREATE statement output. + * These are typically used by DESCRIBE statements to show the complete schema definition including + * any associated metadata. + * </p> + */ +public class SchemaDescriptionsUtil +{ + private static final String COMMENT_DESCRIPTION_TYPE = "COMMENT"; + private static final String SECURITY_LABEL_DESCRIPTION_TYPE = "SECURITY LABEL"; + private static final String KEYSPACE_SCHEMA_ELEMENT = "KEYSPACE"; + private static final String TABLE_SCHEMA_ELEMENT = "TABLE"; + private static final String COLUMN_SCHEMA_ELEMENT = "COLUMN"; + private static final String TYPE_SCHEMA_ELEMENT = "TYPE"; + private static final String FIELD_SCHEMA_ELEMENT = "FIELD"; + + /** + * Maximum length for comments and security labels. + * This limit helps prevent excessive memory usage and storage overhead. + */ + public static final int MAX_METADATA_LENGTH = 1024; Review Comment: 1024 is a lot for free-text. I think a label or comment length limit should be similar to Keyspace name, which is 48, see `org.apache.cassandra.schema.SchemaConstants#NAME_LENGTH`. If you are concerned with it being too short, maybe make it configurable and change the default to 48? Besides the length, the name `MAX_METADATA_LENGTH` is too general.. All the schema things are "metadata". Please rename. ########## src/java/org/apache/cassandra/cql3/statements/SchemaDescriptionsUtil.java: ########## @@ -0,0 +1,292 @@ +/* + * 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 org.apache.commons.lang3.StringUtils; + +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.TableMetadata; + +/** + * Utility class for appending COMMENT and SECURITY LABEL statements to schema element descriptions. + * <p> + * This class provides methods to append CQL statements for comments and security labels on various + * schema elements (keyspaces, tables, columns, and types) to their CREATE statement output. + * These are typically used by DESCRIBE statements to show the complete schema definition including + * any associated metadata. + * </p> + */ +public class SchemaDescriptionsUtil +{ + private static final String COMMENT_DESCRIPTION_TYPE = "COMMENT"; + private static final String SECURITY_LABEL_DESCRIPTION_TYPE = "SECURITY LABEL"; + private static final String KEYSPACE_SCHEMA_ELEMENT = "KEYSPACE"; + private static final String TABLE_SCHEMA_ELEMENT = "TABLE"; + private static final String COLUMN_SCHEMA_ELEMENT = "COLUMN"; + private static final String TYPE_SCHEMA_ELEMENT = "TYPE"; + private static final String FIELD_SCHEMA_ELEMENT = "FIELD"; + + /** + * Maximum length for comments and security labels. + * This limit helps prevent excessive memory usage and storage overhead. + */ + public static final int MAX_METADATA_LENGTH = 1024; + + private SchemaDescriptionsUtil() + { + } + + /** + * Validates that a comment does not exceed the maximum allowed length. + * + * @param comment the comment to validate (can be null) + * @throws InvalidRequestException if the comment exceeds the maximum length + */ + public static void validateComment(String comment) + { + if (comment != null && comment.length() > MAX_METADATA_LENGTH) + { + String msg = String.format("Comment length (%d) exceeds maximum allowed length (%d)", + comment.length(), + MAX_METADATA_LENGTH); + throw new InvalidRequestException(msg); + } + } + + /** + * Validates that a security label does not exceed the maximum allowed length. + * + * @param securityLabel the security label to validate (can be null) + * @throws InvalidRequestException if the security label exceeds the maximum length + */ + public static void validateSecurityLabel(String securityLabel) + { + if (securityLabel != null && securityLabel.length() > MAX_METADATA_LENGTH) + { + throw new InvalidRequestException(String.format("Security label length (%d) exceeds maximum allowed length (%d)", + securityLabel.length(), + MAX_METADATA_LENGTH)); + } + } + + /** + * Appends a COMMENT ON KEYSPACE statement to the builder if the keyspace has a comment. + * + * @param builder the StringBuilder to append to + * @param keyspaceMetadata the keyspace metadata containing the comment + */ + public static void appendCommentOnKeyspace(StringBuilder builder, KeyspaceMetadata keyspaceMetadata) + { + addDescription(builder, COMMENT_DESCRIPTION_TYPE, KEYSPACE_SCHEMA_ELEMENT, keyspaceMetadata.name, keyspaceMetadata.params.comment); + } + + /** + * Appends a SECURITY LABEL ON KEYSPACE statement to the builder if the keyspace has a security label. + * + * @param builder the StringBuilder to append to + * @param keyspaceMetadata the keyspace metadata containing the security label + */ + public static void appendSecurityLabelOnKeyspace(StringBuilder builder, KeyspaceMetadata keyspaceMetadata) + { + addDescription(builder, SECURITY_LABEL_DESCRIPTION_TYPE, KEYSPACE_SCHEMA_ELEMENT, keyspaceMetadata.name, keyspaceMetadata.params.securityLabel); + } + + /** + * Appends a COMMENT ON TABLE statement to the builder if the table has a comment. + * + * @param builder the StringBuilder to append to + * @param tableMetadata the table metadata containing the comment + */ + public static void appendCommentOnTable(StringBuilder builder, TableMetadata tableMetadata) + { + String schemaElement = String.format("%s.%s", tableMetadata.keyspace, tableMetadata.name); + addDescription(builder, COMMENT_DESCRIPTION_TYPE, TABLE_SCHEMA_ELEMENT, schemaElement, tableMetadata.params.comment); + } + + /** + * Appends SECURITY LABEL ON TABLE statement and COMMENT/SECURITY LABEL ON COLUMN statements + * to the builder if the table or any of its columns have security labels or comments. + * + * @param builder the StringBuilder to append to + * @param tableMetadata the table metadata containing the security label and columns + */ + public static void appendSecurityLabelOnTable(StringBuilder builder, TableMetadata tableMetadata) + { + String schemaElement = String.format("%s.%s", tableMetadata.keyspace, tableMetadata.name); + addDescription(builder, SECURITY_LABEL_DESCRIPTION_TYPE, TABLE_SCHEMA_ELEMENT, schemaElement, tableMetadata.params.securityLabel); Review Comment: From architecture's perspective, those should be interface methods in `SchemaElement` or a new interface that the relevant metadata classes implement, instead of having the utility methods per type. Two interface methods are needed, ``` void appendSecurityLabel(StringBuilder builder); void appendComment(StringBuilder builder); ``` ########## doc/modules/cassandra/pages/developing/cql/ddl.adoc: ########## @@ -801,3 +801,195 @@ statements. However, tables are the only object that can be truncated currently, and the `TABLE` keyword can be omitted. Truncating a table permanently removes all existing data from the table, but without removing the table itself. + +[[comment-statement]] +== COMMENT ON + +The `COMMENT ON` statement allows you to add descriptive comments to schema elements for documentation purposes. +Comments are stored in the schema metadata and displayed when using `DESCRIBE` statements. + +=== COMMENT ON KEYSPACE + +Add or modify a comment on a keyspace: + +[source,cql] +---- +COMMENT ON KEYSPACE keyspace_name IS 'comment text'; +COMMENT ON KEYSPACE keyspace_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON KEYSPACE cycling IS 'Keyspace for cycling application data'; +---- + +=== COMMENT ON TABLE + +Add or modify a comment on a table: + +[source,cql] +---- +COMMENT ON TABLE [keyspace_name.]table_name IS 'comment text'; +COMMENT ON TABLE [keyspace_name.]table_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON TABLE cycling.cyclist_name IS 'Table storing cyclist names and basic information'; +---- + +=== COMMENT ON COLUMN + +Add or modify a comment on a column: + +[source,cql] +---- +COMMENT ON COLUMN [keyspace_name.]table_name.column_name IS 'comment text'; +COMMENT ON COLUMN [keyspace_name.]table_name.column_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON COLUMN cycling.cyclist_name.id IS 'Unique identifier for each cyclist'; +---- + +=== COMMENT ON TYPE + +Add or modify a comment on a user-defined type: + +[source,cql] +---- +COMMENT ON TYPE [keyspace_name.]type_name IS 'comment text'; +COMMENT ON TYPE [keyspace_name.]type_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +COMMENT ON TYPE cycling.address IS 'User-defined type for storing address information'; +---- + +=== COMMENT ON FIELD + +Add or modify a comment on a field within a user-defined type: + +[source,cql] +---- +COMMENT ON FIELD [keyspace_name.]type_name.field_name IS 'comment text'; +COMMENT ON FIELD [keyspace_name.]type_name.field_name IS NULL; -- Remove comment +---- + +Example: + +[source,cql] +---- +include::cassandra:example$CQL/comment_on_field.cql[] +---- + +NOTE: Comments can be removed by setting them to `NULL`. Comments are displayed when using `DESCRIBE` statements +and are useful for documenting the purpose and structure of your schema elements. + +[[security-label-statement]] +== SECURITY LABEL ON + +The `SECURITY LABEL ON` statement allows you to add security classification labels to schema elements. +Security labels are stored in the schema metadata and displayed when using `DESCRIBE` statements. +These labels can be used to mark data sensitivity levels or compliance requirements. + +=== SECURITY LABEL ON KEYSPACE + +Add or modify a security label on a keyspace: + +[source,cql] +---- +SECURITY LABEL ON KEYSPACE keyspace_name IS 'label'; +SECURITY LABEL ON KEYSPACE keyspace_name IS NULL; -- Remove label +---- + +Example: + +[source,cql] +---- +SECURITY LABEL ON KEYSPACE cycling IS 'CONFIDENTIAL'; +---- + +=== SECURITY LABEL ON TABLE + +Add or modify a security label on a table: + +[source,cql] +---- +SECURITY LABEL ON TABLE [keyspace_name.]table_name IS 'label'; +SECURITY LABEL ON TABLE [keyspace_name.]table_name IS NULL; -- Remove label +---- + +Example: + +[source,cql] +---- +SECURITY LABEL ON TABLE cycling.cyclist_name IS 'PII'; +---- + +=== SECURITY LABEL ON COLUMN + +Add or modify a security label on a column: + +[source,cql] +---- +SECURITY LABEL ON COLUMN [keyspace_name.]table_name.column_name IS 'label'; +SECURITY LABEL ON COLUMN [keyspace_name.]table_name.column_name IS NULL; -- Remove label +---- + +Example: + +[source,cql] +---- +SECURITY LABEL ON COLUMN cycling.cyclist_name.email IS 'PII-EMAIL'; +---- + +=== SECURITY LABEL ON TYPE + +Add or modify a security label on a user-defined type: + +[source,cql] +---- +SECURITY LABEL ON TYPE [keyspace_name.]type_name IS 'label'; +SECURITY LABEL ON TYPE [keyspace_name.]type_name IS NULL; -- Remove label +---- + +Example: + +[source,cql] +---- +SECURITY LABEL ON TYPE cycling.address IS 'SENSITIVE'; +---- + +=== SECURITY LABEL ON FIELD + +Add or modify a security label on a field within a user-defined type: + +[source,cql] +---- +SECURITY LABEL ON FIELD [keyspace_name.]type_name.field_name IS 'label'; +SECURITY LABEL ON FIELD [keyspace_name.]type_name.field_name IS NULL; -- Remove label +---- + +Example: + +[source,cql] +---- +include::cassandra:example$CQL/security_label_on_field.cql[] +---- + +NOTE: Security labels can be removed by setting them to `NULL`. Security labels are displayed when using `DESCRIBE` statements +and can be used in conjunction with custom authorization plugins or audit systems to enforce data access policies. + +IMPORTANT: `COMMENT ON` and `SECURITY LABEL ON` statements require schema version V8 or higher. Review Comment: The `schema version V8` information is database-internal and not exposed to application developers or database users. Including it in a customer-facing document may lead to unnecessary confusion. You can probably mention the Cassandra version. But I guess it is unnecessary still. It is a new feature in Cassandra 6.0. ########## pylib/cqlshlib/test/test_cqlsh_completion.py: ########## @@ -238,7 +238,7 @@ def test_complete_in_insert(self): self.trycompletions('INSERT INTO twenty_rows_composite_table', immediate=' ') self.trycompletions('INSERT INTO twenty_rows_composite_table ', - choices=['(', 'JSON']) + choices=['(', 'JSON', 'COMMENT', 'SECURITY']) Review Comment: It is surprising to see `comment` and `security` in the insert statements. ########## src/java/org/apache/cassandra/cql3/statements/schema/CommentOnFieldStatement.java: ########## @@ -0,0 +1,145 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.FieldIdentifier; +import org.apache.cassandra.cql3.UTName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; + +/** + * Handles the execution of COMMENT ON FIELD CQL statements. + * <p> + * This statement allows users to add descriptive comments to individual fields of user-defined types + * for documentation purposes. Comments are stored in the type metadata and displayed when using DESCRIBE TYPE. + * </p> + * <p> + * Syntax: {@code COMMENT ON FIELD [keyspace_name.]type_name.field_name IS 'comment text';} + * </p> + * <p> + * Comments can be removed by setting them to an empty string or null. + * </p> + * + * @see CommentOnTypeStatement + * @see CommentOnColumnStatement + */ +public final class CommentOnFieldStatement extends AlterSchemaStatement Review Comment: nit: `CommentOnUserTypeFieldStatement`. Reason: field is not a term specific to user type, so better clarify in the name. 👍 on the excellent documentation. ########## src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java: ########## @@ -83,7 +86,8 @@ public CopyTableStatement(String sourceKeyspace, this.sourceTableName = sourceTableName; this.targetTableName = targetTableName; this.ifNotExists = ifNotExists; - this.createLikeOption = createLikeOption; + this.createLikeOptions = createLikeOptions != null ? + EnumSet.copyOf(createLikeOptions) : EnumSet.noneOf(CreateLikeOption.class); Review Comment: nit: codestyle for ternary operator ```suggestion this.createLikeOptions = createLikeOptions != null ? EnumSet.copyOf(createLikeOptions) : EnumSet.noneOf(CreateLikeOption.class); ``` ########## src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnColumnStatement.java: ########## @@ -0,0 +1,159 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.QualifiedName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +/** + * Handles the execution of SECURITY LABEL ON COLUMN CQL statements. + * <p> + * This statement allows users to add security classification labels to individual columns. + * Security labels are stored in the column metadata and displayed when using DESCRIBE TABLE. + * </p> + * <p> + * Syntax: {@code SECURITY LABEL ON COLUMN [keyspace_name.]table_name.column_name IS 'label';} + * </p> + * <p> + * Security labels can be used to mark data sensitivity levels or compliance requirements. + * Labels can be removed by setting them to an empty string or null. + * </p> + * + * @see SecurityLabelOnKeyspaceStatement + * @see SecurityLabelOnTableStatement + * @see SecurityLabelOnTypeStatement + */ +public final class SecurityLabelOnColumnStatement extends AlterSchemaStatement +{ + private final String tableName; + private final ColumnIdentifier columnName; + private final String securityLabel; + private final String provider; + + public SecurityLabelOnColumnStatement(String keyspaceName, String tableName, ColumnIdentifier columnName, String securityLabel, String provider) + { + super(keyspaceName); + this.tableName = tableName; + this.columnName = columnName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public void validate(ClientState state) + { + super.validate(state); + SchemaDescriptionsUtil.validateSecurityLabel(securityLabel); + } + + @Override + public Keyspaces apply(ClusterMetadata metadata) + { + Keyspaces schema = metadata.schema.getKeyspaces(); + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + + if (null == keyspace) + throw ire("Keyspace '%s' doesn't exist", keyspaceName); + + TableMetadata table = keyspace.getTableOrViewNullable(tableName); + if (null == table) + throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName); + + if (table.isView()) + throw ire("Cannot set security label on column in materialized view '%s.%s'. Security labels should be set on the base table.", keyspaceName, tableName); + + ColumnMetadata column = table.getColumn(columnName); + if (null == column) + throw ire("Column '%s' doesn't exist in table '%s.%s'", columnName, keyspaceName, tableName); + + if (provider != null) + ClientWarn.instance.warn("Provider is not yet implemented but will proceed with adding the security label"); Review Comment: The warning reads confusing. Can you clarify that no provider will be loaded into the process and will not be invoked for the security label? ########## src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnFieldStatement.java: ########## @@ -0,0 +1,155 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.FieldIdentifier; +import org.apache.cassandra.cql3.UTName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; + +/** + * Handles the execution of SECURITY LABEL ON FIELD CQL statements. + * <p> + * This statement allows users to add security classification labels to individual fields of user-defined types. + * Security labels are stored in the type metadata and displayed when using DESCRIBE TYPE. + * </p> + * <p> + * Syntax: {@code SECURITY LABEL ON FIELD [keyspace_name.]type_name.field_name IS 'label';} + * </p> + * <p> + * Security labels can be used to mark data sensitivity levels or compliance requirements. + * Labels can be removed by setting them to an empty string or null. + * </p> + * + * @see SecurityLabelOnTypeStatement + * @see SecurityLabelOnColumnStatement + */ +public final class SecurityLabelOnFieldStatement extends AlterSchemaStatement +{ + private final String typeName; + private final FieldIdentifier fieldName; + private final String securityLabel; + private final String provider; + + public SecurityLabelOnFieldStatement(String keyspaceName, String typeName, FieldIdentifier fieldName, String securityLabel, String provider) + { + super(keyspaceName); + this.typeName = typeName; + this.fieldName = fieldName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public void validate(ClientState state) + { + super.validate(state); + SchemaDescriptionsUtil.validateSecurityLabel(securityLabel); + } + + @Override + public Keyspaces apply(ClusterMetadata metadata) + { + Keyspaces schema = metadata.schema.getKeyspaces(); + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + + if (null == keyspace) + throw ire("Keyspace '%s' doesn't exist", keyspaceName); + + UserType type = keyspace.types.getNullable(bytes(typeName)); + if (null == type) + throw ire("Type '%s.%s' doesn't exist", keyspaceName, typeName); + + if (type.fieldPosition(fieldName) == -1) + throw ire("Field '%s' doesn't exist in type '%s.%s'", fieldName, keyspaceName, typeName); + + if (provider != null) + ClientWarn.instance.warn("Provider is not yet implemented but will proceed with adding the security label"); Review Comment: The code is duplicating. Could you have a base class for security labels to provides the common utilities? Similarly, please check for code duplication in comment statements. ########## src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnColumnStatement.java: ########## @@ -0,0 +1,159 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.QualifiedName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +/** + * Handles the execution of SECURITY LABEL ON COLUMN CQL statements. + * <p> + * This statement allows users to add security classification labels to individual columns. + * Security labels are stored in the column metadata and displayed when using DESCRIBE TABLE. + * </p> + * <p> + * Syntax: {@code SECURITY LABEL ON COLUMN [keyspace_name.]table_name.column_name IS 'label';} + * </p> + * <p> + * Security labels can be used to mark data sensitivity levels or compliance requirements. + * Labels can be removed by setting them to an empty string or null. + * </p> + * + * @see SecurityLabelOnKeyspaceStatement + * @see SecurityLabelOnTableStatement + * @see SecurityLabelOnTypeStatement + */ +public final class SecurityLabelOnColumnStatement extends AlterSchemaStatement +{ + private final String tableName; + private final ColumnIdentifier columnName; + private final String securityLabel; + private final String provider; Review Comment: Can you add a comment regarding `provider`? It is not functional, not implemented. ########## src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnColumnStatement.java: ########## @@ -0,0 +1,159 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.cql3.QualifiedName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +/** + * Handles the execution of SECURITY LABEL ON COLUMN CQL statements. + * <p> + * This statement allows users to add security classification labels to individual columns. + * Security labels are stored in the column metadata and displayed when using DESCRIBE TABLE. + * </p> + * <p> + * Syntax: {@code SECURITY LABEL ON COLUMN [keyspace_name.]table_name.column_name IS 'label';} + * </p> + * <p> + * Security labels can be used to mark data sensitivity levels or compliance requirements. + * Labels can be removed by setting them to an empty string or null. + * </p> + * + * @see SecurityLabelOnKeyspaceStatement + * @see SecurityLabelOnTableStatement + * @see SecurityLabelOnTypeStatement + */ +public final class SecurityLabelOnColumnStatement extends AlterSchemaStatement +{ + private final String tableName; + private final ColumnIdentifier columnName; + private final String securityLabel; + private final String provider; + + public SecurityLabelOnColumnStatement(String keyspaceName, String tableName, ColumnIdentifier columnName, String securityLabel, String provider) + { + super(keyspaceName); + this.tableName = tableName; + this.columnName = columnName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public void validate(ClientState state) + { + super.validate(state); + SchemaDescriptionsUtil.validateSecurityLabel(securityLabel); + } + + @Override + public Keyspaces apply(ClusterMetadata metadata) + { + Keyspaces schema = metadata.schema.getKeyspaces(); + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + + if (null == keyspace) + throw ire("Keyspace '%s' doesn't exist", keyspaceName); + + TableMetadata table = keyspace.getTableOrViewNullable(tableName); + if (null == table) + throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName); + + if (table.isView()) + throw ire("Cannot set security label on column in materialized view '%s.%s'. Security labels should be set on the base table.", keyspaceName, tableName); + + ColumnMetadata column = table.getColumn(columnName); + if (null == column) + throw ire("Column '%s' doesn't exist in table '%s.%s'", columnName, keyspaceName, tableName); + + if (provider != null) + ClientWarn.instance.warn("Provider is not yet implemented but will proceed with adding the security label"); + + TableMetadata newTable = table.unbuild().alterColumnSecurityLabel(columnName, securityLabel).build(); + KeyspaceMetadata newKeyspace = keyspace.withSwapped(keyspace.tables.withSwapped(newTable)); + + return schema.withAddedOrUpdated(newKeyspace); + } + + @Override + SchemaChange schemaChangeEvent(KeyspacesDiff diff) + { + return new SchemaChange(Change.UPDATED, Target.TABLE, keyspaceName, tableName); + } + + @Override + public void authorize(ClientState client) + { + client.ensureTablePermission(keyspaceName, tableName, Permission.ALTER); + } + + @Override + public AuditLogContext getAuditLogContext() + { + return new AuditLogContext(AuditLogEntryType.SECURITY_LABEL_COLUMN, keyspaceName, tableName); + } + + @Override + public String toString() + { + return String.format("%s (%s.%s.%s, %s)", getClass().getSimpleName(), keyspaceName, tableName, columnName, securityLabel); + } + + public static final class Raw extends CQLStatement.Raw + { + private final QualifiedName tableName; + private final ColumnIdentifier columnName; + private final String securityLabel; + private final String provider; + + public Raw(QualifiedName tableName, ColumnIdentifier columnName, String securityLabel, String provider) + { + this.tableName = tableName; + this.columnName = columnName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public SecurityLabelOnColumnStatement prepare(ClientState state) + { + String keyspaceName = tableName.hasKeyspace() ? tableName.getKeyspace() : state.getKeyspace(); + return new SecurityLabelOnColumnStatement(keyspaceName, + tableName.getName(), + columnName, + securityLabel, + provider); + } Review Comment: lines are not aligned ########## src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnTypeStatement.java: ########## @@ -0,0 +1,147 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.UTName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; + +/** + * Handles the execution of SECURITY LABEL ON TYPE CQL statements. + * <p> + * This statement allows users to add security classification labels to user-defined types. + * Security labels are stored in the type metadata and displayed when using DESCRIBE TYPE. + * </p> + * <p> + * Syntax: {@code SECURITY LABEL ON TYPE [keyspace_name.]type_name IS 'label';} + * </p> + * <p> + * Security labels can be used to mark data sensitivity levels or compliance requirements. + * Labels can be removed by setting them to an empty string or null. + * </p> + * + * @see SecurityLabelOnKeyspaceStatement + * @see SecurityLabelOnTableStatement + * @see SecurityLabelOnColumnStatement + */ +public final class SecurityLabelOnTypeStatement extends AlterSchemaStatement +{ + private final String typeName; + private final String securityLabel; + private final String provider; + + public SecurityLabelOnTypeStatement(String keyspaceName, String typeName, String securityLabel, String provider) + { + super(keyspaceName); + this.typeName = typeName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public void validate(ClientState state) + { + super.validate(state); + SchemaDescriptionsUtil.validateSecurityLabel(securityLabel); + } + + @Override + public Keyspaces apply(ClusterMetadata metadata) + { + Keyspaces schema = metadata.schema.getKeyspaces(); + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + + if (null == keyspace) + throw ire("Keyspace '%s' doesn't exist", keyspaceName); + + UserType type = keyspace.types.getNullable(bytes(typeName)); + if (null == type) + throw ire("Type '%s.%s' doesn't exist", keyspaceName, typeName); + + if (provider != null) + ClientWarn.instance.warn("Provider is not yet implemented but will proceed with adding the security label"); + + UserType newType = type.withSecurityLabel(securityLabel); + KeyspaceMetadata newKeyspace = keyspace.withSwapped(keyspace.types.withUpdatedUserType(newType)); + + return schema.withAddedOrUpdated(newKeyspace); + } + + @Override + SchemaChange schemaChangeEvent(KeyspacesDiff diff) + { + return new SchemaChange(Change.UPDATED, Target.TYPE, keyspaceName, typeName); + } + + @Override + public void authorize(ClientState client) + { + client.ensureKeyspacePermission(keyspaceName, Permission.ALTER); Review Comment: I think the permission scope should be similar to create user type. It should be the following. Please also check the other security label and comment statements. ``` client.ensureAllTablesPermission(...) ``` ########## src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java: ########## @@ -618,6 +619,69 @@ public String toCqlString(boolean withWarnings, boolean withInternals, boolean i }; } + /** + * Wraps a schema element to append its associated comments and security labels. + * <p> + * This method creates a wrapper around schema elements (keyspaces, tables, types) that + * augments their CQL string representation with any defined comments or security labels. + * The wrapper delegates all SchemaElement interface methods to the wrapped element, + * but overrides toCqlString() to append COMMENT and SECURITY LABEL statements. + * </p> + * + * @param element the schema element to wrap + * @return a wrapped schema element that includes comments and security labels in its CQL output + */ + private static SchemaElement descriptions(SchemaElement element) + { + return new SchemaElement() + { + @Override + public SchemaElementType elementType() + { + return element.elementType(); + } + + @Override + public String elementKeyspace() + { + return element.elementKeyspace(); + } + + @Override + public String elementName() + { + return element.elementName(); + } + + @Override + public String toCqlString(boolean withWarnings, boolean withInternals, boolean ifNotExists) + { + String baseStatement = element.toCqlString(withWarnings, withInternals, ifNotExists); + StringBuilder result = new StringBuilder(baseStatement); + if (element instanceof KeyspaceMetadata) + { + KeyspaceMetadata ksm = (KeyspaceMetadata) element; + SchemaDescriptionsUtil.appendCommentOnKeyspace(result, ksm); + SchemaDescriptionsUtil.appendSecurityLabelOnKeyspace(result, ksm); + } + else if (element instanceof TableMetadata) + { + TableMetadata tbm = (TableMetadata) element; + SchemaDescriptionsUtil.appendCommentOnTable(result, tbm); + SchemaDescriptionsUtil.appendSecurityLabelOnTable(result, tbm); + } + else if (element instanceof UserType) + { + UserType userType = (UserType) element; + SchemaDescriptionsUtil.appendCommentOnType(result, userType); + SchemaDescriptionsUtil.appendSecurityLabelOnType(result, userType); + } Review Comment: Hmmm... why comparing the types.. Could you move the handling to be part of each `SchemaElement` concrete implementations? ########## src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnFieldStatement.java: ########## @@ -0,0 +1,155 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.FieldIdentifier; +import org.apache.cassandra.cql3.UTName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; + +/** + * Handles the execution of SECURITY LABEL ON FIELD CQL statements. + * <p> + * This statement allows users to add security classification labels to individual fields of user-defined types. + * Security labels are stored in the type metadata and displayed when using DESCRIBE TYPE. + * </p> + * <p> + * Syntax: {@code SECURITY LABEL ON FIELD [keyspace_name.]type_name.field_name IS 'label';} + * </p> + * <p> + * Security labels can be used to mark data sensitivity levels or compliance requirements. + * Labels can be removed by setting them to an empty string or null. + * </p> + * + * @see SecurityLabelOnTypeStatement + * @see SecurityLabelOnColumnStatement + */ +public final class SecurityLabelOnFieldStatement extends AlterSchemaStatement +{ + private final String typeName; + private final FieldIdentifier fieldName; + private final String securityLabel; + private final String provider; + + public SecurityLabelOnFieldStatement(String keyspaceName, String typeName, FieldIdentifier fieldName, String securityLabel, String provider) + { + super(keyspaceName); + this.typeName = typeName; + this.fieldName = fieldName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public void validate(ClientState state) + { + super.validate(state); + SchemaDescriptionsUtil.validateSecurityLabel(securityLabel); + } + + @Override + public Keyspaces apply(ClusterMetadata metadata) + { + Keyspaces schema = metadata.schema.getKeyspaces(); + KeyspaceMetadata keyspace = schema.getNullable(keyspaceName); + + if (null == keyspace) + throw ire("Keyspace '%s' doesn't exist", keyspaceName); + + UserType type = keyspace.types.getNullable(bytes(typeName)); + if (null == type) + throw ire("Type '%s.%s' doesn't exist", keyspaceName, typeName); + + if (type.fieldPosition(fieldName) == -1) + throw ire("Field '%s' doesn't exist in type '%s.%s'", fieldName, keyspaceName, typeName); + + if (provider != null) + ClientWarn.instance.warn("Provider is not yet implemented but will proceed with adding the security label"); + + UserType newType = type.withFieldSecurityLabel(fieldName, securityLabel); + KeyspaceMetadata newKeyspace = keyspace.withSwapped(keyspace.types.withUpdatedUserType(newType)); + + return schema.withAddedOrUpdated(newKeyspace); + } + + @Override + SchemaChange schemaChangeEvent(KeyspacesDiff diff) + { + return new SchemaChange(Change.UPDATED, Target.TYPE, keyspaceName, typeName); + } + + @Override + public void authorize(ClientState client) + { + client.ensureKeyspacePermission(keyspaceName, Permission.ALTER); + } + + @Override + public AuditLogContext getAuditLogContext() + { + return new AuditLogContext(AuditLogEntryType.SECURITY_LABEL_TYPE, keyspaceName, typeName); + } + + @Override + public String toString() + { + return String.format("%s (%s.%s.%s, %s)", getClass().getSimpleName(), keyspaceName, typeName, fieldName, securityLabel); + } + + public static final class Raw extends CQLStatement.Raw + { + private final UTName typeName; + private final FieldIdentifier fieldName; + private final String securityLabel; + private final String provider; + + public Raw(UTName typeName, FieldIdentifier fieldName, String securityLabel, String provider) + { + this.typeName = typeName; + this.fieldName = fieldName; + this.securityLabel = securityLabel; + this.provider = provider; + } + + @Override + public SecurityLabelOnFieldStatement prepare(ClientState state) + { + String keyspaceName = typeName.hasKeyspace() ? typeName.getKeyspace() : state.getKeyspace(); + return new SecurityLabelOnFieldStatement(keyspaceName, + typeName.getStringTypeName(), + fieldName, + securityLabel, + provider); Review Comment: not aligned. ########## src/java/org/apache/cassandra/db/marshal/UserType.java: ########## @@ -384,15 +412,19 @@ private boolean equalsWithoutTypes(UserType other) return name.equals(other.name) && fieldNames.equals(other.fieldNames) && keyspace.equals(other.keyspace) - && isMultiCell == other.isMultiCell; + && isMultiCell == other.isMultiCell + && comment.equals(other.comment) + && securityLabel.equals(other.securityLabel); Review Comment: Similar reasoning with `ColumnSpec`, I do not think comment and securityLabel should be part of equals. ########## src/java/org/apache/cassandra/db/virtual/SchemaCommentsTable.java: ########## @@ -0,0 +1,91 @@ +/* + * 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.db.virtual; + +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.db.marshal.UserType; + +/** + * Virtual table that exposes all comments on schema elements. + * <p> + * This table provides a unified view of documentation metadata across: + * <ul> + * <li>Keyspaces - comments on keyspaces</li> + * <li>Tables - comments on tables</li> + * <li>Columns - comments on columns</li> + * <li>User-Defined Types (UDTs) - comments on UDTs</li> + * </ul> + * </p> + * <p> + * The table automatically reflects the current state of schema metadata without requiring + * explicit updates. Data is read directly from {@link org.apache.cassandra.schema.Schema#instance} on each query. + * </p> + * <p> + * Example queries: + * <pre> + * -- All comments in the system + * SELECT * FROM system_views.schema_comments; + * + * -- All table comments in a specific keyspace + * SELECT * FROM system_views.schema_comments + * WHERE object_type = 'TABLE' AND keyspace_name = 'my_keyspace'; + * + * -- All column comments across all keyspaces + * SELECT * FROM system_views.schema_comments WHERE object_type = 'COLUMN'; Review Comment: Partition key is `(object_type, keyspace_name)`, I do not think this query would work, although I have not tried. Please double check and update the javadoc (here and in SchemaSecurityLabelsTable) if needed. ########## src/java/org/apache/cassandra/cql3/statements/schema/CommentOnTypeStatement.java: ########## @@ -0,0 +1,137 @@ +/* + * 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.schema; + +import org.apache.cassandra.audit.AuditLogContext; +import org.apache.cassandra.audit.AuditLogEntryType; +import org.apache.cassandra.auth.Permission; +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.UTName; +import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil; +import org.apache.cassandra.db.marshal.UserType; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Keyspaces; +import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.tcm.ClusterMetadata; +import org.apache.cassandra.transport.Event.SchemaChange; +import org.apache.cassandra.transport.Event.SchemaChange.Change; +import org.apache.cassandra.transport.Event.SchemaChange.Target; + +import static org.apache.cassandra.utils.ByteBufferUtil.bytes; + +/** + * Handles the execution of COMMENT ON TYPE CQL statements. + * <p> + * This statement allows users to add descriptive comments to user-defined types for documentation purposes. + * Comments are stored in the type metadata and displayed when using DESCRIBE TYPE. + * </p> + * <p> + * Syntax: {@code COMMENT ON TYPE [keyspace_name.]type_name IS 'comment text';} + * </p> + * <p> + * Comments can be removed by setting them to an empty string or null. + * </p> + * + * @see CommentOnKeyspaceStatement + * @see CommentOnTableStatement + * @see CommentOnColumnStatement + */ +public final class CommentOnTypeStatement extends AlterSchemaStatement Review Comment: nit: `CommentOnUserTypeStatement`, user type is the more common term ########## src/java/org/apache/cassandra/db/virtual/AbstractSchemaMetadataTable.java: ########## @@ -0,0 +1,311 @@ +/* + * 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.db.virtual; + +import org.apache.cassandra.db.DecoratedKey; +import org.apache.cassandra.db.marshal.CompositeType; +import org.apache.cassandra.db.marshal.UTF8Type; +import org.apache.cassandra.dht.LocalPartitioner; +import org.apache.cassandra.exceptions.InvalidRequestException; +import org.apache.cassandra.schema.ColumnMetadata; +import org.apache.cassandra.schema.KeyspaceMetadata; +import org.apache.cassandra.schema.Schema; +import org.apache.cassandra.schema.SchemaProvider; +import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.db.marshal.UserType; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import com.google.common.base.Strings; + +/** + * Abstract base class for virtual tables that expose metadata on schema elements. + * <p> + * This class provides a unified implementation for tables that expose metadata across: + * <ul> + * <li>Keyspaces - metadata on keyspaces</li> + * <li>Tables - metadata on tables</li> + * <li>Columns - metadata on columns</li> + * <li>User-Defined Types (UDTs) - metadata on UDTs</li> + * </ul> + * </p> + * <p> + * Subclasses must implement methods to extract the specific metadata field + * (e.g., comment, security label) from each schema element type. + * </p> + */ +abstract class AbstractSchemaMetadataTable extends AbstractVirtualTable +{ + /* + As clustering keys cannot be null, using an EMPTY_VALUE in the results for non-existent + columns in query results. + */ + private static final String EMPTY_VALUE = ""; + + private static final String OBJECT_TYPE = "object_type"; + private static final String KEYSPACE_NAME = "keyspace_name"; + private static final String TABLE_NAME = "table_name"; + private static final String COLUMN_NAME = "column_name"; + private static final String UDT_NAME = "udt_name"; + private static final String FIELD_NAME = "field_name"; + + private final SchemaTableType schemaTableType; + + /** + * Schema object types that can have metadata. + */ + protected enum ObjectType + { + KEYSPACE, + TABLE, + COLUMN, + UDT, + FIELD; + + @Override + public String toString() + { + return name(); + } + + /** + * Parse object type string to enum. + * @param objectType the string representation of the object type + * @return ObjectType enum value or null if invalid + */ + static ObjectType parse(String objectType) + { + try + { + return ObjectType.valueOf(objectType); + } + catch (IllegalArgumentException e) + { + return null; + } + } + } + + protected enum SchemaTableType + { + COMMENT("schema_comments", "comment"), + SECURITY_LABEL("schema_security_labels", "security_label"); + + private final String tableName; + private final String columnName; + + SchemaTableType(String tableName, String columnName) + { + this.tableName = tableName; + this.columnName = columnName; + } + } + + protected AbstractSchemaMetadataTable(String keyspace, SchemaTableType schemaTableType) + { + super(TableMetadata.builder(keyspace, schemaTableType.tableName) + .kind(TableMetadata.Kind.VIRTUAL) + .partitioner(new LocalPartitioner(CompositeType.getInstance(UTF8Type.instance, UTF8Type.instance))) + .addPartitionKeyColumn(OBJECT_TYPE, UTF8Type.instance) + .addPartitionKeyColumn(KEYSPACE_NAME, UTF8Type.instance) + .addClusteringColumn(TABLE_NAME, UTF8Type.instance) + .addClusteringColumn(COLUMN_NAME, UTF8Type.instance) + .addClusteringColumn(UDT_NAME, UTF8Type.instance) + .addClusteringColumn(FIELD_NAME, UTF8Type.instance) + .addRegularColumn(schemaTableType.columnName, UTF8Type.instance) + .build()); + this.schemaTableType = schemaTableType; + } + + /** + * Extract metadata from a keyspace. + * @param keyspace the keyspace metadata + * @return the metadata value, or null if not present + */ + protected abstract String extractKeyspaceMetadata(KeyspaceMetadata keyspace); + + /** + * Extract metadata from a table. + * @param table the table metadata + * @return the metadata value, or null if not present + */ + protected abstract String extractTableMetadata(TableMetadata table); + + /** + * Extract metadata from a column. + * @param column the column metadata + * @return the metadata value, or null if not present + */ + protected abstract String extractColumnMetadata(ColumnMetadata column); + + /** + * Extract metadata from a UDT. + * @param udt the user-defined type + * @return the metadata value, or null if not present + */ + protected abstract String extractUdtMetadata(UserType udt); + + /** + * Extract metadata from a UDT field. + * @param udt the user-defined type + * @param fieldName the field name + * @return the metadata value, or null if not present + */ + protected abstract String extractFieldMetadata(UserType udt, String fieldName); + + @Override + public DataSet data() + { + SimpleDataSet result = new SimpleDataSet(metadata()); + SchemaProvider schemaProvider = Schema.instance; + + for (String keyspaceName : schemaProvider.getKeyspaces()) + { + KeyspaceMetadata keyspace = schemaProvider.getKeyspaceMetadata(keyspaceName); + if (keyspace == null) + { + continue; + } + addKeyspaceRow(result, keyspace); + + for (TableMetadata table : keyspace.tables) + { + addTableRow(result, keyspace, table); + + for (ColumnMetadata column : table.columns()) + addColumnRow(result, keyspace, table, column); + } + + for (UserType udt : keyspace.types) + { + addUdtRow(result, keyspace, udt); + + for (org.apache.cassandra.cql3.FieldIdentifier field : udt.fieldNames()) Review Comment: Why using the full qualified name here?! ########## src/java/org/apache/cassandra/cql3/ColumnSpecification.java: ########## @@ -86,12 +97,14 @@ public boolean equals(Object other) return this.ksName.equals(that.ksName) && this.cfName.equals(that.cfName) && this.name.equals(that.name) && - this.type.equals(that.type); + this.type.equals(that.type) && + Objects.equal(this.comment, that.comment) && + Objects.equal(this.securityLabel, that.securityLabel); } public int hashCode() { - return Objects.hashCode(ksName, cfName, name, type); + return Objects.hashCode(ksName, cfName, name, type, comment, securityLabel); Review Comment: I do not think `comment` and `securirtyLabel` should be part of hashcode and equals for `ColumnSpecification`. Each column should be uniquely identified by its name already. FYI, those 2 methods in `ColumnSpecification` will only be examined in `SelectionColumnMapping` where colSpec is used as key. ########## src/java/org/apache/cassandra/db/marshal/UserType.java: ########## @@ -384,15 +412,19 @@ private boolean equalsWithoutTypes(UserType other) return name.equals(other.name) && fieldNames.equals(other.fieldNames) && keyspace.equals(other.keyspace) - && isMultiCell == other.isMultiCell; + && isMultiCell == other.isMultiCell + && comment.equals(other.comment) + && securityLabel.equals(other.securityLabel); } public boolean equalsWithOutKs(UserType other) { return name.equals(other.name) && fieldNames.equals(other.fieldNames) && types.equals(other.types) - && isMultiCell == other.isMultiCell; + && isMultiCell == other.isMultiCell + && comment.equals(other.comment) + && securityLabel.equals(other.securityLabel); Review Comment: The method is used in table-copy feature. Operators have to create the UDT with the same comments and securtiy label first in order for the table-copy to be successful. It is surprising since table-copy allows to avoid copying the comments and the security labels. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]

