diff --git a/src/components/table.jsx b/src/components/table.jsx index a82c51a..293bd19 100644 --- a/src/components/table.jsx +++ b/src/components/table.jsx @@ -41,6 +41,7 @@ import { TableContext, UndoRedoContext, } from "../pages/editor"; +import { getSize, hasPrecision, isSized } from "../utils"; export default function Table(props) { const [isHovered, setIsHovered] = useState(false); @@ -380,34 +381,35 @@ export default function Table(props) { if (value === "ENUM" || value === "SET") { updateField(props.tableData.id, j, { type: value, + default: "", values: [], increment: incr, }); - } else if (value === "VARCHAR") { + } else if (isSized(value) || hasPrecision(value)) { updateField(props.tableData.id, j, { type: value, - length: 255, + size: getSize(value), increment: incr, }); } else if ( - f.type === "BLOB" || - f.type === "JSON" || - f.type === "GEOMETRY" || - f.type === "TEXT" || + value === "BLOB" || + value === "JSON" || + value === "UUID" || + value === "TEXT" || incr ) { updateField(props.tableData.id, j, { type: value, increment: incr, default: "", - length: "", + size: "", values: [], }); } else { updateField(props.tableData.id, j, { type: value, increment: incr, - length: "", + size: "", values: [], }); } @@ -481,8 +483,8 @@ export default function Table(props) { disabled={ f.type === "BLOB" || f.type === "JSON" || - f.type === "GEOMETRY" || f.type === "TEXT" || + f.type === "UUID" || f.increment } onChange={(value) => @@ -556,26 +558,23 @@ export default function Table(props) { /> )} - {(f.type === "VARCHAR" || f.type === "CHAR") && ( + {isSized(f.type) && ( <> -
Length
+
Size
updateField(props.tableData.id, j, { - length: value, + size: value, }) } onFocus={(e) => - setEditField({ length: e.target.value }) + setEditField({ size: e.target.value }) } onBlur={(e) => { - if (e.target.value === editField.length) return; + if (e.target.value === editField.size) return; setUndoStack((prev) => [ ...prev, { @@ -585,8 +584,48 @@ export default function Table(props) { tid: props.tableData.id, fid: j, undo: editField, - redo: { length: e.target.value }, - message: `Edit table field length to "${e.target.value}"`, + redo: { size: e.target.value }, + message: `Edit table field size to ${e.target.value}`, + }, + ]); + setRedoStack([]); + }} + /> + + )} + {hasPrecision(f.type) && ( + <> +
Precision
+ + updateField(props.tableData.id, j, { + size: value, + }) + } + onFocus={(e) => + setEditField({ size: e.target.value }) + } + onBlur={(e) => { + if (e.target.value === editField.size) return; + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.TABLE, + component: "field", + tid: props.tableData.id, + fid: j, + undo: editField, + redo: { size: e.target.value }, + message: `Edit table field precision to ${e.target.value}`, }, ]); setRedoStack([]); diff --git a/src/components/table_overview.jsx b/src/components/table_overview.jsx index 062355b..c204b43 100644 --- a/src/components/table_overview.jsx +++ b/src/components/table_overview.jsx @@ -36,6 +36,7 @@ import { IllustrationNoContentDark, } from "@douyinfe/semi-illustrations"; import { SelectContext, TableContext, UndoRedoContext } from "../pages/editor"; +import { getSize, hasPrecision, isSized } from "../utils"; export default function TableOverview(props) { const [indexActiveKey, setIndexActiveKey] = useState(""); @@ -226,34 +227,35 @@ export default function TableOverview(props) { if (value === "ENUM" || value === "SET") { updateField(i, j, { type: value, + default: "", values: [], increment: incr, }); - } else if (value === "VARCHAR") { + } else if (isSized(value) || hasPrecision(value)) { updateField(i, j, { type: value, - length: 255, + size: getSize(value), increment: incr, }); } else if ( - f.type === "BLOB" || - f.type === "JSON" || - f.type === "GEOMETRY" || - f.type === "TEXT" || + value === "BLOB" || + value === "JSON" || + value === "UUID" || + value === "TEXT" || incr ) { - updateField(props.tableData.id, j, { + updateField(t.id, j, { type: value, increment: incr, default: "", - length: "", + size: "", values: [], }); } else { updateField(i, j, { type: value, increment: incr, - length: "", + size: "", values: [], }); } @@ -327,8 +329,8 @@ export default function TableOverview(props) { disabled={ f.type === "BLOB" || f.type === "JSON" || - f.type === "GEOMETRY" || f.type === "TEXT" || + f.type === "UUID" || f.increment } onChange={(value) => @@ -403,24 +405,21 @@ export default function TableOverview(props) { /> )} - {(f.type === "VARCHAR" || f.type === "CHAR") && ( + {isSized(f.type) && ( <> -
Length
+
Size
- updateField(i, j, { length: value }) + updateField(i, j, { size: value }) } onFocus={(e) => - setEditField({ length: e.target.value }) + setEditField({ size: e.target.value }) } onBlur={(e) => { - if (e.target.value === editField.length) + if (e.target.value === editField.size) return; setUndoStack((prev) => [ ...prev, @@ -431,8 +430,47 @@ export default function TableOverview(props) { tid: i, fid: j, undo: editField, - redo: { length: e.target.value }, - message: `Edit table field length to ${e.target.value}`, + redo: { size: e.target.value }, + message: `Edit table field size to ${e.target.value}`, + }, + ]); + setRedoStack([]); + }} + /> + + )} + {hasPrecision(f.type) && ( + <> +
Precision
+ + updateField(i, j, { size: value }) + } + onFocus={(e) => + setEditField({ size: e.target.value }) + } + onBlur={(e) => { + if (e.target.value === editField.size) + return; + setUndoStack((prev) => [ + ...prev, + { + action: Action.EDIT, + element: ObjectType.TABLE, + component: "field", + tid: i, + fid: j, + undo: editField, + redo: { size: e.target.value }, + message: `Edit table field precision to ${e.target.value}`, }, ]); setRedoStack([]); @@ -601,11 +639,9 @@ export default function TableOverview(props) { .filter( (e) => !( - (e.startTableId === - props.tableData.id && + (e.startTableId === t.id && e.startFieldId === j) || - (e.endTableId === - props.tableData.id && + (e.endTableId === t.id && e.endFieldId === j) ) ) diff --git a/src/data/data.js b/src/data/data.js index 6a202f1..d1f04fd 100644 --- a/src/data/data.js +++ b/src/data/data.js @@ -5,20 +5,21 @@ const sqlDataTypes = [ "DECIMAL", "NUMERIC", "FLOAT", + "DOUBLE", "REAL", - "DOUBLE PRECISION", "CHAR", "VARCHAR", "TEXT", "DATE", "TIME", "TIMESTAMP", - "INTERVAL", + "DATETIME", "BOOLEAN", "BINARY", "VARBINARY", "BLOB", "JSON", + "UUID", "ENUM", "SET", ]; diff --git a/src/utils/index.js b/src/utils/index.js index f3550a5..1676767 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -47,6 +47,36 @@ function dataURItoBlob(dataUrl) { return new Blob([intArray], { type: mimeString }); } +function getTypeString(field) { + if (field.type === "UUID") { + return `VARCHAR(36)`; + } + if (isSized(field.type)) { + return `${field.type}(${field.size})`; + } + if (hasPrecision(field.type) && field.size !== "") { + return `${field.type}${field.size}`; + } + if (field.type === "SET" || field.type === "ENUM") { + return `${field.type}(${field.values.map((v) => `"${v}"`).join(", ")})`; + } + return field.type; +} + +function hasQuotes(type) { + return [ + "CHAR", + "VARCHAR", + "BINARY", + "VARBINARY", + "ENUM", + "DATE", + "TIME", + "TIMESTAMP", + "DATETIME", + ].includes(type); +} + function jsonToSQL(obj) { return `${obj.tables .map( @@ -58,18 +88,12 @@ function jsonToSQL(obj) { (field) => `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t\`${ field.name - }\` ${field.type}${ - field.type === "VARCHAR" - ? `(${field.length})` - : field.type === "ENUM" || field.type === "SET" - ? `(${field.values.map((v) => `"${v}"`).join(", ")})` - : "" - }${field.notNull ? " NOT NULL" : ""}${ + }\` ${getTypeString(field)}${field.notNull ? " NOT NULL" : ""}${ field.increment ? " AUTO_INCREMENT" : "" }${field.unique ? " UNIQUE" : ""}${ field.default !== "" ? ` DEFAULT ${ - (field.type === "VARCHAR" || field.type === "ENUM") && + hasQuotes(field.type) && field.default.toLowerCase() !== "null" ? `"${field.default}"` : `${field.default}` @@ -115,6 +139,37 @@ function arrayIsEqual(arr1, arr2) { return JSON.stringify(arr1) === JSON.stringify(arr2); } +function isSized(type) { + return ["CHAR", "VARCHAR", "BINARY", "VARBINARY", "TEXT", "FLOAT"].includes( + type + ); +} + +function hasPrecision(type) { + return ["DOUBLE", "NUMERIC", "DECIMAL"].includes(type); +} + +function getSize(type) { + switch (type) { + case "CHAR": + case "BINARY": + return 1; + case "VARCHAR": + case "VARBINARY": + return 255; + case "TEXT": + return 65535; + default: + return ""; + } +} + +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( + str + ); +} + function checkDefault(field) { if (field.default === "") return true; @@ -122,17 +177,51 @@ function checkDefault(field) { case "INT": case "BIGINT": case "SMALLINT": - return /^\d*$/.test(field.default); + return /^-?\d*$/.test(field.default); case "ENUM": case "SET": return field.values.includes(field.default); case "CHAR": case "VARCHAR": - return field.default.length <= field.length; + return field.default.length <= field.size; + case "BINARY": + case "VARBINARY": + return ( + field.default.length <= field.size && /^[01]+$/.test(field.default) + ); case "BOOLEAN": return ( field.default.trim() === "false" || field.default.trim() === "true" ); + case "FLOAT": + case "DECIMAL": + case "DOUBLE": + case "NUMERIC": + case "REAL": + return /^-?\d*.?\d+$/.test(field.default); + case "DATE": + return validateDateStr(field.default); + case "TIME": + return /^(?:[01]?\d|2[0-3]):[0-5]?\d:[0-5]?\d$/.test(field.default); + case "TIMESTAMP": + 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; + } + const content = field.default.split(" "); + const date = content[0].split("-"); + + return parseInt(date[0]) >= 1970 && parseInt(date[0]) <= 2038; + case "DATETIME": + if (!/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(field.default)) { + return false; + } + const c = field.default.split(" "); + const d = c[0].split("-"); + + return parseInt(d[0]) >= 1000 && parseInt(d[0]) <= 9999; default: return true; } @@ -180,6 +269,18 @@ function validateDiagram(diagram) { ); } + if (field.notNull && field.default.toLowerCase() === "null") { + issues.push( + `"${field.name}" field of table "${table.name}" is NOT NULL but has default NULL` + ); + } + + 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 { @@ -254,4 +355,8 @@ export { jsonToSQL, validateDiagram, arrayIsEqual, + isSized, + getSize, + hasPrecision, + validateDateStr, };