Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ export default class TableSchema extends BaseUISchema {
static getErdSupportedData(data) {
let newData = {...data};
const SUPPORTED_KEYS = [
'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
'oid', 'name', 'schema', 'description', 'rlspolicy', 'forcerlspolicy', 'fillfactor',
'toast_tuple_target', 'parallel_workers', 'relhasoids', 'relpersistence',
'columns', 'primary_key', 'foreign_key', 'unique_constraint',
];
Expand All @@ -428,14 +428,18 @@ export default class TableSchema extends BaseUISchema {
return c;
});

/* Make autoindex as true if there is coveringindex since ERD works in create mode */
newData.foreign_key = (newData.foreign_key||[]).map((fk)=>{
newData.original_foreign_keys = (newData.foreign_key||[]).map((fk)=>{
/* Make autoindex as true if there is coveringindex since ERD works in create mode */
fk.autoindex = false;

if(fk.coveringindex) {
fk.autoindex = true;
}

return fk;
});

newData.foreign_key = [];
return newData;
}

Expand Down
14 changes: 14 additions & 0 deletions web/pgadmin/tools/erd/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,20 @@ def register_preferences(self):
)
)

self.preference.register(
'options',
'insert_table_with_relations',
gettext('Insert Table With Relations'),
'boolean',
False,
category_label=PREF_LABEL_OPTIONS,
help_str=gettext(
'Whether inserting a table via drag and drop should '
'also insert its relations to the existing tables in '
'the diagram.'
)
)

self.preference.register(
'options', 'cardinality_notation',
gettext('Cardinality Notation'), 'radioModern', 'crows',
Expand Down
111 changes: 82 additions & 29 deletions web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ export default class ERDCore {

const addLink = (theFk)=>{
if(!theFk) return;

let newData = {
local_table_uid: tableNode.getID(),
local_column_attnum: undefined,
Expand Down Expand Up @@ -590,7 +591,7 @@ export default class ERDCore {
}

cloneTableData(tableData, name) {
const SKIP_CLONE_KEYS = ['foreign_key'];
const SKIP_CLONE_KEYS = ['oid', 'foreign_key', 'original_foreign_keys'];

if(!tableData) {
return tableData;
Expand Down Expand Up @@ -635,43 +636,95 @@ export default class ERDCore {

deserializeData(data){
let oidUidMap = {};
let newNodes = [];

/* Add the nodes */
data.forEach((nodeData)=>{
let newNode = this.addNode(TableSchema.getErdSupportedData(nodeData));
const newNode = this.addNode(TableSchema.getErdSupportedData(nodeData));
oidUidMap[nodeData.oid] = newNode.getID();
newNodes.push(newNode);
});

/* Lets use the oidUidMap for creating the links */
let tableNodesDict = this.getModel().getNodesDict();
// When generating for schema, there may be a reference to another schema table
// We'll remove the FK completely in such cases
newNodes.forEach((node) => {
const nodeData = node.getData();
nodeData.original_foreign_keys = nodeData.original_foreign_keys?.filter(fk =>
fk.columns?.[0]?.references && oidUidMap[fk.columns[0].references]
);
});

this.addLinksBetweenNodes(oidUidMap);
}

addNodeWithLinks(nodeData, position=[50,50], metadata={}){
const tableNodesDict = this.getModel().getNodesDict();
const oidExists = Object.values(tableNodesDict).some(node => node.getData().oid === nodeData.oid);

if (oidExists) {
delete nodeData.oid;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

let oidUidMap = {};
const newNode = this.addNode(nodeData, position, metadata);

if (!oidExists) {
oidUidMap[nodeData.oid] = newNode.getID();
}

_.forIn(tableNodesDict, (node, uid)=>{
let nodeData = node.getData();
if(nodeData.foreign_key) {
nodeData.foreign_key = nodeData.foreign_key.filter((theFk)=>{
delete theFk.oid;
theFk = theFk.columns[0];
theFk.references = oidUidMap[theFk.references];
let newData = {
local_table_uid: uid,
local_column_attnum: undefined,
referenced_table_uid: theFk.references,
referenced_column_attnum: undefined,
};
let sourceNode = tableNodesDict[newData.referenced_table_uid];
let targetNode = tableNodesDict[newData.local_table_uid];
// When generating for schema, there may be a reference to another schema table
// We'll remove the FK completely in such cases.
if(!sourceNode || !targetNode) {
return false;
}
const oid = node.getData().oid;
if (!oid) return;

newData.local_column_attnum = _.find(targetNode.getColumns(), (col)=>col.name==theFk.local_column).attnum;
newData.referenced_column_attnum = _.find(sourceNode.getColumns(), (col)=>col.name==theFk.referenced).attnum;
oidUidMap[oid] = uid;
});

this.addLink(newData, 'onetomany');
return true;
});
}
this.addLinksBetweenNodes(oidUidMap, [newNode.getID()]);
return newNode;
}

addLinksBetweenNodes(oidUidMap, newNodesUids = null) {
const tableNodesDict = this.getModel().getNodesDict();

_.forIn(tableNodesDict, (node, uid)=>{
const nodeData = node.getData();

nodeData.original_foreign_keys?.forEach((theFk)=>{
const theFkColumn = theFk.columns[0];
let referencesUid = oidUidMap[theFkColumn.references];

/* Incomplete reference to missing table */
if (!referencesUid) {
return;
}

/* Avoid creating duplicate links */
if (
newNodesUids
&& !newNodesUids.includes(uid)
&& !newNodesUids.includes(referencesUid)
) {
return;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const newData = {
local_table_uid: uid,
local_column_attnum: _.find(
tableNodesDict[uid].getColumns(),
(col) => col.name == theFkColumn.local_column
).attnum,
referenced_table_uid: referencesUid,
referenced_column_attnum: _.find(
tableNodesDict[referencesUid].getColumns(),
(col) => col.name == theFkColumn.referenced
).attnum,
};

const newForeignKey = _.cloneDeep(theFk);
newForeignKey.columns[0].references = referencesUid;
nodeData.foreign_key.push(newForeignKey);
this.addLink(newData, 'onetomany');
Comment thread
Lancear marked this conversation as resolved.
});
});
}

Expand Down
35 changes: 20 additions & 15 deletions web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -596,21 +596,26 @@ export default class ERDTool extends React.Component {
if(nodeDropData.objUrl.indexOf(matchUrl) == -1) {
pgAdmin.Browser.notifier.error(gettext('Cannot drop table from outside of the current database.'));
} else {
let dataPromise = new Promise((resolve, reject)=>{
this.apiObj.get(nodeDropData.objUrl)
.then((res)=>{
resolve(this.diagram.cloneTableData(TableSchema.getErdSupportedData(res.data)));
})
.catch((err)=>{
console.error(err);
reject(err instanceof Error ? err : Error(gettext('Something went wrong')));
});
});
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
this.diagram.addNode(dataPromise, [x, y], {
fillColor: this.state.fill_color,
textColor: this.state.text_color,
}).setSelected(true);
this.apiObj.get(nodeDropData.objUrl)
.then((res)=>{
const data = TableSchema.getErdSupportedData(res.data);
const {x, y} = this.diagram.getEngine().getRelativeMousePoint(e);
const position = [x,y];
const metadata = {
fillColor: this.state.fill_color,
textColor: this.state.text_color,
};

const newNode = this.state.preferences.insert_table_with_relations
? this.diagram.addNodeWithLinks(data, position, metadata)
: this.diagram.addNode(this.diagram.cloneTableData(data), position, metadata);

newNode.setSelected(true);
})
.catch((err)=>{
console.error(err);
throw (err instanceof Error ? err : Error(gettext('Something went wrong')));
});
Comment thread
Lancear marked this conversation as resolved.
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion web/regression/javascript/erd/erd_core_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
import TEST_TABLES_DATA from './test_tables';
import { FakeLink, FakeNode } from './fake_item';
import { PortModelAlignment } from '@projectstorm/react-diagrams';
import TableSchema from 'pgadmin.tables.js/table.ui';

describe('ERDCore', ()=>{
let eleFactory = {
Expand Down Expand Up @@ -247,7 +248,7 @@ describe('ERDCore', ()=>{
/*This is intentional (SonarQube)*/
},
getData: function() {
return table;
return TableSchema.getErdSupportedData(table);
}
};
});
Expand Down
Loading