Module: Exwiw::DetermineTableProcessingOrder
- Defined in:
- lib/exwiw/determine_table_processing_order.rb
Class Method Summary collapse
-
.compute_table_dependencies(table) ⇒ Object
The belongs_to target table names of
table. -
.run(tables, logger: nil, runtime_reverse_scope: false) ⇒ Array<String>
Sorted table names.
Class Method Details
.compute_table_dependencies(table) ⇒ Object
The belongs_to target table names of table. A polymorphic belongs_to is
expanded into one entry per concrete target by schema generation, so each
entry is a plain table name here.
90 91 92 |
# File 'lib/exwiw/determine_table_processing_order.rb', line 90 def compute_table_dependencies(table) table.belongs_tos.map(&:table_name) end |
.run(tables, logger: nil, runtime_reverse_scope: false) ⇒ Array<String>
Returns sorted table names.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/exwiw/determine_table_processing_order.rb', line 22 def run(tables, logger: nil, runtime_reverse_scope: false) return tables.map(&:name) if tables.size < 2 ordered_table_names = [] ordered = Set.new table_by_name = tables.each_with_object({}) do |table, acc| acc[table.name] = table end reverse_scope_deps = runtime_reverse_scope ? compute_reverse_scope_dependencies(tables) : {} dependencies_by_name = tables.each_with_object({}) do |table, acc| acc[table.name] = compute_dependencies(table, reverse_scope_deps) end # Only relations whose target is also in this run constrain the order. A # dependency pointing at a table that is not being processed here — e.g. # an embedded MongoDB collection (masked through its parent, never dumped # on its own) or any table excluded from the run — is not something we can # or need to order against, so it must never block resolution. Without # this, such a dependency would stay unresolved forever and masquerade as # a circular dependency, freezing every table that (transitively) # references it. present_names = table_by_name.keys.to_set loop do break if table_by_name.empty? resolvable = table_by_name.values.select do |table| unresolved_dependencies(table.name, dependencies_by_name, present_names, ordered).empty? end if resolvable.empty? # No table has all its (in-run) dependencies satisfied, yet tables # remain: the dependency graph has a genuine cycle and no strict # topological order exists. # # When a reverse_scope ordering edge participates in the cycle, there # is no safe way out: emitting the reverse-scoped table before an arm # would build its filter from missing state (silently dropping rows), # so fail loudly instead of guessing. detect_reverse_scope_cycle!(table_by_name, dependencies_by_name, reverse_scope_deps, present_names, ordered) # Otherwise the cycle is a plain belongs_to cycle. Rather than # aborting the whole export, break it by emitting one cycle member; # see pick_cycle_victim for how the member is chosen. Warn so the # dropped constraint is visible. victim = pick_cycle_victim(table_by_name.values, dependencies_by_name, present_names, ordered) warn_cycle_break(logger, victim, unresolved_dependencies(victim.name, dependencies_by_name, present_names, ordered)) resolvable = [victim] end # In the normal (acyclic) path, emit every currently-resolvable table in # insertion order — preserving the historical ordering the snapshot specs # depend on. The cycle-break path emits exactly its single chosen victim. resolvable.each do |table| ordered_table_names << table.name ordered << table.name table_by_name.delete(table.name) end end ordered_table_names end |