fix(mysql): edit enum column values

This commit is contained in:
t8y2 committed 2026-07-10 16:10:54 +08:00
1 parent ea8b3f9800
commit 8c7e34a6de
14 files changed
+283 -54

No files matched your search

@@ -1,5 +1,16 @@
import { describe, expect, it } from "vitest";
import { combineDataTypeForDatabase, createColumnDrafts, dataTypeLengthInputValue, isDataTypeLengthDisabled, isMysqlCharacterDataType, isSqlServerIdentityCompatibleDataType, splitDataType } from "@/lib/table/tableStructureEditorState";
import {
combineDataTypeForDatabase,
createColumnDrafts,
dataTypeLengthInputValue,
isDataTypeLengthDisabled,
isMysqlCharacterDataType,
isMysqlEnumDataType,
isSqlServerIdentityCompatibleDataType,
mysqlEnumDataType,
rehydrateColumnDraftsFromMetadata,
splitDataType,
} from "@/lib/table/tableStructureEditorState";
describe("tableStructureEditorState", () => {
it("keeps mysql unsigned attributes in the editable base type", () => {
@@ -28,6 +39,55 @@ describe("tableStructureEditorState", () => {
expect(dataTypeLengthInputValue("mysql", "set('manual','auto')")).toBe("");
});
it("hydrates mysql enum values into an editable canonical type", () => {
const [draft] = createColumnDrafts(
[
{
name: "status",
data_type: "enum",
enum_values: ["", "pending", "it's", "path\\name"],
is_nullable: false,
column_default: "'pending'",
is_primary_key: false,
extra: null,
},
],
"mysql",
);
expect(draft?.enumValues).toEqual(["", "pending", "it's", "path\\name"]);
expect(draft?.dataType).toBe("enum('','pending','it''s','path\\\\name')");
expect(draft?.original?.data_type).toBe(draft?.dataType);
});
it("builds mysql enum types without confusing values with length", () => {
expect(isMysqlEnumDataType("mysql", "ENUM('a','b')")).toBe(true);
expect(isMysqlEnumDataType("postgres", "enum")).toBe(false);
expect(mysqlEnumDataType(["", "a'b", "a\\b"])).toBe("enum('','a''b','a\\\\b')");
});
it("rehydrates enum values into drafts saved before enum editing existed", () => {
const metadata = {
name: "status",
data_type: "enum",
enum_values: ["pending", "active"],
is_nullable: false,
column_default: "'pending'",
is_primary_key: false,
extra: null,
};
const [legacyDraft] = createColumnDrafts([metadata], "mysql");
legacyDraft!.dataType = "enum";
legacyDraft!.enumValues = undefined;
legacyDraft!.original = { ...metadata };
const [rehydrated] = rehydrateColumnDraftsFromMetadata([legacyDraft!], [metadata], "mysql");
expect(rehydrated?.enumValues).toEqual(["pending", "active"]);
expect(rehydrated?.dataType).toBe("enum('pending','active')");
expect(rehydrated?.original?.data_type).toBe("enum('pending','active')");
});
it("does not expose Oracle-like integer display widths as editable length", () => {
expect(isDataTypeLengthDisabled("dameng", "integer")).toBe(true);
expect(dataTypeLengthInputValue("dameng", "integer(11)")).toBe("");
@@ -27,3 +27,7 @@ export function resolveHeaderColumnType({ tableColumnType, resultColumnTypes, ac
const fromResult = resultColumnTypes?.[actualColIdx]?.trim();
return fromResult ? fromResult : undefined;
}
export function compactHeaderColumnType(dataType: string): string {
return /^enum\s*\(/i.test(dataType.trim()) ? "enum" : dataType;
}
@@ -71,6 +71,7 @@ function templateColumn(createId: () => string, name: string, dataType: string,
id: `new:${createId()}`,
name,
dataType,
enumValues: [],
isNullable,
defaultValue,
comment,
@@ -20,6 +20,7 @@ export interface EditableStructureColumn {
id: string;
name: string;
dataType: string;
enumValues?: string[];
isNullable: boolean;
defaultValue: string;
comment: string;
@@ -296,6 +296,16 @@ export function getDataTypeOptions(dbType: DatabaseType | undefined): string[] {
return DATA_TYPE_OPTIONS[key] ?? [];
}
export function isMysqlEnumDataType(dbType: DatabaseType | undefined, dataType: string): boolean {
return dbType === "mysql" && splitDataType(dataType).baseType.trim().toLowerCase() === "enum";
}
export function mysqlEnumDataType(values: readonly string[]): string {
// Match MySQL's canonical ENUM literal escaping, including values returned by SHOW CREATE TABLE.
const literals = values.map((value) => `'${value.replace(/\\/g, "\\\\").replace(/'/g, "''")}'`);
return `enum(${literals.join(",")})`;
}
export interface ColumnEditorControls {
length: boolean;
nullable: boolean;
@@ -606,10 +616,13 @@ function columnDefaultForEditor(column: ColumnInfo, databaseType?: DatabaseType)
export function createColumnDrafts(columns: ColumnInfo[], databaseType?: DatabaseType): EditableStructureColumn[] {
return columns.map((column, index) => {
const defaultValue = columnDefaultForEditor(column, databaseType);
const enumValues = isMysqlEnumDataType(databaseType, column.data_type) ? [...(column.enum_values ?? [])] : undefined;
const dataType = enumValues?.length ? mysqlEnumDataType(enumValues) : column.data_type;
return {
id: `existing:${column.name}`,
name: column.name,
dataType: column.data_type,
dataType,
enumValues,
isNullable: column.is_nullable,
defaultValue,
comment: column.comment ?? "",
@@ -617,7 +630,7 @@ export function createColumnDrafts(columns: ColumnInfo[], databaseType?: Databas
characterSet: column.character_set ?? "",
collation: column.collation ?? "",
extra: parseExtraToColumnExtra(column.extra, databaseType),
original: { ...column, column_default: column.column_default === null ? null : defaultValue },
original: { ...column, data_type: dataType, column_default: column.column_default === null ? null : defaultValue },
originalPosition: index,
markedForDrop: false,
};
@@ -674,12 +687,15 @@ export function rehydrateColumnDraftsFromMetadata(draftColumns: EditableStructur
const metadataIndex = findColumnDraftByName(metadataDrafts, candidates, usedMetadataIndexes);
if (metadataIndex === undefined) return column;
usedMetadataIndexes.add(metadataIndex);
if (!needsHydration) return column;
const metadataDraft = metadataDrafts[metadataIndex]!;
const shouldHydrateEnum = isMysqlEnumDataType(databaseType, column.dataType) && column.enumValues === undefined && metadataDraft.enumValues !== undefined;
if (!needsHydration && !shouldHydrateEnum) return column;
return {
...column,
original: column.original ?? metadataDraft.original,
dataType: shouldHydrateEnum ? metadataDraft.dataType : column.dataType,
enumValues: column.enumValues ?? metadataDraft.enumValues,
original: shouldHydrateEnum ? metadataDraft.original : (column.original ?? metadataDraft.original),
originalPosition: column.originalPosition ?? metadataDraft.originalPosition,
};
});