public inbox for [email protected]
help / color / mirror / Atom feedFrom: Aditya Toshniwal <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin][RM7249] Support Unique Key in ERD
Date: Mon, 28 Mar 2022 13:52:10 +0530
Message-ID: <CAM9w-_m_C2c0z4WTn5z+RDO9cgVw0ge_=Ho=AntWy=HutAPf4Q@mail.gmail.com> (raw)
Hi Hackers,
Attached patch adds support for unique keys in ERD and also fixes a bug
where foreign key relationships do not update when the primary key is
modified #7197.
Please review.
--
Thanks,
Aditya Toshniwal
pgAdmin Hacker | Software Architect | *edbpostgres.com*
<http://edbpostgres.com;
"Don't Complain about Heat, Plant a TREE"
Attachments:
[application/octet-stream] RM7249_7197.patch (13.9K, 3-RM7249_7197.patch)
download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.svg b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.svg
index 2fb31e358..7a1f42b88 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.svg
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.svg
@@ -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>
\ No newline at end of file
+<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>
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 93fe02712..0197f6671 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
@@ -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'],
diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx
index da58fafa8..d2a070f15 100644
--- a/web/pgadmin/static/js/Theme/index.jsx
+++ b/web/pgadmin/static/js/Theme/index.jsx
@@ -200,7 +200,7 @@ basicSettings = createMuiTheme(basicSettings, {
create: () => 'none',
},
zIndex: {
- modal: 2000,
+ modal: 3001,
},
props: {
MuiTextField: {
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js
index 0265668a9..5048ffb3c 100644
--- a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js
@@ -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]);
}
}
}
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx
index 7dcfe5aa3..8c3686f09 100644
--- a/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx
@@ -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>
diff --git a/web/regression/javascript/erd/fake_item.js b/web/regression/javascript/erd/fake_item.js
index 472c626fa..94b510c8d 100644
--- a/web/regression/javascript/erd/fake_item.js
+++ b/web/regression/javascript/erd/fake_item.js
@@ -48,6 +48,5 @@ export class FakeLink {
}
export class FakePort {
- constructor() {}
getLinks() {return null;}
}
diff --git a/web/regression/javascript/erd/table_node_spec.js b/web/regression/javascript/erd/table_node_spec.js
index 43e45efb7..04fa4c75c 100644
--- a/web/regression/javascript/erd/table_node_spec.js
+++ b/web/regression/javascript/erd/table_node_spec.js
@@ -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', ()=>{
diff --git a/web/regression/javascript/erd/ui_components/body_widget_spec.js b/web/regression/javascript/erd/ui_components/body_widget_spec.js
index 3fb2cf0bd..19eb0d34f 100644
--- a/web/regression/javascript/erd/ui_components/body_widget_spec.js
+++ b/web/regression/javascript/erd/ui_components/body_widget_spec.js
@@ -257,6 +257,7 @@ describe('ERD BodyWidget', ()=>{
'getNodesDict': ()=>nodesDict,
});
spyOn(bodyInstance.diagram, 'addLink');
+ spyOn(bodyInstance.diagram, 'syncTableLinks');
/* New */
tableDialog.show.calls.reset();
bodyInstance.addEditTable();
view thread (2+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [pgAdmin][RM7249] Support Unique Key in ERD
In-Reply-To: <CAM9w-_m_C2c0z4WTn5z+RDO9cgVw0ge_=Ho=AntWy=HutAPf4Q@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox