import _ from "lodash"; import { AppData } from "../../data/app-data"; import handleDBSchemaTable from "./handle-db-schema-table"; import getExistingTablesFromTablesManagerTable from "./get-existing-tables-from-tables-manager-table"; import MariaDBQuoteGen from "./mariadb-quote-gen"; 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) { 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, }); } /** * 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) => Boolean(tableName) && !schemaTableNames.includes(tableName))); if (tablesToDrop.length === 0) { return; } const fkRows = await querySchemaRows({ 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 = []; 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 }); } }