From ad19a645f10e6313fdd6bd85d701aa81121be9eb Mon Sep 17 00:00:00 2001 From: kishansinghifs1 Date: Sat, 14 Jun 2025 11:24:34 +0530 Subject: [PATCH 1/5] feat: add support for PostgreSQL table inheritance in schema export --- .../EditorSidePanel/TablesTab/TableField.jsx | 16 +- .../EditorSidePanel/TablesTab/TableInfo.jsx | 35 +++- src/data/schemas.js | 4 + src/utils/exportSQL/postgres.js | 166 ++++++++++-------- 4 files changed, 148 insertions(+), 73 deletions(-) diff --git a/src/components/EditorSidePanel/TablesTab/TableField.jsx b/src/components/EditorSidePanel/TablesTab/TableField.jsx index 7c744f421..be8f32f0f 100644 --- a/src/components/EditorSidePanel/TablesTab/TableField.jsx +++ b/src/components/EditorSidePanel/TablesTab/TableField.jsx @@ -8,7 +8,7 @@ import { dbToTypes } from "../../../data/datatypes"; import { DragHandle } from "../../SortableList/DragHandle"; import FieldDetails from "./FieldDetails"; -export default function TableField({ data, tid, index }) { +export default function TableField({ data, tid, index, inherited }) { const { updateField } = useDiagram(); const { types } = useTypes(); const { enums } = useEnums(); @@ -21,11 +21,16 @@ export default function TableField({ data, tid, index }) { return (
-
+
updateField(tid, data.id, { name: value })} onFocus={(e) => setEditField({ name: e.target.value })} @@ -49,7 +54,12 @@ export default function TableField({ data, tid, index }) { ]); setRedoStack([]); }} + readOnly={inherited} + style={inherited ? { backgroundColor: "#f5f5f5" } : {}} /> + {inherited && ( + Inherited + )}
t.id !== data.id) + .map((t) => ({ label: t.name, value: t.name }))} + onChange={(value) => updateTable(data.id, { inherits: value })} + placeholder="Select parent tables" + style={{ width: "100%" }} + /> +
+ )}
{t("name")}:
setSaveState(State.SAVING)} renderItem={(item, i) => ( - + )} /> {data.indices.length > 0 && ( diff --git a/src/data/schemas.js b/src/data/schemas.js index b8c112c4f..1946a9259 100644 --- a/src/data/schemas.js +++ b/src/data/schemas.js @@ -56,6 +56,10 @@ export const tableSchema = { }, color: { type: "string", pattern: "^#[0-9a-fA-F]{6}$" }, }, + inherits: { + type: "array", + items: { type: ["string", "integer"] }, + }, required: ["id", "name", "x", "y", "fields", "comment", "indices", "color"], }; diff --git a/src/utils/exportSQL/postgres.js b/src/utils/exportSQL/postgres.js index c4b7b1289..64641e307 100644 --- a/src/utils/exportSQL/postgres.js +++ b/src/utils/exportSQL/postgres.js @@ -1,12 +1,13 @@ import { escapeQuotes, exportFieldComment, parseDefault } from "./shared"; - import { dbToTypes } from "../../data/datatypes"; export function toPostgres(diagram) { const enumStatements = diagram.enums .map( (e) => - `CREATE TYPE "${e.name}" AS ENUM (\n${e.values.map((v) => `\t'${v}'`).join(",\n")}\n);\n`, + `CREATE TYPE "${e.name}" AS ENUM (\n${e.values + .map((v) => `\t'${v}'`) + .join(",\n")}\n);\n` ) .join("\n"); @@ -16,81 +17,108 @@ export function toPostgres(diagram) { `CREATE TYPE ${type.name} AS (\n${type.fields .map((f) => `\t${f.name} ${f.type}`) .join(",\n")}\n);\n\n${ - type.comment && type.comment.trim() !== "" - ? `\nCOMMENT ON TYPE "${type.name}" IS '${escapeQuotes(type.comment)}';\n\n` + type.comment?.trim() + ? `COMMENT ON TYPE "${type.name}" IS '${escapeQuotes(type.comment)}';\n` : "" - }`, + }` ) .join("\n"); - return `${enumStatements}${enumStatements.trim() !== "" ? `\n${typeStatements}` : typeStatements}${diagram.tables - .map( - (table) => - `CREATE TABLE "${table.name}" (\n${table.fields - .map( - (field) => - `${exportFieldComment(field.comment)}\t"${ - field.name - }" ${field.type}${ - field.size !== undefined && field.size !== "" - ? "(" + field.size + ")" - : "" - }${field.isArray ? " ARRAY" : ""}${field.notNull ? " NOT NULL" : ""}${field.unique ? " UNIQUE" : ""}${ - field.increment ? " GENERATED BY DEFAULT AS IDENTITY" : "" - }${ - field.default.trim() !== "" - ? ` DEFAULT ${parseDefault(field, diagram.database)}` - : "" - }${ - field.check === "" || - !dbToTypes[diagram.database][field.type].hasCheck - ? "" - : ` CHECK(${field.check})` - }`, - ) - .join(",\n")}${ - table.fields.filter((f) => f.primary).length > 0 - ? `,\n\tPRIMARY KEY(${table.fields - .filter((f) => f.primary) - .map((f) => `"${f.name}"`) - .join(", ")})` - : "" - }\n);${ - table.comment.trim() !== "" - ? `\nCOMMENT ON TABLE "${table.name}" IS '${escapeQuotes(table.comment)}';\n` - : "" - }${table.fields + const tableStatements = diagram.tables + .map((table) => { + const inheritsClause = + Array.isArray(table.inherits) && table.inherits.length > 0 + ? `\n) INHERITS (${table.inherits.map((parent) => `"${parent}"`).join(", ")})` + : ")"; + + const inheritedFieldNames = Array.from( + new Set( + (Array.isArray(table.inherits) ? table.inherits : []) + .map((parentName) => { + const parent = diagram.tables.find((t) => t.name === parentName); + return parent ? parent.fields.map((f) => f.name) : []; + }) + .flat() + ) + ); + + const ownFields = table.fields.filter((f) => !inheritedFieldNames.includes(f.name)); + + const fieldDefinitions = ownFields + .map( + (field) => + `${exportFieldComment(field.comment)}\t"${ + field.name + }" ${field.type}${ + field.size ? `(${field.size})` : "" + }${field.isArray ? " ARRAY" : ""}${field.notNull ? " NOT NULL" : ""}${field.unique ? " UNIQUE" : ""}${ + field.increment ? " GENERATED BY DEFAULT AS IDENTITY" : "" + }${ + field.default?.trim() + ? ` DEFAULT ${parseDefault(field, diagram.database)}` + : "" + }${ + field.check && + dbToTypes[diagram.database][field.type]?.hasCheck + ? ` CHECK(${field.check})` + : "" + }` + ) + .join(",\n"); + + const primaryKeyClause = ownFields.some((f) => f.primary) + ? `,\n\tPRIMARY KEY(${ownFields + .filter((f) => f.primary) + .map((f) => `"${f.name}"`) + .join(", ")})` + : ""; + + const commentStatements = [ + table.comment?.trim() + ? `COMMENT ON TABLE "${table.name}" IS '${escapeQuotes(table.comment)}';` + : "", + ...ownFields .map((field) => - field.comment.trim() !== "" - ? `COMMENT ON COLUMN ${table.name}.${field.name} IS '${escapeQuotes(field.comment)}';\n` - : "", + field.comment?.trim() + ? `COMMENT ON COLUMN "${table.name}"."${field.name}" IS '${escapeQuotes(field.comment)}';` + : "" ) - .join("")}${table.indices - .map( - (i) => - `\nCREATE ${i.unique ? "UNIQUE " : ""}INDEX "${ - i.name - }"\nON "${table.name}" (${i.fields - .map((f) => `"${f}"`) - .join(", ")});`, - ) - .join("\n")}\n`, - ) - .join("\n")}${diagram.references + .filter(Boolean), + ].join("\n"); + + const indexStatements = table.indices + .map( + (i) => + `CREATE ${i.unique ? "UNIQUE " : ""}INDEX "${i.name}"\nON "${table.name}" (${i.fields + .map((f) => `"${f}"`) + .join(", ")});` + ) + .join("\n"); + + return `CREATE TABLE "${table.name}" (\n${fieldDefinitions}${primaryKeyClause}${inheritsClause};\n${commentStatements}\n${indexStatements}`; + }) + .join("\n\n"); + + const foreignKeyStatements = diagram.references .map((r) => { - const { name: startName, fields: startFields } = diagram.tables.find( - (t) => t.id === r.startTableId, - ); + 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 { name: endName, fields: endFields } = diagram.tables.find( - (t) => t.id === r.endTableId, - ); + if (!startTable || !endTable || !startField || !endField) return ""; - return `\nALTER TABLE "${startName}"\nADD FOREIGN KEY("${ - startFields.find((f) => f.id === r.startFieldId)?.name - }") REFERENCES "${endName}"("${ - endFields.find((f) => f.id === r.endFieldId)?.name - }")\nON UPDATE ${r.updateConstraint.toUpperCase()} ON DELETE ${r.deleteConstraint.toUpperCase()};`; + 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()};`; }) - .join("\n")}`; + .filter(Boolean) + .join("\n"); + + return [ + enumStatements, + enumStatements.trim() && typeStatements ? "\n" + typeStatements : typeStatements, + tableStatements, + foreignKeyStatements, + ] + .filter(Boolean) + .join("\n\n"); } From d9ebced5903481fc50a68bcf6fa12adead55bc6f Mon Sep 17 00:00:00 2001 From: kishansinghifs1 Date: Wed, 2 Jul 2025 14:46:19 +0530 Subject: [PATCH 2/5] fixed the suggested changes in the inheritance feature --- .../EditorSidePanel/TablesTab/TableField.jsx | 266 ++++++++---------- .../EditorSidePanel/TablesTab/TableInfo.jsx | 58 ++-- src/data/schemas.js | 2 +- src/utils/exportSQL/postgres.js | 10 +- src/utils/issues.js | 46 +-- 5 files changed, 179 insertions(+), 203 deletions(-) diff --git a/src/components/EditorSidePanel/TablesTab/TableField.jsx b/src/components/EditorSidePanel/TablesTab/TableField.jsx index be8f32f0f..3f6e54c69 100644 --- a/src/components/EditorSidePanel/TablesTab/TableField.jsx +++ b/src/components/EditorSidePanel/TablesTab/TableField.jsx @@ -1,12 +1,13 @@ import { useMemo, useState } from "react"; import { Action, ObjectType } from "../../../data/constants"; -import { Input, Button, Popover, Select } from "@douyinfe/semi-ui"; +import { Input, Button, Popover, Select, Tooltip } from "@douyinfe/semi-ui"; import { IconMore, IconKeyStroked } from "@douyinfe/semi-icons"; import { useEnums, useDiagram, useTypes, useUndoRedo } from "../../../hooks"; import { useTranslation } from "react-i18next"; import { dbToTypes } from "../../../data/datatypes"; import { DragHandle } from "../../SortableList/DragHandle"; import FieldDetails from "./FieldDetails"; +import { getIssues } from "../../../utils/issues"; export default function TableField({ data, tid, index, inherited }) { const { updateField } = useDiagram(); @@ -18,190 +19,149 @@ export default function TableField({ data, tid, index, inherited }) { const [editField, setEditField] = useState({}); const table = useMemo(() => tables.find((t) => t.id === tid), [tables, tid]); + const allIssues = useMemo(() => getIssues({ tables, types, enums, relationships: [], database }), [tables, types, enums, database]); + + const fieldIssues = useMemo(() => { + if (!table || !data) return []; + return allIssues.filter((issue) => + typeof issue === "string" && + issue.includes(table.name) && + (data.name === "" || issue.includes(data.name)) + ); + }, [allIssues, table, data]); + + const reportOverride = (fieldKey, newVal) => { + if (inherited && data[fieldKey] !== newVal) { + fieldIssues.push( + t("inherited_field_override", { + tableName: table.name, + fieldName: data.name || "(unnamed)", + }) + ); + } + }; + + const recordUndo = (key, oldVal, newVal) => { + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.TABLE, + component: "field", + tid, + fid: data.id, + undo: { [key]: oldVal }, + redo: { [key]: newVal }, + message: t("edit_table", { + tableName: table.name, + extra: "[field]", + }), + }, + ]); + setRedoStack([]); + }; + return (
-
- updateField(tid, data.id, { name: value })} - onFocus={(e) => setEditField({ name: e.target.value })} - onBlur={(e) => { - if (e.target.value === editField.name) return; - setUndoStack((prev) => [ - ...prev, - { - action: Action.EDIT, - element: ObjectType.TABLE, - component: "field", - tid: tid, - fid: data.id, - undo: editField, - redo: { name: e.target.value }, - message: t("edit_table", { - tableName: table.name, - extra: "[field]", - }), - }, - ]); - setRedoStack([]); - }} - readOnly={inherited} - style={inherited ? { backgroundColor: "#f5f5f5" } : {}} - /> - {inherited && ( - Inherited - )} + + {/* Field name input */} +
+ i.includes("field name")) || (inherited ? t("inherited_field_cannot_be_modified") : "") }> + updateField(tid, data.id, { name: value })} + onFocus={(e) => setEditField({ name: e.target.value })} + onBlur={(e) => { + const newName = e.target.value; + if (newName === editField.name) return; + reportOverride("name", newName); + recordUndo("name", editField.name, newName); + updateField(tid, data.id, { name: newName }); + }} + /> +
-
- ({ label: value, value })), + ...types.map((type) => ({ label: type.name.toUpperCase(), value: type.name.toUpperCase() })), + ...enums.map((type) => ({ label: type.name.toUpperCase(), value: type.name.toUpperCase() })), + ]} + filter + value={data.type} + validateStatus={data.type === "" ? "error" : "default"} + placeholder={t("type")} + onChange={(value) => { + if (value === data.type) return; + reportOverride("type", value); + recordUndo("type", data.type, value); + const canIncr = !!dbToTypes[database][value]?.canIncrement; + const updated = { type: value, - increment: incr, - default: "", + increment: data.increment && canIncr, size: "", values: [], - }); - } else if (dbToTypes[database][value].hasCheck) { - updateField(tid, data.id, { - type: value, + default: dbToTypes[database][value]?.hasDefault ? data.default : "", check: "", - increment: incr, - }); - } else { - updateField(tid, data.id, { - type: value, - increment: incr, - size: "", - values: [], - }); - } - }} - /> + }; + if (["ENUM", "SET"].includes(value)) { + updated.values = [...(data.values || [])]; + updated.default = ""; + } else if ( + dbToTypes[database][value]?.isSized || + dbToTypes[database][value]?.hasPrecision + ) { + updated.size = dbToTypes[database][value].defaultSize; + } + updateField(tid, data.id, updated); + }} + /> +
+ + {/* Not null toggle */}
+ + {/* Primary key toggle */}
+ + {/* Field settings */}
0 ? data.inherits @@ -39,23 +38,8 @@ export default function TableInfo({ data }) { return (
- {database === DB.POSTGRES && ( -
-
Inherits:
-
+ { - setTables((prev) => { - return prev.map((t) => - t.id === data.id ? { ...t, fields: newFields } : t, - ); - }); - }} + onChange={(newFields) => + setTables((prev) => + prev.map((t) => + t.id === data.id ? { ...t, fields: newFields } : t + ) + ) + } afterChange={() => setSaveState(State.SAVING)} renderItem={(item, i) => ( )} /> + + {database === DB.POSTGRES && ( +
+
{t("inherits")}:
+ updateField(tid, data.id, { name: value })} - onFocus={(e) => setEditField({ name: e.target.value })} - onBlur={(e) => { - const newName = e.target.value; - if (newName === editField.name) return; - reportOverride("name", newName); - recordUndo("name", editField.name, newName); - updateField(tid, data.id, { name: newName }); - }} - /> - +
+ updateField(tid, data.id, { name: value })} + onFocus={(e) => setEditField({ name: e.target.value })} + onBlur={(e) => { + if (e.target.value === editField.name) return; + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.TABLE, + component: "field", + tid: tid, + fid: data.id, + undo: editField, + redo: { name: e.target.value }, + message: t("edit_table", { + tableName: table.name, + extra: "[field]", + }), + }, + ]); + setRedoStack([]); + }} + />
- {/* Field type select */}
- i.includes("field type")) || ""}> - ({ + label: value, + value, + })), + ...types.map((type) => ({ + label: type.name.toUpperCase(), + value: type.name.toUpperCase(), + })), + ...enums.map((type) => ({ + label: type.name.toUpperCase(), + value: type.name.toUpperCase(), + })), + ]} + filter + value={data.type} + validateStatus={data.type === "" ? "error" : "default"} + placeholder={t("type")} + onChange={(value) => { + if (value === data.type) return; + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.TABLE, + component: "field", + tid: tid, + fid: data.id, + undo: { type: data.type }, + redo: { type: value }, + message: t("edit_table", { + tableName: table.name, + extra: "[field]", + }), + }, + ]); + setRedoStack([]); + const incr = + data.increment && !!dbToTypes[database][value].canIncrement; + + if (value === "ENUM" || value === "SET") { + updateField(tid, data.id, { + type: value, + default: "", + values: data.values ? [...data.values] : [], + increment: incr, + }); + } else if ( + dbToTypes[database][value].isSized || + dbToTypes[database][value].hasPrecision + ) { + updateField(tid, data.id, { type: value, - increment: data.increment && canIncr, + size: dbToTypes[database][value].defaultSize, + increment: incr, + }); + } else if (!dbToTypes[database][value].hasDefault || incr) { + updateField(tid, data.id, { + type: value, + increment: incr, + default: "", size: "", values: [], - default: dbToTypes[database][value]?.hasDefault ? data.default : "", + }); + } else if (dbToTypes[database][value].hasCheck) { + updateField(tid, data.id, { + type: value, check: "", - }; - if (["ENUM", "SET"].includes(value)) { - updated.values = [...(data.values || [])]; - updated.default = ""; - } else if ( - dbToTypes[database][value]?.isSized || - dbToTypes[database][value]?.hasPrecision - ) { - updated.size = dbToTypes[database][value].defaultSize; - } - updateField(tid, data.id, updated); - }} - /> - + increment: incr, + }); + } else { + updateField(tid, data.id, { + type: value, + increment: incr, + size: "", + values: [], + }); + } + }} + />
- {/* Not null toggle */}
- {/* Primary key toggle */}
- {/* Field settings */}
t.id !== data.id) .map((t) => ({ label: t.name, value: t.name }))} onChange={(value) => updateTable(data.id, { inherits: value })} - placeholder={t("inherits_placeholder")} + placeholder={t("inherits")} className="w-full" />
diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js index 47d5b4076..6c4436225 100644 --- a/src/i18n/locales/en.js +++ b/src/i18n/locales/en.js @@ -8,6 +8,8 @@ const en = { translation: { report_bug: "Report a bug", import: "Import", + inherits: "Inherits", + merging_column_w_inherited_definition: "Column '{{fieldName}}' with inherited definition will be merged", import_from: "Import from", file: "File", new: "New", diff --git a/src/utils/exportSQL/postgres.js b/src/utils/exportSQL/postgres.js index 4ae5df111..aa571d99c 100644 --- a/src/utils/exportSQL/postgres.js +++ b/src/utils/exportSQL/postgres.js @@ -31,19 +31,9 @@ export function toPostgres(diagram) { ? `\n)\nINHERITS (${table.inherits.map((parent) => `"${parent}"`).join(", ")})` : "\n)"; - const inheritedFieldNames = Array.from( - new Set( - (Array.isArray(table.inherits) ? table.inherits : []) - .map((parentName) => { - const parent = diagram.tables.find((t) => t.name === parentName); - return parent ? parent.fields.map((f) => f.name) : []; - }) - .flat() - ) - ); - - const ownFields = table.fields.filter((f) => !inheritedFieldNames.includes(f.name)); + const ownFields = table.fields; + const fieldDefinitions = ownFields .map( (field) => diff --git a/src/utils/issues.js b/src/utils/issues.js index 3fbff3ef1..072c59320 100644 --- a/src/utils/issues.js +++ b/src/utils/issues.js @@ -4,11 +4,8 @@ import { isFunction } from "./utils"; function checkDefault(field, database) { if (field.default === "") return true; - if (isFunction(field.default)) return true; - if (!field.notNull && field.default.toLowerCase() === "null") return true; - if (!dbToTypes[database][field.type].checkDefault) return true; return dbToTypes[database][field.type].checkDefault(field); @@ -32,10 +29,14 @@ export function getIssues(diagram) { const duplicateFieldNames = {}; let hasPrimaryKey = false; + const inheritedFields= + table.inherits?.map((parentName) => { + const parent = diagram.tables.find((t) => t.name === parentName); + return parent ? parent.fields.map((f) => f.name) : []; + }).flat() || []; + table.fields.forEach((field) => { - if (field.primary) { - hasPrimaryKey = true; - } + if (field.primary) hasPrimaryKey = true; if (field.name === "") { issues.push(i18n.t("empty_field_name", { tableName: table.name })); @@ -84,14 +85,13 @@ export function getIssues(diagram) { duplicateFieldNames[field.name] = true; } - if (field.inherited && field.override && field.name !== "id") { - issues.push( - i18n.t("inherited_field_override", { - tableName: table.name, - fieldName: field.name || "(unnamed)", - }) - ); - } + if(inheritedFields.includes(field.name)) { + issues.push( + i18n.t("merging_column_w_inherited_definition", { + fieldName: field.name, + }), + ); +} }); const duplicateIndices = {}; @@ -110,18 +110,10 @@ export function getIssues(diagram) { table.indices.forEach((index) => { if (index.name.trim() === "") { - issues.push( - i18n.t("empty_index_name", { - tableName: table.name, - }) - ); + issues.push(i18n.t("empty_index_name", { tableName: table.name })); } if (index.fields.length === 0) { - issues.push( - i18n.t("empty_index", { - tableName: table.name, - }) - ); + issues.push(i18n.t("empty_index", { tableName: table.name })); } }); @@ -150,19 +142,11 @@ export function getIssues(diagram) { const duplicateFieldNames = {}; type.fields.forEach((field) => { if (field.name === "") { - issues.push( - i18n.t("empty_type_field_name", { - typeName: type.name, - }) - ); + issues.push(i18n.t("empty_type_field_name", { typeName: type.name })); } if (field.type === "") { - issues.push( - i18n.t("empty_type_field_type", { - typeName: type.name, - }) - ); + issues.push(i18n.t("empty_type_field_type", { typeName: type.name })); } else if (field.type === "ENUM" || field.type === "SET") { if (!field.values || field.values.length === 0) { issues.push( @@ -209,11 +193,7 @@ export function getIssues(diagram) { const duplicateFKName = {}; diagram.relationships.forEach((r) => { if (duplicateFKName[r.name]) { - issues.push( - i18n.t("duplicate_reference", { - refName: r.name, - }) - ); + issues.push(i18n.t("duplicate_reference", { refName: r.name })); } else { duplicateFKName[r.name] = true; } @@ -248,4 +228,4 @@ export function getIssues(diagram) { }); return issues; -} \ No newline at end of file +} From 19c3568261f468532e34d43dd1ed41d638a46a7f Mon Sep 17 00:00:00 2001 From: kishansinghifs1 Date: Fri, 4 Jul 2025 10:35:07 +0530 Subject: [PATCH 5/5] feat: finalize Postgres table inheritance support with fixes and formatting --- .../EditorSidePanel/TablesTab/TableField.jsx | 6 +-- .../EditorSidePanel/TablesTab/TableInfo.jsx | 20 ++++++++- src/i18n/locales/en.js | 2 +- src/utils/exportSQL/postgres.js | 38 ++++++++-------- src/utils/issues.js | 43 ++++++++++--------- 5 files changed, 63 insertions(+), 46 deletions(-) diff --git a/src/components/EditorSidePanel/TablesTab/TableField.jsx b/src/components/EditorSidePanel/TablesTab/TableField.jsx index 45833ff0e..13369fe90 100644 --- a/src/components/EditorSidePanel/TablesTab/TableField.jsx +++ b/src/components/EditorSidePanel/TablesTab/TableField.jsx @@ -18,15 +18,11 @@ export default function TableField({ data, tid, index, inherited }) { const [editField, setEditField] = useState({}); const table = useMemo(() => tables.find((t) => t.id === tid), [tables, tid]); - return (
-
+
t.id !== data.id) .map((t) => ({ label: t.name, value: t.name }))} - onChange={(value) => updateTable(data.id, { inherits: value })} + onChange={(value) => { + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.TABLE, + component: "self", + tid: data.id, + undo: { inherits: data.inherits }, + redo: { inherits: value }, + message: t("edit_table", { + tableName: data.name, + extra: "[inherits]", + }), + }, + ]); + setRedoStack([]); + updateTable(data.id, { inherits: value }); + }} placeholder={t("inherits")} className="w-full" /> diff --git a/src/i18n/locales/en.js b/src/i18n/locales/en.js index 6c4436225..9d401a377 100644 --- a/src/i18n/locales/en.js +++ b/src/i18n/locales/en.js @@ -9,7 +9,7 @@ const en = { report_bug: "Report a bug", import: "Import", inherits: "Inherits", - merging_column_w_inherited_definition: "Column '{{fieldName}}' with inherited definition will be merged", + merging_column_w_inherited_definition: "Column '{{fieldName}}' in table '{{tableName}}' with inherited definition will be merged", import_from: "Import from", file: "File", new: "New", diff --git a/src/utils/exportSQL/postgres.js b/src/utils/exportSQL/postgres.js index aa571d99c..49b468345 100644 --- a/src/utils/exportSQL/postgres.js +++ b/src/utils/exportSQL/postgres.js @@ -7,7 +7,7 @@ export function toPostgres(diagram) { (e) => `CREATE TYPE "${e.name}" AS ENUM (\n${e.values .map((v) => `\t'${v}'`) - .join(",\n")}\n);\n` + .join(",\n")}\n);\n`, ) .join("\n"); @@ -20,7 +20,7 @@ export function toPostgres(diagram) { type.comment?.trim() ? `COMMENT ON TYPE "${type.name}" IS '${escapeQuotes(type.comment)}';\n` : "" - }` + }`, ) .join("\n"); @@ -28,13 +28,10 @@ export function toPostgres(diagram) { .map((table) => { const inheritsClause = Array.isArray(table.inherits) && table.inherits.length > 0 - ? `\n)\nINHERITS (${table.inherits.map((parent) => `"${parent}"`).join(", ")})` + ? `\n) INHERITS (${table.inherits.map((parent) => `"${parent}"`).join(", ")})` : "\n)"; - - const ownFields = table.fields; - - const fieldDefinitions = ownFields + const fieldDefinitions = table.fields .map( (field) => `${exportFieldComment(field.comment)}\t"${ @@ -48,16 +45,15 @@ export function toPostgres(diagram) { ? ` DEFAULT ${parseDefault(field, diagram.database)}` : "" }${ - field.check && - dbToTypes[diagram.database][field.type]?.hasCheck + field.check && dbToTypes[diagram.database][field.type]?.hasCheck ? ` CHECK(${field.check})` : "" - }` + }`, ) .join(",\n"); - const primaryKeyClause = ownFields.some((f) => f.primary) - ? `,\n\tPRIMARY KEY(${ownFields + const primaryKeyClause = table.fields.some((f) => f.primary) + ? `,\n\tPRIMARY KEY(${table.fields .filter((f) => f.primary) .map((f) => `"${f.name}"`) .join(", ")})` @@ -67,11 +63,11 @@ export function toPostgres(diagram) { table.comment?.trim() ? `COMMENT ON TABLE "${table.name}" IS '${escapeQuotes(table.comment)}';` : "", - ...ownFields + ...table.fields .map((field) => field.comment?.trim() ? `COMMENT ON COLUMN "${table.name}"."${field.name}" IS '${escapeQuotes(field.comment)}';` - : "" + : "", ) .filter(Boolean), ].join("\n"); @@ -81,11 +77,11 @@ export function toPostgres(diagram) { (i) => `CREATE ${i.unique ? "UNIQUE " : ""}INDEX "${i.name}"\nON "${table.name}" (${i.fields .map((f) => `"${f}"`) - .join(", ")});` + .join(", ")});`, ) .join("\n"); - return `CREATE TABLE "${table.name}" (\n${fieldDefinitions}${primaryKeyClause}${inheritsClause};\n${commentStatements}\n${indexStatements}`; + return `CREATE TABLE "${table.name}" (\n${fieldDefinitions}${primaryKeyClause}${inheritsClause};\n\n${commentStatements}\n${indexStatements}`; }) .join("\n\n"); @@ -93,7 +89,9 @@ export function toPostgres(diagram) { .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 startField = startTable?.fields.find( + (f) => f.id === r.startFieldId, + ); const endField = endTable?.fields.find((f) => f.id === r.endFieldId); if (!startTable || !endTable || !startField || !endField) return ""; @@ -105,10 +103,12 @@ export function toPostgres(diagram) { return [ enumStatements, - enumStatements.trim() && typeStatements ? "\n" + typeStatements : typeStatements, + enumStatements.trim() && typeStatements + ? "\n" + typeStatements + : typeStatements, tableStatements, foreignKeyStatements, ] .filter(Boolean) - .join("\n\n"); + .join("\n"); } diff --git a/src/utils/issues.js b/src/utils/issues.js index 072c59320..0553bb495 100644 --- a/src/utils/issues.js +++ b/src/utils/issues.js @@ -29,11 +29,13 @@ export function getIssues(diagram) { const duplicateFieldNames = {}; let hasPrimaryKey = false; - const inheritedFields= - table.inherits?.map((parentName) => { - const parent = diagram.tables.find((t) => t.name === parentName); - return parent ? parent.fields.map((f) => f.name) : []; - }).flat() || []; + const inheritedFields = + table.inherits + ?.map((parentName) => { + const parent = diagram.tables.find((t) => t.name === parentName); + return parent ? parent.fields.map((f) => f.name) : []; + }) + .flat() || []; table.fields.forEach((field) => { if (field.primary) hasPrimaryKey = true; @@ -51,7 +53,7 @@ export function getIssues(diagram) { tableName: table.name, fieldName: field.name, type: field.type, - }) + }), ); } } @@ -61,7 +63,7 @@ export function getIssues(diagram) { i18n.t("default_doesnt_match_type", { tableName: table.name, fieldName: field.name, - }) + }), ); } @@ -70,7 +72,7 @@ export function getIssues(diagram) { i18n.t("not_null_is_null", { tableName: table.name, fieldName: field.name, - }) + }), ); } @@ -79,19 +81,20 @@ export function getIssues(diagram) { i18n.t("duplicate_fields", { tableName: table.name, fieldName: field.name, - }) + }), ); } else { duplicateFieldNames[field.name] = true; } - if(inheritedFields.includes(field.name)) { - issues.push( - i18n.t("merging_column_w_inherited_definition", { - fieldName: field.name, - }), - ); -} + if (inheritedFields.includes(field.name)) { + issues.push( + i18n.t("merging_column_w_inherited_definition", { + fieldName: field.name, + tableName: table.name, + }), + ); + } }); const duplicateIndices = {}; @@ -101,7 +104,7 @@ export function getIssues(diagram) { i18n.t("duplicate_index", { tableName: table.name, indexName: index.name, - }) + }), ); } else { duplicateIndices[index.name] = true; @@ -154,7 +157,7 @@ export function getIssues(diagram) { typeName: type.name, fieldName: field.name, type: field.type, - }) + }), ); } } @@ -164,7 +167,7 @@ export function getIssues(diagram) { i18n.t("duplicate_type_fields", { typeName: type.name, fieldName: field.name, - }) + }), ); } else { duplicateFieldNames[field.name] = true; @@ -206,7 +209,7 @@ export function getIssues(diagram) { issues.push( i18n.t("circular_dependency", { refName: diagram.tables.find((t) => t.id === tableId)?.name, - }) + }), ); return; }