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({ 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(); 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 }); } }