diff --git a/src/components/EditorCanvas/Canvas.jsx b/src/components/EditorCanvas/Canvas.jsx index aed3af69a..b02daf27d 100644 --- a/src/components/EditorCanvas/Canvas.jsx +++ b/src/components/EditorCanvas/Canvas.jsx @@ -619,14 +619,28 @@ export default function Canvas() { const cardinality = getCardinality(startField, endField); + // Normalize direction: startTable must always be the FK holder (many side). + // When the user drags from the PK side (one) to the FK side (many), the + // result is ONE_TO_MANY with startTable = PK. Swap so that startTable = FK. + const isInverted = cardinality === Cardinality.ONE_TO_MANY; + const fkTableId = isInverted ? hoveredTable.tableId : linkingLine.startTableId; + const fkFieldId = isInverted ? hoveredTable.fieldId : linkingLine.startFieldId; + const refTableId = isInverted ? linkingLine.startTableId : hoveredTable.tableId; + const refFieldId = isInverted ? linkingLine.startFieldId : hoveredTable.fieldId; + const fkTableName = isInverted ? endTableName : startTableName; + const fkField = isInverted ? endField : startField; + const refTableName = isInverted ? startTableName : endTableName; + const newRelationship = { ...linkingLine, - cardinality, - endTableId: hoveredTable.tableId, - endFieldId: hoveredTable.fieldId, + cardinality: isInverted ? Cardinality.MANY_TO_ONE : cardinality, + startTableId: fkTableId, + startFieldId: fkFieldId, + endTableId: refTableId, + endFieldId: refFieldId, updateConstraint: Constraint.NONE, deleteConstraint: Constraint.NONE, - name: `fk_${startTableName}_${startField.name}_${endTableName}`, + name: `fk_${fkTableName}_${fkField.name}_${refTableName}`, id: nanoid(), }; delete newRelationship.startX; diff --git a/src/utils/exportSQL/generic.js b/src/utils/exportSQL/generic.js index 04d220839..78587e61f 100644 --- a/src/utils/exportSQL/generic.js +++ b/src/utils/exportSQL/generic.js @@ -1,6 +1,6 @@ import { DB } from "../../data/constants"; import { dbToTypes, defaultTypes } from "../../data/datatypes"; -import { escapeQuotes, getInlineFK, parseDefault } from "./shared"; +import { escapeQuotes, getInlineFK, parseDefault, resolveFKDirection } from "./shared"; export function getJsonType(f) { if (!Object.keys(defaultTypes).includes(f.type)) { @@ -229,17 +229,17 @@ export function jsonToMySQL(obj) { ) .join("\n")}\n${obj.references .map((r) => { - const { name: startName, fields: startFields } = obj.tables.find( - (t) => t.id === r.startTableId, + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const { name: fkName, fields: fkFields } = obj.tables.find( + (t) => t.id === fkTableId, ); - - const { name: endName, fields: endFields } = obj.tables.find( - (t) => t.id === r.endTableId, + const { name: refName, fields: refFields } = obj.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${ - startFields.find((f) => f.id === r.startFieldId).name - }\`) REFERENCES \`${endName}\`(\`${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE \`${fkName}\`\nADD FOREIGN KEY(\`${ + fkFields.find((f) => f.id === fkFieldId).name + }\`) REFERENCES \`${refName}\`(\`${ + refFields.find((f) => f.id === refFieldId).name }\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; }) .join("\n")}`; @@ -336,17 +336,17 @@ export function jsonToPostgreSQL(obj) { ) .join("\n")}\n${obj.references .map((r) => { - const { name: startName, fields: startFields } = obj.tables.find( - (t) => t.id === r.startTableId, + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const { name: fkName, fields: fkFields } = obj.tables.find( + (t) => t.id === fkTableId, ); - - const { name: endName, fields: endFields } = obj.tables.find( - (t) => t.id === r.endTableId, + const { name: refName, fields: refFields } = obj.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE "${startName}"\nADD FOREIGN KEY("${ - startFields.find((f) => f.id === r.startFieldId).name - }") REFERENCES "${endName}"("${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE "${fkName}"\nADD FOREIGN KEY("${ + fkFields.find((f) => f.id === fkFieldId).name + }") REFERENCES "${refName}"("${ + refFields.find((f) => f.id === refFieldId).name }")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; }) .join("\n")}`; @@ -474,17 +474,17 @@ export function jsonToMariaDB(obj) { ) .join("\n")}\n${obj.references .map((r) => { - const { name: startName, fields: startFields } = obj.tables.find( - (t) => t.id === r.startTableId, + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const { name: fkName, fields: fkFields } = obj.tables.find( + (t) => t.id === fkTableId, ); - - const { name: endName, fields: endFields } = obj.tables.find( - (t) => t.id === r.endTableId, + const { name: refName, fields: refFields } = obj.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${ - startFields.find((f) => f.id === r.startFieldId).name - }\`) REFERENCES \`${endName}\`(\`${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE \`${fkName}\`\nADD FOREIGN KEY(\`${ + fkFields.find((f) => f.id === fkFieldId).name + }\`) REFERENCES \`${refName}\`(\`${ + refFields.find((f) => f.id === refFieldId).name }\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; }) .join("\n")}`; @@ -546,17 +546,17 @@ export function jsonToSQLServer(obj) { ) .join("\n")}\n${obj.references .map((r) => { - const { name: startName, fields: startFields } = obj.tables.find( - (t) => t.id === r.startTableId, + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const { name: fkName, fields: fkFields } = obj.tables.find( + (t) => t.id === fkTableId, ); - - const { name: endName, fields: endFields } = obj.tables.find( - (t) => t.id === r.endTableId, + const { name: refName, fields: refFields } = obj.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE [${startName}]\nADD FOREIGN KEY([${ - startFields.find((f) => f.id === r.startFieldId).name - }]) REFERENCES [${endName}]([${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE [${fkName}]\nADD FOREIGN KEY([${ + fkFields.find((f) => f.id === fkFieldId).name + }]) REFERENCES [${refName}]([${ + refFields.find((f) => f.id === refFieldId).name }])\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};\nGO`; }) .join("\n")}`; @@ -619,17 +619,17 @@ export function jsonToOracleSQL(obj) { ) .join("\n\n")}\n${obj.references .map((r) => { - const { name: startName, fields: startFields } = obj.tables.find( - (t) => t.id === r.startTableId, + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const { name: fkName, fields: fkFields } = obj.tables.find( + (t) => t.id === fkTableId, ); - - const { name: endName, fields: endFields } = obj.tables.find( - (t) => t.id === r.endTableId, + const { name: refName, fields: refFields } = obj.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE "${startName}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${ - startFields.find((f) => f.id === r.startFieldId).name - }") REFERENCES "${endName}"("${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE "${fkName}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${ + fkFields.find((f) => f.id === fkFieldId).name + }") REFERENCES "${refName}"("${ + refFields.find((f) => f.id === refFieldId).name }");`; }) .join("\n")}`; diff --git a/src/utils/exportSQL/mariadb.js b/src/utils/exportSQL/mariadb.js index 59255d411..456bcea27 100644 --- a/src/utils/exportSQL/mariadb.js +++ b/src/utils/exportSQL/mariadb.js @@ -1,4 +1,4 @@ -import { escapeQuotes, parseDefault } from "./shared"; +import { escapeQuotes, parseDefault, resolveFKDirection } from "./shared"; import { dbToTypes } from "../../data/datatypes"; import { DB } from "../../data/constants"; @@ -60,17 +60,17 @@ export function toMariaDB(diagram) { ) .join("\n")}\n${diagram.references .map((r) => { - const { name: startName, fields: startFields } = diagram.tables.find( - (t) => t.id === r.startTableId, + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const { name: fkName, fields: fkFields } = diagram.tables.find( + (t) => t.id === fkTableId, ); - - const { name: endName, fields: endFields } = diagram.tables.find( - (t) => t.id === r.endTableId, + const { name: refName, fields: refFields } = diagram.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${ - startFields.find((f) => f.id === r.startFieldId).name - }\`) REFERENCES \`${endName}\`(\`${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE \`${fkName}\`\nADD FOREIGN KEY(\`${ + fkFields.find((f) => f.id === fkFieldId).name + }\`) REFERENCES \`${refName}\`(\`${ + refFields.find((f) => f.id === refFieldId).name }\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; }) .join("\n")}`; diff --git a/src/utils/exportSQL/mssql.js b/src/utils/exportSQL/mssql.js index a2354d358..a37dcf672 100644 --- a/src/utils/exportSQL/mssql.js +++ b/src/utils/exportSQL/mssql.js @@ -1,4 +1,4 @@ -import { parseDefault, escapeQuotes } from "./shared"; +import { parseDefault, escapeQuotes, resolveFKDirection } from "./shared"; import { dbToTypes } from "../../data/datatypes"; import { DB } from "../../data/constants"; @@ -94,19 +94,20 @@ export function toMSSQL(diagram) { const referencesSql = diagram.references .map((r) => { - const startTable = diagram.tables.find((t) => t.id === r.startTableId); - const endTable = diagram.tables.find((t) => t.id === r.endTableId); + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const fkTable = diagram.tables.find((t) => t.id === fkTableId); + const refTable = diagram.tables.find((t) => t.id === refTableId); - if (!startTable || !endTable) return ""; + if (!fkTable || !refTable) return ""; - const startField = startTable.fields.find((f) => f.id === r.startFieldId); - const endField = endTable.fields.find((f) => f.id === r.endFieldId); + const fkField = fkTable.fields.find((f) => f.id === fkFieldId); + const refField = refTable.fields.find((f) => f.id === refFieldId); - if (!startField || !endField) return ""; + if (!fkField || !refField) return ""; - return `\nALTER TABLE [${startTable.name}] -ADD FOREIGN KEY([${startField.name}]) -REFERENCES [${endTable.name}]([${endField.name}]) + return `\nALTER TABLE [${fkTable.name}] +ADD FOREIGN KEY([${fkField.name}]) +REFERENCES [${refTable.name}]([${refField.name}]) ON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()}; GO`; }) diff --git a/src/utils/exportSQL/mysql.js b/src/utils/exportSQL/mysql.js index f35dbda3f..637f4c452 100644 --- a/src/utils/exportSQL/mysql.js +++ b/src/utils/exportSQL/mysql.js @@ -1,7 +1,7 @@ import { escapeQuotes, parseDefault } from "./shared"; import { dbToTypes } from "../../data/datatypes"; -import { DB } from "../../data/constants"; +import { Cardinality, DB } from "../../data/constants"; function parseType(field) { let res = field.type; @@ -64,17 +64,22 @@ export function toMySQL(diagram) { ) .join("\n")}\n${diagram.references .map((r) => { - const { name: startName, fields: startFields } = diagram.tables.find( - (t) => t.id === r.startTableId, - ); + const isInverted = r.cardinality === Cardinality.ONE_TO_MANY; + const fkTableId = isInverted ? r.endTableId : r.startTableId; + const fkFieldId = isInverted ? r.endFieldId : r.startFieldId; + const refTableId = isInverted ? r.startTableId : r.endTableId; + const refFieldId = isInverted ? r.startFieldId : r.endFieldId; - const { name: endName, fields: endFields } = diagram.tables.find( - (t) => t.id === r.endTableId, + const { name: fkName, fields: fkFields } = diagram.tables.find( + (t) => t.id === fkTableId, + ); + const { name: refName, fields: refFields } = diagram.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE \`${startName}\`\nADD FOREIGN KEY(\`${ - startFields.find((f) => f.id === r.startFieldId).name - }\`) REFERENCES \`${endName}\`(\`${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE \`${fkName}\`\nADD FOREIGN KEY(\`${ + fkFields.find((f) => f.id === fkFieldId).name + }\`) REFERENCES \`${refName}\`(\`${ + refFields.find((f) => f.id === refFieldId).name }\`)\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; }) .join("\n")}`; diff --git a/src/utils/exportSQL/oraclesql.js b/src/utils/exportSQL/oraclesql.js index 0a44c5650..811b04675 100644 --- a/src/utils/exportSQL/oraclesql.js +++ b/src/utils/exportSQL/oraclesql.js @@ -1,5 +1,5 @@ import { dbToTypes } from "../../data/datatypes"; -import { parseDefault } from "./shared"; +import { parseDefault, resolveFKDirection } from "./shared"; export function toOracleSQL(diagram) { return `${diagram.tables @@ -47,16 +47,17 @@ export function toOracleSQL(diagram) { ) .join("\n")}\n${diagram.references .map((r) => { - const { name: startName, fields: startFields } = diagram.tables.find( - (t) => t.id === r.startTableId, + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const { name: fkName, fields: fkFields } = diagram.tables.find( + (t) => t.id === fkTableId, ); - const { name: endName, fields: endFields } = diagram.tables.find( - (t) => t.id === r.endTableId, + const { name: refName, fields: refFields } = diagram.tables.find( + (t) => t.id === refTableId, ); - return `ALTER TABLE "${startName}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${ - startFields.find((f) => f.id === r.startFieldId).name - }") REFERENCES "${endName}" ("${ - endFields.find((f) => f.id === r.endFieldId).name + return `ALTER TABLE "${fkName}"\nADD CONSTRAINT "${r.name}" FOREIGN KEY ("${ + fkFields.find((f) => f.id === fkFieldId).name + }") REFERENCES "${refName}" ("${ + refFields.find((f) => f.id === refFieldId).name }")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; }) .join("\n")}`; diff --git a/src/utils/exportSQL/postgres.js b/src/utils/exportSQL/postgres.js index 3dc6c0e13..9d4cd2488 100644 --- a/src/utils/exportSQL/postgres.js +++ b/src/utils/exportSQL/postgres.js @@ -1,4 +1,4 @@ -import { escapeQuotes, exportFieldComment, parseDefault } from "./shared"; +import { escapeQuotes, exportFieldComment, parseDefault, resolveFKDirection } from "./shared"; import { dbToTypes } from "../../data/datatypes"; export function toPostgres(diagram) { @@ -87,16 +87,15 @@ export function toPostgres(diagram) { const foreignKeyStatements = diagram.references .map((r) => { - const startTable = diagram.tables.find((t) => t.id === r.startTableId); - const endTable = diagram.tables.find((t) => t.id === r.endTableId); - const startField = startTable?.fields.find( - (f) => f.id === r.startFieldId, - ); - const endField = endTable?.fields.find((f) => f.id === r.endFieldId); + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + const fkTable = diagram.tables.find((t) => t.id === fkTableId); + const refTable = diagram.tables.find((t) => t.id === refTableId); + const fkField = fkTable?.fields.find((f) => f.id === fkFieldId); + const refField = refTable?.fields.find((f) => f.id === refFieldId); - if (!startTable || !endTable || !startField || !endField) return ""; + if (!fkTable || !refTable || !fkField || !refField) return ""; - return `ALTER TABLE "${startTable.name}"\nADD FOREIGN KEY("${startField.name}") REFERENCES "${endTable.name}"("${endField.name}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; + return `ALTER TABLE "${fkTable.name}"\nADD FOREIGN KEY("${fkField.name}") REFERENCES "${refTable.name}"("${refField.name}")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; }) .filter(Boolean) .join("\n"); diff --git a/src/utils/exportSQL/shared.js b/src/utils/exportSQL/shared.js index 64100bc7f..11217aa67 100644 --- a/src/utils/exportSQL/shared.js +++ b/src/utils/exportSQL/shared.js @@ -1,6 +1,6 @@ import { isFunction, isKeyword } from "../utils"; -import { DB } from "../../data/constants"; +import { Cardinality, DB } from "../../data/constants"; import { dbToTypes } from "../../data/datatypes"; export function parseDefault(field, database = DB.GENERIC) { @@ -30,17 +30,28 @@ export function exportFieldComment(comment) { .join(""); } +export function resolveFKDirection(r) { + const isInverted = r.cardinality === Cardinality.ONE_TO_MANY; + return { + fkTableId: isInverted ? r.endTableId : r.startTableId, + fkFieldId: isInverted ? r.endFieldId : r.startFieldId, + refTableId: isInverted ? r.startTableId : r.endTableId, + refFieldId: isInverted ? r.startFieldId : r.endFieldId, + }; +} + export function getInlineFK(table, obj) { let fks = []; obj.references.forEach((r) => { - if (r.startTableId === table.id) { + const { fkTableId, fkFieldId, refTableId, refFieldId } = resolveFKDirection(r); + if (fkTableId === table.id) { fks.push( - `\tFOREIGN KEY ("${table.fields.find((f) => f.id === r.startFieldId)?.name}") REFERENCES "${ - obj.tables.find((t) => t.id === r.endTableId)?.name + `\tFOREIGN KEY ("${table.fields.find((f) => f.id === fkFieldId)?.name}") REFERENCES "${ + obj.tables.find((t) => t.id === refTableId)?.name }"("${ obj.tables - .find((t) => t.id === r.endTableId) - .fields.find((f) => f.id === r.endFieldId)?.name + .find((t) => t.id === refTableId) + .fields.find((f) => f.id === refFieldId)?.name }")\n\tON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()}`, ); }