1- import json
21import time
32import typing as t
4- from collections import namedtuple
3+ from collections import defaultdict , namedtuple
54
65import click
6+ import networkx as nx
77
88import superduper as s
99from superduper import CFG , logging
@@ -368,6 +368,53 @@ def apply(
368368 )
369369 return result
370370
371+ def _filter_deletions_by_cascade (self , events : t .List [Delete ]):
372+ """
373+ Filter deletions by cascade.
374+
375+ :param events: List of events to filter.
376+ """
377+ all_huuids = set (e .huuid for e in events )
378+ lookup = {e .huuid : e for e in events }
379+
380+ graph = nx .DiGraph ()
381+
382+ for e in events :
383+ graph .add_node (e .huuid )
384+ for dep in e .parents :
385+ if dep in all_huuids :
386+ graph .add_edge (dep , e .huuid )
387+
388+ # sort events by graph
389+ sorted_nodes = nx .topological_sort (graph )
390+ events = [lookup [n ] for n in sorted_nodes if n in all_huuids ]
391+
392+ conflicting_events = [
393+ e .huuid for e in events if not set (e .parents ).issubset (all_huuids )
394+ ]
395+ potential_breakages = defaultdict (list )
396+ if conflicting_events :
397+ for e in conflicting_events :
398+ potential_breakages [e ].extend (
399+ [x for x in lookup [e ].parents if x not in all_huuids ]
400+ )
401+
402+ all_conflicts = conflicting_events [:]
403+
404+ if conflicting_events :
405+ for e in conflicting_events :
406+ # find descendants downstream from the event
407+ downstream = list (nx .descendants (graph , e ))
408+ all_conflicts .extend (downstream )
409+
410+ all_conflicts = set (all_conflicts )
411+
412+ events = [e for e in events if e .huuid not in all_conflicts ]
413+
414+ non_table_events = [e for e in events if e .component != 'Table' ]
415+ table_events = [e for e in events if e .component == 'Table' ]
416+ return non_table_events + table_events , potential_breakages
417+
371418 def remove (
372419 self ,
373420 component : str ,
@@ -385,28 +432,35 @@ def remove(
385432 :param force: Toggle to force remove the component.
386433 """
387434 events : t .List [Delete ] = []
388- failed : t .List [str ] = []
389435 self ._build_remove (
390436 component = component ,
391437 identifier = identifier ,
392438 events = events ,
393- failed = failed ,
394439 recursive = recursive ,
395440 )
396441
397- if failed and not force :
442+ events = list ({e .huuid : e for e in events }.values ()) # remove duplicates
443+
444+ filtered_events , potential_breakages = self ._filter_deletions_by_cascade (events )
445+
446+ if potential_breakages and not force :
447+ msg = '\n ' + '\n ' .join (
448+ ' ' + f'{ k } -> { v } ' for k , v in potential_breakages .items ()
449+ )
398450 raise exceptions .Conflict (
399- component , identifier , f"the following components are in use: { failed } "
451+ component ,
452+ identifier ,
453+ f"the following components are using some components scheduler for deletion: { msg } " ,
400454 )
401455
402- for i , e in enumerate (events ):
456+ for i , e in enumerate (filtered_events ):
403457 logging .info (
404- f'Removing component [{ i + 1 } /{ len (events )} ] '
458+ f'Removing component [{ i + 1 } /{ len (filtered_events )} ] '
405459 f'{ e .component } :{ e .identifier } '
406460 )
407461 e .execute (self )
408462 logging .info (
409- f'Removing component [{ i + 1 } /{ len (events )} ] '
463+ f'Removing component [{ i + 1 } /{ len (filtered_events )} ] '
410464 f'{ e .component } :{ e .identifier } ... DONE'
411465 )
412466
@@ -415,30 +469,22 @@ def _build_remove(
415469 component : str ,
416470 identifier : str ,
417471 events : t .List ,
418- failed : t .List ,
419472 recursive : bool = False ,
420473 ):
421474
422475 object = self .load (component = component , identifier = identifier )
423476
424- previous = [e .huuid for e in events ]
425-
426477 parents = self .metadata .get_component_parents (
427478 component = component , identifier = identifier
428479 )
429- fail = False
430- if parents :
431- # Only fail the deletion attempt if the parents aren't in this cascade
432- for p in parents :
433- if f'{ p [0 ]} :{ p [1 ]} ' not in previous :
434- failed .append (f'{ component } :{ identifier } -> { p [0 ]} :{ p [1 ]} ' )
435- fail = True
436-
437- # If the deletion fails, we need to stop
438- if fail :
439- return
440480
441- events .append (Delete (component = component , identifier = identifier ))
481+ events .append (
482+ Delete (
483+ component = component ,
484+ identifier = identifier ,
485+ parents = [':' .join (p ) for p in parents ],
486+ )
487+ )
442488
443489 if recursive :
444490 children = object .get_children ()
@@ -448,7 +494,6 @@ def _build_remove(
448494 c .identifier ,
449495 recursive = True ,
450496 events = events ,
451- failed = failed ,
452497 )
453498
454499 def load_all (self , component : str , ** kwargs ) -> t .List [Component ]:
0 commit comments