UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

74 lines (72 loc) 4.55 kB
import { useLogger } from "../../logger/index.js"; import { getDefaultIndexName } from "../../utils/get-default-index-name.js"; import { createInspector } from "@directus/schema"; //#region src/database/migrations/20210518A-add-foreign-key-constraints.ts async function up(knex) { const logger = useLogger(); const inspector = createInspector(knex); const foreignKeys = await inspector.foreignKeys(); const constraintsToAdd = (await knex.select("id", "many_collection", "many_field", "one_collection").from("directus_relations")).filter((relation) => { return !!foreignKeys.find((fk) => fk.table === relation?.many_collection && fk.column === relation?.many_field) === false; }); const corruptedRelations = []; for (const constraint of constraintsToAdd) { if (!constraint.one_collection) continue; if (await inspector.hasTable(constraint.many_collection) === false || await inspector.hasTable(constraint.one_collection) === false) { logger.warn(`Ignoring ${constraint.many_collection}.${constraint.many_field}<->${constraint.one_collection}. Tables don't exist.`); corruptedRelations.push(constraint.id); continue; } const currentPrimaryKeyField = await inspector.primary(constraint.many_collection); const relatedPrimaryKeyField = await inspector.primary(constraint.one_collection); if (constraint.many_field === currentPrimaryKeyField) { logger.warn(`Illegal relationship ${constraint.many_collection}.${constraint.many_field}<->${constraint.one_collection} encountered. Many field equals collections primary key.`); corruptedRelations.push(constraint.id); continue; } if (!currentPrimaryKeyField || !relatedPrimaryKeyField) continue; const rowsWithIllegalFKValues = await knex.select(`main.${currentPrimaryKeyField}`).from({ main: constraint.many_collection }).leftJoin({ related: constraint.one_collection }, `main.${constraint.many_field}`, `related.${relatedPrimaryKeyField}`).whereNull(`related.${relatedPrimaryKeyField}`); if (rowsWithIllegalFKValues.length > 0) { const ids = rowsWithIllegalFKValues.map((row) => row[currentPrimaryKeyField]); try { await knex(constraint.many_collection).update({ [constraint.many_field]: null }).whereIn(currentPrimaryKeyField, ids); } catch { logger.error(`${constraint.many_collection}.${constraint.many_field} contains illegal foreign keys which couldn't be set to NULL. Please fix these references and rerun this migration to complete the upgrade.`); if (ids.length < 25) logger.error(`Items with illegal foreign keys: ${ids.join(", ")}`); else logger.error(`Items with illegal foreign keys: ${ids.slice(0, 25).join(", ")} and ${ids.length} others`); throw "Migration aborted"; } } const columnInfo = await inspector.columnInfo(constraint.many_collection, constraint.many_field); const relatedColumnInfo = await inspector.columnInfo(constraint.one_collection, relatedPrimaryKeyField); try { await knex.schema.alterTable(constraint.many_collection, (table) => { if (columnInfo.data_type !== relatedColumnInfo.data_type && columnInfo.data_type === "int" && relatedColumnInfo.data_type === "int unsigned") table.specificType(constraint.many_field, "int unsigned").alter(); const indexName = getDefaultIndexName("foreign", constraint.many_collection, constraint.many_field); const builder = table.foreign(constraint.many_field, indexName).references(relatedPrimaryKeyField).inTable(constraint.one_collection); if (constraint.many_collection !== constraint.one_collection) builder.onDelete("SET NULL"); }); } catch (err) { logger.warn(`Couldn't add foreign key constraint for ${constraint.many_collection}.${constraint.many_field}<->${constraint.one_collection}`); logger.warn(err); } } if (corruptedRelations.length > 0) logger.warn(`Encountered one or more corrupted relationships. Please check the following rows in "directus_relations": ${corruptedRelations.join(", ")}`); } async function down(knex) { const logger = useLogger(); const relations = await knex.select("many_collection", "many_field", "one_collection").from("directus_relations"); for (const relation of relations) { if (!relation.one_collection) continue; try { await knex.schema.alterTable(relation.many_collection, (table) => { table.dropForeign([relation.many_field]); }); } catch (err) { logger.warn(`Couldn't drop foreign key constraint for ${relation.many_collection}.${relation.many_field}<->${relation.one_collection}`); logger.warn(err); } } } //#endregion export { down, up };