Files
bun-mariadb/src/lib/schema/handle-db-schema-tables.ts
T
2026-10-14 01:47:55 +01:00

151 lines
5.0 KiB
TypeScript

import _ from "lodash";
import { AppData } from "../../data/app-data";
import type {
BUN_MARIADB_INFORMATION_SCHEMA_TABLES,
CreateDBSchemaParams,
} from "../../types";
import handleDBSchemaTable from "./handle-db-schema-table";
import getExistingTablesFromTablesManagerTable from "./get-existing-tables-from-tables-manager-table";
import MariaDBQuoteGen from "./mariadb-quote-gen";
import resolveTable from "./resolve-table";
import { ensureForeignKeys } from "./sync-foreign-keys";
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
import schemaCondition from "./schema-condition";
import { removeDbManagerTable } from "./upsert-db-manager-table";
export default async function handleDBSchemaTables(
params: CreateDBSchemaParams,
) {
const { db_schema, config } = params;
/**
* Grab Tables that have been recorded in the Schema
* Manager Table
*/
const existing_schema_tables =
await getExistingTablesFromTablesManagerTable(params);
const schemaCond = schemaCondition(config);
const existing_live_tables =
await querySchemaRows<BUN_MARIADB_INFORMATION_SCHEMA_TABLES>({
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME != ?`,
values: [...schemaCond.values, AppData["DbSchemaManagerTableName"]],
config,
});
for (let i = 0; i < db_schema.tables.length; i++) {
const table = db_schema.tables[i];
if (!table) continue;
const existing_table = existing_schema_tables.find(
(t) => t == table.tableName,
);
const existing_live_table = existing_live_tables.find(
(t) => t.TABLE_NAME == table.tableName,
);
await handleDBSchemaTable({
...params,
table,
db_manager_table_name: existing_table,
existing_live_table,
});
}
/**
* Foreign keys last: every referenced table now exists, so forward
* references, self references and cycles between tables all resolve.
*/
for (const table of db_schema.tables) {
if (!table) continue;
await ensureForeignKeys({
table: resolveTable(table, db_schema),
config,
});
}
/**
* Drop tables tracked by the manager but no longer in the schema.
* Skip drops when remaining schema tables still reference the table via FK
* (e.g. external tables like `users` that are referenced but not managed).
*/
const schemaTableNames = db_schema.tables.map((t) => t.tableName);
const tablesToDrop = _.uniq(
existing_schema_tables.filter(
(tableName): tableName is string =>
Boolean(tableName) && !schemaTableNames.includes(tableName!),
),
);
if (tablesToDrop.length === 0) {
return;
}
const fkRows = await querySchemaRows<{
TABLE_NAME: string;
REFERENCED_TABLE_NAME: string;
}>({
query: `SELECT TABLE_NAME, REFERENCED_TABLE_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE ${schemaCond.where} AND REFERENCED_TABLE_NAME IS NOT NULL`,
values: schemaCond.values,
config,
});
const referencedByRemaining = new Map<string, string[]>();
for (const row of fkRows) {
if (
!row.REFERENCED_TABLE_NAME ||
!tablesToDrop.includes(row.REFERENCED_TABLE_NAME)
) {
continue;
}
// Referenced by a table we are keeping
if (
schemaTableNames.includes(row.TABLE_NAME) ||
!tablesToDrop.includes(row.TABLE_NAME)
) {
const list =
referencedByRemaining.get(row.REFERENCED_TABLE_NAME) || [];
if (!list.includes(row.TABLE_NAME)) {
list.push(row.TABLE_NAME);
}
referencedByRemaining.set(row.REFERENCED_TABLE_NAME, list);
}
}
const safeToDrop: string[] = [];
for (const tableName of tablesToDrop) {
const dependents = referencedByRemaining.get(tableName);
if (dependents && dependents.length > 0) {
console.warn(
`Skipping drop of table \`${tableName}\`: still referenced by ${dependents.join(", ")}. Removing from schema manager tracking only.`,
);
await removeDbManagerTable({ tableName, config });
continue;
}
safeToDrop.push(tableName);
}
if (safeToDrop.length === 0) {
return;
}
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
try {
for (const tableName of safeToDrop) {
console.log(`Dropping table: ${tableName}`);
await runSchemaQuery({
query: `DROP TABLE IF EXISTS ${MariaDBQuoteGen(tableName)}`,
config,
});
await removeDbManagerTable({ tableName, config });
}
} finally {
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
}
}