Skip to content

Conversation

@adityatoshniwal
Copy link
Contributor

@adityatoshniwal adityatoshniwal commented Jan 8, 2026

Summary by CodeRabbit

  • Documentation

    • Added "Show Entire Range" row to Query Tool Toolbar pagination docs.
  • Bug Fixes / Improvements

    • Improved data synchronization and saving flows for more reliable unsaved-changes detection, prompting, and refresh behavior.
    • Updated modal cancellation flow so dialogs close correctly when users cancel.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a stable hook useLatestFunc, refactors modal cancel flow to use modal onClose, and replaces pageDataDirty with pageDataOutOfSync in ResultSet with related save/fetch/unsaved-changes handling; docs add a pagination option and a non-functional comment wrap in server_manager.

Changes

Cohort / File(s) Summary
Custom Hook
web/pgadmin/static/js/custom_hooks.js
New exported useLatestFunc(fn) that keeps the latest function in a ref and returns a stable callback which invokes the current function.
Modal Cancellation Flow
web/pgadmin/helpers/ModalProvider.jsx
Removed onCancelClickClose wrapper; pass modal onClose (set to onCancelClick) and use closeModal for cancellation path instead of directly invoking onCancelClick.
SQL Editor Data Sync
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
Replaced pageDataDirty with pageDataOutOfSync; introduced isDataChangedRef; integrated useLatestFunc for triggerSaveData; added post-fetch resetSelectionAndChanges, updated fetch/save flows, unsaved-changes prompt, and listener lifecycle (EXECUTION_START/END) handling.
Documentation
docs/en_US/query_tool_toolbar.rst
Added "Show Entire Range" row to Pagination Options table.
Backend Comment
web/pgadmin/utils/driver/psycopg3/server_manager.py
Wrapped explanatory comment for boolean connection param conversion; no behavioral change.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.74% 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 directly describes the main objective of the PR: warning users about unsaved data edits and allowing them to save before navigation, which is clearly reflected in the ResultSet.jsx changes implementing unsaved-changes handling and prompt logic.

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

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4a001e3 and 7469ced.

⛔ Files ignored due to path filters (1)
  • docs/en_US/images/query_data_pagination.png is excluded by !**/*.png
📒 Files selected for processing (5)
  • docs/en_US/query_tool_toolbar.rst
  • web/pgadmin/static/js/custom_hooks.js
  • web/pgadmin/static/js/helpers/ModalProvider.jsx
  • web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
  • web/pgadmin/utils/driver/psycopg3/server_manager.py

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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

🤖 Fix all issues with AI agents
In @web/pgadmin/tools/sqleditor/utils/start_running_query.py:
- Around line 165-174: The code captures an unused return value from
conn.execute_async into status, catches a broad Exception, and logs without
traceback; change the assignment to discard the unused value (use _ =
conn.execute_async(...) or drop assignment), narrow the except to specific
DB/connection exceptions (e.g., psycopg.Error, ConnectionLost or your project’s
DBError types) so unexpected exceptions propagate, and replace
self.logger.error(...) with self.logger.exception(...) to include tracebacks;
keep the existing rollback logic using is_rollback_req and still return
internal_server_error(errormsg=str(e)) for handled DB/connection errors.
🧹 Nitpick comments (3)
web/pgadmin/utils/driver/psycopg3/connection.py (1)

1089-1092: Refine exception handling in async cursor cleanup.

Catching bare Exception (line 1091) is overly broad and could mask unexpected errors during cursor cleanup. Consider catching specific psycopg exceptions or asyncio-related errors, and using proper logging instead of print statements.

♻️ Proposed fix
     def release_async_cursor(self):
         if self.__async_cursor and not self.__async_cursor.closed:
             try:
                 run_coroutine_threadsafe(self.__async_cursor.close_cursor(),
                                          self._loop).result()
-            except Exception as e:
-                print("Exception==", str(e))
+            except (psycopg.Error, asyncio.CancelledError) as e:
+                current_app.logger.warning(
+                    f"Failed to close async cursor for connection {self.conn_id}: {e}"
+                )
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (2)

1133-1134: Remove commented-out code.

Line 1134 contains dead code that should be removed to keep the codebase clean.

🧹 Remove dead code
          eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, rsu.current.query, {refreshData: true});
-          // executionStartCallback(rsu.current.query, {refreshData: true});
        } else {

1307-1310: Consider simplifying dependencies now that triggerSaveData is stable.

Since triggerSaveData is wrapped with useLatestFunc, it maintains a stable reference while always invoking the latest function. The dependencies [dataChangeStore, rows, columns] are now unnecessary and cause redundant listener re-registration on each change.

♻️ Simplify effect dependencies
 useEffect(()=>{
   eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_DATA, triggerSaveData);
   return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_DATA, triggerSaveData);
-}, [dataChangeStore, rows, columns]);
+}, [triggerSaveData]);

Or simply [] since triggerSaveData reference is guaranteed stable.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 62e2d18 and 5a99c80.

📒 Files selected for processing (7)
  • web/pgadmin/static/js/custom_hooks.js
  • web/pgadmin/static/js/helpers/ModalProvider.jsx
  • web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js
  • web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
  • web/pgadmin/tools/sqleditor/utils/start_running_query.py
  • web/pgadmin/utils/driver/psycopg3/connection.py
  • web/pgadmin/utils/driver/psycopg3/cursor.py
🧰 Additional context used
🧬 Code graph analysis (4)
web/pgadmin/utils/driver/psycopg3/cursor.py (1)
web/pgadmin/utils/driver/psycopg3/connection.py (1)
  • _execute (509-516)
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (2)
web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js (4)
  • QUERY_TOOL_EVENTS (12-87)
  • QUERY_TOOL_EVENTS (12-87)
  • CONNECTION_STATUS (89-95)
  • CONNECTION_STATUS (89-95)
web/pgadmin/static/js/custom_hooks.js (1)
  • useLatestFunc (290-295)
web/pgadmin/utils/driver/psycopg3/connection.py (2)
web/pgadmin/utils/driver/psycopg3/typecast.py (1)
  • register_global_typecasters (131-158)
web/pgadmin/utils/driver/psycopg3/cursor.py (2)
  • close_cursor (210-214)
  • close_cursor (305-309)
web/pgadmin/static/js/helpers/ModalProvider.jsx (3)
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (1)
  • modalId (847-847)
web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx (1)
  • modalId (69-69)
web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx (1)
  • modalId (59-59)
🪛 Ruff (0.14.10)
web/pgadmin/tools/sqleditor/utils/start_running_query.py

165-165: Unpacked variable status is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


172-172: Do not catch blind exception: Exception

(BLE001)


173-173: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

web/pgadmin/utils/driver/psycopg3/connection.py

1091-1091: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: run-python-tests-pg (windows-latest, 18)
  • GitHub Check: run-python-tests-pg (windows-latest, 15)
  • GitHub Check: run-python-tests-pg (windows-latest, 13)
  • GitHub Check: run-python-tests-pg (macos-latest, 18)
  • GitHub Check: run-python-tests-pg (macos-latest, 16)
  • GitHub Check: run-feature-tests-pg (16)
  • GitHub Check: run-feature-tests-pg (17)
  • GitHub Check: run-feature-tests-pg (18)
  • GitHub Check: run-feature-tests-pg (15)
  • GitHub Check: run-feature-tests-pg (14)
  • GitHub Check: run-feature-tests-pg (13)
🔇 Additional comments (13)
web/pgadmin/static/js/helpers/ModalProvider.jsx (1)

86-97: LGTM! Modal callback wiring refactored for consistency.

The refactoring centralizes modal close handling by:

  • Passing closeModal directly to the cancel button (line 94)
  • Wiring the onCancelClick callback through the modal's onClose handler (line 96)

This ensures onCancelClick is invoked consistently across all close scenarios (cancel button, backdrop click, escape key), improving the reliability of cleanup logic.

web/pgadmin/utils/driver/psycopg3/connection.py (5)

199-212: LGTM! Threaded event loop implementation enables async psycopg3 in Flask.

The _start_event_loop method creates a dedicated asyncio event loop in a background daemon thread, which is the correct approach for integrating async operations within Flask's synchronous request-handling model. The thread is properly named for debugging, and the alive check prevents creating duplicate loops.


287-292: LGTM! Efficient busy state detection for async connections.

The is_busy method correctly checks the connection's internal lock state to determine if a query is executing, avoiding polling overhead. This provides a lightweight way to prevent concurrent query execution on the same connection.


855-861: LGTM! Proper async execution routing through the event loop.

The refactored __internal_blocking_execute correctly routes async cursor execution through the dedicated event loop using run_coroutine_threadsafe(...).result(), making it a blocking call from the caller's perspective while allowing the async psycopg3 cursor to execute on the background loop.


1109-1153: LGTM! Proper async execution with busy check and event loop coordination.

The changes correctly:

  1. Initialize the event loop before async operations (line 1109)
  2. Check if the connection is busy to prevent concurrent queries (lines 1145-1148)
  3. Execute the query on the dedicated event loop using run_coroutine_threadsafe (lines 1150-1153)

This ensures safe concurrent execution and proper error handling.


1548-1560: LGTM! Proper async connection cleanup with loop shutdown.

The _close_async method correctly:

  1. Schedules connection closure on the event loop
  2. Stops the loop gracefully with call_soon_threadsafe
  3. Joins the thread with a timeout to avoid indefinite blocking
  4. Cleans up references to prevent resource leaks

The 1-second timeout on thread join is reasonable for cleanup operations.

web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js (1)

52-52: LGTM! New event constant added for data save completion.

The SAVE_DATA_END event constant follows the existing naming convention and is appropriately placed with related event definitions. This supports the broader data synchronization tracking feature mentioned in the PR objectives.

web/pgadmin/utils/driver/psycopg3/cursor.py (1)

280-280: Async pattern in execute is intentional and properly handled by all call sites.

The execute method returns a coroutine directly (line 280) while methods like fetchmany, fetchall, fetchone, and close_cursor wrap async operations with asyncio.run. This is an intentional design choice: execute is called from within async functions that are managed by run_coroutine_threadsafe() in __internal_blocking_execute() (line 859, 1153), which properly awaits the coroutine. The other methods use asyncio.run() because they bridge from sync to async code. All call sites in connection.py correctly handle the coroutine return type via await within async contexts.

web/pgadmin/static/js/custom_hooks.js (1)

290-295: LGTM! Well-implemented stable callback pattern.

This hook correctly implements the "latest ref" pattern for creating stable callbacks that always invoke the most recent function reference. Minor style note: the outer parentheses around the arrow function on line 294 are unnecessary.

return useCallback((...args) => fnRef.current(...args), []);
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (4)

877-880: LGTM! Clean approach for tracking data changes.

Using a ref synchronized via useEffect provides a reliable way to check for unsaved changes without stale closure issues in callbacks.


1093-1120: LGTM! Proper state reset before fetching new data.

Adding resetSelectionAndChanges() ensures clean slate when navigating to a different data window.


1145-1160: Save confirmation modal flow looks correct.

The implementation properly coordinates save completion with page navigation via the SAVE_DATA_END event listener, ensuring data is persisted before moving to the next page.


1203-1305: LGTM! Well-structured save flow with proper event coordination.

Wrapping triggerSaveData with useLatestFunc ensures the callback always operates on current state values. The SAVE_DATA_END event emissions correctly signal success/failure to waiting listeners.

@adityatoshniwal adityatoshniwal force-pushed the warn-page branch 3 times, most recently from ddb75fe to 5057c30 Compare January 14, 2026 05:36
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

🤖 Fix all issues with AI agents
In `@web/pgadmin/utils/driver/psycopg3/connection.py`:
- Line 1086: Remove the debug print("Exception==", str(e)) in connection.py and
replace it with proper logging: obtain the module/class logger (e.g.,
logging.getLogger(__name__) or an existing self.logger) and call
logger.exception(...) or logger.error(..., exc_info=True) with a clear message
including context about the failure (use the same exception variable e) so stack
trace and error details are recorded instead of printed to stdout.
♻️ Duplicate comments (1)
web/pgadmin/tools/sqleditor/utils/start_running_query.py (1)

165-174: Static analysis issues remain unaddressed.

The previous review flagged three issues that are still present:

  1. Line 165: status is captured but never used - prefix with _ or discard.
  2. Line 172: Catching bare Exception is too broad - consider catching specific exceptions like psycopg.Error or ConnectionLost.
  3. Line 173: Use self.logger.exception(...) instead of self.logger.error(...) to automatically include the traceback.
🧹 Nitpick comments (4)
web/pgadmin/utils/driver/psycopg3/connection.py (2)

287-292: Misleading comment - refers to lock but code checks pgconn.is_busy().

The comment on Line 290 mentions "lock is 'locked'" but the implementation calls conn.pgconn.is_busy(), which checks if the libpq connection is processing a query. Consider updating the comment to accurately describe the behavior.

📝 Suggested comment fix
 def is_busy(self):
     """Returns True if the connection is currently executing a query."""
     if self.async_ == 1 and self.conn:
-        # If the lock is 'locked', it means a query is running
+        # Check if the underlying libpq connection is busy processing a query
         return self.conn.pgconn.is_busy()
     return False

1546-1558: Consider handling thread join timeout failure.

The 1-second timeout on thread.join() is reasonable, but if the thread doesn't stop in time, references are still cleared which could lead to orphaned resources. Since the thread is a daemon, it won't block shutdown, but logging a warning on join failure could help diagnose issues.

📝 Optional: Add warning on join timeout
         if self._thread:
             self._thread.join(timeout=1)
+            if self._thread.is_alive():
+                current_app.logger.warning(
+                    f"Event loop thread {self._thread.name} did not stop within timeout"
+                )
             self._thread = None
             self._loop = None
web/pgadmin/utils/driver/psycopg3/cursor.py (1)

378-388: Overly broad exception handling masks potential errors.

The bare except Exception silently returns False for any failure, which could mask connection issues or other errors. Consider catching more specific exceptions or logging the error for debugging.

♻️ Suggested improvement
     def nextset(self):
         """
         Synchronous bridge for nextset.
         """
         try:
             return run_coroutine_threadsafe(
                 super().nextset(),
                 self._get_loop()
             ).result(timeout=10)
-        except Exception:
+        except (asyncio.TimeoutError, asyncio.CancelledError):
+            return False
+        except Exception as e:
+            current_app.logger.debug("nextset failed: %s", e)
             return False
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (1)

1122-1167: Review the conditional flow and cleanup logic in FETCH_WINDOW handler.

The refactored handler introduces a save-or-discard modal before page navigation, which is a good UX improvement. A few observations:

  1. One-time listener pattern: The true third parameter to registerListener (lines 1128, 1153) creates one-time listeners, which is appropriate here.

  2. Potential issue: In the cleanup function (lines 1162-1166), deregExecEnd and deregSaveDataDone might not be defined if the user never triggers the save flow. However, the optional chaining (?.()) handles this correctly.

  3. Line 1134 comment: There's a commented-out line // executionStartCallback(...). Consider removing dead code.

  4. Flow clarity: The impl() function handles both the out-of-sync refresh and the direct fetch paths cleanly.

🔧 Suggested cleanup
          eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, rsu.current.query, {refreshData: true});
-          // executionStartCallback(rsu.current.query, {refreshData: true});
        } else {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a99c80 and 5057c30.

📒 Files selected for processing (8)
  • web/pgadmin/static/js/custom_hooks.js
  • web/pgadmin/static/js/helpers/ModalProvider.jsx
  • web/pgadmin/tools/sqleditor/__init__.py
  • web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js
  • web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
  • web/pgadmin/tools/sqleditor/utils/start_running_query.py
  • web/pgadmin/utils/driver/psycopg3/connection.py
  • web/pgadmin/utils/driver/psycopg3/cursor.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/static/js/custom_hooks.js
🧰 Additional context used
🧬 Code graph analysis (4)
web/pgadmin/static/js/helpers/ModalProvider.jsx (3)
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (1)
  • modalId (847-847)
web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx (1)
  • modalId (69-69)
web/pgadmin/tools/sqleditor/static/js/components/sections/MainToolBar.jsx (1)
  • modalId (59-59)
web/pgadmin/tools/sqleditor/__init__.py (1)
web/pgadmin/utils/driver/psycopg3/connection.py (1)
  • is_busy (287-292)
web/pgadmin/tools/sqleditor/utils/start_running_query.py (1)
web/pgadmin/utils/driver/psycopg3/connection.py (2)
  • execute_async (1088-1169)
  • execute_void (1171-1231)
web/pgadmin/utils/driver/psycopg3/cursor.py (1)
web/pgadmin/utils/driver/psycopg3/server_manager.py (1)
  • connection (201-281)
🪛 Ruff (0.14.11)
web/pgadmin/tools/sqleditor/utils/start_running_query.py

165-165: Unpacked variable status is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


172-172: Do not catch blind exception: Exception

(BLE001)


173-173: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

web/pgadmin/utils/driver/psycopg3/cursor.py

284-285: Avoid specifying long messages outside the exception class

(TRY003)


387-387: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (20)
web/pgadmin/static/js/helpers/ModalProvider.jsx (1)

94-96: LGTM - Cancel flow refactored to use onClose handler.

The cancel callback is now deferred to the modal's onClose handler, invoked when the modal unmounts via closeModal(). Since closeModal() is called without a reason argument (Line 94), onClose will be triggered (Line 300-301), ensuring onCancelClick is still executed.

This is a cleaner pattern that consolidates all close-related callbacks through the modal lifecycle.

web/pgadmin/utils/driver/psycopg3/connection.py (5)

22-24: LGTM - Required imports for async bridging.

The imports for Thread and run_coroutine_threadsafe support the new per-connection event loop pattern for bridging async psycopg3 operations.


199-212: LGTM - Clean event loop lifecycle management.

The implementation correctly:

  • Creates a dedicated event loop per connection for async bridging
  • Uses daemon threads to avoid blocking application shutdown
  • Checks thread liveness before recreating resources
  • Provides descriptive thread names for debugging

390-394: LGTM - Correct async-to-sync bridging for connection establishment.

The pattern of using run_coroutine_threadsafe(...).result() correctly bridges the async psycopg3 connection to synchronous Flask request handling.


1138-1145: LGTM - Resource busy check prevents concurrent query issues.

The is_busy() check before executing prevents attempting to run multiple queries simultaneously on the same async connection, which would cause undefined behavior with psycopg3's async cursors.


1414-1419: LGTM - Consistent busy check in fetch operation.

The is_busy() check mirrors the pattern in execute_async, preventing data fetch operations while a query is still processing.

web/pgadmin/utils/driver/psycopg3/cursor.py (4)

277-286: LGTM - Clean loop retrieval from connection object.

The _get_loop method correctly retrieves the event loop injected by the Connection class, with appropriate error handling if the loop is missing.


288-299: LGTM - Synchronous wrapper for async execute.

The implementation correctly bridges the async cursor's execute method to synchronous code using run_coroutine_threadsafe with blocking .result().


349-361: LGTM - fetchwindow correctly uses sync bridge methods.

The method properly leverages the refactored scroll() and fetchmany() synchronous bridges to implement windowed fetching.


372-376: LGTM - Consistent sync bridge pattern for cursor lifecycle.

The close_cursor method follows the same run_coroutine_threadsafe pattern as other methods, ensuring proper async cleanup.

web/pgadmin/tools/sqleditor/static/js/components/QueryToolConstants.js (1)

52-52: LGTM - New event constant for save data lifecycle.

The SAVE_DATA_END constant supports the unsaved data warning feature, allowing components to signal save completion status.

web/pgadmin/tools/sqleditor/__init__.py (1)

1084-1088: LGTM - Enhanced busy detection using connection state.

The addition of conn.is_busy() alongside the thread alive check provides more robust detection of ongoing query execution. This catches cases where the connection is busy even if the thread state is ambiguous.

web/pgadmin/tools/sqleditor/utils/start_running_query.py (2)

57-57: LGTM: Status initialization updated correctly.

The change from -1 to True aligns with the boolean status returned by execute_async, which returns (True, None) on success and (False, errmsg) on failure according to the relevant code snippets.


184-185: LGTM: Formatting adjustment.

Minor formatting change with no functional impact.

web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (6)

34-34: LGTM: Import addition for stable callback hook.

The useLatestFunc hook import supports the new pattern for triggerSaveData to avoid stale closure issues in async event handlers.


850-850: LGTM: Ref-based state tracking for async operations.

The pageDataOutOfSync and isDataChangedRef refs provide stable references for checking state in async callbacks and event handlers without stale closure issues. This pattern is appropriate for:

  • pageDataOutOfSync: Tracks when saved changes haven't been re-fetched from the server
  • isDataChangedRef: Provides synchronous access to whether data has unsaved changes

Also applies to: 877-880


1203-1305: LGTM: Well-structured save flow with proper event signaling.

The triggerSaveData function is well-refactored:

  1. useLatestFunc wrapper (line 1203, 1305): Ensures the callback always captures the latest dataChangeStore, rows, and columns values, preventing stale closure bugs.

  2. SAVE_DATA_END signaling: All exit paths correctly fire the event:

    • Line 1205: No changes → SAVE_DATA_END(false)
    • Line 1252: Save failed (status error) → SAVE_DATA_END(false)
    • Line 1292: Save succeeded → SAVE_DATA_END(true)
    • Line 1303: API error → SAVE_DATA_END(false)
  3. pageDataOutOfSync tracking: Correctly set to true only on successful save (line 1256), indicating the displayed data may differ from the database until refresh.


1173-1192: LGTM: Consistent ref usage for unsaved changes check.

The change at line 1175 to use isDataChangedRef.current is consistent with the overall refactoring pattern and avoids potential stale closure issues.


1093-1120: LGTM: Selection and changes reset on window fetch.

Adding resetSelectionAndChanges() at line 1098 ensures that when fetching a new data window, any tracked changes and selections are cleared. This is appropriate since the fetched data represents a fresh view from the server.


953-964: LGTM: Ref-based unsaved changes check.

Using isDataChangedRef.current instead of a computed check ensures the latest state is used in this async callback context.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@adityatoshniwal adityatoshniwal force-pushed the warn-page branch 3 times, most recently from e51dbd3 to 4a001e3 Compare January 14, 2026 12:04
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

🤖 Fix all issues with AI agents
In `@docs/en_US/query_tool_toolbar.rst`:
- Around line 244-245: Update the table entry for "Show Entire Range" so the
description is clearer and ends with a period; replace "Click to get all the
rows and set from and to rows range" with a concise sentence like "Click to load
all rows and set the from/to row range." ensuring the trailing period is present
for consistency with other entries.
🧹 Nitpick comments (2)
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (2)

1125-1151: Remove commented-out code and verify listener cleanup.

  1. Line 1133 contains commented-out code that should be removed.
  2. There's a potential issue with listener cleanup: if pageDataOutOfSync.current is true and the component unmounts before EXECUTION_END fires, deregExecEnd will be properly cleaned up. However, if the user dismisses the confirmation dialog (lines 1145-1147), deregExecEnd won't be set but will be safely handled by optional chaining.
Suggested fix
       const impl = ()=> {
         if(pageDataOutOfSync.current) {
           deregExecEnd = eventBus.registerListener(QUERY_TOOL_EVENTS.EXECUTION_END, (success)=>{
             if(!success) return;
             pageDataOutOfSync.current = false;
             fetchWindow(...args);
           }, true);
           eventBus.fireEvent(QUERY_TOOL_EVENTS.EXECUTION_START, rsu.current.query, {refreshData: true});
-          // executionStartCallback(rsu.current.query, {refreshData: true});
         } else {
           pageDataOutOfSync.current = false;
           fetchWindow(...args);
         }
       };

1293-1296: Consider simplifying the dependency array.

Since triggerSaveData is now wrapped with useLatestFunc, it returns a stable callback that always accesses the latest state. The dependency array [dataChangeStore, rows, columns] causes unnecessary listener re-registrations on every state change.

Suggested optimization
   useEffect(()=>{
     eventBus.registerListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_DATA, triggerSaveData);
     return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.TRIGGER_SAVE_DATA, triggerSaveData);
-  }, [dataChangeStore, rows, columns]);
+  }, [triggerSaveData]);

Since useLatestFunc returns a stable reference, this would register the listener only once.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5057c30 and 4a001e3.

⛔ Files ignored due to path filters (1)
  • docs/en_US/images/query_data_pagination.png is excluded by !**/*.png
📒 Files selected for processing (5)
  • docs/en_US/query_tool_toolbar.rst
  • web/pgadmin/static/js/custom_hooks.js
  • web/pgadmin/static/js/helpers/ModalProvider.jsx
  • web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
  • web/pgadmin/utils/driver/psycopg3/server_manager.py
✅ Files skipped from review due to trivial changes (1)
  • web/pgadmin/utils/driver/psycopg3/server_manager.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/pgadmin/static/js/custom_hooks.js
  • web/pgadmin/static/js/helpers/ModalProvider.jsx
🧰 Additional context used
🧬 Code graph analysis (1)
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (1)
web/pgadmin/static/js/custom_hooks.js (1)
  • useLatestFunc (290-295)
🔇 Additional comments (7)
web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx (7)

34-34: LGTM!

The import of useLatestFunc is correctly added to support the stable callback pattern for triggerSaveData.


850-850: LGTM!

Using useRef for pageDataOutOfSync is appropriate since this flag is used for flow control rather than rendering, avoiding unnecessary re-renders.


877-880: LGTM!

The isDataChangedRef pattern effectively avoids stale closure issues in async callbacks by synchronizing the ref with dataChangeStore changes. This ensures that checks for unsaved changes always reflect the current state.


953-964: LGTM!

The condition correctly uses isDataChangedRef.current to check for unsaved changes and skips the prompt during data refresh operations.


1098-1098: LGTM!

Calling resetSelectionAndChanges() after fetching new window data correctly clears stale selections and change tracking.


1163-1168: LGTM!

The check correctly uses isDataChangedRef.current to determine if there are unsaved changes before showing the save prompt.


1193-1291: LGTM!

The useLatestFunc wrapper correctly ensures the callback always references the latest state, avoiding stale closure issues. The pageDataOutOfSync flag logic is sound:

  • Set to true after successful save → triggers data refresh on next page navigation (server may have applied defaults/triggers)
  • Set to false on error → no refresh needed since data wasn't modified

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@adityatoshniwal adityatoshniwal merged commit 7c36eab into pgadmin-org:master Jan 14, 2026
36 of 37 checks passed
@adityatoshniwal adityatoshniwal deleted the warn-page branch January 14, 2026 12:50
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