diff --git a/docs/en_US/release_notes_9_16.rst b/docs/en_US/release_notes_9_16.rst index 7dfd532959a..1abba78c2be 100644 --- a/docs/en_US/release_notes_9_16.rst +++ b/docs/en_US/release_notes_9_16.rst @@ -44,6 +44,7 @@ Bug fixes | `Issue #6308 `_ - Fix the infinite loading spinner after an idle database connection is silently dropped, by detecting stale connections and offering a reconnect dialog. | `Issue #7596 `_ - Fix the Query Tool turning into a blank white screen when the runtime has a malformed default locale, by guarding the Query History date/time formatting against the resulting RangeError. | `Issue #8318 `_ - Fixed an error ("i.default.find(...) is undefined") that prevented deleting a table or relationship link in the ERD tool when a foreign key referenced a column that had been renamed. + | `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. | `Issue #9091 `_ - Fix the Query Tool re-prompting for an unsaved password in a loop and rejecting the re-entered password, by caching the entered password on the server manager when the primary connection is already established. | `Issue #9128 `_ - Fixed an issue where the object breadcrumbs popup blocked clicks on the object explorer items beneath it. | `Issue #9595 `_ - Fix missing ALTER ... SET DEFAULT statements for inherited columns in the generated table SQL/EDIT script. 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..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 @@ -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,26 @@ 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 + )), + include: uc.include?.map((c)=>(c === oldName ? newName : c)), + }))}; + } + } } },{ id: 'exclude_group', type: 'group', label: gettext('Exclude'), visible: !this.inErd, 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();