mirror of
https://github.com/drawdb-io/drawdb.git
synced 2026-05-07 02:00:40 +08:00
wip
This commit is contained in:
@@ -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({
|
||||
>
|
||||
<Tabs lazyRender keepDOM={false} className="h-[26rem] -mt-3">
|
||||
<TabPane tab={t("scripts")} itemKey="1">
|
||||
<CodeEditor language="sql" height="9rem" filename="hello.sql" />
|
||||
<CodeEditor language="sql" height="9rem" filename="hello.sql" className="mt-2" />
|
||||
<CodeEditor
|
||||
language="sql"
|
||||
height="9rem"
|
||||
filename={`${filename}.up.sql`}
|
||||
value={migrationSQL.up}
|
||||
options={{ readOnly: true }}
|
||||
/>
|
||||
<CodeEditor
|
||||
language="sql"
|
||||
height="9rem"
|
||||
filename={`${filename}.down.sql`}
|
||||
value={migrationSQL.down}
|
||||
className="mt-2"
|
||||
options={{ readOnly: true }}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane tab={t("json_diff")} itemKey="2">
|
||||
<DiffEditor
|
||||
@@ -86,6 +195,14 @@ export default function Migration({
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
<div className="text-sm font-semibold mt-2">{t("filename")}:</div>
|
||||
<Input
|
||||
value={filename}
|
||||
placeholder={t("filename")}
|
||||
suffix={<div className="p-2">.zip</div>}
|
||||
onChange={(value) => setFilename(value)}
|
||||
field="filename"
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export const deepDiff = (original, modified, acc, path = "") => {
|
||||
for (const key of new Set([
|
||||
...Object.keys(original),
|
||||
...Object.keys(modified),
|
||||
])) {
|
||||
const newPath = path ? `${path}.${key}` : key;
|
||||
|
||||
if (
|
||||
typeof original[key] === "object" &&
|
||||
typeof modified[key] === "object" // doesnt handle removes well, searate cases for arrays and objs
|
||||
) {
|
||||
deepDiff(original[key], modified[key], acc, newPath);
|
||||
} else if (original[key] !== modified[key]) {
|
||||
acc[newPath] = {
|
||||
from: original[key] || null,
|
||||
to: modified[key] || null,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user