Skip to content

Conversation

@akshay-joshi
Copy link
Contributor

@akshay-joshi akshay-joshi commented Nov 25, 2025

Summary by CodeRabbit

  • New Features

    • Introduced configurable Plain SQL restore option gated by a new server-mode setting (disabled by default).
  • Documentation

    • Restore dialog docs updated with instructions to enable Plain restore in server mode.
  • Bug Fixes

    • Standardized error handling and confirmation flows in utility dialogs.
  • Behavior Changes

    • Post-save callbacks for backup/maintenance/restore were removed, altering post-save alerts and automatic background job starts.

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

@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

Adds a configurable flag ENABLE_PLAIN_SQL_RESTORE (default False), propagates it to the frontend, gates the Plain restore UI option, introduces server-side SQL file safety checks and an optional confirmation flow, and removes legacy saveCallBack handlers from backup, maintenance, and restore frontend modules. Documentation and editorconfig updated.

Changes

Cohort / File(s) Summary
Configuration & Feature Flag
web/config.py, web/pgadmin/evaluate_config.py
Added ENABLE_PLAIN_SQL_RESTORE = False (removed ON_DEMAND_LOG_COUNT); automatically enable flag in non-server (desktop) mode during config evaluation.
Backend → Frontend Flag Propagation
web/pgadmin/browser/__init__.py, web/pgadmin/browser/templates/browser/js/utils.js
Pass enable_plain_sql_restore template variable into frontend pgAdmin globals and initialize pgAdmin['enable_plain_sql_restore'].
SQL Safety & Restore Flow
web/pgadmin/tools/restore/__init__.py
Introduced is_safe_sql_file(path) performing encoding, null-byte, line-normalization, and backslash/meta-command checks; use_sql_utility() updated to return an extra confirmation_msg and to block plain SQL in server mode when flag disabled; create_restore_job() handles the new confirmation flow.
Frontend Confirmation Flow & Error Handling
web/pgadmin/static/js/UtilityView.jsx
On API responses containing confirmation_msg, present confirmation dialog and re-submit with confirmed:true if accepted; standardized catch blocks to wrap non-Error values in new Error(...).
UI Rendering for Restore Formats
web/pgadmin/tools/restore/static/js/restore.ui.js
Added pgAdmin import and require pgAdmin['enable_plain_sql_restore'] (and nodeType === 'database') to include the Plain restore option.
Removed Post-save Callbacks
web/pgadmin/tools/backup/static/js/backup.js, web/pgadmin/tools/maintenance/static/js/maintenance.js, web/pgadmin/tools/restore/static/js/restore.js
Removed saveCallBack(data) implementations that previously showed error alerts and/or started background jobs; post-save handling changed/removed in these modules.
Docs & EditorConfig
docs/en_US/restore_dialog.rst, web/.editorconfig
Documented that Plain restore is disabled by default in server mode and how to enable it; added [*.jsx] indent_size = 2 entry.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Restore UI
    participant BE as Backend
    participant Val as SQL Validator
    participant Notif as Confirmation Dialog

    User->>UI: Choose Plain restore & Save
    UI->>BE: POST /restore (confirmed=false)
    BE->>Val: is_safe_sql_file(filepath)
    alt File rejected (server mode & feature disabled)
        BE-->>UI: block_msg (error)
        UI-->>User: Show error
    else Unsafe patterns found
        BE-->>UI: confirmation_msg
        UI->>Notif: Show confirmation dialog
        alt User confirms
            UI->>BE: POST /restore (confirmed=true)
            BE-->>UI: Job started response
            UI-->>User: Restore started
        else User cancels
            UI-->>User: Operation cancelled
        end
    else File safe
        BE-->>UI: Job started response
        UI-->>User: Restore started
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review is_safe_sql_file() in web/pgadmin/tools/restore/__init__.py for correctness of encoding handling, null-byte checks, line normalization, and regex/pattern logic.
  • Verify all call sites unpack the updated return value from use_sql_utility() and handle confirmation_msg.
  • Inspect UtilityView.jsx confirmation/resolution flow for recursion, error propagation, and side effects.
  • Confirm removal of saveCallBack in three JS modules does not break expected post-save behavior (background job start / error notifications).

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 primary change: making Plain SQL restore configurable and disabled by default in server mode, which is directly reflected in the changeset across config, documentation, and UI logic.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/pgadmin/static/js/UtilityView.jsx (1)

91-94: Fix self-referential itemNodeData initialization (runtime ReferenceError)

itemNodeData is used in its own initializer:

let itemNodeData = extraData?.itemNodeData ? itemNodeData: undefined;

This will throw ReferenceError: Cannot access 'itemNodeData' before initialization whenever extraData.itemNodeData is truthy.

Consider:

-  let itemNodeData = extraData?.itemNodeData ? itemNodeData: undefined;
+  let itemNodeData = extraData?.itemNodeData ? extraData.itemNodeData : undefined;
🧹 Nitpick comments (2)
web/pgadmin/static/js/UtilityView.jsx (1)

95-120: Confirm dialog flow and cancel semantics in onSaveClick

The new confirmation_msg handling generally looks sound (a second POST/PUT with confirmed: true and promise chaining via resolve(onSaveClick(...))), but two details are worth double-checking:

  • Passing reject directly as the cancel callback means a user cancel will surface as a rejected promise to the caller. If the higher-level code treats any rejection as an error, that may display error UX for a normal cancel.
  • If the backend ever returns confirmation_msg again even when confirmed: true is sent, this will recurse indefinitely. Assumption seems to be "confirm at most once"; please ensure the server side enforces that.

If the intent is to treat cancel as a silent no-op, wrap reject to return a benign value or a specific cancellation error the caller can distinguish.

docs/en_US/restore_dialog.rst (1)

28-34: Doc note aligns with new config behavior; optional clarification

The note that Plain restore is disabled by default in server mode and enabled by setting ENABLE_PLAIN_SQL_RESTORE to True matches the new config and evaluation logic.

Optionally, you might add that the Plain format is only available when restoring from a database node, to mirror the UI behavior.

📜 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 8c153be and dc6ad98.

📒 Files selected for processing (12)
  • docs/en_US/restore_dialog.rst (1 hunks)
  • web/.editorconfig (1 hunks)
  • web/config.py (1 hunks)
  • web/pgadmin/browser/__init__.py (1 hunks)
  • web/pgadmin/browser/templates/browser/js/utils.js (1 hunks)
  • web/pgadmin/evaluate_config.py (1 hunks)
  • web/pgadmin/static/js/UtilityView.jsx (3 hunks)
  • web/pgadmin/tools/backup/static/js/backup.js (0 hunks)
  • web/pgadmin/tools/maintenance/static/js/maintenance.js (0 hunks)
  • web/pgadmin/tools/restore/__init__.py (5 hunks)
  • web/pgadmin/tools/restore/static/js/restore.js (0 hunks)
  • web/pgadmin/tools/restore/static/js/restore.ui.js (2 hunks)
💤 Files with no reviewable changes (3)
  • web/pgadmin/tools/backup/static/js/backup.js
  • web/pgadmin/tools/maintenance/static/js/maintenance.js
  • web/pgadmin/tools/restore/static/js/restore.js
🧰 Additional context used
🧬 Code graph analysis (3)
web/pgadmin/tools/restore/static/js/restore.ui.js (1)
web/pgadmin/static/js/UtilityView.jsx (2)
  • pgAdmin (24-24)
  • pgAdmin (80-80)
web/pgadmin/static/js/UtilityView.jsx (2)
web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx (2)
  • pgAdmin (74-74)
  • onSaveClick (122-147)
web/pgadmin/misc/properties/ObjectNodeProperties.jsx (2)
  • pgAdmin (27-27)
  • onSaveClick (115-135)
web/pgadmin/tools/restore/__init__.py (1)
web/pgadmin/utils/ajax.py (1)
  • make_json_response (66-84)
🪛 Ruff (0.14.5)
web/pgadmin/tools/restore/__init__.py

440-440: Consider moving this statement to an else block

(TRY300)


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

Replace with exception

(TRY400)


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

Replace with exception

(TRY400)

⏰ 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). (10)
  • GitHub Check: run-feature-tests-pg (14)
  • GitHub Check: run-feature-tests-pg (18)
  • GitHub Check: run-feature-tests-pg (16)
  • GitHub Check: run-feature-tests-pg (15)
  • GitHub Check: run-feature-tests-pg (17)
  • GitHub Check: run-feature-tests-pg (13)
  • GitHub Check: run-python-tests-pg (windows-latest, 15)
  • GitHub Check: run-python-tests-pg (windows-latest, 17)
  • GitHub Check: run-python-tests-pg (windows-latest, 16)
  • GitHub Check: run-python-tests-pg (windows-latest, 18)
🔇 Additional comments (10)
web/.editorconfig (1)

22-24: JSX indentation aligned with JS config

Adding indent_size = 2 for *.jsx matches the existing JS style and keeps formatting consistent across React files.

web/pgadmin/static/js/UtilityView.jsx (1)

117-119: Consistent rejection with Error instances

Standardizing the catch blocks to always reject with an Error instance (err instanceof Error ? err : new Error(gettext('Something went wrong'))) in onSaveClick, getSQLValue, and initData improves downstream error handling (stack traces, instanceof Error checks, etc.).

No further changes needed here.

Also applies to: 133-135, 177-185

web/pgadmin/browser/__init__.py (1)

512-542: Config flag correctly exposed to JS utils template

Passing enable_plain_sql_restore=config.ENABLE_PLAIN_SQL_RESTORE into browser/js/utils.js is consistent with how enable_psql and other feature flags are wired and will correctly reflect the evaluated config (desktop vs server) on the frontend.

web/pgadmin/evaluate_config.py (1)

129-136: Desktop-mode patch correctly forces plain SQL restore on

Setting config['ENABLE_PLAIN_SQL_RESTORE'] = True when SERVER_MODE is false cleanly enforces “always enabled in Desktop mode”, matching the config comments and documentation, and avoids accidental disabling via local overrides.

web/pgadmin/tools/restore/static/js/restore.ui.js (1)

10-14: Plain format correctly gated by frontend feature flag

Using pgAdmin['enable_plain_sql_restore'] && this.fieldOptions.nodeType == 'database' to decide whether to insert the Plain option ensures:

  • Desktop mode (flag true) retains existing behavior.
  • Server mode keeps Plain hidden unless explicitly enabled via config.
  • Non-database nodes never see Plain.

The sources/pgadmin import is consistent with other modules that consume global pgAdmin config.

Also applies to: 359-375

web/config.py (1)

972-981: New ENABLE_PLAIN_SQL_RESTORE flag is well-documented and safely defaulted

Introducing ENABLE_PLAIN_SQL_RESTORE = False with clear comments about server vs desktop behavior and potential meta-command risks provides a clean, centralized control point. Combined with evaluate_and_patch_config’s desktop override, this gives a sensible secure-by-default stance in server mode without regressing desktop UX.

web/pgadmin/browser/templates/browser/js/utils.js (1)

75-77: Frontend flag wiring for plain SQL restore matches existing patterns

Defining pgAdmin['enable_plain_sql_restore'] = '{{enable_plain_sql_restore}}' == 'True'; follows the same pattern as other boolean flags (e.g. enable_psql) and reliably converts the template value into a JS boolean, with a safe default of false if unset.

web/pgadmin/tools/restore/__init__.py (3)

13-13: LGTM!

The config import is necessary to access SERVER_MODE and ENABLE_PLAIN_SQL_RESTORE flags used in the security validation flow.


449-484: All callers correctly handle the new 4-value return signature.

Verification confirms:

  • Only one call site exists (line 510 in create_restore_job)
  • The call correctly unpacks all 4 return values: error_msg, utility, args, confirmation_msg
  • The variable is pre-initialized at line 501 (confirmation_msg = None), ensuring safe handling in both branches (plain format sets it from use_sql_utility(), other formats leave it as None)
  • The confirmation message is properly checked and handled at line 519

The function signature change has been fully integrated without issues.


522-526: This code correctly follows pgAdmin's convention for confirmation dialogs—no changes needed.

The use of success=0 with a data field containing confirmation_msg is pgAdmin's standard pattern for confirmation prompts. The frontend is explicitly designed to recognize and handle this response structure, displaying it as a confirmation dialog rather than an error. The implementation is correct.

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

♻️ Duplicate comments (1)
web/pgadmin/tools/restore/__init__.py (1)

441-446: Critical: Fail-open security vulnerability persists in exception handling.

The function returns True (treating the file as safe) when FileNotFoundError or PermissionError occurs. This is a fail-open security posture that could allow unsafe files to be processed if there's a filesystem issue. The function should fail closed by returning False.

Additionally, static analysis suggests using logging.exception instead of logging.error to include stack traces.

Apply this diff to fix the security vulnerability:

         return True
     except FileNotFoundError:
-        current_app.logger.error("File not found.")
+        current_app.logger.exception(f"Security Alert: File {path} not found.")
+        return False
     except PermissionError:
-        current_app.logger.error("Insufficient permissions to access.")
-
-    return True
+        current_app.logger.exception(f"Security Alert: Insufficient permissions to access {path}.")
+        return False
🧹 Nitpick comments (2)
web/pgadmin/static/js/UtilityView.jsx (1)

103-116: Potential unhandled rejection on user cancel.

When the user cancels the confirmation dialog (line 110), reject is called without an argument. This may leave the caller without a meaningful error object, and the promise rejection might not be properly caught downstream, potentially causing unhandled promise rejection warnings.

Consider providing an explicit error or handling cancellation gracefully:

         pgAdmin.Browser.notifier.confirm(
           gettext('Warning'),
           res.data.data.confirmation_msg,
           function() {
             resolve(onSaveClick(isNew, {...data, confirmed: true}));
           },
-          reject
+          function() {
+            reject(new Error(gettext('Operation cancelled by user.')));
+          }
         );
web/pgadmin/tools/restore/__init__.py (1)

388-440: Consider restructuring the try-except for clarity.

The static analysis tool suggests moving the return True at line 440 to an else block for better code clarity. While the current logic works, the suggested structure makes the success path more explicit.

         for i, line in enumerate(normalized_text.split("\n"), 1):
             stripped = line.strip()
             # ... checks ...
             if "\\g" in line or "\\c" in line or "\\!" in line:
                 # ...
                 return False

-        return True
     except FileNotFoundError:
         # ...
     except PermissionError:
         # ...
+    else:
+        return True

Note: This refactor only makes sense after fixing the exception handling to return False on errors.

📜 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 dc6ad98 and f6c6fa3.

📒 Files selected for processing (12)
  • docs/en_US/restore_dialog.rst (1 hunks)
  • web/.editorconfig (1 hunks)
  • web/config.py (1 hunks)
  • web/pgadmin/browser/__init__.py (1 hunks)
  • web/pgadmin/browser/templates/browser/js/utils.js (1 hunks)
  • web/pgadmin/evaluate_config.py (1 hunks)
  • web/pgadmin/static/js/UtilityView.jsx (3 hunks)
  • web/pgadmin/tools/backup/static/js/backup.js (0 hunks)
  • web/pgadmin/tools/maintenance/static/js/maintenance.js (0 hunks)
  • web/pgadmin/tools/restore/__init__.py (5 hunks)
  • web/pgadmin/tools/restore/static/js/restore.js (0 hunks)
  • web/pgadmin/tools/restore/static/js/restore.ui.js (2 hunks)
💤 Files with no reviewable changes (3)
  • web/pgadmin/tools/restore/static/js/restore.js
  • web/pgadmin/tools/maintenance/static/js/maintenance.js
  • web/pgadmin/tools/backup/static/js/backup.js
✅ Files skipped from review due to trivial changes (1)
  • web/.editorconfig
🚧 Files skipped from review as they are similar to previous changes (5)
  • web/pgadmin/tools/restore/static/js/restore.ui.js
  • web/pgadmin/browser/init.py
  • web/config.py
  • web/pgadmin/browser/templates/browser/js/utils.js
  • web/pgadmin/evaluate_config.py
🧰 Additional context used
🧬 Code graph analysis (1)
web/pgadmin/static/js/UtilityView.jsx (2)
web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx (2)
  • pgAdmin (74-74)
  • onSaveClick (122-147)
web/pgadmin/misc/properties/ObjectNodeProperties.jsx (2)
  • pgAdmin (27-27)
  • onSaveClick (115-135)
🪛 Ruff (0.14.5)
web/pgadmin/tools/restore/__init__.py

440-440: Consider moving this statement to an else block

(TRY300)


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

Replace with exception

(TRY400)


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

Replace with exception

(TRY400)

⏰ 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). (6)
  • GitHub Check: run-feature-tests-pg (13)
  • GitHub Check: run-feature-tests-pg (15)
  • GitHub Check: run-feature-tests-pg (17)
  • GitHub Check: run-feature-tests-pg (18)
  • GitHub Check: run-feature-tests-pg (14)
  • GitHub Check: run-feature-tests-pg (16)
🔇 Additional comments (4)
web/pgadmin/static/js/UtilityView.jsx (1)

117-119: LGTM!

The error handling standardization ensures consistent Error object wrapping across all catch blocks, matching the pattern used elsewhere in the codebase.

web/pgadmin/tools/restore/__init__.py (2)

462-475: LGTM on the gating logic.

The server mode gating and confirmation flow is well-structured:

  • Server mode with disabled flag returns immediately with appropriate message
  • Unsafe files in server mode are blocked; in desktop mode, users get a confirmation prompt
  • The confirmed flag correctly skips re-checking once user has acknowledged

522-526: The review comment references code that does not exist in the current codebase.

After thorough verification:

  1. Lines 522-526 don't exist: web/pgadmin/tools/restore/__init__.py contains only 521 lines. The current code at lines 515-535 shows error handling with errormsg, not confirmation_msg.

  2. No 4-tuple unpacking: The current use_sql_utility() function (line 407) returns exactly 3 values: (error_msg, utility, args), not 4 as claimed in line 509-511.

  3. No frontend confirmation_msg handling: A comprehensive search of the codebase finds zero references to confirmation_msg anywhere—neither in UtilityView.jsx nor any other frontend file. The claimed check at UtilityView.jsx:103 does not exist.

The review comment appears to be based on proposed code changes that are not yet present in the repository, or it references an incorrect branch/version of the code.

Likely an incorrect or invalid review comment.

docs/en_US/restore_dialog.rst (1)

30-34: LGTM on documentation update.

The documentation clearly explains that Plain SQL restore is disabled by default in server mode and provides the configuration setting name to enable it. This aligns with the code changes.

Consider adding a reference to where the configuration file is located (e.g., config_local.py) for users unfamiliar with pgAdmin configuration.

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