From c41ec7a70a27e5e569e274fa6b5782c853c5a7f6 Mon Sep 17 00:00:00 2001 From: Dave Page Date: Tue, 9 Jun 2026 12:01:30 +0100 Subject: [PATCH 1/2] Propagate column renames to FK and unique constraints. #9060 In the new-table dialog, the primary key already updated its column references when a column was renamed, but foreign key and unique constraint definitions did not, leaving them pointing at the old name. Mirror the PK rename-propagation in the foreign_key and unique_constraint depChange handlers (and add 'columns' to the unique constraint deps so it fires on column changes). Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/en_US/release_notes_9_16.rst | 2 ++ .../schemas/tables/static/js/table.ui.js | 36 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/en_US/release_notes_9_16.rst b/docs/en_US/release_notes_9_16.rst index a7ec92e1bee..4150d8378f1 100644 --- a/docs/en_US/release_notes_9_16.rst +++ b/docs/en_US/release_notes_9_16.rst @@ -25,3 +25,5 @@ Housekeeping Bug fixes ********* + + | `Issue #9060 `_ - Fixed an issue in the Create/Edit Table dialog where renaming a column did not update the column references in foreign key and unique constraint definitions for the new table. diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js index 8c678ba4b04..da9897abc7a 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js @@ -200,10 +200,25 @@ export class ConstraintsSchema extends BaseUISchema { disabled: this.inCatalog, canAddRow: obj.anyColumnAdded, expandEditOnAdd: true, - depChange: (state)=>{ + depChange: (state, source, topState, actionObj)=>{ if (state.is_partitioned && obj.top.getServerVersion() < 110000 || state.columns?.length <= 0) { return {foreign_key: []}; } + /* If a column is renamed, sync the foreign key local column references. #9060 */ + if(actionObj.type == SCHEMA_STATE_ACTIONS.SET_VALUE && actionObj.path[0] == 'columns' && + actionObj.path[actionObj.path.length-1] == 'name' && state.oid === undefined && + state.foreign_key?.length) { + let oldName = actionObj.oldState.columns[actionObj.path[1]]?.name, + newName = _.get(state, _.slice(actionObj.path, 0, -1))?.name; + if(oldName && newName && oldName !== newName) { + return {foreign_key: state.foreign_key.map((fk)=>({ + ...fk, + columns: fk.columns?.map((c)=>( + c.local_column === oldName ? {...c, local_column: newName} : c + )), + }))}; + } + } } },{ id: 'check_group', type: 'group', label: gettext('Check'), visible: !this.inErd, @@ -223,7 +238,7 @@ export class ConstraintsSchema extends BaseUISchema { schema: this.uniqueConsObj, editable: false, type: 'collection', group: 'unique_group', mode: ['edit', 'create'], - canEdit: true, canDelete: true, deps:['is_partitioned', 'typname'], + canEdit: true, canDelete: true, deps:['is_partitioned', 'typname', 'columns'], columns : ['name', 'columns'], disabled: this.inCatalog, canAdd: function(state) { @@ -231,10 +246,25 @@ export class ConstraintsSchema extends BaseUISchema { }, canAddRow: obj.anyColumnAdded, expandEditOnAdd: true, - depChange: (state)=>{ + depChange: (state, source, topState, actionObj)=>{ if (state.is_partitioned && obj.top.getServerVersion() < 110000 || state.columns?.length <= 0) { return {unique_constraint: []}; } + /* If a column is renamed, sync the unique constraint column references. #9060 */ + if(actionObj.type == SCHEMA_STATE_ACTIONS.SET_VALUE && actionObj.path[0] == 'columns' && + actionObj.path[actionObj.path.length-1] == 'name' && state.oid === undefined && + state.unique_constraint?.length) { + let oldName = actionObj.oldState.columns[actionObj.path[1]]?.name, + newName = _.get(state, _.slice(actionObj.path, 0, -1))?.name; + if(oldName && newName && oldName !== newName) { + return {unique_constraint: state.unique_constraint.map((uc)=>({ + ...uc, + columns: uc.columns?.map((c)=>( + c.column === oldName ? {...c, column: newName} : c + )), + }))}; + } + } } },{ id: 'exclude_group', type: 'group', label: gettext('Exclude'), visible: !this.inErd, From 0924f9cf0e3d23afa6a231699049f75bc8b6888a Mon Sep 17 00:00:00 2001 From: Dave Page Date: Thu, 11 Jun 2026 10:40:54 +0100 Subject: [PATCH 2/2] Address review feedback for column rename propagation (#9060) - Remap unique constraint INCLUDE columns on rename. The INCLUDE list holds bare name strings (not {column} objects), so renaming an included column previously emitted stale DDL. Now mirrors the primary key INCLUDE handling. - Add regression tests covering rename propagation in depChange for foreign_key and unique_constraint, including the unique constraint INCLUDE case. - Correct the release note wording from "Create/Edit Table" to "Create Table"; the propagation only runs on the new-table path (state.oid === undefined). --- docs/en_US/release_notes_9_16.rst | 2 +- .../schemas/tables/static/js/table.ui.js | 1 + .../schema_ui_files/table.ui.spec.js | 83 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) diff --git a/docs/en_US/release_notes_9_16.rst b/docs/en_US/release_notes_9_16.rst index 4150d8378f1..13cbc8d2bc6 100644 --- a/docs/en_US/release_notes_9_16.rst +++ b/docs/en_US/release_notes_9_16.rst @@ -26,4 +26,4 @@ Housekeeping Bug fixes ********* - | `Issue #9060 `_ - Fixed an issue in the Create/Edit Table dialog where renaming a column did not update the column references in foreign key and unique constraint definitions for the new table. + | `Issue #9060 `_ - Fixed an issue in the Create Table dialog where renaming a column did not update the column references in foreign key and unique constraint definitions for the new table. diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js index da9897abc7a..4cc2bc46b47 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js @@ -262,6 +262,7 @@ export class ConstraintsSchema extends BaseUISchema { columns: uc.columns?.map((c)=>( c.column === oldName ? {...c, column: newName} : c )), + include: uc.include?.map((c)=>(c === oldName ? newName : c)), }))}; } } diff --git a/web/regression/javascript/schema_ui_files/table.ui.spec.js b/web/regression/javascript/schema_ui_files/table.ui.spec.js index 51158783a4b..46fa0b1d67c 100644 --- a/web/regression/javascript/schema_ui_files/table.ui.spec.js +++ b/web/regression/javascript/schema_ui_files/table.ui.spec.js @@ -298,6 +298,89 @@ describe('TableSchema', () => { }); }); + it('depChange column rename propagation', () => { + jest.spyOn(schemaObj, 'getServerVersion').mockReturnValue(110000); + schemaObj.constraintsObj.top = schemaObj; + + let actionObj = { + type: SCHEMA_STATE_ACTIONS.SET_VALUE, + path: ['columns', 0, 'name'], + oldState: { + columns: [{ name: 'old_col' }], + }, + }; + + // Foreign key local_column should be renamed (Create path only). + let fkState = { + oid: undefined, + is_partitioned: false, + columns: [{ name: 'new_col' }], + foreign_key: [{ + name: 'fk1', + columns: [ + { local_column: 'old_col', referenced: 'r1' }, + { local_column: 'other', referenced: 'r2' }, + ], + }], + }; + expect(getFieldDepChange(schemaObj.constraintsObj, 'foreign_key')( + fkState, ['columns'], null, actionObj)).toEqual({ + foreign_key: [{ + name: 'fk1', + columns: [ + { local_column: 'new_col', referenced: 'r1' }, + { local_column: 'other', referenced: 'r2' }, + ], + }], + }); + + // Unique constraint columns should be renamed. + let ucState = { + oid: undefined, + is_partitioned: false, + columns: [{ name: 'new_col' }], + unique_constraint: [{ + name: 'uc1', + columns: [ + { column: 'old_col' }, + { column: 'other' }, + ], + include: ['inc1', 'inc2'], + }], + }; + expect(getFieldDepChange(schemaObj.constraintsObj, 'unique_constraint')( + ucState, ['columns'], null, actionObj)).toEqual({ + unique_constraint: [{ + name: 'uc1', + columns: [ + { column: 'new_col' }, + { column: 'other' }, + ], + include: ['inc1', 'inc2'], + }], + }); + + // Unique constraint INCLUDE list should be renamed too (Fix #9060). + let ucIncludeState = { + oid: undefined, + is_partitioned: false, + columns: [{ name: 'new_col' }], + unique_constraint: [{ + name: 'uc2', + columns: [{ column: 'keycol' }], + include: ['old_col', 'inc2'], + }], + }; + expect(getFieldDepChange(schemaObj.constraintsObj, 'unique_constraint')( + ucIncludeState, ['columns'], null, actionObj)).toEqual({ + unique_constraint: [{ + name: 'uc2', + columns: [{ column: 'keycol' }], + include: ['new_col', 'inc2'], + }], + }); + }); + it('validate', () => { let state = {is_partitioned: true}; let setError = jest.fn();