import _ from "lodash"; import createDBManagerTable from "./create-db-manager-table"; import dropRemovedTables from "./drop-removed-tables"; import getExistingTablesFromTablesManagerTable from "./get-existing-tables-from-tables-manager-table"; import handleDBSchemaTable from "./handle-db-schema-table"; import orderDBSchema from "./order-db-schema"; /** * # Sync the database to the schema * @description Changed tables are rebuilt by copying into a new table and * dropping the old one. With foreign keys ON, `DROP TABLE` runs an implicit * `DELETE FROM` that fires `ON DELETE CASCADE` on child tables, so keys are * disabled for the sync and checked before committing. (`PRAGMA * foreign_keys` is a no-op inside a transaction.) With `dry_run` the * transaction is rolled back, so `changes` is the exact plan. */ export default function createDBSchema(params) { const { db, dry_run } = params; /** * Reorder Tables (parents before children with FKs) */ const db_schema = orderDBSchema(params); db.run("PRAGMA foreign_keys = OFF;"); db.run("BEGIN"); try { /** * Create Schema Manager Table */ createDBManagerTable({ db }); const existing_tables = getExistingTablesFromTablesManagerTable({ db, }); /** * Create or update tables */ for (const table of db_schema.tables) { handleDBSchemaTable({ ...params, db_schema, table, existing_tables, }); } /** * Drop tables that no longer exist in schema */ dropRemovedTables({ ...params, existing_tables, schema_tables: db_schema.tables.map((t) => t.tableName), }); const fkViolations = db.query(`PRAGMA foreign_key_check`).all(); if (fkViolations.length) { const tables = _.uniq(fkViolations.map((v) => `${v.table} -> ${v.parent}`)); throw new Error(`Foreign key violations after schema sync: ${tables.join(", ")}`); } db.run(dry_run ? "ROLLBACK" : "COMMIT"); } catch (error) { if (db.inTransaction) db.run("ROLLBACK"); throw error; } finally { db.run("PRAGMA foreign_keys = ON;"); } }