@@ -361,59 +361,7 @@ def delete_quick(self, get_count=False):
361
361
self ._log (query [:255 ])
362
362
return count
363
363
364
- def _delete_cascade (self ):
365
- """service function to perform cascading deletes recursively."""
366
- max_attempts = 50
367
- for _ in range (max_attempts ):
368
- try :
369
- delete_count = self .delete_quick (get_count = True )
370
- except IntegrityError as error :
371
- match = foreign_key_error_regexp .match (error .args [0 ]).groupdict ()
372
- if "`.`" not in match ['child' ]: # if schema name missing, use self
373
- match ['child' ] = '{}.{}' .format (self .full_table_name .split ("." )[0 ],
374
- match ['child' ])
375
- if match ['pk_attrs' ] is not None : # fully matched, adjusting the keys
376
- match ['fk_attrs' ] = [k .strip ('`' ) for k in match ['fk_attrs' ].split (',' )]
377
- match ['pk_attrs' ] = [k .strip ('`' ) for k in match ['pk_attrs' ].split (',' )]
378
- else : # only partially matched, querying with constraint to determine keys
379
- match ['fk_attrs' ], match ['parent' ], match ['pk_attrs' ] = list (map (
380
- list , zip (* self .connection .query (constraint_info_query , args = (
381
- match ['name' ].strip ('`' ),
382
- * [_ .strip ('`' ) for _ in match ['child' ].split ('`.`' )]
383
- )).fetchall ())))
384
- match ['parent' ] = match ['parent' ][0 ]
385
-
386
- # Avoid deleting from child before master (See issue #151)
387
- master = get_master (match ['child' ])
388
- if master and self .full_table_name != master :
389
- raise DataJointError (
390
- 'Attempt to delete from part table {part} before deleting from '
391
- 'its master. Delete from {master} first.' .format (
392
- part = match ['child' ], master = master ))
393
-
394
- # Restrict child by self if
395
- # 1. if self's restriction attributes are not in child's primary key
396
- # 2. if child renames any attributes
397
- # Otherwise restrict child by self's restriction.
398
- child = FreeTable (self .connection , match ['child' ])
399
- if set (self .restriction_attributes ) <= set (child .primary_key ) and \
400
- match ['fk_attrs' ] == match ['pk_attrs' ]:
401
- child ._restriction = self ._restriction
402
- elif match ['fk_attrs' ] != match ['pk_attrs' ]:
403
- child &= self .proj (** dict (zip (match ['fk_attrs' ],
404
- match ['pk_attrs' ])))
405
- else :
406
- child &= self .proj ()
407
- child ._delete_cascade ()
408
- else :
409
- print ("Deleting {count} rows from {table}" .format (
410
- count = delete_count , table = self .full_table_name ))
411
- break
412
- else :
413
- raise DataJointError ('Exceeded maximum number of delete attempts.' )
414
- return delete_count
415
-
416
- def delete (self , transaction = True , safemode = None ):
364
+ def delete (self , transaction = True , safemode = None , force_parts = False ):
417
365
"""
418
366
Deletes the contents of the table and its dependent tables, recursively.
419
367
@@ -422,8 +370,56 @@ def delete(self, transaction=True, safemode=None):
422
370
within another transaction.
423
371
:param safemode: If True, prohibit nested transactions and prompt to confirm. Default
424
372
is dj.config['safemode'].
373
+ :param force_parts: Delete from parts even when not deleting from their masters.
425
374
:return: number of deleted rows (excluding those from dependent tables)
426
375
"""
376
+ deleted = set ()
377
+
378
+ def cascade (table ):
379
+ """service function to perform cascading deletes recursively."""
380
+ max_attempts = 50
381
+ for _ in range (max_attempts ):
382
+ try :
383
+ delete_count = table .delete_quick (get_count = True )
384
+ except IntegrityError as error :
385
+ match = foreign_key_error_regexp .match (error .args [0 ]).groupdict ()
386
+ if "`.`" not in match ['child' ]: # if schema name missing, use table
387
+ match ['child' ] = '{}.{}' .format (table .full_table_name .split ("." )[0 ],
388
+ match ['child' ])
389
+ if match ['pk_attrs' ] is not None : # fully matched, adjusting the keys
390
+ match ['fk_attrs' ] = [k .strip ('`' ) for k in match ['fk_attrs' ].split (',' )]
391
+ match ['pk_attrs' ] = [k .strip ('`' ) for k in match ['pk_attrs' ].split (',' )]
392
+ else : # only partially matched, querying with constraint to determine keys
393
+ match ['fk_attrs' ], match ['parent' ], match ['pk_attrs' ] = list (map (
394
+ list , zip (* table .connection .query (constraint_info_query , args = (
395
+ match ['name' ].strip ('`' ),
396
+ * [_ .strip ('`' ) for _ in match ['child' ].split ('`.`' )]
397
+ )).fetchall ())))
398
+ match ['parent' ] = match ['parent' ][0 ]
399
+
400
+ # Restrict child by table if
401
+ # 1. if table's restriction attributes are not in child's primary key
402
+ # 2. if child renames any attributes
403
+ # Otherwise restrict child by table's restriction.
404
+ child = FreeTable (table .connection , match ['child' ])
405
+ if set (table .restriction_attributes ) <= set (child .primary_key ) and \
406
+ match ['fk_attrs' ] == match ['pk_attrs' ]:
407
+ child ._restriction = table ._restriction
408
+ elif match ['fk_attrs' ] != match ['pk_attrs' ]:
409
+ child &= table .proj (** dict (zip (match ['fk_attrs' ],
410
+ match ['pk_attrs' ])))
411
+ else :
412
+ child &= table .proj ()
413
+ cascade (child )
414
+ else :
415
+ deleted .add (table .full_table_name )
416
+ print ("Deleting {count} rows from {table}" .format (
417
+ count = delete_count , table = table .full_table_name ))
418
+ break
419
+ else :
420
+ raise DataJointError ('Exceeded maximum number of delete attempts.' )
421
+ return delete_count
422
+
427
423
safemode = config ['safemode' ] if safemode is None else safemode
428
424
429
425
# Start transaction
@@ -440,12 +436,23 @@ def delete(self, transaction=True, safemode=None):
440
436
441
437
# Cascading delete
442
438
try :
443
- delete_count = self . _delete_cascade ( )
439
+ delete_count = cascade ( self )
444
440
except :
445
441
if transaction :
446
442
self .connection .cancel_transaction ()
447
443
raise
448
444
445
+ if not force_parts :
446
+ # Avoid deleting from child before master (See issue #151)
447
+ for part in deleted :
448
+ master = get_master (part )
449
+ if master and master not in deleted :
450
+ if transaction :
451
+ self .connection .cancel_transaction ()
452
+ raise DataJointError (
453
+ 'Attempt to delete part table {part} before deleting from '
454
+ 'its master {master} first.' .format (part = part , master = master ))
455
+
449
456
# Confirm and commit
450
457
if delete_count == 0 :
451
458
if safemode :
0 commit comments