diff --git a/package-lock.json b/package-lock.json
index 685fee2..9f66e86 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,7 @@
"jsonschema": "^1.4.1",
"jspdf": "^2.5.1",
"lexical": "^0.12.5",
- "node-sql-parser": "^4.17.0",
+ "node-sql-parser": "^5.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",
@@ -1715,6 +1715,11 @@
"@babel/types": "^7.20.7"
}
},
+ "node_modules/@types/pegjs": {
+ "version": "0.10.6",
+ "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.6.tgz",
+ "integrity": "sha512-eLYXDbZWXh2uxf+w8sXS8d6KSoXTswfps6fvCUuVAGN8eRpfe7h9eSRydxiSJvo9Bf+GzifsDOr9TMQlmJdmkw=="
+ },
"node_modules/@types/prop-types": {
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
@@ -4368,10 +4373,11 @@
"dev": true
},
"node_modules/node-sql-parser": {
- "version": "4.17.0",
- "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-4.17.0.tgz",
- "integrity": "sha512-3IhovpmUBpcETnoKK/KBdkz2mz53kVG5E1dnqz1QuYvtzdxYZW5xaGGEvW9u6Yyy2ivwR3eUZrn9inmEVef02w==",
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-5.0.0.tgz",
+ "integrity": "sha512-hkNU1gIT8BNe8vmcsU7uYie0gzow/6AIj5KnGRBJQSZlgEu1NNuLVS11it5gAEdpmvJHelc34BwR439Iela+zQ==",
"dependencies": {
+ "@types/pegjs": "^0.10.0",
"big-integer": "^1.6.48"
},
"engines": {
diff --git a/package.json b/package.json
index 875cdbc..fa19917 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"jsonschema": "^1.4.1",
"jspdf": "^2.5.1",
"lexical": "^0.12.5",
- "node-sql-parser": "^4.17.0",
+ "node-sql-parser": "^5.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.4.1",
diff --git a/src/components/EditorHeader/Modal/ImportSource.jsx b/src/components/EditorHeader/Modal/ImportSource.jsx
index 1b86454..f5283ab 100644
--- a/src/components/EditorHeader/Modal/ImportSource.jsx
+++ b/src/components/EditorHeader/Modal/ImportSource.jsx
@@ -1,7 +1,12 @@
-import { Upload, Checkbox } from "@douyinfe/semi-ui";
+import { Upload, Checkbox, Banner } from "@douyinfe/semi-ui";
import { STATUS } from "../../../data/constants";
-export default function ImportSource({ importData, setImportData, setError }) {
+export default function ImportSource({
+ importData,
+ setImportData,
+ error,
+ setError,
+}) {
return (
Overwrite existing diagram
+
+ {error.type === STATUS.ERROR ? (
+ {error.message}
}
+ />
+ ) : error.type === STATUS.OK ? (
+ {error.message} }
+ />
+ ) : (
+ error.type === STATUS.WARNING && (
+ {error.message}}
+ />
+ )
+ )}
+
);
diff --git a/src/components/EditorHeader/Modal/Modal.jsx b/src/components/EditorHeader/Modal/Modal.jsx
index 462503b..f2f870b 100644
--- a/src/components/EditorHeader/Modal/Modal.jsx
+++ b/src/components/EditorHeader/Modal/Modal.jsx
@@ -114,9 +114,17 @@ export default function Modal({
try {
ast = parser.astify(importSource.src, { database: "MySQL" });
} catch (err) {
- Toast.error(
- "Could not parse the sql file. Make sure there are no syntax errors.",
- );
+ setError({
+ type: STATUS.ERROR,
+ message:
+ err.name +
+ " [Ln " +
+ err.location.start.line +
+ ", Col " +
+ err.location.start.column +
+ "]: " +
+ err.message,
+ });
return;
}
@@ -124,6 +132,7 @@ export default function Modal({
if (importSource.overwrite) {
setTables(d.tables);
setRelationships(d.relationships);
+ setTransform((prev) => ({ ...prev, pan: { x: 0, y: 0 } }));
setNotes([]);
setAreas([]);
setTypes([]);
@@ -133,6 +142,7 @@ export default function Modal({
setTables((prev) => [...prev, ...d.tables]);
setRelationships((prev) => [...prev, ...d.relationships]);
}
+ setModal(MODAL.NONE);
};
const createNewDiagram = (id) => {
@@ -167,7 +177,6 @@ export default function Modal({
return;
case MODAL.IMPORT_SRC:
parseSQLAndLoadDiagram();
- setModal(MODAL.NONE);
return;
case MODAL.OPEN:
if (selectedDiagramId === 0) return;
@@ -207,6 +216,7 @@ export default function Modal({
);
diff --git a/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx b/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx
index 30b7e33..ae574ba 100644
--- a/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx
+++ b/src/components/EditorSidePanel/TablesTab/FieldDetails.jsx
@@ -80,7 +80,7 @@ export default function FieldDetails({ data, tid, index }) {
undo: editField,
redo: { values: data.values },
message: `Edit table field values to "${JSON.stringify(
- data.values
+ data.values,
)}"`,
},
]);
@@ -123,9 +123,11 @@ export default function FieldDetails({ data, tid, index }) {
Precision
updateField(tid, index, { size: value })}
diff --git a/src/utils/astToDiagram.js b/src/utils/astToDiagram.js
index 2742219..92991ad 100644
--- a/src/utils/astToDiagram.js
+++ b/src/utils/astToDiagram.js
@@ -1,9 +1,49 @@
-import { Cardinality } from "../data/constants";
+import {
+ Cardinality,
+ tableColorStripHeight,
+ tableFieldHeight,
+ tableHeaderHeight,
+} from "../data/constants";
+
+function buildSQLFromAST(ast) {
+ if (ast.type === "binary_expr") {
+ const leftSQL = buildSQLFromAST(ast.left);
+ const rightSQL = buildSQLFromAST(ast.right);
+ return `${leftSQL} ${ast.operator} ${rightSQL}`;
+ }
+
+ if (ast.type === "function") {
+ let expr = "";
+ expr = ast.name;
+ if (ast.args) {
+ expr +=
+ "(" +
+ ast.args.value
+ .map((v) => {
+ if (v.type === "column_ref") return "`" + v.column + "`";
+ if (
+ v.type === "single_quote_string" ||
+ v.type === "double_quote_string"
+ )
+ return "'" + v.value + "'";
+ return v.value;
+ })
+ .join(", ") +
+ ")";
+ }
+ return expr;
+ } else if (ast.type === "column_ref") {
+ return "`" + ast.column + "`";
+ } else if (ast.type === "expr_list") {
+ return ast.value.map((v) => v.value).join(" AND ");
+ } else {
+ return typeof ast.value === "string" ? "'" + ast.value + "'" : ast.value;
+ }
+}
export function astToDiagram(ast) {
const tables = [];
const relationships = [];
- const inlineForeignKeys = [];
ast.forEach((e) => {
if (e.type === "create") {
@@ -14,13 +54,15 @@ export function astToDiagram(ast) {
table.color = "#175e7a";
table.fields = [];
table.indices = [];
- table.x = 0;
- table.y = 0;
+ table.id = tables.length;
e.create_definitions.forEach((d) => {
if (d.resource === "column") {
const field = {};
field.name = d.column.column;
field.type = d.definition.dataType;
+ if (d.definition.expr && d.definition.expr.type === "expr_list") {
+ field.values = d.definition.expr.value.map((v) => v.value);
+ }
field.comment = "";
field.unique = false;
if (d.unique) field.unique = true;
@@ -31,39 +73,42 @@ export function astToDiagram(ast) {
field.primary = false;
if (d.primary_key) field.primary = true;
field.default = "";
- if (d.default_val) field.default = d.default_val.value.value.toString();
- if (d.definition["length"]) field.size = d.definition["length"];
+ if (d.default_val) {
+ let defaultValue = "";
+ if (d.default_val.value.type === "function") {
+ defaultValue = d.default_val.value.name;
+ if (d.default_val.value.args) {
+ defaultValue +=
+ "(" +
+ d.default_val.value.args.value
+ .map((v) => {
+ if (
+ v.type === "single_quote_string" ||
+ v.type === "double_quote_string"
+ )
+ return "'" + v.value + "'";
+ return v.value;
+ })
+ .join(", ") +
+ ")";
+ }
+ } else if (d.default_val.value.type === "null") {
+ defaultValue = "NULL";
+ } else {
+ defaultValue = d.default_val.value.value.toString();
+ }
+ field.default = defaultValue;
+ }
+ if (d.definition["length"]) {
+ if (d.definition.scale) {
+ field.size = d.definition["length"] + "," + d.definition.scale;
+ } else {
+ field.size = d.definition["length"];
+ }
+ }
field.check = "";
if (d.check) {
- let check = "";
- if (d.check.definition[0].left.column) {
- let value = d.check.definition[0].right.value;
- if (
- d.check.definition[0].right.type === "double_quote_string" ||
- d.check.definition[0].right.type === "single_quote_string"
- )
- value = "'" + value + "'";
- check =
- d.check.definition[0].left.column +
- " " +
- d.check.definition[0].operator +
- " " +
- value;
- } else {
- let value = d.check.definition[0].right.value;
- if (
- d.check.definition[0].left.type === "double_quote_string" ||
- d.check.definition[0].left.type === "single_quote_string"
- )
- value = "'" + value + "'";
- check =
- value +
- " " +
- d.check.definition[0].operator +
- " " +
- d.check.definition[0].right.column;
- }
- field.check = check;
+ field.check = buildSQLFromAST(d.check.definition[0]);
}
table.fields.push(field);
@@ -77,17 +122,58 @@ export function astToDiagram(ast) {
});
});
} else if (d.constraint_type === "FOREIGN KEY") {
- inlineForeignKeys.push({ ...d, startTable: e.table[0].table });
+ const relationship = {};
+ const startTableId = table.id;
+ const startTable = e.table[0].table;
+ const startField = d.definition[0].column;
+ const endTable = d.reference_definition.table[0].table;
+ const endField = d.reference_definition.definition[0].column;
+
+ const endTableId = tables.findIndex((t) => t.name === endTable);
+ if (endTableId === -1) return;
+
+ const endFieldId = tables[endTableId].fields.findIndex(
+ (f) => f.name === endField,
+ );
+ if (endField === -1) return;
+
+ const startFieldId = table.fields.findIndex(
+ (f) => f.name === startField,
+ );
+ if (startFieldId === -1) return;
+
+ relationship.name = startTable + "_" + startField + "_fk";
+ relationship.startTableId = startTableId;
+ relationship.endTableId = endTableId;
+ relationship.endFieldId = endFieldId;
+ relationship.startFieldId = startFieldId;
+ let updateConstraint = "No action";
+ let deleteConstraint = "No action";
+ d.reference_definition.on_action.forEach((c) => {
+ if (c.type === "on update") {
+ updateConstraint = c.value.value;
+ updateConstraint =
+ updateConstraint[0].toUpperCase() +
+ updateConstraint.substring(1);
+ } else if (c.type === "on delete") {
+ deleteConstraint = c.value.value;
+ deleteConstraint =
+ deleteConstraint[0].toUpperCase() +
+ deleteConstraint.substring(1);
+ }
+ });
+
+ relationship.updateConstraint = updateConstraint;
+ relationship.deleteConstraint = deleteConstraint;
+ relationship.cardinality = Cardinality.ONE_TO_ONE;
+ relationships.push(relationship);
}
}
});
- tables.push(table);
- tables.forEach((e, i) => {
- e.id = i;
- e.fields.forEach((f, j) => {
- f.id = j;
- });
+ table.fields.forEach((f, j) => {
+ f.id = j;
});
+ tables.push(table);
} else if (e.keyword === "index") {
const index = {};
index.name = e.index;
@@ -108,145 +194,89 @@ export function astToDiagram(ast) {
if (found !== -1) tables[found].indices.forEach((i, j) => (i.id = j));
}
} else if (e.type === "alter") {
- if (
- e.expr[0].action === "add" &&
- e.expr[0].create_definitions.constraint_type === "FOREIGN KEY"
- ) {
- const relationship = {};
- const startTable = e.table[0].table;
- const startField = e.expr[0].create_definitions.definition[0].column;
- const endTable =
- e.expr[0].create_definitions.reference_definition.table[0].table;
- const endField =
- e.expr[0].create_definitions.reference_definition.definition[0]
- .column;
- let updateConstraint = "No action";
- let deleteConstraint = "No action";
- e.expr[0].create_definitions.reference_definition.on_action.forEach(
- (c) => {
- if (c.type === "on update") {
- updateConstraint = c.value.value;
- updateConstraint =
- updateConstraint[0].toUpperCase() +
- updateConstraint.substring(1);
- } else if (c.type === "on delete") {
- deleteConstraint = c.value.value;
- deleteConstraint =
- deleteConstraint[0].toUpperCase() +
- deleteConstraint.substring(1);
- }
- }
- );
+ e.expr.forEach((expr) => {
+ if (
+ expr.action === "add" &&
+ expr.create_definitions.constraint_type === "FOREIGN KEY"
+ ) {
+ const relationship = {};
+ const startTable = e.table[0].table;
+ const startField = expr.create_definitions.definition[0].column;
+ const endTable =
+ expr.create_definitions.reference_definition.table[0].table;
+ const endField =
+ expr.create_definitions.reference_definition.definition[0].column;
+ let updateConstraint = "No action";
+ let deleteConstraint = "No action";
+ expr.create_definitions.reference_definition.on_action.forEach(
+ (c) => {
+ if (c.type === "on update") {
+ updateConstraint = c.value.value;
+ updateConstraint =
+ updateConstraint[0].toUpperCase() +
+ updateConstraint.substring(1);
+ } else if (c.type === "on delete") {
+ deleteConstraint = c.value.value;
+ deleteConstraint =
+ deleteConstraint[0].toUpperCase() +
+ deleteConstraint.substring(1);
+ }
+ },
+ );
- let startTableId = -1;
- let startFieldId = -1;
- let endTableId = -1;
- let endFieldId = -1;
+ const startTableId = tables.findIndex((t) => t.name === startTable);
+ if (startTable === -1) return;
- tables.forEach((t) => {
- if (t.name === startTable) {
- startTableId = t.id;
- return;
- }
+ const endTableId = tables.findIndex((t) => t.name === endTable);
+ if (endTableId === -1) return;
- if (t.name === endTable) {
- endTableId = t.id;
- }
- });
+ const endFieldId = tables[endTableId].fields.findIndex(
+ (f) => f.name === endField,
+ );
+ if (endField === -1) return;
- if (startTableId === -1 || endTableId === -1) return;
+ const startFieldId = tables[startTableId].fields.findIndex(
+ (f) => f.name === startField,
+ );
+ if (startFieldId === -1) return;
- tables[startTableId].fields.forEach((f) => {
- if (f.name === startField) {
- startFieldId = f.id;
- return;
- }
+ relationship.name = startTable + "_" + startField + "_fk";
+ relationship.startTableId = startTableId;
+ relationship.startFieldId = startFieldId;
+ relationship.endTableId = endTableId;
+ relationship.endFieldId = endFieldId;
+ relationship.updateConstraint = updateConstraint;
+ relationship.deleteConstraint = deleteConstraint;
+ relationship.cardinality = Cardinality.ONE_TO_ONE;
+ relationships.push(relationship);
- if (f.name === endField) {
- endFieldId = f.id;
- }
- });
-
- if (startFieldId === -1 || endFieldId === -1) return;
-
- relationship.name = startTable + "_" + startField + "_fk";
- relationship.startTableId = startTableId;
- relationship.startFieldId = startFieldId;
- relationship.endTableId = endTableId;
- relationship.endFieldId = endFieldId;
- relationship.updateConstraint = updateConstraint;
- relationship.deleteConstraint = deleteConstraint;
- relationship.cardinality = Cardinality.ONE_TO_ONE;
- relationships.push(relationship);
-
- relationships.forEach((r, i) => (r.id = i));
- }
+ relationships.forEach((r, i) => (r.id = i));
+ }
+ });
}
});
- inlineForeignKeys.forEach((fk) => {
- const relationship = {};
- const startTable = fk.startTable;
- const startField = fk.definition[0].column;
- const endTable = fk.reference_definition.table[0].table;
- const endField = fk.reference_definition.definition[0].column;
- let updateConstraint = "No action";
- let deleteConstraint = "No action";
- fk.reference_definition.on_action.forEach((c) => {
- if (c.type === "on update") {
- updateConstraint = c.value.value;
- updateConstraint =
- updateConstraint[0].toUpperCase() + updateConstraint.substring(1);
- } else if (c.type === "on delete") {
- deleteConstraint = c.value.value;
- deleteConstraint =
- deleteConstraint[0].toUpperCase() + deleteConstraint.substring(1);
- }
- });
-
- let startTableId = -1;
- let startFieldId = -1;
- let endTableId = -1;
- let endFieldId = -1;
-
- tables.forEach((t) => {
- if (t.name === startTable) {
- startTableId = t.id;
- return;
- }
-
- if (t.name === endTable) {
- endTableId = t.id;
- }
- });
-
- if (startTableId === -1 || endTableId === -1) return;
-
- tables[startTableId].fields.forEach((f) => {
- if (f.name === startField) {
- startFieldId = f.id;
- return;
- }
-
- if (f.name === endField) {
- endFieldId = f.id;
- }
- });
-
- if (startFieldId === -1 || endFieldId === -1) return;
-
- relationship.name = startTable + "_" + startField + "_fk";
- relationship.startTableId = startTableId;
- relationship.startFieldId = startFieldId;
- relationship.endTableId = endTableId;
- relationship.endFieldId = endFieldId;
- relationship.updateConstraint = updateConstraint;
- relationship.deleteConstraint = deleteConstraint;
- relationship.cardinality = Cardinality.ONE_TO_ONE;
- relationships.push(relationship);
- });
-
relationships.forEach((r, i) => (r.id = i));
+ let maxHeight = -1;
+ const tableWidth = 200;
+ const gapX = 54;
+ const gapY = 40;
+ tables.forEach((table, i) => {
+ if (i < tables.length / 2) {
+ table.x = i * tableWidth + (i + 1) * gapX;
+ table.y = gapY;
+ const height =
+ table.fields.length * tableFieldHeight +
+ tableHeaderHeight +
+ tableColorStripHeight;
+ maxHeight = Math.max(height, maxHeight);
+ } else {
+ const index = tables.length - i - 1;
+ table.x = index * tableWidth + (index + 1) * gapX;
+ table.y = maxHeight + 2 * gapY;
+ }
+ });
+
return { tables, relationships };
}
diff --git a/src/utils/issues.js b/src/utils/issues.js
index b95fa74..90e1259 100644
--- a/src/utils/issues.js
+++ b/src/utils/issues.js
@@ -1,4 +1,4 @@
-import { strHasQuotes } from "./utils";
+import { isFunction, strHasQuotes } from "./utils";
function validateDateStr(str) {
return /^(?!0000)(?!00)(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-9]|3[01])|(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31))$/.test(
@@ -9,13 +9,23 @@ function validateDateStr(str) {
function checkDefault(field) {
if (field.default === "") return true;
+ if (isFunction(field.default)) return true;
+
+ if (!field.notNull && field.default.toLowerCase() === "null") return true;
+
switch (field.type) {
case "INT":
case "BIGINT":
case "SMALLINT":
return /^-?\d*$/.test(field.default);
+ case "SET": {
+ const defaultValues = field.default.split(",");
+ for (let i = 0; i < defaultValues.length; i++) {
+ if (!field.values.includes(defaultValues[i].trim())) return false;
+ }
+ return true;
+ }
case "ENUM":
- case "SET":
return field.values.includes(field.default);
case "CHAR":
case "VARCHAR":
@@ -30,7 +40,8 @@ function checkDefault(field) {
);
case "BOOLEAN":
return (
- field.default.trim() === "false" || field.default.trim() === "true"
+ field.default.trim().toLowerCase() === "false" ||
+ field.default.trim().toLowerCase() === "true"
);
case "FLOAT":
case "DECIMAL":
@@ -55,6 +66,9 @@ function checkDefault(field) {
return parseInt(date[0]) >= 1970 && parseInt(date[0]) <= 2038;
}
case "DATETIME": {
+ if (field.default.toUpperCase() === "CURRENT_TIMESTAMP") {
+ return true;
+ }
if (!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(field.default)) {
return false;
}
@@ -116,12 +130,6 @@ export function getIssues(diagram) {
);
}
- if (field.type === "DOUBLE" && field.size !== "") {
- issues.push(
- `Specifying number of digits for floating point data types is deprecated.`,
- );
- }
-
if (duplicateFieldNames[field.name]) {
issues.push(`Duplicate table fields in table "${table.name}"`);
} else {
@@ -182,12 +190,6 @@ export function getIssues(diagram) {
}
}
- if (field.type === "DOUBLE" && field.size !== "") {
- issues.push(
- `Specifying number of digits for floating point data types is deprecated.`,
- );
- }
-
if (duplicateFieldNames[field.name]) {
issues.push(`Duplicate type fields in "${type.name}"`);
} else {
diff --git a/src/utils/toSQL.js b/src/utils/toSQL.js
index 4720a6e..40577ce 100644
--- a/src/utils/toSQL.js
+++ b/src/utils/toSQL.js
@@ -1,5 +1,5 @@
import { sqlDataTypes } from "../data/constants";
-import { strHasQuotes } from "./utils";
+import { isFunction, isKeyword, strHasQuotes } from "./utils";
export function getJsonType(f) {
if (!sqlDataTypes.includes(f.type)) {
@@ -44,10 +44,7 @@ export function getTypeString(field, dbms = "mysql", baseType = false) {
if (field.type === "UUID") {
return `VARCHAR(36)`;
}
- if (isSized(field.type)) {
- return `${field.type}(${field.size})`;
- }
- if (hasPrecision(field.type)) {
+ if (hasPrecision(field.type) || isSized(field.type)) {
return `${field.type}${field.size ? `(${field.size})` : ""}`;
}
if (field.type === "SET" || field.type === "ENUM") {
@@ -147,13 +144,16 @@ export function hasQuotes(type) {
}
export function parseDefault(field) {
- if (strHasQuotes(field.default)) {
+ if (
+ strHasQuotes(field.default) ||
+ isFunction(field.default) ||
+ isKeyword(field.default) ||
+ !hasQuotes(field.type)
+ ) {
return field.default;
}
- return hasQuotes(field.type) && field.default.toLowerCase() !== "null"
- ? `'${field.default}'`
- : `${field.default}`;
+ return `'${field.default}'`;
}
export function jsonToMySQL(obj) {
diff --git a/src/utils/utils.js b/src/utils/utils.js
index 53226a6..a51919f 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -24,3 +24,13 @@ export function strHasQuotes(str) {
(str[0] === str[str.length - 1] && str[0] === "`")
);
}
+
+const keywords = ["CURRENT_TIMESTAMP", "NULL"];
+
+export function isKeyword(str) {
+ return keywords.includes(str.toUpperCase());
+}
+
+export function isFunction(str) {
+ return /\w+\([^)]*\)$/.test(str);
+}