Skip to content
Open
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,12 +428,20 @@ 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)=>{
/* Make autoindex as true if there is coveringindex since ERD works in create mode */
fk.autoindex = false;

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

/* Copy references oid to references_oid for incomplete references to missing tables */
if (fk.columns?.[0]) {
fk.columns[0].references_oid = fk.columns[0].references;
fk.columns[0].references = null;
}

return fk;
});
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
105 changes: 77 additions & 28 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 @@ -635,43 +636,91 @@ 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.foreign_key = nodeData.foreign_key?.filter(fk =>
fk.columns?.[0]?.references_oid && oidUidMap[fk.columns[0].references_oid]
);
});

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;
}

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)=>{
node.getData().foreign_key?.forEach((theFk)=>{
const theFkColumn = theFk.columns[0];
let referencesUid = oidUidMap[theFkColumn.references_oid];

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

/* Avoid creating duplicate links */
if (
newNodesUids
&& !newNodesUids.includes(uid)
&& !newNodesUids.includes(referencesUid)
) {
return;
}

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,
};

theFkColumn.references = referencesUid;
this.addLink(newData, 'onetomany');
});
});
}

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 on lines +599 to +618
Copy link

@coderabbitai coderabbitai bot Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Surface drag‑and‑drop API failures to users.

The current catch logs and rethrows, but nothing handles the rejection, so drops can fail silently. Consider using the existing handleAxiosCatch (or notifier) and avoid rethrowing.

💡 Suggested fix
           .catch((err)=>{
             console.error(err);
-            throw (err instanceof Error ? err : Error(gettext('Something went wrong')));
+            this.handleAxiosCatch(err);
           });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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')));
});
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);
this.handleAxiosCatch(err);
});
🤖 Prompt for AI Agents
In `@web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx` around lines
599 - 618, The catch block for the API call in the apiObj.get(...) chain
currently only logs and rethrows, causing silent failures; replace the
console.error/throw in that catch with a call to the component's existing error
handler (e.g. this.handleAxiosCatch(err) or the notifier used elsewhere) to
surface the failure to users and remove the rethrow so the promise is handled;
update the catch for apiObj.get(...) where TableSchema.getErdSupportedData and
addNode/addNodeWithLinks are invoked to call handleAxiosCatch(err) instead of
console.error/throw.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unchanged

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Lancear, understood. I've noted your decision to keep the error handling as-is.

(´・ω・`)

}
}
}
Expand Down
5 changes: 4 additions & 1 deletion web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ export class TableNodeModel extends DefaultNodeModel {
}

serializeData() {
return this.getData();
const data = this.getData();
// Remove incomplete foreign keys
data.foreign_key = data.foreign_key?.filter((theFk) => theFk.columns?.[0].references);
return data;
}

serialize() {
Expand Down