Handle defaults and sizes for all datatypes

This commit is contained in:
1ilit 2023-09-19 15:51:22 +03:00
parent 9311c1fbaf
commit 10fae6d518
4 changed files with 238 additions and 57 deletions

View File

@ -41,6 +41,7 @@ import {
TableContext, TableContext,
UndoRedoContext, UndoRedoContext,
} from "../pages/editor"; } from "../pages/editor";
import { getSize, hasPrecision, isSized } from "../utils";
export default function Table(props) { export default function Table(props) {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
@ -380,34 +381,35 @@ export default function Table(props) {
if (value === "ENUM" || value === "SET") { if (value === "ENUM" || value === "SET") {
updateField(props.tableData.id, j, { updateField(props.tableData.id, j, {
type: value, type: value,
default: "",
values: [], values: [],
increment: incr, increment: incr,
}); });
} else if (value === "VARCHAR") { } else if (isSized(value) || hasPrecision(value)) {
updateField(props.tableData.id, j, { updateField(props.tableData.id, j, {
type: value, type: value,
length: 255, size: getSize(value),
increment: incr, increment: incr,
}); });
} else if ( } else if (
f.type === "BLOB" || value === "BLOB" ||
f.type === "JSON" || value === "JSON" ||
f.type === "GEOMETRY" || value === "UUID" ||
f.type === "TEXT" || value === "TEXT" ||
incr incr
) { ) {
updateField(props.tableData.id, j, { updateField(props.tableData.id, j, {
type: value, type: value,
increment: incr, increment: incr,
default: "", default: "",
length: "", size: "",
values: [], values: [],
}); });
} else { } else {
updateField(props.tableData.id, j, { updateField(props.tableData.id, j, {
type: value, type: value,
increment: incr, increment: incr,
length: "", size: "",
values: [], values: [],
}); });
} }
@ -481,8 +483,8 @@ export default function Table(props) {
disabled={ disabled={
f.type === "BLOB" || f.type === "BLOB" ||
f.type === "JSON" || f.type === "JSON" ||
f.type === "GEOMETRY" ||
f.type === "TEXT" || f.type === "TEXT" ||
f.type === "UUID" ||
f.increment f.increment
} }
onChange={(value) => onChange={(value) =>
@ -556,26 +558,23 @@ export default function Table(props) {
/> />
</> </>
)} )}
{(f.type === "VARCHAR" || f.type === "CHAR") && ( {isSized(f.type) && (
<> <>
<div className="font-semibold">Length</div> <div className="font-semibold">Size</div>
<InputNumber <InputNumber
className="my-2 w-full" className="my-2 w-full"
placeholder="Set length" placeholder="Set length"
value={f.length} value={f.size}
validateStatus={
f.length === "" ? "error" : "default"
}
onChange={(value) => onChange={(value) =>
updateField(props.tableData.id, j, { updateField(props.tableData.id, j, {
length: value, size: value,
}) })
} }
onFocus={(e) => onFocus={(e) =>
setEditField({ length: e.target.value }) setEditField({ size: e.target.value })
} }
onBlur={(e) => { onBlur={(e) => {
if (e.target.value === editField.length) return; if (e.target.value === editField.size) return;
setUndoStack((prev) => [ setUndoStack((prev) => [
...prev, ...prev,
{ {
@ -585,8 +584,48 @@ export default function Table(props) {
tid: props.tableData.id, tid: props.tableData.id,
fid: j, fid: j,
undo: editField, undo: editField,
redo: { length: e.target.value }, redo: { size: e.target.value },
message: `Edit table field length to "${e.target.value}"`, message: `Edit table field size to ${e.target.value}`,
},
]);
setRedoStack([]);
}}
/>
</>
)}
{hasPrecision(f.type) && (
<>
<div className="font-semibold">Precision</div>
<Input
className="my-2 w-full"
placeholder="Set precision: (size, d)"
validateStatus={
/^\(\d+,\s*\d+\)$|^$/.test(f.size)
? "default"
: "error"
}
value={f.size}
onChange={(value) =>
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([]); setRedoStack([]);

View File

@ -36,6 +36,7 @@ import {
IllustrationNoContentDark, IllustrationNoContentDark,
} from "@douyinfe/semi-illustrations"; } from "@douyinfe/semi-illustrations";
import { SelectContext, TableContext, UndoRedoContext } from "../pages/editor"; import { SelectContext, TableContext, UndoRedoContext } from "../pages/editor";
import { getSize, hasPrecision, isSized } from "../utils";
export default function TableOverview(props) { export default function TableOverview(props) {
const [indexActiveKey, setIndexActiveKey] = useState(""); const [indexActiveKey, setIndexActiveKey] = useState("");
@ -226,34 +227,35 @@ export default function TableOverview(props) {
if (value === "ENUM" || value === "SET") { if (value === "ENUM" || value === "SET") {
updateField(i, j, { updateField(i, j, {
type: value, type: value,
default: "",
values: [], values: [],
increment: incr, increment: incr,
}); });
} else if (value === "VARCHAR") { } else if (isSized(value) || hasPrecision(value)) {
updateField(i, j, { updateField(i, j, {
type: value, type: value,
length: 255, size: getSize(value),
increment: incr, increment: incr,
}); });
} else if ( } else if (
f.type === "BLOB" || value === "BLOB" ||
f.type === "JSON" || value === "JSON" ||
f.type === "GEOMETRY" || value === "UUID" ||
f.type === "TEXT" || value === "TEXT" ||
incr incr
) { ) {
updateField(props.tableData.id, j, { updateField(t.id, j, {
type: value, type: value,
increment: incr, increment: incr,
default: "", default: "",
length: "", size: "",
values: [], values: [],
}); });
} else { } else {
updateField(i, j, { updateField(i, j, {
type: value, type: value,
increment: incr, increment: incr,
length: "", size: "",
values: [], values: [],
}); });
} }
@ -327,8 +329,8 @@ export default function TableOverview(props) {
disabled={ disabled={
f.type === "BLOB" || f.type === "BLOB" ||
f.type === "JSON" || f.type === "JSON" ||
f.type === "GEOMETRY" ||
f.type === "TEXT" || f.type === "TEXT" ||
f.type === "UUID" ||
f.increment f.increment
} }
onChange={(value) => onChange={(value) =>
@ -403,24 +405,21 @@ export default function TableOverview(props) {
/> />
</> </>
)} )}
{(f.type === "VARCHAR" || f.type === "CHAR") && ( {isSized(f.type) && (
<> <>
<div className="font-semibold">Length</div> <div className="font-semibold">Size</div>
<InputNumber <InputNumber
className="my-2 w-full" className="my-2 w-full"
placeholder="Set length" placeholder="Set length"
validateStatus={ value={f.size}
f.length === "" ? "error" : "default"
}
value={f.length}
onChange={(value) => onChange={(value) =>
updateField(i, j, { length: value }) updateField(i, j, { size: value })
} }
onFocus={(e) => onFocus={(e) =>
setEditField({ length: e.target.value }) setEditField({ size: e.target.value })
} }
onBlur={(e) => { onBlur={(e) => {
if (e.target.value === editField.length) if (e.target.value === editField.size)
return; return;
setUndoStack((prev) => [ setUndoStack((prev) => [
...prev, ...prev,
@ -431,8 +430,47 @@ export default function TableOverview(props) {
tid: i, tid: i,
fid: j, fid: j,
undo: editField, undo: editField,
redo: { length: e.target.value }, redo: { size: e.target.value },
message: `Edit table field length to ${e.target.value}`, message: `Edit table field size to ${e.target.value}`,
},
]);
setRedoStack([]);
}}
/>
</>
)}
{hasPrecision(f.type) && (
<>
<div className="font-semibold">Precision</div>
<Input
className="my-2 w-full"
placeholder="Set precision: (size, d)"
validateStatus={
/^\(\d+,\s*\d+\)$|^$/.test(f.size)
? "default"
: "error"
}
value={f.size}
onChange={(value) =>
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([]); setRedoStack([]);
@ -601,11 +639,9 @@ export default function TableOverview(props) {
.filter( .filter(
(e) => (e) =>
!( !(
(e.startTableId === (e.startTableId === t.id &&
props.tableData.id &&
e.startFieldId === j) || e.startFieldId === j) ||
(e.endTableId === (e.endTableId === t.id &&
props.tableData.id &&
e.endFieldId === j) e.endFieldId === j)
) )
) )

View File

@ -5,20 +5,21 @@ const sqlDataTypes = [
"DECIMAL", "DECIMAL",
"NUMERIC", "NUMERIC",
"FLOAT", "FLOAT",
"DOUBLE",
"REAL", "REAL",
"DOUBLE PRECISION",
"CHAR", "CHAR",
"VARCHAR", "VARCHAR",
"TEXT", "TEXT",
"DATE", "DATE",
"TIME", "TIME",
"TIMESTAMP", "TIMESTAMP",
"INTERVAL", "DATETIME",
"BOOLEAN", "BOOLEAN",
"BINARY", "BINARY",
"VARBINARY", "VARBINARY",
"BLOB", "BLOB",
"JSON", "JSON",
"UUID",
"ENUM", "ENUM",
"SET", "SET",
]; ];

View File

@ -47,6 +47,36 @@ function dataURItoBlob(dataUrl) {
return new Blob([intArray], { type: mimeString }); 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) { function jsonToSQL(obj) {
return `${obj.tables return `${obj.tables
.map( .map(
@ -58,18 +88,12 @@ function jsonToSQL(obj) {
(field) => (field) =>
`${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t\`${ `${field.comment === "" ? "" : `\t-- ${field.comment}\n`}\t\`${
field.name field.name
}\` ${field.type}${ }\` ${getTypeString(field)}${field.notNull ? " NOT NULL" : ""}${
field.type === "VARCHAR"
? `(${field.length})`
: field.type === "ENUM" || field.type === "SET"
? `(${field.values.map((v) => `"${v}"`).join(", ")})`
: ""
}${field.notNull ? " NOT NULL" : ""}${
field.increment ? " AUTO_INCREMENT" : "" field.increment ? " AUTO_INCREMENT" : ""
}${field.unique ? " UNIQUE" : ""}${ }${field.unique ? " UNIQUE" : ""}${
field.default !== "" field.default !== ""
? ` DEFAULT ${ ? ` DEFAULT ${
(field.type === "VARCHAR" || field.type === "ENUM") && hasQuotes(field.type) &&
field.default.toLowerCase() !== "null" field.default.toLowerCase() !== "null"
? `"${field.default}"` ? `"${field.default}"`
: `${field.default}` : `${field.default}`
@ -115,6 +139,37 @@ function arrayIsEqual(arr1, arr2) {
return JSON.stringify(arr1) === JSON.stringify(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) { function checkDefault(field) {
if (field.default === "") return true; if (field.default === "") return true;
@ -122,17 +177,51 @@ function checkDefault(field) {
case "INT": case "INT":
case "BIGINT": case "BIGINT":
case "SMALLINT": case "SMALLINT":
return /^\d*$/.test(field.default); return /^-?\d*$/.test(field.default);
case "ENUM": case "ENUM":
case "SET": case "SET":
return field.values.includes(field.default); return field.values.includes(field.default);
case "CHAR": case "CHAR":
case "VARCHAR": 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": case "BOOLEAN":
return ( return (
field.default.trim() === "false" || field.default.trim() === "true" 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: default:
return true; 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]) { if (duplicateFieldNames[field.name]) {
issues.push(`Duplicate table fields in table "${table.name}"`); issues.push(`Duplicate table fields in table "${table.name}"`);
} else { } else {
@ -254,4 +355,8 @@ export {
jsonToSQL, jsonToSQL,
validateDiagram, validateDiagram,
arrayIsEqual, arrayIsEqual,
isSized,
getSize,
hasPrecision,
validateDateStr,
}; };