diff --git a/apps/desktop/src/components/structure/TableStructureEditor.vue b/apps/desktop/src/components/structure/TableStructureEditor.vue index 0555e2c89e..f9362233ea 100644 --- a/apps/desktop/src/components/structure/TableStructureEditor.vue +++ b/apps/desktop/src/components/structure/TableStructureEditor.vue @@ -70,6 +70,7 @@ import { rehydrateColumnDraftsFromMetadata, resolveInsertColumnIndex, restoreDamengLengthUnitsAfterSave, + sameStructureIndexType, splitDataType, toColumnNames, } from "@/lib/table/tableStructureEditorState"; @@ -212,7 +213,7 @@ function indexChanged(index: EditableStructureIndex): boolean { !sameList(index.columns, original.columns) || index.isUnique !== original.is_unique || !sameText(index.filter, original.filter) || - !sameText(index.indexType, original.index_type) || + !sameStructureIndexType(index.indexType, original.index_type) || !sameList(index.includedColumns, original.included_columns) || !sameText(index.comment, original.comment) ); diff --git a/apps/desktop/src/lib/table/tableStructureEditorState.ts b/apps/desktop/src/lib/table/tableStructureEditorState.ts index 1c7ff3b206..b19c747a42 100644 --- a/apps/desktop/src/lib/table/tableStructureEditorState.ts +++ b/apps/desktop/src/lib/table/tableStructureEditorState.ts @@ -758,6 +758,16 @@ export function rehydrateColumnDraftsFromMetadata(draftColumns: EditableStructur return [...nextColumns, ...missingMetadataDrafts]; } +/** Canonicalize index method for structure editor options (e.g. Postgres `btree` → `BTREE`). */ +export function normalizeStructureIndexType(indexType: string | null | undefined): string { + return (indexType ?? "").trim().toUpperCase(); +} + +/** Case-insensitive index-type equality (draft is uppercased; API may still return lowercase amname). */ +export function sameStructureIndexType(left: string | null | undefined, right: string | null | undefined): boolean { + return normalizeStructureIndexType(left) === normalizeStructureIndexType(right); +} + export function createIndexDrafts(indexes: IndexInfo[]): EditableStructureIndex[] { return indexes.map((index) => ({ id: `existing:${index.name}`, @@ -767,7 +777,8 @@ export function createIndexDrafts(indexes: IndexInfo[]): EditableStructureIndex[ isUnique: index.is_unique, isPrimary: index.is_primary, filter: index.filter ?? "", - indexType: index.index_type ?? "", + // Match Select options (BTREE/GIN/…); Postgres pg_am.amname is lowercase. + indexType: normalizeStructureIndexType(index.index_type), includedColumns: index.included_columns ? [...index.included_columns] : [], comment: index.comment ?? "", original: index, diff --git a/packages/app-tests/tableStructureEditorState.test.ts b/packages/app-tests/tableStructureEditorState.test.ts index c662b3f289..c6e2817e32 100644 --- a/packages/app-tests/tableStructureEditorState.test.ts +++ b/packages/app-tests/tableStructureEditorState.test.ts @@ -19,9 +19,11 @@ import { isSqlServerIdentityCompatibleDataType, mysqlEnumDataType, normalizeDataTypeParams, + normalizeStructureIndexType, parseExtraToColumnExtra, rehydrateColumnDraftsFromMetadata, resolveInsertColumnIndex, + sameStructureIndexType, toColumnNames, } from "../../apps/desktop/src/lib/table/tableStructureEditorState.ts"; import { firstStructureMetadataTab, isStructureMetadataTabSupported } from "../../apps/desktop/src/lib/table/tableMetadataCapabilities.ts"; @@ -402,6 +404,48 @@ test("creates editable index drafts and splits pasted column lists", () => { assert.equal(toColumnNames(["id", "name"]), "id, name"); }); +test("normalizes Postgres lowercase index types when creating structure drafts", () => { + const postgresIndexes: IndexInfo[] = [ + { + name: "system_big_screen_asset_pkey", + columns: ["id"], + is_unique: true, + is_primary: true, + index_type: "btree", + }, + { + name: "SYSTEM_BIG_SCREEN_ASSET_TAGS_JSON_IDX", + columns: ["tags_json"], + is_unique: false, + is_primary: false, + index_type: "gin", + }, + { + name: "idx_hash", + columns: ["name"], + is_unique: false, + is_primary: false, + index_type: "hash", + }, + ]; + + const drafts = createIndexDrafts(postgresIndexes); + assert.deepEqual( + drafts.map((draft) => ({ name: draft.name, indexType: draft.indexType })), + [ + { name: "system_big_screen_asset_pkey", indexType: "BTREE" }, + { name: "SYSTEM_BIG_SCREEN_ASSET_TAGS_JSON_IDX", indexType: "GIN" }, + { name: "idx_hash", indexType: "HASH" }, + ], + ); + + // Uppercased drafts must not look like a type change vs Postgres amname. + assert.equal(sameStructureIndexType(drafts[0]!.indexType, postgresIndexes[0]!.index_type), true); + assert.equal(sameStructureIndexType("BTREE", "btree"), true); + assert.equal(sameStructureIndexType("GIN", "hash"), false); + assert.equal(normalizeStructureIndexType(" gist "), "GIST"); +}); + test("generates conventional index names from table and columns", () => { assert.equal(generateIndexName("A", ["B"]), "A_B_IDX"); assert.equal(generateIndexName("order item", ["customer-id", "created_at"]), "ORDER_ITEM_CUSTOMER_ID_CREATED_AT_IDX");