Add support for server side DESCRIBE statements

patch by Robert Stupp; reviewed by Dinesh Joshi and Benjamin Lerer for CASSANDRA-14825
This commit is contained in:
Robert Stupp 2019-09-09 22:28:54 +02:00 committed by Benjamin Lerer
parent 3cfe3c9f0d
commit 4e47bfb3a1
52 changed files with 4014 additions and 1230 deletions

View File

@ -1,4 +1,5 @@
4.0-alpha5 4.0-alpha5
* Add support for server side DESCRIBE statements (CASSANDRA-14825)
* Fail startup if -Xmn is set when the G1 garbage collector is used (CASSANDRA-15839) * Fail startup if -Xmn is set when the G1 garbage collector is used (CASSANDRA-15839)
* generateSplits method replaced the generateRandomTokens for ReplicationAwareTokenAllocator. (CASSANDRA-15877) * generateSplits method replaced the generateRandomTokens for ReplicationAwareTokenAllocator. (CASSANDRA-15877)
* Several mbeans are not unregistered when dropping a keyspace and table (CASSANDRA-14888) * Several mbeans are not unregistered when dropping a keyspace and table (CASSANDRA-14888)

View File

@ -113,6 +113,7 @@ New features
- Added Python 3 support so cqlsh and cqlshlib is now compatible with Python 2.7 and Python 3.6. - Added Python 3 support so cqlsh and cqlshlib is now compatible with Python 2.7 and Python 3.6.
Added --python option to cqlsh so users can specify the path to their chosen Python interpreter. Added --python option to cqlsh so users can specify the path to their chosen Python interpreter.
See CASSANDRA-10190 for details. See CASSANDRA-10190 for details.
- Support for server side DESCRIBE statements has been added. See CASSANDRA-14825
Upgrading Upgrading
--------- ---------

View File

@ -1244,208 +1244,16 @@ class Shell(cmd.Cmd):
if valstr is not None: if valstr is not None:
return cqlruleset.dequote_value(valstr) return cqlruleset.dequote_value(valstr)
def print_recreate_keyspace(self, ksdef, out): def _columnize_unicode(self, name_list):
out.write(ksdef.export_as_string())
out.write("\n")
def print_recreate_columnfamily(self, ksname, cfname, out):
"""
Output CQL commands which should be pasteable back into a CQL session
to recreate the given table.
Writes output to the given out stream.
"""
out.write(self.get_table_meta(ksname, cfname).export_as_string())
out.write("\n")
def print_recreate_index(self, ksname, idxname, out):
"""
Output CQL commands which should be pasteable back into a CQL session
to recreate the given index.
Writes output to the given out stream.
"""
out.write(self.get_index_meta(ksname, idxname).export_as_string())
out.write("\n")
def print_recreate_materialized_view(self, ksname, viewname, out):
"""
Output CQL commands which should be pasteable back into a CQL session
to recreate the given materialized view.
Writes output to the given out stream.
"""
out.write(self.get_view_meta(ksname, viewname).export_as_string())
out.write("\n")
def print_recreate_object(self, ks, name, out):
"""
Output CQL commands which should be pasteable back into a CQL session
to recreate the given object (ks, table or index).
Writes output to the given out stream.
"""
out.write(self.get_object_meta(ks, name).export_as_string())
out.write("\n")
def describe_keyspaces(self):
print('')
cmd.Cmd.columnize(self, protect_names(self.get_keyspace_names()))
print('')
def describe_keyspace(self, ksname):
print('')
self.print_recreate_keyspace(self.get_keyspace_meta(ksname), sys.stdout)
print('')
def describe_columnfamily(self, ksname, cfname):
if ksname is None:
ksname = self.current_keyspace
if ksname is None:
raise NoKeyspaceError("No keyspace specified and no current keyspace")
print('')
self.print_recreate_columnfamily(ksname, cfname, sys.stdout)
print('')
def describe_index(self, ksname, idxname):
print('')
self.print_recreate_index(ksname, idxname, sys.stdout)
print('')
def describe_materialized_view(self, ksname, viewname):
if ksname is None:
ksname = self.current_keyspace
if ksname is None:
raise NoKeyspaceError("No keyspace specified and no current keyspace")
print('')
self.print_recreate_materialized_view(ksname, viewname, sys.stdout)
print('')
def describe_object(self, ks, name):
print('')
self.print_recreate_object(ks, name, sys.stdout)
print('')
def describe_columnfamilies(self, ksname):
print('')
if ksname is None:
for k in self.get_keyspaces():
name = protect_name(k.name)
print('Keyspace %s' % (name,))
print('---------%s' % ('-' * len(name)))
cmd.Cmd.columnize(self, protect_names(self.get_columnfamily_names(k.name)))
print('')
else:
cmd.Cmd.columnize(self, protect_names(self.get_columnfamily_names(ksname)))
print('')
def describe_functions(self, ksname):
print('')
if ksname is None:
for ksmeta in self.get_keyspaces():
name = protect_name(ksmeta.name)
print('Keyspace %s' % (name,))
print('---------%s' % ('-' * len(name)))
self._columnize_unicode(list(ksmeta.functions.keys()))
else:
ksmeta = self.get_keyspace_meta(ksname)
self._columnize_unicode(list(ksmeta.functions.keys()))
def describe_function(self, ksname, functionname):
if ksname is None:
ksname = self.current_keyspace
if ksname is None:
raise NoKeyspaceError("No keyspace specified and no current keyspace")
print('')
ksmeta = self.get_keyspace_meta(ksname)
functions = [f for f in list(ksmeta.functions.values()) if f.name == functionname]
if len(functions) == 0:
raise FunctionNotFound("User defined function {} not found".format(functionname))
print("\n\n".join(func.export_as_string() for func in functions))
print('')
def describe_aggregates(self, ksname):
print('')
if ksname is None:
for ksmeta in self.get_keyspaces():
name = protect_name(ksmeta.name)
print('Keyspace %s' % (name,))
print('---------%s' % ('-' * len(name)))
self._columnize_unicode(list(ksmeta.aggregates.keys()))
else:
ksmeta = self.get_keyspace_meta(ksname)
self._columnize_unicode(list(ksmeta.aggregates.keys()))
def describe_aggregate(self, ksname, aggregatename):
if ksname is None:
ksname = self.current_keyspace
if ksname is None:
raise NoKeyspaceError("No keyspace specified and no current keyspace")
print('')
ksmeta = self.get_keyspace_meta(ksname)
aggregates = [f for f in list(ksmeta.aggregates.values()) if f.name == aggregatename]
if len(aggregates) == 0:
raise FunctionNotFound("User defined aggregate {} not found".format(aggregatename))
print("\n\n".join(aggr.export_as_string() for aggr in aggregates))
print('')
def describe_usertypes(self, ksname):
print('')
if ksname is None:
for ksmeta in self.get_keyspaces():
name = protect_name(ksmeta.name)
print('Keyspace %s' % (name,))
print('---------%s' % ('-' * len(name)))
self._columnize_unicode(list(ksmeta.user_types.keys()), quote=True)
else:
ksmeta = self.get_keyspace_meta(ksname)
self._columnize_unicode(list(ksmeta.user_types.keys()), quote=True)
def describe_usertype(self, ksname, typename):
if ksname is None:
ksname = self.current_keyspace
if ksname is None:
raise NoKeyspaceError("No keyspace specified and no current keyspace")
print('')
ksmeta = self.get_keyspace_meta(ksname)
try:
usertype = ksmeta.user_types[typename]
except KeyError:
raise UserTypeNotFound("User type {} not found".format(typename))
print(usertype.export_as_string())
def _columnize_unicode(self, name_list, quote=False):
""" """
Used when columnizing identifiers that may contain unicode Used when columnizing identifiers that may contain unicode
""" """
names = [n for n in name_list] names = [n for n in name_list]
if quote:
names = protect_names(names)
cmd.Cmd.columnize(self, names) cmd.Cmd.columnize(self, names)
print('') print('')
def describe_cluster(self):
print('\nCluster: %s' % self.get_cluster_name())
p = trim_if_present(self.get_partitioner(), 'org.apache.cassandra.dht.')
print('Partitioner: %s\n' % p)
# TODO: snitch?
# snitch = trim_if_present(self.get_snitch(), 'org.apache.cassandra.locator.')
# print 'Snitch: %s\n' % snitch
if self.current_keyspace is not None and self.current_keyspace != 'system':
print("Range ownership:")
ring = self.get_ring(self.current_keyspace)
for entry in list(ring.items()):
print(' %39s [%s]' % (str(entry[0].value), ', '.join([host.address for host in entry[1]])))
print('')
def describe_schema(self, include_system=False):
print('')
for k in self.get_keyspaces():
if include_system or k.name not in cql3handling.SYSTEM_KEYSPACES:
self.print_recreate_keyspace(k, sys.stdout)
print('')
def do_describe(self, parsed): def do_describe(self, parsed):
""" """
DESCRIBE [cqlsh only] DESCRIBE [cqlsh only]
@ -1464,7 +1272,6 @@ class Shell(cmd.Cmd):
and the objects in it (such as tables, types, functions, etc.). and the objects in it (such as tables, types, functions, etc.).
In some cases, as the CQL interface matures, there will be some metadata In some cases, as the CQL interface matures, there will be some metadata
about a keyspace that is not representable with CQL. That metadata will not be shown. about a keyspace that is not representable with CQL. That metadata will not be shown.
The '<keyspacename>' argument may be omitted, in which case the current The '<keyspacename>' argument may be omitted, in which case the current
keyspace will be described. keyspace will be described.
@ -1536,66 +1343,102 @@ class Shell(cmd.Cmd):
Output CQL commands that could be used to recreate the entire object schema, Output CQL commands that could be used to recreate the entire object schema,
where object can be either a keyspace or a table or an index or a materialized where object can be either a keyspace or a table or an index or a materialized
view (in this order). view (in this order).
""" """
what = parsed.matched[1][1].lower() stmt = SimpleStatement(parsed.extract_orig(), consistency_level=cassandra.ConsistencyLevel.LOCAL_ONE, fetch_size=self.page_size if self.use_paging else None)
if what == 'functions': future = self.session.execute_async(stmt)
self.describe_functions(self.current_keyspace)
elif what == 'function': try:
ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None)) result = future.result()
functionname = self.cql_unprotect_name(parsed.get_binding('udfname'))
self.describe_function(ksname, functionname) what = parsed.matched[1][1].lower()
elif what == 'aggregates':
self.describe_aggregates(self.current_keyspace) if what in ('columnfamilies', 'tables', 'types', 'functions', 'aggregates'):
elif what == 'aggregate': self.describe_list(result)
ksname = self.cql_unprotect_name(parsed.get_binding('ksname', None)) elif what == 'keyspaces':
aggregatename = self.cql_unprotect_name(parsed.get_binding('udaname')) self.describe_keyspaces(result)
self.describe_aggregate(ksname, aggregatename) elif what == 'cluster':
elif what == 'keyspaces': self.describe_cluster(result)
self.describe_keyspaces() elif what:
elif what == 'keyspace': self.describe_element(result)
ksname = self.cql_unprotect_name(parsed.get_binding('ksname', ''))
if not ksname: except CQL_ERRORS as err:
ksname = self.current_keyspace err_msg = ensure_text(err.message if hasattr(err, 'message') else str(err))
if ksname is None: self.printerr(err_msg.partition("message=")[2].strip('"'))
self.printerr('Not in any keyspace.') except Exception:
return import traceback
self.describe_keyspace(ksname) self.printerr(traceback.format_exc())
elif what in ('columnfamily', 'table'):
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None)) if future:
cf = self.cql_unprotect_name(parsed.get_binding('cfname')) if future.warnings:
self.describe_columnfamily(ks, cf) self.print_warnings(future.warnings)
elif what == 'index':
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
idx = self.cql_unprotect_name(parsed.get_binding('idxname', None))
self.describe_index(ks, idx)
elif what == 'materialized' and parsed.matched[2][1].lower() == 'view':
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
mv = self.cql_unprotect_name(parsed.get_binding('mvname'))
self.describe_materialized_view(ks, mv)
elif what in ('columnfamilies', 'tables'):
self.describe_columnfamilies(self.current_keyspace)
elif what == 'types':
self.describe_usertypes(self.current_keyspace)
elif what == 'type':
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
ut = self.cql_unprotect_name(parsed.get_binding('utname'))
self.describe_usertype(ks, ut)
elif what == 'cluster':
self.describe_cluster()
elif what == 'schema':
self.describe_schema(False)
elif what == 'full' and parsed.matched[2][1].lower() == 'schema':
self.describe_schema(True)
elif what:
ks = self.cql_unprotect_name(parsed.get_binding('ksname', None))
name = self.cql_unprotect_name(parsed.get_binding('cfname'))
if not name:
name = self.cql_unprotect_name(parsed.get_binding('idxname', None))
if not name:
name = self.cql_unprotect_name(parsed.get_binding('mvname', None))
self.describe_object(ks, name)
do_desc = do_describe do_desc = do_describe
def describe_keyspaces(self, rows):
"""
Print the output for a DESCRIBE KEYSPACES query
"""
names = list()
for row in rows:
names.append(str(row['name']))
print('')
cmd.Cmd.columnize(self, names)
print('')
def describe_list(self, rows):
"""
Print the output for all the DESCRIBE queries for element names (e.g DESCRIBE TABLES, DESCRIBE FUNCTIONS ...)
"""
keyspace = None
names = list()
for row in rows:
if row['keyspace_name'] != keyspace:
if keyspace is not None:
self.print_keyspace_element_names(keyspace, names)
keyspace = row['keyspace_name']
names = list()
names.append(str(row['name']))
if keyspace is not None:
self.print_keyspace_element_names(keyspace, names)
print('')
def print_keyspace_element_names(self, keyspace, names):
print('')
if self.current_keyspace is None:
print('Keyspace %s' % (keyspace))
print('---------%s' % ('-' * len(keyspace)))
cmd.Cmd.columnize(self, names)
def describe_element(self, rows):
"""
Print the output for all the DESCRIBE queries where an element name as been specified (e.g DESCRIBE TABLE, DESCRIBE INDEX ...)
"""
for row in rows:
print('')
self.query_out.write(row['create_statement'])
print('')
def describe_cluster(self, rows):
"""
Print the output for a DESCRIBE CLUSTER query.
If a specified keyspace was in use the returned ResultSet will contains a 'range_ownership' column,
otherwise not.
"""
for row in rows:
print('\nCluster: %s' % row['cluster'])
print('Partitioner: %s' % row['partitioner'])
print('Snitch: %s\n' % row['snitch'])
if 'range_ownership' in row:
print("Range ownership:")
for entry in list(row['range_ownership'].items()):
print(' %39s [%s]' % (entry[0], ', '.join([host for host in entry[1]])))
print('')
def do_copy(self, parsed): def do_copy(self, parsed):
r""" r"""
COPY [cqlsh only] COPY [cqlsh only]

View File

@ -850,3 +850,325 @@ Note that ``TRUNCATE TABLE foo`` is allowed for consistency with other DDL state
that can be truncated currently and so the ``TABLE`` keyword can be omitted. that can be truncated currently and so the ``TABLE`` keyword can be omitted.
Truncating a table permanently removes all existing data from the table, but without removing the table itself. Truncating a table permanently removes all existing data from the table, but without removing the table itself.
.. _describe-statements:
DESCRIBE
^^^^^^^^
Statements used to outputs information about the connected Cassandra cluster,
or about the data objects stored in the cluster.
.. warning:: Describe statement resultset that exceed the page size are paged. If a schema changes is detected between
two pages the query will fail with an ``InvalidRequestException``. It is safe to retry the whole ``DESCRIBE``
statement after such an error.
DESCRIBE KEYSPACES
""""""""""""""""""
Output the names of all keyspaces.
.. productionlist::
describe_keyspaces_statement: DESCRIBE KEYSPACES
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
======================== ========= ====================================================================================
DESCRIBE KEYSPACE
"""""""""""""""""
Output CQL commands that could be used to recreate the given keyspace, and the objects in it
(such as tables, types, functions, etc.).
.. productionlist::
describe_keyspace_statement: DESCRIBE [ONLY] KEYSPACE [`keyspace_name`] [WITH INTERNALS]
The ``keyspace_name`` argument may be omitted, in which case the current keyspace will be described.
If ``WITH INTERNALS`` is specified, the output contains the table IDs and is adopted to represent the DDL necessary
to "re-create" dropped columns.
If ``ONLY`` is specified, only the DDL to recreate the keyspace will be created. All keyspace elements, like tables,
types, functions, etc will be omitted.
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE TABLES
"""""""""""""""
Output the names of all tables in the current keyspace, or in all keyspaces if there is no current keyspace.
.. productionlist::
describe_tables_statement: DESCRIBE TABLES
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
======================== ========= ====================================================================================
DESCRIBE TABLE
""""""""""""""
Output CQL commands that could be used to recreate the given table.
.. productionlist::
describe_table_statement: DESCRIBE TABLE [`keyspace_name`.]`table_name` [WITH INTERNALS]
If `WITH INTERNALS` is specified, the output contains the table ID and is adopted to represent the DDL necessary
to "re-create" dropped columns.
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE INDEX
""""""""""""""
Output the CQL command that could be used to recreate the given index.
.. productionlist::
describe_index_statement: DESCRIBE INDEX [`keyspace_name`.]`index_name`
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE MATERIALIZED VIEW
""""""""""""""""""""""""""
Output the CQL command that could be used to recreate the given materialized view.
.. productionlist::
describe_materialized_view_statement: DESCRIBE MATERIALIZED VIEW [`keyspace_name`.]`view_name`
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE CLUSTER
""""""""""""""""
Output information about the connected Cassandra cluster, such as the cluster name, and the partitioner and snitch
in use. When you are connected to a non-system keyspace, also shows endpoint-range ownership information for
the Cassandra ring.
.. productionlist::
describe_cluster_statement: DESCRIBE CLUSTER
Returned columns:
======================== ====================== ========================================================================
Columns Type Description
======================== ====================== ========================================================================
cluster text The cluster name
partitioner text The partitioner being used by the cluster
snitch text The snitch being used by the cluster
range_ownership map<text, list<text>> The CQL statement to use to recreate the schema element
======================== ====================== ========================================================================
DESCRIBE SCHEMA
"""""""""""""""
Output CQL commands that could be used to recreate the entire (non-system) schema.
Works as though "DESCRIBE KEYSPACE k" was invoked for each non-system keyspace
.. productionlist::
describe_schema_statement: DESCRIBE [FULL] SCHEMA [WITH INTERNALS]
Use ``DESCRIBE FULL SCHEMA`` to include the system keyspaces.
If ``WITH INTERNALS`` is specified, the output contains the table IDs and is adopted to represent the DDL necessary
to "re-create" dropped columns.
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE TYPES
""""""""""""""
Output the names of all user-defined-types in the current keyspace, or in all keyspaces if there is no current keyspace.
.. productionlist::
describe_types_statement: DESCRIBE TYPES
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
======================== ========= ====================================================================================
DESCRIBE TYPE
"""""""""""""
Output the CQL command that could be used to recreate the given user-defined-type.
.. productionlist::
describe_type_statement: DESCRIBE TYPE [`keyspace_name`.]`type_name`
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE FUNCTIONS
""""""""""""""""""
Output the names of all user-defined-functions in the current keyspace, or in all keyspaces if there is no current
keyspace.
.. productionlist::
describe_functions_statement: DESCRIBE FUNCTIONS
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
======================== ========= ====================================================================================
DESCRIBE FUNCTION
"""""""""""""""""
Output the CQL command that could be used to recreate the given user-defined-function.
.. productionlist::
describe_function_statement: DESCRIBE FUNCTION [`keyspace_name`.]`function_name`
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE AGGREGATES
"""""""""""""""""""
Output the names of all user-defined-aggregates in the current keyspace, or in all keyspaces if there is no current
keyspace.
.. productionlist::
describe_aggregates_statement: DESCRIBE AGGREGATES
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
======================== ========= ====================================================================================
DESCRIBE AGGREGATE
""""""""""""""""""
Output the CQL command that could be used to recreate the given user-defined-aggregate.
.. productionlist::
describe_aggregate_statement: DESCRIBE AGGREGATE [`keyspace_name`.]`aggregate_name`
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================
DESCRIBE object
"""""""""""""""
Output CQL commands that could be used to recreate the entire object schema, where object can be either a keyspace
or a table or an index or a materialized view (in this order).
.. productionlist::
describe_object_statement: DESCRIBE `object_name` [WITH INTERNALS]
If ``WITH INTERNALS`` is specified and ``object_name`` represents a keyspace or table the output contains the table IDs
and is adopted to represent the DDL necessary to "re-create" dropped columns.
``object_name`` cannot be any of the "describe what" qualifiers like "cluster", "table", etc.
Returned columns:
======================== ========= ====================================================================================
Columns Type Description
======================== ========= ====================================================================================
keyspace_name text The keyspace name
type text The schema element type
name text The schema element name
create_statement text The CQL statement to use to recreate the schema element
======================== ========= ====================================================================================

View File

@ -78,12 +78,12 @@ cqlsh_special_cmd_command_syntax_rules = r'''
cqlsh_describe_cmd_syntax_rules = r''' cqlsh_describe_cmd_syntax_rules = r'''
<describeCommand> ::= ( "DESCRIBE" | "DESC" ) <describeCommand> ::= ( "DESCRIBE" | "DESC" )
( "FUNCTIONS" ( ( "FUNCTIONS"
| "FUNCTION" udf=<anyFunctionName> | "FUNCTION" udf=<anyFunctionName>
| "AGGREGATES" | "AGGREGATES"
| "AGGREGATE" uda=<userAggregateName> | "AGGREGATE" uda=<userAggregateName>
| "KEYSPACES" | "KEYSPACES"
| "KEYSPACE" ksname=<keyspaceName>? | "ONLY"? "KEYSPACE" ksname=<keyspaceName>?
| ( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName> | ( "COLUMNFAMILY" | "TABLE" ) cf=<columnFamilyName>
| "INDEX" idx=<indexName> | "INDEX" idx=<indexName>
| "MATERIALIZED" "VIEW" mv=<materializedViewName> | "MATERIALIZED" "VIEW" mv=<materializedViewName>
@ -92,7 +92,9 @@ cqlsh_describe_cmd_syntax_rules = r'''
| "CLUSTER" | "CLUSTER"
| "TYPES" | "TYPES"
| "TYPE" ut=<userTypeName> | "TYPE" ut=<userTypeName>
| (ksname=<keyspaceName> | cf=<columnFamilyName> | idx=<indexName> | mv=<materializedViewName>)) | (ksname=<keyspaceName> | cf=<columnFamilyName> | idx=<indexName> | mv=<materializedViewName>)
) ("WITH" "INTERNALS")?
)
; ;
''' '''

View File

@ -637,7 +637,7 @@ class TestCqlshOutput(BaseTestCase):
def test_describe_columnfamily_output(self): def test_describe_columnfamily_output(self):
# we can change these to regular expressions if/when it makes sense # we can change these to regular expressions if/when it makes sense
# to do so; these will likely be subject to lots of adjustments. # to do so; these will likely be subject to lots of adjustments.
# note columns are now comparator-ordered instead of original-order. # note columns are now comparator-ordered instead of original-order.
table_desc3 = dedent(""" table_desc3 = dedent("""
CREATE TABLE %s.has_all_types ( CREATE TABLE %s.has_all_types (
@ -666,13 +666,13 @@ class TestCqlshOutput(BaseTestCase):
AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'} AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
AND crc_check_chance = 1.0 AND crc_check_chance = 1.0
AND default_time_to_live = 0 AND default_time_to_live = 0
AND extensions = {}
AND gc_grace_seconds = 864000 AND gc_grace_seconds = 864000
AND max_index_interval = 2048 AND max_index_interval = 2048
AND memtable_flush_period_in_ms = 0 AND memtable_flush_period_in_ms = 0
AND min_index_interval = 128 AND min_index_interval = 128
AND read_repair = 'BLOCKING' AND read_repair = 'BLOCKING'
AND speculative_retry = '99p'; AND speculative_retry = '99p';""" % quote_name(get_keyspace()))
""" % quote_name(get_keyspace()))
with testrun_cqlsh(tty=True, env=self.default_env) as c: with testrun_cqlsh(tty=True, env=self.default_env) as c:
for cmdword in ('describe table', 'desc columnfamily'): for cmdword in ('describe table', 'desc columnfamily'):
@ -730,13 +730,14 @@ class TestCqlshOutput(BaseTestCase):
\n \n
Cluster: [ ] (?P<clustername> .* ) \n Cluster: [ ] (?P<clustername> .* ) \n
Partitioner: [ ] (?P<partitionername> .* ) \n Partitioner: [ ] (?P<partitionername> .* ) \n
Snitch: [ ] (?P<snitchname> .* ) \n
\n \n
''' '''
ringinfo_re = r''' ringinfo_re = r'''
Range[ ]ownership: \n Range[ ]ownership: \n
( (
[ ] .*? [ ][ ] \[ ( \d+ \. ){3} \d+ \] \n [ ] .*? [ ][ ] \[ ( \d+ \. ){3} \d+ : \d+ \] \n
)+ )+
\n \n
''' '''

View File

@ -86,11 +86,14 @@ K_TRUNCATE: T R U N C A T E;
K_DELETE: D E L E T E; K_DELETE: D E L E T E;
K_IN: I N; K_IN: I N;
K_CREATE: C R E A T E; K_CREATE: C R E A T E;
K_SCHEMA: S C H E M A;
K_KEYSPACE: ( K E Y S P A C E K_KEYSPACE: ( K E Y S P A C E
| S C H E M A ); | K_SCHEMA );
K_KEYSPACES: K E Y S P A C E S; 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 K_COLUMNFAMILY:( C O L U M N F A M I L Y
| T A B L E ); | T A B L E );
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; K_MATERIALIZED:M A T E R I A L I Z E D;
K_VIEW: V I E W; K_VIEW: V I E W;
K_INDEX: I N D E X; K_INDEX: I N D E X;
@ -108,6 +111,7 @@ K_ALTER: A L T E R;
K_RENAME: R E N A M E; K_RENAME: R E N A M E;
K_ADD: A D D; K_ADD: A D D;
K_TYPE: T Y P E; K_TYPE: T Y P E;
K_TYPES: T Y P E S;
K_COMPACT: C O M P A C T; K_COMPACT: C O M P A C T;
K_STORAGE: S T O R A G E; K_STORAGE: S T O R A G E;
K_ORDER: O R D E R; K_ORDER: O R D E R;
@ -120,6 +124,9 @@ K_IF: I F;
K_IS: I S; K_IS: I S;
K_CONTAINS: C O N T A I N S; K_CONTAINS: C O N T A I N S;
K_GROUP: G R O U P; K_GROUP: G R O U P;
K_CLUSTER: C L U S T E R;
K_INTERNALS: I N T E R N A L S;
K_ONLY: O N L Y;
K_GRANT: G R A N T; K_GRANT: G R A N T;
K_ALL: A L L; K_ALL: A L L;
@ -191,6 +198,7 @@ K_FROZEN: F R O Z E N;
K_FUNCTION: F U N C T I O N; K_FUNCTION: F U N C T I O N;
K_FUNCTIONS: F U N C T I O N S; K_FUNCTIONS: F U N C T I O N S;
K_AGGREGATE: A G G R E G A T E; K_AGGREGATE: A G G R E G A T E;
K_AGGREGATES: A G G R E G A T E S;
K_SFUNC: S F U N C; K_SFUNC: S F U N C;
K_STYPE: S T Y P E; K_STYPE: S T Y P E;
K_FINALFUNC: F I N A L F U N C; K_FINALFUNC: F I N A L F U N C;

View File

@ -246,6 +246,7 @@ cqlStatement returns [CQLStatement.Raw stmt]
| st38=createMaterializedViewStatement { $stmt = st38; } | st38=createMaterializedViewStatement { $stmt = st38; }
| st39=dropMaterializedViewStatement { $stmt = st39; } | st39=dropMaterializedViewStatement { $stmt = st39; }
| st40=alterMaterializedViewStatement { $stmt = st40; } | st40=alterMaterializedViewStatement { $stmt = st40; }
| st41=describeStatement { $stmt = st41; }
; ;
/* /*
@ -1299,6 +1300,47 @@ userPassword[RoleOptions opts]
: K_PASSWORD v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); } : K_PASSWORD v=STRING_LITERAL { opts.setOption(IRoleManager.Option.PASSWORD, $v.text); }
; ;
/**
* DESCRIBE statement(s)
*
* Must be in sync with the javadoc for org.apache.cassandra.cql3.statements.DescribeStatement and the
* cqlsh syntax definition in for cqlsh_describe_cmd_syntax_rules pylib/cqlshlib/cqlshhandling.py.
*/
describeStatement returns [DescribeStatement stmt]
@init {
boolean fullSchema = false;
boolean pending = false;
boolean config = false;
boolean only = false;
QualifiedName gen = new QualifiedName();
}
: ( K_DESCRIBE | K_DESC )
( (K_CLUSTER)=> K_CLUSTER { $stmt = DescribeStatement.cluster(); }
| (K_FULL { fullSchema=true; })? K_SCHEMA { $stmt = DescribeStatement.schema(fullSchema); }
| (K_KEYSPACES)=> K_KEYSPACES { $stmt = DescribeStatement.keyspaces(); }
| (K_ONLY { only=true; })? K_KEYSPACE ( ks=keyspaceName )?
{ $stmt = DescribeStatement.keyspace(ks, only); }
| (K_TABLES) => K_TABLES { $stmt = DescribeStatement.tables(); }
| K_COLUMNFAMILY cf=columnFamilyName { $stmt = DescribeStatement.table(cf.getKeyspace(), cf.getName()); }
| K_INDEX idx=columnFamilyName { $stmt = DescribeStatement.index(idx.getKeyspace(), idx.getName()); }
| K_MATERIALIZED K_VIEW view=columnFamilyName { $stmt = DescribeStatement.view(view.getKeyspace(), view.getName()); }
| (K_TYPES) => K_TYPES { $stmt = DescribeStatement.types(); }
| K_TYPE tn=userTypeName { $stmt = DescribeStatement.type(tn.getKeyspace(), tn.getStringTypeName()); }
| (K_FUNCTIONS) => K_FUNCTIONS { $stmt = DescribeStatement.functions(); }
| K_FUNCTION fn=functionName { $stmt = DescribeStatement.function(fn.keyspace, fn.name); }
| (K_AGGREGATES) => K_AGGREGATES { $stmt = DescribeStatement.aggregates(); }
| K_AGGREGATE ag=functionName { $stmt = DescribeStatement.aggregate(ag.keyspace, ag.name); }
| ( ( ksT=IDENT { gen.setKeyspace($ksT.text, false);}
| ksT=QUOTED_NAME { gen.setKeyspace($ksT.text, true);}
| ksK=unreserved_keyword { gen.setKeyspace(ksK, false);} ) '.' )?
( tT=IDENT { gen.setName($tT.text, false);}
| tT=QUOTED_NAME { gen.setName($tT.text, true);}
| tK=unreserved_keyword { gen.setName(tK, false);} )
{ $stmt = DescribeStatement.generic(gen.getKeyspace(), gen.getName()); }
)
( K_WITH K_INTERNALS { $stmt.withInternalDetails(); } )?
;
/** DEFINITIONS **/ /** DEFINITIONS **/
// Column Identifiers. These need to be treated differently from other // Column Identifiers. These need to be treated differently from other
@ -1812,10 +1854,13 @@ unreserved_function_keyword returns [String str]
basic_unreserved_keyword returns [String str] basic_unreserved_keyword returns [String str]
: k=( K_KEYS : k=( K_KEYS
| K_AS | K_AS
| K_CLUSTER
| K_CLUSTERING | K_CLUSTERING
| K_COMPACT | K_COMPACT
| K_STORAGE | K_STORAGE
| K_TABLES
| K_TYPE | K_TYPE
| K_TYPES
| K_VALUES | K_VALUES
| K_MAP | K_MAP
| K_LIST | K_LIST
@ -1838,12 +1883,15 @@ basic_unreserved_keyword returns [String str]
| K_CUSTOM | K_CUSTOM
| K_TRIGGER | K_TRIGGER
| K_CONTAINS | K_CONTAINS
| K_INTERNALS
| K_ONLY
| K_STATIC | K_STATIC
| K_FROZEN | K_FROZEN
| K_TUPLE | K_TUPLE
| K_FUNCTION | K_FUNCTION
| K_FUNCTIONS | K_FUNCTIONS
| K_AGGREGATE | K_AGGREGATE
| K_AGGREGATES
| K_SFUNC | K_SFUNC
| K_STYPE | K_STYPE
| K_FINALFUNC | K_FINALFUNC

View File

@ -59,6 +59,7 @@ public enum AuditLogEntryType
ALTER_TYPE(AuditLogEntryCategory.DDL), ALTER_TYPE(AuditLogEntryCategory.DDL),
CREATE_ROLE(AuditLogEntryCategory.DCL), CREATE_ROLE(AuditLogEntryCategory.DCL),
USE_KEYSPACE(AuditLogEntryCategory.OTHER), USE_KEYSPACE(AuditLogEntryCategory.OTHER),
DESCRIBE(AuditLogEntryCategory.OTHER),
/* /*
* Common Audit Log Entry Types * Common Audit Log Entry Types

View File

@ -466,16 +466,26 @@ public interface CQL3Type
@Override @Override
public String toString() public String toString()
{
return toString(true);
}
public String toString(boolean withFrozen)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("frozen<tuple<"); if (withFrozen)
sb.append("frozen<");
sb.append("tuple<");
for (int i = 0; i < type.size(); i++) for (int i = 0; i < type.size(); i++)
{ {
if (i > 0) if (i > 0)
sb.append(", "); sb.append(", ");
sb.append(type.type(i).asCQL3Type()); sb.append(type.type(i).asCQL3Type());
} }
sb.append(">>"); sb.append('>');
if (withFrozen)
sb.append('>');
return sb.toString(); return sb.toString();
} }
} }

View File

@ -0,0 +1,226 @@
/*
* 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;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.db.marshal.AbstractType;
/**
* Utility class to facilitate the creation of the CQL representation of {@code SchemaElements}.
*/
public final class CqlBuilder
{
@FunctionalInterface
public static interface Appender<T>
{
public void appendTo(CqlBuilder builder, T obj);
}
/**
* The new line character
*/
private static final char NEW_LINE = '\n';
private static final String INDENTATION = " ";
private final StringBuilder builder;
private int indent;
private boolean isNewLine = false;
public CqlBuilder()
{
this(64);
}
public CqlBuilder(int capacity)
{
builder = new StringBuilder(capacity);
}
public CqlBuilder append(String str)
{
indentIfNeeded();
builder.append(str);
return this;
}
public CqlBuilder appendQuotingIfNeeded(String str)
{
return append(ColumnIdentifier.maybeQuote(str));
}
public CqlBuilder appendWithSingleQuotes(String str)
{
indentIfNeeded();
builder.append('\'')
.append(str.replaceAll("'", "''"))
.append('\'');
return this;
}
public CqlBuilder append(char c)
{
indentIfNeeded();
builder.append(c);
return this;
}
public CqlBuilder append(boolean b)
{
indentIfNeeded();
builder.append(b);
return this;
}
public CqlBuilder append(int i)
{
indentIfNeeded();
builder.append(i);
return this;
}
public CqlBuilder append(long l)
{
indentIfNeeded();
builder.append(l);
return this;
}
public CqlBuilder append(float f)
{
indentIfNeeded();
builder.append(f);
return this;
}
public CqlBuilder append(double d)
{
indentIfNeeded();
builder.append(d);
return this;
}
public CqlBuilder newLine()
{
builder.append(NEW_LINE);
isNewLine = true;
return this;
}
public CqlBuilder append(AbstractType<?> type)
{
return append(type.asCQL3Type().toString());
}
public CqlBuilder append(ColumnIdentifier column)
{
return append(column.toCQLString());
}
public CqlBuilder append(FunctionName name)
{
name.appendCqlTo(this);
return this;
}
public CqlBuilder append(Map<String, String> map)
{
return append(map, true);
}
public CqlBuilder append(Map<String, String> map, boolean quoteValue)
{
indentIfNeeded();
builder.append('{');
Iterator<Entry<String, String>> iter = new TreeMap<>(map).entrySet()
.iterator();
while(iter.hasNext())
{
Entry<String, String> e = iter.next();
appendWithSingleQuotes(e.getKey());
builder.append(": ");
if (quoteValue)
appendWithSingleQuotes(e.getValue());
else
builder.append(e.getValue());
if (iter.hasNext())
builder.append(", ");
}
builder.append('}');
return this;
}
public <T> CqlBuilder appendWithSeparators(Iterable<T> iterable, Appender<T> appender, String separator)
{
return appendWithSeparators(iterable.iterator(), appender, separator);
}
public <T> CqlBuilder appendWithSeparators(Iterator<T> iter, Appender<T> appender, String separator)
{
while (iter.hasNext())
{
appender.appendTo(this, iter.next());
if (iter.hasNext())
{
append(separator);
}
}
return this;
}
public CqlBuilder increaseIndent()
{
indent++;
return this;
}
public CqlBuilder decreaseIndent()
{
if (indent > 0)
indent--;
return this;
}
private void indentIfNeeded()
{
if (isNewLine)
{
for (int i = 0; i < indent; i++)
builder.append(INDENTATION);
isNewLine = false;
}
}
public String toString()
{
return builder.toString();
}
}

View File

@ -216,7 +216,14 @@ public class ResultSet
// when re-preparing we create the intermediate object // when re-preparing we create the intermediate object
public ResultMetadata(List<ColumnSpecification> names) public ResultMetadata(List<ColumnSpecification> names)
{ {
this(computeResultMetadataId(names), EnumSet.noneOf(Flag.class), names, names.size(), null); this(names, null);
}
// Problem is that we compute the metadata from the columns on creation;
// when re-preparing we create the intermediate object
public ResultMetadata(List<ColumnSpecification> names, PagingState pagingState)
{
this(computeResultMetadataId(names), EnumSet.noneOf(Flag.class), names, names.size(), pagingState);
if (!names.isEmpty() && ColumnSpecification.allInSameTable(names)) if (!names.isEmpty() && ColumnSpecification.allInSameTable(names))
flags.add(Flag.GLOBAL_TABLES_SPEC); flags.add(Flag.GLOBAL_TABLES_SPEC);
} }

View File

@ -0,0 +1,96 @@
/*
* 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;
import java.util.Comparator;
import java.util.Locale;
/**
* A schema element (keyspace, udt, udf, uda, table, index, view).
*/
public interface SchemaElement
{
/**
* Comparator used to sort {@code Describable} name.
*/
Comparator<SchemaElement> NAME_COMPARATOR = (o1, o2) -> o1.elementName().compareToIgnoreCase(o2.elementName());
enum SchemaElementType
{
KEYSPACE,
TYPE,
FUNCTION,
AGGREGATE,
TABLE,
INDEX,
MATERIALIZED_VIEW;
@Override
public String toString()
{
return super.toString().toLowerCase(Locale.US);
}
}
/**
* Return the schema element type
*
* @return the schema element type
*/
SchemaElementType elementType();
/**
* Returns the CQL name of the keyspace to which this schema element belong.
*
* @return the keyspace name.
*/
String elementKeyspace();
/**
* Returns the CQL name of this schema element.
*
* @return the name of this schema element.
*/
String elementName();
default String elementNameQuotedIfNeeded()
{
String name = elementName();
if (elementType() == SchemaElementType.FUNCTION
|| elementType() == SchemaElementType.AGGREGATE)
{
int index = name.indexOf('(');
return ColumnIdentifier.maybeQuote(name.substring(0, index)) + name.substring(index);
}
return ColumnIdentifier.maybeQuote(name);
}
default String elementKeyspaceQuotedIfNeeded()
{
return ColumnIdentifier.maybeQuote(elementKeyspace());
}
/**
* Returns a CQL representation of this element
*
* @param withInternals if the internals part of the CQL should be exposed.
* @return a CQL representation of this element
*/
String toCqlString(boolean withInternals);
}

View File

@ -24,7 +24,10 @@ import com.google.common.base.Objects;
import org.apache.cassandra.cql3.AssignmentTestable; import org.apache.cassandra.cql3.AssignmentTestable;
import org.apache.cassandra.cql3.CQL3Type; import org.apache.cassandra.cql3.CQL3Type;
import org.apache.cassandra.cql3.CQL3Type.Tuple;
import org.apache.cassandra.cql3.ColumnSpecification; import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.CqlBuilder.Appender;
import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.commons.lang3.text.StrBuilder; import org.apache.commons.lang3.text.StrBuilder;
@ -118,16 +121,41 @@ public abstract class AbstractFunction implements Function
@Override @Override
public String toString() public String toString()
{ {
StringBuilder sb = new StringBuilder(); return new CqlBuilder().append(name)
sb.append(name).append(" : ("); .append(" : (")
for (int i = 0; i < argTypes.size(); i++) .appendWithSeparators(argTypes, (b, t) -> b.append(toCqlString(t)), ", ")
{ .append(") -> ")
if (i > 0) .append(returnType)
sb.append(", "); .toString();
sb.append(argTypes.get(i).asCQL3Type()); }
}
sb.append(") -> ").append(returnType.asCQL3Type()); public String elementKeyspace()
return sb.toString(); {
return name.keyspace;
}
public String elementName()
{
return new CqlBuilder().append(name.name)
.append('(')
.appendWithSeparators(argTypes, (b, t) -> b.append(toCqlString(t)), ", ")
.append(')')
.toString();
}
/**
* Converts the specified type into its CQL representation.
*
* <p>For user function and aggregates tuples need to be handle in a special way as they are frozen by nature
* but the frozen keyword should not appear in their CQL definition.</p>
*
* @param type the type
* @return the CQL representation of the specified type
*/
protected String toCqlString(AbstractType<?> type)
{
return type.isTuple() ? ((Tuple) type.asCQL3Type()).toString(false)
: type.asCQL3Type().toString();
} }
@Override @Override

View File

@ -19,10 +19,14 @@ package org.apache.cassandra.cql3.functions;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
public final class FunctionName public final class FunctionName
{ {
// We special case the token function because that's the only function which name is a reserved keyword
private static final FunctionName TOKEN_FUNCTION_NAME = FunctionName.nativeFunction("token");
public final String keyspace; public final String keyspace;
public final String name; public final String name;
@ -79,4 +83,21 @@ public final class FunctionName
{ {
return keyspace == null ? name : keyspace + "." + name; return keyspace == null ? name : keyspace + "." + name;
} }
public void appendCqlTo(CqlBuilder builder)
{
if (equalsNativeFunction(TOKEN_FUNCTION_NAME))
{
builder.append(name);
}
else
{
if (keyspace != null)
{
builder.appendQuotingIfNeeded(keyspace)
.append('.');
}
builder.appendQuotingIfNeeded(name);
}
}
} }

View File

@ -25,6 +25,8 @@ import com.google.common.collect.Lists;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.SchemaElement;
import org.apache.cassandra.cql3.functions.types.TypeCodec; import org.apache.cassandra.cql3.functions.types.TypeCodec;
import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.marshal.UserType;
@ -41,7 +43,7 @@ import static com.google.common.collect.Iterables.transform;
/** /**
* Base class for user-defined-aggregates. * Base class for user-defined-aggregates.
*/ */
public class UDAggregate extends AbstractFunction implements AggregateFunction public class UDAggregate extends AbstractFunction implements AggregateFunction, SchemaElement
{ {
protected static final Logger logger = LoggerFactory.getLogger(UDAggregate.class); protected static final Logger logger = LoggerFactory.getLogger(UDAggregate.class);
@ -322,4 +324,41 @@ public class UDAggregate extends AbstractFunction implements AggregateFunction
{ {
return Objects.hashCode(name, Functions.typeHashCode(argTypes), Functions.typeHashCode(returnType), stateFunction, finalFunction, stateType, initcond); return Objects.hashCode(name, Functions.typeHashCode(argTypes), Functions.typeHashCode(returnType), stateFunction, finalFunction, stateType, initcond);
} }
@Override
public SchemaElementType elementType()
{
return SchemaElementType.AGGREGATE;
}
@Override
public String toCqlString(boolean withInternals)
{
CqlBuilder builder = new CqlBuilder();
builder.append("CREATE AGGREGATE ")
.append(name())
.append('(')
.appendWithSeparators(argTypes, (b, t) -> b.append(toCqlString(t)), ", ")
.append(')')
.newLine()
.increaseIndent()
.append("SFUNC ")
.append(stateFunction().name().name)
.newLine()
.append("STYPE ")
.append(toCqlString(stateType()));
if (finalFunction() != null)
builder.newLine()
.append("FINALFUNC ")
.append(finalFunction().name().name);
if (initialCondition() != null)
builder.newLine()
.append("INITCOND ")
.append(stateType().asCQL3Type().toCQLLiteral(initialCondition(), ProtocolVersion.CURRENT));
return builder.append(";")
.toString();
}
} }

View File

@ -45,16 +45,15 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.Config; import org.apache.cassandra.config.Config;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.SchemaElement;
import org.apache.cassandra.cql3.functions.types.DataType; import org.apache.cassandra.cql3.functions.types.DataType;
import org.apache.cassandra.cql3.functions.types.TypeCodec; import org.apache.cassandra.cql3.functions.types.TypeCodec;
import org.apache.cassandra.db.marshal.AbstractType; import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.exceptions.FunctionExecutionException; import org.apache.cassandra.exceptions.FunctionExecutionException;
import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.schema.Difference; import org.apache.cassandra.schema.*;
import org.apache.cassandra.schema.Functions;
import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.ClientWarn;
import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
@ -66,7 +65,7 @@ import static com.google.common.collect.Iterables.transform;
/** /**
* Base class for User Defined Functions. * Base class for User Defined Functions.
*/ */
public abstract class UDFunction extends AbstractFunction implements ScalarFunction public abstract class UDFunction extends AbstractFunction implements ScalarFunction, SchemaElement
{ {
protected static final Logger logger = LoggerFactory.getLogger(UDFunction.class); protected static final Logger logger = LoggerFactory.getLogger(UDFunction.class);
@ -303,6 +302,48 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct
}; };
} }
@Override
public SchemaElementType elementType()
{
return SchemaElementType.FUNCTION;
}
@Override
public String toCqlString(boolean withInternals)
{
CqlBuilder builder = new CqlBuilder();
builder.append("CREATE FUNCTION ")
.append(name())
.append("(");
for (int i = 0, m = argNames().size(); i < m; i++)
{
if (i > 0)
builder.append(", ");
builder.append(argNames().get(i))
.append(' ')
.append(toCqlString(argTypes().get(i)));
}
builder.append(')')
.newLine()
.increaseIndent()
.append(isCalledOnNullInput() ? "CALLED" : "RETURNS NULL")
.append(" ON NULL INPUT")
.newLine()
.append("RETURNS ")
.append(toCqlString(returnType()))
.newLine()
.append("LANGUAGE ")
.append(language())
.newLine()
.append("AS $$")
.append(body())
.append("$$;");
return builder.toString();
}
public final ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters) public final ByteBuffer execute(ProtocolVersion protocolVersion, List<ByteBuffer> parameters)
{ {
assertUdfsEnabled(language); assertUdfsEnabled(language);

View File

@ -0,0 +1,750 @@
/*
* 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 java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.collect.ImmutableList;
import org.apache.cassandra.audit.AuditLogContext;
import org.apache.cassandra.audit.AuditLogEntryType;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.FunctionName;
import org.apache.cassandra.db.KeyspaceNotDefinedException;
import org.apache.cassandra.db.marshal.ListType;
import org.apache.cassandra.db.marshal.MapType;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.exceptions.RequestValidationException;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.pager.PagingState;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.UUIDGen;
import static java.lang.String.format;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotEmpty;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
import static org.apache.cassandra.utils.ByteBufferUtil.bytes;
/**
* The differents <code>DESCRIBE</code> statements parsed from a CQL statement.
*/
public abstract class DescribeStatement<T> extends CQLStatement.Raw implements CQLStatement
{
private static final String KS = "system";
private static final String CF = "describe";
/**
* The columns returned by the describe queries that only list elements names (e.g. DESCRIBE KEYSPACES, DESCRIBE TABLES...)
*/
private static final List<ColumnSpecification> LIST_METADATA =
ImmutableList.of(new ColumnSpecification(KS, CF, new ColumnIdentifier("keyspace_name", true), UTF8Type.instance),
new ColumnSpecification(KS, CF, new ColumnIdentifier("type", true), UTF8Type.instance),
new ColumnSpecification(KS, CF, new ColumnIdentifier("name", true), UTF8Type.instance));
/**
* The columns returned by the describe queries that returns the CREATE STATEMENT for the different elements (e.g. DESCRIBE KEYSPACE, DESCRIBE TABLE ...)
*/
private static final List<ColumnSpecification> ELEMENT_METADATA =
ImmutableList.<ColumnSpecification>builder().addAll(LIST_METADATA)
.add(new ColumnSpecification(KS, CF, new ColumnIdentifier("create_statement", true), UTF8Type.instance))
.build();
/**
* "Magic version" for the paging state.
*/
private static final int PAGING_STATE_VERSION = 0x0001;
static final String SCHEMA_CHANGED_WHILE_PAGING_MESSAGE = "The schema has changed since the previous page of the DESCRIBE statement result. " +
"Please retry the DESCRIBE statement.";
private boolean includeInternalDetails;
public final void withInternalDetails()
{
this.includeInternalDetails = true;
}
@Override
public final CQLStatement prepare(ClientState clientState) throws RequestValidationException
{
return this;
}
public final List<ColumnSpecification> getBindVariables()
{
return Collections.emptyList();
}
@Override
public final void authorize(ClientState state)
{
}
@Override
public final void validate(ClientState state)
{
}
public final AuditLogContext getAuditLogContext()
{
return new AuditLogContext(AuditLogEntryType.DESCRIBE);
}
@Override
public final ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) throws RequestValidationException, RequestExecutionException
{
return executeLocally(state, options);
}
@Override
public ResultMessage executeLocally(QueryState state, QueryOptions options)
{
Keyspaces keyspaces = Schema.instance.snapshot();
UUID schemaVersion = Schema.instance.getVersion();
keyspaces = Keyspaces.builder()
.add(keyspaces)
.add(VirtualKeyspaceRegistry.instance.virtualKeyspacesMetadata())
.build();
PagingState pagingState = options.getPagingState();
// The paging implemented here uses some arbitray row number as the partition-key for paging,
// which is used to skip/limit the result from the Java Stream. This works good enough for
// reasonably sized schemas. Even a 'DESCRIBE SCHEMA' for an abnormally schema with 10000 tables
// completes within a few seconds. This seems good enough for now. Once Cassandra actually supports
// more than a few hundred tables, the implementation here should be reconsidered.
//
// Paging is only supported on row-level.
//
// The "partition key" in the paging-state contains a serialized object:
// (short) version, currently 0x0001
// (long) row offset
// (vint bytes) serialized schema hash (currently the result of Keyspaces.hashCode())
//
long offset = getOffset(pagingState, schemaVersion);
int pageSize = options.getPageSize();
Stream<? extends T> stream = describe(state.getClientState(), keyspaces);
if (offset > 0L)
stream = stream.skip(offset);
if (pageSize > 0)
stream = stream.limit(pageSize);
List<List<ByteBuffer>> rows = stream.map(e -> toRow(e, includeInternalDetails))
.collect(Collectors.toList());
ResultSet.ResultMetadata resultMetadata = new ResultSet.ResultMetadata(metadata(state.getClientState()));
ResultSet result = new ResultSet(resultMetadata, rows);
if (pageSize > 0 && rows.size() == pageSize)
{
result.metadata.setHasMorePages(getPagingState(offset + pageSize, schemaVersion));
}
return new ResultMessage.Rows(result);
}
/**
* Returns the columns of the {@code ResultMetadata}
*/
protected abstract List<ColumnSpecification> metadata(ClientState state);
private PagingState getPagingState(long nextPageOffset, UUID schemaVersion)
{
try (DataOutputBuffer out = new DataOutputBuffer())
{
out.writeShort(PAGING_STATE_VERSION);
out.writeUTF(FBUtilities.getReleaseVersionString());
out.write(UUIDGen.decompose(schemaVersion));
out.writeLong(nextPageOffset);
return new PagingState(out.asNewBuffer(),
null,
Integer.MAX_VALUE,
Integer.MAX_VALUE);
}
catch (IOException e)
{
throw new InvalidRequestException("Invalid paging state.", e);
}
}
private long getOffset(PagingState pagingState, UUID schemaVersion)
{
if (pagingState == null)
return 0L;
try (DataInputBuffer in = new DataInputBuffer(pagingState.partitionKey, false))
{
checkTrue(in.readShort() == PAGING_STATE_VERSION, "Incompatible paging state");
final String pagingStateServerVersion = in.readUTF();
final String releaseVersion = FBUtilities.getReleaseVersionString();
checkTrue(pagingStateServerVersion.equals(releaseVersion),
"The server version of the paging state %s is different from the one of the server %s",
pagingStateServerVersion,
releaseVersion);
byte[] bytes = new byte[UUIDGen.UUID_LEN];
in.read(bytes);
UUID version = UUIDGen.getUUID(ByteBuffer.wrap(bytes));
checkTrue(schemaVersion.equals(version), SCHEMA_CHANGED_WHILE_PAGING_MESSAGE);
return in.readLong();
}
catch (IOException e)
{
throw new InvalidRequestException("Invalid paging state.", e);
}
}
protected abstract List<ByteBuffer> toRow(T element, boolean withInternals);
/**
* Returns the schema elements that must be part of the output.
*/
protected abstract Stream<? extends T> describe(ClientState state, Keyspaces keyspaces);
/**
* Returns the metadata for the given keyspace or throws a {@link KeyspaceNotDefinedException} exception.
*/
private static KeyspaceMetadata validateKeyspace(String ks, Keyspaces keyspaces)
{
return keyspaces.get(ks)
.orElseThrow(() -> new KeyspaceNotDefinedException(format("'%s' not found in keyspaces", ks)));
}
/**
* {@code DescribeStatement} implementation used for describe queries that only list elements names.
*/
public static final class Listing extends DescribeStatement<SchemaElement>
{
private final java.util.function.Function<KeyspaceMetadata, Stream<? extends SchemaElement>> elementsProvider;
public Listing(java.util.function.Function<KeyspaceMetadata, Stream<? extends SchemaElement>> elementsProvider)
{
this.elementsProvider = elementsProvider;
}
@Override
protected Stream<? extends SchemaElement> describe(ClientState state, Keyspaces keyspaces)
{
String keyspace = state.getRawKeyspace();
Stream<KeyspaceMetadata> stream = keyspace == null ? keyspaces.stream().sorted(SchemaElement.NAME_COMPARATOR)
: Stream.of(validateKeyspace(keyspace, keyspaces));
return stream.flatMap(k -> elementsProvider.apply(k).sorted(SchemaElement.NAME_COMPARATOR));
}
@Override
protected List<ColumnSpecification> metadata(ClientState state)
{
return LIST_METADATA;
}
@Override
protected List<ByteBuffer> toRow(SchemaElement element, boolean withInternals)
{
return ImmutableList.of(bytes(element.elementKeyspaceQuotedIfNeeded()),
bytes(element.elementType().toString()),
bytes(element.elementNameQuotedIfNeeded()));
}
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE TABLES}.
*/
public static DescribeStatement<SchemaElement> tables()
{
return new Listing(ks -> ks.tables.stream());
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE TYPES}.
*/
public static DescribeStatement<SchemaElement> types()
{
return new Listing(ks -> ks.types.stream());
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE FUNCTIONS}.
*/
public static DescribeStatement<SchemaElement> functions()
{
return new Listing(ks -> ks.functions.udfs());
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE AGGREGATES}.
*/
public static DescribeStatement<SchemaElement> aggregates()
{
return new Listing(ks -> ks.functions.udas());
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE KEYSPACES}.
*/
public static DescribeStatement<SchemaElement> keyspaces()
{
return new DescribeStatement<SchemaElement>()
{
@Override
protected Stream<? extends SchemaElement> describe(ClientState state, Keyspaces keyspaces)
{
return keyspaces.stream().sorted(SchemaElement.NAME_COMPARATOR);
}
@Override
protected List<ColumnSpecification> metadata(ClientState state)
{
return LIST_METADATA;
}
@Override
protected List<ByteBuffer> toRow(SchemaElement element, boolean withInternals)
{
return ImmutableList.of(bytes(element.elementKeyspaceQuotedIfNeeded()),
bytes(element.elementType().toString()),
bytes(element.elementNameQuotedIfNeeded()));
}
};
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE [FULL] SCHEMA}.
*/
public static DescribeStatement<SchemaElement> schema(boolean includeSystemKeyspaces)
{
return new DescribeStatement<SchemaElement>()
{
@Override
protected Stream<? extends SchemaElement> describe(ClientState state, Keyspaces keyspaces)
{
return keyspaces.stream()
.filter(ks -> includeSystemKeyspaces || !SchemaConstants.isSystemKeyspace(ks.name))
.sorted(SchemaElement.NAME_COMPARATOR)
.flatMap(ks -> getKeyspaceElements(ks, false));
}
@Override
protected List<ColumnSpecification> metadata(ClientState state)
{
return ELEMENT_METADATA;
}
@Override
protected List<ByteBuffer> toRow(SchemaElement element, boolean withInternals)
{
return ImmutableList.of(bytes(element.elementKeyspaceQuotedIfNeeded()),
bytes(element.elementType().toString()),
bytes(element.elementNameQuotedIfNeeded()),
bytes(element.toCqlString(withInternals)));
}
};
}
/**
* {@code DescribeStatement} implementation used for describe queries for a single schema element.
*/
public static class Element extends DescribeStatement<SchemaElement>
{
/**
* The keyspace name
*/
private final String keyspace;
/**
* The element name
*/
private final String name;
private final BiFunction<KeyspaceMetadata, String, Stream<? extends SchemaElement>> elementsProvider;
public Element(String keyspace, String name, BiFunction<KeyspaceMetadata, String, Stream<? extends SchemaElement>> elementsProvider)
{
this.keyspace = keyspace;
this.name = name;
this.elementsProvider = elementsProvider;
}
@Override
protected Stream<? extends SchemaElement> describe(ClientState state, Keyspaces keyspaces)
{
String ks = keyspace == null ? checkNotNull(state.getRawKeyspace(), "No keyspace specified and no current keyspace")
: keyspace;
return elementsProvider.apply(validateKeyspace(ks, keyspaces), name);
}
@Override
protected List<ColumnSpecification> metadata(ClientState state)
{
return ELEMENT_METADATA;
}
@Override
protected List<ByteBuffer> toRow(SchemaElement element, boolean withInternals)
{
return ImmutableList.of(bytes(element.elementKeyspaceQuotedIfNeeded()),
bytes(element.elementType().toString()),
bytes(element.elementNameQuotedIfNeeded()),
bytes(element.toCqlString(withInternals)));
}
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE KEYSPACE}.
*/
public static DescribeStatement<SchemaElement> keyspace(String keyspace, boolean onlyKeyspaceDefinition)
{
return new Element(keyspace, null, (ks, t) -> getKeyspaceElements(ks, onlyKeyspaceDefinition));
}
private static Stream<? extends SchemaElement> getKeyspaceElements(KeyspaceMetadata ks, boolean onlyKeyspace)
{
Stream<? extends SchemaElement> s = Stream.of(ks);
if (!onlyKeyspace)
{
s = Stream.concat(s, ks.types.sortedStream());
s = Stream.concat(s, ks.functions.udfs().sorted(SchemaElement.NAME_COMPARATOR));
s = Stream.concat(s, ks.functions.udas().sorted(SchemaElement.NAME_COMPARATOR));
s = Stream.concat(s, ks.tables.stream().sorted(SchemaElement.NAME_COMPARATOR)
.flatMap(tm -> getTableElements(ks, tm)));
}
return s;
}
private static Stream<? extends SchemaElement> getTableElements(KeyspaceMetadata ks, TableMetadata table)
{
Stream<? extends SchemaElement> s = Stream.of(table);
s = Stream.concat(s, table.indexes.stream()
.map(i -> toDescribable(table, i))
.sorted(SchemaElement.NAME_COMPARATOR));
s = Stream.concat(s, ks.views.stream(table.id)
.sorted(SchemaElement.NAME_COMPARATOR));
return s;
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE TABLE}.
*/
public static DescribeStatement<SchemaElement> table(String keyspace, String name)
{
return new Element(keyspace, name, (ks, t) -> {
TableMetadata table = checkNotNull(ks.getTableOrViewNullable(t),
"Table '%s' not found in keyspace '%s'", t, ks.name);
return Stream.concat(Stream.of(table), table.indexes.stream()
.map(index -> toDescribable(table, index))
.sorted(SchemaElement.NAME_COMPARATOR));
});
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE INDEX}.
*/
public static DescribeStatement<SchemaElement> index(String keyspace, String name)
{
return new Element(keyspace, name, (ks, index) -> {
TableMetadata tm = ks.findIndexedTable(index)
.orElseThrow(() -> invalidRequest("Table for existing index '%s' not found in '%s'",
index,
ks.name));
return tm.indexes.get(index)
.map(i -> toDescribable(tm, i))
.map(Stream::of)
.orElseThrow(() -> invalidRequest("Index '%s' not found in '%s'", index, ks.name));
});
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE MATERIALIZED VIEW}.
*/
public static DescribeStatement<SchemaElement> view(String keyspace, String name)
{
return new Element(keyspace, name, (ks, view) -> {
return ks.views.get(view)
.map(Stream::of)
.orElseThrow(() -> invalidRequest("Materialized view '%s' not found in '%s'", view, ks.name));
});
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE TYPE}.
*/
public static DescribeStatement<SchemaElement> type(String keyspace, String name)
{
return new Element(keyspace, name, (ks, type) -> {
return ks.types.get(ByteBufferUtil.bytes(type))
.map(Stream::of)
.orElseThrow(() -> invalidRequest("User defined type '%s' not found in '%s'",
type,
ks.name));
});
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE FUNCTION}.
*/
public static DescribeStatement<SchemaElement> function(String keyspace, String name)
{
return new Element(keyspace, name, (ks, n) -> {
return checkNotEmpty(ks.functions.getUdfs(new FunctionName(ks.name, n)),
"User defined function '%s' not found in '%s'", n, ks.name).stream()
.sorted(SchemaElement.NAME_COMPARATOR);
});
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE FUNCTION}.
*/
public static DescribeStatement<SchemaElement> aggregate(String keyspace, String name)
{
return new Element(keyspace, name, (ks, n) -> {
return checkNotEmpty(ks.functions.getUdas(new FunctionName(ks.name, n)),
"User defined aggregate '%s' not found in '%s'", n, ks.name).stream()
.sorted(SchemaElement.NAME_COMPARATOR);
});
}
private static SchemaElement toDescribable(TableMetadata table, IndexMetadata index)
{
return new SchemaElement()
{
@Override
public SchemaElementType elementType()
{
return SchemaElementType.INDEX;
}
@Override
public String elementKeyspace()
{
return table.keyspace;
}
@Override
public String elementName()
{
return index.name;
}
@Override
public String toCqlString(boolean withInternals)
{
return index.toCqlString(table);
}
};
}
/**
* Creates a {@link DescribeStatement} for the generic {@code DESCRIBE ...}.
*/
public static DescribeStatement<SchemaElement> generic(String keyspace, String name)
{
return new DescribeStatement<SchemaElement>()
{
private DescribeStatement<SchemaElement> delegate;
private DescribeStatement<SchemaElement> resolve(ClientState state, Keyspaces keyspaces)
{
String ks = keyspace;
// from cqlsh help: "keyspace or a table or an index or a materialized view (in this order)."
if (keyspace == null)
{
if (keyspaces.containsKeyspace(name))
return keyspace(name, false);
String rawKeyspace = state.getRawKeyspace();
ks = rawKeyspace == null ? name : rawKeyspace;
}
KeyspaceMetadata keyspaceMetadata = validateKeyspace(ks, keyspaces);
if (keyspaceMetadata.tables.getNullable(name) != null)
return table(ks, name);
Optional<TableMetadata> indexed = keyspaceMetadata.findIndexedTable(name);
if (indexed.isPresent())
{
Optional<IndexMetadata> index = indexed.get().indexes.get(name);
if (index.isPresent())
return index(ks, name);
}
if (keyspaceMetadata.views.getNullable(name) != null)
return view(ks, name);
throw invalidRequest("'%s' not found in keyspace '%s'", name, ks);
}
@Override
protected Stream<? extends SchemaElement> describe(ClientState state, Keyspaces keyspaces)
{
delegate = resolve(state, keyspaces);
return delegate.describe(state, keyspaces);
}
@Override
protected List<ColumnSpecification> metadata(ClientState state)
{
return delegate.metadata(state);
}
@Override
protected List<ByteBuffer> toRow(SchemaElement element, boolean withInternals)
{
return delegate.toRow(element, withInternals);
}
};
}
/**
* Creates a {@link DescribeStatement} for {@code DESCRIBE CLUSTER}.
*/
public static DescribeStatement<List<Object>> cluster()
{
return new DescribeStatement<List<Object>>()
{
/**
* The column index of the cluster name
*/
private static final int CLUSTER_NAME_INDEX = 0;
/**
* The column index of the partitioner name
*/
private static final int PARTITIONER_NAME_INDEX = 1;
/**
* The column index of the snitch class
*/
private static final int SNITCH_CLASS_INDEX = 2;
/**
* The range ownerships index
*/
private static final int RANGE_OWNERSHIPS_INDEX = 3;
@Override
protected Stream<List<Object>> describe(ClientState state, Keyspaces keyspaces)
{
List<Object> list = new ArrayList<Object>();
list.add(DatabaseDescriptor.getClusterName());
list.add(trimIfPresent(DatabaseDescriptor.getPartitionerName(), "org.apache.cassandra.dht."));
list.add(trimIfPresent(DatabaseDescriptor.getEndpointSnitch().getClass().getName(),
"org.apache.cassandra.locator."));
String useKs = state.getRawKeyspace();
if (mustReturnsRangeOwnerships(useKs))
{
list.add(StorageService.instance.getRangeToAddressMap(useKs)
.entrySet()
.stream()
.sorted(Comparator.comparing(Map.Entry::getKey))
.collect(Collectors.toMap(e -> e.getKey().right.toString(),
e -> e.getValue()
.stream()
.map(r -> r.endpoint().toString())
.collect(Collectors.toList()))));
}
return Stream.of(list);
}
private boolean mustReturnsRangeOwnerships(String useKs)
{
return useKs != null && !SchemaConstants.isLocalSystemKeyspace(useKs) && !SchemaConstants.isSystemKeyspace(useKs);
}
@Override
protected List<ColumnSpecification> metadata(ClientState state)
{
ImmutableList.Builder<ColumnSpecification> builder = ImmutableList.builder();
builder.add(new ColumnSpecification(KS, CF, new ColumnIdentifier("cluster", true), UTF8Type.instance),
new ColumnSpecification(KS, CF, new ColumnIdentifier("partitioner", true), UTF8Type.instance),
new ColumnSpecification(KS, CF, new ColumnIdentifier("snitch", true), UTF8Type.instance));
if (mustReturnsRangeOwnerships(state.getRawKeyspace()))
builder.add(new ColumnSpecification(KS, CF, new ColumnIdentifier("range_ownership", true), MapType.getInstance(UTF8Type.instance,
ListType.getInstance(UTF8Type.instance, false), false)));
return builder.build();
}
@Override
protected List<ByteBuffer> toRow(List<Object> elements, boolean withInternals)
{
ImmutableList.Builder<ByteBuffer> builder = ImmutableList.builder();
builder.add(UTF8Type.instance.decompose((String) elements.get(CLUSTER_NAME_INDEX)),
UTF8Type.instance.decompose((String) elements.get(PARTITIONER_NAME_INDEX)),
UTF8Type.instance.decompose((String) elements.get(SNITCH_CLASS_INDEX)));
if (elements.size() > 3)
{
MapType<String, List<String>> rangeOwnershipType = MapType.getInstance(UTF8Type.instance,
ListType.getInstance(UTF8Type.instance, false),
false);
builder.add(rangeOwnershipType.decompose((Map<String, List<String>>) elements.get(RANGE_OWNERSHIPS_INDEX)));
}
return builder.build();
}
private String trimIfPresent(String src, String begin)
{
if (src.startsWith(begin))
return src.substring(begin.length());
return src;
}
};
}
}

View File

@ -19,6 +19,7 @@ package org.apache.cassandra.cql3.statements;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -141,6 +142,23 @@ public final class RequestValidations
return object; return object;
} }
/**
* Checks that the specified collections is NOT <code>empty</code>.
* If it is an <code>InvalidRequestException</code> will be throws.
*
* @param collection the collection to test
* @param messageTemplate the template used to build the error message
* @param messageArgs the message arguments
* @return the collection
* @throws InvalidRequestException if the specified collection is <code>empty</code>.
*/
public static <T extends Collection<E>, E> T checkNotEmpty(T collection, String messageTemplate, Object... messageArgs)
throws InvalidRequestException
{
checkTrue(!collection.isEmpty(), messageTemplate, messageArgs);
return collection;
}
/** /**
* Checks that the specified bind marker value is set to a meaningful value. * Checks that the specified bind marker value is set to a meaningful value.
* If it is not a <code>InvalidRequestException</code> will be thrown. * If it is not a <code>InvalidRequestException</code> will be thrown.

View File

@ -103,11 +103,11 @@ public abstract class AlterTypeStatement extends AlterSchemaStatement
UserType apply(KeyspaceMetadata keyspace, UserType userType) UserType apply(KeyspaceMetadata keyspace, UserType userType)
{ {
if (userType.fieldPosition(fieldName) >= 0) if (userType.fieldPosition(fieldName) >= 0)
throw ire("Cannot add field %s to type %s: a field with name %s already exists", fieldName, userType.toCQLString(), fieldName); throw ire("Cannot add field %s to type %s: a field with name %s already exists", fieldName, userType.getCqlTypeName(), fieldName);
AbstractType<?> fieldType = type.prepare(keyspaceName, keyspace.types).getType(); AbstractType<?> fieldType = type.prepare(keyspaceName, keyspace.types).getType();
if (fieldType.referencesUserType(userType.name)) if (fieldType.referencesUserType(userType.name))
throw ire("Cannot add new field %s of type %s to user type %s as it would create a circular reference", fieldName, type, userType.toCQLString()); throw ire("Cannot add new field %s of type %s to user type %s as it would create a circular reference", fieldName, type, userType.getCqlTypeName());
List<FieldIdentifier> fieldNames = new ArrayList<>(userType.fieldNames()); fieldNames.add(fieldName); List<FieldIdentifier> fieldNames = new ArrayList<>(userType.fieldNames()); fieldNames.add(fieldName);
List<AbstractType<?>> fieldTypes = new ArrayList<>(userType.fieldTypes()); fieldTypes.add(fieldType); List<AbstractType<?>> fieldTypes = new ArrayList<>(userType.fieldTypes()); fieldTypes.add(fieldType);
@ -138,7 +138,7 @@ public abstract class AlterTypeStatement extends AlterSchemaStatement
if (!dependentAggregates.isEmpty()) if (!dependentAggregates.isEmpty())
{ {
throw ire("Cannot alter user type %s as it is still used in INITCOND by aggregates %s", throw ire("Cannot alter user type %s as it is still used in INITCOND by aggregates %s",
userType.toCQLString(), userType.getCqlTypeName(),
join(", ", dependentAggregates)); join(", ", dependentAggregates));
} }
@ -148,14 +148,14 @@ public abstract class AlterTypeStatement extends AlterSchemaStatement
{ {
int idx = userType.fieldPosition(oldName); int idx = userType.fieldPosition(oldName);
if (idx < 0) if (idx < 0)
throw ire("Unkown field %s in user type %s", oldName, keyspaceName, userType.toCQLString()); throw ire("Unkown field %s in user type %s", oldName, keyspaceName, userType.getCqlTypeName());
fieldNames.set(idx, newName); fieldNames.set(idx, newName);
}); });
fieldNames.forEach(name -> fieldNames.forEach(name ->
{ {
if (fieldNames.stream().filter(isEqual(name)).count() > 1) if (fieldNames.stream().filter(isEqual(name)).count() > 1)
throw ire("Duplicate field name %s in type %s", name, keyspaceName, userType.toCQLString()); throw ire("Duplicate field name %s in type %s", name, keyspaceName, userType.getCqlTypeName());
}); });
return new UserType(keyspaceName, userType.name, fieldNames, userType.fieldTypes(), true); return new UserType(keyspaceName, userType.name, fieldNames, userType.fieldTypes(), true);

View File

@ -1779,8 +1779,9 @@ public class ColumnFamilyStore implements ColumnFamilyStoreMBean
try (PrintStream out = new PrintStream(schemaFile)) try (PrintStream out = new PrintStream(schemaFile))
{ {
for (String s: TableCQLHelper.dumpReCreateStatements(metadata())) SchemaCQLHelper.reCreateStatementsForSchemaCql(metadata(),
out.println(s); keyspace.getMetadata().types)
.forEach(out::println);
} }
} }
catch (IOException e) catch (IOException e)

View File

@ -0,0 +1,158 @@
/*
* 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;
import java.nio.ByteBuffer;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.marshal.UserType;
import org.apache.cassandra.schema.*;
/**
* Helper methods to represent TableMetadata and related objects in CQL format
*/
public class SchemaCQLHelper
{
/**
* Generates the DDL statement for a {@code schema.cql} snapshot file.
*/
public static Stream<String> reCreateStatementsForSchemaCql(TableMetadata metadata, Types types)
{
// Types come first, as table can't be created without them
Stream<String> udts = SchemaCQLHelper.getUserTypesAsCQL(metadata, types);
return Stream.concat(udts,
reCreateStatements(metadata,
true,
true,
true,
true));
}
public static Stream<String> reCreateStatements(TableMetadata metadata,
boolean includeDroppedColumns,
boolean internals,
boolean ifNotExists,
boolean includeIndexes)
{
// Record re-create schema statements
Stream<String> r = Stream.of(metadata)
.map((tm) -> SchemaCQLHelper.getTableMetadataAsCQL(tm,
includeDroppedColumns,
internals,
ifNotExists));
if (includeIndexes)
{
// Indexes applied as last, since otherwise they may interfere with column drops / re-additions
r = Stream.concat(r, SchemaCQLHelper.getIndexesAsCQL(metadata));
}
return r;
}
/**
* Build a CQL String representation of Column Family Metadata.
*
* *Note*: this is _only_ visible for testing; you generally shouldn't re-create a single table in isolation as
* that will not contain everything needed for user types.
*/
@VisibleForTesting
public static String getTableMetadataAsCQL(TableMetadata metadata,
boolean includeDroppedColumns,
boolean internals,
boolean ifNotExists)
{
if (metadata.isView())
{
KeyspaceMetadata keyspaceMetadata = Schema.instance.getKeyspaceMetadata(metadata.keyspace);
ViewMetadata viewMetadata = keyspaceMetadata.views.get(metadata.name).orElse(null);
assert viewMetadata != null;
return viewMetadata.toCqlString(internals, ifNotExists);
}
return metadata.toCqlString(includeDroppedColumns, internals, ifNotExists);
}
/**
* Build a CQL String representation of User Types used in the given table.
*
* Type order is ensured as types are built incrementally: from the innermost (most nested)
* to the outermost.
*
* @param metadata the table for which to extract the user types CQL statements.
* @param types the user types defined in the keyspace of the dumped table (which will thus contain any user type
* used by {@code metadata}).
* @return a list of {@code CREATE TYPE} statements corresponding to all the types used in {@code metadata}.
*/
@VisibleForTesting
public static Stream<String> getUserTypesAsCQL(TableMetadata metadata, Types types)
{
/*
* Implementation note: at first approximation, it may seem like we don't need the Types argument and instead
* directly extract the user types from the provided TableMetadata. Indeed, full user types definitions are
* contained in UserType instances.
*
* However, the UserType instance found within the TableMetadata may have been frozen in such a way that makes
* it challenging.
*
* Consider the user has created:
* CREATE TYPE inner (a set<int>);
* CREATE TYPE outer (b inner);
* CREATE TABLE t (k int PRIMARY KEY, c1 frozen<outer>, c2 set<frozen<inner>>)
* The corresponding TableMetadata would have, as types (where 'mc=true' means that the type has his isMultiCell
* set to true):
* c1: UserType(mc=false, "outer", b->UserType(mc=false, "inner", a->SetType(mc=fase, Int32Type)))
* c2: SetType(mc=true, UserType(mc=false, "inner", a->SetType(mc=fase, Int32Type)))
* From which, it's impossible to decide if we should dump the types above, or instead:
* CREATE TYPE inner (a frozen<set<int>>);
* CREATE TYPE outer (b frozen<inner>);
* or anything in-between.
*
* And while, as of the current limitation around multi-cell types (that are only support non-frozen at
* top-level), any of the generated definition would kind of "work", 1) this could confuse users and 2) this
* would break if we do lift the limitation, which wouldn't be future proof.
*/
return metadata.getReferencedUserTypes()
.stream()
.map(name -> getType(metadata, types, name).toCqlString(false));
}
/**
* Build a CQL String representation of Indexes on columns in the given Column Family
*/
@VisibleForTesting
public static Stream<String> getIndexesAsCQL(TableMetadata metadata)
{
return metadata.indexes
.stream()
.map(indexMetadata -> indexMetadata.toCqlString(metadata));
}
private static UserType getType(TableMetadata metadata, Types types, ByteBuffer name)
{
return types.get(name)
.orElseThrow(() -> new IllegalStateException(String.format("user type %s is part of table %s definition but its definition was missing",
UTF8Type.instance.getString(name),
metadata)));
}
}

View File

@ -1,430 +0,0 @@
/*
* 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;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Iterables;
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.utils.*;
/**
* Helper methods to represent TableMetadata and related objects in CQL format
*/
public class TableCQLHelper
{
private static final Pattern singleQuotePattern = Pattern.compile("'");
public static List<String> dumpReCreateStatements(TableMetadata metadata)
{
List<String> l = new ArrayList<>();
// Types come first, as table can't be created without them
l.addAll(TableCQLHelper.getUserTypesAsCQL(metadata));
// Record re-create schema statements
l.add(TableCQLHelper.getTableMetadataAsCQL(metadata, true));
// Dropped columns (and re-additions)
l.addAll(TableCQLHelper.getDroppedColumnsAsCQL(metadata));
// Indexes applied as last, since otherwise they may interfere with column drops / re-additions
l.addAll(TableCQLHelper.getIndexesAsCQL(metadata));
return l;
}
private static List<ColumnMetadata> getClusteringColumns(TableMetadata metadata)
{
List<ColumnMetadata> cds = new ArrayList<>(metadata.clusteringColumns().size());
if (!metadata.isStaticCompactTable())
for (ColumnMetadata cd : metadata.clusteringColumns())
cds.add(cd);
return cds;
}
private static List<ColumnMetadata> getPartitionColumns(TableMetadata metadata)
{
List<ColumnMetadata> cds = new ArrayList<>(metadata.regularAndStaticColumns().size());
for (ColumnMetadata cd : metadata.staticColumns())
cds.add(cd);
if (metadata.isDense())
{
// remove an empty type
for (ColumnMetadata cd : metadata.regularColumns())
if (!cd.type.equals(EmptyType.instance))
cds.add(cd);
}
// "regular" columns are not exposed for static compact tables
else if (!metadata.isStaticCompactTable())
{
for (ColumnMetadata cd : metadata.regularColumns())
cds.add(cd);
}
return cds;
}
/**
* Build a CQL String representation of Table Metadata
*/
@VisibleForTesting
public static String getTableMetadataAsCQL(TableMetadata metadata, boolean includeDroppedColumns)
{
StringBuilder sb = new StringBuilder();
if (!isCqlCompatible(metadata))
{
sb.append(String.format("/*\nWarning: Table %s omitted because it has constructs not compatible with CQL (was created via legacy API).\n",
metadata.toString()));
sb.append("\nApproximate structure, for reference:");
sb.append("\n(this should not be used to reproduce this schema)\n\n");
}
sb.append("CREATE TABLE IF NOT EXISTS ");
sb.append(metadata.toString()).append(" (");
List<ColumnMetadata> partitionKeyColumns = metadata.partitionKeyColumns();
List<ColumnMetadata> clusteringColumns = getClusteringColumns(metadata);
List<ColumnMetadata> partitionColumns = getPartitionColumns(metadata);
Consumer<StringBuilder> cdCommaAppender = commaAppender("\n\t");
sb.append("\n\t");
for (ColumnMetadata cfd: partitionKeyColumns)
{
cdCommaAppender.accept(sb);
sb.append(toCQL(cfd));
if (partitionKeyColumns.size() == 1 && clusteringColumns.size() == 0)
sb.append(" PRIMARY KEY");
}
for (ColumnMetadata cfd: clusteringColumns)
{
cdCommaAppender.accept(sb);
sb.append(toCQL(cfd));
}
for (ColumnMetadata cfd: partitionColumns)
{
cdCommaAppender.accept(sb);
sb.append(toCQL(cfd, metadata.isStaticCompactTable()));
}
if (includeDroppedColumns)
{
for (Map.Entry<ByteBuffer, DroppedColumn> entry: metadata.droppedColumns.entrySet())
{
if (metadata.getColumn(entry.getKey()) != null)
continue;
DroppedColumn droppedColumn = entry.getValue();
cdCommaAppender.accept(sb);
sb.append(droppedColumn.column.name.toCQLString());
sb.append(' ');
sb.append(droppedColumn.column.type.asCQL3Type().toString());
}
}
if (clusteringColumns.size() > 0 || partitionKeyColumns.size() > 1)
{
sb.append(",\n\tPRIMARY KEY (");
if (partitionKeyColumns.size() > 1)
{
sb.append("(");
Consumer<StringBuilder> pkCommaAppender = commaAppender(" ");
for (ColumnMetadata cfd : partitionKeyColumns)
{
pkCommaAppender.accept(sb);
sb.append(cfd.name.toCQLString());
}
sb.append(")");
}
else
{
sb.append(partitionKeyColumns.get(0).name.toCQLString());
}
for (ColumnMetadata cfd : metadata.clusteringColumns())
sb.append(", ").append(cfd.name.toCQLString());
sb.append(')');
}
sb.append(")\n\t");
sb.append("WITH ");
sb.append("ID = ").append(metadata.id).append("\n\tAND ");
if (metadata.isCompactTable())
sb.append("COMPACT STORAGE\n\tAND ");
if (clusteringColumns.size() > 0)
{
sb.append("CLUSTERING ORDER BY (");
Consumer<StringBuilder> cOrderCommaAppender = commaAppender(" ");
for (ColumnMetadata cd : clusteringColumns)
{
cOrderCommaAppender.accept(sb);
sb.append(cd.name.toCQLString()).append(' ').append(cd.clusteringOrder().toString());
}
sb.append(")\n\tAND ");
}
sb.append(toCQL(metadata.params));
sb.append(";");
if (!isCqlCompatible(metadata))
{
sb.append("\n*/");
}
return sb.toString();
}
/**
* Build a CQL String representation of User Types used in the given Table.
*
* Type order is ensured as types are built incrementally: from the innermost (most nested)
* to the outermost.
*/
@VisibleForTesting
public static List<String> getUserTypesAsCQL(TableMetadata metadata)
{
List<AbstractType> types = new ArrayList<>();
Set<AbstractType> typeSet = new HashSet<>();
for (ColumnMetadata cd: Iterables.concat(metadata.partitionKeyColumns(), metadata.clusteringColumns(), metadata.regularAndStaticColumns()))
{
AbstractType type = cd.type;
if (type.isUDT())
resolveUserType((UserType) type, typeSet, types);
}
List<String> typeStrings = new ArrayList<>(types.size());
for (AbstractType type: types)
typeStrings.add(toCQL((UserType) type));
return typeStrings;
}
/**
* Build a CQL String representation of Dropped Columns in the given Table.
*
* If the column was dropped once, but is now re-created `ADD` will be appended accordingly.
*/
@VisibleForTesting
public static List<String> getDroppedColumnsAsCQL(TableMetadata metadata)
{
List<String> droppedColumns = new ArrayList<>();
for (Map.Entry<ByteBuffer, DroppedColumn> entry: metadata.droppedColumns.entrySet())
{
DroppedColumn column = entry.getValue();
droppedColumns.add(toCQLDrop(metadata, column));
if (metadata.getColumn(entry.getKey()) != null)
droppedColumns.add(toCQLAdd(metadata, metadata.getColumn(entry.getKey())));
}
return droppedColumns;
}
/**
* Build a CQL String representation of Indexes on columns in the given Table
*/
@VisibleForTesting
public static List<String> getIndexesAsCQL(TableMetadata metadata)
{
List<String> indexes = new ArrayList<>(metadata.indexes.size());
for (IndexMetadata indexMetadata: metadata.indexes)
indexes.add(toCQL(metadata, indexMetadata));
return indexes;
}
private static String toCQL(TableMetadata baseTable, IndexMetadata indexMetadata)
{
if (indexMetadata.isCustom())
{
Map<String, String> options = new HashMap<>();
indexMetadata.options.forEach((k, v) -> {
if (!k.equals(IndexTarget.TARGET_OPTION_NAME) && !k.equals(IndexTarget.CUSTOM_INDEX_OPTION_NAME))
options.put(k, v);
});
return String.format("CREATE CUSTOM INDEX %s ON %s (%s) USING '%s'%s;",
indexMetadata.toCQLString(),
baseTable.toString(),
indexMetadata.options.get(IndexTarget.TARGET_OPTION_NAME),
indexMetadata.options.get(IndexTarget.CUSTOM_INDEX_OPTION_NAME),
options.isEmpty() ? "" : " WITH OPTIONS " + toCQL(options));
}
else
{
return String.format("CREATE INDEX %s ON %s (%s);",
indexMetadata.toCQLString(),
baseTable.toString(),
indexMetadata.options.get(IndexTarget.TARGET_OPTION_NAME));
}
}
private static String toCQL(UserType userType)
{
StringBuilder sb = new StringBuilder();
sb.append("CREATE TYPE ").append(userType.toCQLString()).append(" (");
Consumer<StringBuilder> commaAppender = commaAppender(" ");
for (int i = 0; i < userType.size(); i++)
{
commaAppender.accept(sb);
sb.append(String.format("%s %s",
userType.fieldNameAsString(i),
userType.fieldType(i).asCQL3Type()));
}
sb.append(");");
return sb.toString();
}
private static String toCQL(TableParams tableParams)
{
StringBuilder builder = new StringBuilder();
builder.append("bloom_filter_fp_chance = ").append(tableParams.bloomFilterFpChance);
builder.append("\n\tAND crc_check_chance = ").append(tableParams.crcCheckChance);
builder.append("\n\tAND default_time_to_live = ").append(tableParams.defaultTimeToLive);
builder.append("\n\tAND gc_grace_seconds = ").append(tableParams.gcGraceSeconds);
builder.append("\n\tAND min_index_interval = ").append(tableParams.minIndexInterval);
builder.append("\n\tAND max_index_interval = ").append(tableParams.maxIndexInterval);
builder.append("\n\tAND memtable_flush_period_in_ms = ").append(tableParams.memtableFlushPeriodInMs);
builder.append("\n\tAND speculative_retry = '").append(tableParams.speculativeRetry).append("'");
builder.append("\n\tAND additional_write_policy = '").append(tableParams.additionalWritePolicy).append("'");
builder.append("\n\tAND comment = ").append(singleQuote(tableParams.comment));
builder.append("\n\tAND caching = ").append(toCQL(tableParams.caching.asMap()));
builder.append("\n\tAND compaction = ").append(toCQL(tableParams.compaction.asMap()));
builder.append("\n\tAND compression = ").append(toCQL(tableParams.compression.asMap()));
builder.append("\n\tAND cdc = ").append(tableParams.cdc);
builder.append("\n\tAND extensions = { ");
for (Map.Entry<String, ByteBuffer> entry : tableParams.extensions.entrySet())
{
builder.append(singleQuote(entry.getKey()));
builder.append(": ");
builder.append("0x").append(ByteBufferUtil.bytesToHex(entry.getValue()));
}
builder.append(" }");
return builder.toString();
}
private static String toCQL(Map<?, ?> map)
{
StringBuilder builder = new StringBuilder("{ ");
boolean isFirst = true;
for (Map.Entry entry: map.entrySet())
{
if (isFirst)
isFirst = false;
else
builder.append(", ");
builder.append(singleQuote(entry.getKey().toString()));
builder.append(": ");
builder.append(singleQuote(entry.getValue().toString()));
}
builder.append(" }");
return builder.toString();
}
private static String toCQL(ColumnMetadata cd)
{
return toCQL(cd, false);
}
private static String toCQL(ColumnMetadata cd, boolean isStaticCompactTable)
{
return String.format("%s %s%s",
cd.name.toCQLString(),
cd.type.asCQL3Type().toString(),
cd.isStatic() && !isStaticCompactTable ? " static" : "");
}
private static String toCQLAdd(TableMetadata table, ColumnMetadata cd)
{
return String.format("ALTER TABLE %s ADD %s %s%s;",
table.toString(),
cd.name.toCQLString(),
cd.type.asCQL3Type().toString(),
cd.isStatic() ? " static" : "");
}
private static String toCQLDrop(TableMetadata table, DroppedColumn droppedColumn)
{
return String.format("ALTER TABLE %s DROP %s USING TIMESTAMP %s;",
table.toString(),
droppedColumn.column.name.toCQLString(),
droppedColumn.droppedTime);
}
private static void resolveUserType(UserType type, Set<AbstractType> typeSet, List<AbstractType> types)
{
for (AbstractType subType: type.fieldTypes())
if (!typeSet.contains(subType) && subType.isUDT())
resolveUserType((UserType) subType, typeSet, types);
if (!typeSet.contains(type))
{
typeSet.add(type);
types.add(type);
}
}
private static String singleQuote(String s)
{
return String.format("'%s'", singleQuotePattern.matcher(s).replaceAll("''"));
}
private static Consumer<StringBuilder> commaAppender(String afterComma)
{
AtomicBoolean isFirst = new AtomicBoolean(true);
return new Consumer<StringBuilder>()
{
public void accept(StringBuilder stringBuilder)
{
if (!isFirst.getAndSet(false))
stringBuilder.append(',').append(afterComma);
}
};
}
/**
* Whether or not the given metadata is compatible / representable with CQL Language
*/
public static boolean isCqlCompatible(TableMetadata metaData)
{
if (metaData.isSuper())
return false;
if (metaData.isCompactTable()
&& metaData.regularColumns().size() > 1
&& metaData.clusteringColumns().size() >= 1)
return false;
return true;
}
}

View File

@ -20,12 +20,7 @@ package org.apache.cassandra.db.marshal;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -36,15 +31,14 @@ import org.apache.cassandra.cql3.ColumnSpecification;
import org.apache.cassandra.cql3.Term; import org.apache.cassandra.cql3.Term;
import org.apache.cassandra.db.TypeSizes; import org.apache.cassandra.db.TypeSizes;
import org.apache.cassandra.exceptions.SyntaxException; import org.apache.cassandra.exceptions.SyntaxException;
import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FastByteOperations; import org.apache.cassandra.utils.FastByteOperations;
import org.github.jamm.Unmetered; import org.github.jamm.Unmetered;
import org.apache.cassandra.io.util.DataOutputPlus;
import org.apache.cassandra.io.util.DataInputPlus;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.apache.cassandra.db.marshal.AbstractType.ComparisonType.CUSTOM; import static org.apache.cassandra.db.marshal.AbstractType.ComparisonType.CUSTOM;
@ -348,6 +342,11 @@ public abstract class AbstractType<T> implements Comparator<ByteBuffer>, Assignm
return this; return this;
} }
public List<AbstractType<?>> subTypes()
{
return Collections.emptyList();
}
/** /**
* Returns an AbstractType instance that is equivalent to this one, but with all nested UDTs and collections * Returns an AbstractType instance that is equivalent to this one, but with all nested UDTs and collections
* explicitly frozen. * explicitly frozen.

View File

@ -138,6 +138,12 @@ public class ListType<T> extends CollectionType<List<T>>
return getInstance(elements.freezeNestedMulticellTypes(), isMultiCell); return getInstance(elements.freezeNestedMulticellTypes(), isMultiCell);
} }
@Override
public List<AbstractType<?>> subTypes()
{
return Collections.singletonList(elements);
}
@Override @Override
public boolean isMultiCell() public boolean isMultiCell()
{ {

View File

@ -128,6 +128,12 @@ public class MapType<K, V> extends CollectionType<Map<K, V>>
return isMultiCell; return isMultiCell;
} }
@Override
public List<AbstractType<?>> subTypes()
{
return Arrays.asList(keys, values);
}
@Override @Override
public AbstractType<?> freeze() public AbstractType<?> freeze()
{ {

View File

@ -120,6 +120,12 @@ public class SetType<T> extends CollectionType<Set<T>>
return this; return this;
} }
@Override
public List<AbstractType<?>> subTypes()
{
return Collections.singletonList(elements);
}
@Override @Override
public AbstractType<?> freezeNestedMulticellTypes() public AbstractType<?> freezeNestedMulticellTypes()
{ {

View File

@ -126,6 +126,12 @@ public class TupleType extends AbstractType<ByteBuffer>
return types.size(); return types.size();
} }
@Override
public List<AbstractType<?>> subTypes()
{
return types;
}
public List<AbstractType<?>> allTypes() public List<AbstractType<?>> allTypes()
{ {
return types; return types;

View File

@ -29,9 +29,9 @@ import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.CellPath; import org.apache.cassandra.db.rows.CellPath;
import org.apache.cassandra.schema.Difference; import org.apache.cassandra.schema.Difference;
import org.apache.cassandra.serializers.MarshalException; import org.apache.cassandra.serializers.MarshalException;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.serializers.TypeSerializer; import org.apache.cassandra.serializers.TypeSerializer;
import org.apache.cassandra.serializers.UserTypeSerializer; import org.apache.cassandra.serializers.UserTypeSerializer;
import org.apache.cassandra.transport.ProtocolVersion;
import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.Pair; import org.apache.cassandra.utils.Pair;
@ -43,7 +43,7 @@ import static com.google.common.collect.Iterables.transform;
* *
* A user type is really just a tuple type on steroids. * A user type is really just a tuple type on steroids.
*/ */
public class UserType extends TupleType public class UserType extends TupleType implements SchemaElement
{ {
public final String keyspace; public final String keyspace;
public final ByteBuffer name; public final ByteBuffer name;
@ -432,7 +432,7 @@ public class UserType extends TupleType
return sb.toString(); return sb.toString();
} }
public String toCQLString() public String getCqlTypeName()
{ {
return String.format("%s.%s", ColumnIdentifier.maybeQuote(keyspace), ColumnIdentifier.maybeQuote(getNameAsString())); return String.format("%s.%s", ColumnIdentifier.maybeQuote(keyspace), ColumnIdentifier.maybeQuote(getNameAsString()));
} }
@ -442,4 +442,52 @@ public class UserType extends TupleType
{ {
return serializer; return serializer;
} }
@Override
public SchemaElementType elementType()
{
return SchemaElementType.TYPE;
}
@Override
public String elementKeyspace()
{
return keyspace;
}
@Override
public String elementName()
{
return getNameAsString();
}
@Override
public String toCqlString(boolean withInternals)
{
CqlBuilder builder = new CqlBuilder();
builder.append("CREATE TYPE ")
.appendQuotingIfNeeded(keyspace)
.append('.')
.appendQuotingIfNeeded(getNameAsString())
.append(" (")
.newLine()
.increaseIndent();
for (int i = 0; i < size(); i++)
{
if (i > 0)
builder.append(",")
.newLine();
builder.append(fieldNameAsString(i))
.append(' ')
.append(fieldType(i));
}
builder.newLine()
.decreaseIndent()
.append(");");
return builder.toString();
}
} }

View File

@ -19,24 +19,24 @@ package org.apache.cassandra.db.virtual;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_VIEWS;
public final class SystemViewsKeyspace extends VirtualKeyspace public final class SystemViewsKeyspace extends VirtualKeyspace
{ {
private static final String NAME = "system_views";
public static SystemViewsKeyspace instance = new SystemViewsKeyspace(); public static SystemViewsKeyspace instance = new SystemViewsKeyspace();
private SystemViewsKeyspace() private SystemViewsKeyspace()
{ {
super(NAME, new ImmutableList.Builder<VirtualTable>() super(VIRTUAL_VIEWS, new ImmutableList.Builder<VirtualTable>()
.add(new CachesTable(NAME)) .add(new CachesTable(VIRTUAL_VIEWS))
.add(new ClientsTable(NAME)) .add(new ClientsTable(VIRTUAL_VIEWS))
.add(new SettingsTable(NAME)) .add(new SettingsTable(VIRTUAL_VIEWS))
.add(new SystemPropertiesTable(NAME)) .add(new SystemPropertiesTable(VIRTUAL_VIEWS))
.add(new SSTableTasksTable(NAME)) .add(new SSTableTasksTable(VIRTUAL_VIEWS))
.add(new ThreadPoolsTable(NAME)) .add(new ThreadPoolsTable(VIRTUAL_VIEWS))
.add(new InternodeOutboundTable(NAME)) .add(new InternodeOutboundTable(VIRTUAL_VIEWS))
.add(new InternodeInboundTable(NAME)) .add(new InternodeInboundTable(VIRTUAL_VIEWS))
.addAll(TableMetricTables.getAll(NAME)) .addAll(TableMetricTables.getAll(VIRTUAL_VIEWS))
.build()); .build());
} }
} }

View File

@ -25,20 +25,18 @@ import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.dht.LocalPartitioner; import org.apache.cassandra.dht.LocalPartitioner;
import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.KeyspaceMetadata; import org.apache.cassandra.schema.KeyspaceMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadata;
import static org.apache.cassandra.schema.SchemaConstants.VIRTUAL_SCHEMA;
import static org.apache.cassandra.schema.TableMetadata.builder; import static org.apache.cassandra.schema.TableMetadata.builder;
public final class VirtualSchemaKeyspace extends VirtualKeyspace public final class VirtualSchemaKeyspace extends VirtualKeyspace
{ {
private static final String NAME = "system_virtual_schema";
public static final VirtualSchemaKeyspace instance = new VirtualSchemaKeyspace(); public static final VirtualSchemaKeyspace instance = new VirtualSchemaKeyspace();
private VirtualSchemaKeyspace() private VirtualSchemaKeyspace()
{ {
super(NAME, ImmutableList.of(new VirtualKeyspaces(NAME), new VirtualTables(NAME), new VirtualColumns(NAME))); super(VIRTUAL_SCHEMA, ImmutableList.of(new VirtualKeyspaces(VIRTUAL_SCHEMA), new VirtualTables(VIRTUAL_SCHEMA), new VirtualColumns(VIRTUAL_SCHEMA)));
} }
private static final class VirtualKeyspaces extends AbstractVirtualTable private static final class VirtualKeyspaces extends AbstractVirtualTable

View File

@ -23,4 +23,9 @@ public class InvalidRequestException extends RequestValidationException
{ {
super(ExceptionCode.INVALID, msg); super(ExceptionCode.INVALID, msg);
} }
public InvalidRequestException(String msg, Throwable t)
{
super(ExceptionCode.INVALID, msg, t);
}
} }

View File

@ -25,8 +25,6 @@ import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token; import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.ReplicaCollection.Builder.Conflict; import org.apache.cassandra.locator.ReplicaCollection.Builder.Conflict;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class EndpointsByRange extends ReplicaMultimap<Range<Token>, EndpointsForRange> public class EndpointsByRange extends ReplicaMultimap<Range<Token>, EndpointsForRange>

View File

@ -426,6 +426,16 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
((UserType)type).nameComparator().validate(path.get(0)); ((UserType)type).nameComparator().validate(path.get(0));
} }
public void appendCqlTo(CqlBuilder builder, boolean ignoreStatic)
{
builder.append(name)
.append(' ')
.append(type);
if (isStatic() && !ignoreStatic)
builder.append(" static");
}
public static String toCQLString(Iterable<ColumnMetadata> defs) public static String toCQLString(Iterable<ColumnMetadata> defs)
{ {
return toCQLString(defs.iterator()); return toCQLString(defs.iterator());
@ -443,6 +453,14 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
return sb.toString(); return sb.toString();
} }
public void appendNameAndOrderTo(CqlBuilder builder)
{
builder.append(name.toCQLString())
.append(' ')
.append(clusteringOrder().toString());
}
/** /**
* The type of the cell values for cell belonging to this column. * The type of the cell values for cell belonging to this column.
* *
@ -648,7 +666,4 @@ public final class ColumnMetadata extends ColumnSpecification implements Selecta
} }
} }
} }
} }

View File

@ -20,6 +20,7 @@ package org.apache.cassandra.schema;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.google.common.collect.*; import com.google.common.collect.*;
@ -142,6 +143,36 @@ public final class Functions implements Iterable<Function>
return functions.get(name); return functions.get(name);
} }
/**
* Get all UDFs overloads with the specified name
*
* @param name fully qualified function name
* @return an empty list if the function name is not found; a non-empty collection of {@link UDFunction} otherwise
*/
public Collection<UDFunction> getUdfs(FunctionName name)
{
return functions.get(name)
.stream()
.filter(Filter.UDF)
.map(f -> (UDFunction) f)
.collect(Collectors.toList());
}
/**
* Get all UDAs overloads with the specified name
*
* @param name fully qualified function name
* @return an empty list if the function name is not found; a non-empty collection of {@link UDAggregate} otherwise
*/
public Collection<UDAggregate> getUdas(FunctionName name)
{
return functions.get(name)
.stream()
.filter(Filter.UDA)
.map(f -> (UDAggregate) f)
.collect(Collectors.toList());
}
public Optional<Function> find(FunctionName name, List<AbstractType<?>> argTypes) public Optional<Function> find(FunctionName name, List<AbstractType<?>> argTypes)
{ {
return find(name, argTypes, Filter.ALL); return find(name, argTypes, Filter.ALL);

View File

@ -32,6 +32,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.statements.schema.IndexTarget; import org.apache.cassandra.cql3.statements.schema.IndexTarget;
import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.exceptions.UnknownIndexException; import org.apache.cassandra.exceptions.UnknownIndexException;
@ -224,9 +225,49 @@ public final class IndexMetadata
.build(); .build();
} }
public String toCQLString() public String toCqlString(TableMetadata table)
{ {
return ColumnIdentifier.maybeQuote(name); CqlBuilder builder = new CqlBuilder();
appendCqlTo(builder, table);
return builder.toString();
}
/**
* Appends to the specified builder the CQL used to create this index.
*
* @param builder the builder to which the CQL myst be appended
* @param table the parent table
*/
public void appendCqlTo(CqlBuilder builder, TableMetadata table)
{
if (isCustom())
{
Map<String, String> copyOptions = new HashMap<>(options);
builder.append("CREATE CUSTOM INDEX ")
.appendQuotingIfNeeded(name)
.append(" ON ")
.append(table.toString())
.append(" (")
.append(copyOptions.remove(IndexTarget.TARGET_OPTION_NAME))
.append(") USING ")
.appendWithSingleQuotes(copyOptions.remove(IndexTarget.CUSTOM_INDEX_OPTION_NAME));
if (!copyOptions.isEmpty())
builder.append(" WITH OPTIONS = ")
.append(options);
}
else
{
builder.append("CREATE INDEX ")
.appendQuotingIfNeeded(name)
.append(" ON ")
.append(table.toString())
.append(" (")
.append(options.get(IndexTarget.TARGET_OPTION_NAME))
.append(')');
}
builder.append(';');
} }
public static class Serializer public static class Serializer

View File

@ -28,6 +28,8 @@ import com.google.common.base.Objects;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.SchemaElement;
import org.apache.cassandra.cql3.functions.UDAggregate; import org.apache.cassandra.cql3.functions.UDAggregate;
import org.apache.cassandra.cql3.functions.UDFunction; import org.apache.cassandra.cql3.functions.UDFunction;
import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.marshal.UserType;
@ -46,7 +48,7 @@ import static com.google.common.collect.Iterables.any;
/** /**
* An immutable representation of keyspace metadata (name, params, tables, types, and functions). * An immutable representation of keyspace metadata (name, params, tables, types, and functions).
*/ */
public final class KeyspaceMetadata public final class KeyspaceMetadata implements SchemaElement
{ {
public enum Kind public enum Kind
{ {
@ -230,6 +232,61 @@ public final class KeyspaceMetadata
.toString(); .toString();
} }
@Override
public SchemaElementType elementType()
{
return SchemaElementType.KEYSPACE;
}
@Override
public String elementKeyspace()
{
return name;
}
@Override
public String elementName()
{
return name;
}
@Override
public String toCqlString(boolean withInternals)
{
CqlBuilder builder = new CqlBuilder();
if (isVirtual())
{
builder.append("/*")
.newLine()
.append("Warning: Keyspace ")
.appendQuotingIfNeeded(name)
.append(" is a virtual keyspace and cannot be recreated with CQL.")
.newLine()
.append("Structure, for reference:")
.newLine()
.append("VIRTUAL KEYSPACE ")
.appendQuotingIfNeeded(name)
.append(';')
.newLine()
.append("*/")
.toString();
}
else
{
builder.append("CREATE KEYSPACE ")
.appendQuotingIfNeeded(name)
.append(" WITH replication = ");
params.replication.appendCqlTo(builder);
builder.append(" AND durable_writes = ")
.append(params.durableWrites)
.append(';')
.toString();
}
return builder.toString();
}
public void validate() public void validate()
{ {
if (!SchemaConstants.isValidName(name)) if (!SchemaConstants.isValidName(name))

View File

@ -25,6 +25,7 @@ import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.locator.*; import org.apache.cassandra.locator.*;
import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageService;
@ -128,4 +129,21 @@ public final class ReplicationParams
helper.add(entry.getKey(), entry.getValue()); helper.add(entry.getKey(), entry.getValue());
return helper.toString(); return helper.toString();
} }
public void appendCqlTo(CqlBuilder builder)
{
String classname = "org.apache.cassandra.locator".equals(klass.getPackage().getName()) ? klass.getSimpleName()
: klass.getName();
builder.append("{'class': ")
.appendWithSingleQuotes(classname);
options.forEach((k, v) -> {
builder.append(", ")
.appendWithSingleQuotes(k)
.append(": ")
.appendWithSingleQuotes(v);
});
builder.append('}');
}
} }

View File

@ -239,6 +239,11 @@ public final class Schema
return keyspaceInstances.remove(keyspaceName); return keyspaceInstances.remove(keyspaceName);
} }
public Keyspaces snapshot()
{
return keyspaces;
}
/** /**
* Remove keyspace definition from system * Remove keyspace definition from system
* *

View File

@ -39,6 +39,10 @@ public final class SchemaConstants
public static final String AUTH_KEYSPACE_NAME = "system_auth"; public static final String AUTH_KEYSPACE_NAME = "system_auth";
public static final String DISTRIBUTED_KEYSPACE_NAME = "system_distributed"; public static final String DISTRIBUTED_KEYSPACE_NAME = "system_distributed";
public static final String VIRTUAL_SCHEMA = "system_virtual_schema";
public static final String VIRTUAL_VIEWS = "system_views";
/* system keyspace names (the ones with LocalStrategy replication strategy) */ /* system keyspace names (the ones with LocalStrategy replication strategy) */
public static final Set<String> LOCAL_SYSTEM_KEYSPACE_NAMES = public static final Set<String> LOCAL_SYSTEM_KEYSPACE_NAMES =
ImmutableSet.of(SYSTEM_KEYSPACE_NAME, SCHEMA_KEYSPACE_NAME); ImmutableSet.of(SYSTEM_KEYSPACE_NAME, SCHEMA_KEYSPACE_NAME);
@ -84,4 +88,24 @@ public final class SchemaConstants
{ {
return REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(keyspaceName.toLowerCase()); return REPLICATED_SYSTEM_KEYSPACE_NAMES.contains(keyspaceName.toLowerCase());
} }
/**
* Checks if the keyspace is a virtual system keyspace.
* @return {@code true} if the keyspace is a virtual system keyspace, {@code false} otherwise.
*/
public static boolean isVirtualSystemKeyspace(String keyspaceName)
{
return VIRTUAL_SCHEMA.equals(keyspaceName.toLowerCase()) || VIRTUAL_VIEWS.equals(keyspaceName.toLowerCase());
}
/**
* Checks if the keyspace is a system keyspace (local replicated or virtual).
* @return {@code true} if the keyspace is a system keyspace, {@code false} otherwise.
*/
public static boolean isSystemKeyspace(String keyspaceName)
{
return isLocalSystemKeyspace(keyspaceName)
|| isReplicatedSystemKeyspace(keyspaceName)
|| isVirtualSystemKeyspace(keyspaceName);
}
} }

View File

@ -19,7 +19,7 @@ package org.apache.cassandra.schema;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import java.util.Objects; import java.util.Map.Entry;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -32,6 +32,8 @@ import org.slf4j.LoggerFactory;
import org.apache.cassandra.auth.DataResource; import org.apache.cassandra.auth.DataResource;
import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.ColumnIdentifier; import org.apache.cassandra.cql3.ColumnIdentifier;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.cql3.SchemaElement;
import org.apache.cassandra.db.*; import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.*; import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.dht.IPartitioner; import org.apache.cassandra.dht.IPartitioner;
@ -40,16 +42,15 @@ import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.utils.AbstractIterator; import org.apache.cassandra.utils.AbstractIterator;
import org.github.jamm.Unmetered; import org.github.jamm.Unmetered;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.transform;
import static java.lang.String.format; import static java.lang.String.format;
import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet; import static java.util.stream.Collectors.toSet;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.transform;
import static org.apache.cassandra.schema.IndexMetadata.isNameValid; import static org.apache.cassandra.schema.IndexMetadata.isNameValid;
@Unmetered @Unmetered
public final class TableMetadata public final class TableMetadata implements SchemaElement
{ {
private static final Logger logger = LoggerFactory.getLogger(TableMetadata.class); private static final Logger logger = LoggerFactory.getLogger(TableMetadata.class);
private static final ImmutableSet<Flag> DEFAULT_CQL_FLAGS = ImmutableSet.of(Flag.COMPOUND); private static final ImmutableSet<Flag> DEFAULT_CQL_FLAGS = ImmutableSet.of(Flag.COMPOUND);
@ -295,6 +296,11 @@ public final class TableMetadata
return clusteringColumns; return clusteringColumns;
} }
public ImmutableList<ColumnMetadata> createStatementClusteringColumns()
{
return isStaticCompactTable() ? ImmutableList.of() : clusteringColumns;
}
public RegularAndStaticColumns regularAndStaticColumns() public RegularAndStaticColumns regularAndStaticColumns()
{ {
return regularAndStaticColumns; return regularAndStaticColumns;
@ -317,27 +323,53 @@ public final class TableMetadata
*/ */
public Iterator<ColumnMetadata> allColumnsInSelectOrder() public Iterator<ColumnMetadata> allColumnsInSelectOrder()
{ {
final boolean isStaticCompactTable = isStaticCompactTable(); boolean isStaticCompactTable = isStaticCompactTable();
final boolean noNonPkColumns = isCompactTable() && CompactTables.hasEmptyCompactValue(this); boolean noNonPkColumns = isCompactTable() && CompactTables.hasEmptyCompactValue(this);
Iterator<ColumnMetadata> partitionKeyIter = partitionKeyColumns.iterator();
Iterator<ColumnMetadata> clusteringIter =
isStaticCompactTable ? Collections.emptyIterator() : clusteringColumns.iterator();
Iterator<ColumnMetadata> otherColumns =
noNonPkColumns
? Collections.emptyIterator()
: (isStaticCompactTable ? staticColumns().selectOrderIterator()
: regularAndStaticColumns.selectOrderIterator());
return columnsIterator(partitionKeyIter, clusteringIter, otherColumns);
}
/**
* Returns an iterator over all column definitions that respect the order of the CREATE statement.
*/
public Iterator<ColumnMetadata> allColumnsInCreateOrder()
{
boolean isStaticCompactTable = isStaticCompactTable();
boolean noNonPkColumns = isCompactTable() && CompactTables.hasEmptyCompactValue(this);
Iterator<ColumnMetadata> partitionKeyIter = partitionKeyColumns.iterator();
Iterator<ColumnMetadata> clusteringIter = createStatementClusteringColumns().iterator();
Iterator<ColumnMetadata> otherColumns =
noNonPkColumns
? Collections.emptyIterator()
: (isStaticCompactTable ? staticColumns().iterator()
: regularAndStaticColumns.iterator());
return columnsIterator(partitionKeyIter, clusteringIter, otherColumns);
}
private static Iterator<ColumnMetadata> columnsIterator(Iterator<ColumnMetadata> partitionKeys,
Iterator<ColumnMetadata> clusteringColumns,
Iterator<ColumnMetadata> otherColumns)
{
return new AbstractIterator<ColumnMetadata>() return new AbstractIterator<ColumnMetadata>()
{ {
private final Iterator<ColumnMetadata> partitionKeyIter = partitionKeyColumns.iterator();
private final Iterator<ColumnMetadata> clusteringIter =
isStaticCompactTable ? Collections.emptyIterator() : clusteringColumns.iterator();
private final Iterator<ColumnMetadata> otherColumns =
noNonPkColumns
? Collections.emptyIterator()
: (isStaticCompactTable ? staticColumns().selectOrderIterator()
: regularAndStaticColumns.selectOrderIterator());
protected ColumnMetadata computeNext() protected ColumnMetadata computeNext()
{ {
if (partitionKeyIter.hasNext()) if (partitionKeys.hasNext())
return partitionKeyIter.next(); return partitionKeys.next();
if (clusteringIter.hasNext()) if (clusteringColumns.hasNext())
return clusteringIter.next(); return clusteringColumns.next();
return otherColumns.hasNext() ? otherColumns.next() : endOfData(); return otherColumns.hasNext() ? otherColumns.next() : endOfData();
} }
@ -1090,4 +1122,268 @@ public final class TableMetadata
{ {
return isView() && Keyspace.open(keyspace).viewManager.getByName(name).enforceStrictLiveness(); return isView() && Keyspace.open(keyspace).viewManager.getByName(name).enforceStrictLiveness();
} }
/**
* Returns the names of all the user types referenced by this table.
*
* @return the names of all the user types referenced by this table.
*/
public Set<ByteBuffer> getReferencedUserTypes()
{
Set<ByteBuffer> types = new LinkedHashSet<>();
columns().forEach(c -> addUserTypes(c.type, types));
return types;
}
/**
* Find all user types used by the specified type and add them to the set.
*
* @param type the type to check for user types.
* @param types the set of UDT names to which to add new user types found in {@code type}. Note that the
* insertion ordering is important and ensures that if a user type A uses another user type B, then B will appear
* before A in iteration order.
*/
private static void addUserTypes(AbstractType<?> type, Set<ByteBuffer> types)
{
// Reach into subtypes first, so that if the type is a UDT, it's dependencies are recreated first.
type.subTypes().forEach(t -> addUserTypes(t, types));
if (type.isUDT())
types.add(((UserType)type).name);
}
@Override
public SchemaElementType elementType()
{
return SchemaElementType.TABLE;
}
@Override
public String elementKeyspace()
{
return keyspace;
}
@Override
public String elementName()
{
return name;
}
@Override
public String toCqlString(boolean withInternals)
{
CqlBuilder builder = new CqlBuilder(2048);
appendCqlTo(builder, withInternals, withInternals, false);
return builder.toString();
}
public String toCqlString(boolean includeDroppedColumns,
boolean internals,
boolean ifNotExists)
{
CqlBuilder builder = new CqlBuilder(2048);
appendCqlTo(builder, includeDroppedColumns, internals, ifNotExists);
return builder.toString();
}
public void appendCqlTo(CqlBuilder builder,
boolean includeDroppedColumns,
boolean internals,
boolean ifNotExists)
{
assert !isView();
String createKeyword = "CREATE";
if (!isCQLTable())
{
builder.append("/*")
.newLine()
.append("Warning: Table ")
.append(toString())
.append(" omitted because it has constructs not compatible with CQL (was created via legacy API).")
.newLine()
.append("Approximate structure, for reference:")
.newLine()
.append("(this should not be used to reproduce this schema)")
.newLine()
.newLine();
}
else if (isVirtual())
{
builder.append(String.format("/*\n" +
"Warning: Table %s is a virtual table and cannot be recreated with CQL.\n" +
"Structure, for reference:\n",
toString()));
createKeyword = "VIRTUAL";
}
builder.append(createKeyword)
.append(" TABLE ");
if (ifNotExists)
builder.append("IF NOT EXISTS ");
builder.append(toString())
.append(" (")
.newLine()
.increaseIndent();
boolean hasSingleColumnPrimaryKey = partitionKeyColumns.size() == 1 && clusteringColumns.isEmpty();
appendColumnDefinitions(builder, includeDroppedColumns, hasSingleColumnPrimaryKey);
if (!hasSingleColumnPrimaryKey)
appendPrimaryKey(builder);
builder.decreaseIndent()
.append(')');
appendTableOptions(builder, internals);
builder.decreaseIndent();
if (!isCQLTable() || isVirtual())
{
builder.newLine()
.append("*/");
}
if (includeDroppedColumns)
appendDropColumns(builder);
}
private void appendColumnDefinitions(CqlBuilder builder,
boolean includeDroppedColumns,
boolean hasSingleColumnPrimaryKey)
{
Iterator<ColumnMetadata> iter = allColumnsInCreateOrder();
while (iter.hasNext())
{
ColumnMetadata column = iter.next();
// If the column has been re-added after a drop, we don't include it right away. Instead, we'll add the
// dropped one first below, then we'll issue the DROP and then the actual ADD for this column, thus
// simulating the proper sequence of events.
if (includeDroppedColumns && droppedColumns.containsKey(column.name.bytes))
continue;
column.appendCqlTo(builder, isStaticCompactTable());
if (hasSingleColumnPrimaryKey && column.isPartitionKey())
builder.append(" PRIMARY KEY");
if (!hasSingleColumnPrimaryKey || (includeDroppedColumns && !droppedColumns.isEmpty()) || iter.hasNext())
builder.append(',');
builder.newLine();
}
if (includeDroppedColumns)
{
Iterator<DroppedColumn> iterDropped = droppedColumns.values().iterator();
while (iterDropped.hasNext())
{
DroppedColumn dropped = iterDropped.next();
dropped.column.appendCqlTo(builder, isStaticCompactTable());
if (!hasSingleColumnPrimaryKey || iter.hasNext())
builder.append(',');
builder.newLine();
}
}
}
void appendPrimaryKey(CqlBuilder builder)
{
List<ColumnMetadata> partitionKeyColumns = partitionKeyColumns();
List<ColumnMetadata> clusteringColumns = createStatementClusteringColumns();
builder.append("PRIMARY KEY (");
if (partitionKeyColumns.size() > 1)
{
builder.append('(')
.appendWithSeparators(partitionKeyColumns, (b, c) -> b.append(c.name), ", ")
.append(')');
}
else
{
builder.append(partitionKeyColumns.get(0).name);
}
if (!clusteringColumns.isEmpty())
builder.append(", ")
.appendWithSeparators(clusteringColumns, (b, c) -> b.append(c.name), ", ");
builder.append(')')
.newLine();
}
void appendTableOptions(CqlBuilder builder, boolean internals)
{
builder.append(" WITH ")
.increaseIndent();
if (internals)
builder.append("ID = ")
.append(id.toString())
.newLine()
.append("AND ");
if (isCompactTable())
builder.append("COMPACT STORAGE")
.newLine()
.append("AND ");
List<ColumnMetadata> clusteringColumns = createStatementClusteringColumns();
if (!clusteringColumns.isEmpty())
{
builder.append("CLUSTERING ORDER BY (")
.appendWithSeparators(clusteringColumns, (b, c) -> c.appendNameAndOrderTo(b), ", ")
.append(')')
.newLine()
.append("AND ");
}
if (isVirtual())
{
builder.append("comment = ").appendWithSingleQuotes(params.comment);
}
else
{
params.appendCqlTo(builder);
}
builder.append(";");
}
private void appendDropColumns(CqlBuilder builder)
{
for (Entry<ByteBuffer, DroppedColumn> entry : droppedColumns.entrySet())
{
DroppedColumn dropped = entry.getValue();
builder.newLine()
.append("ALTER TABLE ")
.append(toString())
.append(" DROP ")
.append(dropped.column.name)
.append(" USING TIMESTAMP ")
.append(dropped.droppedTime)
.append(';');
ColumnMetadata column = getColumn(entry.getKey());
if (column != null)
{
builder.newLine()
.append("ALTER TABLE ")
.append(toString())
.append(" ADD ");
column.appendCqlTo(builder, false);
builder.append(';');
}
}
}
} }

View File

@ -19,19 +19,23 @@ package org.apache.cassandra.schema;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import org.apache.cassandra.cql3.Attributes; import org.apache.cassandra.cql3.Attributes;
import org.apache.cassandra.cql3.CqlBuilder;
import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.service.reads.PercentileSpeculativeRetryPolicy; import org.apache.cassandra.service.reads.PercentileSpeculativeRetryPolicy;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy; import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.service.reads.repair.ReadRepairStrategy; import org.apache.cassandra.service.reads.repair.ReadRepairStrategy;
import org.apache.cassandra.utils.BloomCalculations; import org.apache.cassandra.utils.BloomCalculations;
import org.apache.cassandra.utils.ByteBufferUtil;
import static java.lang.String.format; import static java.lang.String.format;
import static java.util.stream.Collectors.toMap;
public final class TableParams public final class TableParams
{ {
@ -253,6 +257,47 @@ public final class TableParams
.toString(); .toString();
} }
public void appendCqlTo(CqlBuilder builder)
{
// option names should be in alphabetical order
builder.append("additional_write_policy = ").appendWithSingleQuotes(additionalWritePolicy.toString())
.newLine()
.append("AND bloom_filter_fp_chance = ").append(bloomFilterFpChance)
.newLine()
.append("AND caching = ").append(caching.asMap())
.newLine()
.append("AND cdc = ").append(cdc)
.newLine()
.append("AND comment = ").appendWithSingleQuotes(comment)
.newLine()
.append("AND compaction = ").append(compaction.asMap())
.newLine()
.append("AND compression = ").append(compression.asMap())
.newLine()
.append("AND crc_check_chance = ").append(crcCheckChance)
.newLine()
.append("AND default_time_to_live = ").append(defaultTimeToLive)
.newLine()
.append("AND extensions = ").append(extensions.entrySet()
.stream()
.collect(toMap(Entry::getKey,
e -> "0x" + ByteBufferUtil.bytesToHex(e.getValue()))),
false)
.newLine()
.append("AND gc_grace_seconds = ").append(gcGraceSeconds)
.newLine()
.append("AND max_index_interval = ").append(maxIndexInterval)
.newLine()
.append("AND memtable_flush_period_in_ms = ").append(memtableFlushPeriodInMs)
.newLine()
.append("AND min_index_interval = ").append(minIndexInterval)
.newLine()
.append("AND read_repair = ").appendWithSingleQuotes(readRepair.toString())
.newLine()
.append("AND speculative_retry = ").appendWithSingleQuotes(speculativeRetry.toString());
}
public static final class Builder public static final class Builder
{ {
private String comment = ""; private String comment = "";

View File

@ -23,6 +23,8 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -77,6 +79,11 @@ public final class Tables implements Iterable<TableMetadata>
return tables.values().iterator(); return tables.values().iterator();
} }
public Stream<TableMetadata> stream()
{
return StreamSupport.stream(spliterator(), false);
}
public Iterable<TableMetadata> referencingUserType(ByteBuffer name) public Iterable<TableMetadata> referencingUserType(ByteBuffer name)
{ {
return Iterables.filter(tables.values(), t -> t.referencesUserType(name)); return Iterables.filter(tables.values(), t -> t.referencesUserType(name));

View File

@ -20,6 +20,8 @@ package org.apache.cassandra.schema;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.*; import java.util.*;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -86,6 +88,22 @@ public final class Types implements Iterable<UserType>
return types.values().iterator(); return types.values().iterator();
} }
public Stream<UserType> stream()
{
return StreamSupport.stream(spliterator(), false);
}
/**
* Returns a stream of user types sorted by dependencies
* @return a stream of user types sorted by dependencies
*/
public Stream<UserType> sortedStream()
{
Set<ByteBuffer> sorted = new LinkedHashSet<>();
types.values().forEach(t -> addUserTypes(t, sorted));
return sorted.stream().map(n -> types.get(n));
}
public Iterable<UserType> referencingUserType(ByteBuffer name) public Iterable<UserType> referencingUserType(ByteBuffer name)
{ {
return Iterables.filter(types.values(), t -> t.referencesUserType(name) && !t.name.equals(name)); return Iterables.filter(types.values(), t -> t.referencesUserType(name) && !t.name.equals(name));
@ -201,6 +219,36 @@ public final class Types implements Iterable<UserType>
return types.values().toString(); return types.values().toString();
} }
/**
* Sorts the types by dependencies.
*
* @param types the types to sort
* @return the types sorted by dependencies and names
*/
private static Set<ByteBuffer> sortByDependencies(Collection<UserType> types)
{
Set<ByteBuffer> sorted = new LinkedHashSet<>();
types.stream().forEach(t -> addUserTypes(t, sorted));
return sorted;
}
/**
* Find all user types used by the specified type and add them to the set.
*
* @param type the type to check for user types.
* @param types the set of UDT names to which to add new user types found in {@code type}. Note that the
* insertion ordering is important and ensures that if a user type A uses another user type B, then B will appear
* before A in iteration order.
*/
private static void addUserTypes(AbstractType<?> type, Set<ByteBuffer> types)
{
// Reach into subtypes first, so that if the type is a UDT, it's dependencies are recreated first.
type.subTypes().forEach(t -> addUserTypes(t, types));
if (type.isUDT())
types.add(((UserType) type).name);
}
public static final class Builder public static final class Builder
{ {
final ImmutableSortedMap.Builder<ByteBuffer, UserType> types = ImmutableSortedMap.naturalOrder(); final ImmutableSortedMap.Builder<ByteBuffer, UserType> types = ImmutableSortedMap.naturalOrder();

View File

@ -26,7 +26,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.cassandra.cql3.*; import org.apache.cassandra.cql3.*;
import org.apache.cassandra.db.marshal.UserType; import org.apache.cassandra.db.marshal.UserType;
public final class ViewMetadata public final class ViewMetadata implements SchemaElement
{ {
public final TableId baseTableId; public final TableId baseTableId;
public final String baseTableName; public final String baseTableName;
@ -161,4 +161,79 @@ public final class ViewMetadata
whereClause, whereClause,
metadata.unbuild().addColumn(column).build()); metadata.unbuild().addColumn(column).build());
} }
public void appendCqlTo(CqlBuilder builder,
boolean internals,
boolean ifNotExists)
{
builder.append("CREATE MATERIALIZED VIEW ");
if (ifNotExists)
builder.append("IF NOT EXISTS ");
builder.append(metadata.toString())
.append(" AS")
.newLine()
.increaseIndent()
.append("SELECT ");
if (includeAllColumns)
{
builder.append('*');
}
else
{
builder.appendWithSeparators(metadata.allColumnsInSelectOrder(), (b, c) -> b.append(c.name), ", ");
}
builder.newLine()
.append("FROM ")
.appendQuotingIfNeeded(metadata.keyspace)
.append('.')
.appendQuotingIfNeeded(baseTableName)
.newLine()
.append("WHERE ")
.append(whereClause.toString())
.newLine();
metadata.appendPrimaryKey(builder);
builder.decreaseIndent();
metadata.appendTableOptions(builder, internals);
}
@Override
public SchemaElementType elementType()
{
return SchemaElementType.MATERIALIZED_VIEW;
}
@Override
public String elementKeyspace()
{
return keyspace();
}
@Override
public String elementName()
{
return name();
}
@Override
public String toCqlString(boolean withInternals)
{
CqlBuilder builder = new CqlBuilder(2048);
appendCqlTo(builder, withInternals, false);
return builder.toString();
}
public String toCqlString(boolean internals,
boolean ifNotExists)
{
CqlBuilder builder = new CqlBuilder(2048);
appendCqlTo(builder, internals, ifNotExists);
return builder.toString();
}
} }

View File

@ -22,6 +22,8 @@ import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -83,6 +85,16 @@ public final class Views implements Iterable<ViewMetadata>
return Iterables.filter(this, v -> v.baseTableId.equals(tableId)); return Iterables.filter(this, v -> v.baseTableId.equals(tableId));
} }
public Stream<ViewMetadata> stream()
{
return StreamSupport.stream(spliterator(), false);
}
public Stream<ViewMetadata> stream(TableId tableId)
{
return stream().filter(v -> v.baseTableId.equals(tableId));
}
/** /**
* Get the materialized view with the specified name * Get the materialized view with the specified name
* *

View File

@ -26,6 +26,9 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.zip.CRC32; import java.util.zip.CRC32;
import java.util.zip.Checksum; import java.util.zip.Checksum;

View File

@ -604,7 +604,7 @@ public abstract class CQLTester
protected String createType(String query) protected String createType(String query)
{ {
String typeName = "type_" + seqNumber.getAndIncrement(); String typeName = String.format("type_%02d", seqNumber.getAndIncrement());
String fullQuery = String.format(query, KEYSPACE + "." + typeName); String fullQuery = String.format(query, KEYSPACE + "." + typeName);
types.add(typeName); types.add(typeName);
logger.info(fullQuery); logger.info(fullQuery);
@ -614,7 +614,7 @@ public abstract class CQLTester
protected String createFunction(String keyspace, String argTypes, String query) throws Throwable protected String createFunction(String keyspace, String argTypes, String query) throws Throwable
{ {
String functionName = keyspace + ".function_" + seqNumber.getAndIncrement(); String functionName = String.format("%s.function_%02d", keyspace, seqNumber.getAndIncrement());
createFunctionOverload(functionName, argTypes, query); createFunctionOverload(functionName, argTypes, query);
return functionName; return functionName;
} }
@ -629,7 +629,7 @@ public abstract class CQLTester
protected String createAggregate(String keyspace, String argTypes, String query) throws Throwable protected String createAggregate(String keyspace, String argTypes, String query) throws Throwable
{ {
String aggregateName = keyspace + "." + "aggregate_" + seqNumber.getAndIncrement(); String aggregateName = String.format("%s.aggregate_%02d", keyspace, seqNumber.getAndIncrement());
createAggregateOverload(aggregateName, argTypes, query); createAggregateOverload(aggregateName, argTypes, query);
return aggregateName; return aggregateName;
} }
@ -667,7 +667,7 @@ public abstract class CQLTester
protected String createKeyspaceName() protected String createKeyspaceName()
{ {
String currentKeyspace = "keyspace_" + seqNumber.getAndIncrement(); String currentKeyspace = String.format("keyspace_%02d", seqNumber.getAndIncrement());
keyspaces.add(currentKeyspace); keyspaces.add(currentKeyspace);
return currentKeyspace; return currentKeyspace;
} }
@ -688,7 +688,7 @@ public abstract class CQLTester
protected String createTableName() protected String createTableName()
{ {
String currentTable = "table_" + seqNumber.getAndIncrement(); String currentTable = String.format("table_%02d", seqNumber.getAndIncrement());
tables.add(currentTable); tables.add(currentTable);
return currentTable; return currentTable;
} }
@ -893,6 +893,11 @@ public abstract class CQLTester
return sessionNet().execute(formatQuery(query), values); return sessionNet().execute(formatQuery(query), values);
} }
protected com.datastax.driver.core.ResultSet executeNet(ProtocolVersion protocolVersion, Statement statement)
{
return sessionNet(protocolVersion).execute(statement);
}
protected com.datastax.driver.core.ResultSet executeNetWithPaging(ProtocolVersion version, String query, int pageSize) throws Throwable protected com.datastax.driver.core.ResultSet executeNetWithPaging(ProtocolVersion version, String query, int pageSize) throws Throwable
{ {
return sessionNet(version).execute(new SimpleStatement(formatQuery(query)).setFetchSize(pageSize)); return sessionNet(version).execute(new SimpleStatement(formatQuery(query)).setFetchSize(pageSize));
@ -1079,16 +1084,18 @@ public abstract class CQLTester
ByteBuffer expectedByteValue = makeByteBuffer(expected == null ? null : expected[j], column.type); ByteBuffer expectedByteValue = makeByteBuffer(expected == null ? null : expected[j], column.type);
ByteBuffer actualValue = actual.getBytes(column.name.toString()); ByteBuffer actualValue = actual.getBytes(column.name.toString());
if (expectedByteValue != null)
expectedByteValue = expectedByteValue.duplicate();
if (!Objects.equal(expectedByteValue, actualValue)) if (!Objects.equal(expectedByteValue, actualValue))
{ {
Object actualValueDecoded = actualValue == null ? null : column.type.getSerializer().deserialize(actualValue); Object actualValueDecoded = actualValue == null ? null : column.type.getSerializer().deserialize(actualValue);
if (!Objects.equal(expected[j], actualValueDecoded)) if (!Objects.equal(expected != null ? expected[j] : null, actualValueDecoded))
Assert.fail(String.format("Invalid value for row %d column %d (%s of type %s), expected <%s> but got <%s>", Assert.fail(String.format("Invalid value for row %d column %d (%s of type %s), expected <%s> but got <%s>",
i, i,
j, j,
column.name, column.name,
column.type.asCQL3Type(), column.type.asCQL3Type(),
formatValue(expectedByteValue, column.type), formatValue(expectedByteValue != null ? expectedByteValue.duplicate() : null, column.type),
formatValue(actualValue, column.type))); formatValue(actualValue, column.type)));
} }
} }

View File

@ -0,0 +1,815 @@
/*
* 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 java.util.Iterator;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.Test;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.SimpleStatement;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.TokenMetadata;
import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.TableId;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.transport.ProtocolVersion;
import static java.lang.String.format;
import static org.apache.cassandra.schema.SchemaConstants.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class DescribeStatementTest extends CQLTester
{
@Test
public void testSchemaChangeDuringPaging()
{
SimpleStatement stmt = new SimpleStatement("DESCRIBE KEYSPACES");
stmt.setFetchSize(1);
ResultSet rs = executeNet(ProtocolVersion.CURRENT, stmt);
Iterator<Row> iter = rs.iterator();
assertTrue(iter.hasNext());
iter.next();
createKeyspace("CREATE KEYSPACE %s WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1};");
try
{
iter.next();
fail("Expected InvalidQueryException");
}
catch (InvalidQueryException e)
{
assertEquals(DescribeStatement.SCHEMA_CHANGED_WHILE_PAGING_MESSAGE, e.getMessage());
}
}
@Test
public void testDescribeFunctionAndAggregate() throws Throwable
{
String fNonOverloaded = createFunction(KEYSPACE,
"",
"CREATE OR REPLACE FUNCTION %s() " +
"CALLED ON NULL INPUT " +
"RETURNS int " +
"LANGUAGE java " +
"AS 'throw new RuntimeException();';");
String fOverloaded = createFunction(KEYSPACE,
"int, ascii",
"CREATE FUNCTION %s (input int, other_in ascii) " +
"RETURNS NULL ON NULL INPUT " +
"RETURNS text " +
"LANGUAGE java " +
"AS 'return \"Hello World\";'");
createFunctionOverload(fOverloaded,
"text, ascii",
"CREATE FUNCTION %s (input text, other_in ascii) " +
"RETURNS NULL ON NULL INPUT " +
"RETURNS text " +
"LANGUAGE java " +
"AS 'return \"Hello World\";'");
for (String describeKeyword : new String[]{"DESCRIBE", "DESC"})
{
assertRowsNet(executeDescribeNet(describeKeyword + " FUNCTION " + fNonOverloaded),
row(KEYSPACE,
"function",
shortFunctionName(fNonOverloaded) + "()",
"CREATE FUNCTION " + fNonOverloaded + "()\n" +
" CALLED ON NULL INPUT\n" +
" RETURNS int\n" +
" LANGUAGE java\n" +
" AS $$throw new RuntimeException();$$;"));
assertRowsNet(executeDescribeNet(describeKeyword + " FUNCTION " + fOverloaded),
row(KEYSPACE,
"function",
shortFunctionName(fOverloaded) + "(int, ascii)",
"CREATE FUNCTION " + fOverloaded + "(input int, other_in ascii)\n" +
" RETURNS NULL ON NULL INPUT\n" +
" RETURNS text\n" +
" LANGUAGE java\n" +
" AS $$return \"Hello World\";$$;"),
row(KEYSPACE,
"function",
shortFunctionName(fOverloaded) + "(text, ascii)",
"CREATE FUNCTION " + fOverloaded + "(input text, other_in ascii)\n" +
" RETURNS NULL ON NULL INPUT\n" +
" RETURNS text\n" +
" LANGUAGE java\n" +
" AS $$return \"Hello World\";$$;"));
assertRowsNet(executeDescribeNet(describeKeyword + " FUNCTIONS"),
row(KEYSPACE,
"function",
shortFunctionName(fNonOverloaded) + "()"),
row(KEYSPACE,
"function",
shortFunctionName(fOverloaded) + "(int, ascii)"),
row(KEYSPACE,
"function",
shortFunctionName(fOverloaded) + "(text, ascii)"));
}
String fIntState = createFunction(KEYSPACE,
"int, int",
"CREATE FUNCTION %s (state int, add_to int) " +
"CALLED ON NULL INPUT " +
"RETURNS int " +
"LANGUAGE java " +
"AS 'return state + add_to;'");
String fFinal = createFunction(KEYSPACE,
"int",
"CREATE FUNCTION %s (state int) " +
"RETURNS NULL ON NULL INPUT " +
"RETURNS int " +
"LANGUAGE java " +
"AS 'return state;'");
String aNonDeterministic = createAggregate(KEYSPACE,
"int",
format("CREATE AGGREGATE %%s(int) " +
"SFUNC %s " +
"STYPE int " +
"INITCOND 42",
shortFunctionName(fIntState)));
String aDeterministic = createAggregate(KEYSPACE,
"int",
format("CREATE AGGREGATE %%s(int) " +
"SFUNC %s " +
"STYPE int " +
"FINALFUNC %s ",
shortFunctionName(fIntState),
shortFunctionName(fFinal)));
for (String describeKeyword : new String[]{"DESCRIBE", "DESC"})
{
assertRowsNet(executeDescribeNet(describeKeyword + " AGGREGATE " + aNonDeterministic),
row(KEYSPACE,
"aggregate",
shortFunctionName(aNonDeterministic) + "(int)",
"CREATE AGGREGATE " + aNonDeterministic + "(int)\n" +
" SFUNC " + shortFunctionName(fIntState) + "\n" +
" STYPE int\n" +
" INITCOND 42;"));
assertRowsNet(executeDescribeNet(describeKeyword + " AGGREGATE " + aDeterministic),
row(KEYSPACE,
"aggregate",
shortFunctionName(aDeterministic) + "(int)",
"CREATE AGGREGATE " + aDeterministic + "(int)\n" +
" SFUNC " + shortFunctionName(fIntState) + "\n" +
" STYPE int\n" +
" FINALFUNC " + shortFunctionName(fFinal) + ";"));
assertRowsNet(executeDescribeNet(describeKeyword + " AGGREGATES"),
row(KEYSPACE,
"aggregate",
shortFunctionName(aNonDeterministic) + "(int)"),
row(KEYSPACE,
"aggregate",
shortFunctionName(aDeterministic) + "(int)"));
}
}
@Test
public void testDescribeFunctionWithTuples() throws Throwable
{
String function = createFunction(KEYSPACE,
"tuple<int>, list<frozen<tuple<int, text>>>, tuple<frozen<tuple<int, text>>, text>",
"CREATE OR REPLACE FUNCTION %s(t tuple<int>, l list<frozen<tuple<int, text>>>, nt tuple<frozen<tuple<int, text>>, text>) " +
"CALLED ON NULL INPUT " +
"RETURNS tuple<int, text> " +
"LANGUAGE java " +
"AS 'throw new RuntimeException();';");
assertRowsNet(executeDescribeNet("DESCRIBE FUNCTION " + function),
row(KEYSPACE,
"function",
shortFunctionName(function) + "(tuple<int>, list<frozen<tuple<int, text>>>, tuple<frozen<tuple<int, text>>, text>)",
"CREATE FUNCTION " + function + "(t tuple<int>, l list<frozen<tuple<int, text>>>, nt tuple<frozen<tuple<int, text>>, text>)\n" +
" CALLED ON NULL INPUT\n" +
" RETURNS tuple<int, text>\n" +
" LANGUAGE java\n" +
" AS $$throw new RuntimeException();$$;"));
}
@Test
public void testDescribeMaterializedView() throws Throwable
{
assertRowsNet(executeDescribeNet("DESCRIBE ONLY KEYSPACE system_virtual_schema;"),
row("system_virtual_schema",
"keyspace",
"system_virtual_schema",
"/*\n" +
"Warning: Keyspace system_virtual_schema is a virtual keyspace and cannot be recreated with CQL.\n" +
"Structure, for reference:\n" +
"VIRTUAL KEYSPACE system_virtual_schema;\n" +
"*/"));
assertRowsNet(executeDescribeNet("DESCRIBE TABLE system_virtual_schema.columns;"),
row("system_virtual_schema",
"table",
"columns",
"/*\n" +
"Warning: Table system_virtual_schema.columns is a virtual table and cannot be recreated with CQL.\n" +
"Structure, for reference:\n" +
"VIRTUAL TABLE system_virtual_schema.columns (\n" +
" keyspace_name text,\n" +
" table_name text,\n" +
" column_name text,\n" +
" clustering_order text,\n" +
" column_name_bytes blob,\n" +
" kind text,\n" +
" position int,\n" +
" type text,\n" +
" 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" +
"*/"));
}
@Test
public void testDescribe() throws Throwable
{
try
{
execute("CREATE KEYSPACE test WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1};");
execute("CREATE TABLE test.users ( userid text PRIMARY KEY, firstname text, lastname text, age int);");
execute("CREATE INDEX myindex ON test.users (age);");
execute("CREATE TABLE test.\"Test\" (id int, col int, val text, PRIMARY KEY(id, col));");
execute("CREATE INDEX ON test.\"Test\" (col);");
execute("CREATE INDEX ON test.\"Test\" (val)");
execute("CREATE TABLE test.users_mv (username varchar, password varchar, gender varchar, session_token varchar, " +
"state varchar, birth_year bigint, PRIMARY KEY (username));");
execute("CREATE MATERIALIZED VIEW test.users_by_state AS SELECT * FROM test.users_mv " +
"WHERE STATE IS NOT NULL AND username IS NOT NULL PRIMARY KEY (state, username)");
execute(allTypesTable());
// Test describe schema
Object[][] testSchemaOutput = rows(
row(KEYSPACE, "keyspace", KEYSPACE,
"CREATE KEYSPACE " + KEYSPACE +
" WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}" +
" AND durable_writes = true;"),
row(KEYSPACE_PER_TEST, "keyspace", KEYSPACE_PER_TEST,
"CREATE KEYSPACE " + KEYSPACE_PER_TEST +
" WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}" +
" AND durable_writes = true;"),
row("test", "keyspace", "test", keyspaceOutput()),
row("test", "table", "has_all_types", allTypesTable()),
row("test", "table", "\"Test\"", testTableOutput()),
row("test", "index", "\"Test_col_idx\"", indexOutput("\"Test_col_idx\"", "\"Test\"", "col")),
row("test", "index", "\"Test_val_idx\"", indexOutput("\"Test_val_idx\"", "\"Test\"", "val")),
row("test", "table", "users", userTableOutput()),
row("test", "index", "myindex", indexOutput("myindex", "users", "age")),
row("test", "table", "users_mv", usersMvTableOutput()),
row("test", "materialized_view", "users_by_state", usersByStateMvOutput()));
assertRowsNet(executeDescribeNet("DESCRIBE SCHEMA"), testSchemaOutput);
assertRowsNet(executeDescribeNet("DESC SCHEMA"), testSchemaOutput);
// Test describe keyspaces/keyspace
Object[][] testKeyspacesOutput = rows(row(KEYSPACE, "keyspace", KEYSPACE),
row(KEYSPACE_PER_TEST, "keyspace", KEYSPACE_PER_TEST),
row(SYSTEM_KEYSPACE_NAME, "keyspace", SYSTEM_KEYSPACE_NAME),
row(AUTH_KEYSPACE_NAME, "keyspace", AUTH_KEYSPACE_NAME),
row(DISTRIBUTED_KEYSPACE_NAME, "keyspace", DISTRIBUTED_KEYSPACE_NAME),
row(SCHEMA_KEYSPACE_NAME, "keyspace", SCHEMA_KEYSPACE_NAME),
row(TRACE_KEYSPACE_NAME, "keyspace", TRACE_KEYSPACE_NAME),
row(VIRTUAL_SCHEMA, "keyspace", VIRTUAL_SCHEMA),
row("test", "keyspace", "test"));
for (String describeKeyword : new String[]{"DESCRIBE", "DESC"})
{
assertRowsNet(executeDescribeNet(describeKeyword + " KEYSPACES"), testKeyspacesOutput);
assertRowsNet(executeDescribeNet("test", describeKeyword + " KEYSPACES"), testKeyspacesOutput);
assertRowsNet(executeDescribeNet(describeKeyword + " ONLY KEYSPACE test"),
row("test", "keyspace", "test", keyspaceOutput()));
}
Object[][] testKeyspaceOutput = rows(row("test", "keyspace", "test", keyspaceOutput()),
row("test", "table", "has_all_types", allTypesTable()),
row("test", "table", "\"Test\"", testTableOutput()),
row("test", "index", "\"Test_col_idx\"", indexOutput("\"Test_col_idx\"", "\"Test\"", "col")),
row("test", "index", "\"Test_val_idx\"", indexOutput("\"Test_val_idx\"", "\"Test\"", "val")),
row("test", "table", "users", userTableOutput()),
row("test", "index", "myindex", indexOutput("myindex", "users", "age")),
row("test", "table", "users_mv", usersMvTableOutput()),
row("test", "materialized_view", "users_by_state", usersByStateMvOutput()));
for (String describeKeyword : new String[]{"DESCRIBE", "DESC"})
{
assertRowsNet(executeDescribeNet(describeKeyword + " KEYSPACE test"), testKeyspaceOutput);
assertRowsNet(executeDescribeNet(describeKeyword + " test"), testKeyspaceOutput);
describeError(describeKeyword + " test2", "'test2' not found in keyspaces");
}
// Test describe tables/table
for (String cmd : new String[]{"describe TABLES", "DESC tables"})
assertRowsNet(executeDescribeNet("test", cmd),
row("test", "table", "has_all_types"),
row("test", "table", "\"Test\""),
row("test", "table", "users"),
row("test", "table", "users_mv"));
testDescribeTable("test", "has_all_types", row("test", "table", "has_all_types", allTypesTable()));
testDescribeTable("test", "\"Test\"",
row("test", "table", "\"Test\"", testTableOutput()),
row("test", "index", "\"Test_col_idx\"", indexOutput("\"Test_col_idx\"", "\"Test\"", "col")),
row("test", "index", "\"Test_val_idx\"", indexOutput("\"Test_val_idx\"", "\"Test\"", "val")));
testDescribeTable("test", "users", row("test", "table", "users", userTableOutput()),
row("test", "index", "myindex", indexOutput("myindex", "users", "age")));
describeError("test", "DESCRIBE users2", "'users2' not found in keyspace 'test'");
describeError("DESCRIBE test.users2", "'users2' not found in keyspace 'test'");
// Test describe index
testDescribeIndex("test", "myindex", row("test", "index", "myindex", indexOutput("myindex", "users", "age")));
testDescribeIndex("test", "\"Test_col_idx\"", row("test", "index", "\"Test_col_idx\"", indexOutput("\"Test_col_idx\"", "\"Test\"", "col")));
testDescribeIndex("test", "\"Test_val_idx\"", row("test", "index", "\"Test_val_idx\"", indexOutput("\"Test_val_idx\"", "\"Test\"", "val")));
describeError("DESCRIBE test.myindex2", "'myindex2' not found in keyspace 'test'");
describeError("test", "DESCRIBE myindex2", "'myindex2' not found in keyspace 'test'");
// Test describe materialized view
testDescribeMaterializedView("test", "users_by_state", row("test", "materialized_view", "users_by_state", usersByStateMvOutput()));
}
finally
{
execute("DROP KEYSPACE IF EXISTS test");
}
}
private void testDescribeTable(String keyspace, String table, Object[]... rows) throws Throwable
{
for (String describeKeyword : new String[]{"describe", "desc"})
{
for (String cmd : new String[]{describeKeyword + " table " + keyspace + "." + table,
describeKeyword + " columnfamily " + keyspace + "." + table,
describeKeyword + " " + keyspace + "." + table})
{
assertRowsNet(executeDescribeNet(cmd), rows);
}
for (String cmd : new String[]{describeKeyword + " table " + table,
describeKeyword + " columnfamily " + table,
describeKeyword + " " + table})
{
assertRowsNet(executeDescribeNet(keyspace, cmd), rows);
}
}
}
private void testDescribeIndex(String keyspace, String index, Object[]... rows) throws Throwable
{
for (String describeKeyword : new String[]{"describe", "desc"})
{
for (String cmd : new String[]{describeKeyword + " index " + keyspace + "." + index,
describeKeyword + " " + keyspace + "." + index})
{
assertRowsNet(executeDescribeNet(cmd), rows);
}
for (String cmd : new String[]{describeKeyword + " index " + index,
describeKeyword + " " + index})
{
assertRowsNet(executeDescribeNet(keyspace, cmd), rows);
}
}
}
private void testDescribeMaterializedView(String keyspace, String view, Object[]... rows) throws Throwable
{
for (String describeKeyword : new String[]{"describe", "desc"})
{
for (String cmd : new String[]{describeKeyword + " materialized view " + keyspace + "." + view,
describeKeyword + " " + keyspace + "." + view})
{
assertRowsNet(executeDescribeNet(cmd), rows);
}
for (String cmd : new String[]{describeKeyword + " materialized view " + view,
describeKeyword + " " + view})
{
assertRowsNet(executeDescribeNet(keyspace, cmd), rows);
}
}
}
@Test
public void testDescribeCluster() throws Throwable
{
for (String describeKeyword : new String[]{"DESCRIBE", "DESC"})
{
assertRowsNet(executeDescribeNet(describeKeyword + " CLUSTER"),
row("Test Cluster",
"ByteOrderedPartitioner",
DatabaseDescriptor.getEndpointSnitch().getClass().getName()));
assertRowsNet(executeDescribeNet("system_virtual_schema", describeKeyword + " CLUSTER"),
row("Test Cluster",
"ByteOrderedPartitioner",
DatabaseDescriptor.getEndpointSnitch().getClass().getName()));
}
TokenMetadata tokenMetadata = StorageService.instance.getTokenMetadata();
Token token = tokenMetadata.sortedTokens().get(0);
InetAddressAndPort addressAndPort = tokenMetadata.getAllEndpoints().iterator().next();
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE CLUSTER"),
row("Test Cluster",
"ByteOrderedPartitioner",
DatabaseDescriptor.getEndpointSnitch().getClass().getName(),
ImmutableMap.of(token.toString(), ImmutableList.of(addressAndPort.toString()))));
}
@Test
public void testDescribeTableWithInternals() throws Throwable
{
String table = createTable("CREATE TABLE %s (pk1 text, pk2 int, c int, s decimal static, v1 text, v2 int, v3 int, PRIMARY KEY ((pk1, pk2), c ))");
TableId id = Schema.instance.getTableMetadata(KEYSPACE, table).id;
String tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
" pk1 text,\n" +
" pk2 int,\n" +
" c int,\n" +
" s decimal static,\n" +
" v1 text,\n" +
" v2 int,\n" +
" v3 int,\n" +
" PRIMARY KEY ((pk1, pk2), c)\n" +
") WITH ID = " + id + "\n" +
" AND CLUSTERING ORDER BY (c ASC)\n" +
" AND " + tableParametersCql();
assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE + "." + table + " WITH INTERNALS"),
row(KEYSPACE,
"table",
table,
tableCreateStatement));
String dropStatement = "ALTER TABLE " + KEYSPACE + "." + table + " DROP v3 USING TIMESTAMP 1589286942065000;";
execute(dropStatement);
assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE + "." + table + " WITH INTERNALS"),
row(KEYSPACE,
"table",
table,
tableCreateStatement + "\n" +
dropStatement));
String addStatement = "ALTER TABLE " + KEYSPACE + "." + table + " ADD v3 int;";
execute(addStatement);
assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE + "." + table + " WITH INTERNALS"),
row(KEYSPACE,
"table",
table,
tableCreateStatement + "\n" +
dropStatement + "\n" +
addStatement));
}
@Test
public void testPrimaryKeyPositionWithAndWithoutInternals() throws Throwable
{
String table = createTable("CREATE TABLE %s (pk text, v1 text, v2 int, v3 int, PRIMARY KEY (pk))");
TableId id = Schema.instance.getTableMetadata(KEYSPACE, table).id;
String tableCreateStatement = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
" pk text PRIMARY KEY,\n" +
" v1 text,\n" +
" v2 int,\n" +
" v3 int\n" +
") WITH ID = " + id + "\n" +
" AND " + tableParametersCql();
assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE + "." + table + " WITH INTERNALS"),
row(KEYSPACE,
"table",
table,
tableCreateStatement));
String dropStatement = "ALTER TABLE " + KEYSPACE + "." + table + " DROP v3 USING TIMESTAMP 1589286942065000;";
execute(dropStatement);
assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE + "." + table + " WITH INTERNALS"),
row(KEYSPACE,
"table",
table,
tableCreateStatement + "\n" +
dropStatement));
String tableCreateStatementWithoutDroppedColumn = "CREATE TABLE " + KEYSPACE + "." + table + " (\n" +
" pk text PRIMARY KEY,\n" +
" v1 text,\n" +
" v2 int\n" +
") WITH " + tableParametersCql();
assertRowsNet(executeDescribeNet("DESCRIBE TABLE " + KEYSPACE + "." + table),
row(KEYSPACE,
"table",
table,
tableCreateStatementWithoutDroppedColumn));
}
@Test
public void testDescribeMissingKeyspace() throws Throwable
{
describeError("DESCRIBE TABLE foop",
"No keyspace specified and no current keyspace");
describeError("DESCRIBE MATERIALIZED VIEW foop",
"No keyspace specified and no current keyspace");
describeError("DESCRIBE INDEX foop",
"No keyspace specified and no current keyspace");
describeError("DESCRIBE TYPE foop",
"No keyspace specified and no current keyspace");
describeError("DESCRIBE FUNCTION foop",
"No keyspace specified and no current keyspace");
describeError("DESCRIBE AGGREGATE foop",
"No keyspace specified and no current keyspace");
}
@Test
public void testDescribeNotFound() throws Throwable
{
describeError(format("DESCRIBE AGGREGATE %s.%s", KEYSPACE, "aggr_foo"),
format("User defined aggregate '%s' not found in '%s'", "aggr_foo", KEYSPACE));
describeError(format("DESCRIBE FUNCTION %s.%s", KEYSPACE, "func_foo"),
format("User defined function '%s' not found in '%s'", "func_foo", KEYSPACE));
describeError(format("DESCRIBE %s.%s", KEYSPACE, "func_foo"),
format("'%s' not found in keyspace '%s'", "func_foo", KEYSPACE));
describeError(format("DESCRIBE %s", "foo"),
format("'%s' not found in keyspaces", "foo"));
}
@Test
public void testDescribeTypes() throws Throwable
{
String type1 = createType("CREATE TYPE %s (a int)");
String type2 = createType("CREATE TYPE %s (x text, y text)");
String type3 = createType("CREATE TYPE %s (a text, b frozen<" + type2 + ">)");
execute("ALTER TYPE " + KEYSPACE + "." + type1 + " ADD b frozen<" + type3 + ">");
try
{
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TYPES"),
row(KEYSPACE, "type", type1),
row(KEYSPACE, "type", type2),
row(KEYSPACE, "type", type3));
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TYPE " + type2),
row(KEYSPACE, "type", type2, "CREATE TYPE " + KEYSPACE + "." + type2 + " (\n" +
" x text,\n" +
" y text\n" +
");"));
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE TYPE " + type1),
row(KEYSPACE, "type", type1, "CREATE TYPE " + KEYSPACE + "." + type1 + " (\n" +
" a int,\n" +
" b frozen<" + type3 + ">\n" +
");"));
assertRowsNet(executeDescribeNet(KEYSPACE, "DESCRIBE KEYSPACE " + KEYSPACE),
row(KEYSPACE, "keyspace", KEYSPACE, "CREATE KEYSPACE " + KEYSPACE +
" WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'}" +
" AND durable_writes = true;"),
row(KEYSPACE, "type", type2, "CREATE TYPE " + KEYSPACE + "." + type2 + " (\n" +
" x text,\n" +
" y text\n" +
");"),
row(KEYSPACE, "type", type3, "CREATE TYPE " + KEYSPACE + "." + type3 + " (\n" +
" a text,\n" +
" b frozen<" + type2 + ">\n" +
");"),
row(KEYSPACE, "type", type1, "CREATE TYPE " + KEYSPACE + "." + type1 + " (\n" +
" a int,\n" +
" b frozen<" + type3 + ">\n" +
");"));
}
finally
{
execute("DROP TYPE " + KEYSPACE + "." + type1);
execute("DROP TYPE " + KEYSPACE + "." + type3);
execute("DROP TYPE " + KEYSPACE + "." + type2);
}
}
/**
* Tests for the error reported in CASSANDRA-9064 by:
*
* - creating the table described in the bug report, using LCS,
* - DESCRIBE-ing that table via cqlsh, then DROPping it,
* - running the output of the DESCRIBE statement as a CREATE TABLE statement, and
* - inserting a value into the table.
*
* The final two steps of the test should not fall down. If one does, that
* indicates the output of DESCRIBE is not a correct CREATE TABLE statement.
*/
@Test
public void testDescribeRoundtrip() throws Throwable
{
for (String withInternals : new String[]{"", " WITH INTERNALS"})
{
String table = createTable("CREATE TABLE %s (key int PRIMARY KEY) WITH compaction = {'class': 'LeveledCompactionStrategy'}");
String output = executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + table + withInternals).all().get(0).getString("create_statement");
execute("DROP TABLE %s");
executeNet(output);
String output2 = executeDescribeNet(KEYSPACE, "DESCRIBE TABLE " + table + withInternals).all().get(0).getString("create_statement");
assertEquals(output, output2);
execute("INSERT INTO %s (key) VALUES (1)");
}
}
private static String allTypesTable()
{
return "CREATE TABLE test.has_all_types (\n" +
" num int PRIMARY KEY,\n" +
" asciicol ascii,\n" +
" bigintcol bigint,\n" +
" blobcol blob,\n" +
" booleancol boolean,\n" +
" decimalcol decimal,\n" +
" doublecol double,\n" +
" durationcol duration,\n" +
" floatcol float,\n" +
" frozenlistcol frozen<list<text>>,\n" +
" frozenmapcol frozen<map<timestamp, timeuuid>>,\n" +
" frozensetcol frozen<set<bigint>>,\n" +
" intcol int,\n" +
" smallintcol smallint,\n" +
" textcol text,\n" +
" timestampcol timestamp,\n" +
" tinyintcol tinyint,\n" +
" tuplecol frozen<tuple<text, int, frozen<tuple<timestamp>>>>,\n" +
" uuidcol uuid,\n" +
" varcharcol text,\n" +
" varintcol varint,\n" +
" listcol list<decimal>,\n" +
" mapcol map<timestamp, timeuuid>,\n" +
" setcol set<tinyint>\n" +
") WITH " + tableParametersCql();
}
private static String usersByStateMvOutput()
{
return "CREATE MATERIALIZED VIEW test.users_by_state AS\n" +
" SELECT *\n" +
" FROM test.users_mv\n" +
" WHERE state IS NOT NULL AND username IS NOT NULL\n" +
" PRIMARY KEY (state, username)\n" +
" WITH CLUSTERING ORDER BY (username ASC)\n" +
" AND " + tableParametersCql();
}
private static String indexOutput(String index, String table, String col)
{
return format("CREATE INDEX %s ON %s.%s (%s);", index, "test", table, col);
}
private static String usersMvTableOutput()
{
return "CREATE TABLE test.users_mv (\n" +
" username text PRIMARY KEY,\n" +
" birth_year bigint,\n" +
" gender text,\n" +
" password text,\n" +
" session_token text,\n" +
" state text\n" +
") WITH " + tableParametersCql();
}
private static String userTableOutput()
{
return "CREATE TABLE test.users (\n" +
" userid text PRIMARY KEY,\n" +
" age int,\n" +
" firstname text,\n" +
" lastname text\n" +
") WITH " + tableParametersCql();
}
private static String testTableOutput()
{
return "CREATE TABLE test.\"Test\" (\n" +
" id int,\n" +
" col int,\n" +
" val text,\n" +
" PRIMARY KEY (id, col)\n" +
") WITH CLUSTERING ORDER BY (col ASC)\n" +
" AND " + tableParametersCql();
}
private static String tableParametersCql()
{
return "additional_write_policy = '99p'\n" +
" 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 compaction = {'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}\n" +
" AND compression = {'chunk_length_in_kb': '16', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}\n" +
" AND crc_check_chance = 1.0\n" +
" AND default_time_to_live = 0\n" +
" AND extensions = {}\n" +
" AND gc_grace_seconds = 864000\n" +
" AND max_index_interval = 2048\n" +
" AND memtable_flush_period_in_ms = 0\n" +
" AND min_index_interval = 128\n" +
" AND read_repair = 'BLOCKING'\n" +
" AND speculative_retry = '99p';";
}
private static String keyspaceOutput()
{
return "CREATE KEYSPACE test WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND durable_writes = true;";
}
private void describeError(String cql, String msg) throws Throwable
{
describeError(null, cql, msg);
}
private void describeError(String useKs, String cql, String msg) throws Throwable
{
assertInvalidThrowMessage(Optional.of(getProtocolVersion(useKs)), msg, InvalidQueryException.class, cql, ArrayUtils.EMPTY_OBJECT_ARRAY);
}
private ResultSet executeDescribeNet(String cql) throws Throwable
{
return executeDescribeNet(null, cql);
}
private ResultSet executeDescribeNet(String useKs, String cql) throws Throwable
{
return executeNetWithPaging(getProtocolVersion(useKs), cql, 3);
}
private ProtocolVersion getProtocolVersion(String useKs) throws Throwable
{
// We're using a trick here to distinguish driver sessions with a "USE keyspace" and without:
// As different ProtocolVersions use different driver instances, we use different ProtocolVersions
// for the with and without "USE keyspace" cases.
ProtocolVersion v = useKs != null ? ProtocolVersion.CURRENT : ProtocolVersion.V5;
if (useKs != null)
executeNet(v, "USE " + useKs);
return v;
}
private static Object[][] rows(Object[]... rows)
{
return rows;
}
}

View File

@ -0,0 +1,468 @@
/*
* 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;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.apache.cassandra.*;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.index.sasi.SASIIndex;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.service.reads.SpeculativeRetryPolicy;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.cassandra.utils.FBUtilities;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import java.io.FileReader;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class SchemaCQLHelperTest extends CQLTester
{
@Before
public void defineSchema() throws ConfigurationException
{
SchemaLoader.prepareServer();
}
@Test
public void testUserTypesCQL()
{
String keyspace = "cql_test_keyspace_user_types";
String table = "test_table_user_types";
UserType typeA = new UserType(keyspace, ByteBufferUtil.bytes("a"),
Arrays.asList(FieldIdentifier.forUnquoted("a1"),
FieldIdentifier.forUnquoted("a2"),
FieldIdentifier.forUnquoted("a3")),
Arrays.asList(IntegerType.instance,
IntegerType.instance,
IntegerType.instance),
true);
UserType typeB = new UserType(keyspace, ByteBufferUtil.bytes("b"),
Arrays.asList(FieldIdentifier.forUnquoted("b1"),
FieldIdentifier.forUnquoted("b2"),
FieldIdentifier.forUnquoted("b3")),
Arrays.asList(typeA,
typeA,
typeA),
true);
UserType typeC = new UserType(keyspace, ByteBufferUtil.bytes("c"),
Arrays.asList(FieldIdentifier.forUnquoted("c1"),
FieldIdentifier.forUnquoted("c2"),
FieldIdentifier.forUnquoted("c3")),
Arrays.asList(typeB,
typeB,
typeB),
true);
TableMetadata cfm =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("ck1", IntegerType.instance)
.addRegularColumn("reg1", typeC.freeze())
.addRegularColumn("reg2", ListType.getInstance(IntegerType.instance, false))
.addRegularColumn("reg3", MapType.getInstance(AsciiType.instance, IntegerType.instance, true))
.build();
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), Tables.of(cfm), Types.of(typeA, typeB, typeC));
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertEquals(ImmutableList.of("CREATE TYPE cql_test_keyspace_user_types.a (\n" +
" a1 varint,\n" +
" a2 varint,\n" +
" a3 varint\n" +
");",
"CREATE TYPE cql_test_keyspace_user_types.b (\n" +
" b1 a,\n" +
" b2 a,\n" +
" b3 a\n" +
");",
"CREATE TYPE cql_test_keyspace_user_types.c (\n" +
" c1 b,\n" +
" c2 b,\n" +
" c3 b\n" +
");"),
SchemaCQLHelper.getUserTypesAsCQL(cfs.metadata(), cfs.keyspace.getMetadata().types).collect(Collectors.toList()));
}
@Test
public void testDroppedColumnsCQL()
{
String keyspace = "cql_test_keyspace_dropped_columns";
String table = "test_table_dropped_columns";
TableMetadata.Builder builder =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("ck1", IntegerType.instance)
.addStaticColumn("st1", IntegerType.instance)
.addRegularColumn("reg1", IntegerType.instance)
.addRegularColumn("reg2", IntegerType.instance)
.addRegularColumn("reg3", IntegerType.instance);
ColumnMetadata st1 = builder.getColumn(ByteBufferUtil.bytes("st1"));
ColumnMetadata reg1 = builder.getColumn(ByteBufferUtil.bytes("reg1"));
ColumnMetadata reg2 = builder.getColumn(ByteBufferUtil.bytes("reg2"));
ColumnMetadata reg3 = builder.getColumn(ByteBufferUtil.bytes("reg3"));
builder.removeRegularOrStaticColumn(st1.name)
.removeRegularOrStaticColumn(reg1.name)
.removeRegularOrStaticColumn(reg2.name)
.removeRegularOrStaticColumn(reg3.name);
builder.recordColumnDrop(st1, 5000)
.recordColumnDrop(reg1, 10000)
.recordColumnDrop(reg2, 20000)
.recordColumnDrop(reg3, 30000);
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
String expected = "CREATE TABLE IF NOT EXISTS cql_test_keyspace_dropped_columns.test_table_dropped_columns (\n" +
" pk1 varint,\n" +
" ck1 varint,\n" +
" reg1 varint,\n" +
" reg3 varint,\n" +
" reg2 varint,\n" +
" st1 varint static,\n" +
" PRIMARY KEY (pk1, ck1)\n) WITH ID =";
String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true, true, true);
assertThat(actual,
allOf(startsWith(expected),
containsString("ALTER TABLE cql_test_keyspace_dropped_columns.test_table_dropped_columns DROP reg1 USING TIMESTAMP 10000;"),
containsString("ALTER TABLE cql_test_keyspace_dropped_columns.test_table_dropped_columns DROP reg3 USING TIMESTAMP 30000;"),
containsString("ALTER TABLE cql_test_keyspace_dropped_columns.test_table_dropped_columns DROP reg2 USING TIMESTAMP 20000;"),
containsString("ALTER TABLE cql_test_keyspace_dropped_columns.test_table_dropped_columns DROP st1 USING TIMESTAMP 5000;")));
}
@Test
public void testReaddedColumns()
{
String keyspace = "cql_test_keyspace_readded_columns";
String table = "test_table_readded_columns";
TableMetadata.Builder builder =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("ck1", IntegerType.instance)
.addRegularColumn("reg1", IntegerType.instance)
.addStaticColumn("st1", IntegerType.instance)
.addRegularColumn("reg2", IntegerType.instance);
ColumnMetadata reg1 = builder.getColumn(ByteBufferUtil.bytes("reg1"));
ColumnMetadata st1 = builder.getColumn(ByteBufferUtil.bytes("st1"));
builder.removeRegularOrStaticColumn(reg1.name);
builder.removeRegularOrStaticColumn(st1.name);
builder.recordColumnDrop(reg1, 10000);
builder.recordColumnDrop(st1, 20000);
builder.addColumn(reg1);
builder.addColumn(st1);
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
// when re-adding, column is present as both column and as dropped column record.
String actual = SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true, true, true);
String expected = "CREATE TABLE IF NOT EXISTS cql_test_keyspace_readded_columns.test_table_readded_columns (\n" +
" pk1 varint,\n" +
" ck1 varint,\n" +
" reg2 varint,\n" +
" reg1 varint,\n" +
" st1 varint static,\n" +
" PRIMARY KEY (pk1, ck1)\n" +
") WITH ID";
assertThat(actual,
allOf(startsWith(expected),
containsString("ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns DROP reg1 USING TIMESTAMP 10000;"),
containsString("ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns ADD reg1 varint;"),
containsString("ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns DROP st1 USING TIMESTAMP 20000;"),
containsString("ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns ADD st1 varint static;")));
}
@Test
public void testCfmColumnsCQL()
{
String keyspace = "cql_test_keyspace_create_table";
String table = "test_table_create_table";
TableMetadata.Builder metadata =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addPartitionKeyColumn("pk2", AsciiType.instance)
.addClusteringColumn("ck1", ReversedType.getInstance(IntegerType.instance))
.addClusteringColumn("ck2", IntegerType.instance)
.addStaticColumn("st1", AsciiType.instance)
.addRegularColumn("reg1", AsciiType.instance)
.addRegularColumn("reg2", ListType.getInstance(IntegerType.instance, false))
.addRegularColumn("reg3", MapType.getInstance(AsciiType.instance, IntegerType.instance, true));
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), metadata);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertThat(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true, true, true),
startsWith(
"CREATE TABLE IF NOT EXISTS cql_test_keyspace_create_table.test_table_create_table (\n" +
" pk1 varint,\n" +
" pk2 ascii,\n" +
" ck1 varint,\n" +
" ck2 varint,\n" +
" st1 ascii static,\n" +
" reg1 ascii,\n" +
" reg2 frozen<list<varint>>,\n" +
" reg3 map<ascii, varint>,\n" +
" PRIMARY KEY ((pk1, pk2), ck1, ck2)\n" +
") WITH ID = " + cfs.metadata.id + "\n" +
" AND CLUSTERING ORDER BY (ck1 DESC, ck2 ASC)"));
}
@Test
public void testCfmOptionsCQL()
{
String keyspace = "cql_test_keyspace_options";
String table = "test_table_options";
TableMetadata.Builder builder = TableMetadata.builder(keyspace, table);
long droppedTimestamp = FBUtilities.timestampMicros();
builder.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("cl1", IntegerType.instance)
.addRegularColumn("reg1", AsciiType.instance)
.bloomFilterFpChance(1.0)
.comment("comment")
.compaction(CompactionParams.lcs(Collections.singletonMap("sstable_size_in_mb", "1")))
.compression(CompressionParams.lz4(1 << 16, 1 << 15))
.crcCheckChance(0.3)
.defaultTimeToLive(4)
.gcGraceSeconds(5)
.minIndexInterval(6)
.maxIndexInterval(7)
.memtableFlushPeriod(8)
.speculativeRetry(SpeculativeRetryPolicy.fromString("always"))
.additionalWritePolicy(SpeculativeRetryPolicy.fromString("always"))
.extensions(ImmutableMap.of("ext1", ByteBuffer.wrap("val1".getBytes())))
.recordColumnDrop(ColumnMetadata.regularColumn(keyspace, table, "reg1", AsciiType.instance),
droppedTimestamp);
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertThat(SchemaCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true, true, true),
containsString("CLUSTERING ORDER BY (cl1 ASC)\n" +
" AND additional_write_policy = 'ALWAYS'\n" +
" AND bloom_filter_fp_chance = 1.0\n" +
" AND caching = {'keys': 'ALL', 'rows_per_partition': 'NONE'}\n" +
" AND cdc = false\n" +
" AND comment = 'comment'\n" +
" AND compaction = {'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4', 'sstable_size_in_mb': '1'}\n" +
" AND compression = {'chunk_length_in_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor', 'min_compress_ratio': '2.0'}\n" +
" AND crc_check_chance = 0.3\n" +
" AND default_time_to_live = 4\n" +
" AND extensions = {'ext1': 0x76616c31}\n" +
" AND gc_grace_seconds = 5\n" +
" AND max_index_interval = 7\n" +
" AND memtable_flush_period_in_ms = 8\n" +
" AND min_index_interval = 6\n" +
" AND read_repair = 'BLOCKING'\n" +
" AND speculative_retry = 'ALWAYS';"
));
}
@Test
public void testCfmIndexJson()
{
String keyspace = "cql_test_keyspace_3";
String table = "test_table_3";
TableMetadata.Builder builder =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("cl1", IntegerType.instance)
.addRegularColumn("reg1", AsciiType.instance);
ColumnIdentifier reg1 = ColumnIdentifier.getInterned("reg1", true);
builder.indexes(
Indexes.of(IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.VALUES)),
"indexName",
IndexMetadata.Kind.COMPOSITES,
Collections.emptyMap()),
IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.KEYS)),
"indexName2",
IndexMetadata.Kind.COMPOSITES,
Collections.emptyMap()),
IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.KEYS_AND_VALUES)),
"indexName3",
IndexMetadata.Kind.COMPOSITES,
Collections.emptyMap()),
IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.KEYS_AND_VALUES)),
"indexName4",
IndexMetadata.Kind.CUSTOM,
Collections.singletonMap(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName()))));
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertEquals(ImmutableList.of("CREATE INDEX \"indexName\" ON cql_test_keyspace_3.test_table_3 (values(reg1));",
"CREATE INDEX \"indexName2\" ON cql_test_keyspace_3.test_table_3 (keys(reg1));",
"CREATE INDEX \"indexName3\" ON cql_test_keyspace_3.test_table_3 (entries(reg1));",
"CREATE CUSTOM INDEX \"indexName4\" ON cql_test_keyspace_3.test_table_3 (entries(reg1)) USING 'org.apache.cassandra.index.sasi.SASIIndex';"),
SchemaCQLHelper.getIndexesAsCQL(cfs.metadata()).collect(Collectors.toList()));
}
private final static String SNAPSHOT = "testsnapshot";
@Test
public void testSnapshot() throws Throwable
{
String typeA = createType("CREATE TYPE %s (a1 varint, a2 varint, a3 varint);");
String typeB = createType("CREATE TYPE %s (b1 frozen<" + typeA + ">, b2 frozen<" + typeA + ">, b3 frozen<" + typeA + ">);");
String typeC = createType("CREATE TYPE %s (c1 frozen<" + typeB + ">, c2 frozen<" + typeB + ">, c3 frozen<" + typeB + ">);");
String tableName = createTable("CREATE TABLE IF NOT EXISTS %s (" +
"pk1 varint," +
"pk2 ascii," +
"ck1 varint," +
"ck2 varint," +
"reg1 " + typeC + "," +
"reg2 int," +
"reg3 int," +
"PRIMARY KEY ((pk1, pk2), ck1, ck2)) WITH " +
"CLUSTERING ORDER BY (ck1 ASC, ck2 DESC);");
alterTable("ALTER TABLE %s DROP reg3 USING TIMESTAMP 10000;");
alterTable("ALTER TABLE %s ADD reg3 int;");
for (int i = 0; i < 10; i++)
execute("INSERT INTO %s (pk1, pk2, ck1, ck2, reg1, reg2) VALUES (?, ?, ?, ?, ?, ?)", i, i + 1, i + 2, i + 3, null, i + 5);
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName);
cfs.snapshot(SNAPSHOT);
String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT), Charset.defaultCharset());
assertThat(schema,
allOf(containsString(String.format("CREATE TYPE %s.%s (\n" +
" a1 varint,\n" +
" a2 varint,\n" +
" a3 varint\n" +
");", keyspace(), typeA)),
containsString(String.format("CREATE TYPE %s.%s (\n" +
" a1 varint,\n" +
" a2 varint,\n" +
" a3 varint\n" +
");", keyspace(), typeA)),
containsString(String.format("CREATE TYPE %s.%s (\n" +
" b1 frozen<%s>,\n" +
" b2 frozen<%s>,\n" +
" b3 frozen<%s>\n" +
");", keyspace(), typeB, typeA, typeA, typeA)),
containsString(String.format("CREATE TYPE %s.%s (\n" +
" c1 frozen<%s>,\n" +
" c2 frozen<%s>,\n" +
" c3 frozen<%s>\n" +
");", keyspace(), typeC, typeB, typeB, typeB))));
schema = schema.substring(schema.indexOf("CREATE TABLE")); // trim to ensure order
String expected = "CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" +
" pk1 varint,\n" +
" pk2 ascii,\n" +
" ck1 varint,\n" +
" ck2 varint,\n" +
" reg2 int,\n" +
" reg1 " + typeC+ ",\n" +
" reg3 int,\n" +
" PRIMARY KEY ((pk1, pk2), ck1, ck2)\n" +
") WITH ID = " + cfs.metadata.id + "\n" +
" AND CLUSTERING ORDER BY (ck1 ASC, ck2 DESC)";
assertThat(schema,
allOf(startsWith(expected),
containsString("ALTER TABLE " + keyspace() + "." + tableName + " DROP reg3 USING TIMESTAMP 10000;"),
containsString("ALTER TABLE " + keyspace() + "." + tableName + " ADD reg3 int;")));
JSONObject manifest = (JSONObject) new JSONParser().parse(new FileReader(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT)));
JSONArray files = (JSONArray) manifest.get("files");
Assert.assertEquals(1, files.size());
}
@Test
public void testSystemKsSnapshot()
{
ColumnFamilyStore cfs = Keyspace.open("system").getColumnFamilyStore("peers");
cfs.snapshot(SNAPSHOT);
Assert.assertTrue(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT).exists());
Assert.assertFalse(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).exists());
}
@Test
public void testBooleanCompositeKey() throws Throwable
{
createTable("CREATE TABLE %s (t_id boolean, id boolean, ck boolean, nk boolean, PRIMARY KEY ((t_id, id), ck))");
execute("insert into %s (t_id, id, ck, nk) VALUES (true, false, false, true)");
assertRows(execute("select * from %s"), row(true, false, false, true));
// CASSANDRA-14752 -
// a problem with composite boolean types meant that calling this would
// prevent any boolean values to be inserted afterwards
ColumnFamilyStore cfs = getCurrentColumnFamilyStore();
cfs.getSSTablesForKey("false:true");
execute("insert into %s (t_id, id, ck, nk) VALUES (true, true, false, true)");
assertRows(execute("select t_id, id, ck, nk from %s"), row(true, false, false, true), row(true, true, false, true));
}
}

View File

@ -1,446 +0,0 @@
/*
* 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;
import java.io.FileReader;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import org.apache.cassandra.service.reads.NeverSpeculativeRetryPolicy;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.apache.cassandra.*;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.statements.schema.IndexTarget;
import org.apache.cassandra.db.marshal.*;
import org.apache.cassandra.exceptions.*;
import org.apache.cassandra.index.sasi.*;
import org.apache.cassandra.schema.*;
import org.apache.cassandra.service.reads.AlwaysSpeculativeRetryPolicy;
import org.apache.cassandra.utils.*;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TableCQLHelperTest extends CQLTester
{
@Before
public void defineSchema() throws ConfigurationException
{
SchemaLoader.prepareServer();
}
@Test
public void testUserTypesCQL()
{
String keyspace = "cql_test_keyspace_user_types";
String table = "test_table_user_types";
UserType typeA = new UserType(keyspace, ByteBufferUtil.bytes("a"),
Arrays.asList(FieldIdentifier.forUnquoted("a1"),
FieldIdentifier.forUnquoted("a2"),
FieldIdentifier.forUnquoted("a3")),
Arrays.asList(IntegerType.instance,
IntegerType.instance,
IntegerType.instance),
true);
UserType typeB = new UserType(keyspace, ByteBufferUtil.bytes("b"),
Arrays.asList(FieldIdentifier.forUnquoted("b1"),
FieldIdentifier.forUnquoted("b2"),
FieldIdentifier.forUnquoted("b3")),
Arrays.asList(typeA,
typeA,
typeA),
true);
UserType typeC = new UserType(keyspace, ByteBufferUtil.bytes("c"),
Arrays.asList(FieldIdentifier.forUnquoted("c1"),
FieldIdentifier.forUnquoted("c2"),
FieldIdentifier.forUnquoted("c3")),
Arrays.asList(typeB,
typeB,
typeB),
true);
TableMetadata cfm =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("ck1", IntegerType.instance)
.addRegularColumn("reg1", typeC)
.addRegularColumn("reg2", ListType.getInstance(IntegerType.instance, false))
.addRegularColumn("reg3", MapType.getInstance(AsciiType.instance, IntegerType.instance, true))
.build();
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), Tables.of(cfm), Types.of(typeA, typeB, typeC));
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertEquals(ImmutableList.of("CREATE TYPE cql_test_keyspace_user_types.a (a1 varint, a2 varint, a3 varint);",
"CREATE TYPE cql_test_keyspace_user_types.b (b1 a, b2 a, b3 a);",
"CREATE TYPE cql_test_keyspace_user_types.c (c1 b, c2 b, c3 b);"),
TableCQLHelper.getUserTypesAsCQL(cfs.metadata()));
}
@Test
public void testDroppedColumnsCQL()
{
String keyspace = "cql_test_keyspace_dropped_columns";
String table = "test_table_dropped_columns";
TableMetadata.Builder builder =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("ck1", IntegerType.instance)
.addRegularColumn("reg1", IntegerType.instance)
.addRegularColumn("reg2", IntegerType.instance)
.addRegularColumn("reg3", IntegerType.instance);
ColumnMetadata reg1 = builder.getColumn(ByteBufferUtil.bytes("reg1"));
ColumnMetadata reg2 = builder.getColumn(ByteBufferUtil.bytes("reg2"));
ColumnMetadata reg3 = builder.getColumn(ByteBufferUtil.bytes("reg3"));
builder.removeRegularOrStaticColumn(reg1.name)
.removeRegularOrStaticColumn(reg2.name)
.removeRegularOrStaticColumn(reg3.name);
builder.recordColumnDrop(reg1, 10000)
.recordColumnDrop(reg2, 20000)
.recordColumnDrop(reg3, 30000);
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertEquals(ImmutableList.of("ALTER TABLE cql_test_keyspace_dropped_columns.test_table_dropped_columns DROP reg1 USING TIMESTAMP 10000;",
"ALTER TABLE cql_test_keyspace_dropped_columns.test_table_dropped_columns DROP reg3 USING TIMESTAMP 30000;",
"ALTER TABLE cql_test_keyspace_dropped_columns.test_table_dropped_columns DROP reg2 USING TIMESTAMP 20000;"),
TableCQLHelper.getDroppedColumnsAsCQL(cfs.metadata()));
assertTrue(TableCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true).startsWith(
"CREATE TABLE IF NOT EXISTS cql_test_keyspace_dropped_columns.test_table_dropped_columns (\n" +
"\tpk1 varint,\n" +
"\tck1 varint,\n" +
"\treg1 varint,\n" +
"\treg3 varint,\n" +
"\treg2 varint,\n" +
"\tPRIMARY KEY (pk1, ck1))"));
}
@Test
public void testReaddedColumns()
{
String keyspace = "cql_test_keyspace_readded_columns";
String table = "test_table_readded_columns";
TableMetadata.Builder builder =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("ck1", IntegerType.instance)
.addRegularColumn("reg1", IntegerType.instance)
.addStaticColumn("reg2", IntegerType.instance)
.addRegularColumn("reg3", IntegerType.instance);
ColumnMetadata reg1 = builder.getColumn(ByteBufferUtil.bytes("reg1"));
ColumnMetadata reg2 = builder.getColumn(ByteBufferUtil.bytes("reg2"));
builder.removeRegularOrStaticColumn(reg1.name);
builder.removeRegularOrStaticColumn(reg2.name);
builder.recordColumnDrop(reg1, 10000);
builder.recordColumnDrop(reg2, 20000);
builder.addColumn(reg1);
builder.addColumn(reg2);
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
// when re-adding, column is present in CREATE, then in DROP and then in ADD again, to record DROP with a proper timestamp
assertTrue(TableCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true).startsWith(
"CREATE TABLE IF NOT EXISTS cql_test_keyspace_readded_columns.test_table_readded_columns (\n" +
"\tpk1 varint,\n" +
"\tck1 varint,\n" +
"\treg2 varint static,\n" +
"\treg1 varint,\n" +
"\treg3 varint,\n" +
"\tPRIMARY KEY (pk1, ck1))"));
assertEquals(ImmutableList.of("ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns DROP reg1 USING TIMESTAMP 10000;",
"ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns ADD reg1 varint;",
"ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns DROP reg2 USING TIMESTAMP 20000;",
"ALTER TABLE cql_test_keyspace_readded_columns.test_table_readded_columns ADD reg2 varint static;"),
TableCQLHelper.getDroppedColumnsAsCQL(cfs.metadata()));
}
@Test
public void testCfmColumnsCQL()
{
String keyspace = "cql_test_keyspace_create_table";
String table = "test_table_create_table";
TableMetadata.Builder metadata =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addPartitionKeyColumn("pk2", AsciiType.instance)
.addClusteringColumn("ck1", ReversedType.getInstance(IntegerType.instance))
.addClusteringColumn("ck2", IntegerType.instance)
.addStaticColumn("st1", AsciiType.instance)
.addRegularColumn("reg1", AsciiType.instance)
.addRegularColumn("reg2", ListType.getInstance(IntegerType.instance, false))
.addRegularColumn("reg3", MapType.getInstance(AsciiType.instance, IntegerType.instance, true));
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), metadata);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertTrue(TableCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true).startsWith(
"CREATE TABLE IF NOT EXISTS cql_test_keyspace_create_table.test_table_create_table (\n" +
"\tpk1 varint,\n" +
"\tpk2 ascii,\n" +
"\tck1 varint,\n" +
"\tck2 varint,\n" +
"\tst1 ascii static,\n" +
"\treg1 ascii,\n" +
"\treg2 frozen<list<varint>>,\n" +
"\treg3 map<ascii, varint>,\n" +
"\tPRIMARY KEY ((pk1, pk2), ck1, ck2))\n" +
"\tWITH ID = " + cfs.metadata.id + "\n" +
"\tAND CLUSTERING ORDER BY (ck1 DESC, ck2 ASC)"));
}
@Test
public void testCfmOptionsCQL()
{
String keyspace = "cql_test_keyspace_options";
String table = "test_table_options";
TableMetadata.Builder builder = TableMetadata.builder(keyspace, table);
builder.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("cl1", IntegerType.instance)
.addRegularColumn("reg1", AsciiType.instance)
.bloomFilterFpChance(1.0)
.comment("comment")
.compaction(CompactionParams.lcs(Collections.singletonMap("sstable_size_in_mb", "1")))
.compression(CompressionParams.lz4(1 << 16, 1 << 15))
.crcCheckChance(0.3)
.defaultTimeToLive(4)
.gcGraceSeconds(5)
.minIndexInterval(6)
.maxIndexInterval(7)
.memtableFlushPeriod(8)
.speculativeRetry(AlwaysSpeculativeRetryPolicy.INSTANCE)
.additionalWritePolicy(NeverSpeculativeRetryPolicy.INSTANCE)
.extensions(ImmutableMap.of("ext1", ByteBuffer.wrap("val1".getBytes())))
.recordColumnDrop(ColumnMetadata.regularColumn(keyspace, table, "reg1", AsciiType.instance),
FBUtilities.timestampMicros());
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertTrue(TableCQLHelper.getTableMetadataAsCQL(cfs.metadata(), true).endsWith(
"AND bloom_filter_fp_chance = 1.0\n" +
"\tAND crc_check_chance = 0.3\n" +
"\tAND default_time_to_live = 4\n" +
"\tAND gc_grace_seconds = 5\n" +
"\tAND min_index_interval = 6\n" +
"\tAND max_index_interval = 7\n" +
"\tAND memtable_flush_period_in_ms = 8\n" +
"\tAND speculative_retry = 'ALWAYS'\n" +
"\tAND additional_write_policy = 'NEVER'\n" +
"\tAND comment = 'comment'\n" +
"\tAND caching = { 'keys': 'ALL', 'rows_per_partition': 'NONE' }\n" +
"\tAND compaction = { 'max_threshold': '32', 'min_threshold': '4', 'sstable_size_in_mb': '1', 'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy' }\n" +
"\tAND compression = { 'chunk_length_in_kb': '64', 'min_compress_ratio': '2.0', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor' }\n" +
"\tAND cdc = false\n" +
"\tAND extensions = { 'ext1': 0x76616c31 };"
));
}
@Test
public void testCfmIndexJson()
{
String keyspace = "cql_test_keyspace_3";
String table = "test_table_3";
TableMetadata.Builder builder =
TableMetadata.builder(keyspace, table)
.addPartitionKeyColumn("pk1", IntegerType.instance)
.addClusteringColumn("cl1", IntegerType.instance)
.addRegularColumn("reg1", AsciiType.instance);
ColumnIdentifier reg1 = ColumnIdentifier.getInterned("reg1", true);
builder.indexes(
Indexes.of(IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.VALUES)),
"indexName",
IndexMetadata.Kind.COMPOSITES,
Collections.emptyMap()),
IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.KEYS)),
"indexName2",
IndexMetadata.Kind.COMPOSITES,
Collections.emptyMap()),
IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.KEYS_AND_VALUES)),
"indexName3",
IndexMetadata.Kind.COMPOSITES,
Collections.emptyMap()),
IndexMetadata.fromIndexTargets(
Collections.singletonList(new IndexTarget(reg1, IndexTarget.Type.KEYS_AND_VALUES)),
"indexName4",
IndexMetadata.Kind.CUSTOM,
Collections.singletonMap(IndexTarget.CUSTOM_INDEX_OPTION_NAME, SASIIndex.class.getName()))));
SchemaLoader.createKeyspace(keyspace, KeyspaceParams.simple(1), builder);
ColumnFamilyStore cfs = Keyspace.open(keyspace).getColumnFamilyStore(table);
assertEquals(ImmutableList.of("CREATE INDEX \"indexName\" ON cql_test_keyspace_3.test_table_3 (values(reg1));",
"CREATE INDEX \"indexName2\" ON cql_test_keyspace_3.test_table_3 (keys(reg1));",
"CREATE INDEX \"indexName3\" ON cql_test_keyspace_3.test_table_3 (entries(reg1));",
"CREATE CUSTOM INDEX \"indexName4\" ON cql_test_keyspace_3.test_table_3 (entries(reg1)) USING 'org.apache.cassandra.index.sasi.SASIIndex';"),
TableCQLHelper.getIndexesAsCQL(cfs.metadata()));
}
private final static String SNAPSHOT = "testsnapshot";
@Test
public void testSnapshot() throws Throwable
{
String typeA = createType("CREATE TYPE %s (a1 varint, a2 varint, a3 varint);");
String typeB = createType("CREATE TYPE %s (b1 frozen<" + typeA + ">, b2 frozen<" + typeA + ">, b3 frozen<" + typeA + ">);");
String typeC = createType("CREATE TYPE %s (c1 frozen<" + typeB + ">, c2 frozen<" + typeB + ">, c3 frozen<" + typeB + ">);");
String tableName = createTable("CREATE TABLE IF NOT EXISTS %s (" +
"pk1 varint," +
"pk2 ascii," +
"ck1 varint," +
"ck2 varint," +
"reg1 " + typeC + "," +
"reg2 int," +
"reg3 int," +
"PRIMARY KEY ((pk1, pk2), ck1, ck2)) WITH " +
"CLUSTERING ORDER BY (ck1 ASC, ck2 DESC);");
alterTable("ALTER TABLE %s DROP reg3 USING TIMESTAMP 10000;");
alterTable("ALTER TABLE %s ADD reg3 int;");
for (int i = 0; i < 10; i++)
execute("INSERT INTO %s (pk1, pk2, ck1, ck2, reg1, reg2) VALUES (?, ?, ?, ?, ?, ?)", i, i + 1, i + 2, i + 3, null, i + 5);
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName);
cfs.snapshot(SNAPSHOT);
String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT), Charset.defaultCharset());
assertTrue(schema.contains(String.format("CREATE TYPE %s.%s (a1 varint, a2 varint, a3 varint);", keyspace(), typeA)));
assertTrue(schema.contains(String.format("CREATE TYPE %s.%s (a1 varint, a2 varint, a3 varint);", keyspace(), typeA)));
assertTrue(schema.contains(String.format("CREATE TYPE %s.%s (b1 frozen<%s>, b2 frozen<%s>, b3 frozen<%s>);", keyspace(), typeB, typeA, typeA, typeA)));
assertTrue(schema.contains(String.format("CREATE TYPE %s.%s (c1 frozen<%s>, c2 frozen<%s>, c3 frozen<%s>);", keyspace(), typeC, typeB, typeB, typeB)));
schema = schema.substring(schema.indexOf("CREATE TABLE")); // trim to ensure order
assertTrue(schema.startsWith("CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" +
"\tpk1 varint,\n" +
"\tpk2 ascii,\n" +
"\tck1 varint,\n" +
"\tck2 varint,\n" +
"\treg2 int,\n" +
"\treg3 int,\n" +
"\treg1 " + typeC + ",\n" +
"\tPRIMARY KEY ((pk1, pk2), ck1, ck2))\n" +
"\tWITH ID = " + cfs.metadata.id + "\n" +
"\tAND CLUSTERING ORDER BY (ck1 ASC, ck2 DESC)"));
schema = schema.substring(schema.indexOf("ALTER"));
assertTrue(schema.startsWith(String.format("ALTER TABLE %s.%s DROP reg3 USING TIMESTAMP 10000;", keyspace(), tableName)));
assertTrue(schema.contains(String.format("ALTER TABLE %s.%s ADD reg3 int;", keyspace(), tableName)));
JSONObject manifest = (JSONObject) new JSONParser().parse(new FileReader(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT)));
JSONArray files = (JSONArray) manifest.get("files");
Assert.assertEquals(1, files.size());
}
@Test
public void testSystemKsSnapshot() throws Throwable
{
ColumnFamilyStore cfs = Keyspace.open("system").getColumnFamilyStore("peers");
cfs.snapshot(SNAPSHOT);
Assert.assertTrue(cfs.getDirectories().getSnapshotManifestFile(SNAPSHOT).exists());
Assert.assertFalse(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT).exists());
}
@Test
public void testDroppedType() throws Throwable
{
String typeA = createType("CREATE TYPE %s (a1 varint, a2 varint, a3 varint);");
String typeB = createType("CREATE TYPE %s (b1 frozen<" + typeA + ">, b2 frozen<" + typeA + ">, b3 frozen<" + typeA + ">);");
String tableName = createTable("CREATE TABLE IF NOT EXISTS %s (" +
"pk1 varint," +
"ck1 varint," +
"reg1 frozen<" + typeB + ">," +
"reg2 varint," +
"PRIMARY KEY (pk1, ck1));");
alterTable("ALTER TABLE %s DROP reg1 USING TIMESTAMP 10000;");
Runnable validate = () -> {
try
{
ColumnFamilyStore cfs = Keyspace.open(keyspace()).getColumnFamilyStore(tableName);
cfs.snapshot(SNAPSHOT);
String schema = Files.toString(cfs.getDirectories().getSnapshotSchemaFile(SNAPSHOT), Charset.defaultCharset());
// When both column and it's type are dropped, the type in column definition gets substituted with a tuple
assertTrue(schema.startsWith("CREATE TABLE IF NOT EXISTS " + keyspace() + "." + tableName + " (\n" +
"\tpk1 varint,\n" +
"\tck1 varint,\n" +
"\treg2 varint,\n" +
"\treg1 frozen<tuple<frozen<tuple<varint, varint, varint>>, frozen<tuple<varint, varint, varint>>, frozen<tuple<varint, varint, varint>>>>,\n" +
"\tPRIMARY KEY (pk1, ck1))"));
assertTrue(schema.contains("ALTER TABLE " + keyspace() + "." + tableName + " DROP reg1 USING TIMESTAMP 10000;"));
}
catch (Exception e)
{
throw new RuntimeException(e);
}
};
// Validate before and after the type drop
validate.run();
schemaChange("DROP TYPE " + keyspace() + "." + typeB);
schemaChange("DROP TYPE " + keyspace() + "." + typeA);
validate.run();
}
}

View File

@ -227,5 +227,4 @@ public class FBUtilitiesTest
executor.shutdown(); executor.shutdown();
} }
} }
} }