Skip to content

Commit 002ff27

Browse files
committed
Handle multiple secondary indexes per column
Also started working toward multiple-column indexes, but I don't think the server-side metadata is there yet.
1 parent 555c2fd commit 002ff27

2 files changed

Lines changed: 68 additions & 90 deletions

File tree

cassandra/metadata.py

Lines changed: 67 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,9 +1021,8 @@ def all_as_cql(self):
10211021
ret = self.as_cql_query(formatted=True)
10221022
ret += ";"
10231023

1024-
for col_meta in self.columns.values():
1025-
if col_meta.index:
1026-
ret += "\n%s;" % (col_meta.index.as_cql_query(),)
1024+
for index in self.indexes.values():
1025+
ret += "\n%s;" % index.as_cql_query()
10271026

10281027
for trigger_meta in self.triggers.values():
10291028
ret += "\n%s;" % (trigger_meta.as_cql_query(),)
@@ -1223,64 +1222,84 @@ class IndexMetadata(object):
12231222
A representation of a secondary index on a column.
12241223
"""
12251224

1226-
column = None
1227-
"""
1228-
The column (:class:`.ColumnMetadata`) this index is on.
1229-
"""
1225+
table = None
1226+
""" The :class:`.TableMetadata` this column belongs to. """
12301227

12311228
name = None
12321229
""" A string name for the index. """
12331230

1231+
columns = None
1232+
"""
1233+
The set of columns (:class:`.ColumnMetadata`) this index is on
1234+
(may be empty for per-row indexes).
1235+
1236+
.. versionchanged:: 3.0.0
1237+
1238+
Was previously a singular column
1239+
"""
1240+
12341241
index_type = None
12351242
""" A string representing the type of index. """
12361243

1237-
index_options = {}
1244+
index_options = None
12381245
""" A dict of index options. """
12391246

1240-
def __init__(self, column_metadata, index_name=None, index_type=None, index_options={}):
1241-
self.column = column_metadata
1247+
target_type = None
1248+
"""
1249+
String target type (COLUMN, ROW, etc), if available
1250+
1251+
.. versionadded:: 3.0.0
1252+
"""
1253+
1254+
def __init__(self, table_metadata, index_name=None, index_type=None, index_options=None, target_columns=None, target_type=None):
1255+
self.table = table_metadata
12421256
self.name = index_name
1257+
self.columns = set(table_metadata.columns[col_name] for col_name in target_columns) if target_columns else set()
12431258
self.index_type = index_type
1244-
self.index_options = index_options
1259+
self.index_options = index_options or {}
1260+
self.target_type = target_type
12451261

12461262
def as_cql_query(self):
12471263
"""
12481264
Returns a CQL query that can be used to recreate this index.
12491265
"""
1250-
table = self.column.table
12511266
if self.index_type != "CUSTOM":
1252-
index_target = protect_name(self.column.name)
1253-
if self.index_options is not None:
1254-
option_keys = self.index_options.keys()
1255-
if "index_keys" in option_keys:
1256-
index_target = 'keys(%s)' % (index_target,)
1257-
elif "index_values" in option_keys:
1258-
# don't use any "function" for collection values
1259-
pass
1260-
else:
1261-
# it might be a "full" index on a frozen collection, but
1262-
# we need to check the data type to verify that, because
1263-
# there is no special index option for full-collection
1264-
# indexes.
1265-
data_type = self.column.data_type
1266-
collection_types = ('map', 'set', 'list')
1267-
if data_type.typename == "frozen" and data_type.subtypes[0].typename in collection_types:
1268-
# no index option for full-collection index
1269-
index_target = 'full(%s)' % (index_target,)
1270-
1271-
return "CREATE INDEX %s ON %s.%s (%s)" % (
1267+
target_fmt = "CREATE INDEX %s ON %s.%s (%%s)" % (
12721268
self.name, # Cassandra doesn't like quoted index names for some reason
1273-
protect_name(table.keyspace.name),
1274-
protect_name(table.name),
1275-
index_target)
1269+
protect_name(self.table.keyspace.name),
1270+
protect_name(self.table.name))
12761271
else:
1277-
return "CREATE CUSTOM INDEX %s ON %s.%s (%s) USING '%s'" % (
1272+
target_fmt = "CREATE CUSTOM INDEX %s ON %s.%s (%%s) USING '%s'" % (
12781273
self.name, # Cassandra doesn't like quoted index names for some reason
1279-
protect_name(table.keyspace.name),
1280-
protect_name(table.name),
1281-
protect_name(self.column.name),
1274+
protect_name(self.table.keyspace.name),
1275+
protect_name(self.table.name),
12821276
self.index_options["class_name"])
12831277

1278+
# TODO: when CASSANDRA-10124 lands, we'll need to loop over self.columns
1279+
# not updating yet because it seems like index_options will have to change to
1280+
# specify different options per column(?)
1281+
# Also, grammar will be changing for indexes with no target columns. This is TBD
1282+
col = tuple(self.columns)[0] if self.columns else None
1283+
index_target = protect_name(col.name) if col else ''
1284+
if col and self.index_options is not None:
1285+
option_keys = self.index_options.keys()
1286+
if "index_keys" in option_keys:
1287+
index_target = 'keys(%s)' % (index_target,)
1288+
elif "index_values" in option_keys:
1289+
# don't use any "function" for collection values
1290+
pass
1291+
else:
1292+
# it might be a "full" index on a frozen collection, but
1293+
# we need to check the data type to verify that, because
1294+
# there is no special index option for full-collection
1295+
# indexes.
1296+
data_type = col.data_type
1297+
collection_types = ('map', 'set', 'list')
1298+
if data_type.typename == "frozen" and data_type.subtypes[0].typename in collection_types:
1299+
# no index option for full-collection index
1300+
index_target = 'full(%s)' % (index_target,)
1301+
return target_fmt % index_target
1302+
12841303
def export_as_string(self):
12851304
"""
12861305
Returns a CQL query string that can be used to recreate this index.
@@ -1605,7 +1624,6 @@ def get_table(self, keyspace, table):
16051624
def get_type(self, keyspace, type):
16061625
where_clause = " WHERE keyspace_name = '%s' AND type_name = '%s'" % (keyspace, type)
16071626
return self._query_build_row(self._SELECT_TYPES + where_clause, self._build_user_type)
1608-
16091627
def get_function(self, keyspace, function):
16101628
where_clause = " WHERE keyspace_name = '%s' AND function_name = '%s' AND signature = [%s]" \
16111629
% (keyspace, function.name, ','.join("'%s'" % t for t in function.type_signature))
@@ -1793,6 +1811,10 @@ def _build_table_metadata(self, row, col_rows=None, trigger_rows=None):
17931811
for col_row in col_rows:
17941812
column_meta = self._build_column_metadata(table_meta, col_row)
17951813
table_meta.columns[column_meta.name] = column_meta
1814+
index_meta = self._build_index_metadata(column_meta, col_row)
1815+
column_meta.index = index_meta
1816+
if index_meta:
1817+
table_meta.indexes[index_meta.name] = index_meta
17961818

17971819
for trigger_row in trigger_rows:
17981820
trigger_meta = self._build_trigger_metadata(table_meta, trigger_row)
@@ -1823,10 +1845,6 @@ def _build_column_metadata(self, table_metadata, row):
18231845
data_type = types.lookup_casstype(row["validator"])
18241846
is_static = row.get("type", None) == "static"
18251847
column_meta = ColumnMetadata(table_metadata, name, data_type, is_static=is_static)
1826-
index_meta = self._build_index_metadata(column_meta, row)
1827-
column_meta.index = index_meta
1828-
if index_meta:
1829-
table_metadata.indexes[index_meta.name] = index_meta
18301848
return column_meta
18311849

18321850
@staticmethod
@@ -1835,8 +1853,8 @@ def _build_index_metadata(column_metadata, row):
18351853
index_type = row.get("index_type")
18361854
if index_name or index_type:
18371855
options = row.get("index_options")
1838-
index_options = json.loads(options) if options else {}
1839-
return IndexMetadata(column_metadata, index_name, index_type, index_options)
1856+
index_options = json.loads(options) if options else None
1857+
return IndexMetadata(column_metadata.table, index_name, index_type, index_options, (column_metadata.name,))
18401858
else:
18411859
return None
18421860

@@ -2073,10 +2091,10 @@ def _build_index_metadata(table_metadata, row):
20732091
index_name = row.get("index_name")
20742092
index_type = row.get("index_type")
20752093
if index_name or index_type:
2076-
index_options = dict(row.get("options") or {})
2077-
target_columns = row.get('target_columns') or set()
2094+
index_options = row.get("options")
2095+
target_columns = row.get('target_columns')
20782096
target_type = row.get('target_type')
2079-
return IndexMetadataV3(table_metadata, index_name, index_type, index_options, target_columns, target_type)
2097+
return IndexMetadata(table_metadata, index_name, index_type, index_options, target_columns, target_type)
20802098
else:
20812099
return None
20822100

@@ -2150,47 +2168,6 @@ def _make_option_strings(self):
21502168
return list(sorted(ret))
21512169

21522170

2153-
class IndexMetadataV3(IndexMetadata):
2154-
2155-
table = None
2156-
"""
2157-
The table (:class:`.TableMetadata`) this index is on.
2158-
"""
2159-
2160-
columns = None
2161-
"""
2162-
The set of columns (:class:`.ColumnMetadata`) this index is on
2163-
(may be empty for per-row indexes).
2164-
"""
2165-
2166-
name = None
2167-
""" A string name for the index. """
2168-
2169-
index_type = None
2170-
""" A string representing the type of index. """
2171-
2172-
index_options = {}
2173-
""" A dict of index options. """
2174-
2175-
target_columns = set()
2176-
""" A set of target columns """
2177-
2178-
target_type = None
2179-
2180-
def __init__(self, table_metadata, index_name, index_type, index_options, target_columns, target_type):
2181-
self.table = table_metadata
2182-
self.name = index_name
2183-
self.index_type = index_type
2184-
self.index_options = index_options
2185-
self.target_columns = set(target_columns)
2186-
self.target_type = target_type
2187-
2188-
# TODO: temporary until we diverge more with multiple column indexes
2189-
# giving the base class what it expects
2190-
self.column = table_metadata.columns[tuple(target_columns)[0]]
2191-
self.column.index = self
2192-
2193-
21942171
def get_schema_parser(connection, timeout):
21952172
server_version = connection.server_version
21962173
if server_version.startswith('3'):

tests/unit/test_metadata.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ def test_build_index_as_cql(self):
316316
column_meta.name = 'column_name_here'
317317
column_meta.table.name = 'table_name_here'
318318
column_meta.table.keyspace.name = 'keyspace_name_here'
319+
column_meta.table.columns = {column_meta.name: column_meta}
319320
connection = Mock()
320321
connection.server_version = '2.1.0'
321322
parser = get_schema_parser(connection, 0.1)

0 commit comments

Comments
 (0)