You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by jk...@apache.org on 2025/11/19 19:46:52 UTC

(cassandra) branch trunk updated: CASSANDRA-20943 Introducing comments and security labels for schema elements

This is an automated email from the ASF dual-hosted git repository.

jkonisa pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b11633be4f CASSANDRA-20943 Introducing comments and security labels for schema elements
b11633be4f is described below

commit b11633be4f3ee4b7aa5096dc7afdcc86accc2deb
Author: jkonisa <jk...@apple.com>
AuthorDate: Fri Oct 3 15:15:42 2025 -0700

    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 ++++-
 .../apache/cassandra/audit/AuditLogEntryType.java  |   8 +
 src/java/org/apache/cassandra/config/Config.java   |   3 +
 .../cassandra/config/DatabaseDescriptor.java       |  10 +
 .../apache/cassandra/cql3/ColumnSpecification.java |  15 +-
 .../org/apache/cassandra/cql3/SchemaElement.java   |  19 +
 .../cql3/statements/DescribeStatement.java         |   4 +-
 .../cql3/statements/SchemaDescriptionsUtil.java    | 273 ++++++++++
 .../schema/CommentOnColumnStatement.java           | 129 +++++
 .../schema/CommentOnKeyspaceStatement.java         | 108 ++++
 .../statements/schema/CommentOnTableStatement.java | 122 +++++
 .../schema/CommentOnUserTypeFieldStatement.java    | 126 +++++
 .../schema/CommentOnUserTypeStatement.java         | 119 +++++
 .../cql3/statements/schema/CopyTableStatement.java | 189 ++++---
 .../schema/SchemaDescriptionStatement.java         | 145 ++++++
 .../schema/SecurityLabelOnColumnStatement.java     | 161 ++++++
 .../schema/SecurityLabelOnKeyspaceStatement.java   | 140 ++++++
 .../schema/SecurityLabelOnTableStatement.java      | 152 ++++++
 .../SecurityLabelOnUserTypeFieldStatement.java     | 157 ++++++
 .../schema/SecurityLabelOnUserTypeStatement.java   | 148 ++++++
 .../org/apache/cassandra/db/marshal/UserType.java  | 101 +++-
 .../db/virtual/AbstractSchemaMetadataTable.java    | 312 ++++++++++++
 .../cassandra/db/virtual/SchemaCommentsTable.java  |  92 ++++
 .../db/virtual/SchemaSecurityLabelsTable.java      |  92 ++++
 .../cassandra/db/virtual/SystemViewsKeyspace.java  |   2 +
 .../apache/cassandra/schema/ColumnMetadata.java    |  66 ++-
 .../apache/cassandra/schema/KeyspaceMetadata.java  |  11 +
 .../apache/cassandra/schema/KeyspaceParams.java    |  57 ++-
 .../org/apache/cassandra/schema/TableMetadata.java |  45 +-
 .../org/apache/cassandra/schema/TableParams.java   |  27 +-
 src/java/org/apache/cassandra/schema/Types.java    | 165 +++++-
 .../cassandra/tcm/membership/NodeVersion.java      |   2 +-
 .../cassandra/tcm/serialization/Version.java       |   5 +
 .../cql3/statements/DescribeStatementTest.java     | 189 ++++++-
 .../miscellaneous/CommentAndSecurityLabelTest.java | 558 +++++++++++++++++++++
 .../db/virtual/SchemaMetadataTableTest.java        | 336 +++++++++++++
 .../org/apache/cassandra/schema/MockSchema.java    |  20 +-
 .../schema/SchemaMetadataSerializationTest.java    | 290 +++++++++++
 .../schema/createlike/CreateLikeTest.java          |  79 ++-
 .../createlike/CreateLikeWithSessionTest.java      |   2 +-
 .../ExportImportListCompressionDictionaryTest.java |   8 +-
 47 files changed, 4749 insertions(+), 118 deletions(-)

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]|$)|[/][*].*?[*][/])/ ;
                           | <alterTableStatement>
                           | <alterKeyspaceStatement>
                           | <alterUserTypeStatement>
+                          | <commentOnKeyspaceStatement>
+                          | <commentOnTableStatement>
+                          | <commentOnColumnStatement>
+                          | <commentOnTypeStatement>
+                          | <securityLabelOnKeyspaceStatement>
+                          | <securityLabelOnTableStatement>
+                          | <securityLabelOnColumnStatement>
+                          | <securityLabelOnTypeStatement>
                           ;
 
 <authenticationStatement> ::= <createUserStatement>
@@ -402,6 +410,8 @@ JUNK ::= /([ \t\r\f\v]+|(--|[/][/])[^\n\r]*([\n\r]|$)|[/][*].*?[*][/])/ ;
                     ;
 <propertyOrOption> ::= <property>
                      | "INDEXES"
+                     | "COMMENTS"
+                     | "SECURITY" "LABELS"
                      ;
 
 '''
@@ -1593,6 +1603,32 @@ syntax_rules += r'''
                            ;
 '''
 
+syntax_rules += r'''
+<commentOnKeyspaceStatement> ::= "COMMENT" "ON" "KEYSPACE" ks=<keyspaceName> "IS" comment=( <stringLiteral> | "NULL" )
+                               ;
+
+<commentOnTableStatement> ::= "COMMENT" "ON" wat=( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName> "IS" comment=( <stringLiteral> | "NULL" )
+                            ;
+
+<commentOnColumnStatement> ::= "COMMENT" "ON" "COLUMN" cf=<columnFamilyName> dot="." col=<cident> "IS" comment=( <stringLiteral> | "NULL" )
+                             ;
+
+<commentOnTypeStatement> ::= "COMMENT" "ON" "TYPE" ut=<userTypeName> "IS" comment=( <stringLiteral> | "NULL" )
+                           ;
+
+<securityLabelOnKeyspaceStatement> ::= "SECURITY" "LABEL" "ON" "KEYSPACE" ks=<keyspaceName> "IS" label=( <stringLiteral> | "NULL" )
+                                     ;
+
+<securityLabelOnTableStatement> ::= "SECURITY" "LABEL" "ON" wat=( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName> "IS" label=( <stringLiteral> | "NULL" )
+                                  ;
+
+<securityLabelOnColumnStatement> ::= "SECURITY" "LABEL" "ON" "COLUMN" cf=<columnFamilyName> dot="." col=<cident> "IS" label=( <stringLiteral> | "NULL" )
+                                   ;
+
+<securityLabelOnTypeStatement> ::= "SECURITY" "LABEL" "ON" "TYPE" ut=<userTypeName> "IS" label=( <stringLiteral> | "NULL" )
+                                 ;
+'''
+
 syntax_rules += r'''
 <username> ::= name=( <identifier> | <stringLiteral> )
              ;
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 <keyspace> IS <comment>;
+ */
+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 <provider>] ON KEYSPACE <keyspace> IS <label>;
+ */
+securityLabelOnKeyspaceStatement returns [SecurityLabelOnKeyspaceStatement.Raw stmt]
+    @init { String provider = null; }
+    : K_SECURITY K_LABEL (K_FOR prov=noncol_ident { provider = prov.toString(); })? K_ON K_KEYSPACE ks=keyspaceName K_IS (label=STRING_LITERAL | K_NULL) { $stmt = new SecurityLabelOnKeyspaceStatement.Raw(ks, label != null ? $label.text : null, provider); }
+    ;
+
+/**
+ * COMMENT ON TABLE <table> IS <comment>;
+ */
+commentOnTableStatement returns [CommentOnTableStatement.Raw stmt]
+    : K_COMMENT K_ON K_COLUMNFAMILY cf=columnFamilyName K_IS (comment=STRING_LITERAL | K_NULL) { $stmt = new CommentOnTableStatement.Raw(cf, comment != null ? $comment.text : null); }
+    ;
+
+/**
+ * SECURITY LABEL [FOR <provider>] ON TABLE <table> IS <label>;
+ */
+securityLabelOnTableStatement returns [SecurityLabelOnTableStatement.Raw stmt]
+    @init { String provider = null; }
+    : K_SECURITY K_LABEL (K_FOR prov=noncol_ident { provider = prov.toString(); })? K_ON K_COLUMNFAMILY cf=columnFamilyName K_IS (label=STRING_LITERAL | K_NULL) { $stmt = new SecurityLabelOnTableStatement.Raw(cf, label != null ? $label.text : null, provider); }
+    ;
+
+/**
+ * COMMENT ON COLUMN <table>.<column> IS <comment>;
+ * COMMENT ON COLUMN <keyspace>.<table>.<column> IS <comment>;
+ */
+commentOnColumnStatement returns [CommentOnColumnStatement.Raw stmt]
+    : K_COMMENT K_ON K_COLUMN columnRef=columnReference K_IS (comment=STRING_LITERAL | K_NULL)
+      { $stmt = new CommentOnColumnStatement.Raw($columnRef.table, $columnRef.column, comment != null ? $comment.text : null); }
+    ;
+
+/**
+ * SECURITY LABEL [FOR <provider>] ON COLUMN <table>.<column> IS <label>;
+ * SECURITY LABEL [FOR <provider>] ON COLUMN <keyspace>.<table>.<column> IS <label>;
+ */
+securityLabelOnColumnStatement returns [SecurityLabelOnColumnStatement.Raw stmt]
+    @init { String provider = null; }
+    : K_SECURITY K_LABEL (K_FOR prov=noncol_ident { provider = prov.toString(); })? K_ON K_COLUMN columnRef=columnReference K_IS (label=STRING_LITERAL | K_NULL)
+      { $stmt = new SecurityLabelOnColumnStatement.Raw($columnRef.table, $columnRef.column, label != null ? $label.text : null, provider); }
+    ;
+
+/**
+ * COMMENT ON TYPE <type> IS <comment>;
+ */
+commentOnUserTypeStatement returns [CommentOnUserTypeStatement.Raw stmt]
+    : K_COMMENT K_ON K_TYPE tn=userTypeName K_IS (comment=STRING_LITERAL | K_NULL) { $stmt = new CommentOnUserTypeStatement.Raw(tn, comment != null ? $comment.text : null); }
+    ;
+
+/**
+ * SECURITY LABEL [FOR <provider>] ON TYPE <type> IS <label>;
+ */
+securityLabelOnUserTypeStatement returns [SecurityLabelOnUserTypeStatement.Raw stmt]
+    @init { String provider = null; }
+    : K_SECURITY K_LABEL (K_FOR prov=noncol_ident { provider = prov.toString(); })? K_ON K_TYPE tn=userTypeName K_IS (label=STRING_LITERAL | K_NULL) { $stmt = new SecurityLabelOnUserTypeStatement.Raw(tn, label != null ? $label.text : null, provider); }
+    ;
+
+/**
+ * COMMENT ON FIELD <type>.<field> IS <comment>;
+ * COMMENT ON FIELD <keyspace>.<type>.<field> IS <comment>;
+ */
+commentOnUserTypeFieldStatement returns [CommentOnUserTypeFieldStatement.Raw stmt]
+    : K_COMMENT K_ON K_FIELD typeFieldRef=typeFieldReference K_IS (comment=STRING_LITERAL | K_NULL)
+      { $stmt = new CommentOnUserTypeFieldStatement.Raw($typeFieldRef.typeName, $typeFieldRef.field, comment != null ? $comment.text : null); }
+    ;
+
+/**
+ * SECURITY LABEL [FOR <provider>] ON FIELD <type>.<field> IS <label>;
+ * SECURITY LABEL [FOR <provider>] ON FIELD <keyspace>.<type>.<field> IS <label>;
+ */
+securityLabelOnUserTypeFieldStatement returns [SecurityLabelOnUserTypeFieldStatement.Raw stmt]
+    @init { String provider = null; }
+    : K_SECURITY K_LABEL (K_FOR prov=noncol_ident { provider = prov.toString(); })? K_ON K_FIELD typeFieldRef=typeFieldReference K_IS (label=STRING_LITERAL | K_NULL)
+      { $stmt = new SecurityLabelOnUserTypeFieldStatement.Raw($typeFieldRef.typeName, $typeFieldRef.field, label != null ? $label.text : null, provider); }
+    ;
+
 /**
  * DROP TABLE [IF EXISTS] <table>;
  */
@@ -1784,6 +1879,22 @@ columnFamilyName returns [QualifiedName name]
     : (ksName[name] '.')? cfName[name]
     ;
 
+columnReference returns [QualifiedName table, ColumnIdentifier column]
+    @init { $table = new QualifiedName(); }
+    : cfName[$table] '.' col=cident
+      { $column = col; }
+    | ksName[$table] '.' cfName[$table] '.' col=cident
+      { $column = col; }
+    ;
+
+typeFieldReference returns [UTName typeName, FieldIdentifier field]
+    : ut=non_type_ident '.' fld=fident
+      { $typeName = new UTName(null, ut); $field = fld; }
+    | ks=noncol_ident '.' ut=non_type_ident '.' fld=fident
+      { $typeName = new UTName(ks, ut); $field = fld; }
+    ;
+
+
 userTypeName returns [UTName name]
     : (ks=noncol_ident '.')? ut=non_type_ident { $name = new UTName(ks, ut); }
     ;
@@ -2324,6 +2435,7 @@ basic_unreserved_keyword returns [String str]
         | K_ONLY
         | K_STATIC
         | K_FROZEN
+        | K_FOR
         | K_TUPLE
         | K_FUNCTION
         | K_FUNCTIONS
@@ -2362,5 +2474,12 @@ basic_unreserved_keyword returns [String str]
         | K_LET
         | K_THEN
         | K_TRANSACTION
+        | K_COMMENT
+        | K_COMMENTS
+        | K_SECURITY
+        | K_LABEL
+        | K_LABELS
+        | K_FIELD
+        | K_COLUMN
         ) { $str = $k.text; }
     ;
diff --git a/src/java/org/apache/cassandra/audit/AuditLogEntryType.java b/src/java/org/apache/cassandra/audit/AuditLogEntryType.java
index 4ee3348055..ff61f2f2a7 100644
--- a/src/java/org/apache/cassandra/audit/AuditLogEntryType.java
+++ b/src/java/org/apache/cassandra/audit/AuditLogEntryType.java
@@ -64,6 +64,14 @@ public enum AuditLogEntryType
     USE_KEYSPACE(AuditLogEntryCategory.OTHER),
     DESCRIBE(AuditLogEntryCategory.OTHER),
     TRANSACTION(AuditLogEntryCategory.TRANSACTION),
+    COMMENT_KEYSPACE(AuditLogEntryCategory.DDL),
+    COMMENT_TABLE(AuditLogEntryCategory.DDL),
+    COMMENT_COLUMN(AuditLogEntryCategory.DDL),
+    COMMENT_TYPE(AuditLogEntryCategory.DDL),
+    SECURITY_LABEL_KEYSPACE(AuditLogEntryCategory.DDL),
+    SECURITY_LABEL_TABLE(AuditLogEntryCategory.DDL),
+    SECURITY_LABEL_COLUMN(AuditLogEntryCategory.DDL),
+    SECURITY_LABEL_TYPE(AuditLogEntryCategory.DDL),
 
     /*
      * Common Audit Log Entry Types
diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java
index 7ea67ebc4c..986346da41 100644
--- a/src/java/org/apache/cassandra/config/Config.java
+++ b/src/java/org/apache/cassandra/config/Config.java
@@ -108,6 +108,9 @@ public class Config
     public volatile DurationSpec.IntMillisecondsBound credentials_update_interval = null;
     public volatile boolean credentials_cache_active_update = false;
 
+    public int max_comment_length = 128;
+    public int max_security_label_length = 48;
+
     /* Hashing strategy Random or OPHF */
     public String partitioner;
 
diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
index dec260f9b9..bf0beadd0e 100644
--- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
+++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java
@@ -2212,6 +2212,16 @@ public class DatabaseDescriptor
         conf.credentials_cache_active_update = update;
     }
 
+    public static int getMaxCommentLength()
+    {
+        return conf.max_comment_length;
+    }
+
+    public static int getMaxSecurityLabelLength()
+    {
+        return conf.max_security_label_length;
+    }
+
     public static int getMaxValueSize()
     {
         return Ints.saturatedCast(conf.max_value_size.toMebibytes() * 1024L * 1024);
diff --git a/src/java/org/apache/cassandra/cql3/ColumnSpecification.java b/src/java/org/apache/cassandra/cql3/ColumnSpecification.java
index 8cf869bd38..863f9eb5b8 100644
--- a/src/java/org/apache/cassandra/cql3/ColumnSpecification.java
+++ b/src/java/org/apache/cassandra/cql3/ColumnSpecification.java
@@ -28,17 +28,28 @@ import java.util.Iterator;
 
 public class ColumnSpecification
 {
+    protected static final String EMPTY_COMMENT = "";
+    protected static final String EMPTY_SECURITY_LABEL = "";
     public final String ksName;
     public final String cfName;
     public final ColumnIdentifier name;
     public final AbstractType<?> type;
+    public final String comment;
+    public final String securityLabel;
 
     public ColumnSpecification(String ksName, String cfName, ColumnIdentifier name, AbstractType<?> type)
+    {
+        this(ksName, cfName, name, type, EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
+    }
+
+    public ColumnSpecification(String ksName, String cfName, ColumnIdentifier name, AbstractType<?> type, String comment, String securityLabel)
     {
         this.ksName = ksName;
         this.cfName = cfName;
         this.name = name;
         this.type = type;
+        this.comment = comment == null ? EMPTY_COMMENT : comment;
+        this.securityLabel = securityLabel == null ? EMPTY_SECURITY_LABEL : securityLabel;
     }
 
     /**
@@ -49,7 +60,7 @@ public class ColumnSpecification
      */
     public ColumnSpecification withAlias(ColumnIdentifier alias)
     {
-        return new ColumnSpecification(ksName, cfName, alias, type);
+        return new ColumnSpecification(ksName, cfName, alias, type, comment, securityLabel);
     }
 
     public boolean isReversedType()
@@ -100,6 +111,8 @@ public class ColumnSpecification
         return MoreObjects.toStringHelper(this)
                           .add("name", name)
                           .add("type", type)
+                          .add("comment", comment)
+                          .add("securityLabel", securityLabel)
                           .toString();
     }
 }
diff --git a/src/java/org/apache/cassandra/cql3/SchemaElement.java b/src/java/org/apache/cassandra/cql3/SchemaElement.java
index 4fd96f84b5..6544bdc5e4 100644
--- a/src/java/org/apache/cassandra/cql3/SchemaElement.java
+++ b/src/java/org/apache/cassandra/cql3/SchemaElement.java
@@ -96,4 +96,23 @@ public interface SchemaElement
      * @return a CQL representation of this element
      */
     String toCqlString(boolean withWarnings, boolean withInternals, boolean ifNotExists);
+
+    /**
+     * Returns a complete CQL representation of this schema element for DESCRIBE statements.
+     * <p>
+     * This method returns the full schema definition including the CREATE statement and any
+     * associated metadata such as comments and security labels. It is specifically designed
+     * for use by DESCRIBE statements to provide a comprehensive view of the schema element.
+     * </p>
+     *
+     * @param withWarnings if commented warnings should be included
+     * @param withInternals if the internals part of the CQL should be exposed
+     * @param ifNotExists if "IF NOT EXISTS" should be included
+     * @return a complete CQL representation of this element including comments and security labels
+     * @see org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil
+     */
+    default String describe(boolean withWarnings, boolean withInternals, boolean ifNotExists)
+    {
+        return toCqlString(withWarnings, withInternals, ifNotExists);
+    }
 }
diff --git a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
index 64b7862d6d..6ee6561cc1 100644
--- a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java
@@ -374,7 +374,7 @@ public abstract class DescribeStatement<T> extends CQLStatement.Raw implements C
                 return ImmutableList.of(bytes(element.elementKeyspaceQuotedIfNeeded()),
                                         bytes(element.elementType().toString()),
                                         bytes(element.elementNameQuotedIfNeeded()),
-                                        bytes(element.toCqlString(true, withInternals, false)));
+                                        bytes(element.describe(true, withInternals, false)));
             }
         };
     }
@@ -424,7 +424,7 @@ public abstract class DescribeStatement<T> extends CQLStatement.Raw implements C
             return ImmutableList.of(bytes(element.elementKeyspaceQuotedIfNeeded()),
                                     bytes(element.elementType().toString()),
                                     bytes(element.elementNameQuotedIfNeeded()),
-                                    bytes(element.toCqlString(true, withInternals, false)));
+                                    bytes(element.describe(true, withInternals, false)));
         }
     }
 
diff --git a/src/java/org/apache/cassandra/cql3/statements/SchemaDescriptionsUtil.java b/src/java/org/apache/cassandra/cql3/statements/SchemaDescriptionsUtil.java
new file mode 100644
index 0000000000..e524f4c2eb
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/SchemaDescriptionsUtil.java
@@ -0,0 +1,273 @@
+/*
+ * 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.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.
+     */
+
+    private SchemaDescriptionsUtil()
+    {
+    }
+
+    /**
+     * 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);
+
+        // Add comments and security labels on columns
+        for (ColumnMetadata column: tableMetadata.columns())
+        {
+            appendCommentOnColumn(builder, tableMetadata, column);
+            appendSecurityLabelOnColumn(builder, tableMetadata, column);
+        }
+    }
+
+    /**
+     * Appends a COMMENT ON TYPE statement to the builder if the type has a comment.
+     *
+     * @param builder the StringBuilder to append to
+     * @param userType the user-defined type containing the comment
+     */
+    public static void appendCommentOnType(StringBuilder builder, UserType userType)
+    {
+        String schemaElement = String.format("%s.%s", userType.keyspace, userType.getNameAsString());
+        addDescription(builder, COMMENT_DESCRIPTION_TYPE, TYPE_SCHEMA_ELEMENT, schemaElement, userType.comment);
+
+        // Add comments on fields
+        for (int i = 0; i < userType.size(); i++)
+            appendCommentOnField(builder, userType, i);
+    }
+
+    /**
+     * Appends SECURITY LABEL ON TYPE statement and COMMENT/SECURITY LABEL ON FIELD statements
+     * to the builder if the type or any of its fields have security labels or comments.
+     *
+     * @param builder the StringBuilder to append to
+     * @param userType the user-defined type containing the security label and fields
+     */
+    public static void appendSecurityLabelOnType(StringBuilder builder, UserType userType)
+    {
+        String schemaElement = String.format("%s.%s", userType.keyspace, userType.getNameAsString());
+        addDescription(builder, SECURITY_LABEL_DESCRIPTION_TYPE, TYPE_SCHEMA_ELEMENT, schemaElement, userType.securityLabel);
+
+        // Add security labels on fields
+        for (int i = 0; i < userType.size(); i++)
+            appendSecurityLabelOnField(builder, userType, i);
+    }
+
+    /* Helper methods */
+
+    /**
+     * Appends a COMMENT ON COLUMN statement to the builder if the column has a comment.
+     *
+     * @param builder the StringBuilder to append to
+     * @param tableMetadata the table containing the column
+     * @param columnMetadata the column metadata containing the comment
+     */
+    private static void appendCommentOnColumn(StringBuilder builder, TableMetadata tableMetadata, ColumnMetadata columnMetadata)
+    {
+        appendColumnDescription(builder, COMMENT_DESCRIPTION_TYPE, tableMetadata, columnMetadata, columnMetadata.comment);
+    }
+
+    /**
+     * Appends a SECURITY LABEL ON COLUMN statement to the builder if the column has a security label.
+     *
+     * @param builder the StringBuilder to append to
+     * @param tableMetadata the table containing the column
+     * @param columnMetadata the column metadata containing the security label
+     */
+    private static void appendSecurityLabelOnColumn(StringBuilder builder, TableMetadata tableMetadata, ColumnMetadata columnMetadata)
+    {
+        appendColumnDescription(builder, SECURITY_LABEL_DESCRIPTION_TYPE, tableMetadata, columnMetadata, columnMetadata.securityLabel);
+    }
+
+    /**
+     * Helper method to append a column-level comment or security label statement.
+     *
+     * @param builder the StringBuilder to append to
+     * @param descriptionType the type of description (COMMENT or SECURITY LABEL)
+     * @param tableMetadata the table containing the column
+     * @param columnMetadata the column metadata
+     * @param description the comment or security label text
+     */
+    private static void appendColumnDescription(StringBuilder builder, String descriptionType, TableMetadata tableMetadata, ColumnMetadata columnMetadata, String description)
+    {
+        String schemaElement = String.format("%s.%s.%s", tableMetadata.keyspace, tableMetadata.name, columnMetadata.name.toCQLString());
+        addDescription(builder, descriptionType, COLUMN_SCHEMA_ELEMENT, schemaElement, description);
+    }
+
+    /**
+     * Appends a COMMENT ON FIELD statement to the builder if the field has a comment.
+     *
+     * @param builder the StringBuilder to append to
+     * @param userType the user-defined type containing the field
+     * @param fieldIndex the index of the field
+     */
+    private static void appendCommentOnField(StringBuilder builder, UserType userType, int fieldIndex)
+    {
+        String comment = userType.fieldComment(userType.fieldName(fieldIndex));
+        appendFieldDescription(builder, COMMENT_DESCRIPTION_TYPE, userType, fieldIndex, comment);
+    }
+
+    /**
+     * Appends a SECURITY LABEL ON FIELD statement to the builder if the field has a security label.
+     *
+     * @param builder the StringBuilder to append to
+     * @param userType the user-defined type containing the field
+     * @param fieldIndex the index of the field
+     */
+    private static void appendSecurityLabelOnField(StringBuilder builder, UserType userType, int fieldIndex)
+    {
+        String securityLabel = userType.fieldSecurityLabel(userType.fieldName(fieldIndex));
+        appendFieldDescription(builder, SECURITY_LABEL_DESCRIPTION_TYPE, userType, fieldIndex, securityLabel);
+    }
+
+    /**
+     * Helper method to append a field-level comment or security label statement.
+     *
+     * @param builder the StringBuilder to append to
+     * @param descriptionType the type of description (COMMENT or SECURITY LABEL)
+     * @param userType the user-defined type containing the field
+     * @param fieldIndex the index of the field
+     * @param description the comment or security label text
+     */
+    private static void appendFieldDescription(StringBuilder builder, String descriptionType, UserType userType, int fieldIndex, String description)
+    {
+        String schemaElement = String.format("%s.%s.%s", userType.keyspace, userType.getNameAsString(), userType.fieldName(fieldIndex).toString());
+        addDescription(builder, descriptionType, FIELD_SCHEMA_ELEMENT, schemaElement, description);
+    }
+
+    /**
+     * Helper method to append a comment or security label statement to the builder.
+     * <p>
+     * Generates CQL statements in the format:
+     * {@code <descriptionType> ON <schemaElement> <schemaElementName> IS '<comment>';}
+     * </p>
+     *
+     * @param builder the StringBuilder to append to
+     * @param descriptionType the type of description (COMMENT or SECURITY LABEL)
+     * @param schemaElement the type of schema element (KEYSPACE, TABLE, COLUMN, or TYPE)
+     * @param schemaElementName the fully qualified name of the schema element
+     * @param description the comment or security label text (null or empty values are ignored)
+     */
+    private static void addDescription(StringBuilder builder, String descriptionType, String schemaElement, String schemaElementName, String description)
+    {
+        if (StringUtils.isEmpty(description))
+        {
+            return;
+        }
+
+        /**
+         * CQL Injection Prevention: Single quotes in comments and security labels must be escaped.
+         *
+         * Example of unescaped input creating a CQL injection vulnerability:
+         *   COMMENT ON COLUMN keyspace1.tbl.key IS 'a'; DROP TABLE keyspace1.tbl;';
+         *
+         * Without proper escaping, this would execute as two separate statements:
+         *   1. COMMENT ON COLUMN keyspace1.tbl.key IS 'a';
+         *   2. DROP TABLE keyspace1.tbl;';
+         *
+         * This is particularly dangerous when operators use DESCRIBE statements to export
+         * schema from one database and import it into another, as malicious comments could
+         * execute arbitrary CQL commands in the target database.
+         */
+        String escapedSingleQuoteDescription = StringUtils.replace(description, "'", "''");
+        builder.append("\n")
+               .append(descriptionType)
+               .append(" ON ")
+               .append(schemaElement)
+               .append(" ")
+               .append(schemaElementName)
+               .append(" IS ")
+               .append('\'')
+               .append(escapedSingleQuoteDescription)
+               .append('\'')
+               .append(';');
+    }
+
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnColumnStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnColumnStatement.java
new file mode 100644
index 0000000000..0bcb6088c8
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnColumnStatement.java
@@ -0,0 +1,129 @@
+/*
+ * 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.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.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 COLUMN CQL statements.
+ * <p>
+ * This statement allows users to add descriptive comments to individual columns for documentation purposes.
+ * Comments are stored in the column metadata and displayed when using DESCRIBE TABLE.
+ * </p>
+ * <p>
+ * Syntax: {@code COMMENT ON COLUMN [keyspace_name.]table_name.column_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 CommentOnUserTypeStatement
+ */
+public final class CommentOnColumnStatement extends SchemaDescriptionStatement
+{
+    private final String tableName;
+    private final ColumnIdentifier columnName;
+
+    public CommentOnColumnStatement(String keyspaceName, String tableName, ColumnIdentifier columnName, String comment)
+    {
+        super(keyspaceName, comment, DescriptionType.COMMENT);
+        this.tableName = tableName;
+        this.columnName = columnName;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        TableMetadata table = validateAndGetTable(schema, tableName);
+        ColumnMetadata columnMetadata = table.getColumn(columnName);
+        if (null == columnMetadata)
+            throw ire("Column '%s' doesn't exist in table '%s.%s'", columnName, keyspaceName, tableName);
+
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        TableMetadata newTable = table.unbuild().alterColumnComment(columnName, effectiveDescription()).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.COMMENT_COLUMN, keyspaceName, tableName);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s (%s.%s.%s, %s)", getClass().getSimpleName(), keyspaceName, tableName, columnName, description);
+    }
+
+    public static final class Raw extends CQLStatement.Raw
+    {
+        private final QualifiedName tableName;
+        private final ColumnIdentifier columnName;
+        private final String comment;
+
+        public Raw(QualifiedName tableName, ColumnIdentifier columnName, String comment)
+        {
+            this.tableName = tableName;
+            this.columnName = columnName;
+            this.comment = comment;
+        }
+
+        @Override
+        public CommentOnColumnStatement prepare(ClientState state)
+        {
+            String keyspaceName = tableName.hasKeyspace() ? tableName.getKeyspace() : state.getKeyspace();
+            return new CommentOnColumnStatement(keyspaceName,
+                                                tableName.getName(),
+                                                columnName,
+                                                comment);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnKeyspaceStatement.java
new file mode 100644
index 0000000000..5def5af758
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnKeyspaceStatement.java
@@ -0,0 +1,108 @@
+/*
+ * 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.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.KeyspaceParams;
+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;
+
+/**
+ * Handles the execution of COMMENT ON KEYSPACE CQL statements.
+ * <p>
+ * This statement allows users to add descriptive comments to keyspaces for documentation purposes.
+ * Comments are stored in the keyspace metadata and displayed when using DESCRIBE KEYSPACE.
+ * </p>
+ * <p>
+ * Syntax: {@code COMMENT ON KEYSPACE keyspace_name IS 'comment text';}
+ * </p>
+ * <p>
+ * Comments can be removed by setting them to an empty string or null.
+ * </p>
+ *
+ * @see CommentOnTableStatement
+ * @see CommentOnColumnStatement
+ * @see CommentOnUserTypeStatement
+ */
+public final class CommentOnKeyspaceStatement extends SchemaDescriptionStatement
+{
+    public CommentOnKeyspaceStatement(String keyspaceName, String comment)
+    {
+        super(keyspaceName, comment, DescriptionType.COMMENT);
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        KeyspaceMetadata keyspace = validateAndGetKeyspace(schema);
+        KeyspaceParams newParams = keyspace.params.withComment(effectiveDescription());
+        KeyspaceMetadata newKeyspace = keyspace.withSwapped(newParams);
+        return schema.withAddedOrUpdated(newKeyspace);
+    }
+
+    @Override
+    SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+    {
+        return new SchemaChange(Change.UPDATED, keyspaceName);
+    }
+
+    @Override
+    public void authorize(ClientState client)
+    {
+        client.ensureKeyspacePermission(keyspaceName, Permission.ALTER);
+    }
+
+    @Override
+    public AuditLogContext getAuditLogContext()
+    {
+        return new AuditLogContext(AuditLogEntryType.COMMENT_KEYSPACE, keyspaceName);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, description);
+    }
+
+    public static final class Raw extends CQLStatement.Raw
+    {
+        private final String keyspaceName;
+        private final String comment;
+
+        public Raw(String keyspaceName, String comment)
+        {
+            this.keyspaceName = keyspaceName;
+            this.comment = comment;
+        }
+
+        @Override
+        public CommentOnKeyspaceStatement prepare(ClientState state)
+        {
+            return new CommentOnKeyspaceStatement(keyspaceName, comment);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnTableStatement.java
new file mode 100644
index 0000000000..0c2ced7daf
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnTableStatement.java
@@ -0,0 +1,122 @@
+/*
+ * 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.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 CommentOnUserTypeStatement
+ */
+public final class CommentOnTableStatement extends SchemaDescriptionStatement
+{
+    private final String tableName;
+
+    public CommentOnTableStatement(String keyspaceName, String tableName, String comment)
+    {
+        super(keyspaceName, comment, DescriptionType.COMMENT);
+        this.tableName = tableName;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+
+        TableMetadata table = validateAndGetTable(schema, tableName);
+        TableParams newParams = table.params.unbuild().comment(effectiveDescription()).build();
+        TableMetadata newTable = table.withSwapped(newParams);
+
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        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.COMMENT_TABLE, keyspaceName, tableName);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s (%s.%s, %s)", getClass().getSimpleName(), keyspaceName, tableName, description);
+    }
+
+    public static final class Raw extends CQLStatement.Raw
+    {
+        private final QualifiedName tableName;
+        private final String comment;
+
+        public Raw(QualifiedName tableName, String comment)
+        {
+            this.tableName = tableName;
+            this.comment = comment;
+        }
+
+        @Override
+        public CommentOnTableStatement prepare(ClientState state)
+        {
+            String keyspaceName = tableName.hasKeyspace() ? tableName.getKeyspace() : state.getKeyspace();
+            return new CommentOnTableStatement(keyspaceName,
+                                               tableName.getName(),
+                                               comment);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeFieldStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeFieldStatement.java
new file mode 100644
index 0000000000..049b2de7e1
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeFieldStatement.java
@@ -0,0 +1,126 @@
+/*
+ * 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.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;
+
+/**
+ * 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 CommentOnUserTypeStatement
+ * @see CommentOnColumnStatement
+ */
+public final class CommentOnUserTypeFieldStatement extends SchemaDescriptionStatement
+{
+    private final String typeName;
+    private final FieldIdentifier fieldName;
+
+    public CommentOnUserTypeFieldStatement(String keyspaceName, String typeName, FieldIdentifier fieldName, String comment)
+    {
+        super(keyspaceName, comment, DescriptionType.COMMENT);
+        this.typeName = typeName;
+        this.fieldName = fieldName;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        UserType type = validateAndGetTypeField(schema, typeName, fieldName);
+
+        String effectiveComment = effectiveDescription();
+        UserType newType = type.withFieldComment(fieldName, effectiveComment);
+
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        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.ensureAllTablesPermission(keyspaceName, Permission.ALTER);
+    }
+
+    @Override
+    public AuditLogContext getAuditLogContext()
+    {
+        return new AuditLogContext(AuditLogEntryType.COMMENT_TYPE, keyspaceName, typeName);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s (%s.%s.%s, %s)", getClass().getSimpleName(), keyspaceName, typeName, fieldName, description);
+    }
+
+    public static final class Raw extends CQLStatement.Raw
+    {
+        private final UTName typeName;
+        private final FieldIdentifier fieldName;
+        private final String comment;
+
+        public Raw(UTName typeName, FieldIdentifier fieldName, String comment)
+        {
+            this.typeName = typeName;
+            this.fieldName = fieldName;
+            this.comment = comment;
+        }
+
+        @Override
+        public CommentOnUserTypeFieldStatement prepare(ClientState state)
+        {
+            String keyspaceName = typeName.hasKeyspace() ? typeName.getKeyspace() : state.getKeyspace();
+            return new CommentOnUserTypeFieldStatement(keyspaceName,
+                                                       typeName.getStringTypeName(),
+                                                       fieldName,
+                                                       comment);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeStatement.java
new file mode 100644
index 0000000000..c12d9918cc
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CommentOnUserTypeStatement.java
@@ -0,0 +1,119 @@
+/*
+ * 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.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;
+
+/**
+ * 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 CommentOnUserTypeStatement extends SchemaDescriptionStatement
+{
+    private final String typeName;
+
+    public CommentOnUserTypeStatement(String keyspaceName, String typeName, String comment)
+    {
+        super(keyspaceName, comment, DescriptionType.COMMENT);
+        this.typeName = typeName;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        UserType type = validateAndGetType(schema, typeName);
+        UserType newType = type.withComment(effectiveDescription());
+
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        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.ensureAllTablesPermission(keyspaceName, Permission.ALTER);
+    }
+
+    @Override
+    public AuditLogContext getAuditLogContext()
+    {
+        return new AuditLogContext(AuditLogEntryType.COMMENT_TYPE, keyspaceName, typeName);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s (%s.%s, %s)", getClass().getSimpleName(), keyspaceName, typeName, description);
+    }
+
+    public static final class Raw extends CQLStatement.Raw
+    {
+        private final UTName typeName;
+        private final String comment;
+
+        public Raw(UTName typeName, String comment)
+        {
+            this.typeName = typeName;
+            this.comment = comment;
+        }
+
+        @Override
+        public CommentOnUserTypeStatement prepare(ClientState state)
+        {
+            String keyspaceName = typeName.hasKeyspace() ? typeName.getKeyspace() : state.getKeyspace();
+            return new CommentOnUserTypeStatement(keyspaceName,
+                                                  typeName.getStringTypeName(),
+                                                  comment);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
index c5b38e5e08..bde06c35dc 100644
--- a/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/CopyTableStatement.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.cql3.statements.schema;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -27,6 +28,8 @@ import java.util.stream.Collectors;
 
 import com.google.common.collect.Sets;
 
+import org.apache.commons.lang3.StringUtils;
+
 import org.apache.cassandra.audit.AuditLogContext;
 import org.apache.cassandra.audit.AuditLogEntryType;
 import org.apache.cassandra.auth.Permission;
@@ -67,14 +70,14 @@ public final class CopyTableStatement extends AlterSchemaStatement
     private final String targetTableName;
     private final boolean ifNotExists;
     private final TableAttributes attrs;
-    private final CreateLikeOption createLikeOption;
+    private final Set<CreateLikeOption> createLikeOptions;
 
     public CopyTableStatement(String sourceKeyspace,
                               String targetKeyspace,
                               String sourceTableName,
                               String targetTableName,
                               boolean ifNotExists,
-                              CreateLikeOption createLikeOption,
+                              Set<CreateLikeOption> createLikeOptions,
                               TableAttributes attrs)
     {
         super(targetKeyspace);
@@ -83,7 +86,9 @@ public final class CopyTableStatement extends AlterSchemaStatement
         this.sourceTableName = sourceTableName;
         this.targetTableName = targetTableName;
         this.ifNotExists = ifNotExists;
-        this.createLikeOption = createLikeOption;
+        this.createLikeOptions = createLikeOptions != null
+                                 ? EnumSet.copyOf(createLikeOptions)
+                                 : EnumSet.noneOf(CreateLikeOption.class);
         this.attrs = attrs;
     }
 
@@ -115,10 +120,10 @@ public final class CopyTableStatement extends AlterSchemaStatement
         if (null == sourceKeyspaceMeta)
             throw ire("Source Keyspace '%s' doesn't exist", sourceKeyspace);
 
-        TableMetadata sourceTableMeta = sourceKeyspaceMeta.getTableNullable(sourceTableName);
+        TableMetadata sourceTableMeta = sourceKeyspaceMeta.getTableOrViewNullable(sourceTableName);
 
         if (null == sourceTableMeta)
-            throw ire("Souce Table '%s.%s' doesn't exist", sourceKeyspace, sourceTableName);
+            throw ire("Source Table '%s.%s' doesn't exist", sourceKeyspace, sourceTableName);
 
         if (sourceTableMeta.isIndex())
             throw ire("Cannot use CREATE TABLE LIKE on an index table '%s.%s'.", sourceKeyspace, sourceTableName);
@@ -191,7 +196,7 @@ public final class CopyTableStatement extends AlterSchemaStatement
         if (!sourceTableMeta.params.compression.isEnabled())
             Guardrails.uncompressedTablesEnabled.ensureEnabled(state);
 
-        // withInternals can be set to false as it is only used for souce table id, which is not need for target table and the table
+        // withInternals can be set to false as it is only used for source table id, which is not need for target table and the table
         // id can be set through create table like cql using WITH ID
         String sourceCQLString = sourceTableMeta.toCqlString(false, false, false, false);
 
@@ -203,9 +208,12 @@ public final class CopyTableStatement extends AlterSchemaStatement
                                                                   .indexes(Indexes.none())
                                                                   .triggers(Triggers.none());
 
+        // Copy requested features using the CreateLikeHandler pattern
+        for (CreateLikeOption option : createLikeOptions)
+            option.copy(targetBuilder, targetKeyspace, targetTableName, sourceTableMeta, targetKeyspaceMeta);
+
         TableParams originalParams = targetBuilder.build().params;
         TableParams newTableParams = attrs.asAlteredTableParams(originalParams);
-        maybeCopyIndexes(targetBuilder, sourceTableMeta, targetKeyspaceMeta);
 
         TableMetadata table = targetBuilder.params(newTableParams)
                                            .id(TableId.get(metadata))
@@ -239,59 +247,13 @@ public final class CopyTableStatement extends AlterSchemaStatement
         validateDefaultTimeToLive(attrs.asNewTableParams(keyspaceName));
     }
 
-    private void maybeCopyIndexes(TableMetadata.Builder builder, TableMetadata sourceTableMeta, KeyspaceMetadata targetKeyspaceMeta)
-    {
-        if (createLikeOption != CreateLikeOption.INDEXES || sourceTableMeta.indexes.isEmpty())
-            return;
-
-        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())
-            builder.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));
-    }
-
     public final static class Raw extends CQLStatement.Raw
     {
         private final QualifiedName oldName;
         private final QualifiedName newName;
         private final boolean ifNotExists;
         public final TableAttributes attrs = new TableAttributes();
-        private CreateLikeOption createLikeOption = null;
+        private Set<CreateLikeOption> createLikeOptions = EnumSet.noneOf(CreateLikeOption.class);
 
         public Raw(QualifiedName newName, QualifiedName oldName, boolean ifNotExists)
         {
@@ -305,17 +267,126 @@ public final class CopyTableStatement extends AlterSchemaStatement
         {
             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
+    /**
+     * Handler interface for copying specific table features when using CREATE TABLE LIKE.
+     * Each CreateLikeOption implements this to provide specific copy logic.
+     */
+    interface CreateLikeHandler
     {
-        INDEXES;
+        /**
+         * Copies a specific feature from the source table to the target table builder.
+         *
+         * @param targetTableBuilder the builder for the target table being created
+         * @param targetKeyspace the keyspace where the target table will be created
+         * @param targetTableName the name of the target table being created
+         * @param sourceTableMeta metadata of the source table to copy from
+         * @param targetKeyspaceMeta metadata of the target keyspace for schema lookup
+         */
+        void copy(TableMetadata.Builder targetTableBuilder,
+                  String targetKeyspace,
+                  String targetTableName,
+                  TableMetadata sourceTableMeta,
+                  KeyspaceMetadata targetKeyspaceMeta);
+    }
+
+    public enum CreateLikeOption implements CreateLikeHandler
+    {
+        INDEXES
+        {
+            @Override
+            public void copy(TableMetadata.Builder targetTableBuilder,
+                             String targetKeyspace,
+                             String targetTableName,
+                             TableMetadata sourceTableMeta,
+                             KeyspaceMetadata targetKeyspaceMeta)
+            {
+                if (sourceTableMeta.indexes.isEmpty())
+                    return;
+
+                String sourceTableName = sourceTableMeta.name;
+                String sourceKeyspace = sourceTableMeta.keyspace;
+                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())
+                    targetTableBuilder.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 targetTableBuilder,
+                             String targetKeyspace,
+                             String targetTableName,
+                             TableMetadata sourceTableMeta,
+                             KeyspaceMetadata targetKeyspaceMeta)
+            {
+                if (!StringUtils.isEmpty(sourceTableMeta.params.comment))
+                    targetTableBuilder.comment(sourceTableMeta.params.comment);
+
+                for (ColumnMetadata columnMetadata : sourceTableMeta.columns())
+                    targetTableBuilder.alterColumnComment(columnMetadata.name, columnMetadata.comment);
+            }
+        },
+        SECURITY_LABELS
+        {
+            @Override
+            public void copy(TableMetadata.Builder targetTableBuilder,
+                             String targetKeyspace,
+                             String targetTableName,
+                             TableMetadata sourceTableMeta,
+                             KeyspaceMetadata targetKeyspaceMeta)
+            {
+                if (!StringUtils.isEmpty(sourceTableMeta.params.securityLabel))
+                    targetTableBuilder.securityLabel(sourceTableMeta.params.securityLabel);
+
+                for (ColumnMetadata columnMetadata : sourceTableMeta.columns())
+                    targetTableBuilder.alterColumnSecurityLabel(columnMetadata.name, columnMetadata.securityLabel);
+            }
+        }
+
     }
 }
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/SchemaDescriptionStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/SchemaDescriptionStatement.java
new file mode 100644
index 0000000000..c532376321
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/SchemaDescriptionStatement.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.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.FieldIdentifier;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+import org.apache.cassandra.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.Keyspaces;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.service.ClientState;
+import org.apache.cassandra.service.ClientWarn;
+
+import static org.apache.cassandra.schema.TableMetadata.Kind.REGULAR;
+import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
+
+public abstract class SchemaDescriptionStatement extends AlterSchemaStatement
+{
+    private static final String NO_DESCRIPTION = "";
+    protected final String description;
+    private final DescriptionType descriptionType;
+
+    protected SchemaDescriptionStatement(String keyspaceName, String comment, DescriptionType descriptionType)
+    {
+        super(keyspaceName);
+        this.description = comment;
+        this.descriptionType = descriptionType;
+    }
+
+    @Override
+    public void validate(ClientState state)
+    {
+        super.validate(state);
+        if(description == null)
+        {
+            // Statement is removing comment on schema elements, ignore
+            return;
+        }
+
+        if(description.isEmpty())
+        {
+            throw new InvalidRequestException(String.format("Cannot set %s to empty string", descriptionType.value));
+        }
+
+        if (description.length() > descriptionType.maxLength)
+        {
+            String msg = String.format("%s length (%d) exceeds maximum allowed length (%d)",
+                                       descriptionType.value,
+                                       description.length(),
+                                       descriptionType.maxLength);
+            throw new InvalidRequestException(msg);
+        }
+    }
+
+    protected KeyspaceMetadata validateAndGetKeyspace(Keyspaces schema)
+    {
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+
+        if (null == keyspace)
+            throw ire("Keyspace '%s' doesn't exist", keyspaceName);
+
+        return keyspace;
+    }
+
+    protected TableMetadata validateAndGetTable(Keyspaces schema, String tableName)
+    {
+        KeyspaceMetadata keyspaceMetadata = validateAndGetKeyspace(schema);
+
+        TableMetadata tableMetadata = keyspaceMetadata.getTableOrViewNullable(tableName);
+        if (null == tableMetadata)
+            throw ire("Table '%s.%s' doesn't exist", keyspaceName, tableName);
+
+        if (tableMetadata.kind != REGULAR)
+            throw ire("Cannot set %s on non-regular table '%s.%s'",descriptionType.value, keyspaceName, tableName);
+
+        return tableMetadata;
+    }
+
+    protected UserType validateAndGetType(Keyspaces schema, String typeName)
+    {
+        KeyspaceMetadata keyspaceMetadata = validateAndGetKeyspace(schema);
+
+        UserType type = keyspaceMetadata.types.getNullable(bytes(typeName));
+        if (null == type)
+            throw ire("Type '%s.%s' doesn't exist", keyspaceName, typeName);
+
+        return type;
+    }
+
+    protected UserType validateAndGetTypeField(Keyspaces schema,
+                                               String typeName,
+                                               FieldIdentifier fieldName)
+    {
+        UserType type = validateAndGetType(schema, typeName);
+
+        if (type.fieldPosition(fieldName) == -1)
+            throw ire("Field '%s' doesn't exist in type '%s.%s'", fieldName, keyspaceName, typeName);
+
+        return type;
+    }
+
+    protected String effectiveDescription()
+    {
+        return description == null ? NO_DESCRIPTION : description;
+    }
+
+    protected void providerWarning(String provider)
+    {
+        if (provider != null)
+            ClientWarn.instance.warn("Provider functionality not implemented." +
+                                     " Provider '" + provider + "' will not be loaded or invoked.");
+    }
+
+    protected enum DescriptionType
+    {
+        COMMENT("comment", DatabaseDescriptor.getMaxCommentLength()),
+        SECURITY_LABEL("security label", DatabaseDescriptor.getMaxSecurityLabelLength());
+
+        private final String value;
+        private final int maxLength;
+
+        DescriptionType(String value, int maxLength)
+        {
+            this.value = value;
+            this.maxLength = maxLength;
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnColumnStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnColumnStatement.java
new file mode 100644
index 0000000000..22cb81e6e9
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnColumnStatement.java
@@ -0,0 +1,161 @@
+/*
+ * 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.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.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 [FOR provider_name] 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 NULL.
+ * </p>
+ * <h3>Provider Support</h3>
+ * <p>
+ * The optional {@code FOR provider_name} clause is accepted in the syntax to support future
+ * extensibility. Providers are intended to be pluggable security policy modules that can
+ * interpret and enforce access controls based on security labels. However, provider
+ * functionality is not currently implemented. If a provider is specified, a warning will be
+ * issued but the security label will still be applied. The provider parameter is reserved
+ * for future use.
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre>
+ * // Basic syntax without provider
+ * SECURITY LABEL ON COLUMN users.ssn IS 'PII';
+ *
+ * // Syntax with provider (accepted but not yet implemented)
+ * SECURITY LABEL FOR custom_provider ON COLUMN users.ssn IS 'sensitive';
+ *
+ * // Remove security label
+ * SECURITY LABEL ON COLUMN users.ssn IS NULL;
+ * </pre>
+ * </p>
+ *
+ * @see SecurityLabelOnKeyspaceStatement
+ * @see SecurityLabelOnTableStatement
+ * @see SecurityLabelOnUserTypeStatement
+ */
+public final class SecurityLabelOnColumnStatement extends SchemaDescriptionStatement
+{
+    private final String tableName;
+    private final ColumnIdentifier columnName;
+    private final String provider;
+
+    public SecurityLabelOnColumnStatement(String keyspaceName, String tableName, ColumnIdentifier columnName, String securityLabel, String provider)
+    {
+        super(keyspaceName, securityLabel, DescriptionType.SECURITY_LABEL);
+        this.tableName = tableName;
+        this.columnName = columnName;
+        this.provider = provider;
+    }
+
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        TableMetadata table = validateAndGetTable(schema, tableName);
+
+        ColumnMetadata column = table.getColumn(columnName);
+        if (null == column)
+            throw ire("Column '%s' doesn't exist in table '%s.%s'", columnName, keyspaceName, tableName);
+
+        providerWarning(provider);
+
+        TableMetadata newTable = table.unbuild().alterColumnSecurityLabel(columnName, effectiveDescription()).build();
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        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, description);
+    }
+
+    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);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnKeyspaceStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnKeyspaceStatement.java
new file mode 100644
index 0000000000..e9ab8625d1
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnKeyspaceStatement.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.schema.KeyspaceMetadata;
+import org.apache.cassandra.schema.KeyspaceParams;
+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;
+
+/**
+ * Handles the execution of SECURITY LABEL ON KEYSPACE CQL statements.
+ * <p>
+ * This statement allows users to add security classification labels to keyspaces.
+ * Security labels are stored in the keyspace metadata and displayed when using DESCRIBE KEYSPACE.
+ * </p>
+ * <p>
+ * Syntax: {@code SECURITY LABEL [FOR provider_name] ON KEYSPACE keyspace_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 NULL.
+ * </p>
+ * <h3>Provider Support</h3>
+ * <p>
+ * The optional {@code FOR provider_name} clause is accepted in the syntax to support future
+ * extensibility. Providers are intended to be pluggable security policy modules that can
+ * interpret and enforce access controls based on security labels. However, provider
+ * functionality is not currently implemented. If a provider is specified, a warning will be
+ * issued but the security label will still be applied. The provider parameter is reserved
+ * for future use.
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre>
+ * // Basic syntax without provider
+ * SECURITY LABEL ON KEYSPACE production IS 'confidential';
+ *
+ * // Syntax with provider (accepted but not yet implemented)
+ * SECURITY LABEL FOR custom_provider ON KEYSPACE production IS 'top_secret';
+ *
+ * // Remove security label
+ * SECURITY LABEL ON KEYSPACE production IS NULL;
+ * </pre>
+ * </p>
+ *
+ * @see SecurityLabelOnTableStatement
+ * @see SecurityLabelOnColumnStatement
+ * @see SecurityLabelOnUserTypeStatement
+ */
+public final class SecurityLabelOnKeyspaceStatement extends SchemaDescriptionStatement
+{
+    private final String provider;
+
+    public SecurityLabelOnKeyspaceStatement(String keyspaceName, String securityLabel, String provider)
+    {
+        super(keyspaceName, securityLabel, DescriptionType.SECURITY_LABEL);
+        this.provider = provider;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        KeyspaceMetadata keyspace = validateAndGetKeyspace(schema);
+
+        providerWarning(provider);
+
+        KeyspaceParams newParams = keyspace.params.withSecurityLabel(effectiveDescription());
+        KeyspaceMetadata newKeyspace = keyspace.withSwapped(newParams);
+
+        return schema.withAddedOrUpdated(newKeyspace);
+    }
+
+    @Override
+    SchemaChange schemaChangeEvent(KeyspacesDiff diff)
+    {
+        return new SchemaChange(Change.UPDATED, keyspaceName);
+    }
+
+    @Override
+    public void authorize(ClientState client)
+    {
+        client.ensureKeyspacePermission(keyspaceName, Permission.ALTER);
+    }
+
+    @Override
+    public AuditLogContext getAuditLogContext()
+    {
+        return new AuditLogContext(AuditLogEntryType.SECURITY_LABEL_KEYSPACE, keyspaceName);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s (%s, %s)", getClass().getSimpleName(), keyspaceName, description);
+    }
+
+    public static final class Raw extends CQLStatement.Raw
+    {
+        private final String keyspaceName;
+        private final String securityLabel;
+        private final String provider;
+
+        public Raw(String keyspaceName, String securityLabel, String provider)
+        {
+            this.keyspaceName = keyspaceName;
+            this.securityLabel = securityLabel;
+            this.provider = provider;
+        }
+
+        @Override
+        public SecurityLabelOnKeyspaceStatement prepare(ClientState state)
+        {
+            return new SecurityLabelOnKeyspaceStatement(keyspaceName, securityLabel, provider);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnTableStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnTableStatement.java
new file mode 100644
index 0000000000..969014c8ab
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnTableStatement.java
@@ -0,0 +1,152 @@
+/*
+ * 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.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 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 [FOR provider_name] 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 NULL.
+ * </p>
+ * <h3>Provider Support</h3>
+ * <p>
+ * The optional {@code FOR provider_name} clause is accepted in the syntax to support future
+ * extensibility. Providers are intended to be pluggable security policy modules that can
+ * interpret and enforce access controls based on security labels. However, provider
+ * functionality is not currently implemented. If a provider is specified, a warning will be
+ * issued but the security label will still be applied. The provider parameter is reserved
+ * for future use.
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre>
+ * // Basic syntax without provider
+ * SECURITY LABEL ON TABLE users IS 'restricted';
+ *
+ * // Syntax with provider (accepted but not yet implemented)
+ * SECURITY LABEL FOR custom_provider ON TABLE users IS 'classified';
+ *
+ * // Remove security label
+ * SECURITY LABEL ON TABLE users IS NULL;
+ * </pre>
+ * </p>
+ *
+ * @see SecurityLabelOnKeyspaceStatement
+ * @see SecurityLabelOnColumnStatement
+ * @see SecurityLabelOnUserTypeStatement
+ */
+public final class SecurityLabelOnTableStatement extends SchemaDescriptionStatement
+{
+    private final String tableName;
+    private final String provider;
+
+    public SecurityLabelOnTableStatement(String keyspaceName, String tableName, String securityLabel, String provider)
+    {
+        super(keyspaceName, securityLabel, DescriptionType.SECURITY_LABEL);
+        this.tableName = tableName;
+        this.provider = provider;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+
+        TableMetadata table = validateAndGetTable(schema, tableName);
+        providerWarning(provider);
+
+        TableParams newParams = table.params.unbuild().securityLabel(effectiveDescription()).build();
+        TableMetadata newTable = table.withSwapped(newParams);
+
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        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, description);
+    }
+
+    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);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeFieldStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeFieldStatement.java
new file mode 100644
index 0000000000..fee6fa6b2f
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeFieldStatement.java
@@ -0,0 +1,157 @@
+/*
+ * 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.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;
+
+/**
+ * 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 [FOR provider_name] 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 NULL.
+ * </p>
+ * <h3>Provider Support</h3>
+ * <p>
+ * The optional {@code FOR provider_name} clause is accepted in the syntax to support future
+ * extensibility. Providers are intended to be pluggable security policy modules that can
+ * interpret and enforce access controls based on security labels. However, provider
+ * functionality is not currently implemented. If a provider is specified, a warning will be
+ * issued but the security label will still be applied. The provider parameter is reserved
+ * for future use.
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre>
+ * // Basic syntax without provider
+ * SECURITY LABEL ON FIELD address.street IS 'public';
+ *
+ * // Syntax with provider (accepted but not yet implemented)
+ * SECURITY LABEL FOR custom_provider ON FIELD address.street IS 'confidential';
+ *
+ * // Remove security label
+ * SECURITY LABEL ON FIELD address.street IS NULL;
+ * </pre>
+ * </p>
+ *
+ * @see SecurityLabelOnUserTypeStatement
+ * @see SecurityLabelOnColumnStatement
+ */
+public final class SecurityLabelOnUserTypeFieldStatement extends SchemaDescriptionStatement
+{
+    private final String typeName;
+    private final FieldIdentifier fieldName;
+    private final String provider;
+
+    public SecurityLabelOnUserTypeFieldStatement(String keyspaceName, String typeName, FieldIdentifier fieldName, String securityLabel, String provider)
+    {
+        super(keyspaceName, securityLabel, DescriptionType.SECURITY_LABEL);
+        this.typeName = typeName;
+        this.fieldName = fieldName;
+        this.provider = provider;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        UserType type = validateAndGetTypeField(schema, typeName, fieldName);
+
+        if (provider != null)
+            ClientWarn.instance.warn("Provider functionality not implemented." +
+                                     " Provider '" + provider + "' will not be loaded or invoked.");
+
+        UserType newType = type.withFieldSecurityLabel(fieldName, effectiveDescription());
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        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.ensureAllTablesPermission(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, description);
+    }
+
+    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 SecurityLabelOnUserTypeFieldStatement prepare(ClientState state)
+        {
+            String keyspaceName = typeName.hasKeyspace() ? typeName.getKeyspace() : state.getKeyspace();
+            return new SecurityLabelOnUserTypeFieldStatement(keyspaceName,
+                                                             typeName.getStringTypeName(),
+                                                             fieldName,
+                                                             securityLabel,
+                                                             provider);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeStatement.java
new file mode 100644
index 0000000000..cd30e456a3
--- /dev/null
+++ b/src/java/org/apache/cassandra/cql3/statements/schema/SecurityLabelOnUserTypeStatement.java
@@ -0,0 +1,148 @@
+/*
+ * 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.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;
+
+/**
+ * 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 [FOR provider_name] 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 NULL.
+ * </p>
+ * <h3>Provider Support</h3>
+ * <p>
+ * The optional {@code FOR provider_name} clause is accepted in the syntax to support future
+ * extensibility. Providers are intended to be pluggable security policy modules that can
+ * interpret and enforce access controls based on security labels. However, provider
+ * functionality is not currently implemented. If a provider is specified, a warning will be
+ * issued but the security label will still be applied. The provider parameter is reserved
+ * for future use.
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre>
+ * // Basic syntax without provider
+ * SECURITY LABEL ON TYPE address IS 'internal';
+ *
+ * // Syntax with provider (accepted but not yet implemented)
+ * SECURITY LABEL FOR custom_provider ON TYPE address IS 'restricted';
+ *
+ * // Remove security label
+ * SECURITY LABEL ON TYPE address IS NULL;
+ * </pre>
+ * </p>
+ *
+ * @see SecurityLabelOnKeyspaceStatement
+ * @see SecurityLabelOnTableStatement
+ * @see SecurityLabelOnColumnStatement
+ */
+public final class SecurityLabelOnUserTypeStatement extends SchemaDescriptionStatement
+{
+    private final String typeName;
+    private final String provider;
+
+    public SecurityLabelOnUserTypeStatement(String keyspaceName, String typeName, String securityLabel, String provider)
+    {
+        super(keyspaceName, securityLabel, DescriptionType.SECURITY_LABEL);
+        this.typeName = typeName;
+        this.provider = provider;
+    }
+
+    @Override
+    public Keyspaces apply(ClusterMetadata metadata)
+    {
+        Keyspaces schema = metadata.schema.getKeyspaces();
+        UserType type = validateAndGetType(schema, typeName);
+        providerWarning(provider);
+
+        UserType newType = type.withSecurityLabel(effectiveDescription());
+        KeyspaceMetadata keyspace = schema.getNullable(keyspaceName);
+        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.ensureAllTablesPermission(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)", getClass().getSimpleName(), keyspaceName, typeName, description);
+    }
+
+    public static final class Raw extends CQLStatement.Raw
+    {
+        private final UTName typeName;
+        private final String securityLabel;
+        private final String provider;
+
+        public Raw(UTName typeName, String securityLabel, String provider)
+        {
+            this.typeName = typeName;
+            this.securityLabel = securityLabel;
+            this.provider = provider;
+        }
+
+        @Override
+        public SecurityLabelOnUserTypeStatement prepare(ClientState state)
+        {
+            String keyspaceName = typeName.hasKeyspace() ? typeName.getKeyspace() : state.getKeyspace();
+            return new SecurityLabelOnUserTypeStatement(keyspaceName,
+                                                        typeName.getStringTypeName(),
+                                                        securityLabel,
+                                                        provider);
+        }
+    }
+}
diff --git a/src/java/org/apache/cassandra/db/marshal/UserType.java b/src/java/org/apache/cassandra/db/marshal/UserType.java
index 15ab78e82a..01d1d6a64e 100644
--- a/src/java/org/apache/cassandra/db/marshal/UserType.java
+++ b/src/java/org/apache/cassandra/db/marshal/UserType.java
@@ -31,6 +31,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.cassandra.cql3.*;
+import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil;
 import org.apache.cassandra.cql3.terms.Constants;
 import org.apache.cassandra.cql3.terms.MultiElements;
 import org.apache.cassandra.cql3.terms.Term;
@@ -63,22 +64,50 @@ public class UserType extends TupleType implements SchemaElement
     private static final Logger logger = LoggerFactory.getLogger(UserType.class);
 
     private static final ConflictBehavior CONFLICT_BEHAVIOR = ConflictBehavior.get();
+    private static final String EMPTY_COMMENT = "";
+    private static final String EMPTY_SECURITY_LABEL = "";
 
     public final String keyspace;
     public final ByteBuffer name;
+    public final String comment;
+    public final String securityLabel;
     private final List<FieldIdentifier> fieldNames;
     private final List<String> stringFieldNames;
+    private final Map<FieldIdentifier, String> fieldComments;
+    private final Map<FieldIdentifier, String> fieldSecurityLabels;
     private final boolean isMultiCell;
     private final UserTypeSerializer serializer;
 
     public UserType(String keyspace, ByteBuffer name, List<FieldIdentifier> fieldNames, List<AbstractType<?>> fieldTypes, boolean isMultiCell)
+    {
+        this(keyspace, name, fieldNames, fieldTypes, isMultiCell, EMPTY_COMMENT, EMPTY_SECURITY_LABEL, Collections.emptyMap(), Collections.emptyMap());
+    }
+
+    public UserType(String keyspace, ByteBuffer name, List<FieldIdentifier> fieldNames, List<AbstractType<?>> fieldTypes, boolean isMultiCell, String comment, String securityLabel)
+    {
+        this(keyspace, name, fieldNames, fieldTypes, isMultiCell, comment, securityLabel, Collections.emptyMap(), Collections.emptyMap());
+    }
+
+    public UserType(String keyspace,
+                    ByteBuffer name,
+                    List<FieldIdentifier> fieldNames,
+                    List<AbstractType<?>> fieldTypes,
+                    boolean isMultiCell,
+                    String comment,
+                    String securityLabel,
+                    Map<FieldIdentifier, String> fieldComments,
+                    Map<FieldIdentifier, String> fieldSecurityLabels)
     {
         super(fieldTypes, false);
         assert fieldNames.size() == fieldTypes.size();
         this.keyspace = keyspace;
         this.name = name;
+        this.comment = comment == null ? EMPTY_COMMENT : comment;
+        this.securityLabel = securityLabel == null ? EMPTY_SECURITY_LABEL : securityLabel;
         this.fieldNames = fieldNames;
         this.stringFieldNames = new ArrayList<>(fieldNames.size());
+        this.fieldComments = Collections.unmodifiableMap(new HashMap<>(fieldComments));
+        this.fieldSecurityLabels = Collections.unmodifiableMap(new HashMap<>(fieldSecurityLabels));
         this.isMultiCell = isMultiCell;
 
         LinkedHashMap<String , TypeSerializer<?>> fieldSerializers = new LinkedHashMap<>(fieldTypes.size());
@@ -106,7 +135,7 @@ public class UserType extends TupleType implements SchemaElement
             columnTypes.add(p.right);
         }
 
-        return new UserType(keyspace, name, columnNames, columnTypes, true);
+        return new UserType(keyspace, name, columnNames, columnTypes, true, EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
     }
 
     @Override
@@ -311,13 +340,13 @@ public class UserType extends TupleType implements SchemaElement
     @Override
     public UserType freeze()
     {
-        return isMultiCell ? new UserType(keyspace, name, fieldNames, fieldTypes(), false) : this;
+        return isMultiCell ? new UserType(keyspace, name, fieldNames, fieldTypes(), false, comment, securityLabel, fieldComments, fieldSecurityLabels) : this;
     }
 
     @Override
     public UserType unfreeze()
     {
-        return isMultiCell ? this : new UserType(keyspace, name, fieldNames, fieldTypes(), true);
+        return isMultiCell ? this : new UserType(keyspace, name, fieldNames, fieldTypes(), true, comment, securityLabel, fieldComments, fieldSecurityLabels);
     }
 
     @Override
@@ -442,14 +471,66 @@ public class UserType extends TupleType implements SchemaElement
         {
             return isMultiCell == udt.isMultiCell
                  ? udt
-                 : new UserType(keyspace, name, udt.fieldNames(), udt.fieldTypes(), isMultiCell);
+                 : new UserType(keyspace, name, udt.fieldNames(), udt.fieldTypes(), isMultiCell, udt.comment, udt.securityLabel, udt.fieldComments, udt.fieldSecurityLabels);
         }
 
         return new UserType(keyspace,
                             name,
                             fieldNames,
                             Lists.newArrayList(transform(fieldTypes(), t -> t.withUpdatedUserType(udt))),
-                            isMultiCell());
+                            isMultiCell(),
+                            comment,
+                            securityLabel,
+                            fieldComments,
+                            fieldSecurityLabels);
+    }
+
+    public UserType withComment(String comment)
+    {
+        return new UserType(keyspace, name, fieldNames, fieldTypes(), isMultiCell(), comment, securityLabel, fieldComments, fieldSecurityLabels);
+    }
+
+    public UserType withSecurityLabel(String securityLabel)
+    {
+        return new UserType(keyspace, name, fieldNames, fieldTypes(), isMultiCell(), comment, securityLabel, fieldComments, fieldSecurityLabels);
+    }
+
+    public UserType withFieldComment(FieldIdentifier fieldName, String fieldComment)
+    {
+        if (fieldPosition(fieldName) == -1)
+            throw new IllegalArgumentException(String.format("Field '%s' doesn't exist in type '%s.%s'", fieldName, keyspace, getNameAsString()));
+
+        Map<FieldIdentifier, String> newFieldComments = new HashMap<>(fieldComments);
+        if (fieldComment == null || fieldComment.isEmpty())
+            newFieldComments.remove(fieldName);
+        else
+            newFieldComments.put(fieldName, fieldComment);
+
+        return new UserType(keyspace, name, fieldNames, fieldTypes(), isMultiCell(), comment, securityLabel, newFieldComments, fieldSecurityLabels);
+    }
+
+    public UserType withFieldSecurityLabel(FieldIdentifier fieldName, String fieldSecurityLabel)
+    {
+        if (fieldPosition(fieldName) == -1)
+            throw new IllegalArgumentException(String.format("Field '%s' doesn't exist in type '%s.%s'", fieldName, keyspace, getNameAsString()));
+
+        Map<FieldIdentifier, String> newFieldSecurityLabels = new HashMap<>(fieldSecurityLabels);
+        if (fieldSecurityLabel == null || fieldSecurityLabel.isEmpty())
+            newFieldSecurityLabels.remove(fieldName);
+        else
+            newFieldSecurityLabels.put(fieldName, fieldSecurityLabel);
+
+        return new UserType(keyspace, name, fieldNames, fieldTypes(), isMultiCell(), comment, securityLabel, fieldComments, newFieldSecurityLabels);
+    }
+
+    public String fieldComment(FieldIdentifier fieldName)
+    {
+        return fieldComments.getOrDefault(fieldName, "");
+    }
+
+    public String fieldSecurityLabel(FieldIdentifier fieldName)
+    {
+        return fieldSecurityLabels.getOrDefault(fieldName, "");
     }
 
     @Override
@@ -629,6 +710,16 @@ public class UserType extends TupleType implements SchemaElement
         return builder.toString();
     }
 
+    @Override
+    public String describe(boolean withWarnings, boolean withInternals, boolean ifNotExists)
+    {
+        String baseStatement = toCqlString(withWarnings, withInternals, ifNotExists);
+        StringBuilder result = new StringBuilder(baseStatement);
+        SchemaDescriptionsUtil.appendCommentOnType(result, this);
+        SchemaDescriptionsUtil.appendSecurityLabelOnType(result, this);
+        return result.toString();
+    }
+
     @Override
     protected String componentOrFieldName(int i)
     {
diff --git a/src/java/org/apache/cassandra/db/virtual/AbstractSchemaMetadataTable.java b/src/java/org/apache/cassandra/db/virtual/AbstractSchemaMetadataTable.java
new file mode 100644
index 0000000000..dbcb3d95cc
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/AbstractSchemaMetadataTable.java
@@ -0,0 +1,312 @@
+/*
+ * 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.cql3.FieldIdentifier;
+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 (FieldIdentifier field : udt.fieldNames())
+                    addFieldRow(result, keyspace, udt, field.toString());
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public DataSet data(DecoratedKey partitionKey)
+    {
+        SimpleDataSet result = new SimpleDataSet(metadata());
+
+        ByteBuffer key = partitionKey.getKey();
+        ByteBuffer[] components = ((CompositeType) metadata().partitionKeyType).split(key);
+        String objectType = UTF8Type.instance.compose(components[0]);
+        String keyspaceName = UTF8Type.instance.compose(components[1]);
+
+        KeyspaceMetadata keyspace = Schema.instance.getKeyspaceMetadata(keyspaceName);
+        if (keyspace == null)
+            throw new InvalidRequestException("Unknown keyspace: '" + keyspaceName + '\'');
+
+        ObjectType type = ObjectType.parse(objectType);
+        if (type == null)
+            throw new InvalidRequestException("Unknown object type: '" + objectType +
+                                              "'. Valid types are: " + Arrays.toString(ObjectType.values()));
+
+        switch (type)
+        {
+            case KEYSPACE:
+                addKeyspaceRow(result, keyspace);
+                break;
+            case TABLE:
+                for (TableMetadata table : keyspace.tables)
+                    addTableRow(result, keyspace, table);
+                break;
+            case COLUMN:
+                for (TableMetadata table : keyspace.tables)
+                    for (ColumnMetadata column : table.columns())
+                        addColumnRow(result, keyspace, table, column);
+                break;
+            case UDT:
+                for (UserType udt : keyspace.types)
+                    addUdtRow(result, keyspace, udt);
+                break;
+            case FIELD:
+                for (UserType udt : keyspace.types)
+                    for (FieldIdentifier field : udt.fieldNames())
+                        addFieldRow(result, keyspace, udt, field.toString());
+                break;
+        }
+        return result;
+    }
+
+    /**
+     * Add a row for keyspace metadata if present.
+     */
+    private void addKeyspaceRow(SimpleDataSet result, KeyspaceMetadata keyspace)
+    {
+        String metadata = Strings.emptyToNull(extractKeyspaceMetadata(keyspace));
+
+        if (metadata != null)
+            result.row(ObjectType.KEYSPACE.toString(), keyspace.name, EMPTY_VALUE, EMPTY_VALUE, EMPTY_VALUE, EMPTY_VALUE)
+                  .column(schemaTableType.columnName, metadata);
+    }
+
+    /**
+     * Add a row for table metadata if present.
+     */
+    private void addTableRow(SimpleDataSet result, KeyspaceMetadata keyspace, TableMetadata table)
+    {
+        String metadata = Strings.emptyToNull(extractTableMetadata(table));
+
+        if (metadata != null)
+            result.row(ObjectType.TABLE.toString(), keyspace.name, table.name, EMPTY_VALUE, EMPTY_VALUE, EMPTY_VALUE)
+                  .column(schemaTableType.columnName, metadata);
+    }
+
+    /**
+     * Add a row for column metadata if present.
+     */
+    private void addColumnRow(SimpleDataSet result, KeyspaceMetadata keyspace, TableMetadata table, ColumnMetadata column)
+    {
+        String metadata = Strings.emptyToNull(extractColumnMetadata(column));
+
+        if (metadata != null)
+            result.row(ObjectType.COLUMN.toString(), keyspace.name, table.name, column.name.toString(), EMPTY_VALUE, EMPTY_VALUE)
+                  .column(schemaTableType.columnName, metadata);
+    }
+
+    /**
+     * Add a row for UDT metadata if present.
+     */
+    private void addUdtRow(SimpleDataSet result, KeyspaceMetadata keyspace, UserType udt)
+    {
+        String metadata = Strings.emptyToNull(extractUdtMetadata(udt));
+
+        if (metadata != null)
+            result.row(ObjectType.UDT.toString(), keyspace.name, EMPTY_VALUE, EMPTY_VALUE, udt.getNameAsString(), EMPTY_VALUE)
+                  .column(schemaTableType.columnName, metadata);
+    }
+
+    /**
+     * Add a row for UDT field metadata if present.
+     */
+    private void addFieldRow(SimpleDataSet result, KeyspaceMetadata keyspace, UserType udt, String fieldName)
+    {
+        String metadata = Strings.emptyToNull(extractFieldMetadata(udt, fieldName));
+
+        if (metadata != null)
+            result.row(ObjectType.FIELD.toString(), keyspace.name, EMPTY_VALUE, EMPTY_VALUE, udt.getNameAsString(), fieldName)
+                  .column(schemaTableType.columnName, metadata);
+    }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/SchemaCommentsTable.java b/src/java/org/apache/cassandra/db/virtual/SchemaCommentsTable.java
new file mode 100644
index 0000000000..c67395cb79
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/SchemaCommentsTable.java
@@ -0,0 +1,92 @@
+/*
+ * 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.cql3.FieldIdentifier;
+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';
+ * </pre>
+ * </p>
+ */
+final class SchemaCommentsTable extends AbstractSchemaMetadataTable
+{
+    SchemaCommentsTable(String keyspace)
+    {
+        super(keyspace, SchemaTableType.COMMENT);
+    }
+
+    @Override
+    protected String extractKeyspaceMetadata(KeyspaceMetadata keyspace)
+    {
+        return keyspace.params.comment;
+    }
+
+    @Override
+    protected String extractTableMetadata(TableMetadata table)
+    {
+        return table.params.comment;
+    }
+
+    @Override
+    protected String extractColumnMetadata(ColumnMetadata column)
+    {
+        return column.comment;
+    }
+
+    @Override
+    protected String extractUdtMetadata(UserType udt)
+    {
+        return udt.comment;
+    }
+
+    @Override
+    protected String extractFieldMetadata(UserType udt, String fieldName)
+    {
+        return udt.fieldComment(FieldIdentifier.forUnquoted(fieldName));
+    }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/SchemaSecurityLabelsTable.java b/src/java/org/apache/cassandra/db/virtual/SchemaSecurityLabelsTable.java
new file mode 100644
index 0000000000..1b589649d2
--- /dev/null
+++ b/src/java/org/apache/cassandra/db/virtual/SchemaSecurityLabelsTable.java
@@ -0,0 +1,92 @@
+/*
+ * 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.cql3.FieldIdentifier;
+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 security labels on schema elements.
+ * <p>
+ * This table provides a unified view of security label metadata across:
+ * <ul>
+ *   <li>Keyspaces - security labels on keyspaces</li>
+ *   <li>Tables - security labels on tables</li>
+ *   <li>Columns - security labels on columns</li>
+ *   <li>User-Defined Types (UDTs) - security labels 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 security labels in the system
+ * SELECT * FROM system_views.schema_security_labels;
+ *
+ * -- All table security labels in a specific keyspace
+ * SELECT * FROM system_views.schema_security_labels
+ * WHERE object_type = 'TABLE' AND keyspace_name = 'my_keyspace';
+ *
+ * -- All column security labels across all keyspaces
+ * SELECT * FROM system_views.schema_security_labels WHERE object_type = 'COLUMN';
+ * </pre>
+ * </p>
+ */
+final class SchemaSecurityLabelsTable extends AbstractSchemaMetadataTable
+{
+    SchemaSecurityLabelsTable(String keyspace)
+    {
+        super(keyspace, SchemaTableType.SECURITY_LABEL);
+    }
+
+    @Override
+    protected String extractKeyspaceMetadata(KeyspaceMetadata keyspace)
+    {
+        return keyspace.params.securityLabel;
+    }
+
+    @Override
+    protected String extractTableMetadata(TableMetadata table)
+    {
+        return table.params.securityLabel;
+    }
+
+    @Override
+    protected String extractColumnMetadata(ColumnMetadata column)
+    {
+        return column.securityLabel;
+    }
+
+    @Override
+    protected String extractUdtMetadata(UserType udt)
+    {
+        return udt.securityLabel;
+    }
+
+    @Override
+    protected String extractFieldMetadata(UserType udt, String fieldName)
+    {
+        return udt.fieldSecurityLabel(FieldIdentifier.forUnquoted(fieldName));
+    }
+}
diff --git a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
index 7233bb4d53..9a80d1e9b9 100644
--- a/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
+++ b/src/java/org/apache/cassandra/db/virtual/SystemViewsKeyspace.java
@@ -37,6 +37,8 @@ public final class SystemViewsKeyspace extends VirtualKeyspace
                     .add(new ClientsTable(VIRTUAL_VIEWS))
                     .add(new SettingsTable(VIRTUAL_VIEWS))
                     .add(new SystemPropertiesTable(VIRTUAL_VIEWS))
+                    .add(new SchemaCommentsTable(VIRTUAL_VIEWS))
+                    .add(new SchemaSecurityLabelsTable(VIRTUAL_VIEWS))
                     .add(new SSTableTasksTable(VIRTUAL_VIEWS))
                     // Fully backward/forward compatible with the legace ThreadPoolsTable under the same "system_views.thread_pools" name.
                     .add(CollectionVirtualTableAdapter.create(VIRTUAL_VIEWS,
diff --git a/src/java/org/apache/cassandra/schema/ColumnMetadata.java b/src/java/org/apache/cassandra/schema/ColumnMetadata.java
index 817c99327b..69cb56bc59 100644
--- a/src/java/org/apache/cassandra/schema/ColumnMetadata.java
+++ b/src/java/org/apache/cassandra/schema/ColumnMetadata.java
@@ -251,7 +251,22 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
                           @Nullable ColumnMask mask,
                           @Nonnull ColumnConstraints columnConstraints)
     {
-        super(ksName, cfName, name, type);
+        this(ksName, cfName, name, type, uniqueId, position, kind, mask, columnConstraints, EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
+    }
+
+    public ColumnMetadata(String ksName,
+                          String cfName,
+                          ColumnIdentifier name,
+                          AbstractType<?> type,
+                          int uniqueId,
+                          int position,
+                          Kind kind,
+                          @Nullable ColumnMask mask,
+                          @Nonnull ColumnConstraints columnConstraints,
+                          String comment,
+                          String securityLabel)
+    {
+        super(ksName, cfName, name, type, comment, securityLabel);
         this.uniqueId = uniqueId;
         assert name != null && type != null && kind != null;
         assert (position == NO_POSITION) == !kind.isPrimaryKeyKind(); // The position really only make sense for partition and clustering columns (and those must have one),
@@ -302,22 +317,32 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
 
     public ColumnMetadata copy()
     {
-        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, mask, columnConstraints);
+        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, mask, columnConstraints, comment, securityLabel);
     }
 
     public ColumnMetadata withNewName(ColumnIdentifier newName)
     {
-        return new ColumnMetadata(ksName, cfName, newName, type, uniqueId, position, kind, mask, columnConstraints);
+        return new ColumnMetadata(ksName, cfName, newName, type, uniqueId, position, kind, mask, columnConstraints, comment, securityLabel);
     }
 
     public ColumnMetadata withNewType(AbstractType<?> newType)
     {
-        return new ColumnMetadata(ksName, cfName, name, newType, uniqueId, position, kind, mask, columnConstraints);
+        return new ColumnMetadata(ksName, cfName, name, newType, uniqueId, position, kind, mask, columnConstraints, comment, securityLabel);
     }
 
     public ColumnMetadata withNewMask(@Nullable ColumnMask newMask)
     {
-        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, newMask, columnConstraints);
+        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, newMask, columnConstraints, comment, securityLabel);
+    }
+
+    public ColumnMetadata withNewComment(String newComment)
+    {
+        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, mask, columnConstraints, newComment, securityLabel);
+    }
+
+    public ColumnMetadata withNewSecurityLabel(String newSecurityLabel)
+    {
+        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, mask, columnConstraints, comment, newSecurityLabel);
     }
 
     public boolean isPartitionKey()
@@ -397,7 +422,13 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
     public ColumnMetadata withNewColumnConstraints(ColumnConstraints constraints)
     {
         constraints.validate(this);
-        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, mask, constraints);
+        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, mask, constraints, comment, securityLabel);
+    }
+
+
+    public ColumnMetadata withSecurityLabel(String securityLabel)
+    {
+        return new ColumnMetadata(ksName, cfName, name, type, uniqueId, position, kind, mask, columnConstraints, comment, securityLabel);
     }
 
     public void removeColumnConstraints()
@@ -739,6 +770,11 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
             }
             if (version.isAtLeast(Version.V7))
                 out.writeVInt32(t.uniqueId);
+            if (version.isAtLeast(Version.V8))
+            {
+                out.writeUTF(t.comment);
+                out.writeUTF(t.securityLabel);
+            }
         }
 
         public ColumnMetadata deserialize(DataInputPlus in, Types types, UserFunctions functions, Version version) throws IOException
@@ -765,7 +801,14 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
             int uniqueId = NO_UNIQUE_ID;
             if (version.isAtLeast(Version.V7))
                 uniqueId = in.readVInt32();
-            return new ColumnMetadata(ksName, tableName, new ColumnIdentifier(nameBB, name), type, uniqueId, position, kind, mask, constraints);
+            String comment = EMPTY_COMMENT;
+            String securityLabel = EMPTY_SECURITY_LABEL;
+            if (version.isAtLeast(Version.V8))
+            {
+                comment = in.readUTF();
+                securityLabel = in.readUTF();
+            }
+            return new ColumnMetadata(ksName, tableName, new ColumnIdentifier(nameBB, name), type, uniqueId, position, kind, mask, constraints, comment, securityLabel);
         }
 
         public long serializedSize(ColumnMetadata t, Version version)
@@ -777,6 +820,12 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
                 if (t.hasConstraint())
                     constraintsSize += t.getColumnConstraints().serializer().serializedSize(t.columnConstraints, version);
             }
+            long commentsAndSecurityLabelSize = 0;
+            if (version.isAtLeast(Version.V8))
+            {
+                commentsAndSecurityLabelSize += sizeof(t.comment);
+                commentsAndSecurityLabelSize += sizeof(t.securityLabel);
+            }
             return sizeof(t.ksName) +
                    sizeof(t.cfName) +
                    sizeof(t.kind.name()) +
@@ -788,7 +837,8 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
                    BOOL_SIZE +
                    ((t.mask == null) ? 0 : ColumnMask.serializer.serializedSize(t.mask, version)) +
                    constraintsSize +
-                   (version.isAtLeast(Version.V7) ? sizeofVInt(t.uniqueId) : 0);
+                   (version.isAtLeast(Version.V7) ? sizeofVInt(t.uniqueId) : 0) +
+                   commentsAndSecurityLabelSize;
         }
     }
 }
diff --git a/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java b/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java
index 703f1c0298..32e3eed97e 100644
--- a/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java
+++ b/src/java/org/apache/cassandra/schema/KeyspaceMetadata.java
@@ -37,6 +37,7 @@ import org.apache.cassandra.cql3.SchemaElement;
 import org.apache.cassandra.cql3.functions.Function;
 import org.apache.cassandra.cql3.functions.UDAggregate;
 import org.apache.cassandra.cql3.functions.UDFunction;
+import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil;
 import org.apache.cassandra.db.marshal.UserType;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.RequestValidationException;
@@ -397,6 +398,16 @@ public final class KeyspaceMetadata implements SchemaElement
         return builder.toString();
     }
 
+    @Override
+    public String describe(boolean withWarnings, boolean withInternals, boolean ifNotExists)
+    {
+        String cqlString = toCqlString(withWarnings, withInternals, ifNotExists);
+        StringBuilder result = new StringBuilder(cqlString);
+        SchemaDescriptionsUtil.appendCommentOnKeyspace(result, this);
+        SchemaDescriptionsUtil.appendSecurityLabelOnKeyspace(result, this);
+        return result.toString();
+    }
+
     public void validate(ClusterMetadata metadata)
     {
         validateKeyspaceName(name, ConfigurationException::new);
diff --git a/src/java/org/apache/cassandra/schema/KeyspaceParams.java b/src/java/org/apache/cassandra/schema/KeyspaceParams.java
index f683fabf49..ffc3167358 100644
--- a/src/java/org/apache/cassandra/schema/KeyspaceParams.java
+++ b/src/java/org/apache/cassandra/schema/KeyspaceParams.java
@@ -44,6 +44,8 @@ public final class KeyspaceParams
     public static final Serializer serializer = new Serializer();
 
     public static final boolean DEFAULT_DURABLE_WRITES = true;
+    private static final String EMPTY_COMMENT = "";
+    private static final String EMPTY_SECURITY_LABEL = "";
 
     /**
      * This determines durable writes for the {@link org.apache.cassandra.schema.SchemaConstants#SCHEMA_KEYSPACE_NAME}
@@ -57,7 +59,9 @@ public final class KeyspaceParams
     {
         DURABLE_WRITES,
         REPLICATION,
-        FAST_PATH;
+        FAST_PATH,
+        COMMENT,
+        SECURITY_LABEL;
 
         @Override
         public String toString()
@@ -69,17 +73,26 @@ public final class KeyspaceParams
     public final boolean durableWrites;
     public final ReplicationParams replication;
     public final FastPathStrategy fastPath;
+    public final String comment;
+    public final String securityLabel;
 
     public KeyspaceParams(boolean durableWrites, ReplicationParams replication, FastPathStrategy fastPath)
+    {
+        this(durableWrites, replication, fastPath, EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
+    }
+
+    public KeyspaceParams(boolean durableWrites, ReplicationParams replication, FastPathStrategy fastPath, String comment, String securityLabel)
     {
         this.durableWrites = durableWrites;
         this.replication = replication;
         this.fastPath = fastPath;
+        this.comment = comment == null ? EMPTY_COMMENT : comment;
+        this.securityLabel = securityLabel == null ? EMPTY_SECURITY_LABEL : securityLabel;
     }
 
     public static KeyspaceParams create(boolean durableWrites, Map<String, String> replication, FastPathStrategy fastPath)
     {
-        return new KeyspaceParams(durableWrites, ReplicationParams.fromMap(replication), fastPath);
+        return new KeyspaceParams(durableWrites, ReplicationParams.fromMap(replication), fastPath, EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
     }
 
     public static KeyspaceParams create(boolean durableWrites, Map<String, String> replication, Map<String, String> fastPath)
@@ -94,32 +107,42 @@ public final class KeyspaceParams
 
     public static KeyspaceParams local()
     {
-        return new KeyspaceParams(DEFAULT_LOCAL_DURABLE_WRITES, ReplicationParams.local(), FastPathStrategy.simple());
+        return new KeyspaceParams(DEFAULT_LOCAL_DURABLE_WRITES, ReplicationParams.local(), FastPathStrategy.simple(), EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
     }
 
     public static KeyspaceParams simple(int replicationFactor)
     {
-        return new KeyspaceParams(true, ReplicationParams.simple(replicationFactor), FastPathStrategy.simple());
+        return new KeyspaceParams(true, ReplicationParams.simple(replicationFactor), FastPathStrategy.simple(), EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
     }
 
     public static KeyspaceParams simple(String replicationFactor)
     {
-        return new KeyspaceParams(true, ReplicationParams.simple(replicationFactor), FastPathStrategy.simple());
+        return new KeyspaceParams(true, ReplicationParams.simple(replicationFactor), FastPathStrategy.simple(), EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
     }
 
     public static KeyspaceParams simpleTransient(int replicationFactor)
     {
-        return new KeyspaceParams(false, ReplicationParams.simple(replicationFactor), FastPathStrategy.simple());
+        return new KeyspaceParams(false, ReplicationParams.simple(replicationFactor), FastPathStrategy.simple(), EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
     }
 
     public static KeyspaceParams nts(Object... args)
     {
-        return new KeyspaceParams(true, ReplicationParams.nts(args), FastPathStrategy.simple());
+        return new KeyspaceParams(true, ReplicationParams.nts(args), FastPathStrategy.simple(), EMPTY_COMMENT, EMPTY_SECURITY_LABEL);
     }
 
     public KeyspaceParams withSwapped(ReplicationParams params)
     {
-        return new KeyspaceParams(durableWrites, params, fastPath);
+        return new KeyspaceParams(durableWrites, params, fastPath, comment, securityLabel);
+    }
+
+    public KeyspaceParams withComment(String comment)
+    {
+        return new KeyspaceParams(durableWrites, replication, fastPath, comment, securityLabel);
+    }
+
+    public KeyspaceParams withSecurityLabel(String securityLabel)
+    {
+        return new KeyspaceParams(durableWrites, replication, fastPath, comment, securityLabel);
     }
 
     public void validate(String name, ClientState state, ClusterMetadata metadata)
@@ -154,6 +177,8 @@ public final class KeyspaceParams
                           .add(Option.DURABLE_WRITES.toString(), durableWrites)
                           .add(Option.REPLICATION.toString(), replication)
                           .add(Option.FAST_PATH.toString(), fastPath.toString())
+                          .add(Option.COMMENT.toString(), comment)
+                          .add(Option.SECURITY_LABEL.toString(), securityLabel)
                           .toString();
     }
 
@@ -165,6 +190,11 @@ public final class KeyspaceParams
             out.writeBoolean(t.durableWrites);
             if (version.isAtLeast(MIN_ACCORD_VERSION))
                 FastPathStrategy.serializer.serialize(t.fastPath, out, version);
+            if (version.isAtLeast(Version.V8))
+            {
+                out.writeUTF(t.comment);
+                out.writeUTF(t.securityLabel);
+            }
         }
 
         public KeyspaceParams deserialize(DataInputPlus in, Version version) throws IOException
@@ -174,13 +204,22 @@ public final class KeyspaceParams
             FastPathStrategy fastPath = version.isAtLeast(MIN_ACCORD_VERSION)
                     ? FastPathStrategy.serializer.deserialize(in, version)
                     : FastPathStrategy.simple();
-            return new KeyspaceParams(durableWrites, params, fastPath);
+            String comment = EMPTY_COMMENT;
+            String securityLabel = EMPTY_SECURITY_LABEL;
+            if (version.isAtLeast(Version.V8))
+            {
+                comment = in.readUTF();
+                securityLabel = in.readUTF();
+            }
+            return new KeyspaceParams(durableWrites, params, fastPath, comment, securityLabel);
         }
 
         public long serializedSize(KeyspaceParams t, Version version)
         {
             return ReplicationParams.serializer.serializedSize(t.replication, version) +
                    TypeSizes.sizeof(t.durableWrites) +
+                   (version.isAtLeast(Version.V8) ? TypeSizes.sizeof(t.comment) : 0) +
+                   (version.isAtLeast(Version.V8) ? TypeSizes.sizeof(t.securityLabel) : 0) +
                    (version.isAtLeast(MIN_ACCORD_VERSION) ? FastPathStrategy.serializer.serializedSize(t.fastPath, version) : 0);
         }
     }
diff --git a/src/java/org/apache/cassandra/schema/TableMetadata.java b/src/java/org/apache/cassandra/schema/TableMetadata.java
index 1336747379..1c5b577793 100644
--- a/src/java/org/apache/cassandra/schema/TableMetadata.java
+++ b/src/java/org/apache/cassandra/schema/TableMetadata.java
@@ -56,6 +56,7 @@ import org.apache.cassandra.cql3.constraints.InvalidConstraintDefinitionExceptio
 import org.apache.cassandra.cql3.constraints.NotNullConstraint;
 import org.apache.cassandra.cql3.functions.Function;
 import org.apache.cassandra.cql3.functions.masking.ColumnMask;
+import org.apache.cassandra.cql3.statements.SchemaDescriptionsUtil;
 import org.apache.cassandra.db.Clustering;
 import org.apache.cassandra.db.ClusteringComparator;
 import org.apache.cassandra.db.Columns;
@@ -1070,7 +1071,7 @@ public class TableMetadata implements SchemaElement
 
         static ColumnMetadata withUniqueId(ColumnMetadata prev, int uniqueId)
         {
-            return new ColumnMetadata(prev.ksName, prev.cfName, prev.name, prev.type, uniqueId, prev.position(), prev.kind, prev.getMask(), prev.getColumnConstraints());
+            return new ColumnMetadata(prev.ksName, prev.cfName, prev.name, prev.type, uniqueId, prev.position(), prev.kind, prev.getMask(), prev.getColumnConstraints(), prev.comment, prev.securityLabel);
         }
 
         public Builder id(TableId val)
@@ -1132,6 +1133,12 @@ public class TableMetadata implements SchemaElement
             return this;
         }
 
+        public Builder securityLabel(String val)
+        {
+            params.securityLabel(val);
+            return this;
+        }
+
         public Builder compaction(CompactionParams val)
         {
             params.compaction(val);
@@ -1482,6 +1489,32 @@ public class TableMetadata implements SchemaElement
             return this;
         }
 
+        public Builder alterColumnComment(ColumnIdentifier name, String comment)
+        {
+            ColumnMetadata column = columns.get(name.bytes);
+            if (column == null)
+                throw new IllegalArgumentException("Column " + name + " doesn't exist");
+
+            ColumnMetadata newColumn = column.withNewComment(comment);
+
+            updateColumn(column, newColumn);
+
+            return this;
+        }
+
+        public Builder alterColumnSecurityLabel(ColumnIdentifier name, String securityLabel)
+        {
+            ColumnMetadata column = columns.get(name.bytes);
+            if (column == null)
+                throw new IllegalArgumentException("Column " + name + " doesn't exist" );
+
+            ColumnMetadata newColumn = column.withNewSecurityLabel(securityLabel);
+
+            updateColumn(column, newColumn);
+
+            return this;
+        }
+
         public Builder alterColumnConstraints(ColumnIdentifier name, ColumnConstraints constraints)
         {
             ColumnMetadata column = columns.get(name.bytes);
@@ -1608,6 +1641,16 @@ public class TableMetadata implements SchemaElement
         return builder.toString();
     }
 
+    @Override
+    public String describe(boolean withWarnings, boolean withInternals, boolean ifNotExists)
+    {
+        String baseStatement = toCqlString(withWarnings, withInternals, ifNotExists);
+        StringBuilder result = new StringBuilder(baseStatement);
+        SchemaDescriptionsUtil.appendCommentOnTable(result, this);
+        SchemaDescriptionsUtil.appendSecurityLabelOnTable(result, this);
+        return result.toString();
+    }
+
     public String toCqlString(boolean withWarnings,
                               boolean withDroppedColumns,
                               boolean withInternals,
diff --git a/src/java/org/apache/cassandra/schema/TableParams.java b/src/java/org/apache/cassandra/schema/TableParams.java
index fe0e13adf5..7f27e47b5a 100644
--- a/src/java/org/apache/cassandra/schema/TableParams.java
+++ b/src/java/org/apache/cassandra/schema/TableParams.java
@@ -70,6 +70,7 @@ import static org.apache.cassandra.schema.TableParams.Option.MEMTABLE_FLUSH_PERI
 import static org.apache.cassandra.schema.TableParams.Option.MIN_INDEX_INTERVAL;
 import static org.apache.cassandra.schema.TableParams.Option.PENDING_DROP;
 import static org.apache.cassandra.schema.TableParams.Option.READ_REPAIR;
+import static org.apache.cassandra.schema.TableParams.Option.SECURITY_LABEL;
 import static org.apache.cassandra.schema.TableParams.Option.SPECULATIVE_RETRY;
 import static org.apache.cassandra.utils.LocalizeString.toLowerCaseLocalized;
 
@@ -101,7 +102,8 @@ public final class TableParams
         TRANSACTIONAL_MODE,
         TRANSACTIONAL_MIGRATION_FROM,
         PENDING_DROP,
-        AUTO_REPAIR;
+        AUTO_REPAIR,
+        SECURITY_LABEL;
 
         @Override
         public String toString()
@@ -111,6 +113,7 @@ public final class TableParams
     }
 
     public final String comment;
+    public final String securityLabel;
     public final boolean allowAutoSnapshot;
     public final double bloomFilterFpChance;
     public final double crcCheckChance;
@@ -139,6 +142,7 @@ public final class TableParams
     private TableParams(Builder builder)
     {
         comment = builder.comment;
+        securityLabel = builder.securityLabel;
         allowAutoSnapshot = builder.allowAutoSnapshot;
         bloomFilterFpChance = builder.bloomFilterFpChance == -1
                             ? builder.compaction.defaultBloomFilterFbChance()
@@ -178,6 +182,7 @@ public final class TableParams
                             .bloomFilterFpChance(params.bloomFilterFpChance)
                             .caching(params.caching)
                             .comment(params.comment)
+                            .securityLabel(params.securityLabel)
                             .compaction(params.compaction)
                             .compression(params.compression)
                             .memtable(params.memtable)
@@ -276,6 +281,7 @@ public final class TableParams
         TableParams p = (TableParams) o;
 
         return comment.equals(p.comment)
+            && securityLabel.equals(p.securityLabel)
             && additionalWritePolicy.equals(p.additionalWritePolicy)
             && allowAutoSnapshot == p.allowAutoSnapshot
             && bloomFilterFpChance == p.bloomFilterFpChance
@@ -305,6 +311,7 @@ public final class TableParams
     public int hashCode()
     {
         return Objects.hashCode(comment,
+                                securityLabel,
                                 additionalWritePolicy,
                                 allowAutoSnapshot,
                                 bloomFilterFpChance,
@@ -335,6 +342,7 @@ public final class TableParams
     {
         return MoreObjects.toStringHelper(this)
                           .add(COMMENT.toString(), comment)
+                          .add(SECURITY_LABEL.toString(), securityLabel)
                           .add(ADDITIONAL_WRITE_POLICY.toString(), additionalWritePolicy)
                           .add(ALLOW_AUTO_SNAPSHOT.toString(), allowAutoSnapshot)
                           .add(BLOOM_FILTER_FP_CHANCE.toString(), bloomFilterFpChance)
@@ -375,6 +383,8 @@ public final class TableParams
                .newLine()
                .append("AND cdc = ").append(cdc)
                .newLine()
+               // TODO: AND comment should be deprecated in future releases in favor of
+               //  JIRA CASSANDRA-20943 Introducing comments and security labels for schema elements
                .append("AND comment = ").appendWithSingleQuotes(comment)
                .newLine()
                .append("AND compaction = ").append(compaction.asMap())
@@ -431,6 +441,7 @@ public final class TableParams
     public static final class Builder
     {
         private String comment = "";
+        private String securityLabel = "";
         private boolean allowAutoSnapshot = true;
         private double bloomFilterFpChance = -1;
         private double crcCheckChance = 1.0;
@@ -470,6 +481,12 @@ public final class TableParams
             return this;
         }
 
+        public Builder securityLabel(String val)
+        {
+            securityLabel = val;
+            return this;
+        }
+
         public Builder allowAutoSnapshot(boolean val)
         {
             allowAutoSnapshot = val;
@@ -643,6 +660,8 @@ public final class TableParams
                 out.writeUnsignedVInt32(t.transactionalMigrationFrom.ordinal());
                 out.writeBoolean(t.pendingDrop);
             }
+            if (version.isAtLeast(Version.V8))
+                out.writeUTF(t.securityLabel);
         }
 
         public TableParams deserialize(DataInputPlus in, Version version) throws IOException
@@ -674,6 +693,8 @@ public final class TableParams
                        .transactionalMigrationFrom(TransactionalMigrationFromMode.fromOrdinal(in.readUnsignedVInt32()))
                        .pendingDrop(in.readBoolean());
             }
+            if (version.isAtLeast(Version.V8))
+                builder.securityLabel(in.readUTF());
             return builder.build();
         }
 
@@ -705,6 +726,10 @@ public final class TableParams
                         sizeofUnsignedVInt(t.transactionalMigrationFrom.ordinal()) +
                         sizeof(t.pendingDrop);
             }
+            if (version.isAtLeast(Version.V8))
+            {
+                size += sizeof(t.securityLabel);
+            }
             return size;
         }
 
diff --git a/src/java/org/apache/cassandra/schema/Types.java b/src/java/org/apache/cassandra/schema/Types.java
index 9b6738e10f..d9c22b94ee 100644
--- a/src/java/org/apache/cassandra/schema/Types.java
+++ b/src/java/org/apache/cassandra/schema/Types.java
@@ -20,6 +20,7 @@ package org.apache.cassandra.schema;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.*;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
@@ -44,6 +45,7 @@ import static com.google.common.collect.Iterables.any;
 import static com.google.common.collect.Iterables.transform;
 
 import static org.apache.cassandra.db.TypeSizes.sizeof;
+import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt;
 import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
 
 /**
@@ -54,6 +56,10 @@ public final class Types implements Iterable<UserType>
     public static final Serializer serializer = new Serializer();
 
     private static final Types NONE = new Types(ImmutableMap.of());
+    private static final String EMPTY_COMMENT = "";
+    private static final String EMPTY_SECURITY_LABEL = "";
+    private static final List<String> EMPTY_FIELD_COMMENTS = List.of();
+    private static final List<String> EMPTY_FIELD_SECURITY_LABELS = List.of();
 
     private final Map<ByteBuffer, UserType> types;
 
@@ -362,13 +368,29 @@ public final class Types implements Iterable<UserType>
         }
 
         public void add(String name, List<String> fieldNames, List<String> fieldTypes)
+        {
+            add(name, fieldNames, fieldTypes, EMPTY_COMMENT, EMPTY_SECURITY_LABEL, EMPTY_FIELD_COMMENTS, EMPTY_FIELD_SECURITY_LABELS);
+        }
+
+        public void add(String name, List<String> fieldNames, List<String> fieldTypes, String comment, String securityLabel)
+        {
+            add(name, fieldNames, fieldTypes, comment, securityLabel, EMPTY_FIELD_COMMENTS, EMPTY_FIELD_SECURITY_LABELS);
+        }
+
+        public void add(String name,
+                        List<String> fieldNames,
+                        List<String> fieldTypes,
+                        String comment,
+                        String securityLabel,
+                        List<String> fieldComments,
+                        List<String> fieldSecurityLabels)
         {
             List<CQL3Type.Raw> rawFieldTypes =
                 fieldTypes.stream()
                           .map(CQLTypeParser::parseRaw)
                           .collect(toList());
 
-            definitions.add(new RawUDT(name, fieldNames, rawFieldTypes));
+            definitions.add(new RawUDT(name, fieldNames, rawFieldTypes, comment, securityLabel, fieldComments, fieldSecurityLabels));
         }
 
         private static final class RawUDT
@@ -376,12 +398,36 @@ public final class Types implements Iterable<UserType>
             final String name;
             final List<String> fieldNames;
             final List<CQL3Type.Raw> fieldTypes;
+            final String comment;
+            final String securityLabel;
+            final List<String> fieldComments;
+            final List<String> fieldSecurityLabels;
 
             RawUDT(String name, List<String> fieldNames, List<CQL3Type.Raw> fieldTypes)
+            {
+                this(name, fieldNames, fieldTypes, EMPTY_COMMENT, EMPTY_SECURITY_LABEL, EMPTY_FIELD_COMMENTS, EMPTY_FIELD_SECURITY_LABELS);
+            }
+
+            RawUDT(String name, List<String> fieldNames, List<CQL3Type.Raw> fieldTypes, String comment, String securityLabel)
+            {
+                this(name, fieldNames, fieldTypes, comment, securityLabel, EMPTY_FIELD_COMMENTS, EMPTY_FIELD_SECURITY_LABELS);
+            }
+
+            RawUDT(String name,
+                   List<String> fieldNames,
+                   List<CQL3Type.Raw> fieldTypes,
+                   String comment,
+                   String securityLabel,
+                   List<String> fieldComments,
+                   List<String> fieldSecurityLabels)
             {
                 this.name = name;
                 this.fieldNames = fieldNames;
                 this.fieldTypes = fieldTypes;
+                this.comment = comment;
+                this.securityLabel = securityLabel;
+                this.fieldComments = fieldComments;
+                this.fieldSecurityLabels = fieldSecurityLabels;
             }
 
             boolean referencesUserType(RawUDT other)
@@ -401,7 +447,18 @@ public final class Types implements Iterable<UserType>
                               .map(t -> t.prepareInternal(keyspace, types).getType())
                               .collect(toList());
 
-                return new UserType(keyspace, bytes(name), preparedFieldNames, preparedFieldTypes, true);
+                Map<FieldIdentifier, String> preparedFieldComments = new HashMap<>();
+                Map<FieldIdentifier, String> preparedFieldSecurityLabels = new HashMap<>();
+
+                for (int i = 0; i < preparedFieldNames.size(); i++)
+                {
+                    if (i < fieldComments.size() && !fieldComments.get(i).isEmpty())
+                        preparedFieldComments.put(preparedFieldNames.get(i), fieldComments.get(i));
+                    if (i < fieldSecurityLabels.size() && !fieldSecurityLabels.get(i).isEmpty())
+                        preparedFieldSecurityLabels.put(preparedFieldNames.get(i), fieldSecurityLabels.get(i));
+                }
+
+                return new UserType(keyspace, bytes(name), preparedFieldNames, preparedFieldTypes, true, comment, securityLabel, preparedFieldComments, preparedFieldSecurityLabels);
             }
 
             @Override
@@ -461,7 +518,8 @@ public final class Types implements Iterable<UserType>
             for (UserType type : t.types.values())
             {
                 out.writeUTF(type.getNameAsString());
-                List<String> fieldNames = type.fieldNames().stream().map(FieldIdentifier::toString).collect(toList());
+                List<FieldIdentifier> fieldIdentifiers = type.fieldNames();
+                List<String> fieldNames = fieldIdentifiers.stream().map(FieldIdentifier::toString).collect(toList());
                 List<String> fieldTypes = type.fieldTypes().stream().map(AbstractType::asCQL3Type).map(CQL3Type::toString).collect(toList());
                 out.writeInt(fieldNames.size());
                 for (String s : fieldNames)
@@ -469,6 +527,12 @@ public final class Types implements Iterable<UserType>
                 out.writeInt(fieldTypes.size());
                 for (String s : fieldTypes)
                     out.writeUTF(s);
+                if (version.isAtLeast(Version.V8))
+                {
+                    out.writeUTF(type.comment);
+                    out.writeUTF(type.securityLabel);
+                    serializeFieldMetadata(type, fieldIdentifiers, out);
+                }
             }
         }
 
@@ -487,7 +551,17 @@ public final class Types implements Iterable<UserType>
                 List<String> fieldTypes = new ArrayList<>(fieldTypeSize);
                 for (int x = 0; x < fieldTypeSize; x++)
                     fieldTypes.add(in.readUTF());
-                builder.add(name, fieldNames, fieldTypes);
+                String comment = EMPTY_COMMENT;
+                String securityLabel = EMPTY_SECURITY_LABEL;
+                List<String> fieldComments = new ArrayList<>(Collections.nCopies(fieldNamesSize, ""));
+                List<String> fieldSecurityLabels = new ArrayList<>(Collections.nCopies(fieldNamesSize, ""));
+                if (version.isAtLeast(Version.V8))
+                {
+                    comment = in.readUTF();
+                    securityLabel = in.readUTF();
+                    deserializeFieldMetadata(in, fieldComments, fieldSecurityLabels);
+                }
+                builder.add(name, fieldNames, fieldTypes, comment, securityLabel, fieldComments, fieldSecurityLabels);
             }
             return builder.build();
         }
@@ -498,16 +572,95 @@ public final class Types implements Iterable<UserType>
             for (UserType type : t.types.values())
             {
                 size += sizeof(type.getNameAsString());
-                List<String> fieldNames = type.fieldNames().stream().map(FieldIdentifier::toString).collect(toList());
-                List<String> fieldTypes = type.fieldTypes().stream().map(AbstractType::asCQL3Type).map(CQL3Type::toString).collect(toList());
+                List<FieldIdentifier> fieldIdentifiers = type.fieldNames();
+                List<String> fieldNames = fieldIdentifiers.stream().map(FieldIdentifier::toString).collect(toList());
+                List<String> fieldTypes = type.fieldTypes().stream().map(AbstractType::asCQL3Type).map(CQL3Type::toString).collect(toList());;
                 size += sizeof(fieldNames.size());
                 for (String s : fieldNames)
                     size += sizeof(s);
                 size += sizeof(fieldTypes.size());
                 for (String s : fieldTypes)
                     size += sizeof(s);
+                if (version.isAtLeast(Version.V8))
+                {
+                    size += sizeof(type.comment);
+                    size += sizeof(type.securityLabel);
+                    size += fieldMetadataSize(type, fieldIdentifiers);
+                }
             }
             return size;
         }
+
+        private void serializeFieldMetadata(UserType type, List<FieldIdentifier> fieldIdentifiers, DataOutputPlus out) throws IOException
+        {
+            // Sparse serialization: only serialize non-empty metadata with their positions
+            serializeSparseMetadata(fieldIdentifiers, type::fieldComment, out);
+            serializeSparseMetadata(fieldIdentifiers, type::fieldSecurityLabel, out);
+        }
+
+        private void serializeSparseMetadata(List<FieldIdentifier> fieldIdentifiers,
+                                             Function<FieldIdentifier, String> metadataGetter,
+                                             DataOutputPlus out) throws IOException
+        {
+            // Collect only non-empty indexes
+            List<Integer> nonEmptyIndexes = new ArrayList<>();
+            for (int i = 0; i < fieldIdentifiers.size(); i++)
+            {
+                String value = metadataGetter.apply(fieldIdentifiers.get(i));
+                if (!value.isEmpty())
+                    nonEmptyIndexes.add(i);
+            }
+
+            // Write count followed by position-value pairs
+            out.writeUnsignedVInt32(nonEmptyIndexes.size());
+            for (int index : nonEmptyIndexes)
+            {
+                out.writeUnsignedVInt32(index);
+                out.writeUTF(metadataGetter.apply(fieldIdentifiers.get(index)));
+            }
+        }
+
+        private void deserializeFieldMetadata(DataInputPlus in, List<String> fieldComments, List<String> fieldSecurityLabels) throws IOException
+        {
+            deserializeSparseMetadata(in, fieldComments);
+            deserializeSparseMetadata(in, fieldSecurityLabels);
+        }
+
+        private void deserializeSparseMetadata(DataInputPlus in, List<String> target) throws IOException
+        {
+            int count = in.readUnsignedVInt32();
+            for (int i = 0; i < count; i++)
+            {
+                int position = in.readUnsignedVInt32();
+                String value = in.readUTF();
+                target.set(position, value);
+            }
+        }
+
+        private long fieldMetadataSize(UserType type, List<FieldIdentifier> fieldIdentifiers)
+        {
+            return sparseMetadataSize(fieldIdentifiers, type::fieldComment)
+                 + sparseMetadataSize(fieldIdentifiers, type::fieldSecurityLabel);
+        }
+
+        private long sparseMetadataSize(List<FieldIdentifier> fieldIdentifiers,
+                                       java.util.function.Function<FieldIdentifier, String> metadataGetter)
+        {
+            int nonEmptyCount = 0;
+            long dataSize = 0;
+
+            for (int i = 0; i < fieldIdentifiers.size(); i++)
+            {
+                String value = metadataGetter.apply(fieldIdentifiers.get(i));
+                if (!value.isEmpty())
+                {
+                    nonEmptyCount++;
+                    dataSize += sizeofUnsignedVInt(i);     // position (int)
+                    dataSize += sizeof(value); // value string
+                }
+            }
+
+            return sizeofUnsignedVInt(nonEmptyCount) + dataSize;
+        }
     }
 }
diff --git a/src/java/org/apache/cassandra/tcm/membership/NodeVersion.java b/src/java/org/apache/cassandra/tcm/membership/NodeVersion.java
index 64d1cc71c6..9a15951ed0 100644
--- a/src/java/org/apache/cassandra/tcm/membership/NodeVersion.java
+++ b/src/java/org/apache/cassandra/tcm/membership/NodeVersion.java
@@ -34,7 +34,7 @@ import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt;
 public class NodeVersion implements Comparable<NodeVersion>
 {
     public static final Serializer serializer = new Serializer();
-    public static final Version CURRENT_METADATA_VERSION = Version.V7;
+    public static final Version CURRENT_METADATA_VERSION = Version.V8;
     public static final NodeVersion CURRENT = new NodeVersion(new CassandraVersion(FBUtilities.getReleaseVersionString()), CURRENT_METADATA_VERSION);
     private static final CassandraVersion SINCE_VERSION = CassandraVersion.CASSANDRA_5_1;
 
diff --git a/src/java/org/apache/cassandra/tcm/serialization/Version.java b/src/java/org/apache/cassandra/tcm/serialization/Version.java
index 955c6996e0..c4fb39cd51 100644
--- a/src/java/org/apache/cassandra/tcm/serialization/Version.java
+++ b/src/java/org/apache/cassandra/tcm/serialization/Version.java
@@ -68,6 +68,11 @@ public enum Version
      */
     V7(7),
 
+    /**
+     *  - Comments and security labels for schema elements (keyspaces, tables, columns, UDTs, and UDT fields)
+     */
+    V8(8),
+
     UNKNOWN(Integer.MAX_VALUE);
 
     /**
diff --git a/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java
index eb5015da61..36355144a5 100644
--- a/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java
+++ b/test/unit/org/apache/cassandra/cql3/statements/DescribeStatementTest.java
@@ -46,6 +46,7 @@ import org.apache.cassandra.transport.ProtocolVersion;
 
 import static java.lang.String.format;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -265,7 +266,8 @@ public class DescribeStatementTest extends CQLTester
                           "    PRIMARY KEY (keyspace_name, table_name, column_name)\n" +
                           ") WITH CLUSTERING ORDER BY (table_name ASC, column_name ASC)\n" +
                           "    AND comment = 'virtual column definitions';\n" +
-                          "*/"));
+                          "*/\n" +
+                          "COMMENT ON TABLE system_virtual_schema.columns IS 'virtual column definitions';"));
     }
 
     @Test
@@ -845,7 +847,7 @@ public class DescribeStatementTest extends CQLTester
     {
         requireNetwork();
         DatabaseDescriptor.setDynamicDataMaskingEnabled(true);
-        String souceTable = createTable(KEYSPACE_PER_TEST,
+        String sourceTable = createTable(KEYSPACE_PER_TEST,
                                         "CREATE TABLE %s (" +
                                         "  pk1 text, " +
                                         "  pk2 int MASKED WITH DEFAULT, " +
@@ -857,14 +859,14 @@ public class DescribeStatementTest extends CQLTester
                                         "PRIMARY KEY ((pk1, pk2), ck1, ck2 ))");
         TableMetadata source = getTableMetadata(KEYSPACE_PER_TEST, currentTable());
         assertNotNull(source);
-        String targetTable = createTableLike("CREATE TABLE %s LIKE %s", souceTable, KEYSPACE_PER_TEST, KEYSPACE_PER_TEST);
+        String targetTable = createTableLike("CREATE TABLE %s LIKE %s", sourceTable, KEYSPACE_PER_TEST, KEYSPACE_PER_TEST);
         TableMetadata target = getTableMetadata(KEYSPACE_PER_TEST, currentTable());
         assertNotNull(target);
         assertTrue(equalsWithoutTableNameAndDropCns(source, target, true, true, true));
         assertNotEquals(source.id, target.id);
         assertNotEquals(source.name, target.name);
 
-        String sourceTableCreateStatement = "CREATE TABLE " + KEYSPACE_PER_TEST + "." + souceTable + " (\n" +
+        String sourceTableCreateStatement = "CREATE TABLE " + KEYSPACE_PER_TEST + "." + sourceTable + " (\n" +
                                             "    pk1 text,\n" +
                                             "    pk2 int MASKED WITH system.mask_default(),\n" +
                                             "    ck1 int,\n" +
@@ -889,10 +891,10 @@ public class DescribeStatementTest extends CQLTester
                                             "    AND CLUSTERING ORDER BY (ck1 ASC, ck2 ASC)\n" +
                                             "    AND " + tableParametersCql();
 
-        assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE_PER_TEST + "." + souceTable + " WITH INTERNALS"),
+        assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE_PER_TEST + "." + sourceTable + " WITH INTERNALS"),
                       row(KEYSPACE_PER_TEST,
                           "table",
-                          souceTable,
+                          sourceTable,
                           sourceTableCreateStatement));
         assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE_PER_TEST + "." + targetTable + " WITH INTERNALS"),
                       row(KEYSPACE_PER_TEST,
@@ -1050,6 +1052,174 @@ public class DescribeStatementTest extends CQLTester
         }
     }
 
+    @Test
+    public void testDescribeKeyspaceWithCommentsAndSecurityLabels() throws Throwable
+    {
+        try
+        {
+            execute("CREATE KEYSPACE test_comments WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1}");
+            String describeStatement = "DESCRIBE KEYSPACE test_comments";
+            String dropStatement = "DROP KEYSPACE IF EXISTS test_comments";
+            testDescribeOnSchemaElement("KEYSPACE", "test_comments", describeStatement, dropStatement);
+        }
+        finally
+        {
+            execute("DROP KEYSPACE IF EXISTS test_comments");
+        }
+    }
+
+    @Test
+    public void testDescribeTableWithCommentsAndSecurityLabels() throws Throwable
+    {
+        try
+        {
+            execute("CREATE TABLE " + KEYSPACE_PER_TEST + ".table_comment_test (id int PRIMARY KEY, name text)");
+            testDescribeOnSchemaElement("TABLE",
+                                        KEYSPACE_PER_TEST + ".table_comment_test",
+                                        "DESCRIBE TABLE " + KEYSPACE_PER_TEST + ".table_comment_test",
+                                        "DROP TABLE IF EXISTS " + KEYSPACE_PER_TEST + ".table_comment_test");
+        }
+        finally
+        {
+            execute("DROP TABLE IF EXISTS " + KEYSPACE_PER_TEST + ".table_comment_test");
+        }
+    }
+
+    @Test
+    public void testDescribeColumnWithCommentsAndSecurityLabels() throws Throwable
+    {
+        try
+        {
+            execute("CREATE TABLE " + KEYSPACE_PER_TEST + ".column_test (id int PRIMARY KEY, test_column text)");
+            testDescribeOnSchemaElement("COLUMN",
+                                        KEYSPACE_PER_TEST + ".column_test.test_column",
+                                        "DESCRIBE TABLE " + KEYSPACE_PER_TEST + ".column_test",
+                                        "ALTER TABLE " + KEYSPACE_PER_TEST + ".column_test DROP test_column");
+        }
+        finally
+        {
+            execute("DROP TABLE IF EXISTS " + KEYSPACE_PER_TEST + ".column_test");
+        }
+    }
+
+    @Test
+    public void testDescribeUserTypeWithCommentsAndSecurityLabels() throws Throwable
+    {
+        try
+        {
+            String type = createType(KEYSPACE_PER_TEST, "CREATE TYPE %s (name text, age int, address text)");
+            testDescribeOnSchemaElement("TYPE", KEYSPACE_PER_TEST + "." + type, "DESCRIBE TYPE " + KEYSPACE_PER_TEST + "." + type, "DROP TYPE IF EXISTS " + KEYSPACE_PER_TEST + "." + type);
+        }
+        finally
+        {
+            // Cleanup is handled by the base test class
+        }
+    }
+
+    @Test
+    public void testDescribeUserTypeWithFieldCommentsAndSecurityLabels() throws Throwable
+    {
+        try
+        {
+            String type = createType(KEYSPACE_PER_TEST, "CREATE TYPE %s (latitude double, longitude double)");
+            testDescribeOnSchemaElement("FIELD", KEYSPACE_PER_TEST + "." + type + ".latitude", "DESCRIBE TYPE " + KEYSPACE_PER_TEST + "." + type, "DROP TYPE " + KEYSPACE_PER_TEST + "." + type);
+
+        }
+        finally
+        {
+            // Cleanup is handled by the base test class
+        }
+    }
+
+
+    @Test
+    public void testDescribeEscapesSingleQuotesInKeyspaceComment() throws Throwable
+    {
+        String commentWithQuotes ="a''; DROP KEYSPACE " + KEYSPACE_PER_TEST + ";";
+        execute(String.format("COMMENT ON KEYSPACE %s IS '%s'", KEYSPACE_PER_TEST, commentWithQuotes));
+        String describeOutput = executeDescribeNet("DESCRIBE KEYSPACE " + KEYSPACE_PER_TEST).one().getString("create_statement");
+
+        // Verify that single quotes are properly escaped (doubled) in the output
+        assertTrue("DESCRIBE output should contain escaped single quotes in comment",
+                  describeOutput.contains("COMMENT ON KEYSPACE " + KEYSPACE_PER_TEST + " IS 'a''; DROP KEYSPACE " + KEYSPACE_PER_TEST + ";';"));
+    }
+
+    /**
+     * Helper method to set comment and security label on a schema element and test DESCRIBE output.
+     * Handles special cases for FIELD and COLUMN which don't have their own DESCRIBE commands.
+     */
+    private void testDescribeOnSchemaElement(String objectType, String objectName, String describeCommand, String dropStatement) throws Throwable
+    {
+        String commentStatement = String.format("COMMENT ON %s %s IS 'testComment'", objectType, objectName);
+        String securitylabelStatement = String.format("SECURITY LABEL ON %s %s IS 'sensitive'", objectType, objectName);
+
+        testSetCommentAndLabel(describeCommand, commentStatement, securitylabelStatement);
+        testUnsetCommentAndLabel(describeCommand, objectType, objectName, commentStatement, securitylabelStatement);
+        testDropRemovesCommentAndLabel(objectType, objectName, commentStatement, securitylabelStatement, dropStatement);
+    }
+
+    private void testSetCommentAndLabel(String describeCommand, String commentStatement, String securitylabelStatement) throws Throwable
+    {
+        execute(commentStatement);
+        execute(securitylabelStatement);
+
+        String schema = describeSchemaElement(describeCommand);
+        assertTrue(schema.contains(commentStatement));
+        assertTrue(schema.contains(securitylabelStatement));
+        String fullSchema = describeFullSchema();
+        assertTrue(fullSchema.contains(commentStatement));
+        assertTrue(fullSchema.contains(securitylabelStatement));
+    }
+
+    private void testUnsetCommentAndLabel(String describeCommand, String objectType, String objectName,
+                                          String commentStatement, String securitylabelStatement) throws Throwable
+    {
+        String unsetCommentStatement = String.format("COMMENT ON %s %s IS NULL", objectType, objectName);
+        String unsetSecurityLabelStatement = String.format("SECURITY LABEL ON %s %s IS NULL", objectType, objectName);
+        execute(unsetCommentStatement);
+        execute(unsetSecurityLabelStatement);
+
+        String schema = describeSchemaElement(describeCommand);
+        String fullSchema = describeFullSchema();
+        assertFalse(schema.contains(unsetSecurityLabelStatement));
+        assertFalse(schema.contains(unsetCommentStatement));
+        assertFalse(fullSchema.contains(commentStatement));
+        assertFalse(fullSchema.contains(securitylabelStatement));
+    }
+
+    private void testDropRemovesCommentAndLabel(String objectType, String objectName,
+                                                String commentStatement, String securitylabelStatement,
+                                                String dropStatement) throws Throwable
+    {
+        execute(commentStatement);
+        execute(securitylabelStatement);
+        execute(dropStatement);
+
+        String unsetCommentStatement = String.format("COMMENT ON %s %s IS NULL", objectType, objectName);
+        String unsetSecurityLabelStatement = String.format("SECURITY LABEL ON %s %s IS NULL", objectType, objectName);
+        String fullSchema = describeFullSchema();
+        assertFalse(fullSchema.contains(unsetSecurityLabelStatement));
+        assertFalse(fullSchema.contains(unsetCommentStatement));
+        assertFalse(fullSchema.contains(commentStatement));
+        assertFalse(fullSchema.contains(securitylabelStatement));
+    }
+
+    private String describeSchemaElement(String describeCommand) throws Throwable
+    {
+        return executeDescribeNet(describeCommand).one().getString("create_statement");
+    }
+
+    private String describeFullSchema() throws Throwable
+    {
+        ResultSet result = executeDescribeNet("DESCRIBE SCHEMA");
+        StringBuilder fullSchema = new StringBuilder();
+        for (Row row : result.all())
+        {
+            fullSchema.append(row.getString("create_statement")).append("\n");
+        }
+        return fullSchema.toString();
+    }
+
     private static String allTypesTable()
     {
         return "CREATE TABLE test.has_all_types (\n" +
@@ -1134,6 +1304,11 @@ public class DescribeStatementTest extends CQLTester
     }
 
     private static String tableParametersCql()
+    {
+        return tableParametersCql("");
+    }
+
+    private static String tableParametersCql(String comment)
     {
         if (!DatabaseDescriptor.getAutoRepairConfig().isAutoRepairSchedulingEnabled())
         {
@@ -1142,7 +1317,7 @@ public class DescribeStatementTest extends CQLTester
                    "    AND bloom_filter_fp_chance = 0.01\n" +
                    "    AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}\n" +
                    "    AND cdc = false\n" +
-                   "    AND comment = ''\n" +
+                   "    AND comment = '" + comment + "'\n" +
                    "    AND compaction = " + cqlQuoted(CompactionParams.DEFAULT.asMap()) + "\n" +
                    "    AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}\n" +
                    "    AND memtable = 'default'\n" +
diff --git a/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CommentAndSecurityLabelTest.java b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CommentAndSecurityLabelTest.java
new file mode 100644
index 0000000000..fe404afa9d
--- /dev/null
+++ b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/CommentAndSecurityLabelTest.java
@@ -0,0 +1,558 @@
+/*
+ * 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.validation.miscellaneous;
+
+import org.junit.Test;
+
+import com.datastax.driver.core.ResultSet;
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.FieldIdentifier;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.schema.ColumnMetadata;
+import org.apache.cassandra.schema.KeyspaceParams;
+import org.apache.cassandra.schema.Schema;
+import org.apache.cassandra.schema.TableMetadata;
+import org.apache.cassandra.schema.TableParams;
+
+import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
+import static org.junit.Assert.assertEquals;
+
+public class CommentAndSecurityLabelTest extends CQLTester
+{
+    public static final String KEYSPACE_NAME = "ks_comment";
+    public static final String SECURITY_KEYSPACE = "ks_security";
+    public static final String TABLE_NAME = "tbl_comment";
+    public static final String SECURITY_TABLE_NAME = "tbl_security";
+    public static final String TYPE_NAME = "address";
+
+    // Test data constants
+    private static final String TEST_COMMENT = "Test comment";
+    private static final String UPDATED_COMMENT = "Updated comment";
+    private static final String TEST_LABEL = "TEST_LABEL";
+    private static final String UPDATED_LABEL = "UPDATED_LABEL";
+
+    enum ObjectType
+    {KEYSPACE, TABLE, COLUMN, TYPE, FIELD}
+
+    @Test
+    public void testCommentOnKeyspace()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        testCommentLifecycle(ObjectType.KEYSPACE, KEYSPACE_NAME, KEYSPACE_NAME);
+    }
+
+    @Test
+    public void testSecurityLabelOnKeyspace()
+    {
+        createKeyspaceWithName(SECURITY_KEYSPACE);
+        testSecurityLabelLifecycle(ObjectType.KEYSPACE, SECURITY_KEYSPACE, SECURITY_KEYSPACE);
+
+        // Test provider warning
+        ResultSet result = executeNet(String.format("SECURITY LABEL FOR test_provider ON KEYSPACE %s IS 'SENSITIVE'", SECURITY_KEYSPACE));
+        assertWarningsContain(result.getExecutionInfo().getWarnings(), "Provider functionality not implemented.");
+        assertSecurityLabel(ObjectType.KEYSPACE, SECURITY_KEYSPACE, SECURITY_KEYSPACE, "SENSITIVE");
+    }
+
+    @Test
+    public void testCommentOnTable()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        createTableWithName(KEYSPACE_NAME, TABLE_NAME);
+        String tableRef = String.format("%s.%s", KEYSPACE_NAME, TABLE_NAME);
+        testCommentLifecycle(ObjectType.TABLE, KEYSPACE_NAME, tableRef);
+    }
+
+    @Test
+    public void testSecurityLabelOnTable()
+    {
+        createKeyspaceWithName(SECURITY_KEYSPACE);
+        createTableWithName(SECURITY_KEYSPACE, SECURITY_TABLE_NAME);
+        String tableRef = String.format("%s.%s", SECURITY_KEYSPACE, SECURITY_TABLE_NAME);
+        testSecurityLabelLifecycle(ObjectType.TABLE, SECURITY_KEYSPACE, tableRef);
+
+        // Test provider warning
+        ResultSet result = executeNet(String.format("SECURITY LABEL FOR my_provider ON TABLE %s IS 'CONFIDENTIAL'", tableRef));
+        assertWarningsContain(result.getExecutionInfo().getWarnings(), "Provider functionality not implemented.");
+        assertSecurityLabel(ObjectType.TABLE, SECURITY_KEYSPACE, tableRef, "CONFIDENTIAL");
+    }
+
+    @Test
+    public void testCommentOnColumn()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        createTableWithName(KEYSPACE_NAME, TABLE_NAME);
+        String columnRef = String.format("%s.%s.name", KEYSPACE_NAME, TABLE_NAME);
+        testCommentLifecycle(ObjectType.COLUMN, KEYSPACE_NAME, columnRef);
+    }
+
+    @Test
+    public void testSecurityLabelOnColumn()
+    {
+        createKeyspaceWithName(SECURITY_KEYSPACE);
+        String createTableQuery = String.format("CREATE TABLE %s.%s (id int PRIMARY KEY, ssn text, name text)", SECURITY_KEYSPACE, SECURITY_TABLE_NAME);
+        createTable(createTableQuery);
+        String columnRef = String.format("%s.%s.ssn", SECURITY_KEYSPACE, SECURITY_TABLE_NAME);
+        testSecurityLabelLifecycle(ObjectType.COLUMN, SECURITY_KEYSPACE, columnRef);
+
+        // Test provider warning
+        ResultSet result = executeNet(String.format("SECURITY LABEL FOR data_classifier ON COLUMN %s IS 'PII'", columnRef));
+        assertWarningsContain(result.getExecutionInfo().getWarnings(), "Provider functionality not implemented.");
+        assertSecurityLabel(ObjectType.COLUMN, SECURITY_KEYSPACE, columnRef, "PII");
+    }
+
+    @Test
+    public void testCommentOnType()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        execute(String.format("CREATE TYPE %s.%s (street text, city text, zip int)", KEYSPACE_NAME, TYPE_NAME));
+        String typeRef = String.format("%s.%s", KEYSPACE_NAME, TYPE_NAME);
+        testCommentLifecycle(ObjectType.TYPE, KEYSPACE_NAME, typeRef);
+    }
+
+    @Test
+    public void testSecurityLabelOnType()
+    {
+        createKeyspaceWithName(SECURITY_KEYSPACE);
+        String typeName = "personal_info";
+        execute(String.format("CREATE TYPE %s.%s (ssn text, dob date)", SECURITY_KEYSPACE, typeName));
+        String typeRef = String.format("%s.%s", SECURITY_KEYSPACE, typeName);
+        testSecurityLabelLifecycle(ObjectType.TYPE, SECURITY_KEYSPACE, typeRef);
+
+        // Test provider warning
+        ResultSet result = executeNet(String.format("SECURITY LABEL FOR security_provider ON TYPE %s IS 'RESTRICTED'", typeRef));
+        assertWarningsContain(result.getExecutionInfo().getWarnings(), "Provider functionality not implemented.");
+        assertSecurityLabel(ObjectType.TYPE, SECURITY_KEYSPACE, typeRef, "RESTRICTED");
+    }
+
+    @Test
+    public void testCommentOnField()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        execute(String.format("CREATE TYPE %s.geo_position (latitude double, longitude double, altitude double)", KEYSPACE_NAME));
+        String fieldRef = String.format("%s.geo_position.latitude", KEYSPACE_NAME);
+        testCommentLifecycle(ObjectType.FIELD, KEYSPACE_NAME, fieldRef);
+    }
+
+    @Test
+    public void testSecurityLabelOnField()
+    {
+        createKeyspaceWithName(SECURITY_KEYSPACE);
+        execute(String.format("CREATE TYPE %s.patient_record (ssn text, diagnosis text, treatment text)", SECURITY_KEYSPACE));
+        String fieldRef = String.format("%s.patient_record.ssn", SECURITY_KEYSPACE);
+        testSecurityLabelLifecycle(ObjectType.FIELD, SECURITY_KEYSPACE, fieldRef);
+
+        // Test provider warning
+        ResultSet result = executeNet(String.format("SECURITY LABEL FOR healthcare_provider ON FIELD %s IS 'PHI'", fieldRef));
+        assertWarningsContain(result.getExecutionInfo().getWarnings(), "Provider functionality not implemented.");
+        assertSecurityLabel(ObjectType.FIELD, SECURITY_KEYSPACE, fieldRef, "PHI");
+    }
+
+    @Test
+    public void testFieldWithUseKeyspace()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        execute(String.format("CREATE TYPE %s.address (street text, city text, zip int)", KEYSPACE_NAME));
+        execute(String.format("USE %s", KEYSPACE_NAME));
+
+        // Test unqualified field reference with USE KEYSPACE context
+        setComment(ObjectType.FIELD, "address.street", "Street address");
+        setSecurityLabel(ObjectType.FIELD, "address.street", "PUBLIC");
+        setComment(ObjectType.FIELD, "address.city", "City name");
+        setSecurityLabel(ObjectType.FIELD, "address.city", "PUBLIC");
+
+        // Verify
+        assertComment(ObjectType.FIELD, KEYSPACE_NAME, "address.street", "Street address");
+        assertSecurityLabel(ObjectType.FIELD, KEYSPACE_NAME, "address.street", "PUBLIC");
+        assertComment(ObjectType.FIELD, KEYSPACE_NAME, "address.city", "City name");
+        assertSecurityLabel(ObjectType.FIELD, KEYSPACE_NAME, "address.city", "PUBLIC");
+    }
+
+    @Test
+    public void testErrorCases()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        createTableWithName(KEYSPACE_NAME, TABLE_NAME);
+        execute(String.format("CREATE TYPE %s.test_type (field1 text, field2 int)", KEYSPACE_NAME));
+
+        // Test non-existent keyspace
+        String commentOnKeyspace = "COMMENT ON KEYSPACE nonexistent IS 'comment'";
+        assertInvalidMessage("Keyspace 'nonexistent' doesn't exist", commentOnKeyspace);
+
+        // Test non-existent table
+        String commentOnTableQuery = String.format("COMMENT ON TABLE %s.nonexistent IS 'comment'", KEYSPACE_NAME);
+        assertInvalidMessage(String.format("Table '%s.nonexistent' doesn't exist", KEYSPACE_NAME), commentOnTableQuery);
+
+        // Test non-existent column
+        String commentOnColumnQuery = String.format("COMMENT ON COLUMN %s.%s.nonexistent IS 'comment'", KEYSPACE_NAME, TABLE_NAME);
+        assertInvalidMessage("Column 'nonexistent' doesn't exist", commentOnColumnQuery);
+
+        // Test non-existent type
+        String commentOnType = String.format("COMMENT ON TYPE %s.nonexistent IS 'comment'", KEYSPACE_NAME);
+        assertInvalidMessage("Type", commentOnType);
+
+        // Test non-existent type for field
+        String commentOnNonExistentType = String.format("COMMENT ON FIELD %s.nonexistent.somefield IS 'comment'", KEYSPACE_NAME);
+        assertInvalidMessage("doesn't exist", commentOnNonExistentType);
+
+        // Test non-existent field
+        String commentOnNonExistentField = String.format("COMMENT ON FIELD %s.test_type.nonexistent IS 'comment'", KEYSPACE_NAME);
+        assertInvalidMessage("doesn't exist", commentOnNonExistentField);
+    }
+
+    @Test
+    public void testMultipleOperations()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        createTableWithName(KEYSPACE_NAME, TABLE_NAME);
+        execute(String.format("CREATE TYPE %s.contact_info (phone text, address text)", KEYSPACE_NAME));
+
+        // Set comments and labels on multiple objects
+        String tableRef = String.format("%s.%s", KEYSPACE_NAME, TABLE_NAME);
+        String columnRef = String.format("%s.%s.name", KEYSPACE_NAME, TABLE_NAME);
+        String typeRef = String.format("%s.contact_info", KEYSPACE_NAME);
+
+        setComment(ObjectType.TABLE, tableRef, "User table");
+        setSecurityLabel(ObjectType.TABLE, tableRef, "USER_DATA");
+        setComment(ObjectType.COLUMN, columnRef, "User name");
+        setSecurityLabel(ObjectType.COLUMN, columnRef, "PUBLIC");
+        setComment(ObjectType.TYPE, typeRef, "Contact information");
+        setSecurityLabel(ObjectType.TYPE, typeRef, "PERSONAL");
+
+        // Verify all are set correctly
+        assertComment(ObjectType.TABLE, KEYSPACE_NAME, tableRef, "User table");
+        assertSecurityLabel(ObjectType.TABLE, KEYSPACE_NAME, tableRef, "USER_DATA");
+        assertComment(ObjectType.COLUMN, KEYSPACE_NAME, columnRef, "User name");
+        assertSecurityLabel(ObjectType.COLUMN, KEYSPACE_NAME, columnRef, "PUBLIC");
+        assertComment(ObjectType.TYPE, KEYSPACE_NAME, typeRef, "Contact information");
+        assertSecurityLabel(ObjectType.TYPE, KEYSPACE_NAME, typeRef, "PERSONAL");
+    }
+
+    @Test
+    public void testEmptyAndSpecialCharacters()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        createTableWithName(KEYSPACE_NAME, TABLE_NAME);
+        String tableRef = String.format("%s.%s", KEYSPACE_NAME, TABLE_NAME);
+
+        // Test empty string - should be rejected
+        assertInvalidMessage("Cannot set comment to empty string", buildCommentStatement(ObjectType.TABLE, tableRef, ""));
+        assertInvalidMessage("Cannot set security label to empty string", buildSecurityLabelStatement(ObjectType.TABLE, tableRef, ""));
+
+        // Test special characters
+        String specialComment = "Comment with \"quotes\" and '' and \nnewlines";
+        setComment(ObjectType.TABLE, tableRef, specialComment);
+        assertComment(ObjectType.TABLE, KEYSPACE_NAME, tableRef, "Comment with \"quotes\" and '' and \nnewlines");
+
+        // Test Unicode characters
+        String unicodeComment = "Unicode comment: 测试 ñoño 🚀";
+        setComment(ObjectType.TABLE, tableRef, unicodeComment);
+        assertComment(ObjectType.TABLE, KEYSPACE_NAME, tableRef, unicodeComment);
+    }
+
+    @Test
+    public void testUseKeyspaceContext()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        createTableWithName(KEYSPACE_NAME, TABLE_NAME);
+        execute(String.format("CREATE TYPE %s.test_type (field1 text, field2 int)", KEYSPACE_NAME));
+
+        // Use the keyspace to set current context
+        execute(String.format("USE %s", KEYSPACE_NAME));
+
+        // Test unqualified names with USE KEYSPACE context
+        setComment(ObjectType.TABLE, TABLE_NAME, "Table comment via USE");
+        setSecurityLabel(ObjectType.TABLE, TABLE_NAME, "TABLE_LABEL");
+        setComment(ObjectType.COLUMN, TABLE_NAME + ".name", "Column comment via USE");
+        setSecurityLabel(ObjectType.COLUMN, TABLE_NAME + ".name", "COLUMN_LABEL");
+        setComment(ObjectType.TYPE, "test_type", "Type comment via USE");
+        setSecurityLabel(ObjectType.TYPE, "test_type", "TYPE_LABEL");
+
+        // Verify all are set correctly using the current keyspace context
+        assertComment(ObjectType.TABLE, KEYSPACE_NAME, TABLE_NAME, "Table comment via USE");
+        assertSecurityLabel(ObjectType.TABLE, KEYSPACE_NAME, TABLE_NAME, "TABLE_LABEL");
+        assertComment(ObjectType.COLUMN, KEYSPACE_NAME, TABLE_NAME + ".name", "Column comment via USE");
+        assertSecurityLabel(ObjectType.COLUMN, KEYSPACE_NAME, TABLE_NAME + ".name", "COLUMN_LABEL");
+        assertComment(ObjectType.TYPE, KEYSPACE_NAME, "test_type", "Type comment via USE");
+        assertSecurityLabel(ObjectType.TYPE, KEYSPACE_NAME, "test_type", "TYPE_LABEL");
+    }
+
+
+    @Test
+    public void testCommentAndSecurityLabelOnVirtualTableFails()
+    {
+        assertInvalidMessage("is not user-modifiable", "COMMENT ON TABLE system_views.settings IS 'fail'");
+        assertInvalidMessage("is not user-modifiable", "SECURITY LABEL ON TABLE system_views.settings IS 'fail'");
+    }
+
+    @Test
+    public void testCommentAndSecurityLabelOnSystemTableFails()
+    {
+        assertInvalidMessage("is not user-modifiable", "COMMENT ON TABLE system.local IS 'fail'");
+        assertInvalidMessage("is not user-modifiable", "SECURITY LABEL ON TABLE system.local IS 'fail'");
+        assertInvalidMessage("is not user-modifiable", "COMMENT ON COLUMN system.local.key IS 'fail'");
+        assertInvalidMessage("is not user-modifiable", "SECURITY LABEL ON COLUMN system.local.key IS 'fail'");
+    }
+
+    @Test
+    public void testMaterializedViewFails() throws Throwable
+    {
+        String tableName = createTable("CREATE TABLE %s (pk int, ck int, v int, PRIMARY KEY (pk, ck))");
+        String mvFullName = KEYSPACE + ".test_mv";
+
+        // Create view using executeNet with fully qualified names (like GrantAndRevokeTest does)
+        executeNet("CREATE MATERIALIZED VIEW " + mvFullName + " AS SELECT * FROM " + KEYSPACE + "." + tableName +
+                   " WHERE pk IS NOT NULL AND ck IS NOT NULL PRIMARY KEY (ck, pk)");
+
+        // Wait for the view to be fully built before testing
+        waitForViewBuild("test_mv");
+
+        // Test that comment and security label statements fail
+        assertInvalidMessage("Cannot set comment on non-regular table",
+                           "COMMENT ON TABLE " + mvFullName + " IS 'fail'");
+        assertInvalidMessage("Cannot set security label on non-regular table",
+                           "SECURITY LABEL ON TABLE " + mvFullName + " IS 'fail'");
+    }
+
+    @Test
+    public void testCommentAndSecurityLabelOnSystemKeyspaceFails()
+    {
+        // Test comment and security label on system keyspaces
+        assertInvalidMessage("is not user-modifiable", "COMMENT ON KEYSPACE system IS 'fail'");
+        assertInvalidMessage("is not user-modifiable", "SECURITY LABEL ON KEYSPACE system IS 'fail'");
+    }
+
+    @Test
+    public void testCommentAndSecurityLabelOnVirtualKeyspaceFails()
+    {
+        // Test comment and security label on virtual keyspaces
+        assertInvalidMessage("is not user-modifiable", "COMMENT ON KEYSPACE system_views IS 'fail'");
+        assertInvalidMessage("is not user-modifiable", "SECURITY LABEL ON KEYSPACE system_views IS 'fail'");
+    }
+
+    @Test
+    public void testEmptyStringRejection()
+    {
+        createKeyspaceWithName(KEYSPACE_NAME);
+        createTableWithName(KEYSPACE_NAME, TABLE_NAME);
+        execute(String.format("CREATE TYPE %s.test_type (field1 text, field2 int)", KEYSPACE_NAME));
+
+        String tableRef = String.format("%s.%s", KEYSPACE_NAME, TABLE_NAME);
+        String columnRef = String.format("%s.%s.name", KEYSPACE_NAME, TABLE_NAME);
+        String typeRef = String.format("%s.test_type", KEYSPACE_NAME);
+        String fieldRef = String.format("%s.test_type.field1", KEYSPACE_NAME);
+
+        // Test that empty strings are rejected for comments on all schema elements
+        assertInvalidMessage("Cannot set comment to empty string", buildCommentStatement(ObjectType.KEYSPACE, KEYSPACE_NAME, ""));
+        assertInvalidMessage("Cannot set comment to empty string", buildCommentStatement(ObjectType.TABLE, tableRef, ""));
+        assertInvalidMessage("Cannot set comment to empty string", buildCommentStatement(ObjectType.COLUMN, columnRef, ""));
+        assertInvalidMessage("Cannot set comment to empty string", buildCommentStatement(ObjectType.TYPE, typeRef, ""));
+        assertInvalidMessage("Cannot set comment to empty string", buildCommentStatement(ObjectType.FIELD, fieldRef, ""));
+
+        // Test that empty strings are rejected for security labels on all schema elements
+        assertInvalidMessage("Cannot set security label to empty string", buildSecurityLabelStatement(ObjectType.KEYSPACE, KEYSPACE_NAME, ""));
+        assertInvalidMessage("Cannot set security label to empty string", buildSecurityLabelStatement(ObjectType.TABLE, tableRef, ""));
+        assertInvalidMessage("Cannot set security label to empty string", buildSecurityLabelStatement(ObjectType.COLUMN, columnRef, ""));
+        assertInvalidMessage("Cannot set security label to empty string", buildSecurityLabelStatement(ObjectType.TYPE, typeRef, ""));
+        assertInvalidMessage("Cannot set security label to empty string", buildSecurityLabelStatement(ObjectType.FIELD, fieldRef, ""));
+    }
+
+    // Helper methods for setting comments and security labels
+    private void setComment(ObjectType type, String objectName, String comment)
+    {
+        String statement = buildCommentStatement(type, objectName, comment);
+        execute(statement);
+    }
+
+    private void setSecurityLabel(ObjectType type, String objectName, String label)
+    {
+        String statement = buildSecurityLabelStatement(type, objectName, label);
+        execute(statement);
+    }
+
+    private String buildStatement(String statementType, ObjectType type, String objectName, String value)
+    {
+        String valueClause = value != null ? String.format("'%s'", value.replace("'", "''")) : "NULL";
+        String typeKeyword = type.name();
+        return String.format("%s ON %s %s IS %s", statementType, typeKeyword, objectName, valueClause);
+    }
+
+    private String buildCommentStatement(ObjectType type, String objectName, String comment)
+    {
+        return buildStatement("COMMENT", type, objectName, comment);
+    }
+
+    private String buildSecurityLabelStatement(ObjectType type, String objectName, String label)
+    {
+        return buildStatement("SECURITY LABEL", type, objectName, label);
+    }
+
+    // Helper methods for assertions
+    private void assertComment(ObjectType type, String keyspace, String objectName, String expected)
+    {
+        String actual = getComment(type, keyspace, objectName);
+        assertEquals(expected, actual);
+    }
+
+    private void assertSecurityLabel(ObjectType type, String keyspace, String objectName, String expected)
+    {
+        String actual = getSecurityLabel(type, keyspace, objectName);
+        assertEquals(expected, actual);
+    }
+
+    private String extractObjectName(ObjectType type, String objectName)
+    {
+        if (type == ObjectType.TABLE || type == ObjectType.TYPE)
+        {
+            return objectName.contains(".") ? objectName.split("\\.")[1] : objectName;
+        }
+        return objectName;
+    }
+
+    private String[] parseColumnReference(String objectName)
+    {
+        String[] parts = objectName.split("\\.");
+        if (parts.length == 2)
+        {
+            return new String[]{ parts[0], parts[1] }; // table/type, column/field
+        }
+        else if (parts.length == 3)
+        {
+            return new String[]{ parts[1], parts[2] }; // table/type, column/field (ignore keyspace part)
+        }
+        else
+        {
+            throw new IllegalArgumentException("Invalid reference format: " + objectName);
+        }
+    }
+
+    private String getMetadataValue(ObjectType type, String keyspace, String objectName, boolean isComment)
+    {
+        switch (type)
+        {
+            case KEYSPACE:
+                KeyspaceParams keyspaceMetadata = Schema.instance.getKeyspaceMetadata(keyspace).params;
+                return isComment ? keyspaceMetadata.comment : keyspaceMetadata.securityLabel;
+            case TABLE:
+                String tableName = extractObjectName(type, objectName);
+                TableParams tableParams = retrieveTableMetadata(keyspace, tableName).params;
+                return isComment ? tableParams.comment : tableParams.securityLabel;
+            case COLUMN:
+                String[] columnParts = parseColumnReference(objectName);
+                ColumnMetadata columnMetadata = getColumnMetadata(keyspace, columnParts[0], columnParts[1]);
+                return isComment ? columnMetadata.comment : columnMetadata.securityLabel;
+            case TYPE:
+                String typeName = extractObjectName(type, objectName);
+                UserType userType = getUserType(keyspace, typeName);
+                return isComment ? userType.comment : userType.securityLabel;
+            case FIELD:
+                String[] fieldParts = parseColumnReference(objectName);
+                UserType type1 = getUserType(keyspace, fieldParts[0]);
+                FieldIdentifier fieldId = FieldIdentifier.forUnquoted(fieldParts[1]);
+                return isComment ? type1.fieldComment(fieldId) : type1.fieldSecurityLabel(fieldId);
+            default:
+                throw new IllegalArgumentException("Unsupported object type: " + type);
+        }
+    }
+
+    private String getComment(ObjectType type, String keyspace, String objectName)
+    {
+        return getMetadataValue(type, keyspace, objectName, true);
+    }
+
+    private String getSecurityLabel(ObjectType type, String keyspace, String objectName)
+    {
+        return getMetadataValue(type, keyspace, objectName, false);
+    }
+
+    // Metadata retrieval helpers
+    private TableMetadata retrieveTableMetadata(String keyspace, String table)
+    {
+        return getColumnFamilyStore(keyspace, table).metadata();
+    }
+
+    private ColumnMetadata getColumnMetadata(String keyspace, String table, String column)
+    {
+        return retrieveTableMetadata(keyspace, table).getColumn(ColumnIdentifier.getInterned(column, false));
+    }
+
+    private UserType getUserType(String keyspace, String typeName)
+    {
+        return Schema.instance.getKeyspaceMetadata(keyspace).types.get(bytes(typeName)).get();
+    }
+
+    // Generic lifecycle test method
+    private void testMetadataLifecycle(ObjectType type, String keyspace, String objectName, boolean isComment)
+    {
+        String testValue = isComment ? TEST_COMMENT : TEST_LABEL;
+        String updatedValue = isComment ? UPDATED_COMMENT : UPDATED_LABEL;
+        if (isComment)
+        {
+            String emptyStringStatement = buildCommentStatement(type, objectName, "");
+            assertInvalidMessage("Cannot set comment to empty string", emptyStringStatement);
+            setComment(type, objectName, testValue);
+            assertComment(type, keyspace, objectName, testValue);
+            setComment(type, objectName, updatedValue);
+            assertComment(type, keyspace, objectName, updatedValue);
+            setComment(type, objectName, null);
+            assertComment(type, keyspace, objectName, "");
+            setComment(type, objectName, null);
+            assertComment(type, keyspace, objectName, "");
+            String longComment = buildCommentStatement(type, objectName, "a".repeat(129));
+            assertInvalidMessage("comment length (129) exceeds maximum allowed length (128)", longComment);
+        }
+        else
+        {
+            String emptyStringStatement = buildSecurityLabelStatement(type, objectName, "");
+            assertInvalidMessage("Cannot set security label to empty string", emptyStringStatement);
+            setSecurityLabel(type, objectName, testValue);
+            assertSecurityLabel(type, keyspace, objectName, testValue);
+            setSecurityLabel(type, objectName, updatedValue);
+            assertSecurityLabel(type, keyspace, objectName, updatedValue);
+            setSecurityLabel(type, objectName, null);
+            assertSecurityLabel(type, keyspace, objectName, "");
+            setSecurityLabel(type, objectName, null);
+            assertSecurityLabel(type, keyspace, objectName, "");
+            String longSecurityLabel = buildSecurityLabelStatement(type, objectName, "a".repeat(49));
+            assertInvalidMessage("security label length (49) exceeds maximum allowed length (48)", longSecurityLabel);
+        }
+    }
+
+    private void testCommentLifecycle(ObjectType type, String keyspace, String objectName)
+    {
+        testMetadataLifecycle(type, keyspace, objectName, true);
+    }
+
+    private void testSecurityLabelLifecycle(ObjectType type, String keyspace, String objectName)
+    {
+        testMetadataLifecycle(type, keyspace, objectName, false);
+    }
+
+    private void createKeyspaceWithName(String keyspace)
+    {
+        String query = String.format("CREATE KEYSPACE %s WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}", keyspace);
+        createKeyspace(query);
+    }
+
+    private void createTableWithName(String keyspace, String taleName)
+    {
+        String query = String.format("CREATE TABLE %s.%s (id int PRIMARY KEY, name text)", keyspace, taleName);
+        createTable(query);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/db/virtual/SchemaMetadataTableTest.java b/test/unit/org/apache/cassandra/db/virtual/SchemaMetadataTableTest.java
new file mode 100644
index 0000000000..b16c4078fb
--- /dev/null
+++ b/test/unit/org/apache/cassandra/db/virtual/SchemaMetadataTableTest.java
@@ -0,0 +1,336 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.cql3.UntypedResultSet;
+import org.apache.cassandra.exceptions.InvalidRequestException;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Parameterized tests for schema metadata virtual tables.
+ * <p>
+ * This test class runs the same test suite for both {@link SchemaCommentsTable}
+ * and {@link SchemaSecurityLabelsTable}, ensuring consistent behavior across both tables.
+ * </p>
+ */
+@RunWith(Parameterized.class)
+public class SchemaMetadataTableTest extends CQLTester
+{
+    private static final String KS_NAME = "vts";
+    private static final String KS1 = "ks_metadata_test1";
+    private static final String KS2 = "ks_metadata_test2";
+    private static final String TABLE1 = "test_table";
+    private static final String TABLE2 = "user_data";
+
+    private static final String REPLICATION = "WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}";
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<Object[]> data()
+    {
+        return Arrays.asList(new Object[][]{
+            {"COMMENT", "schema_comments", "comment", "COMMENT ON", "IS"},
+            {"SECURITY_LABEL", "schema_security_labels", "security_label", "SECURITY LABEL ON", "IS"}
+        });
+    }
+
+    @Parameterized.Parameter(0)
+    public String metadataType;
+
+    @Parameterized.Parameter(1)
+    public String tableName;
+
+    @Parameterized.Parameter(2)
+    public String columnName;
+
+    @Parameterized.Parameter(3)
+    public String setStatementPrefix;
+
+    @Parameterized.Parameter(4)
+    public String setStatementSuffix;
+
+    @Before
+    public void setup()
+    {
+        if (metadataType.equals("COMMENT"))
+        {
+            SchemaCommentsTable table = new SchemaCommentsTable(KS_NAME);
+            VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table)));
+        }
+        else
+        {
+            SchemaSecurityLabelsTable table = new SchemaSecurityLabelsTable(KS_NAME);
+            VirtualKeyspaceRegistry.instance.register(new VirtualKeyspace(KS_NAME, ImmutableList.of(table)));
+        }
+    }
+
+    @After
+    public void tearDown()
+    {
+        execute("DROP KEYSPACE IF EXISTS " + KS1);
+        execute("DROP KEYSPACE IF EXISTS " + KS2);
+        execute("DROP KEYSPACE IF EXISTS ks_no_metadata");
+    }
+
+    @Test
+    public void testEmptyResults() throws Throwable
+    {
+        String ks = "ks_no_metadata";
+        createTestKeyspace(ks);
+        execute("CREATE TABLE " + ks + ".test (id int PRIMARY KEY, value text)");
+        assertEmpty(execute("SELECT * FROM vts." + tableName + " WHERE keyspace_name = ?", ks));
+    }
+
+    @Test
+    public void testKeyspaceMetadata() throws Throwable
+    {
+        createTestKeyspace(KS1);
+
+        setMetadata("KEYSPACE", KS1, "Test keyspace metadata");
+        assertMetadata("Test keyspace metadata");
+
+        setMetadata("KEYSPACE", KS1, "Updated keyspace metadata");
+        assertMetadata("Updated keyspace metadata");
+
+        setMetadata("KEYSPACE", KS1, null);
+        assertEmpty(querySchemaMetadata("KEYSPACE"));
+    }
+
+    @Test
+    public void testTableMetadata()
+    {
+        createTestKeyspace(KS1);
+        execute("CREATE TABLE " + KS1 + '.' + TABLE1 + " (id int PRIMARY KEY, name text)");
+
+        setMetadata("TABLE", KS1 + '.' + TABLE1, "User information table");
+        assertTableMetadata(KS1, TABLE1, "User information table");
+
+        setMetadata("TABLE", KS1 + '.' + TABLE1, "Updated table metadata");
+        assertTableMetadata(KS1, TABLE1, "Updated table metadata");
+    }
+
+    @Test
+    public void testColumnMetadata()
+    {
+        createTestKeyspace(KS1);
+        execute("CREATE TABLE " + KS1 + '.' + TABLE1 + " (id int PRIMARY KEY, name text, email text)");
+
+        setMetadata("COLUMN", KS1 + '.' + TABLE1 + ".name", "User full name");
+        setMetadata("COLUMN", KS1 + '.' + TABLE1 + ".email", "User email address");
+
+        assertColumnMetadata("name", "User full name");
+        assertColumnMetadata("email", "User email address");
+
+        setMetadata("COLUMN", KS1 + '.' + TABLE1 + ".name", "Updated name metadata");
+        assertColumnMetadata("name", "Updated name metadata");
+    }
+
+    @Test
+    public void testUdtMetadata()
+    {
+        createTestKeyspace(KS1);
+
+        execute("CREATE TYPE " + KS1 + ".address (street text, city text, zip int)");
+        setMetadata("TYPE", KS1 + ".address", "User address information");
+        assertUdtMetadata("User address information");
+
+        setMetadata("TYPE", KS1 + ".address", "Postal address");
+        assertUdtMetadata("Postal address");
+    }
+
+    @Test
+    public void testUdtFieldMetadata()
+    {
+        createTestKeyspace(KS1);
+
+        execute("CREATE TYPE " + KS1 + ".address (street text, city text, zip int)");
+        setMetadata("FIELD", KS1 + ".address.street", "Street address");
+        setMetadata("FIELD", KS1 + ".address.city", "City name");
+        setMetadata("FIELD", KS1 + ".address.zip", "Postal code");
+
+        assertFieldMetadata("street", "Street address");
+        assertFieldMetadata("city", "City name");
+        assertFieldMetadata("zip", "Postal code");
+
+        setMetadata("FIELD", KS1 + ".address.street", "Updated street metadata");
+        assertFieldMetadata("street", "Updated street metadata");
+    }
+
+    @Test
+    public void testPartitionKeyFiltering()
+    {
+        createTestKeyspace(KS1);
+        createTestKeyspace(KS2);
+
+        execute("CREATE TABLE " + KS1 + '.' + TABLE1 + " (id int PRIMARY KEY)");
+        execute("CREATE TABLE " + KS2 + '.' + TABLE2 + " (id int PRIMARY KEY)");
+
+        setMetadata("KEYSPACE", KS1, "First keyspace");
+        setMetadata("KEYSPACE", KS2, "Second keyspace");
+        setMetadata("TABLE", KS1 + '.' + TABLE1, "Table in KS1");
+        setMetadata("TABLE", KS2 + '.' + TABLE2, "Table in KS2");
+
+        assertMetadata("First keyspace");
+        assertTableMetadata(KS1, TABLE1, "Table in KS1");
+        assertTableMetadata(KS2, TABLE2, "Table in KS2");
+    }
+
+    @Test
+    public void testMetadataRemoval() throws Throwable
+    {
+        createTestKeyspace(KS1);
+
+        setMetadata("KEYSPACE", KS1, "Test metadata");
+        assertRowCount(querySchemaMetadata("KEYSPACE"), 1);
+
+        setMetadata("KEYSPACE", KS1, null);
+        assertEmpty(querySchemaMetadata("KEYSPACE"));
+    }
+
+    @Test
+    public void testMultipleSchemaElements()
+    {
+        createTestKeyspace(KS1);
+        execute("CREATE TABLE " + KS1 + ".users (id int PRIMARY KEY, name text, email text)");
+        execute("CREATE TABLE " + KS1 + ".posts (id int PRIMARY KEY, title text)");
+        execute("CREATE TYPE " + KS1 + ".address (street text, city text)");
+
+        setMetadata("KEYSPACE", KS1, "Application database");
+        setMetadata("TABLE", KS1 + ".users", "User accounts");
+        setMetadata("TABLE", KS1 + ".posts", "Blog posts");
+        setMetadata("COLUMN", KS1 + ".users.name", "Full name");
+        setMetadata("COLUMN", KS1 + ".users.email", "Email address");
+        setMetadata("TYPE", KS1 + ".address", "Postal address");
+
+        assertRowCount(execute("SELECT * FROM vts." + tableName + " WHERE keyspace_name = ? ALLOW FILTERING", KS1), 6);
+
+        assertRowCount(querySchemaMetadata("KEYSPACE"), 1);
+        assertRowCount(querySchemaMetadata("TABLE"), 2);
+        assertRowCount(execute("SELECT * FROM vts." + tableName + " WHERE object_type = 'COLUMN' AND keyspace_name = ? ALLOW FILTERING", KS1), 2);
+        assertRowCount(querySchemaMetadata("UDT"), 1);
+    }
+
+    @Test
+    public void testScanAllMetadata()
+    {
+        createTestKeyspace(KS1);
+        execute("CREATE TABLE " + KS1 + '.' + TABLE1 + " (id int PRIMARY KEY)");
+
+        setMetadata("KEYSPACE", KS1, "Test");
+        setMetadata("TABLE", KS1 + '.' + TABLE1, "Test table");
+
+        Object[][] results = getRows(execute("SELECT * FROM vts." + tableName));
+
+        boolean foundKeyspace = false;
+        boolean foundTable = false;
+
+        for (Object[] row : results)
+        {
+            if (row[1].equals(KS1))
+            {
+                if (row[0].equals("KEYSPACE"))
+                    foundKeyspace = true;
+                else if (row[0].equals("TABLE"))
+                    foundTable = true;
+            }
+        }
+
+        assert foundKeyspace : "Keyspace metadata not found in full scan";
+        assert foundTable : "Table metadata not found in full scan";
+    }
+
+    private void createTestKeyspace(String keyspaceName)
+    {
+        createKeyspace("CREATE KEYSPACE " + keyspaceName + ' ' + REPLICATION);
+    }
+
+    private UntypedResultSet querySchemaMetadata(String objectType)
+    {
+        return execute("SELECT * FROM vts." + tableName + " WHERE object_type = ? AND keyspace_name = ?",
+                       objectType, KS1);
+    }
+
+    private void setMetadata(String objectType, String objectName, String metadata)
+    {
+        String value = metadata != null ? '\'' + metadata.replace("'", "''") + '\'' : "NULL";
+        execute(setStatementPrefix + ' ' + objectType + ' ' + objectName + ' ' + setStatementSuffix + ' ' + value);
+    }
+
+    private void assertMetadata(String expectedMetadata)
+    {
+        assertRows(execute("SELECT " + columnName + " FROM vts." + tableName + " WHERE object_type = ? AND keyspace_name = ?",
+                           "KEYSPACE", KS1),
+                   row(expectedMetadata));
+    }
+
+    private void assertTableMetadata(String keyspaceName, String table, String expectedMetadata)
+    {
+        assertRows(execute("SELECT " + columnName + " FROM vts." + tableName + " WHERE object_type = 'TABLE' AND keyspace_name = ? AND table_name = ?",
+                          keyspaceName, table),
+                   row(expectedMetadata));
+    }
+
+    private void assertColumnMetadata(String column, String expectedMetadata)
+    {
+        assertRows(execute("SELECT " + columnName + " FROM vts." + tableName + " WHERE object_type = 'COLUMN' AND keyspace_name = ? AND table_name = ? AND column_name = ? ALLOW FILTERING",
+                           KS1, TABLE1, column),
+                   row(expectedMetadata));
+    }
+
+    private void assertUdtMetadata(String expectedMetadata)
+    {
+        assertRows(execute("SELECT " + columnName + " FROM vts." + tableName + " WHERE object_type = 'UDT' AND keyspace_name = ? AND udt_name = ?",
+                           KS1, "address"),
+                   row(expectedMetadata));
+    }
+
+    private void assertFieldMetadata(String field, String expectedMetadata)
+    {
+        assertRows(execute("SELECT " + columnName + " FROM vts." + tableName + " WHERE object_type = 'FIELD' AND keyspace_name = ? AND udt_name = ? AND field_name = ? ALLOW FILTERING",
+                           KS1, "address", field),
+                   row(expectedMetadata));
+    }
+
+    @Test
+    public void testInvalidKeyspaceName() throws Throwable
+    {
+        String invalidKeyspace = "nonexistent_keyspace";
+        assertInvalidThrowMessage("Unknown keyspace: '" + invalidKeyspace + "'",
+                                 InvalidRequestException.class,
+                                 "SELECT * FROM vts." + tableName + " WHERE object_type = 'KEYSPACE' AND keyspace_name = '" + invalidKeyspace + "'");
+    }
+
+    @Test
+    public void testInvalidObjectType() throws Throwable
+    {
+        createTestKeyspace(KS1);
+        String invalidObjectType = "INVALID_TYPE";
+        assertInvalidThrowMessage("Unknown object type: '" + invalidObjectType + "'. Valid types are: [KEYSPACE, TABLE, COLUMN, UDT, FIELD]",
+                                 InvalidRequestException.class,
+                                 "SELECT * FROM vts." + tableName + " WHERE object_type = '" + invalidObjectType + "' AND keyspace_name = '" + KS1 + "'");
+    }
+}
diff --git a/test/unit/org/apache/cassandra/schema/MockSchema.java b/test/unit/org/apache/cassandra/schema/MockSchema.java
index 8ca112ed18..a348a7dff3 100644
--- a/test/unit/org/apache/cassandra/schema/MockSchema.java
+++ b/test/unit/org/apache/cassandra/schema/MockSchema.java
@@ -324,7 +324,11 @@ public class MockSchema
                         ? mockKS.tables.with(metadata)
                         : mockKS.tables.withSwapped(metadata);
         mockKS = mockKS.withSwapped(tables);
-        return new ColumnFamilyStore(new Keyspace(mockKS), metadata.name, Util.newSeqGen(), metadata, new Directories(metadata), false, false);
+        // Use Schema.instance to get or create the keyspace instance to avoid creating duplicate metrics
+        Keyspace keyspace = Schema.instance.getKeyspaceInstance(metadata.keyspace);
+        if (keyspace == null)
+            keyspace = new Keyspace(mockKS);
+        return new ColumnFamilyStore(keyspace, metadata.name, Util.newSeqGen(), metadata, new Directories(metadata), false, false);
     }
 
     private static TableMetadata newTableMetadata(String ksname)
@@ -381,6 +385,7 @@ public class MockSchema
     public static class MockSchemaProvider implements SchemaProvider
     {
         private final SchemaProvider originalSchemaProvider = Schema.instance;
+        private volatile Keyspace mockKeyspaceInstance;
 
         @Override
         public Set<String> getKeyspaces()
@@ -464,7 +469,18 @@ public class MockSchema
         public Keyspace getKeyspaceInstance(String keyspaceName)
         {
             if (isMockKS(keyspaceName))
-                return new Keyspace(mockKS);
+            {
+                // Cache the keyspace instance to avoid creating duplicate metrics
+                if (mockKeyspaceInstance == null)
+                {
+                    synchronized (this)
+                    {
+                        if (mockKeyspaceInstance == null)
+                            mockKeyspaceInstance = new Keyspace(mockKS);
+                    }
+                }
+                return mockKeyspaceInstance;
+            }
 
             return originalSchemaProvider.getKeyspaceInstance(keyspaceName);
         }
diff --git a/test/unit/org/apache/cassandra/schema/SchemaMetadataSerializationTest.java b/test/unit/org/apache/cassandra/schema/SchemaMetadataSerializationTest.java
new file mode 100644
index 0000000000..297b047e4f
--- /dev/null
+++ b/test/unit/org/apache/cassandra/schema/SchemaMetadataSerializationTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.schema;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.cql3.ColumnIdentifier;
+import org.apache.cassandra.cql3.FieldIdentifier;
+import org.apache.cassandra.db.marshal.Int32Type;
+import org.apache.cassandra.db.marshal.UTF8Type;
+import org.apache.cassandra.db.marshal.UserType;
+import org.apache.cassandra.io.util.DataInputBuffer;
+import org.apache.cassandra.io.util.DataOutputBuffer;
+import org.apache.cassandra.tcm.serialization.Version;
+
+import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Tests serialization and deserialization of schema metadata (keyspace, table, type, field)
+ * to verify that comments and security labels persist correctly across serialization boundaries.
+ */
+public class SchemaMetadataSerializationTest
+{
+    static
+    {
+        DatabaseDescriptor.daemonInitialization();
+    }
+
+    private static final String KEYSPACE = "test_ks";
+
+    @Test
+    public void testKeyspaceMetadataSerialization() throws IOException
+    {
+        KeyspaceMetadata original = KeyspaceMetadata.create(KEYSPACE,
+                                                             KeyspaceParams.simple(1)
+                                                                           .withComment("Test keyspace")
+                                                                           .withSecurityLabel("TEST"));
+
+        KeyspaceMetadata deserialized = serializeAndDeserializeKeyspace(original);
+
+        assertNotNull("Deserialized keyspace should not be null", deserialized);
+        assertEquals("Test keyspace", deserialized.params.comment);
+        assertEquals("TEST", deserialized.params.securityLabel);
+    }
+
+    @Test
+    public void testTableMetadataSerialization() throws IOException
+    {
+        TableMetadata original = createSimpleTable("users", "Users table", "SENSITIVE");
+        TableMetadata deserialized = serializeAndDeserializeTable(original);
+
+        assertNotNull("Deserialized table should not be null", deserialized);
+        assertEquals("Users table", deserialized.params.comment);
+        assertEquals("SENSITIVE", deserialized.params.securityLabel);
+    }
+
+    @Test
+    public void testColumnMetadataSerialization() throws IOException
+    {
+        ColumnIdentifier emailCol = ColumnIdentifier.getInterned("email", false);
+        TableMetadata original = TableMetadata.builder(KEYSPACE, "users")
+                                              .addPartitionKeyColumn("id", Int32Type.instance)
+                                              .addRegularColumn(emailCol, UTF8Type.instance)
+                                              .alterColumnComment(emailCol, "User email")
+                                              .alterColumnSecurityLabel(emailCol, "PII-EMAIL")
+                                              .build();
+
+        TableMetadata deserialized = serializeAndDeserializeTable(original);
+
+        ColumnMetadata emailColumn = deserialized.getColumn(bytes("email"));
+        assertNotNull("Email column should exist", emailColumn);
+        assertEquals("User email", emailColumn.comment);
+        assertEquals("PII-EMAIL", emailColumn.securityLabel);
+    }
+
+    @Test
+    public void testUserTypeSerialization() throws IOException
+    {
+        UserType original = createUserType("address", "Address type", "PUBLIC");
+        UserType deserialized = serializeAndDeserializeType(original);
+
+        assertNotNull("Deserialized type should not be null", deserialized);
+        assertEquals("Address type", deserialized.comment);
+        assertEquals("PUBLIC", deserialized.securityLabel);
+    }
+
+    @Test
+    public void testTypeFieldSerialization() throws IOException
+    {
+        FieldIdentifier streetField = FieldIdentifier.forUnquoted("street");
+        FieldIdentifier cityField = FieldIdentifier.forUnquoted("city");
+
+        Map<FieldIdentifier, String> fieldComments = new HashMap<>();
+        fieldComments.put(streetField, "Street address");
+        fieldComments.put(cityField, "City name");
+
+        Map<FieldIdentifier, String> fieldLabels = new HashMap<>();
+        fieldLabels.put(streetField, "PII");
+        fieldLabels.put(cityField, "PUBLIC");
+
+        UserType original = new UserType(KEYSPACE,
+                                         bytes("address"),
+                                         Arrays.asList(streetField, cityField),
+                                         Arrays.asList(UTF8Type.instance, UTF8Type.instance),
+                                         true,
+                                         "Address type",
+                                         "PUBLIC",
+                                         fieldComments,
+                                         fieldLabels);
+
+        UserType deserialized = serializeAndDeserializeType(original);
+
+        assertNotNull("Deserialized type should not be null", deserialized);
+        assertEquals("Street address", deserialized.fieldComment(streetField));
+        assertEquals("City name", deserialized.fieldComment(cityField));
+        assertEquals("PII", deserialized.fieldSecurityLabel(streetField));
+        assertEquals("PUBLIC", deserialized.fieldSecurityLabel(cityField));
+    }
+
+    @Test
+    public void testTypesSerialization() throws IOException
+    {
+        UserType type1 = createUserType("type1", "Type 1 comment", "Type 1 label");
+        UserType type2 = createUserType("type2", "Type 2 comment", "Type 2 label");
+        Types original = Types.of(type1, type2);
+
+        Types deserialized = serializeAndDeserializeTypes(original);
+
+        UserType deserializedType1 = deserialized.getNullable(bytes("type1"));
+        UserType deserializedType2 = deserialized.getNullable(bytes("type2"));
+
+        assertNotNull("Type1 should exist", deserializedType1);
+        assertNotNull("Type2 should exist", deserializedType2);
+        assertEquals("Type 1 comment", deserializedType1.comment);
+        assertEquals("Type 1 label", deserializedType1.securityLabel);
+        assertEquals("Type 2 comment", deserializedType2.comment);
+        assertEquals("Type 2 label", deserializedType2.securityLabel);
+    }
+
+    @Test
+    public void testCompleteKeyspaceMetadataSerialization() throws IOException
+    {
+        UserType addressType = createUserType("address", "Address type", "PUBLIC");
+
+        ColumnIdentifier nameCol = ColumnIdentifier.getInterned("name", false);
+        TableMetadata table = TableMetadata.builder(KEYSPACE, "users")
+                                           .addPartitionKeyColumn("id", Int32Type.instance)
+                                           .addRegularColumn(nameCol, UTF8Type.instance)
+                                           .alterColumnComment(nameCol, "User name")
+                                           .alterColumnSecurityLabel(nameCol, "PUBLIC")
+                                           .comment("Users table")
+                                           .securityLabel("SENSITIVE")
+                                           .build();
+
+        KeyspaceMetadata original = KeyspaceMetadata.create(KEYSPACE,
+                                                             KeyspaceParams.simple(1)
+                                                                           .withComment("Test keyspace")
+                                                                           .withSecurityLabel("TEST"),
+                                                             Tables.of(table),
+                                                             Views.none(),
+                                                             Types.of(addressType),
+                                                             UserFunctions.none());
+
+        KeyspaceMetadata deserialized = serializeAndDeserializeKeyspace(original);
+
+        // Verify keyspace metadata
+        assertEquals("Test keyspace", deserialized.params.comment);
+        assertEquals("TEST", deserialized.params.securityLabel);
+
+        // Verify table metadata
+        TableMetadata deserializedTable = deserialized.tables.getNullable("users");
+        assertNotNull("Table should exist", deserializedTable);
+        assertEquals("Users table", deserializedTable.params.comment);
+        assertEquals("SENSITIVE", deserializedTable.params.securityLabel);
+
+        // Verify column metadata
+        ColumnMetadata nameColumn = deserializedTable.getColumn(bytes("name"));
+        assertNotNull("Name column should exist", nameColumn);
+        assertEquals("User name", nameColumn.comment);
+        assertEquals("PUBLIC", nameColumn.securityLabel);
+
+        // Verify type metadata
+        UserType deserializedType = deserialized.types.getNullable(bytes("address"));
+        assertNotNull("Type should exist", deserializedType);
+        assertEquals("Address type", deserializedType.comment);
+        assertEquals("PUBLIC", deserializedType.securityLabel);
+    }
+
+    @Test
+    public void testBackwardCompatibilityV7() throws IOException
+    {
+        UserType type = createUserType("test_type", "Test comment", "Test label");
+
+        // Serialize with V7 (should not include metadata)
+        DataOutputBuffer out = new DataOutputBuffer();
+        Types.serializer.serialize(Types.of(type), out, Version.V7);
+
+        // Deserialize with V7
+        DataInputBuffer in = new DataInputBuffer(out.toByteArray());
+        Types deserializedTypes = Types.serializer.deserialize(KEYSPACE, in, Version.V7);
+
+        // Verify metadata is empty for V7
+        UserType deserializedType = deserializedTypes.getNullable(bytes("test_type"));
+        assertNotNull("Type should exist", deserializedType);
+        assertEquals("", deserializedType.comment);
+        assertEquals("", deserializedType.securityLabel);
+    }
+
+    // Helper methods for creating test objects
+
+    private TableMetadata createSimpleTable(String name, String comment, String securityLabel)
+    {
+        return TableMetadata.builder(KEYSPACE, name)
+                            .addPartitionKeyColumn("id", Int32Type.instance)
+                            .addRegularColumn("email", UTF8Type.instance)
+                            .comment(comment)
+                            .securityLabel(securityLabel)
+                            .build();
+    }
+
+    private UserType createUserType(String name, String comment, String securityLabel)
+    {
+        FieldIdentifier field = FieldIdentifier.forUnquoted("field1");
+        return new UserType(KEYSPACE,
+                           bytes(name),
+                           Arrays.asList(field),
+                           Arrays.asList(UTF8Type.instance),
+                           true,
+                           comment,
+                           securityLabel);
+    }
+
+    // Helper methods for serialization/deserialization
+
+    private KeyspaceMetadata serializeAndDeserializeKeyspace(KeyspaceMetadata original) throws IOException
+    {
+        DataOutputBuffer out = new DataOutputBuffer();
+        KeyspaceMetadata.serializer.serialize(original, out, Version.V8);
+
+        DataInputBuffer in = new DataInputBuffer(out.toByteArray());
+        return KeyspaceMetadata.serializer.deserialize(in, Version.V8);
+    }
+
+    private TableMetadata serializeAndDeserializeTable(TableMetadata original) throws IOException
+    {
+        DataOutputBuffer out = new DataOutputBuffer();
+        TableMetadata.serializer.serialize(original, out, Version.V8);
+
+        DataInputBuffer in = new DataInputBuffer(out.toByteArray());
+        return TableMetadata.serializer.deserialize(in, Types.none(), UserFunctions.none(), Version.V8);
+    }
+
+    private UserType serializeAndDeserializeType(UserType original) throws IOException
+    {
+        Types types = Types.of(original);
+        return serializeAndDeserializeTypes(types).getNullable(original.name);
+    }
+
+    private Types serializeAndDeserializeTypes(Types original) throws IOException
+    {
+        DataOutputBuffer out = new DataOutputBuffer();
+        Types.serializer.serialize(original, out, Version.V8);
+
+        DataInputBuffer in = new DataInputBuffer(out.toByteArray());
+        return Types.serializer.deserialize(KEYSPACE, in, Version.V8);
+    }
+}
diff --git a/test/unit/org/apache/cassandra/schema/createlike/CreateLikeTest.java b/test/unit/org/apache/cassandra/schema/createlike/CreateLikeTest.java
index 291a050464..e7b786cd14 100644
--- a/test/unit/org/apache/cassandra/schema/createlike/CreateLikeTest.java
+++ b/test/unit/org/apache/cassandra/schema/createlike/CreateLikeTest.java
@@ -42,6 +42,7 @@ import org.apache.cassandra.cql3.CQLTester;
 import org.apache.cassandra.cql3.Duration;
 import org.apache.cassandra.cql3.validation.operations.CreateTest;
 import org.apache.cassandra.db.compaction.LeveledCompactionStrategy;
+import org.apache.cassandra.db.marshal.UTF8Type;
 import org.apache.cassandra.exceptions.AlreadyExistsException;
 import org.apache.cassandra.exceptions.ConfigurationException;
 import org.apache.cassandra.exceptions.InvalidRequestException;
@@ -581,7 +582,7 @@ public class CreateLikeTest extends CQLTester
     {
         createTable(sourceKs, "CREATE TABLE %s (a int PRIMARY KEY, b int, c text)", "tb");
         String index = createIndex("CREATE INDEX ON " + sourceKs + ".tb (c)");
-        assertInvalidThrowMessage("Souce Table '" + targetKs + "." + index + "' doesn't exist", InvalidRequestException.class,
+        assertInvalidThrowMessage("Source Table '" + targetKs + "." + index + "' doesn't exist", InvalidRequestException.class,
                                   "CREATE TABLE " + sourceKs + ".newtb LIKE  " + targetKs + "." + index + ";");
         assertInvalidThrowMessage("System keyspace 'system' is not user-modifiable", InvalidRequestException.class,
                                   "CREATE TABLE system.local_clone LIKE system.local ;");
@@ -700,6 +701,82 @@ public class CreateLikeTest extends CQLTester
                               "Source table " + sourceKs + "." + sourceTb + " to copy indexes from to " + targetKs + ".targettbb has custom indexes. These indexes were not copied: " + customIndexes);
     }
 
+    @Test
+    public void testCreateTableLikeWithComments() throws Throwable
+    {
+        String sourceTb = createTable(sourceKs, "CREATE TABLE %s (a int PRIMARY KEY, b int, c text)", "source_tb_comments");
+        execute("COMMENT ON TABLE " + sourceKs + "." + sourceTb + " IS 'Test table comment'");
+        execute("COMMENT ON COLUMN " + sourceKs + "." + sourceTb + ".b IS 'Test column comment'");
+
+        // Test CREATE TABLE copy LIKE source WITH COMMENTS
+        String targetTb = createTableLike("CREATE TABLE %s LIKE %s WITH COMMENTS", sourceTb, sourceKs, targetKs);
+
+        // Verify that the table structure is copied
+        assertTableMetaEqualsWithoutKs(sourceKs, targetKs, sourceTb, targetTb, true, false, false);
+
+        // Verify that comments are copied
+        TableMetadata sourceTable = getTableMetadata(sourceKs, sourceTb);
+        TableMetadata targetTable = getTableMetadata(targetKs, targetTb);
+
+        assertEquals("Table comment should be copied", sourceTable.params.comment, targetTable.params.comment);
+        assertEquals("Column comment should be copied",
+                    sourceTable.getColumn(UTF8Type.instance.decompose("b")).comment,
+                    targetTable.getColumn(UTF8Type.instance.decompose("b")).comment);
+    }
+
+    @Test
+    public void testCreateTableLikeWithSecurityLabels() throws Throwable
+    {
+        String sourceTb = createTable(sourceKs, "CREATE TABLE %s (a int PRIMARY KEY, b int, c text)", "source_tb_labels");
+        execute("SECURITY LABEL ON TABLE " + sourceKs + "." + sourceTb + " IS 'confidential'");
+        execute("SECURITY LABEL ON COLUMN " + sourceKs + "." + sourceTb + ".b IS 'restricted'");
+
+        // Test CREATE TABLE copy LIKE source WITH SECURITY LABELS
+        String targetTb = createTableLike("CREATE TABLE %s LIKE %s WITH SECURITY LABELS", sourceTb, sourceKs, targetKs);
+
+        // Verify that the table structure is copied
+        assertTableMetaEqualsWithoutKs(sourceKs, targetKs, sourceTb, targetTb, true, false, false);
+
+        // Verify that security labels are copied
+        TableMetadata sourceTable = getTableMetadata(sourceKs, sourceTb);
+        TableMetadata targetTable = getTableMetadata(targetKs, targetTb);
+
+        assertEquals("Table security label should be copied", sourceTable.params.securityLabel, targetTable.params.securityLabel);
+        assertEquals("Column security label should be copied",
+                    sourceTable.getColumn(UTF8Type.instance.decompose("b")).securityLabel,
+                    targetTable.getColumn(UTF8Type.instance.decompose("b")).securityLabel);
+    }
+
+    @Test
+    public void testCreateTableLikeWithAllOptions() throws Throwable
+    {
+        String sourceTb = createTable(sourceKs, "CREATE TABLE %s (a int PRIMARY KEY, b int, c text)", "source_tb_all");
+        execute("COMMENT ON TABLE " + sourceKs + "." + sourceTb + " IS 'Test table comment'");
+        execute("COMMENT ON COLUMN " + sourceKs + "." + sourceTb + ".b IS 'Test column comment'");
+        execute("SECURITY LABEL ON TABLE " + sourceKs + "." + sourceTb + " IS 'confidential'");
+        execute("SECURITY LABEL ON COLUMN " + sourceKs + "." + sourceTb + ".b IS 'restricted'");
+        createIndex(sourceKs, "CREATE INDEX ON %s (b)");
+
+        // Test CREATE TABLE copy LIKE source WITH INDEXES AND COMMENTS AND SECURITY LABELS
+        String targetTb = createTableLike("CREATE TABLE %s LIKE %s WITH INDEXES AND COMMENTS AND SECURITY LABELS", sourceTb, sourceKs, targetKs);
+
+        // Verify that the table structure and indexes are copied
+        assertTableMetaEqualsWithoutKs(sourceKs, targetKs, sourceTb, targetTb, true, true, true);
+
+        // Verify that comments and security labels are copied
+        TableMetadata sourceTable = getTableMetadata(sourceKs, sourceTb);
+        TableMetadata targetTable = getTableMetadata(targetKs, targetTb);
+
+        assertEquals("Table comment should be copied", sourceTable.params.comment, targetTable.params.comment);
+        assertEquals("Table security label should be copied", sourceTable.params.securityLabel, targetTable.params.securityLabel);
+        assertEquals("Column comment should be copied",
+                    sourceTable.getColumn(UTF8Type.instance.decompose("b")).comment,
+                    targetTable.getColumn(UTF8Type.instance.decompose("b")).comment);
+        assertEquals("Column security label should be copied",
+                    sourceTable.getColumn(UTF8Type.instance.decompose("b")).securityLabel,
+                    targetTable.getColumn(UTF8Type.instance.decompose("b")).securityLabel);
+    }
+
     private void assertTableMetaEqualsWithoutKs(String sourceKs, String targetKs, String sourceTb, String targetTb)
     {
         assertTableMetaEqualsWithoutKs(sourceKs, targetKs, sourceTb, targetTb, true, true, false);
diff --git a/test/unit/org/apache/cassandra/schema/createlike/CreateLikeWithSessionTest.java b/test/unit/org/apache/cassandra/schema/createlike/CreateLikeWithSessionTest.java
index e676961d18..2045868646 100644
--- a/test/unit/org/apache/cassandra/schema/createlike/CreateLikeWithSessionTest.java
+++ b/test/unit/org/apache/cassandra/schema/createlike/CreateLikeWithSessionTest.java
@@ -59,7 +59,7 @@ public class CreateLikeWithSessionTest extends CQLTester
 
 
         assertThatExceptionOfType(com.datastax.driver.core.exceptions.InvalidQueryException.class).isThrownBy(() -> executeNet("CREATE TABLE tb7 LIKE " + ks2 + "." + tb1))
-                                                                                                  .withMessage("Souce Table 'ks2.tb1' doesn't exist");
+                                                                                                  .withMessage("Source Table 'ks2.tb1' doesn't exist");
 
         assertNotNull(getTableMetadata(ks1, tb1));
         assertNotNull(getTableMetadata(ks1, "tb3"));
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/ExportImportListCompressionDictionaryTest.java b/test/unit/org/apache/cassandra/tools/nodetool/ExportImportListCompressionDictionaryTest.java
index bd806ef953..fbb6f2a3aa 100644
--- a/test/unit/org/apache/cassandra/tools/nodetool/ExportImportListCompressionDictionaryTest.java
+++ b/test/unit/org/apache/cassandra/tools/nodetool/ExportImportListCompressionDictionaryTest.java
@@ -38,7 +38,6 @@ import static java.lang.String.format;
 import static org.apache.cassandra.tools.ToolRunner.invokeNodetool;
 import static org.apache.cassandra.utils.JsonUtils.serializeToJsonFile;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 public class ExportImportListCompressionDictionaryTest extends CQLTester
@@ -151,10 +150,9 @@ public class ExportImportListCompressionDictionaryTest extends CQLTester
 
         ToolResult result = invokeNodetool("compressiondictionary", "import", pair.right.absolutePath());
         Assert.assertEquals(1, result.getExitCode());
-        assertEquals(format("Unable to import dictionary JSON: Table %s.%s does not exist or does not support dictionary compression\n",
-                            keyspace(),
-                            table),
-                     result.getStderr());
+        assertTrue(result.getStderr().contains(format("Unable to import dictionary JSON: Table %s.%s does not exist or does not support dictionary compression",
+                                                      keyspace(),
+                                                      table)));
     }
 
     private void importDictionary(File dictFile)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org