feat: ER Diagram visualization for PostgreSQL, MySQL, SQLite#209
Conversation
- Interactive entity-relationship diagram with @xyflow/react + dagre auto-layout - Table selector dialog before generation to prevent freeze on large schemas - Compact view (PK/FK columns only) by default - PostgreSQL: bulk introspection via pg_attribute/pg_index/pg_constraint - MySQL/MariaDB: information_schema-based column and FK extraction - SQLite: batched PRAGMA queries with Promise.all for performance - Schema data cache (30s TTL) for instant re-open - FK relationship rendering with direct system catalog fallback queries - Connection-aware opening — diagram uses the active editor tab's database - Public schema prefix handling for PostgreSQL column/FK matching - SVG export via Tauri native save dialog - Docs page for ER diagram feature - Auto-save now updates originalQuery on write to prevent spurious dirty prompts - Session restore handles null originalQuery migration from old files - Discard on close overwrites session with clean state - Compact view enabled by default
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 43 minutes and 37 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThis PR delivers a complete ER Diagram visualization system with React Flow, alongside fixes for session management (originalQuery syncing), CLI tool detection improvements for Windows, enhanced drag-and-drop in the explorer tree, and supporting UX improvements and documentation. ChangesFull Feature Release
Sequence Diagram(s)sequenceDiagram
participant User
participant MainContent
participant ERDDialog
participant useERData
participant Backend
User->>MainContent: Click ER Diagram button
MainContent->>ERDDialog: Open (showERDDialog=true)
User->>ERDDialog: Select tables in TableSelectorDialog
ERDDialog->>useERData: Fetch schema for selected tables
useERData->>Backend: Query columns & foreign keys (provider-specific)
Backend-->>useERData: Return metadata
useERData->>useERData: Correlate FKs, build nodes/edges, run Dagre layout
useERData-->>ERDDialog: Return ERData with positioned nodes
ERDDialog->>ERDDialog: Filter nodes/edges by visibility & display mode
ERDDialog->>ERDCanvas: Render with processed nodes/edges
User->>ERDDialog: Export SVG / Adjust filters
ERDDialog->>User: Save file / Update diagram
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (3 warnings)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src-tauri/src/cli.rs (1)
229-244:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winRestrict system PATH lookup for
ToolKind::Psqltopsqlonly.
find_available(ToolKind::Psql)currently iterateskind.all_binaries()(["psql","pg_dump","pg_restore","pg_dumpall"]) and returns the first one found on PATH; if onlypg_dump/pg_restore/pg_dumpallis present,cli_detect_pg_versionwill execute that binary withpsqlflags (SELECT version()), failing version detection. The bundled-dir logic already useskind.primary_binary(), so align the PATH/system lookup similarly (also applies at 765-772).Proposed fix
async fn find_available(&self, kind: ToolKind) -> Option<(PathBuf, u32)> { - for binary in kind.all_binaries() { - if which::which(binary).is_ok() { - return which::which(binary).ok().map(|p| (p, 0)); // version 0 = system - } + if let Ok(path) = which::which(kind.primary_binary()) { + return Some((path, 0)); // version 0 = system } // Windows: check common install paths (psql.exe often not on PATH) #[cfg(target_os = "windows")] if kind == ToolKind::Psql {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src-tauri/src/cli.rs` around lines 229 - 244, The PATH lookup for ToolKind::Psql currently checks kind.all_binaries(), which can pick pg_dump/pg_restore/pg_dumpall and break cli_detect_pg_version; change the logic in find_available (and the similar block around the other occurrence) so that when kind == ToolKind::Psql you only probe kind.primary_binary() on PATH (use which::which for that single name and return its PathBuf with version 0), otherwise keep existing all_binaries() behavior; ensure references to find_available, ToolKind::Psql, kind.all_binaries(), kind.primary_binary(), and cli_detect_pg_version are used to locate the affected code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src-tauri/src/cli.rs`:
- Around line 195-227: The Windows fallback find_windows_psql_path currently
hardcodes C:\Program Files paths and should instead read %ProgramFiles% and
%ProgramFiles(x86)% environment variables and join the
PostgreSQL\<version>\bin\psql.exe paths so installs on other drives are found;
update find_windows_psql_path to iterate a versions list but prefix each with
env::var_os("ProgramFiles") and env::var_os("ProgramFiles(x86)") (if present) to
build PathBufs and check exists(). Also fix find_available so when
ToolKind::Psql is requested it only probes the "psql" executable (not
"pg_dump"/"pg_restore"/"pg_dumpall") to avoid returning a dump tool; ensure
callers like cli_detect_pg_version receive a true psql path.
In `@src/components/help/HelpDialog.tsx`:
- Line 167: The fallback call to window.open in the HelpDialog component
currently uses window.open(url, "_blank") which leaves the new page access to
window.opener; update that fallback to pass a feature string that includes
"noopener,noreferrer" (e.g., window.open(url, "_blank", "noopener,noreferrer"))
so the opened docs page cannot access window.opener or send referrer
information; locate the window.open call in HelpDialog.tsx (inside the
HelpDialog component / handler that opens documentation) and replace the
second-argument-only call with the three-argument form including
"noopener,noreferrer".
In `@src/components/layout/MainContent.tsx`:
- Around line 2621-2628: The ERD dialog is opened regardless of whether
connectToDatabase(...) succeeded, causing stale schema to show; update the logic
so setShowERDDialog(true) only runs when the connection switch succeeds: call
setShowERDDialog(true) inside the try block after await
connectToDatabase(activeTab.target.connectionId, activeTab.target.database), or
rethrow/return from the catch to prevent falling through; reference the
activeTab, connectToDatabase, setShowERDDialog, activeConnection and
selectedDatabase variables to locate and modify the conditional flow.
In `@src/components/tools/ActivityMonitor.tsx`:
- Line 106: The effect in ActivityMonitor resets targetDb via setTargetDb("")
and immediately calls fetchStats(), but fetchStats is memoized with targetDb in
its closure (useCallback(..., [currentDb, activeConnection, targetDb])) so it
can still read the previous targetDb; change the logic so fetchStats runs with
the updated targetDb: either call a variant of fetchStats that accepts an
explicit targetDb argument (pass "") or move the fetchStats invocation into a
separate useEffect that depends on targetDb (i.e., call fetchStats when targetDb
changes), and update references to the useCallback/memoized fetchStats
accordingly to ensure it uses the current targetDb value; update useEffect (the
one with isOpen) to only set state (setTargetDb, setSearchTerm, etc.) and not
directly call fetchStats.
In `@src/components/tools/ERDCanvas.tsx`:
- Around line 35-40: FitViewHelper currently calls fitView on every render
because useEffect has no dependency array; change the effect to run only once or
only when the fitView reference changes by adding a dependency array (e.g.,
[fitView]) so it schedules requestAnimationFrame/f fitView only on mount or when
fitView changes, preventing repeated refitting during ERDFlow updates and
preserving user pan/zoom.
In `@src/components/tools/ERDDialog.tsx`:
- Around line 244-264: The exported SVG currently only injects a tiny static
<style> block (built around svgData using clone/inner/rect in ERDDialog.tsx), so
Tailwind classes, React Flow CSS and theme variables are missing and the SVG
appears unstyled; fix by collecting and embedding the required CSS into the SVG:
gather computed styles for cloned nodes (using getComputedStyle on elements in
clone) and/or inline critical styles into the cloned HTML, and also concatenate
the app’s stylesheet rules (filter document.styleSheets for Tailwind/React
Flow/theme sheets and serialize their CSSText) into a <style> tag injected into
svgData so the foreignObject contains all necessary CSS for correct rendering
outside the app.
- Around line 285-290: The hasNoTables check in ERDDialog incorrectly uses
data.tables.length and misses the case where filters/search hide all tables;
update the logic to use the actual visible/filtered tables collection (e.g., the
variable or selector that represents tables after applying search/schema filters
— or derive visibleTables by applying the same filtering used to render the ERD)
and change both occurrences (the hasNoTables declaration and the similar check
at lines ~460-468) to test visibleTables.length === 0 so the dialog shows the
"No tables match the current filters" state instead of an empty canvas.
In `@src/components/tools/TableSelectorDialog.tsx`:
- Around line 155-162: In TableSelectorDialog.tsx inside the filteredTables.map
callback, the current code checks exec(table)?.groups but the regex
/^(.*?)\.(.*)$/ has no named groups so it always falls back to schema "",
tableName table; fix by extracting captures directly: call const match =
/^(.*?)\.(.*)$/.exec(table) and if match set schema = match[1] and tableName =
match[2], otherwise schema = "" and tableName = table (or alternatively use
table.split(".", 2) to derive schema and tableName). Update the block around
filteredTables.map to use this match-based extraction and remove the
.groups-based check.
In `@src/components/tools/useERData.ts`:
- Around line 283-315: The shared abortRef in buildERData allows older in-flight
requests to overwrite newer results; change to a per-request token (e.g., a
requestIdRef counter or a local unique token captured by the async closure) and
compare that token before applying results or cache updates so only the latest
request writes state; update the same pattern used around the other fetch block
(lines referenced near 534-539) so both places create and check a per-request
token instead of toggling abortRef.current.
- Around line 285-288: The check for CockroachDB is wrong: in useERData.ts the
dbType variable (from connectionType) is compared against "cockroachdb" in the
isPostgres array, but the codebase uses "cockroach"; update the isPostgres
determination so Cockroach connections match the PostgreSQL branch (e.g.,
include "cockroach" alongside "cockroachdb" in the array used by isPostgres or
normalize connectionType to a canonical value before the includes check) so the
ERD loader follows the PostgreSQL path when connectionType is "cockroach".
- Around line 119-121: The schemaClause interpolation is vulnerable because
schemas are inserted raw; update the branch that builds schemaClause (the
schemas variable and schemaClause constant) to escape single quotes in each
schema name (e.g., replace ' with '') before wrapping in single quotes, or reuse
the same escaping helper used by fetchPostgresColumns(), so schemas.map(...)
produces safely quoted values and then join(",") to build the IN list for
schemaClause.
- Around line 125-135: The current SQL in useERData.ts pairs source and target
columns incorrectly for composite foreign keys because it joins pg_attribute to
ANY(c.conkey)/ANY(c.confkey) independently; replace those joins with
unnest(c.conkey) WITH ORDINALITY AS src(colnum, ord) and unnest(c.confkey) WITH
ORDINALITY AS tgt(colnum, ord), join src.ord = tgt.ord and then join
pg_attribute AS a ON a.attrelid = c.conrelid AND a.attnum = src.colnum and
pg_attribute AS af ON af.attrelid = c.confrelid AND af.attnum = tgt.colnum so
column ordering is preserved for multi-column FKs and source/target columns are
paired correctly.
- Around line 478-485: The current dedup logic in the loop over fkRelationships
collapses multiple distinct FKs between the same tables because edgeKey is only
`${src}->${tgt}`; update the key to include a unique identifier from the FK
(e.g., fk.constraintName or the joined fk.sourceColumns/fk.targetColumns) so
parallel relationships are preserved. Locate the loop using fkRelationships,
normalizeTableName, nodeIdSet, edgeKey and edgeSet and change edgeKey generation
to include the FK-specific fields (fallback to column lists if constraintName is
absent) before checking/adding to edgeSet.
---
Outside diff comments:
In `@src-tauri/src/cli.rs`:
- Around line 229-244: The PATH lookup for ToolKind::Psql currently checks
kind.all_binaries(), which can pick pg_dump/pg_restore/pg_dumpall and break
cli_detect_pg_version; change the logic in find_available (and the similar block
around the other occurrence) so that when kind == ToolKind::Psql you only probe
kind.primary_binary() on PATH (use which::which for that single name and return
its PathBuf with version 0), otherwise keep existing all_binaries() behavior;
ensure references to find_available, ToolKind::Psql, kind.all_binaries(),
kind.primary_binary(), and cli_detect_pg_version are used to locate the affected
code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 00bd49e0-a0da-47ee-ab10-686d321c1ce5
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (17)
.github/workflows/beta.ymlCHANGELOG.mdpackage.jsonsrc-tauri/.cargo/config.tomlsrc-tauri/src/cli.rssrc/components/explorer/DatabaseExplorer.tsxsrc/components/help/HelpDialog.tsxsrc/components/layout/MainContent.tsxsrc/components/tools/ActivityMonitor.tsxsrc/components/tools/ERDCanvas.tsxsrc/components/tools/ERDDialog.tsxsrc/components/tools/RelationshipEdge.tsxsrc/components/tools/TableNode.tsxsrc/components/tools/TableSelectorDialog.tsxsrc/components/tools/useERData.tssrc/styles/erd.csswebsite/src/content/docs/editor/er-diagram.mdx
- cli: Windows PSQL path uses %ProgramFiles% env vars (not hardcoded C:\) - cli: ToolKind::Psql.all_binaries narrowed to just ["psql"] - HelpDialog: window.open fallback gets noopener,noreferrer - MainContent: ERD dialog return on failed connection switch - ActivityMonitor: stale targetDb fixed via optional override param + ref - ERDCanvas: FitViewHelper useEffect gets [] deps - useERData: escape schema names in FK query SQL injection - useERData: composite FK query uses unnest WITH ORDINALITY (fix cross-join) - useERData: abortRef -> generationRef to prevent stale writes - useERData: cockroachdb -> cockroach engine string typo - useERData: edge dedup key includes column names (parallel FKs preserved)
Summary
Interactive ER Diagram visualization for PostgreSQL, MySQL/MariaDB, and SQLite with compact view by default, table selector to prevent freezing on large schemas, and connection-aware opening that resolves the active editor tab's target database.
Related issue
Fixes #208
Fixes #204
Fixes #131
Changes
Testing notes
npx tsc --noEmit— passescargo check— passesChecklist
npm run tauri devornpm run tauri build) and verified the change works.npx tsc --noEmit).cargo check --manifest-path src-tauri/Cargo.toml).#[tauri::command], I registered it insrc-tauri/src/lib.rs'sgenerate_handler!.CHANGELOG.mdunder## [Unreleased].storage.rs,updater.rs,capabilities/, or the AI assistant code path, I flagged it in the summary above.Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Documentation