fix(mysql): edit enum column values
This commit is contained in:
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,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user