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
+38 -57
View File
@@ -5,82 +5,63 @@ import type {
CreateDBSchemaParams,
} from "../../types";
/**
* 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,
}: CreateDBSchemaParams): Promise<BUN_MARIADB_DatabaseSchemaType> {
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<BUN_MARIADB_TableSchemaType>();
let new_tables_start_set = new Set<BUN_MARIADB_TableSchemaType>();
let new_tables_end_set = new Set<BUN_MARIADB_TableSchemaType>();
const tables_by_name = new Map<string, BUN_MARIADB_TableSchemaType>();
for (const table of tables) {
if (table) tables_by_name.set(table.tableName, table);
}
function setParentTable(table: BUN_MARIADB_TableSchemaType) {
const fields = table.fields;
let does_table_have_foreign_keys = false;
const ordered: BUN_MARIADB_TableSchemaType[] = [];
const visited = new Set<string>();
const visiting = new Set<string>();
for (let f = 0; f < fields.length; f++) {
const field = fields[f];
if (!field) continue;
function visit(table: BUN_MARIADB_TableSchemaType) {
if (visited.has(table.tableName) || visiting.has(table.tableName)) {
return;
}
const dst_table_name = field.foreignKey?.destinationTableName;
visiting.add(table.tableName);
if (dst_table_name) {
const fk_table = tables.find(
(tb) => tb.tableName == dst_table_name,
);
for (const field of table.fields || []) {
const dst_table_name = field?.foreignKey?.destinationTableName;
if (
fk_table &&
!new_tables_start_set.has(fk_table) &&
!new_tables_end_set.has(fk_table)
) {
setParentTable(fk_table);
}
if (!dst_table_name || dst_table_name === table.tableName) {
continue;
}
new_tables_end_set.add(table);
const fk_table = tables_by_name.get(dst_table_name);
if (fk_table) {
new_tables_start_set.add(fk_table);
}
does_table_have_foreign_keys = true;
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;
}