Fix bugs, improve schema gen

This commit is contained in:
2026-10-14 01:47:55 +01:00
parent 2ddf450b9f
commit 155263a79f
25 changed files with 571 additions and 172 deletions
+39 -47
View File
@@ -1,57 +1,49 @@
import _ from "lodash";
/**
* Topologically order tables so referenced (parent) tables come before the
* tables that reference them. Declaration order is kept wherever the FK
* graph allows it.
*
* - Self references (e.g. `comments.parent_comment_id`) are ignored.
* - References to tables outside the schema are ignored.
* - Cycles do not throw: a table already on the current path is skipped,
* so every table is still emitted exactly once. Foreign keys are created
* after all tables exist, so cycles are safe to sync.
*/
export default async function orderDBSchema({ db_schema, }) {
let new_db_schema = _.cloneDeep(db_schema);
const new_db_schema = _.cloneDeep(db_schema);
const tables = new_db_schema.tables;
let new_tables_set = new Set();
let new_tables_start_set = new Set();
let new_tables_end_set = new Set();
function setParentTable(table) {
const fields = table.fields;
let does_table_have_foreign_keys = false;
for (let f = 0; f < fields.length; f++) {
const field = fields[f];
if (!field)
const tables_by_name = new Map();
for (const table of tables) {
if (table)
tables_by_name.set(table.tableName, table);
}
const ordered = [];
const visited = new Set();
const visiting = new Set();
function visit(table) {
if (visited.has(table.tableName) || visiting.has(table.tableName)) {
return;
}
visiting.add(table.tableName);
for (const field of table.fields || []) {
const dst_table_name = field?.foreignKey?.destinationTableName;
if (!dst_table_name || dst_table_name === table.tableName) {
continue;
const dst_table_name = field.foreignKey?.destinationTableName;
if (dst_table_name) {
const fk_table = tables.find((tb) => tb.tableName == dst_table_name);
if (fk_table &&
!new_tables_start_set.has(fk_table) &&
!new_tables_end_set.has(fk_table)) {
setParentTable(fk_table);
}
new_tables_end_set.add(table);
if (fk_table) {
new_tables_start_set.add(fk_table);
}
does_table_have_foreign_keys = true;
}
const fk_table = tables_by_name.get(dst_table_name);
if (fk_table) {
visit(fk_table);
}
}
return { does_table_have_foreign_keys };
visiting.delete(table.tableName);
visited.add(table.tableName);
ordered.push(table);
}
for (let i = 0; i < tables.length; i++) {
const table = tables[i];
if (!table) {
continue;
}
let { does_table_have_foreign_keys } = setParentTable(table);
if (does_table_have_foreign_keys) {
new_tables_end_set.add(table);
}
else {
new_tables_start_set.add(table);
}
for (const table of tables) {
if (table)
visit(table);
}
const parsed_tables = [
...Array.from(new_tables_start_set),
...Array.from(new_tables_end_set),
];
for (let nt = 0; nt < parsed_tables.length; nt++) {
const new_table = parsed_tables[nt];
if (new_table) {
new_tables_set.add(new_table);
}
}
new_db_schema.tables = Array.from(new_tables_set);
new_db_schema.tables = ordered;
return new_db_schema;
}