Skip to content

Conversation

@Lancear
Copy link

@Lancear Lancear commented Feb 1, 2026

Feature: Drag and Drop Tables with Relations in ERD

This feature allows tables to be dragged into the ERD tool along with their foreign key relationships automatically.

Closes #5578
Closes #8198

Key Decisions

  1. Keep original oid and a copy of foreign_key in nodes
    Preserves table identity and allows checking future references. Only the first node with each oid retains the oid.

  2. First instance priority
    Only the first insert of a table is checked for incoming references from other nodes, preventing duplicate foreign key links.

  3. File save preserves all original foreign keys
    Missing table references are kept when saving, allowing users to add the referenced tables later.

  4. User preference added
    New preference "Drag and Drop table with relations" to control this behavior, default acts like before.

Co-authored-by: Christian P. [email protected]

Summary by CodeRabbit

  • New Features

    • Added a preference to automatically insert related tables when dragging a table into the ERD diagram.
  • Bug Fixes

    • Improved handling of incomplete foreign key references when adding tables to the diagram.
    • Enhanced relationship linking logic for better accuracy in diagram generation.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Feb 1, 2026

Walkthrough

This change adds functionality to automatically include related tables when dragging and dropping tables into an ERD diagram. It introduces a new preference flag, extends table schema data extraction with OID support, and implements centralized foreign key link creation logic with a two-phase deserialization flow.

Changes

Cohort / File(s) Summary
Table Schema Data Extraction
web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.ui.js
Added 'oid' to supported ERD data keys. Introduced original_foreign_keys extraction from foreign_key with autoindex and coveringindex handling. Cleared foreign_key array post-extraction to separate original FK references from transformed list.
ERD Tool Configuration
web/pgadmin/tools/erd/__init__.py
Added new boolean user preference insert_table_with_relations (defaulting to False) to control whether dragging a table also includes its related tables in the diagram.
ERDCore Link Management
web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js
Introduced two-phase deserialization with centralized link creation. Added public methods addNodeWithLinks() and addLinksBetweenNodes() to handle node creation with automatic FK link establishment. Updated cloneTableData() to skip oid and original_foreign_keys keys.
ERDTool Drag-Drop Handler
web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx
Updated onDropNode() to conditionally call addNodeWithLinks() or addNode() based on insert_table_with_relations preference. Simplified data fetch and node creation flow with direct API handling instead of Promise wrapping.

Sequence Diagram

sequenceDiagram
    actor User
    participant ERDTool as ERDTool.jsx
    participant API as Backend API
    participant ERDCore
    participant Canvas as Diagram

    User->>ERDTool: Drag & drop table
    ERDTool->>API: Fetch table data with ERD schema
    API-->>ERDTool: Return table + original_foreign_keys
    
    alt insert_table_with_relations enabled
        ERDTool->>ERDCore: addNodeWithLinks(data, position)
        ERDCore->>ERDCore: Create node from table data
        ERDCore->>ERDCore: Build oid-to-uid map
        ERDCore->>ERDCore: addLinksBetweenNodes(oidUidMap)
        ERDCore->>ERDCore: Resolve & create FK links
        ERDCore->>Canvas: Render node + FK links
    else insert_table_with_relations disabled
        ERDTool->>ERDCore: addNode(cloneTableData, position)
        ERDCore->>Canvas: Render node (no auto-links)
    end
    
    ERDTool->>Canvas: Select created node
    Canvas-->>User: Display table with/without relations
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • yogeshmahajan-1903
🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main feature: adding the ability to insert related tables via drag-and-drop in the ERD tool.
Linked Issues check ✅ Passed The PR successfully implements FK link display for drag-and-dropped tables (#5578) and adds user control via preference to prevent unwanted table imports (#8198).
Out of Scope Changes check ✅ Passed All changes directly support the feature: table.ui.js adds oid field, erd/init.py adds user preference, ERDCore.js implements FK handling, and ERDTool.jsx uses the preference to trigger new behavior.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx`:
- Around line 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.

In `@web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx`:
- Around line 161-165: serializeData() is mutating the node's live data by
filtering data.foreign_key in-place which can permanently remove FKs; fix by
cloning the data (or at least the foreign_key array) returned from getData()
before applying the filter so the node's persisted state remains unchanged —
e.g., obtain const data = this.getData(); create a shallow copy (or copy of
data.foreign_key) and run the .filter on that copy, then return the
cloned/modified copy while leaving the original getData() object intact.
🧹 Nitpick comments (1)
web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js (1)

686-724: Guard against missing FK column metadata during link creation.

addLinksBetweenNodes assumes fk.columns[0] and column lookups always resolve. A malformed FK or a column removed in the ERD could throw here and break drag‑and‑drop. A small guard keeps this path resilient.

🛡️ Suggested defensive checks
-        const theFkColumn = theFk.columns[0];
+        const theFkColumn = theFk.columns?.[0];
+        if (!theFkColumn) return;
         let referencesUid = oidUidMap[theFkColumn.references_oid];
@@
-        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 localCol = _.find(
+          tableNodesDict[uid].getColumns(), 
+          (col) => col.name == theFkColumn.local_column
+        );
+        const refCol = _.find(
+          tableNodesDict[referencesUid].getColumns(), 
+          (col) => col.name == theFkColumn.referenced
+        );
+        if (!localCol || !refCol) return;
+        const newData = {
+          local_table_uid: uid,
+          local_column_attnum: localCol.attnum,
+          referenced_table_uid: referencesUid,
+          referenced_column_attnum: refCol.attnum,
+        };

@Lancear
Copy link
Author

Lancear commented Feb 1, 2026

We were unsure how to update the translations, thus skipped that for now

@pravesh-sharma
Copy link
Contributor

Hi @Lancear,

I tested the PR and identified the following issues:

  1. If a table contains a Foreign Key (FK) referencing a missing table, generating the SQL and returning to the ERD does not resolve the link. Even after adding the missing table back to the ERD, the relationship is not automatically generated.
  2. When a table is cloned using the "Clone Table" toolbar button, dragging and dropping a third table that has an FK referencing the original table incorrectly draws the relationship to the cloned table instead (attached screenshot).
Screenshot 2026-02-09 at 1 26 21 PM

@Lancear
Copy link
Author

Lancear commented Feb 10, 2026

1 was intentional so the ERD diagram matches the generated SQL and has no additional foreign keys.
2 should be easy to fix, will check.

I think we can change 1 by separating the original foreign keys from the schema, from the ones drawn in the ER diagram, will check it out.

Will do both of these on Thursday when I have more time in the evening.

@Lancear Lancear force-pushed the erd-insert-table-with-relations branch from 9ab5f69 to ccd894a Compare February 12, 2026 18:00
@Lancear Lancear force-pushed the erd-insert-table-with-relations branch from ccd894a to 2cace1c Compare February 12, 2026 18:52
@Lancear
Copy link
Author

Lancear commented Feb 12, 2026

Addressed both issues

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js`:
- Around line 702-708: The duplicate-link guard in addNodeWithLinks only checks
newNodesUids and misses existing visual links, so update the check (around the
block using newNodesUids, uid and referencesUid) to also detect and skip if a
link between uid and referencesUid already exists before calling addLink;
specifically, query the current links collection (e.g., this.links /
graph.getLinks() or the structure that stores links) and if any link has source
=== uid and target === referencesUid (or vice‑versa if links are undirected),
return/skip adding; ensure the check uses the same identifier fields that
addLink expects so duplicates are never created.
- Around line 692-726: The loop that processes nodeData.original_foreign_keys
must guard against missing pieces: ensure theFk.columns && theFk.columns[0]
exists, initialize nodeData.foreign_key if falsy, and check the _.find(...)
results before accessing .attnum; if either the local column lookup or the
referenced column lookup returns undefined (or referencesUid is missing) then
skip this FK (do not push to nodeData.foreign_key or call this.addLink).
Concretely, in the block handling theFk use a guard like: if (!theFk.columns ||
!theFk.columns[0]) return; set nodeData.foreign_key = nodeData.foreign_key ||
[]; resolve localCol = _.find(tableNodesDict[uid].getColumns(), ...) and refCol
= _.find(tableNodesDict[referencesUid].getColumns(), ...); if (!localCol ||
!refCol) return; then build newData using localCol.attnum and refCol.attnum,
clone theFk, set newForeignKey.columns[0].references = referencesUid, push to
nodeData.foreign_key and call this.addLink.
- Around line 660-666: The function addNodeWithLinks currently calls delete
nodeData.oid which mutates the caller's object; instead make a shallow clone or
destructure at the start (e.g., const node = { ...nodeData } or const { oid,
...node } = nodeData) and operate on that local copy (delete node.oid or use the
destructured node) so you never modify the original nodeData; update all
subsequent references in addNodeWithLinks to use the local cloned variable
(node) and leave nodeData untouched.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants