diff --git a/src/components/EditorHeader/SideSheet/Migration.jsx b/src/components/EditorHeader/SideSheet/Migration.jsx
index a57759c..ff986a6 100644
--- a/src/components/EditorHeader/SideSheet/Migration.jsx
+++ b/src/components/EditorHeader/SideSheet/Migration.jsx
@@ -1,11 +1,77 @@
import { useCallback, useState } from "react";
-import { Tabs, TabPane, Modal } from "@douyinfe/semi-ui";
+import { Tabs, TabPane, Modal, Input } from "@douyinfe/semi-ui";
import { DiffEditor } from "@monaco-editor/react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
-import { useSettings } from "../../../hooks";
+import { useDiagram, useSettings } from "../../../hooks";
import { compare, VERSION_FILENAME } from "../../../api/gists";
+import { deepDiff } from "../../../utils/diff";
+import { DateTime } from "luxon";
import CodeEditor from "../../CodeEditor";
+import {
+ escapeQuotes,
+ exportFieldComment,
+ parseDefault,
+} from "../../../utils/exportSQL/shared";
+import { dbToTypes } from "../../../data/datatypes";
+import { DB } from "../../../data/constants";
+
+const toTable = (table) => {
+ const inheritsClause =
+ Array.isArray(table.inherits) && table.inherits.length > 0
+ ? `\n) INHERITS (${table.inherits.map((parent) => `"${parent}"`).join(", ")})`
+ : "\n)";
+
+ const fieldDefinitions = table.fields
+ .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, DB.POSTGRES)}`
+ : ""
+ }${
+ field.check && dbToTypes[DB.POSTGRES][field.type]?.hasCheck
+ ? ` CHECK(${field.check})`
+ : ""
+ }`,
+ )
+ .join(",\n");
+
+ const primaryKeyClause = table.fields.some((f) => f.primary)
+ ? `,\n\tPRIMARY KEY(${table.fields
+ .filter((f) => f.primary)
+ .map((f) => `"${f.name}"`)
+ .join(", ")})`
+ : "";
+
+ const commentStatements = [
+ table.comment?.trim()
+ ? `COMMENT ON TABLE "${table.name}" IS '${escapeQuotes(table.comment)}';`
+ : "",
+ ...table.fields
+ .map((field) =>
+ field.comment?.trim()
+ ? `COMMENT ON COLUMN "${table.name}"."${field.name}" IS '${escapeQuotes(field.comment)}';`
+ : "",
+ )
+ .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\n${commentStatements}\n${indexStatements}`;
+};
export default function Migration({
gistId,
@@ -15,46 +81,76 @@ export default function Migration({
}) {
const { t } = useTranslation();
const { settings } = useSettings();
+ // const { tables } = useDiagram();
const [contentA, setContentA] = useState("");
const [contentB, setContentB] = useState("");
+ const [filename, setFilename] = useState(
+ `${DateTime.now().toFormat("yyyyMMddHHmmss")}-migration`,
+ );
+ const [migrationSQL, setMigrationSQL] = useState({
+ up: "",
+ down: "",
+ });
- const getDiff = useCallback(async () => {
- const acc = {};
- const { data } = await compare(
- gistId,
- VERSION_FILENAME,
- selectedVersion,
- versionToCompareTo,
- );
- setContentA(JSON.stringify(JSON.parse(data.contentA), null, 2));
- setContentB(JSON.stringify(JSON.parse(data.contentB), null, 2));
+ const generateMigrationSQL = (diff) => {
+ const keysToIgnore = ["x", "y", "id", "width", "height", "locked", "color"];
+ const elementsToIgnore = ["notes", "areas"];
- deepDiff(contentA, contentB, acc);
+ let up = [];
+ let down = [];
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [gistId, selectedVersion, versionToCompareTo]);
+ for (const [path, change] of Object.entries(diff)) {
+ const keys = path.split(".");
- const deepDiff = (original, modified, acc, path = "") => {
- for (const key of new Set([
- ...Object.keys(original),
- ...Object.keys(modified),
- ])) {
- const newPath = path ? `${path}.${key}` : key;
+ const targetField = keys[keys.length - 1];
+ if (keysToIgnore.includes(targetField)) continue;
- if (
- typeof original[key] === "object" &&
- typeof modified[key] === "object"
- ) {
- deepDiff(original[key], modified[key], acc, newPath);
- } else if (original[key] !== modified[key]) {
- acc[newPath] = {
- from: original[key] || null,
- to: modified[key] || null,
- };
+ const element = keys[0];
+ if (elementsToIgnore.includes(element)) continue;
+
+ let next = 1;
+ if (element === "tables") {
+ const tableIndex = keys[next];
+ next++;
+ if (isNaN(tableIndex)) continue;
+
+ if (keys.length === next) {
+ if (!change.from) {
+ up.push(toTable(change.to));
+ down.push(`DROP TABLE "${change.to.name}";`);
+ }
+
+ if (!change.to) {
+ up.push(`DROP TABLE "${change.from.name}";`);
+ down.push(toTable(change.from));
+ }
+ }
}
}
+
+ return { up: up.join("\n"), down: down.join("\n") };
};
+ const getDiff = useCallback(async () => {
+ try {
+ const diff = {};
+ const { data } = await compare(
+ gistId,
+ VERSION_FILENAME,
+ selectedVersion,
+ versionToCompareTo,
+ );
+ setContentA(JSON.stringify(JSON.parse(data.contentA), null, 2));
+ setContentB(JSON.stringify(JSON.parse(data.contentB), null, 2));
+
+ deepDiff(JSON.parse(data.contentB), JSON.parse(data.contentA), diff);
+ setMigrationSQL(generateMigrationSQL(diff));
+ console.log(diff);
+ } catch (error) {
+ console.error(error);
+ }
+ }, [gistId, selectedVersion, versionToCompareTo]);
+
useEffect(() => {
if (!gistId || !selectedVersion || !versionToCompareTo) return;
getDiff();
@@ -72,8 +168,21 @@ export default function Migration({
>