mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
1) Added support for unique keys in ERD. Fixes #7249
2) Fixed an issue where foreign key relationships do not update when the primary key is modified. Fixes #7197
This commit is contained in:
parent
9f992a9e5d
commit
903b300b9e
@ -9,7 +9,7 @@ This release contains a number of bug fixes and new features since the release o
|
||||
New features
|
||||
************
|
||||
|
||||
|
||||
| `Issue #7257 <https://redmine.postgresql.org/issues/7257>`_ - Support running the container under OpenShift with alternate UIDs.
|
||||
|
||||
Housekeeping
|
||||
************
|
||||
@ -20,10 +20,11 @@ Bug fixes
|
||||
*********
|
||||
|
||||
| `Issue #7059 <https://redmine.postgresql.org/issues/7059>`_ - Fixed an issue where the error is shown on logout when the authentication source is oauth2.
|
||||
| `Issue #7197 <https://redmine.postgresql.org/issues/7197>`_ - Fixed an issue where foreign key relationships do not update when the primary key is modified.
|
||||
| `Issue #7216 <https://redmine.postgresql.org/issues/7216>`_ - Ensure that the values of certain fields are prettified in the statistics tab for collection nodes.
|
||||
| `Issue #7221 <https://redmine.postgresql.org/issues/7221>`_ - Ensure objects depending on extensions are not displayed in Schema Diff.
|
||||
| `Issue #7238 <https://redmine.postgresql.org/issues/7238>`_ - Fixed an issue where foreign key is not removed even if the referred table is removed in ERD.
|
||||
| `Issue #7257 <https://redmine.postgresql.org/issues/7257>`_ - Support running the container under OpenShift with alternate UIDs.
|
||||
| `Issue #7249 <https://redmine.postgresql.org/issues/7249>`_ - Added support for unique keys in ERD.
|
||||
| `Issue #7261 <https://redmine.postgresql.org/issues/7261>`_ - Correct typo in the documentation.
|
||||
| `Issue #7263 <https://redmine.postgresql.org/issues/7263>`_ - Fixed schema diff issue where function's difference DDL was showing incorrectly when arguments had default values with commas.
|
||||
| `Issue #7265 <https://redmine.postgresql.org/issues/7265>`_ - Fixed schema diff issue in which the option 'null' doesn't appear in the DDL statement for the foreign table.
|
||||
|
@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#cbe7f6;}.cls-1,.cls-2{stroke:#2c66bd;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75px;}.cls-2{font-size:8px;fill:#2c66bd;font-family:Georgia, Georgia;}</style></defs><title>unique_constraint</title><g id="_2" data-name="2"><circle class="cls-1" cx="8" cy="8" r="5.4"/><text class="cls-2" transform="translate(6.28 10.17)">1</text></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20"><defs><style>.cls-1{fill:#cbe7f6;}.cls-1,.cls-2{stroke:#2c66bd;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75px;}.cls-2{font-size:8px;fill:#2c66bd;font-family:Georgia, Georgia;}</style></defs><title>unique_constraint</title><g id="_2" data-name="2"><circle class="cls-1" cx="8" cy="8" r="5.4"/><text class="cls-2" transform="translate(6.28 10.17)">1</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 440 B After Width: | Height: | Size: 464 B |
@ -87,8 +87,8 @@ export class ConstraintsSchema extends BaseUISchema {
|
||||
changeColumnOptions(colOptions) {
|
||||
this.primaryKeyObj.changeColumnOptions(colOptions);
|
||||
this.fkObj.changeColumnOptions(colOptions);
|
||||
this.uniqueConsObj.changeColumnOptions(colOptions);
|
||||
if(!this.inErd) {
|
||||
this.uniqueConsObj.changeColumnOptions(colOptions);
|
||||
this.exConsObj.changeColumnOptions(colOptions);
|
||||
}
|
||||
}
|
||||
@ -163,7 +163,7 @@ export class ConstraintsSchema extends BaseUISchema {
|
||||
columns : ['name', 'consrc'],
|
||||
disabled: this.inCatalog,
|
||||
},{
|
||||
id: 'unique_group', type: 'group', label: gettext('Unique'), visible: !this.inErd,
|
||||
id: 'unique_group', type: 'group', label: gettext('Unique'),
|
||||
},{
|
||||
id: 'unique_constraint', label: '',
|
||||
schema: this.uniqueConsObj,
|
||||
@ -338,7 +338,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
const SUPPORTED_KEYS = [
|
||||
'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
|
||||
'toast_tuple_target', 'parallel_workers', 'relhasoids', 'relpersistence',
|
||||
'columns', 'primary_key', 'foreign_key',
|
||||
'columns', 'primary_key', 'foreign_key', 'unique_constraint',
|
||||
];
|
||||
newData = _.pick(newData, SUPPORTED_KEYS);
|
||||
|
||||
@ -975,7 +975,7 @@ export default class TableSchema extends BaseUISchema {
|
||||
id: 'seclabels', label: gettext('Security labels'), canEdit: false,
|
||||
schema: new SecLabelSchema(), editable: false, canAdd: true,
|
||||
type: 'collection', min_version: 90100, mode: ['edit', 'create'],
|
||||
group: 'security_group', canDelete: true, control: 'unique-col-collection',
|
||||
group: 'security_group', canDelete: true,
|
||||
},{
|
||||
id: 'vacuum_settings_str', label: gettext('Storage settings'),
|
||||
type: 'multiline', group: 'advanced', mode: ['properties'],
|
||||
|
@ -200,7 +200,7 @@ basicSettings = createMuiTheme(basicSettings, {
|
||||
create: () => 'none',
|
||||
},
|
||||
zIndex: {
|
||||
modal: 2000,
|
||||
modal: 3001,
|
||||
},
|
||||
props: {
|
||||
MuiTextField: {
|
||||
|
@ -268,10 +268,55 @@ export default class ERDCore {
|
||||
}
|
||||
|
||||
syncTableLinks(tableNode, oldTableData) {
|
||||
let self = this;
|
||||
let tableData = tableNode.getData();
|
||||
let tableNodesDict = this.getModel().getNodesDict();
|
||||
const tableNodesDict = this.getModel().getNodesDict();
|
||||
|
||||
/* Remove the links if column dropped or primary key removed */
|
||||
_.differenceWith(oldTableData.columns, tableData.columns, function(existing, incoming) {
|
||||
if(existing.attnum == incoming.attnum && existing.is_primary_key && !incoming.is_primary_key) {
|
||||
return false;
|
||||
}
|
||||
return existing.attnum == incoming.attnum;
|
||||
}).forEach((col)=>{
|
||||
let existPort = tableNode.getPort(tableNode.getPortName(col.attnum));
|
||||
if(existPort) {
|
||||
Object.values(existPort.getLinks()).forEach((link)=>{
|
||||
self.removeOneToManyLink(link);
|
||||
});
|
||||
}
|
||||
tableNode.removePort(existPort);
|
||||
});
|
||||
|
||||
/* Sync the name changes in references FK */
|
||||
Object.values(tableNode.getPorts()).forEach((port)=>{
|
||||
if(port.getSubtype() != 'one') {
|
||||
return;
|
||||
}
|
||||
Object.values(port.getLinks()).forEach((link)=>{
|
||||
let linkData = link.getData();
|
||||
let fkTableNode = this.getModel().getNodesDict()[linkData.local_table_uid];
|
||||
|
||||
let newForeingKeys = [];
|
||||
fkTableNode.getData().foreign_key?.forEach((theFkRow)=>{
|
||||
for(let fkColumn of theFkRow.columns) {
|
||||
let attnum = _.find(oldTableData.columns, (c)=>c.name==fkColumn.referenced).attnum;
|
||||
fkColumn.referenced = _.find(tableData.columns, (colm)=>colm.attnum==attnum).name;
|
||||
fkColumn.references_table_name = tableData.name;
|
||||
}
|
||||
newForeingKeys.push(theFkRow);
|
||||
});
|
||||
fkTableNode.setData({
|
||||
...fkTableNode.getData(),
|
||||
foreign_key: newForeingKeys,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* Sync the changed/removed/added foreign keys */
|
||||
tableData = tableNode.getData();
|
||||
const addLink = (theFk)=>{
|
||||
if(!theFk) return;
|
||||
let newData = {
|
||||
local_table_uid: tableNode.getID(),
|
||||
local_column_attnum: undefined,
|
||||
@ -287,6 +332,7 @@ export default class ERDCore {
|
||||
};
|
||||
|
||||
const removeLink = (theFk)=>{
|
||||
if(!theFk) return;
|
||||
let attnum = _.find(tableNode.getColumns(), (col)=>col.name==theFk.local_column).attnum;
|
||||
let existPort = tableNode.getPort(tableNode.getPortName(attnum));
|
||||
if(existPort && existPort.getSubtype() == 'many') {
|
||||
@ -316,9 +362,12 @@ export default class ERDCore {
|
||||
tableData.foreign_key[rowIndx].columns,
|
||||
'cid'
|
||||
);
|
||||
if(changeDiffCols.removed.length > 0 || changeDiffCols.added.length > 0) {
|
||||
removeLink(changeDiffCols.removed[0]);
|
||||
addLink(changeDiffCols.added[0]);
|
||||
if(changeDiffCols.removed.length > 0) {
|
||||
/* any change in columns length remove all and create new link */
|
||||
oldTableData.foreign_key[rowIndx].columns.forEach((col)=>{
|
||||
removeLink(col);
|
||||
});
|
||||
addLink(tableData.foreign_key[rowIndx].columns[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import TableIcon from 'top/browser/server_groups/servers/databases/schemas/table
|
||||
import PrimaryKeyIcon from 'top/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/primary_key.svg';
|
||||
import ForeignKeyIcon from 'top/browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/img/foreign_key.svg';
|
||||
import ColumnIcon from 'top/browser/server_groups/servers/databases/schemas/tables/columns/static/img/column.svg';
|
||||
import UniqueKeyIcon from 'top/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.svg';
|
||||
import PropTypes from 'prop-types';
|
||||
import gettext from 'sources/gettext';
|
||||
|
||||
@ -94,17 +95,6 @@ export class TableNodeModel extends DefaultNodeModel {
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
let self = this;
|
||||
/* Remove the links if column dropped or primary key removed */
|
||||
_.differenceWith(this._data.columns, data.columns, function(existing, incoming) {
|
||||
return existing.attnum == incoming.attnum && incoming.is_primary_key == true;
|
||||
}).forEach((col)=>{
|
||||
let existPort = self.getPort(self.getPortName(col.attnum));
|
||||
if(existPort && existPort.getSubtype() == 'one') {
|
||||
existPort.removeAllLinks();
|
||||
self.removePort(existPort);
|
||||
}
|
||||
});
|
||||
this._data = data;
|
||||
this.fireEvent({}, 'nodeUpdated');
|
||||
}
|
||||
@ -172,18 +162,23 @@ export class TableNodeWidget extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
generateColumn(col, tableData) {
|
||||
generateColumn(col, localFkCols, localUkCols) {
|
||||
let port = this.props.node.getPort(this.props.node.getPortName(col.attnum));
|
||||
let icon = ColumnIcon;
|
||||
let localFkCols = [];
|
||||
(tableData.foreign_key||[]).forEach((fk)=>{
|
||||
localFkCols.push(...fk.columns.map((c)=>c.local_column));
|
||||
});
|
||||
/* Less priority */
|
||||
if(localUkCols.indexOf(col.name) > -1) {
|
||||
icon = UniqueKeyIcon;
|
||||
}
|
||||
if(col.is_primary_key) {
|
||||
icon = PrimaryKeyIcon;
|
||||
} else if(localFkCols.indexOf(col.name) > -1) {
|
||||
icon = ForeignKeyIcon;
|
||||
}
|
||||
|
||||
let cltype = col.cltype;
|
||||
if(col.attlen) {
|
||||
cltype += '('+ col.attlen + (col.attprecision ? ',' + col.attprecision : '') +')';
|
||||
}
|
||||
return (
|
||||
<div className='d-flex col-row' key={col.attnum}>
|
||||
<div className='d-flex col-row-data'>
|
||||
@ -191,7 +186,7 @@ export class TableNodeWidget extends React.Component {
|
||||
<div className="my-auto">
|
||||
<span className='col-name'>{col.name}</span>
|
||||
{this.state.show_details &&
|
||||
<span className='col-datatype'>{col.cltype}{col.attlen ? ('('+ col.attlen + (col.attprecision ? ','+col.attprecision : '') +')') : ''}</span>}
|
||||
<span className='col-datatype'>{cltype}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto col-row-port">{this.generatePort(port)}</div>
|
||||
@ -216,6 +211,14 @@ export class TableNodeWidget extends React.Component {
|
||||
render() {
|
||||
let tableData = this.props.node.getData();
|
||||
let tableMetaData = this.props.node.getMetadata();
|
||||
let localFkCols = [];
|
||||
(tableData.foreign_key||[]).forEach((fk)=>{
|
||||
localFkCols.push(...fk.columns.map((c)=>c.local_column));
|
||||
});
|
||||
let localUkCols = [];
|
||||
(tableData.unique_constraint||[]).forEach((uk)=>{
|
||||
localUkCols.push(...uk.columns.map((c)=>c.column));
|
||||
});
|
||||
return (
|
||||
<div className={'table-node ' + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
|
||||
<div className="table-toolbar">
|
||||
@ -243,7 +246,7 @@ export class TableNodeWidget extends React.Component {
|
||||
<div className="table-name my-auto">{tableData.name}</div>
|
||||
</div>
|
||||
<div className="table-cols">
|
||||
{_.map(tableData.columns, (col)=>this.generateColumn(col, tableData))}
|
||||
{_.map(tableData.columns, (col)=>this.generateColumn(col, localFkCols, localUkCols))}
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
|
@ -48,6 +48,5 @@ export class FakeLink {
|
||||
}
|
||||
|
||||
export class FakePort {
|
||||
constructor() {}
|
||||
getLinks() {return null;}
|
||||
}
|
||||
|
@ -135,31 +135,6 @@ describe('ERD TableNodeModel', ()=>{
|
||||
});
|
||||
expect(existPort.removeAllLinks).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('remove columns', ()=>{
|
||||
spyOn(existPort, 'getSubtype').and.returnValue('one');
|
||||
existPort.removeAllLinks.calls.reset();
|
||||
modelObj.setData({
|
||||
name: 'noname',
|
||||
schema: 'erd',
|
||||
columns: [
|
||||
{name: 'col2', not_null:false, attnum: 1},
|
||||
{name: 'col3', not_null:false, attnum: 2},
|
||||
],
|
||||
});
|
||||
expect(modelObj.getData()).toEqual({
|
||||
name: 'noname',
|
||||
schema: 'erd',
|
||||
columns: [
|
||||
{name: 'col2', not_null:false, attnum: 1},
|
||||
{name: 'col3', not_null:false, attnum: 2},
|
||||
],
|
||||
});
|
||||
|
||||
expect(modelObj.getPortName).toHaveBeenCalledWith(0);
|
||||
expect(existPort.removeAllLinks).toHaveBeenCalled();
|
||||
expect(modelObj.removePort).toHaveBeenCalledWith(existPort);
|
||||
});
|
||||
});
|
||||
|
||||
it('getSchemaTableName', ()=>{
|
||||
|
@ -257,6 +257,7 @@ describe('ERD BodyWidget', ()=>{
|
||||
'getNodesDict': ()=>nodesDict,
|
||||
});
|
||||
spyOn(bodyInstance.diagram, 'addLink');
|
||||
spyOn(bodyInstance.diagram, 'syncTableLinks');
|
||||
/* New */
|
||||
tableDialog.show.calls.reset();
|
||||
bodyInstance.addEditTable();
|
||||
|
Loading…
Reference in New Issue
Block a user