Skip to content
This repository was archived by the owner on Apr 27, 2021. It is now read-only.

Commit a89fddc

Browse files
committed
Merge pull request apache#534 from datastax/532
PYTHON-532 - validate PK composition
2 parents 8599b83 + 2ce9168 commit a89fddc

2 files changed

Lines changed: 59 additions & 36 deletions

File tree

cassandra/cqlengine/management.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,11 @@ def sync_table(model):
151151

152152
cluster = get_cluster()
153153

154-
keyspace = cluster.metadata.keyspaces[ks_name]
154+
try:
155+
keyspace = cluster.metadata.keyspaces[ks_name]
156+
except KeyError:
157+
raise CQLEngineException("Keyspace '{0}' for model {1} does not exist.".format(ks_name, model))
158+
155159
tables = keyspace.tables
156160

157161
syncd_types = set()
@@ -161,7 +165,6 @@ def sync_table(model):
161165
for udt in [u for u in udts if u not in syncd_types]:
162166
_sync_type(ks_name, udt, syncd_types)
163167

164-
# check for an existing column family
165168
if raw_cf_name not in tables:
166169
log.debug("sync_table creating new table %s", cf_name)
167170
qs = _get_create_table(model)
@@ -175,32 +178,35 @@ def sync_table(model):
175178
raise
176179
else:
177180
log.debug("sync_table checking existing table %s", cf_name)
178-
# see if we're missing any columns
179181
table_meta = tables[raw_cf_name]
180-
field_names = _get_non_pk_field_names(tables[raw_cf_name])
182+
183+
_validate_pk(model, table_meta)
184+
185+
table_columns = table_meta.columns
181186
model_fields = set()
182-
# # TODO: does this work with db_name??
183-
for name, col in model._columns.items():
184-
if col.primary_key or col.partition_key:
185-
continue # we can't mess with the PK
186-
model_fields.add(name)
187-
if col.db_field_name in field_names:
188-
col_meta = table_meta.columns[col.db_field_name]
189187

188+
for model_name, col in model._columns.items():
189+
db_name = col.db_field_name
190+
model_fields.add(db_name)
191+
if db_name in table_columns:
192+
col_meta = table_columns[db_name]
190193
if col_meta.cql_type != col.db_type:
191194
msg = 'Existing table {0} has column "{1}" with a type ({2}) differing from the model type ({3}).' \
192-
' Model should be updated.'.format(cf_name, col.db_field_name, col_meta.cql_type, col.db_type)
195+
' Model should be updated.'.format(cf_name, db_name, col_meta.cql_type, col.db_type)
193196
warnings.warn(msg)
194197
log.warning(msg)
195-
continue # skip columns already defined
196198

197-
# add missing column using the column def
199+
continue
200+
201+
if col.primary_key or col.primary_key:
202+
raise CQLEngineException("Cannot add primary key '{0}' (with db_field '{1}') to existing table {2}".format(model_name, db_name, cf_name))
203+
198204
query = "ALTER TABLE {0} add {1}".format(cf_name, col.get_column_def())
199205
execute(query)
200206

201-
db_fields_not_in_model = model_fields.symmetric_difference(field_names)
207+
db_fields_not_in_model = model_fields.symmetric_difference(table_columns)
202208
if db_fields_not_in_model:
203-
log.info("Table %s has fields not referenced by model: %s", cf_name, db_fields_not_in_model)
209+
log.info("Table {0} has fields not referenced by model: {1}".format(cf_name, db_fields_not_in_model))
204210

205211
_update_options(model)
206212

@@ -221,6 +227,22 @@ def sync_table(model):
221227
execute(qs)
222228

223229

230+
def _validate_pk(model, table_meta):
231+
model_partition = [c.db_field_name for c in model._partition_keys.values()]
232+
meta_partition = [c.name for c in table_meta.partition_key]
233+
model_clustering = [c.db_field_name for c in model._clustering_keys.values()]
234+
meta_clustering = [c.name for c in table_meta.clustering_key]
235+
236+
if model_partition != meta_partition or model_clustering != meta_clustering:
237+
def _pk_string(partition, clustering):
238+
return "PRIMARY KEY (({0}){1})".format(', '.join(partition), ', ' + ', '.join(clustering) if clustering else '')
239+
raise CQLEngineException("Model {0} PRIMARY KEY composition does not match existing table {1}. "
240+
"Model: {2}; Table: {3}. "
241+
"Update model or drop the table.".format(model, model.column_family_name(),
242+
_pk_string(model_partition, model_clustering),
243+
_pk_string(meta_partition, meta_clustering)))
244+
245+
224246
def sync_type(ks_name, type_model):
225247
"""
226248
Inspects the type_model and creates / updates the corresponding type.
@@ -339,13 +361,6 @@ def add_column(col):
339361
return ' '.join(query_strings)
340362

341363

342-
def _get_non_pk_field_names(table_meta):
343-
# returns all fields that aren't part of the PK
344-
pk_names = set(col.name for col in table_meta.primary_key)
345-
field_names = [name for name in table_meta.columns.keys() if name not in pk_names]
346-
return field_names
347-
348-
349364
def _get_table_metadata(model):
350365
# returns the table as provided by the native driver for a given model
351366
cluster = get_cluster()

tests/integration/cqlengine/management/test_management.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from cassandra.cqlengine.connection import get_session, get_cluster
2222
from cassandra.cqlengine import CQLEngineException
2323
from cassandra.cqlengine import management
24-
from cassandra.cqlengine.management import _get_non_pk_field_names, _get_table_metadata, sync_table, drop_table, sync_type
24+
from cassandra.cqlengine.management import _get_table_metadata, sync_table, drop_table, sync_type
2525
from cassandra.cqlengine.models import Model
2626
from cassandra.cqlengine import columns
2727

@@ -128,7 +128,7 @@ class FourthModel(Model):
128128
first_key = columns.UUID(primary_key=True)
129129
second_key = columns.UUID()
130130
third_key = columns.Text()
131-
# removed fourth key, but it should stay in the DB
131+
# renamed model field, but map to existing column
132132
renamed = columns.Map(columns.Text, columns.Text, db_field='blah')
133133

134134

@@ -138,23 +138,31 @@ def setUp(self):
138138

139139
def test_add_column(self):
140140
sync_table(FirstModel)
141-
fields = _get_non_pk_field_names(_get_table_metadata(FirstModel))
141+
meta_columns = _get_table_metadata(FirstModel).columns
142+
self.assertEqual(set(meta_columns), set(FirstModel._columns))
142143

143-
# this should contain the second key
144-
self.assertEqual(len(fields), 2)
145-
# get schema
146144
sync_table(SecondModel)
147-
148-
fields = _get_non_pk_field_names(_get_table_metadata(FirstModel))
149-
self.assertEqual(len(fields), 3)
145+
meta_columns = _get_table_metadata(FirstModel).columns
146+
self.assertEqual(set(meta_columns), set(SecondModel._columns))
150147

151148
sync_table(ThirdModel)
152-
fields = _get_non_pk_field_names(_get_table_metadata(FirstModel))
153-
self.assertEqual(len(fields), 4)
149+
meta_columns = _get_table_metadata(FirstModel).columns
150+
self.assertEqual(len(meta_columns), 5)
151+
self.assertEqual(len(ThirdModel._columns), 4)
152+
self.assertIn('fourth_key', meta_columns)
153+
self.assertNotIn('fourth_key', ThirdModel._columns)
154+
self.assertIn('blah', ThirdModel._columns)
155+
self.assertIn('blah', meta_columns)
154156

155157
sync_table(FourthModel)
156-
fields = _get_non_pk_field_names(_get_table_metadata(FirstModel))
157-
self.assertEqual(len(fields), 4)
158+
meta_columns = _get_table_metadata(FirstModel).columns
159+
self.assertEqual(len(meta_columns), 5)
160+
self.assertEqual(len(ThirdModel._columns), 4)
161+
self.assertIn('fourth_key', meta_columns)
162+
self.assertNotIn('fourth_key', FourthModel._columns)
163+
self.assertIn('renamed', FourthModel._columns)
164+
self.assertNotIn('renamed', meta_columns)
165+
self.assertIn('blah', meta_columns)
158166

159167

160168
class ModelWithTableProperties(Model):

0 commit comments

Comments
 (0)