From b11633be4f3ee4b7aa5096dc7afdcc86accc2deb Mon Sep 17 00:00:00 2001 From: jkonisa Date: Fri, 3 Oct 2025 15:15:42 -0700 Subject: [PATCH] CASSANDRA-20943 Introducing comments and security labels for schema elements --- CHANGES.txt | 1 + conf/cassandra.yaml | 8 + .../cassandra/pages/developing/cql/ddl.adoc | 189 ++++++ pylib/cqlshlib/cql3handling.py | 36 ++ pylib/cqlshlib/test/test_cqlsh_completion.py | 12 +- src/antlr/Lexer.g | 9 + src/antlr/Parser.g | 125 +++- .../cassandra/audit/AuditLogEntryType.java | 8 + .../org/apache/cassandra/config/Config.java | 3 + .../cassandra/config/DatabaseDescriptor.java | 10 + .../cassandra/cql3/ColumnSpecification.java | 15 +- .../apache/cassandra/cql3/SchemaElement.java | 19 + .../cql3/statements/DescribeStatement.java | 4 +- .../statements/SchemaDescriptionsUtil.java | 273 +++++++++ .../schema/CommentOnColumnStatement.java | 129 ++++ .../schema/CommentOnKeyspaceStatement.java | 108 ++++ .../schema/CommentOnTableStatement.java | 122 ++++ .../CommentOnUserTypeFieldStatement.java | 126 ++++ .../schema/CommentOnUserTypeStatement.java | 119 ++++ .../statements/schema/CopyTableStatement.java | 189 ++++-- .../schema/SchemaDescriptionStatement.java | 145 +++++ .../SecurityLabelOnColumnStatement.java | 161 +++++ .../SecurityLabelOnKeyspaceStatement.java | 140 +++++ .../schema/SecurityLabelOnTableStatement.java | 152 +++++ ...SecurityLabelOnUserTypeFieldStatement.java | 157 +++++ .../SecurityLabelOnUserTypeStatement.java | 148 +++++ .../apache/cassandra/db/marshal/UserType.java | 101 +++- .../virtual/AbstractSchemaMetadataTable.java | 312 ++++++++++ .../db/virtual/SchemaCommentsTable.java | 92 +++ .../db/virtual/SchemaSecurityLabelsTable.java | 92 +++ .../db/virtual/SystemViewsKeyspace.java | 2 + .../cassandra/schema/ColumnMetadata.java | 66 ++- .../cassandra/schema/KeyspaceMetadata.java | 11 + .../cassandra/schema/KeyspaceParams.java | 57 +- .../cassandra/schema/TableMetadata.java | 45 +- .../apache/cassandra/schema/TableParams.java | 27 +- .../org/apache/cassandra/schema/Types.java | 165 +++++- .../cassandra/tcm/membership/NodeVersion.java | 2 +- .../cassandra/tcm/serialization/Version.java | 5 + .../statements/DescribeStatementTest.java | 189 +++++- .../CommentAndSecurityLabelTest.java | 558 ++++++++++++++++++ .../db/virtual/SchemaMetadataTableTest.java | 336 +++++++++++ .../apache/cassandra/schema/MockSchema.java | 20 +- .../SchemaMetadataSerializationTest.java | 290 +++++++++ .../schema/createlike/CreateLikeTest.java | 79 ++- .../createlike/CreateLikeWithSessionTest.java | 2 +- ...rtImportListCompressionDictionaryTest.java | 8 +- 47 files changed, 4749 insertions(+), 118 deletions(-) create mode 100644 src/java/org/apache/cassandra/cql3/statements/SchemaDescriptionsUtil.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/CommentOnColumnStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/CommentOnKeyspaceStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/CommentOnTableStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeFieldStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/SchemaDescriptionStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnColumnStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnKeyspaceStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnTableStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeFieldStatement.java create mode 100644 src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeStatement.java create mode 100644 src/java/org/apache/cassandra/db/virtual/AbstractSchemaMetadataTable.java create mode 100644 src/java/org/apache/cassandra/db/virtual/SchemaCommentsTable.java create mode 100644 src/java/org/apache/cassandra/db/virtual/SchemaSecurityLabelsTable.java create mode 100644 test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CommentAndSecurityLabelTest.java create mode 100644 test/unit/org/apache/cassandra/db/virtual/SchemaMetadataTableTest.java create mode 100644 test/unit/org/apache/cassandra/schema/SchemaMetadataSerializationTest.java diff --git a/CHANGES.txt b/CHANGES.txt index 5c22973ed0..06b33e9846 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Introducing comments and security labels for schema elements (CASSANDRA-20943) * Extend nodetool tablestats for dictionary memory usage (CASSANDRA-20940) * Introduce separate GCInspector thresholds for concurrent GC events (CASSANDRA-20980) * Reduce contention in MemtableAllocator.allocate (CASSANDRA-20226) diff --git a/conf/cassandra.yaml b/conf/cassandra.yaml index 20476ee547..106f5e01e2 100644 --- a/conf/cassandra.yaml +++ b/conf/cassandra.yaml @@ -2610,6 +2610,14 @@ drop_compact_storage_enabled: false # Defaults to true, which means that reconfiguration of password validator via JMX is possible. #password_policy_reconfiguration_enabled: true +# Maximum allowed length for comments on schema elements (keyspaces, tables, columns, types, type fields). +# Comments exceeding this length will be rejected. Defaults to 128 characters. +max_comment_length: 128 + +# Maximum allowed length for security labels on schema elements (tables, columns). +# Security labels exceeding this length will be rejected. Defaults to 48 characters. +max_security_label_length: 48 + # Guardrail to enable a CREATE or ALTER TABLE statement when default_time_to_live is set to 0 # and the table is using TimeWindowCompactionStrategy compaction or a subclass of it. # It is suspicious to use default_time_to_live set to 0 with such compaction strategy. diff --git a/doc/modules/cassandra/pages/developing/cql/ddl.adoc b/doc/modules/cassandra/pages/developing/cql/ddl.adoc index d771f4e2bc..6b42eabc5f 100644 --- a/doc/modules/cassandra/pages/developing/cql/ddl.adoc +++ b/doc/modules/cassandra/pages/developing/cql/ddl.adoc @@ -803,3 +803,192 @@ 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 + +The `COMMENT` 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] +---- +COMMENT ON FIELD cycling.address.street IS 'Street address line'; +---- + +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 + +The `SECURITY LABEL` 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] +---- +SECURITY LABEL ON FIELD cycling.personal_info.ssn IS 'PII-SSN'; +---- + +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. diff --git a/pylib/cqlshlib/cql3handling.py b/pylib/cqlshlib/cql3handling.py index 4587727d97..a83c6535b8 100644 --- a/pylib/cqlshlib/cql3handling.py +++ b/pylib/cqlshlib/cql3handling.py @@ -299,6 +299,14 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; | | | + | + | + | + | + | + | + | + | ; ::= @@ -402,6 +410,8 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ; ; ::= | "INDEXES" + | "COMMENTS" + | "SECURITY" "LABELS" ; ''' @@ -1593,6 +1603,32 @@ syntax_rules += r''' ; ''' +syntax_rules += r''' + ::= "COMMENT" "ON" "KEYSPACE" ks= "IS" comment=( | "NULL" ) + ; + + ::= "COMMENT" "ON" wat=( "COLUMNFAMILY" | "TABLE" ) cf= "IS" comment=( | "NULL" ) + ; + + ::= "COMMENT" "ON" "COLUMN" cf= dot="." col= "IS" comment=( | "NULL" ) + ; + + ::= "COMMENT" "ON" "TYPE" ut= "IS" comment=( | "NULL" ) + ; + + ::= "SECURITY" "LABEL" "ON" "KEYSPACE" ks= "IS" label=( | "NULL" ) + ; + + ::= "SECURITY" "LABEL" "ON" wat=( "COLUMNFAMILY" | "TABLE" ) cf= "IS" label=( | "NULL" ) + ; + + ::= "SECURITY" "LABEL" "ON" "COLUMN" cf= dot="." col= "IS" label=( | "NULL" ) + ; + + ::= "SECURITY" "LABEL" "ON" "TYPE" ut= "IS" label=( | "NULL" ) + ; +''' + syntax_rules += r''' ::= name=( | ) ; diff --git a/pylib/cqlshlib/test/test_cqlsh_completion.py b/pylib/cqlshlib/test/test_cqlsh_completion.py index 44650ae074..aa7904471b 100644 --- a/pylib/cqlshlib/test/test_cqlsh_completion.py +++ b/pylib/cqlshlib/test/test_cqlsh_completion.py @@ -166,10 +166,10 @@ class TestCqlshCompletion(CqlshCompletionCase): cqlver = '3.1.6' def test_complete_on_empty_string(self): - self.trycompletions('', choices=('?', 'ADD', 'ALTER', 'BEGIN', 'CAPTURE', 'CONSISTENCY', + self.trycompletions('', choices=('?', 'ADD', 'ALTER', 'BEGIN', 'CAPTURE', 'COMMENT', 'CONSISTENCY', 'COPY', 'CREATE', 'DEBUG', 'DELETE', 'DESC', 'DESCRIBE', 'DROP', 'GRANT', 'HELP', 'INSERT', 'LIST', 'LOGIN', 'PAGING', 'REVOKE', - 'SELECT', 'SHOW', 'SOURCE', 'TRACING', 'ELAPSED', 'EXPAND', 'SERIAL', 'TRUNCATE', + 'SECURITY', 'SELECT', 'SHOW', 'SOURCE', 'TRACING', 'ELAPSED', 'EXPAND', 'SERIAL', 'TRUNCATE', 'UPDATE', 'USE', 'exit', 'quit', 'CLEAR', 'CLS', 'history')) def test_complete_command_words(self): @@ -288,10 +288,10 @@ class TestCqlshCompletion(CqlshCompletionCase): self.trycompletions( ("INSERT INTO twenty_rows_composite_table (a, b, c) " "VALUES ( 'eggs', 'sausage', 'spam');"), - choices=['?', 'ADD', 'ALTER', 'BEGIN', 'CAPTURE', 'CONSISTENCY', 'COPY', + choices=['?', 'ADD', 'ALTER', 'BEGIN', 'CAPTURE', 'COMMENT', 'CONSISTENCY', 'COPY', 'CREATE', 'DEBUG', 'DELETE', 'DESC', 'DESCRIBE', 'DROP', 'ELAPSED', 'EXPAND', 'GRANT', 'HELP', 'INSERT', 'LIST', 'LOGIN', 'PAGING', - 'REVOKE', 'SELECT', 'SHOW', 'SOURCE', 'SERIAL', 'TRACING', + 'REVOKE', 'SECURITY', 'SELECT', 'SHOW', 'SOURCE', 'SERIAL', 'TRACING', 'TRUNCATE', 'UPDATE', 'USE', 'exit', 'history', 'quit', 'CLEAR', 'CLS']) @@ -856,7 +856,7 @@ class TestCqlshCompletion(CqlshCompletionCase): 'min_index_interval', 'speculative_retry', 'additional_write_policy', 'cdc', 'read_repair', - 'INDEXES']) + 'INDEXES', 'COMMENTS', 'SECURITY']) self.trycompletions('CREATE TABLE new_table LIKE old_table WITH INDEXES ', choices=[';' , '=', 'AND']) self.trycompletions('CREATE TABLE ' + 'new_table LIKE old_table WITH bloom_filter_fp_chance ', @@ -909,7 +909,7 @@ class TestCqlshCompletion(CqlshCompletionCase): 'min_index_interval', 'speculative_retry', 'additional_write_policy', 'cdc', 'read_repair', - 'INDEXES']) + 'INDEXES', 'COMMENTS', 'SECURITY']) self.trycompletions('CREATE TABLE ' + "new_table LIKE old_table WITH compaction = " + "{'class': 'TimeWindowCompactionStrategy', '", choices=['compaction_window_unit', 'compaction_window_size', diff --git a/src/antlr/Lexer.g b/src/antlr/Lexer.g index b3ae1aba49..c7065f7351 100644 --- a/src/antlr/Lexer.g +++ b/src/antlr/Lexer.g @@ -96,6 +96,7 @@ K_KEYSPACE: ( K E Y S P A C E K_KEYSPACES: K E Y S P A C E S; K_COLUMNFAMILY:( C O L U M N F A M I L Y | T A B L E ); +K_COLUMN: C O L U M N; K_TABLES: ( C O L U M N F A M I L I E S | T A B L E S ); K_MATERIALIZED:M A T E R I A L I Z E D; @@ -209,6 +210,8 @@ K_TUPLE: T U P L E; K_TRIGGER: T R I G G E R; K_STATIC: S T A T I C; K_FROZEN: F R O Z E N; +K_FOR: F O R; +K_FIELD: F I E L D; K_FUNCTION: F U N C T I O N; K_FUNCTIONS: F U N C T I O N S; @@ -237,6 +240,12 @@ K_SELECT_MASKED: S E L E C T '_' M A S K E D; K_VECTOR: V E C T O R; K_ANN: A N N; +K_COMMENT: C O M M E N T; +K_COMMENTS: C O M M E N T S; +K_SECURITY: S E C U R I T Y; +K_LABEL: L A B E L; +K_LABELS: L A B E L S; + // Case-insensitive alpha characters fragment A: ('a'|'A'); fragment B: ('b'|'B'); diff --git a/src/antlr/Parser.g b/src/antlr/Parser.g index ea52617e4c..8794d00d02 100644 --- a/src/antlr/Parser.g +++ b/src/antlr/Parser.g @@ -284,6 +284,16 @@ cqlStatement returns [CQLStatement.Raw stmt] | st45=copyTableStatement { $stmt = st45; } | st46=batchTxnStatement { $stmt = st46; } | st47=letStatement { $stmt = st47; } + | st48=commentOnKeyspaceStatement { $stmt = st48; } + | st49=securityLabelOnKeyspaceStatement { $stmt = st49; } + | st50=commentOnTableStatement { $stmt = st50; } + | st51=securityLabelOnTableStatement { $stmt = st51; } + | st52=commentOnColumnStatement { $stmt = st52; } + | st53=securityLabelOnColumnStatement { $stmt = st53; } + | st54=commentOnUserTypeStatement { $stmt = st54; } + | st55=securityLabelOnUserTypeStatement { $stmt = st55; } + | st56=commentOnUserTypeFieldStatement { $stmt = st56; } + | st57=securityLabelOnUserTypeFieldStatement { $stmt = st57; } ; /* @@ -1056,12 +1066,14 @@ copyTableStatement returns [CopyTableStatement.Raw stmt] ; propertyOrOption[CopyTableStatement.Raw stmt] - : tableLikeSingleOption[stmt] + : likeOption[stmt] | property[stmt.attrs] ; -tableLikeSingleOption[CopyTableStatement.Raw stmt] - : K_INDEXES {$stmt.withLikeOption(CopyTableStatement.CreateLikeOption.INDEXES);} +likeOption[CopyTableStatement.Raw stmt] + : K_INDEXES {$stmt.addLikeOption(CopyTableStatement.CreateLikeOption.INDEXES);} + | K_COMMENTS {$stmt.addLikeOption(CopyTableStatement.CreateLikeOption.COMMENTS);} + | K_SECURITY K_LABELS {$stmt.addLikeOption(CopyTableStatement.CreateLikeOption.SECURITY_LABELS);} ; /** @@ -1275,6 +1287,89 @@ dropKeyspaceStatement returns [DropKeyspaceStatement.Raw stmt] : K_DROP K_KEYSPACE (K_IF K_EXISTS { ifExists = true; } )? ks=keyspaceName { $stmt = new DropKeyspaceStatement.Raw(ks, ifExists); } ; +/** + * COMMENT ON KEYSPACE IS ; + */ +commentOnKeyspaceStatement returns [CommentOnKeyspaceStatement.Raw stmt] + : K_COMMENT K_ON K_KEYSPACE ks=keyspaceName K_IS (comment=STRING_LITERAL | K_NULL) { $stmt = new CommentOnKeyspaceStatement.Raw(ks, comment != null ? $comment.text : null); } + ; + +/** + * SECURITY LABEL [FOR ] ON KEYSPACE IS