Given database tables and foreign key relationships, return tables in loading order (parents before children). If a cycle exists, throw IllegalStateException.
This is how Synthesized.io determines the order to process tables during data masking and subsetting — you can't insert child rows until parent rows exist.
Setup (folded above the editor — click + to view): data class FK(val child: String, val parent: String), plus main() with the test cases.
departments → users → orders → order_items plus products → order_items; verify parent-before-child for every edge.a → b → c → a must throw IllegalStateException.a → {b, c} → d; a first, d last, b and c in either order.FK("employees","employees") must throw.fun topologicalSort(tables: List<String>, fks: List<FK>): List<String> {
val inDegree = tables.associateWith { 0 }.toMutableMap()
val children = mutableMapOf<String, MutableList<String>>()
for (fk in fks) {
inDegree[fk.child] = (inDegree[fk.child] ?: 0) + 1
children.getOrPut(fk.parent) { mutableListOf() }.add(fk.child)
}
val queue = ArrayDeque<String>()
for ((table, deg) in inDegree) if (deg == 0) queue.add(table)
val result = mutableListOf<String>()
while (queue.isNotEmpty()) {
val table = queue.removeFirst()
result.add(table)
for (child in children[table].orEmpty()) {
inDegree[child] = inDegree[child]!! - 1
if (inDegree[child] == 0) queue.add(child)
}
}
if (result.size != tables.size)
throw IllegalStateException("Cycle detected: ${tables - result.toSet()}")
return result
}
static List<String> topologicalSort(List<String> tables, List<FK> fks) {
Map<String, Integer> inDegree = new HashMap<>();
for (String t : tables) inDegree.put(t, 0);
Map<String, List<String>> children = new HashMap<>();
for (FK fk : fks) {
inDegree.merge(fk.child, 1, Integer::sum);
children.computeIfAbsent(fk.parent, k -> new ArrayList<>()).add(fk.child);
}
Deque<String> queue = new ArrayDeque<>();
for (var e : inDegree.entrySet()) if (e.getValue() == 0) queue.add(e.getKey());
List<String> result = new ArrayList<>();
while (!queue.isEmpty()) {
String t = queue.removeFirst();
result.add(t);
for (String c : children.getOrDefault(t, List.of())) {
int d = inDegree.get(c) - 1;
inDegree.put(c, d);
if (d == 0) queue.add(c);
}
}
if (result.size() != tables.size())
throw new IllegalStateException("Cycle detected");
return result;
}