@@ -1291,7 +1291,6 @@ def update_async(self, **values):
12911291 if not values :
12921292 return
12931293
1294- nulled_columns = set ()
12951294 updated_columns = set ()
12961295 us = UpdateStatement (self .column_family_name , where = self ._where , ttl = self ._ttl ,
12971296 timestamp = self ._timestamp , conditionals = self ._conditional , if_exists = self ._if_exists )
@@ -1314,25 +1313,13 @@ def update_async(self, **values):
13141313 # we should not provide default values in this use case.
13151314 val = col .validate (val )
13161315
1317- if val is None :
1318- nulled_columns .add (col_name )
1319- continue
1320-
13211316 us .add_update (col , val , operation = col_op )
13221317 updated_columns .add (col_name )
13231318
1324- futures = []
1325- if us .assignments :
1326- futures .append (self ._execute_async (us ))
1327-
1328- if nulled_columns :
1329- delete_conditional = [condition for condition in self ._conditional
1330- if condition .field not in updated_columns ] if self ._conditional else None
1331- ds = DeleteStatement (self .column_family_name , fields = nulled_columns ,
1332- where = self ._where , conditionals = delete_conditional , if_exists = self ._if_exists )
1333- futures .append (self ._execute_async (ds ))
1319+ if not us .assignments :
1320+ return CQLEngineFuture ()
13341321
1335- return CQLEngineFutureWaiter ( futures )
1322+ return self . _execute_async ( us )
13361323
13371324 def update (self , ** values ):
13381325 """
@@ -1481,20 +1468,20 @@ def batch(self, batch_obj):
14811468 self ._batch = batch_obj
14821469 return self
14831470
1484- def _delete_null_columns_async (self , conditionals = None ):
1471+ def _delete_map_keys_async (self , conditionals = None ):
14851472 """
1486- executes a delete query to remove columns that have changed to null
1473+ executes a delete query to remove map keys that have changed to null.
1474+
1475+ Doing map keys deletion in two phases allow us to update a map collection
1476+ without creating a tombstone.
14871477 """
14881478 ds = DeleteStatement (self .column_family_name , conditionals = conditionals , if_exists = self ._if_exists )
14891479 deleted_fields = False
14901480 static_only = True
1481+
14911482 for _ , v in self .instance ._values .items ():
14921483 col = v .column
1493- if v .deleted :
1494- ds .add_field (col .db_field_name )
1495- deleted_fields = True
1496- static_only &= col .static
1497- elif isinstance (col , columns .Map ):
1484+ if isinstance (col , columns .Map ):
14981485 uc = MapDeleteClause (col .db_field_name , v .value , v .previous_value )
14991486 if uc .get_context_size () > 0 :
15001487 ds .add_field (uc )
@@ -1509,11 +1496,11 @@ def _delete_null_columns_async(self, conditionals=None):
15091496 else :
15101497 return CQLEngineFuture ()
15111498
1512- def _delete_null_columns (self , conditionals = None ):
1499+ def _delete_map_keys (self , conditionals = None ):
15131500 """
15141501 executes a delete query to remove columns that have changed to null
15151502 """
1516- return self ._delete_null_columns_async (conditionals = conditionals ).result ()
1503+ return self ._delete_map_keys_async (conditionals = conditionals ).result ()
15171504
15181505 def update_async (self ):
15191506 """
@@ -1541,18 +1528,14 @@ def update_async(self):
15411528 val = getattr (self .instance , name , None )
15421529 val_mgr = self .instance ._values [name ]
15431530
1544- if val is None :
1545- continue
1546-
1547- if not val_mgr .changed and not isinstance (col , columns .Counter ):
1531+ if not (val_mgr .changed or val_mgr .deleted ) and not isinstance (col , columns .Counter ):
15481532 continue
15491533
15501534 static_changed_only = static_changed_only and col .static
15511535 statement .add_update (col , val , previous = val_mgr .previous_value )
15521536 updated_columns .add (col .db_field_name )
15531537
15541538 futures = []
1555-
15561539 if statement .assignments :
15571540 for name , col in self .model ._primary_keys .items ():
15581541 # only include clustering key if clustering key is not null, and non static columns are changed to avoid cql error
@@ -1565,7 +1548,7 @@ def update_async(self):
15651548 # remove conditions on fields that have been updated
15661549 delete_conditionals = [condition for condition in self ._conditional
15671550 if condition .field not in updated_columns ] if self ._conditional else None
1568- futures .append (self ._delete_null_columns_async (delete_conditionals ))
1551+ futures .append (self ._delete_map_keys_async (delete_conditionals ))
15691552
15701553 return CQLEngineFutureWaiter (futures )
15711554
@@ -1591,7 +1574,6 @@ def save_async(self):
15911574 if self .instance ._has_counter :
15921575 raise CQLEngineException ("Counters can only be updated. Use the 'update' mechanism instead." )
15931576
1594- nulled_fields = set ()
15951577 if self .instance ._can_update ():
15961578 return self .update_async ()
15971579 else :
@@ -1603,9 +1585,8 @@ def save_async(self):
16031585 if static_save_only and not col .static and not col .partition_key :
16041586 continue
16051587 val = getattr (self .instance , name , None )
1606- if col ._val_is_null (val ):
1607- if self .instance ._values [name ].changed :
1608- nulled_fields .add (col .db_field_name )
1588+ if (col ._val_is_null (val ) and
1589+ not (self .instance ._values [name ].changed or self .instance ._values [name ].deleted )):
16091590 continue
16101591 if col .has_default and not self .instance ._values [name ].changed :
16111592 # Ensure default columns included in a save() are marked as explicit, to get them *persisted* properly
@@ -1614,17 +1595,10 @@ def save_async(self):
16141595
16151596 # skip query execution if it's empty
16161597 # caused by pointless update queries
1617- if insert .is_empty and static_save_only :
1598+ if insert .is_empty :
16181599 return CQLEngineFuture ()
16191600
1620- futures = []
1621- if not insert .is_empty :
1622- futures .append (self ._execute_async (insert ))
1623- # delete any nulled columns
1624- if not static_save_only :
1625- futures .append (self ._delete_null_columns_async ())
1626-
1627- return CQLEngineFutureWaiter (futures )
1601+ return self ._execute_async (insert )
16281602
16291603 def save (self ):
16301604 """
0 commit comments