1919import six
2020import warnings
2121
22- from cassandra . metadata import KeyspaceMetadata
22+ from cassandra import metadata
2323from cassandra .cqlengine import CQLEngineException , SizeTieredCompactionStrategy , LeveledCompactionStrategy
24+ from cassandra .cqlengine import columns
2425from cassandra .cqlengine .connection import execute , get_cluster
2526from cassandra .cqlengine .models import Model
2627from cassandra .cqlengine .named import NamedTable
28+ from cassandra .cqlengine .usertype import UserType
2729
2830CQLENG_ALLOW_SCHEMA_MANAGEMENT = 'CQLENG_ALLOW_SCHEMA_MANAGEMENT'
2931
@@ -132,7 +134,7 @@ def _create_keyspace(name, durable_writes, strategy_class, strategy_options):
132134
133135 if name not in cluster .metadata .keyspaces :
134136 log .info ("Creating keyspace %s " , name )
135- ks_meta = KeyspaceMetadata (name , durable_writes , strategy_class , strategy_options )
137+ ks_meta = metadata . KeyspaceMetadata (name , durable_writes , strategy_class , strategy_options )
136138 execute (ks_meta .as_cql_query ())
137139 else :
138140 log .info ("Not creating keyspace %s because it already exists" , name )
@@ -168,6 +170,8 @@ def sync_table(model):
168170 """
169171 Inspects the model and creates / updates the corresponding table and columns.
170172
173+ Any User Defined Types used in the table are implicitly synchronized.
174+
171175 This function can only add fields that are not part of the primary key.
172176
173177 Note that the attributes removed from the model are not deleted on the database.
@@ -198,6 +202,13 @@ def sync_table(model):
198202 keyspace = cluster .metadata .keyspaces [ks_name ]
199203 tables = keyspace .tables
200204
205+ syncd_types = set ()
206+ for col in model ._columns .values ():
207+ udts = []
208+ columns .resolve_udts (col , udts )
209+ for udt in [u for u in udts if u not in syncd_types ]:
210+ _sync_type (ks_name , udt , syncd_types )
211+
201212 # check for an existing column family
202213 if raw_cf_name not in tables :
203214 log .debug ("sync_table creating new table %s" , cf_name )
@@ -216,6 +227,7 @@ def sync_table(model):
216227 fields = get_fields (model )
217228 field_names = [x .name for x in fields ]
218229 model_fields = set ()
230+ # # TODO: does this work with db_name??
219231 for name , col in model ._columns .items ():
220232 if col .primary_key or col .partition_key :
221233 continue # we can't mess with the PK
@@ -248,6 +260,77 @@ def sync_table(model):
248260 execute (qs )
249261
250262
263+ def sync_type (ks_name , type_model ):
264+ """
265+ Inspects the type_model and creates / updates the corresponding type.
266+
267+ Note that the attributes removed from the type_model are not deleted on the database (this operation is not supported).
268+ They become effectively ignored by (will not show up on) the type_model.
269+
270+ **This function should be used with caution, especially in production environments.
271+ Take care to execute schema modifications in a single context (i.e. not concurrently with other clients).**
272+
273+ *There are plans to guard schema-modifying functions with an environment-driven conditional.*
274+ """
275+ if not _allow_schema_modification ():
276+ return
277+
278+ if not issubclass (type_model , UserType ):
279+ raise CQLEngineException ("Types must be derived from base UserType." )
280+
281+ _sync_type (ks_name , type_model )
282+
283+
284+ def _sync_type (ks_name , type_model , omit_subtypes = None ):
285+
286+ syncd_sub_types = omit_subtypes or set ()
287+ for field in type_model ._fields .values ():
288+ udts = []
289+ columns .resolve_udts (field , udts )
290+ for udt in [u for u in udts if u not in syncd_sub_types ]:
291+ _sync_type (ks_name , udt , syncd_sub_types )
292+ syncd_sub_types .add (udt )
293+
294+ type_name = type_model .type_name ()
295+ type_name_qualified = "%s.%s" % (ks_name , type_name )
296+
297+ cluster = get_cluster ()
298+
299+ keyspace = cluster .metadata .keyspaces [ks_name ]
300+ defined_types = keyspace .user_types
301+
302+ if type_name not in defined_types :
303+ log .debug ("sync_type creating new type %s" , type_name_qualified )
304+ cql = get_create_type (type_model , ks_name )
305+ execute (cql )
306+ type_model .register_for_keyspace (ks_name )
307+ else :
308+ defined_fields = defined_types [type_name ].field_names
309+ model_fields = set ()
310+ for field in type_model ._fields .values ():
311+ model_fields .add (field .db_field_name )
312+ if field .db_field_name not in defined_fields :
313+ execute ("ALTER TYPE {} ADD {}" .format (type_name_qualified , field .get_column_def ()))
314+
315+ if len (defined_fields ) == len (model_fields ):
316+ log .info ("Type %s did not require synchronization" , type_name_qualified )
317+ return
318+
319+ db_fields_not_in_model = model_fields .symmetric_difference (defined_fields )
320+ if db_fields_not_in_model :
321+ log .info ("Type %s has fields not referenced by model: %s" , type_name_qualified , db_fields_not_in_model )
322+
323+ type_model .register_for_keyspace (ks_name )
324+
325+
326+ def get_create_type (type_model , keyspace ):
327+ type_meta = metadata .UserType (keyspace ,
328+ type_model .type_name (),
329+ (f .db_field_name for f in type_model ._fields .values ()),
330+ type_model ._fields .values ())
331+ return type_meta .as_cql_query ()
332+
333+
251334def get_create_table (model ):
252335 cf_name = model .column_family_name ()
253336 qs = ['CREATE TABLE {}' .format (cf_name )]
@@ -439,11 +522,12 @@ def drop_table(model):
439522 raw_cf_name = model .column_family_name (include_keyspace = False )
440523
441524 try :
442- table = meta .keyspaces [ks_name ].tables [raw_cf_name ]
525+ meta .keyspaces [ks_name ].tables [raw_cf_name ]
443526 execute ('drop table {};' .format (model .column_family_name (include_keyspace = True )))
444527 except KeyError :
445528 pass
446529
530+
447531def _allow_schema_modification ():
448532 if not os .getenv (CQLENG_ALLOW_SCHEMA_MANAGEMENT ):
449533 msg = CQLENG_ALLOW_SCHEMA_MANAGEMENT + " environment variable is not set. Future versions of this package will require this variable to enable management functions."
0 commit comments