From 84b6d720dba7deea22c92d16f9588db9397897b2 Mon Sep 17 00:00:00 2001 From: Anil Sahoo Date: Wed, 18 Feb 2026 11:27:16 +0530 Subject: [PATCH 1/8] Fixed an issue where Geometry Viewer was showing stale data and not auto-updating on query reruns or new query runs with new data or different geometry columns in Query tool. #9392 --- .../js/components/sections/GeometryViewer.jsx | 97 +++++++++++++++++-- .../js/components/sections/ResultSet.jsx | 72 ++++++++++---- 2 files changed, 144 insertions(+), 25 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index 8842d6d287c..5aa12ee35e5 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -436,25 +436,110 @@ export function GeometryViewer({rows, columns, column}) { const mapRef = React.useRef(); const contentRef = React.useRef(); - const data = parseData(rows, columns, column); const queryToolCtx = React.useContext(QueryToolContext); + // Track previous state to detect changes + const prevStateRef = React.useRef({ + columnKey: null, + columnNames: null, + selectedRowPKs: [], + }); + + const [mapKey, setMapKey] = React.useState(0); + const currentColumnKey = column?.key; + const currentColumnNames = React.useMemo( + () => columns.map(c => c.key).sort().join(','), + [columns] + ); + + // Detect when to clear, filter, or re-render the map based on changes in geometry column, columns list, or rows + useEffect(() => { + const prevState = prevStateRef.current; + + if (!currentColumnKey) { + setMapKey(prev => prev + 1); + prevStateRef.current = { + columnKey: null, + columnNames: null, + selectedRowPKs: [], + }; + return; + } + + if (currentColumnKey !== prevState.columnKey || + currentColumnNames !== prevState.columnNames) { + setMapKey(prev => prev + 1); + prevStateRef.current = { + columnKey: currentColumnKey, + columnNames: currentColumnNames, + selectedRowPKs: [], + }; + return; + } + + if (currentColumnKey === prevState.columnKey && + currentColumnNames === prevState.columnNames && + rows.length > 0) { + + // If user previously selected specific rows, filter them from new data + 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); + + prevStateRef.current.selectedRowPKs = newSelectedPKs.length > 0 ? newSelectedPKs : rows.map(r => r.__temp_PK); + } else { + // All rows are displayed + const allPKs = rows.map(r => r.__temp_PK); + prevStateRef.current.selectedRowPKs = allPKs; + } + } + }, [currentColumnKey, currentColumnNames, rows]); + + 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)) + : rows; + }, [rows, currentColumnKey]); + + // Parse geometry data only when needed + const data = React.useMemo(() => { + if (!currentColumnKey) { + return { + 'geoJSONs': [], + 'selectedSRID': 0, + 'getPopupContent': undefined, + 'infoList': [gettext('Select a geometry/geography column to visualize.')], + }; + } + return parseData(displayRows, columns, column); + }, [displayRows, columns, column, currentColumnKey]); + useEffect(()=>{ let timeoutId; const contentResizeObserver = new ResizeObserver(()=>{ clearTimeout(timeoutId); - if(queryToolCtx.docker.isTabVisible(PANELS.GEOMETRY)) { + if(queryToolCtx?.docker?.isTabVisible(PANELS.GEOMETRY)) { timeoutId = setTimeout(function () { mapRef.current?.invalidateSize(); }, 100); } }); - contentResizeObserver.observe(contentRef.current); - }, []); + if(contentRef.current) { + contentResizeObserver.observe(contentRef.current); + } + return () => { + clearTimeout(timeoutId); + contentResizeObserver.disconnect(); + }; + }, [queryToolCtx]); - // Dyanmic CRS is not supported. Use srid as key and recreate the map on change + // Dyanmic CRS is not supported. Use srid and mapKey as key and recreate the map on change return ( - + { isDataChangedRef.current = Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted)); }, [dataChangeStore]); @@ -1460,30 +1463,61 @@ export function ResultSet() { return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, triggerAddRows); }, [columns, selectedRows.size]); + const getFilteredRowsForGeometryViewer = React.useCallback(() => { + let selRowsData = rows; + if(selectedRows.size != 0) { + selRowsData = rows.filter((r)=>selectedRows.has(rowKeyGetter(r))); + } else if(selectedColumns.size > 0) { + let selectedCols = _.filter(columns, (_c, i)=>selectedColumns.has(i+1)); + selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key))); + } else if(selectedRange.current) { + let [,, startRowIdx, endRowIdx] = getRangeIndexes(); + selRowsData = rows.slice(startRowIdx, endRowIdx+1); + } else if(selectedCell.current?.[0]) { + selRowsData = [selectedCell.current[0]]; + } + return selRowsData; + }, [rows, columns, selectedRows, selectedColumns]); + + const openGeometryViewerTab = React.useCallback((column, rowsData) => { + layoutDocker.openTab({ + id: PANELS.GEOMETRY, + title: gettext('Geometry Viewer'), + content: , + closable: true, + }, PANELS.MESSAGES, 'after-tab', true); + }, [layoutDocker, columns]); + + // Handle manual Geometry Viewer opening useEffect(()=>{ const renderGeometries = (column)=>{ - let selRowsData = rows; - if(selectedRows.size != 0) { - selRowsData = rows.filter((r)=>selectedRows.has(rowKeyGetter(r))); - } else if(selectedColumns.size > 0) { - let selectedCols = _.filter(columns, (_c, i)=>selectedColumns.has(i+1)); - selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key))); - } else if(selectedRange.current) { - let [,, startRowIdx, endRowIdx] = getRangeIndexes(); - selRowsData = rows.slice(startRowIdx, endRowIdx+1); - } else if(selectedCell.current?.[0]) { - selRowsData = [selectedCell.current[0]]; - } - layoutDocker.openTab({ - id: PANELS.GEOMETRY, - title:gettext('Geometry Viewer'), - content: , - closable: true, - }, PANELS.MESSAGES, 'after-tab', true); + const selRowsData = getFilteredRowsForGeometryViewer(); + openGeometryViewerTab(column, selRowsData); }; eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries); return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries); - }, [rows, columns, selectedRows.size, selectedColumns.size]); + }, [getFilteredRowsForGeometryViewer, openGeometryViewerTab, eventBus]); + + // Auto-update Geometry Viewer when rows/columns change + useEffect(()=>{ + const rowsChanged = prevRowsRef.current !== rows; + const columnsChanged = prevColumnsRef.current !== columns; + const currentGeometryColumn = columns.find(col => col.cell === 'geometry' || col.cell === 'geography'); + + if((rowsChanged || columnsChanged) && layoutDocker.isTabOpen(PANELS.GEOMETRY)) { + + if(currentGeometryColumn) { + const selRowsData = getFilteredRowsForGeometryViewer(); + openGeometryViewerTab(currentGeometryColumn, selRowsData); + } else { + // No geometry column + openGeometryViewerTab(null, []); + } + } + + prevRowsRef.current = rows; + prevColumnsRef.current = columns; + }, [rows, columns, getFilteredRowsForGeometryViewer, openGeometryViewerTab, layoutDocker]); const triggerResetScroll = () => { // Reset the scroll position to previously saved location. From 0431f0d44195054491a83fcdbe29538b534e8323 Mon Sep 17 00:00:00 2001 From: Anil Sahoo Date: Wed, 18 Feb 2026 18:47:04 +0530 Subject: [PATCH 2/8] Fixed coderabbit comments and removed the __temp_PK to identifier based of first column or rowdata --- .../js/components/sections/GeometryViewer.jsx | 80 +++++++++++++------ 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index 5aa12ee35e5..78301f49b73 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -6,7 +6,7 @@ // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// -import React, { useEffect, useRef } from 'react'; +import React, { useEffect, useRef, useMemo } from 'react'; import { styled } from '@mui/material/styles'; import ReactDOMServer from 'react-dom/server'; import _ from 'lodash'; @@ -49,6 +49,8 @@ const StyledBox = styled(Box)(({theme}) => ({ }, })); +const PK_COLUMN_NAMES = ['id', 'oid', 'ctid']; + function parseEwkbData(rows, column) { let key = column.key; const maxRenderByteLength = 20 * 1024 * 1024; //render geometry data up to 20MB @@ -191,6 +193,33 @@ function parseData(rows, columns, column) { }; } +// Find primary key column from columns array +function findPkColumn(columns) { + return columns.find(c => PK_COLUMN_NAMES.includes(c.name)); +} + +// Get unique row identifier using PK column or first column +function getRowIdentifier(row, pkColumn, columns) { + if (pkColumn?.key && row[pkColumn.key] !== undefined) { + return row[pkColumn.key]; + } + const firstKey = columns[0]?.key; + return firstKey && row[firstKey] !== undefined ? row[firstKey] : JSON.stringify(row); +} + +// Create Set of row identifiers +function createIdentifierSet(rowData, pkColumn, columns) { + return new Set(rowData.map(row => getRowIdentifier(row, pkColumn, columns))); +} + +// Match rows from previous selection to current rows +function matchRowSelection(prevRowData, currentRows, pkColumn, columns) { + if (prevRowData.length === 0) return []; + + const prevIdSet = createIdentifierSet(prevRowData, pkColumn, columns); + return currentRows.filter(row => prevIdSet.has(getRowIdentifier(row, pkColumn, columns))); +} + function PopupTable({data}) { return ( @@ -438,20 +467,22 @@ export function GeometryViewer({rows, columns, column}) { const contentRef = React.useRef(); const queryToolCtx = React.useContext(QueryToolContext); - // Track previous state to detect changes + // Track previous column state AND selected row data const prevStateRef = React.useRef({ columnKey: null, columnNames: null, - selectedRowPKs: [], + selectedRowData: [], }); const [mapKey, setMapKey] = React.useState(0); - const currentColumnKey = column?.key; + const currentColumnKey = useMemo(() => column?.key, [column]); const currentColumnNames = React.useMemo( () => columns.map(c => c.key).sort().join(','), [columns] ); + const pkColumn = useMemo(() => findPkColumn(columns), [columns]); + // Detect when to clear, filter, or re-render the map based on changes in geometry column, columns list, or rows useEffect(() => { const prevState = prevStateRef.current; @@ -461,7 +492,7 @@ export function GeometryViewer({rows, columns, column}) { prevStateRef.current = { columnKey: null, columnNames: null, - selectedRowPKs: [], + selectedRowData: [], }; return; } @@ -472,7 +503,7 @@ export function GeometryViewer({rows, columns, column}) { prevStateRef.current = { columnKey: currentColumnKey, columnNames: currentColumnNames, - selectedRowPKs: [], + selectedRowData: [], }; return; } @@ -480,30 +511,31 @@ export function GeometryViewer({rows, columns, column}) { if (currentColumnKey === prevState.columnKey && currentColumnNames === prevState.columnNames && rows.length > 0) { - - // If user previously selected specific rows, filter them from new data - 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); - - prevStateRef.current.selectedRowPKs = newSelectedPKs.length > 0 ? newSelectedPKs : rows.map(r => r.__temp_PK); + let newSelectedRowData; + if (prevState.selectedRowData.length === 0) { + // No previous selection, show all rows + newSelectedRowData = rows; + } else if (prevState.selectedRowData.length < rows.length) { + const matched = matchRowSelection(prevState.selectedRowData, rows, pkColumn, columns); + newSelectedRowData = matched.length > 0 ? matched : rows; } else { - // All rows are displayed - const allPKs = rows.map(r => r.__temp_PK); - prevStateRef.current.selectedRowPKs = allPKs; + newSelectedRowData = rows; } + prevStateRef.current.selectedRowData = newSelectedRowData; } - }, [currentColumnKey, currentColumnNames, rows]); + }, [currentColumnKey, currentColumnNames, rows, pkColumn, columns]); + // Get rows to display based on selection const displayRows = React.useMemo(() => { if (!currentColumnKey || rows.length === 0) return []; + const prevState = prevStateRef.current; + if (currentColumnKey !== prevState.columnKey || currentColumnNames !== prevState.columnNames) { + return rows; + } - const selectedPKs = prevStateRef.current.selectedRowPKs; - return selectedPKs.length > 0 && selectedPKs.length < rows.length - ? rows.filter(row => selectedPKs.includes(row.__temp_PK)) - : rows; - }, [rows, currentColumnKey]); + const selected = prevState.selectedRowData; + return selected.length > 0 && selected.length < rows.length ? selected : rows; + }, [rows, currentColumnKey, currentColumnNames]); // Parse geometry data only when needed const data = React.useMemo(() => { @@ -537,7 +569,7 @@ export function GeometryViewer({rows, columns, column}) { }; }, [queryToolCtx]); - // Dyanmic CRS is not supported. Use srid and mapKey as key and recreate the map on change + // Dynamic CRS is not supported. Use srid and mapKey as key and recreate the map on change return ( Date: Thu, 19 Feb 2026 10:10:16 +0530 Subject: [PATCH 3/8] Fixed some minor data assignment issue in react states --- .../static/js/components/sections/GeometryViewer.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index 78301f49b73..85e8d42314a 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -49,7 +49,7 @@ const StyledBox = styled(Box)(({theme}) => ({ }, })); -const PK_COLUMN_NAMES = ['id', 'oid', 'ctid']; +const PK_COLUMN_NAMES = ['id', 'oid']; function parseEwkbData(rows, column) { let key = column.key; @@ -503,7 +503,7 @@ export function GeometryViewer({rows, columns, column}) { prevStateRef.current = { columnKey: currentColumnKey, columnNames: currentColumnNames, - selectedRowData: [], + selectedRowData: rows, }; return; } From c2533eeb5d27e7ba518027c4471fd940838cb780 Mon Sep 17 00:00:00 2001 From: Anil Sahoo Date: Thu, 19 Feb 2026 11:55:27 +0530 Subject: [PATCH 4/8] Fixed more coderabbit review comments for some scenario it suggested --- .../js/components/sections/GeometryViewer.jsx | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index 85e8d42314a..a27923872de 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -511,17 +511,7 @@ export function GeometryViewer({rows, columns, column}) { if (currentColumnKey === prevState.columnKey && currentColumnNames === prevState.columnNames && rows.length > 0) { - let newSelectedRowData; - if (prevState.selectedRowData.length === 0) { - // No previous selection, show all rows - newSelectedRowData = rows; - } else if (prevState.selectedRowData.length < rows.length) { - const matched = matchRowSelection(prevState.selectedRowData, rows, pkColumn, columns); - newSelectedRowData = matched.length > 0 ? matched : rows; - } else { - newSelectedRowData = rows; - } - prevStateRef.current.selectedRowData = newSelectedRowData; + prevStateRef.current.selectedRowData = displayRows; } }, [currentColumnKey, currentColumnNames, rows, pkColumn, columns]); @@ -532,10 +522,15 @@ export function GeometryViewer({rows, columns, column}) { if (currentColumnKey !== prevState.columnKey || currentColumnNames !== prevState.columnNames) { return rows; } - - const selected = prevState.selectedRowData; - return selected.length > 0 && selected.length < rows.length ? selected : rows; - }, [rows, currentColumnKey, currentColumnNames]); + + const prevSelected = prevState.selectedRowData; + if (prevSelected.length === 0) return rows; + if (prevSelected.length < rows.length) { + const matched = matchRowSelection(prevSelected, rows, pkColumn, columns); + return matched.length > 0 ? matched : rows; + } + return rows; + }, [rows, currentColumnKey, currentColumnNames, pkColumn, columns]); // Parse geometry data only when needed const data = React.useMemo(() => { From 09ae8a3774cd4c755674a8dfc5526e3d486dc383 Mon Sep 17 00:00:00 2001 From: Anil Sahoo Date: Fri, 20 Feb 2026 14:03:30 +0530 Subject: [PATCH 5/8] Fixed a scenario where different query with different column execution will return blank geometry viewer --- .../js/components/sections/GeometryViewer.jsx | 26 +++++++++++- .../js/components/sections/ResultSet.jsx | 40 ++++++++++++++++--- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index a27923872de..9f67d72455b 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -323,6 +323,27 @@ function TheMap({data}) { infoControl.current.onAdd = function () { let ele = Leaflet.DomUtil.create('div', 'geometry-viewer-info-control'); ele.innerHTML = data.infoList.join('
'); + // Style the parent control container after it's added to the map + setTimeout(() => { + let controlContainer = ele.closest('.leaflet-control'); + if(controlContainer) { + controlContainer.style.cssText = ` + position: fixed; + top: 70%; + left: 50%; + transform: translate(-50%, -50%); + margin: 0; + max-width: 80%; + text-align: center; + white-space: normal; + word-wrap: break-word; + background: none; + box-shadow: none; + border: none; + font-size: 16px; + `; + } + }, 0); return ele; }; if(data.infoList.length > 0) { @@ -535,11 +556,14 @@ export function GeometryViewer({rows, columns, column}) { // Parse geometry data only when needed const data = React.useMemo(() => { if (!currentColumnKey) { + const hasGeometryColumn = columns.some(c => c.cell === 'geometry' || c.cell === 'geography'); return { 'geoJSONs': [], 'selectedSRID': 0, 'getPopupContent': undefined, - 'infoList': [gettext('Select a geometry/geography column to visualize.')], + 'infoList': hasGeometryColumn + ? [gettext('Query complete. Use the Geometry Viewer button in the Data Output tab to visualize results.')] + : [gettext('No spatial data found. At least one geometry or geography column is required for visualization.')], }; } return parseData(displayRows, columns, column); diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx index 752a5425017..dba048f2846 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx @@ -878,6 +878,11 @@ export function ResultSet() { const isDataChangedRef = useRef(false); const prevRowsRef = React.useRef(null); const prevColumnsRef = React.useRef(null); + const gvClearedForColumnsRef = useRef(null); + const lastGvSelectionRef = useRef({ + type: 'all', // 'all' | 'rows' | 'columns' + selectedColumns: new Set(), + }); useEffect(()=>{ isDataChangedRef.current = Boolean(_.size(dataChangeStore.updated) || _.size(dataChangeStore.added) || _.size(dataChangeStore.deleted)); @@ -1463,13 +1468,19 @@ export function ResultSet() { return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, triggerAddRows); }, [columns, selectedRows.size]); - const getFilteredRowsForGeometryViewer = React.useCallback(() => { + const getFilteredRowsForGeometryViewer = React.useCallback((useLastGvSelection = false) => { let selRowsData = rows; if(selectedRows.size != 0) { selRowsData = rows.filter((r)=>selectedRows.has(rowKeyGetter(r))); } else if(selectedColumns.size > 0) { let selectedCols = _.filter(columns, (_c, i)=>selectedColumns.has(i+1)); selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key))); + } else if(useLastGvSelection && lastGvSelectionRef.current.type === 'columns' + && lastGvSelectionRef.current.selectedColumns.size > 0) { + let selectedCols = _.filter(columns, (_c, i)=>lastGvSelectionRef.current.selectedColumns.has(i+1)); + if(selectedCols.length > 0) { + selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key))); + } } else if(selectedRange.current) { let [,, startRowIdx, endRowIdx] = getRangeIndexes(); selRowsData = rows.slice(startRowIdx, endRowIdx+1); @@ -1491,12 +1502,20 @@ export function ResultSet() { // Handle manual Geometry Viewer opening useEffect(()=>{ const renderGeometries = (column)=>{ + gvClearedForColumnsRef.current = null; + if(selectedRows.size > 0) { + lastGvSelectionRef.current = { type: 'rows', selectedColumns: new Set() }; + } else if(selectedColumns.size > 0) { + lastGvSelectionRef.current = { type: 'columns', selectedColumns: new Set(selectedColumns) }; + } else { + lastGvSelectionRef.current = { type: 'all', selectedColumns: new Set() }; + } const selRowsData = getFilteredRowsForGeometryViewer(); openGeometryViewerTab(column, selRowsData); }; eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries); return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries); - }, [getFilteredRowsForGeometryViewer, openGeometryViewerTab, eventBus]); + }, [getFilteredRowsForGeometryViewer, openGeometryViewerTab, eventBus, selectedRows, selectedColumns]); // Auto-update Geometry Viewer when rows/columns change useEffect(()=>{ @@ -1506,8 +1525,19 @@ export function ResultSet() { if((rowsChanged || columnsChanged) && layoutDocker.isTabOpen(PANELS.GEOMETRY)) { - if(currentGeometryColumn) { - const selRowsData = getFilteredRowsForGeometryViewer(); + const prevColumnNames = prevColumnsRef.current?.map(c => c.key).sort().join(',') ?? ''; + const currColumnNames = columns.map(c => c.key).sort().join(','); + const columnsChanged = prevColumnNames !== currColumnNames; + + if(columnsChanged && currentGeometryColumn) { + gvClearedForColumnsRef.current = currColumnNames; + lastGvSelectionRef.current = { type: 'all', selectedColumns: new Set() }; + openGeometryViewerTab(null, []); + } else if(gvClearedForColumnsRef.current === currColumnNames) { + openGeometryViewerTab(null, []); + } else if(currentGeometryColumn && rowsChanged) { + const useColSelection = lastGvSelectionRef.current.type === 'columns'; + const selRowsData = getFilteredRowsForGeometryViewer(useColSelection); openGeometryViewerTab(currentGeometryColumn, selRowsData); } else { // No geometry column @@ -1517,7 +1547,7 @@ export function ResultSet() { prevRowsRef.current = rows; prevColumnsRef.current = columns; - }, [rows, columns, getFilteredRowsForGeometryViewer, openGeometryViewerTab, layoutDocker]); + }, [rows, columns, getFilteredRowsForGeometryViewer, layoutDocker]); const triggerResetScroll = () => { // Reset the scroll position to previously saved location. From 1531ff6b03bf32e6b3830f50ca8de5bf4bef6008 Mon Sep 17 00:00:00 2001 From: Anil Sahoo Date: Tue, 24 Feb 2026 11:17:48 +0530 Subject: [PATCH 6/8] Fixed review comments 1. Used hash function in place of JSON.stringify(row) 2. Storing row identifiers in place of complete row object. 3. Removed Leaflet's infoControl and added a react div --- .../js/components/sections/GeometryViewer.jsx | 113 ++++++++++-------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index 9f67d72455b..506a95c670b 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -22,6 +22,7 @@ import { PANELS } from '../QueryToolConstants'; import { QueryToolContext } from '../QueryToolComponent'; const StyledBox = styled(Box)(({theme}) => ({ + position: 'relative', '& .GeometryViewer-mapContainer': { backgroundColor: theme.palette.background.default, height: '100%', @@ -193,31 +194,40 @@ function parseData(rows, columns, column) { }; } -// Find primary key column from columns array +// Find primary key column i.e a column with unique values from columns array in Data Output tab function findPkColumn(columns) { return columns.find(c => PK_COLUMN_NAMES.includes(c.name)); } +// Hash function for row objects +function hashRow(row) { + const str = Object.keys(row).sort().map(k => `${k}:${row[k]}`).join('|'); + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; + } + return `hash_${hash}`; +} + // Get unique row identifier using PK column or first column function getRowIdentifier(row, pkColumn, columns) { if (pkColumn?.key && row[pkColumn.key] !== undefined) { return row[pkColumn.key]; } const firstKey = columns[0]?.key; - return firstKey && row[firstKey] !== undefined ? row[firstKey] : JSON.stringify(row); -} - -// Create Set of row identifiers -function createIdentifierSet(rowData, pkColumn, columns) { - return new Set(rowData.map(row => getRowIdentifier(row, pkColumn, columns))); + if (firstKey && row[firstKey] !== undefined) { + return row[firstKey]; + } + return hashRow(row); } // Match rows from previous selection to current rows -function matchRowSelection(prevRowData, currentRows, pkColumn, columns) { - if (prevRowData.length === 0) return []; +function matchRowSelection(prevIdentifiers, currentRows, pkColumn, columns) { + if (prevIdentifiers.size === 0) return []; - const prevIdSet = createIdentifierSet(prevRowData, pkColumn, columns); - return currentRows.filter(row => prevIdSet.has(getRowIdentifier(row, pkColumn, columns))); + return currentRows.filter(row => prevIdentifiers.has(getRowIdentifier(row, pkColumn, columns))); } function PopupTable({data}) { @@ -314,41 +324,10 @@ GeoJsonLayer.propTypes = { function TheMap({data}) { const mapObj = useMap(); - const infoControl = useRef(null); const resetLayersKey = useRef(0); const zoomControlWithHome = useRef(null); const homeCoordinates = useRef(null); useEffect(()=>{ - infoControl.current = Leaflet.control({position: 'topright'}); - infoControl.current.onAdd = function () { - let ele = Leaflet.DomUtil.create('div', 'geometry-viewer-info-control'); - ele.innerHTML = data.infoList.join('
'); - // Style the parent control container after it's added to the map - setTimeout(() => { - let controlContainer = ele.closest('.leaflet-control'); - if(controlContainer) { - controlContainer.style.cssText = ` - position: fixed; - top: 70%; - left: 50%; - transform: translate(-50%, -50%); - margin: 0; - max-width: 80%; - text-align: center; - white-space: normal; - word-wrap: break-word; - background: none; - box-shadow: none; - border: none; - font-size: 16px; - `; - } - }, 0); - return ele; - }; - if(data.infoList.length > 0) { - infoControl.current.addTo(mapObj); - } resetLayersKey.current++; zoomControlWithHome.current = Leaflet.control.zoom({ @@ -398,7 +377,6 @@ function TheMap({data}) { zoomControlWithHome.current.addTo(mapObj); return ()=>{ - infoControl.current?.remove(); zoomControlWithHome.current?.remove(); }; }, [data]); @@ -409,6 +387,25 @@ function TheMap({data}) { return ( <> + {data.infoList.length > 0 && ( +
+ {data.infoList.map((info, idx) => ( +
{info}
+ ))} +
+ )} {data.selectedSRID === 4326 && @@ -492,7 +489,7 @@ export function GeometryViewer({rows, columns, column}) { const prevStateRef = React.useRef({ columnKey: null, columnNames: null, - selectedRowData: [], + selectedRowIdentifiers: new Set(), }); const [mapKey, setMapKey] = React.useState(0); @@ -513,7 +510,7 @@ export function GeometryViewer({rows, columns, column}) { prevStateRef.current = { columnKey: null, columnNames: null, - selectedRowData: [], + selectedRowIdentifiers: new Set(), }; return; } @@ -524,7 +521,7 @@ export function GeometryViewer({rows, columns, column}) { prevStateRef.current = { columnKey: currentColumnKey, columnNames: currentColumnNames, - selectedRowData: rows, + selectedRowIdentifiers: new Set(rows.map(r => getRowIdentifier(r, pkColumn, columns))), }; return; } @@ -532,24 +529,38 @@ export function GeometryViewer({rows, columns, column}) { if (currentColumnKey === prevState.columnKey && currentColumnNames === prevState.columnNames && rows.length > 0) { - prevStateRef.current.selectedRowData = displayRows; + prevStateRef.current.selectedRowIdentifiers = new Set( + displayRows.map(r => getRowIdentifier(r, pkColumn, columns)) + ); } }, [currentColumnKey, currentColumnNames, rows, pkColumn, columns]); // Get rows to display based on selection const displayRows = React.useMemo(() => { + // No geometry column selected or no rows available - nothing to display if (!currentColumnKey || rows.length === 0) return []; const prevState = prevStateRef.current; + + // Column context changed (different geometry column or different query schema) + // Show all new rows since previous selection is no longer valid if (currentColumnKey !== prevState.columnKey || currentColumnNames !== prevState.columnNames) { return rows; } - const prevSelected = prevState.selectedRowData; - if (prevSelected.length === 0) return rows; - if (prevSelected.length < rows.length) { - const matched = matchRowSelection(prevSelected, rows, pkColumn, columns); + const prevIdentifiers = prevState.selectedRowIdentifiers; + // No previous selection recorded - show all rows + if (prevIdentifiers.size === 0) return rows; + + // Previous selection was a subset of total rows, meaning user had specific rows selected. + // Try to match those previously selected rows in the new result set using stable + // row identifiers (PK value, first column value, or hash fallback). + // This handles the case where same query reruns with more/fewer rows + if (prevIdentifiers.size < rows.length) { + const matched = matchRowSelection(prevIdentifiers, rows, pkColumn, columns); + // If matched rows found, show only those; otherwise fall back to all rows return matched.length > 0 ? matched : rows; } + // Previous selection covered all rows (or same count) - show all current rows return rows; }, [rows, currentColumnKey, currentColumnNames, pkColumn, columns]); From ec08b6a94ae5ccc29b8792cb6662334ab8f4ce33 Mon Sep 17 00:00:00 2001 From: Anil Sahoo Date: Thu, 5 Mar 2026 23:31:23 +0530 Subject: [PATCH 7/8] Fixed the review comments --- .../js/components/sections/GeometryViewer.jsx | 123 +--------------- .../js/components/sections/ResultSet.jsx | 135 ++++++++++-------- 2 files changed, 79 insertions(+), 179 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index 506a95c670b..1717b1d735f 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -50,8 +50,6 @@ const StyledBox = styled(Box)(({theme}) => ({ }, })); -const PK_COLUMN_NAMES = ['id', 'oid']; - function parseEwkbData(rows, column) { let key = column.key; const maxRenderByteLength = 20 * 1024 * 1024; //render geometry data up to 20MB @@ -194,41 +192,6 @@ function parseData(rows, columns, column) { }; } -// Find primary key column i.e a column with unique values from columns array in Data Output tab -function findPkColumn(columns) { - return columns.find(c => PK_COLUMN_NAMES.includes(c.name)); -} - -// Hash function for row objects -function hashRow(row) { - const str = Object.keys(row).sort().map(k => `${k}:${row[k]}`).join('|'); - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; - } - return `hash_${hash}`; -} - -// Get unique row identifier using PK column or first column -function getRowIdentifier(row, pkColumn, columns) { - if (pkColumn?.key && row[pkColumn.key] !== undefined) { - return row[pkColumn.key]; - } - const firstKey = columns[0]?.key; - if (firstKey && row[firstKey] !== undefined) { - return row[firstKey]; - } - return hashRow(row); -} - -// Match rows from previous selection to current rows -function matchRowSelection(prevIdentifiers, currentRows, pkColumn, columns) { - if (prevIdentifiers.size === 0) return []; - - return currentRows.filter(row => prevIdentifiers.has(getRowIdentifier(row, pkColumn, columns))); -} function PopupTable({data}) { @@ -485,86 +448,8 @@ export function GeometryViewer({rows, columns, column}) { const contentRef = React.useRef(); const queryToolCtx = React.useContext(QueryToolContext); - // Track previous column state AND selected row data - const prevStateRef = React.useRef({ - columnKey: null, - columnNames: null, - selectedRowIdentifiers: new Set(), - }); - - const [mapKey, setMapKey] = React.useState(0); const currentColumnKey = useMemo(() => column?.key, [column]); - const currentColumnNames = React.useMemo( - () => columns.map(c => c.key).sort().join(','), - [columns] - ); - - const pkColumn = useMemo(() => findPkColumn(columns), [columns]); - - // Detect when to clear, filter, or re-render the map based on changes in geometry column, columns list, or rows - useEffect(() => { - const prevState = prevStateRef.current; - - if (!currentColumnKey) { - setMapKey(prev => prev + 1); - prevStateRef.current = { - columnKey: null, - columnNames: null, - selectedRowIdentifiers: new Set(), - }; - return; - } - - if (currentColumnKey !== prevState.columnKey || - currentColumnNames !== prevState.columnNames) { - setMapKey(prev => prev + 1); - prevStateRef.current = { - columnKey: currentColumnKey, - columnNames: currentColumnNames, - selectedRowIdentifiers: new Set(rows.map(r => getRowIdentifier(r, pkColumn, columns))), - }; - return; - } - - if (currentColumnKey === prevState.columnKey && - currentColumnNames === prevState.columnNames && - rows.length > 0) { - prevStateRef.current.selectedRowIdentifiers = new Set( - displayRows.map(r => getRowIdentifier(r, pkColumn, columns)) - ); - } - }, [currentColumnKey, currentColumnNames, rows, pkColumn, columns]); - - // Get rows to display based on selection - const displayRows = React.useMemo(() => { - // No geometry column selected or no rows available - nothing to display - if (!currentColumnKey || rows.length === 0) return []; - const prevState = prevStateRef.current; - - // Column context changed (different geometry column or different query schema) - // Show all new rows since previous selection is no longer valid - if (currentColumnKey !== prevState.columnKey || currentColumnNames !== prevState.columnNames) { - return rows; - } - - const prevIdentifiers = prevState.selectedRowIdentifiers; - // No previous selection recorded - show all rows - if (prevIdentifiers.size === 0) return rows; - - // Previous selection was a subset of total rows, meaning user had specific rows selected. - // Try to match those previously selected rows in the new result set using stable - // row identifiers (PK value, first column value, or hash fallback). - // This handles the case where same query reruns with more/fewer rows - if (prevIdentifiers.size < rows.length) { - const matched = matchRowSelection(prevIdentifiers, rows, pkColumn, columns); - // If matched rows found, show only those; otherwise fall back to all rows - return matched.length > 0 ? matched : rows; - } - // Previous selection covered all rows (or same count) - show all current rows - return rows; - }, [rows, currentColumnKey, currentColumnNames, pkColumn, columns]); - // Parse geometry data only when needed const data = React.useMemo(() => { if (!currentColumnKey) { const hasGeometryColumn = columns.some(c => c.cell === 'geometry' || c.cell === 'geography'); @@ -577,8 +462,8 @@ export function GeometryViewer({rows, columns, column}) { : [gettext('No spatial data found. At least one geometry or geography column is required for visualization.')], }; } - return parseData(displayRows, columns, column); - }, [displayRows, columns, column, currentColumnKey]); + return parseData(rows, columns, column); + }, [rows, columns, column, currentColumnKey]); useEffect(()=>{ let timeoutId; @@ -599,9 +484,9 @@ export function GeometryViewer({rows, columns, column}) { }; }, [queryToolCtx]); - // Dynamic CRS is not supported. Use srid and mapKey as key and recreate the map on change + // Dynamic CRS is not supported. Use srid and column key as key and recreate the map on change return ( - + { @@ -1468,86 +1470,99 @@ export function ResultSet() { return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_ADD_ROWS, triggerAddRows); }, [columns, selectedRows.size]); - const getFilteredRowsForGeometryViewer = React.useCallback((useLastGvSelection = false) => { - let selRowsData = rows; - if(selectedRows.size != 0) { - selRowsData = rows.filter((r)=>selectedRows.has(rowKeyGetter(r))); - } else if(selectedColumns.size > 0) { - let selectedCols = _.filter(columns, (_c, i)=>selectedColumns.has(i+1)); - selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key))); - } else if(useLastGvSelection && lastGvSelectionRef.current.type === 'columns' - && lastGvSelectionRef.current.selectedColumns.size > 0) { - let selectedCols = _.filter(columns, (_c, i)=>lastGvSelectionRef.current.selectedColumns.has(i+1)); - if(selectedCols.length > 0) { - selRowsData = _.map(rows, (r)=>_.pick(r, _.map(selectedCols, (c)=>c.key))); - } - } else if(selectedRange.current) { - let [,, startRowIdx, endRowIdx] = getRangeIndexes(); - selRowsData = rows.slice(startRowIdx, endRowIdx+1); - } else if(selectedCell.current?.[0]) { - selRowsData = [selectedCell.current[0]]; - } - return selRowsData; - }, [rows, columns, selectedRows, selectedColumns]); - const openGeometryViewerTab = React.useCallback((column, rowsData) => { layoutDocker.openTab({ id: PANELS.GEOMETRY, title: gettext('Geometry Viewer'), - content: , + content: , closable: true, }, PANELS.MESSAGES, 'after-tab', true); }, [layoutDocker, columns]); - // Handle manual Geometry Viewer opening + // Handle manual Geometry Viewer opening. + // Determines which rows to plot based on the current grid selection (rows, columns, + // range, or cell) and stores the selection indices in lastGvSelectionRef so the + // auto-update effect can re-apply the same selection on subsequent query re-runs. useEffect(()=>{ const renderGeometries = (column)=>{ - gvClearedForColumnsRef.current = null; + const defaultSel = { geometryColumnKey: column?.key, rowIndices: [], columnIndices: new Set(), rangeStartIdx: null, rangeEndIdx: null, cellIdx: null }; + let selRowsData = rows; + if(selectedRows.size > 0) { - lastGvSelectionRef.current = { type: 'rows', selectedColumns: new Set() }; + // Specific rows selected in the grid — plot only those rows + const rowIndices = []; + rows.forEach((r, i) => { + if(selectedRows.has(rowKeyGetter(r))) { + rowIndices.push(i); + } + }); + selRowsData = rowIndices.map(i => rows[i]); + lastGvSelectionRef.current = { ...defaultSel, type: 'rows', rowIndices }; } else if(selectedColumns.size > 0) { - lastGvSelectionRef.current = { type: 'columns', selectedColumns: new Set(selectedColumns) }; + // Specific columns selected — plot all rows but only with selected column data + let selectedCols = _.filter(columns, (_c, i) => selectedColumns.has(i + 1)); + selRowsData = _.map(rows, (r) => _.pick(r, _.map(selectedCols, (c) => c.key))); + lastGvSelectionRef.current = { ...defaultSel, type: 'columns', columnIndices: new Set(selectedColumns) }; + } else if(selectedRange.current) { + // Cell range selected — plot the rows within the range + let [,, startRowIdx, endRowIdx] = getRangeIndexes(); + selRowsData = rows.slice(startRowIdx, endRowIdx + 1); + lastGvSelectionRef.current = { ...defaultSel, type: 'range', rangeStartIdx: startRowIdx, rangeEndIdx: endRowIdx }; + } else if(selectedCell.current?.[0]) { + // Single cell selected — plot only that row + const cellIdx = rows.indexOf(selectedCell.current[0]); + selRowsData = [selectedCell.current[0]]; + lastGvSelectionRef.current = { ...defaultSel, type: 'cell', cellIdx: cellIdx >= 0 ? cellIdx : null }; } else { - lastGvSelectionRef.current = { type: 'all', selectedColumns: new Set() }; + // No selection — plot all rows + lastGvSelectionRef.current = { ...defaultSel, type: 'all' }; } - const selRowsData = getFilteredRowsForGeometryViewer(); + openGeometryViewerTab(column, selRowsData); }; eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries); return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, renderGeometries); - }, [getFilteredRowsForGeometryViewer, openGeometryViewerTab, eventBus, selectedRows, selectedColumns]); + }, [openGeometryViewerTab, eventBus, rows, columns, selectedRows, selectedColumns]); // Auto-update Geometry Viewer when rows/columns change useEffect(()=>{ - const rowsChanged = prevRowsRef.current !== rows; - const columnsChanged = prevColumnsRef.current !== columns; - const currentGeometryColumn = columns.find(col => col.cell === 'geometry' || col.cell === 'geography'); - - if((rowsChanged || columnsChanged) && layoutDocker.isTabOpen(PANELS.GEOMETRY)) { - - const prevColumnNames = prevColumnsRef.current?.map(c => c.key).sort().join(',') ?? ''; - const currColumnNames = columns.map(c => c.key).sort().join(','); - const columnsChanged = prevColumnNames !== currColumnNames; - - if(columnsChanged && currentGeometryColumn) { - gvClearedForColumnsRef.current = currColumnNames; - lastGvSelectionRef.current = { type: 'all', selectedColumns: new Set() }; - openGeometryViewerTab(null, []); - } else if(gvClearedForColumnsRef.current === currColumnNames) { - openGeometryViewerTab(null, []); - } else if(currentGeometryColumn && rowsChanged) { - const useColSelection = lastGvSelectionRef.current.type === 'columns'; - const selRowsData = getFilteredRowsForGeometryViewer(useColSelection); - openGeometryViewerTab(currentGeometryColumn, selRowsData); + if(layoutDocker.isTabOpen(PANELS.GEOMETRY)) { + const lastGeomKey = lastGvSelectionRef.current.geometryColumnKey; + const matchedGeomCol = lastGeomKey + ? columns.find(c => c.key === lastGeomKey && (c.cell === 'geometry' || c.cell === 'geography')) + : null; + + if(matchedGeomCol) { + // Previously plotted geometry column still exists → re-apply selection and re-render + const lastSel = lastGvSelectionRef.current; + let selRowsData = rows; + + if(lastSel.type === 'rows' && lastSel.rowIndices.length > 0) { + if(lastSel.rowIndices.every(idx => idx < rows.length)) { + selRowsData = lastSel.rowIndices.map(idx => rows[idx]); + } + } else if(lastSel.type === 'columns' && lastSel.columnIndices.size > 0) { + let selectedCols = _.filter(columns, (_c, i) => lastSel.columnIndices.has(i + 1)); + if(selectedCols.length > 0) { + selRowsData = _.map(rows, (r) => _.pick(r, _.map(selectedCols, (c) => c.key))); + } + } else if(lastSel.type === 'range' && lastSel.rangeStartIdx != null) { + if(lastSel.rangeStartIdx < rows.length && lastSel.rangeEndIdx < rows.length) { + selRowsData = rows.slice(lastSel.rangeStartIdx, lastSel.rangeEndIdx + 1); + } + } else if(lastSel.type === 'cell' && lastSel.cellIdx != null) { + if(lastSel.cellIdx < rows.length) { + selRowsData = [rows[lastSel.cellIdx]]; + } + } + + openGeometryViewerTab(matchedGeomCol, selRowsData); } else { - // No geometry column + // Previously plotted geometry column not found → clear GV openGeometryViewerTab(null, []); } } - - prevRowsRef.current = rows; - prevColumnsRef.current = columns; - }, [rows, columns, getFilteredRowsForGeometryViewer, layoutDocker]); + }, [rows, columns, layoutDocker]); const triggerResetScroll = () => { // Reset the scroll position to previously saved location. From 2928de2f1456a4669bca3f6987e8cde849b8c15f Mon Sep 17 00:00:00 2001 From: Anil Sahoo Date: Fri, 6 Mar 2026 13:31:28 +0530 Subject: [PATCH 8/8] Used EmptyPanelMessage component and added proper comments for all the if/else if statements --- .../js/components/sections/GeometryViewer.jsx | 21 +++++++------------ .../js/components/sections/ResultSet.jsx | 8 +++++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx index 1717b1d735f..6476aaa47a4 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/GeometryViewer.jsx @@ -18,6 +18,7 @@ import gettext from 'sources/gettext'; import Theme from 'sources/Theme'; import PropTypes from 'prop-types'; import { Box } from '@mui/material'; +import EmptyPanelMessage from '../../../../../../static/js/components/EmptyPanelMessage'; import { PANELS } from '../QueryToolConstants'; import { QueryToolContext } from '../QueryToolComponent'; @@ -351,23 +352,15 @@ function TheMap({data}) { return ( <> {data.infoList.length > 0 && ( -
- {data.infoList.map((info, idx) => ( -
{info}
- ))} -
+ }} /> )} {data.selectedSRID === 4326 && diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx index 113f2eeecf5..a349eccd584 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx @@ -1537,28 +1537,32 @@ export function ResultSet() { const lastSel = lastGvSelectionRef.current; let selRowsData = rows; + // Re-apply row selection — plot only previously selected rows if indices are still valid if(lastSel.type === 'rows' && lastSel.rowIndices.length > 0) { if(lastSel.rowIndices.every(idx => idx < rows.length)) { selRowsData = lastSel.rowIndices.map(idx => rows[idx]); } + // Re-apply column selection — filter each row to only the previously selected columns } else if(lastSel.type === 'columns' && lastSel.columnIndices.size > 0) { let selectedCols = _.filter(columns, (_c, i) => lastSel.columnIndices.has(i + 1)); if(selectedCols.length > 0) { selRowsData = _.map(rows, (r) => _.pick(r, _.map(selectedCols, (c) => c.key))); } + // Re-apply range selection — plot the previously selected row range if bounds are still valid } else if(lastSel.type === 'range' && lastSel.rangeStartIdx != null) { if(lastSel.rangeStartIdx < rows.length && lastSel.rangeEndIdx < rows.length) { selRowsData = rows.slice(lastSel.rangeStartIdx, lastSel.rangeEndIdx + 1); } + // Re-apply single cell selection — plot the row of the previously selected cell if still valid } else if(lastSel.type === 'cell' && lastSel.cellIdx != null) { if(lastSel.cellIdx < rows.length) { selRowsData = [rows[lastSel.cellIdx]]; } } - + // If any validation fails above, selRowsData remains as all rows (default) openGeometryViewerTab(matchedGeomCol, selRowsData); } else { - // Previously plotted geometry column not found → clear GV + // Previously plotted geometry column not found - clear GV openGeometryViewerTab(null, []); } }