Skip to content

Fixed an issue where Geometry Viewer was showing stale data and not a…#9644

Open
anilsahoo20 wants to merge 4 commits intopgadmin-org:masterfrom
anilsahoo20:GH-9392
Open

Fixed an issue where Geometry Viewer was showing stale data and not a…#9644
anilsahoo20 wants to merge 4 commits intopgadmin-org:masterfrom
anilsahoo20:GH-9392

Conversation

@anilsahoo20
Copy link
Contributor

@anilsahoo20 anilsahoo20 commented Feb 18, 2026

…uto-updating on query reruns or new query runs with new data or different geometry columns in Query tool. #9392

Summary by CodeRabbit

  • New Features

    • Geometry Viewer tab auto-refreshes and can be opened/updated automatically when result rows or columns change.
    • Viewer now fully reloads when coordinate system or geometry context changes to ensure correct rendering.
  • Improvements

    • Selection persistence improved so previously selected geometries remain matched after data updates.
    • Geometry parsing is deferred until a geometry column is selected to reduce work.
    • Safer tab-visibility checks, resize handling, and cleanup for improved stability.

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

Walkthrough

Adds PK-based row-identification and memoized selection logic to GeometryViewer; ResultSet gets helpers to compute filtered rows and open/refresh the Geometry Viewer tab when rows or columns change; also tightens ResizeObserver lifecycle and conditional geometry parsing. (≤50 words)

Changes

Cohort / File(s) Summary
Geometry Viewer
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx
Adds useMemo, prevStateRef, mapKey state; computes currentColumnKey/currentColumnNames; introduces PK helpers (PK_COLUMN_NAMES, findPkColumn, getRowIdentifier, createIdentifierSet, matchRowSelection); memoizes displayRows and parsed geometry data; changes map-keying to include mapKey; adds tab-visibility guards and safer ResizeObserver cleanup.
Result Set & Integration
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
Adds prevRowsRef/prevColumnsRef; implements getFilteredRowsForGeometryViewer and openGeometryViewerTab; centralizes geometry-open logic in renderGeometries; auto-refreshes Geometry Viewer when rows/columns change and geometry tab is open; persists previous-state refs for change detection.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ResultSet
  participant TabManager
  participant GeometryViewer
  participant Map

  User->>ResultSet: select cell or change rows/columns
  ResultSet->>ResultSet: getFilteredRowsForGeometryViewer(rows, cols, selection)
  ResultSet->>TabManager: openGeometryViewerTab(filteredData)
  TabManager-->>GeometryViewer: supply tab data / ensure tab visible
  GeometryViewer->>GeometryViewer: compute currentColumnKey/currentColumnNames and pkColumn
  GeometryViewer->>GeometryViewer: derive displayRows (memoized) and parse geometries if column selected
  GeometryViewer->>Map: render map with key = `${selectedSRID}-${mapKey}`
  Map->>GeometryViewer: attach ResizeObserver
  GeometryViewer->>GeometryViewer: cleanup ResizeObserver on unmount or change
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fix: addressing stale data display in Geometry Viewer and adding auto-update functionality on query reruns, which aligns with the substantial changes made to GeometryViewer.jsx and ResultSet.jsx.

✏️ 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: 3

🧹 Nitpick comments (3)
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx (2)

485-490: Array.includes for PK lookup is O(n) per row — use a Set for large datasets.

Both lines 487 and 504 use prevState.selectedRowPKs.includes(row.__temp_PK) inside .filter(), making the overall complexity O(n × m). For result sets approaching the 100k geometry limit, this can be sluggish.

♻️ Proposed fix using Set
-      if (prevState.selectedRowPKs.length > 0 && prevState.selectedRowPKs.length < rows.length) {
-        const newSelectedPKs = rows
-          .filter(row => prevState.selectedRowPKs.includes(row.__temp_PK))
-          .map(row => row.__temp_PK);
+      if (prevState.selectedRowPKs.length > 0 && prevState.selectedRowPKs.length < rows.length) {
+        const prevPKSet = new Set(prevState.selectedRowPKs);
+        const newSelectedPKs = rows
+          .filter(row => prevPKSet.has(row.__temp_PK))
+          .map(row => row.__temp_PK);

Similarly in displayRows:

   const displayRows = React.useMemo(() => {
     if (!currentColumnKey || rows.length === 0) return [];
     
     const selectedPKs = prevStateRef.current.selectedRowPKs;
-    return selectedPKs.length > 0 && selectedPKs.length < rows.length
-      ? rows.filter(row => selectedPKs.includes(row.__temp_PK))
+    if (selectedPKs.length > 0 && selectedPKs.length < rows.length) {
+      const pkSet = new Set(selectedPKs);
+      return rows.filter(row => pkSet.has(row.__temp_PK));
+    }
+    return rows;
-      : rows;
   }, [rows, currentColumnKey]);

Also applies to: 499-506

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 485 - 490, The code uses
prevState.selectedRowPKs.includes(row.__temp_PK) inside rows.filter(...) (see
the block updating prevStateRef.current.selectedRowPKs and the similar logic in
displayRows), which is O(n*m); change it to build a Set once from
prevState.selectedRowPKs (e.g., const prevPKs = new
Set(prevState.selectedRowPKs)) and then use prevPKs.has(row.__temp_PK) in the
.filter() calls so lookups become O(1); update both the block that assigns
prevStateRef.current.selectedRowPKs and the analogous displayRows section to use
the Set-based lookup.

441-497: Reading a mutable ref inside useMemo creates a render-order dependency.

displayRows (line 502) reads prevStateRef.current.selectedRowPKs, but this ref is updated in the useEffect below, which runs after render. On the first render cycle where rows changes (without a column change), displayRows is computed with the previous cycle's selectedRowPKs, and no re-render is triggered to correct it (unlike the column-change branches which call setMapKey).

In practice the fallback (selectedPKs.length === 0 → return rows) masks this for initial loads. But for subsequent row-set changes where a subset was previously selected, the displayed data during that render frame is based on stale PKs until an unrelated re-render occurs.

Consider either:

  1. Moving the PK computation into displayRows itself (pure derivation, no ref), or
  2. Storing selectedRowPKs in state (via useState) so updating it triggers a re-render.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 441 - 497, The bug is stale selectedRowPKs being read from
prevStateRef during render; instead lift selectedRowPKs into React state so
updates trigger renders: add a useState (e.g., [selectedRowPKs,
setSelectedRowPKs]) and remove selectedRowPKs from prevStateRef, update the
useEffect branches that currently write prevStateRef.current.selectedRowPKs to
call setSelectedRowPKs (including the column-change branch that resets to []),
and update any consumers (like displayRows) to read selectedRowPKs state rather
than prevStateRef.current.selectedRowPKs; keep prevStateRef for
columnKey/columnNames only and continue calling setMapKey where needed.
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (1)

1501-1520: Auto-update effect: columns.find picks only the first geometry/geography column.

Line 1505 selects the first column matching 'geometry' or 'geography'. If a table has multiple geometry columns and the user previously chose a different one via the manual trigger, the auto-refresh will silently switch to the first one.

Consider preserving the previously selected geometry column (e.g., by storing its key in a ref when TRIGGER_RENDER_GEOMETRIES fires) and falling back to columns.find(...) only if that column no longer exists in the new result set.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx`
around lines 1501 - 1520, The auto-update useEffect currently picks the first
geometry/geography column via columns.find, which can override a user-selected
geometry column; modify this by persisting the user-selected geometry column key
(e.g., in a ref like selectedGeometryKeyRef that's set when
TRIGGER_RENDER_GEOMETRIES fires) and in the effect prefer columns.find(col =>
col.key === selectedGeometryKeyRef.current) before falling back to
columns.find(col => col.cell === 'geometry' || col.cell === 'geography'); keep
using getFilteredRowsForGeometryViewer and openGeometryViewerTab as before, and
clear selectedGeometryKeyRef when that key no longer exists in the new columns
so the fallback behavior works.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Line 540: Fix the typo in the inline comment inside GeometryViewer.jsx: change
"Dyanmic CRS is not supported." to "Dynamic CRS is not supported." so the
comment above the map key logic (in the GeometryViewer component) reads
correctly; no code logic changes needed, just update the comment text near the
line referencing srid and mapKey.
- Around line 486-490: Replace the hardcoded '__temp_PK' property with the
shared GRID_ROW_SELECT_KEY constant: update the filtering and mapping that
builds newSelectedPKs (and any other uses in this snippet that reference
__temp_PK) to use rows[GRID_ROW_SELECT_KEY] (or the bracket notation using the
GRID_ROW_SELECT_KEY symbol) so the logic that computes
prevStateRef.current.selectedRowPKs and compares to prevState.selectedRowPKs
uses the actual row key constant defined in QueryToolDataGrid/ResultSet; apply
this substitution for all five occurrences referenced in the comment.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx`:
- Around line 1501-1520: The effect currently checks
layoutDocker.isTabOpen(PANELS.GEOMETRY) which triggers updates even when the
geometry tab is not visible; change that call to
layoutDocker.isTabVisible(PANELS.GEOMETRY) inside the useEffect so the geometry
viewer update logic (using prevRowsRef, prevColumnsRef,
getFilteredRowsForGeometryViewer and openGeometryViewerTab) only runs when the
tab is actively visible; keep the rest of the effect (rows/columns change
detection and the null/empty fallback) unchanged.

---

Nitpick comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 485-490: The code uses
prevState.selectedRowPKs.includes(row.__temp_PK) inside rows.filter(...) (see
the block updating prevStateRef.current.selectedRowPKs and the similar logic in
displayRows), which is O(n*m); change it to build a Set once from
prevState.selectedRowPKs (e.g., const prevPKs = new
Set(prevState.selectedRowPKs)) and then use prevPKs.has(row.__temp_PK) in the
.filter() calls so lookups become O(1); update both the block that assigns
prevStateRef.current.selectedRowPKs and the analogous displayRows section to use
the Set-based lookup.
- Around line 441-497: The bug is stale selectedRowPKs being read from
prevStateRef during render; instead lift selectedRowPKs into React state so
updates trigger renders: add a useState (e.g., [selectedRowPKs,
setSelectedRowPKs]) and remove selectedRowPKs from prevStateRef, update the
useEffect branches that currently write prevStateRef.current.selectedRowPKs to
call setSelectedRowPKs (including the column-change branch that resets to []),
and update any consumers (like displayRows) to read selectedRowPKs state rather
than prevStateRef.current.selectedRowPKs; keep prevStateRef for
columnKey/columnNames only and continue calling setMapKey where needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx`:
- Around line 1501-1520: The auto-update useEffect currently picks the first
geometry/geography column via columns.find, which can override a user-selected
geometry column; modify this by persisting the user-selected geometry column key
(e.g., in a ref like selectedGeometryKeyRef that's set when
TRIGGER_RENDER_GEOMETRIES fires) and in the effect prefer columns.find(col =>
col.key === selectedGeometryKeyRef.current) before falling back to
columns.find(col => col.cell === 'geometry' || col.cell === 'geography'); keep
using getFilteredRowsForGeometryViewer and openGeometryViewerTab as before, and
clear selectedGeometryKeyRef when that key no longer exists in the new columns
so the fallback behavior works.

…uto-updating on query reruns or new query runs with new data or different geometry columns in Query tool. pgadmin-org#9392
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.

🧹 Nitpick comments (3)
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx (2)

487-538: displayRows useMemo reads from prevStateRef which is updated by a useEffect that runs after render — the matched selection is always one render behind.

useMemo (line 529) runs during render and reads prevStateRef.current.selectedRowData. The useEffect (line 487) that computes the matched selection runs after render and writes to that same ref — but triggers no state update to force a re-render. This means when only rows changes (same columns), displayRows uses the previous render's selectedRowData, and the freshly matched data computed by the effect is never reflected.

In practice, since openGeometryViewerTab in ResultSet.jsx likely creates a new GeometryViewer instance each time (resetting all refs), this code path may rarely be exercised. But if the layout docker reuses the component instance, the selection-preservation logic will be off by one render.

Consider moving the matching logic into the useMemo directly (removing the effect for the row-matching branch) so that displayRows always reflects the current computation:

♻️ Sketch — merge matching into useMemo
-  useEffect(() => {
-    const prevState = prevStateRef.current;
-    if (!currentColumnKey) {
-      setMapKey(prev => prev + 1);
-      ...
-      return;
-    }
-    if (currentColumnKey !== prevState.columnKey || 
-        currentColumnNames !== prevState.columnNames) {
-      setMapKey(prev => prev + 1);
-      ...
-      return;
-    }
-    if (currentColumnKey === prevState.columnKey && 
-        currentColumnNames === prevState.columnNames &&
-        rows.length > 0) {
-      ...
-      prevStateRef.current.selectedRowData = newSelectedRowData;
-    }
-  }, [currentColumnKey, currentColumnNames, rows, pkColumn, columns]);
-
-  const displayRows = React.useMemo(() => {
-    ...
-    const selected = prevState.selectedRowData;
-    return selected.length > 0 && selected.length < rows.length ? selected : rows;
-  }, [rows, currentColumnKey, currentColumnNames]);
+  // Keep the effect only for the branches that call setMapKey
+  useEffect(() => {
+    if (!currentColumnKey) {
+      setMapKey(prev => prev + 1);
+      prevStateRef.current = { columnKey: null, columnNames: null, selectedRowData: [] };
+      return;
+    }
+    if (currentColumnKey !== prevStateRef.current.columnKey ||
+        currentColumnNames !== prevStateRef.current.columnNames) {
+      setMapKey(prev => prev + 1);
+      prevStateRef.current = { columnKey: currentColumnKey, columnNames: currentColumnNames, selectedRowData: [] };
+    }
+  }, [currentColumnKey, currentColumnNames]);
+
+  // Compute displayRows synchronously during render
+  const displayRows = React.useMemo(() => {
+    if (!currentColumnKey || rows.length === 0) return [];
+    const prevState = prevStateRef.current;
+    if (currentColumnKey !== prevState.columnKey || currentColumnNames !== prevState.columnNames) {
+      return rows;
+    }
+    let result = rows;
+    if (prevState.selectedRowData.length > 0 && prevState.selectedRowData.length < rows.length) {
+      const matched = matchRowSelection(prevState.selectedRowData, rows, pkColumn, columns);
+      result = matched.length > 0 ? matched : rows;
+    }
+    prevStateRef.current.selectedRowData = result;
+    return result;
+  }, [rows, columns, currentColumnKey, currentColumnNames, pkColumn]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 487 - 538, The effect-based matching is producing stale results
because useEffect runs after render; move the row-matching logic into the
displayRows useMemo so it computes matched selection synchronously during
render: in other words, remove the branch of the useEffect that computes matched
selection and instead, inside the displayRows React.useMemo, call
matchRowSelection(prevStateRef.current.selectedRowData, rows, pkColumn, columns)
when currentColumnKey/currentColumnNames match and rows changed, then return the
matched result (or rows fallback) and also update
prevStateRef.current.selectedRowData with that computed selection so subsequent
renders use the up-to-date value; keep the effect only for resetting
mapKey/prevState when columnKey/columnNames change or become null.

52-52: Hardcoded PK heuristic won't match many real-world primary keys.

PK_COLUMN_NAMES = ['id', 'oid', 'ctid'] is a narrow heuristic. Columns like gid (common in PostGIS tables), pk, UUIDs, or composite PKs will miss, falling back to columns[0]?.key or JSON.stringify(row). Since this is only used for best-effort selection preservation (not data integrity), the fallback is safe — but worth a comment noting the limitation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
at line 52, PK_COLUMN_NAMES is a too-narrow heuristic (only ['id','oid','ctid'])
and will miss common PK names like gid, pk, UUID columns and composite keys;
update the heuristic in GeometryViewer.jsx by (1) extending PK_COLUMN_NAMES to
include 'gid' and 'pk', (2) adding a simple UUID-name/pattern check (e.g.,
detect column names containing 'uuid' or matching a UUID-like regex) when
choosing a primary-key-like column, and (3) leave the existing safe fallbacks
(columns[0]?.key and JSON.stringify(row)) intact; also add a brief inline
comment near PK_COLUMN_NAMES and the selection-preservation logic explaining
that this is a best-effort heuristic and may not catch composite or custom PKs.
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (1)

1501-1520: Auto-update effect: dependency list is broader than necessary, causing redundant runs.

The effect's purpose is to react to rows/columns changes when the geometry tab is open. However, including getFilteredRowsForGeometryViewer (which changes when selectedRows/selectedColumns change) and openGeometryViewerTab in the dependency array causes this effect to re-run on every selection change — even though the rowsChanged || columnsChanged guard prevents a tab update. These redundant executions are harmless but wasteful.

Consider extracting the current values of the callbacks via refs (similar to useLatestFunc already in the codebase) so the dependency array only contains [rows, columns, layoutDocker].

Also, isTabOpen is intentionally used instead of isTabVisible to avoid unwanted focus on the Geometry Viewer tab when opening Query Tool. Based on learnings, this is the correct choice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx`
around lines 1501 - 1520, Extract the callbacks into stable refs using the
existing useLatestFunc (or implement refs) so the effect uses the latest
getFilteredRowsForGeometryViewer and openGeometryViewerTab via those refs
instead of listing them as dependencies; then change the useEffect dependency
array to only [rows, columns, layoutDocker] while keeping the same internal
logic that compares prevRowsRef/current prevColumnsRef, checks
layoutDocker.isTabOpen(PANELS.GEOMETRY), finds currentGeometryColumn, and calls
the ref-wrapped getFilteredRowsForGeometryViewer() and openGeometryViewerTab()
(or passes null/[] when no geometry column).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 487-538: The effect-based matching is producing stale results
because useEffect runs after render; move the row-matching logic into the
displayRows useMemo so it computes matched selection synchronously during
render: in other words, remove the branch of the useEffect that computes matched
selection and instead, inside the displayRows React.useMemo, call
matchRowSelection(prevStateRef.current.selectedRowData, rows, pkColumn, columns)
when currentColumnKey/currentColumnNames match and rows changed, then return the
matched result (or rows fallback) and also update
prevStateRef.current.selectedRowData with that computed selection so subsequent
renders use the up-to-date value; keep the effect only for resetting
mapKey/prevState when columnKey/columnNames change or become null.
- Line 52: PK_COLUMN_NAMES is a too-narrow heuristic (only ['id','oid','ctid'])
and will miss common PK names like gid, pk, UUID columns and composite keys;
update the heuristic in GeometryViewer.jsx by (1) extending PK_COLUMN_NAMES to
include 'gid' and 'pk', (2) adding a simple UUID-name/pattern check (e.g.,
detect column names containing 'uuid' or matching a UUID-like regex) when
choosing a primary-key-like column, and (3) leave the existing safe fallbacks
(columns[0]?.key and JSON.stringify(row)) intact; also add a brief inline
comment near PK_COLUMN_NAMES and the selection-preservation logic explaining
that this is a best-effort heuristic and may not catch composite or custom PKs.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx`:
- Around line 1501-1520: Extract the callbacks into stable refs using the
existing useLatestFunc (or implement refs) so the effect uses the latest
getFilteredRowsForGeometryViewer and openGeometryViewerTab via those refs
instead of listing them as dependencies; then change the useEffect dependency
array to only [rows, columns, layoutDocker] while keeping the same internal
logic that compares prevRowsRef/current prevColumnsRef, checks
layoutDocker.isTabOpen(PANELS.GEOMETRY), finds currentGeometryColumn, and calls
the ref-wrapped getFilteredRowsForGeometryViewer() and openGeometryViewerTab()
(or passes null/[] when no geometry column).

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: 1

🧹 Nitpick comments (1)
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx (1)

196-221: Row identifier heuristics may match wrong rows in some scenarios.

A few concerns with the row identification approach:

  1. PK_COLUMN_NAMES only covers 'id' and 'oid' with exact case match. Common geometry query columns like gid, pk, or ID (uppercase) won't be detected.
  2. The first-column fallback in getRowIdentifier (line 206-207) is not guaranteed unique — it could cause false-positive matches when two different rows share the same first-column value but differ in other columns.
  3. JSON.stringify(row) is property-order-dependent and expensive for large rows.

Since this is best-effort for auto-update UX, these may be acceptable, but consider at minimum a case-insensitive PK name match:

♻️ Suggested improvement
-const PK_COLUMN_NAMES = ['id', 'oid'];
+const PK_COLUMN_NAMES = ['id', 'oid', 'gid', 'pk'];

 function findPkColumn(columns) {
-  return columns.find(c => PK_COLUMN_NAMES.includes(c.name));
+  return columns.find(c => PK_COLUMN_NAMES.includes(c.name?.toLowerCase()));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 196 - 221, Update the PK detection and fallback used by
findPkColumn/getRowIdentifier: make PK_COLUMN_NAMES matching case-insensitive
and include common alternatives like "gid" and "pk" by normalizing column.name
to lowerCase before checking (refer to PK_COLUMN_NAMES and function
findPkColumn), and replace the fragile first-column/JSON.stringify fallback in
getRowIdentifier with a deterministic composite identifier built from the
ordered columns array (e.g., join of column keys and their values) so
identifiers are stable and less error-prone; ensure createIdentifierSet and
matchRowSelection continue to call getRowIdentifier so the new logic is used
everywhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 486-551: The bug is caused by splitting selection logic between
the useEffect and the displayRows useMemo, causing displayRows to read stale
prevStateRef.current.selectedRowData during render; consolidate the
row-selection computation into the displayRows React.useMemo so it
deterministically returns the correct rows during render (use
prevStateRef.current, rows, pkColumn, columns and call matchRowSelection there),
and keep the useEffect simplified to only detect column/columns changes and bump
setMapKey or to sync prevStateRef.current (assign the computed selection into
prevStateRef.current.selectedRowData) after render; ensure you still increment
setMapKey when currentColumnKey or currentColumnNames change and that
parseData(data) continues to consume displayRows.

---

Nitpick comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 196-221: Update the PK detection and fallback used by
findPkColumn/getRowIdentifier: make PK_COLUMN_NAMES matching case-insensitive
and include common alternatives like "gid" and "pk" by normalizing column.name
to lowerCase before checking (refer to PK_COLUMN_NAMES and function
findPkColumn), and replace the fragile first-column/JSON.stringify fallback in
getRowIdentifier with a deterministic composite identifier built from the
ordered columns array (e.g., join of column keys and their values) so
identifiers are stable and less error-prone; ensure createIdentifierSet and
matchRowSelection continue to call getRowIdentifier so the new logic is used
everywhere.

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.

🧹 Nitpick comments (3)
web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx (3)

477-484: Minor: currentColumnKey memo provides no memoization benefit.

column?.key is a simple property access that always returns the same value for the same column reference. The useMemo adds overhead without caching benefit since it depends on [column] — whenever column changes, the memo recomputes anyway. A plain const would be equivalent and simpler:

const currentColumnKey = column?.key;

That said, this is cosmetic — no functional impact.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 477 - 484, Replace the unnecessary useMemo wrapper around
currentColumnKey: instead of computing currentColumnKey via useMemo(() =>
column?.key, [column]) simply assign the value directly (const currentColumnKey
= column?.key) in the GeometryViewer component; this removes the extra hook
overhead while keeping the same semantics and still updates when the column
variable changes.

52-52: Row identification heuristic is fragile and can silently match wrong rows.

PK_COLUMN_NAMES only covers 'id' and 'oid'. Most tables use names like user_id, gid, pk, etc., so findPkColumn will return undefined for those. The fallback in getRowIdentifier (first column value) is not guaranteed to be unique — if two rows share the same first-column value, matchRowSelection will match both, showing incorrect geometry.

Additionally, the JSON.stringify last-resort fallback (line 207) is expensive for rows containing large hex geometry strings and produces unstable keys if property enumeration order differs across rows.

Consider either:

  1. Accepting the PK column key from the caller (ResultSet already knows the actual row key via GRID_ROW_SELECT_KEY/clientPK), or
  2. At minimum, using a composite key from all column values rather than just the first column as the non-PK fallback, to reduce false matches.
#!/bin/bash
# Check how ResultSet passes data to GeometryViewer and whether it already has PK info
rg -n -B5 -A10 "GeometryViewer" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx | head -80

echo ""
echo "=== Check GRID_ROW_SELECT_KEY and clientPK in ResultSet ==="
rg -n "GRID_ROW_SELECT_KEY\|clientPK" web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx | head -30

Also applies to: 196-221

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
at line 52, The current row identification logic in GeometryViewer
(findPkColumn, getRowIdentifier, matchRowSelection) is fragile: update it to
prefer a caller-supplied primary-key when available (use
GRID_ROW_SELECT_KEY/clientPK coming from ResultSet) and only fall back to
computed keys if none provided; if you must compute a fallback, build a stable
composite key from all column values in predictable column order (not just the
first column) instead of JSON.stringify or single-column values, and avoid
expensive serialization of large geometry blobs by excluding geometry columns
when composing the key; locate and modify functions findPkColumn,
getRowIdentifier, and matchRowSelection in GeometryViewer.jsx and ensure
ResultSet passes its GRID_ROW_SELECT_KEY/clientPK through props when rendering
GeometryViewer.

486-516: displayRows used in effect but not listed in its dependency array.

Line 514 assigns prevStateRef.current.selectedRowData = displayRows, but displayRows is absent from the dependency array on line 516. While displayRows is fully derived from the listed dependencies today (making this functionally safe), adding it to the dependency array improves clarity and prevents fragility if displayRows's computation changes in the future.

Add displayRows to the dependency array:

Suggested fix
-  }, [currentColumnKey, currentColumnNames, rows, pkColumn, columns]);
+  }, [currentColumnKey, currentColumnNames, rows, pkColumn, columns, displayRows]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`
around lines 486 - 516, The effect in useEffect (which updates
prevStateRef.current.selectedRowData = displayRows) uses displayRows but does
not list it in the dependency array; update the dependency array for the
useEffect that references
currentColumnKey/currentColumnNames/rows/pkColumn/columns to also include
displayRows so the effect re-runs whenever displayRows changes, ensuring
prevStateRef and mapKey logic (setMapKey, prevStateRef.current updates) remain
consistent; locate the useEffect and add displayRows to its dependency array.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx`:
- Around line 477-484: Replace the unnecessary useMemo wrapper around
currentColumnKey: instead of computing currentColumnKey via useMemo(() =>
column?.key, [column]) simply assign the value directly (const currentColumnKey
= column?.key) in the GeometryViewer component; this removes the extra hook
overhead while keeping the same semantics and still updates when the column
variable changes.
- Line 52: The current row identification logic in GeometryViewer (findPkColumn,
getRowIdentifier, matchRowSelection) is fragile: update it to prefer a
caller-supplied primary-key when available (use GRID_ROW_SELECT_KEY/clientPK
coming from ResultSet) and only fall back to computed keys if none provided; if
you must compute a fallback, build a stable composite key from all column values
in predictable column order (not just the first column) instead of
JSON.stringify or single-column values, and avoid expensive serialization of
large geometry blobs by excluding geometry columns when composing the key;
locate and modify functions findPkColumn, getRowIdentifier, and
matchRowSelection in GeometryViewer.jsx and ensure ResultSet passes its
GRID_ROW_SELECT_KEY/clientPK through props when rendering GeometryViewer.
- Around line 486-516: The effect in useEffect (which updates
prevStateRef.current.selectedRowData = displayRows) uses displayRows but does
not list it in the dependency array; update the dependency array for the
useEffect that references
currentColumnKey/currentColumnNames/rows/pkColumn/columns to also include
displayRows so the effect re-runs whenever displayRows changes, ensuring
prevStateRef and mapKey logic (setMapKey, prevStateRef.current updates) remain
consistent; locate the useEffect and add displayRows to its dependency array.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments