mirror of https://github.com/apache/cassandra
CASSANDRA-20943 Introducing comments and security labels for schema elements
This commit is contained in:
parent
dc89b8c802
commit
b11633be4f
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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> )
|
||||
;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(';');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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" +
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + "'");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"));
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue