Module: Exwiw::DetermineTableProcessingOrder

Defined in:
lib/exwiw/determine_table_processing_order.rb

Class Method Summary collapse

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.



66
67
68
# File 'lib/exwiw/determine_table_processing_order.rb', line 66

def compute_table_dependencies(table)
  table.belongs_tos.map(&:table_name)
end

.run(tables, logger: nil) ⇒ Array<String>

Returns sorted table names.

Parameters:

  • tables (Array<Exwiw::TableConfig>)

    tables

  • logger (Logger, nil) (defaults to: nil)

    receives a warning when a cycle has to be broken

Returns:

  • (Array<String>)

    sorted table names



12
13
14
15
16
17
18
19
20
21
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
# File 'lib/exwiw/determine_table_processing_order.rb', line 12

def run(tables, logger: nil)
  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

  # Only belongs_to relations whose target is also in this run constrain the
  # order. A belongs_to 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, present_names, ordered).empty?
    end

    if resolvable.empty?
      # No table has all its (in-run) dependencies satisfied, yet tables
      # remain: the belongs_to graph has a genuine cycle and no strict
      # topological order exists. Rather than aborting the whole export, break
      # the cycle 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, present_names, ordered)
      warn_cycle_break(logger, victim, unresolved_dependencies(victim, 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