Skip to content

feat: add ~/.pgpass file support and pre-connect script#279

Merged
datlechin merged 2 commits intomainfrom
feat/pgpass-pre-connect-hook
Mar 11, 2026
Merged

feat: add ~/.pgpass file support and pre-connect script#279
datlechin merged 2 commits intomainfrom
feat/pgpass-pre-connect-hook

Conversation

@datlechin
Copy link
Owner

@datlechin datlechin commented Mar 11, 2026

Summary

  • Add ~/.pgpass file support for PostgreSQL/Redshift connections: toggle in the Authentication section disables the password field and lets libpq read credentials from ~/.pgpass, with live validation (file exists, permissions are 0600, matching entry found)
  • Add pre-connect script: shell command in the Advanced tab that runs via /bin/bash -c before each explicit connection attempt (10s timeout, non-zero exit aborts connection), useful for refreshing credentials or updating ~/.pgpass from secrets managers
  • Both features use additionalFields storage on DatabaseConnection, requiring no data migration

Test plan

  • Create ~/.pgpass with chmod 0600, verify toggle shows green "matching entry exists" status
  • Change permissions to 0644, verify red "incorrect permissions" warning
  • Delete ~/.pgpass, verify yellow "not found" warning
  • Connect to PostgreSQL with pgpass enabled and no password — should authenticate via pgpass
  • Test pre-connect hook with echo test script — verify it runs before connect
  • Test pre-connect hook with exit 1 — verify connection is aborted with error message
  • Test pre-connect hook with sleep 15 — verify 10s timeout terminates it
  • Verify pgpass toggle only appears for PostgreSQL/Redshift types
  • Verify pre-connect hook does NOT run on auto-reconnect from ConnectionHealthMonitor
  • Verify switching database type away from PostgreSQL resets usePgpass to false

Summary by CodeRabbit

  • New Features

    • Support using ~/.pgpass for PostgreSQL/Redshift auth with live validation in the connection form.
    • Pre-connect script option to run a shell command before each connection (10s timeout, environment inherited).
  • Documentation

    • Added PostgreSQL guides (English and Vietnamese) for ~/.pgpass usage and pre-connect scripts, plus localization strings and UI status messages.

@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e1c0178-f113-48ef-bd2d-822461b0be12

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7e293 and 73b4b31.

📒 Files selected for processing (3)
  • TablePro/Core/Database/DatabaseManager.swift
  • TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift
  • TablePro/Views/Connection/ConnectionFormView.swift

📝 Walkthrough

Walkthrough

Adds ~/.pgpass support and a pre-connect shell-hook for database connections, including pgpass parsing, UI status, connection-model flags, driver password resolution, pre-connect execution with timeout, and documentation/localization updates.

Changes

Cohort / File(s) Summary
Pgpass Infrastructure
TablePro/Core/Utilities/Connection/PgpassReader.swift
New utility to locate/validate ~/.pgpass and resolve passwords using pgpass semantics (wildcards, first-match).
Pre-Connect Hook
TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift
New runner to execute a shell script (/bin/bash -c ...) with stderr capture and a 10s timeout; defines HookError and logs outcomes.
Connection Model
TablePro/Models/Connection/DatabaseConnection.swift
Adds usePgpass: Bool and preConnectScript: String? persisted in additionalFields.
Driver & Manager Integration
TablePro/Core/Database/DatabaseDriver.swift, TablePro/Core/Database/DatabaseManager.swift
Centralized password resolution (returns empty when usePgpass + Postgres/Redshift), and DatabaseManager runs pre-connect hook before finalizing session; propagates additionalFields through tunneled connections.
Connection Form UI
TablePro/Views/Connection/ConnectionFormView.swift
UI controls for toggling Use ~/.pgpass, pgpass status view, and Pre-Connect Script editor; loads/saves flags and updates test/save flows.
Docs & Localization
TablePro/Resources/Localizable.xcstrings, docs/databases/postgresql.mdx, docs/vi/databases/postgresql.mdx
Added localization keys/translations for pgpass and pre-connect messages; added docs describing ~/.pgpass usage and pre-connect script behavior/examples.
Changelog
CHANGELOG.md
Changelog entries noting pgpass support and pre-connect script capability.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Form as ConnectionFormView
    participant Manager as DatabaseManager
    participant Driver as DatabaseDriverFactory
    participant Pgpass as PgpassReader
    participant Hook as PreConnectHookRunner
    participant Storage as ConnectionStorage
    participant DB as Database

    User->>Form: connect request
    Form->>Manager: connectToSession(connection)
    Manager->>Driver: createDriver(connection)
    Driver->>Driver: resolvePassword(connection)
    alt usePgpass & Postgres/Redshift
        Driver->>Pgpass: resolve(host, port, db, user)
        Pgpass-->>Driver: password or nil
    else
        Driver->>Storage: loadPassword(connection.id)
        Storage-->>Driver: password
    end
    Driver-->>Manager: driver ready
    opt preConnectScript present
        Manager->>Hook: run(script, env)
        alt hook fails (timeout/exit != 0)
            Hook-->>Manager: HookError
            Manager->>Manager: cleanup session & rethrow
        else
            Hook-->>Manager: success
        end
    end
    Manager->>DB: establish connection
    DB-->>Manager: connected
    Manager-->>Form: connection ready
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • ~/.pgpass file #277: Implements ~/.pgpass support and pre-connect hook behavior requested by the issue (PgpassReader, usePgpass, PreConnectHookRunner integrations).

Possibly related PRs

Poem

🐰 I dug for keys in ~/.pgpass,
and hopped a pre-connect bash—
a timeout set, stderr watched,
credentials found, connections matched,
hop! the rabbit logs success at last.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% 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 'feat: add ~/.pgpass file support and pre-connect script' directly and clearly summarizes the main changes—adding two new features for PostgreSQL/Redshift connections as demonstrated throughout the changeset.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/pgpass-pre-connect-hook

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (9)
TablePro/Resources/Localizable.xcstrings (1)

12048-12068: Consolidate stale pre-connect error keys to a single placeholder format.

Both %d and stale %lld variants now exist for the same error messages. Keep one canonical key format and remove stale duplicates once call sites are aligned, to avoid translator drift and key mismatch risk.

Also applies to: 12072-12082

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

In `@TablePro/Resources/Localizable.xcstrings` around lines 12048 - 12068,
Consolidate the two localized keys for the pre-connect error into a single
canonical placeholder format by keeping the "%d" variant ("Pre-connect script
failed (exit %d): %@") and removing the stale "%lld" key ("Pre-connect script
failed (exit %lld): %@") and its extractionState entry; update any call sites
that still use the %lld placeholder to use the %d placeholder so they match the
canonical key before deletion, and ensure the Vietnamese localization value is
migrated to the retained key's localizations so translators are not lost.
docs/databases/postgresql.mdx (3)

422-422: Use sentence case for heading.

📝 Suggested fix
-## Pre-Connect Script
+## Pre-connect script
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/databases/postgresql.mdx` at line 422, Heading "## Pre-Connect Script"
uses title case; change it to sentence case ("## Pre-connect script") in the
docs/databases/postgresql.mdx file by updating the heading text (the line
starting with "## Pre-Connect Script") so only the first word is capitalized and
subsequent words use lowercase.

390-392: Add language identifiers to code blocks.

Per documentation guidelines, include language identifiers in fenced code blocks. These appear to be plain text configuration examples.

📝 Suggested fix
-```
+```text
 hostname:port:database:username:password

And similarly for lines 396-402.

</details>


Also applies to: 396-402

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/databases/postgresql.mdx around lines 390 - 392, The fenced code blocks
showing configuration examples (e.g., the block containing
"hostname:port:database:username:password" and the other blocks around lines
396-402) are missing language identifiers; update each fenced block to include
the "text" language tag (```text) so the examples are explicitly marked as plain
text per docs guidelines.


</details>

---

`375-375`: **Use sentence case for heading.**

Per documentation guidelines, headings should use sentence case. "File" and "Support" should be lowercase.


<details>
<summary>📝 Suggested fix</summary>

```diff
-## ~/.pgpass File Support
+## ~/.pgpass file support
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@docs/databases/postgresql.mdx` at line 375, Change the heading "## ~/.pgpass
File Support" to sentence case by updating it to "## ~/.pgpass file support" in
the docs where the heading string appears; ensure any table of contents or
internal references use the same sentence-case heading text to keep links and
anchors consistent.
```

</details>

</blockquote></details>
<details>
<summary>TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift (2)</summary><blockquote>

`52-63`: **Consider wrapping the blocking `waitUntilExit()` for better async behavior.**

`process.waitUntilExit()` blocks the current thread. While this works within the 10-second timeout window, wrapping it in a detached task or using `terminationHandler` would be more idiomatic for async Swift. However, given the short timeout, this is acceptable.



The current implementation is functional. If you want to improve it later:

```swift
// Alternative using terminationHandler (optional future improvement)
await withCheckedContinuation { continuation in
    process.terminationHandler = { _ in
        continuation.resume()
    }
}
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift` around
lines 52 - 63, The code currently calls process.waitUntilExit(), which blocks
the thread; replace this blocking call with an asynchronous wait by using a
detached Task or process.terminationHandler combined with await
withCheckedContinuation to resume when the process exits (refer to
process.waitUntilExit(), timeoutTask, Task, and process.terminationHandler), and
ensure timeoutTask still terminates the process after 10s and is cancelled once
the continuation resumes to avoid leaks.
```

</details>

---

`11-12`: **Add explicit access control.**

Per coding guidelines, use explicit access control on the enum and its members.


<details>
<summary>♻️ Suggested fix</summary>

```diff
-enum PreConnectHookRunner {
+internal enum PreConnectHookRunner {
     private static let logger = Logger(subsystem: "com.TablePro", category: "PreConnectHookRunner")
 
-    enum HookError: LocalizedError {
+    internal enum HookError: LocalizedError {
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift` around
lines 11 - 12, The enum PreConnectHookRunner currently lacks an explicit access
modifier—add an explicit access level (e.g., internal or public per your module
API) to the enum declaration and ensure all members have explicit modifiers as
well (for example keep private static let logger as private, and mark any
functions/props inside PreConnectHookRunner with internal/private/public as
appropriate) so the enum and its members conform to the codebase access-control
guideline.
```

</details>

</blockquote></details>
<details>
<summary>TablePro/Core/Utilities/Connection/PgpassReader.swift (2)</summary><blockquote>

`10-11`: **Add explicit access control to the enum and its members.**

Per coding guidelines, always use explicit access control. The `enum` and its `static` methods should be marked with appropriate visibility.


<details>
<summary>♻️ Suggested fix</summary>

```diff
-enum PgpassReader {
-    private static let logger = Logger(subsystem: "com.TablePro", category: "PgpassReader")
+internal enum PgpassReader {
+    private static let logger = Logger(subsystem: "com.TablePro", category: "PgpassReader")
```

And for each public-facing static method (lines 14, 20, 34), add `internal` explicitly:

```diff
-    static func fileExists() -> Bool {
+    internal static func fileExists() -> Bool {
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Utilities/Connection/PgpassReader.swift` around lines 10 - 11,
The enum PgpassReader and its members lack explicit access control; mark the
enum as internal and explicitly annotate its stored property logger and each
static method (the three static functions referenced in the review) with
internal visibility (e.g., change "enum PgpassReader" to "internal enum
PgpassReader" and add "internal" before "private static let logger" where
appropriate or make logger "internal" if used externally, and prefix each static
function declaration with "internal") so all enum, property, and static method
declarations have explicit access control.
```

</details>

---

`35-39`: **Consider using URL-based file reading API.**

`String(contentsOfFile:encoding:)` works but the URL-based variant is preferred in modern Swift. This is a minor refinement.


<details>
<summary>♻️ Suggested change</summary>

```diff
-        let path = NSHomeDirectory() + "/.pgpass"
-        guard let contents = try? String(contentsOfFile: path, encoding: .utf8) else {
+        let url = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".pgpass")
+        guard let contents = try? String(contentsOf: url, encoding: .utf8) else {
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@TablePro/Core/Utilities/Connection/PgpassReader.swift` around lines 35 - 39,
Replace the file-read call to use the URL-based API: build a URL for the home
directory entry (e.g., using
FileManager.homeDirectoryForCurrentUser.appendingPathComponent(".pgpass") or
URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".pgpass")), then
call String(contentsOf: url, encoding: .utf8) instead of
String(contentsOfFile:encoding:); update the guard in the PgpassReader read
logic (the variable currently named path/contents and the guard that logs "Could
not read ~/.pgpass") to use the URL and preserve the existing error handling and
logger.debug behavior.
```

</details>

</blockquote></details>
<details>
<summary>docs/vi/databases/postgresql.mdx (1)</summary><blockquote>

`390-392`: **Add language identifiers to code blocks.**

Same as the English version - add language identifiers to the configuration examples for consistency.


<details>
<summary>📝 Suggested fix</summary>

```diff
-```
+```text
 hostname:port:database:username:password
 ```
```

</details>


Also applies to: 396-402

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @docs/vi/databases/postgresql.mdx around lines 390 - 392, Update the fenced
code blocks that show the PostgreSQL connection format so they include a
language identifier; specifically change the triple-backtick blocks containing
the text "hostname:port:database:username:password" (and the other similar
example further down) to use "text" instead of "" so the examples match
the English version and render consistently.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Inline comments:
In @TablePro/Resources/Localizable.xcstrings:

  • Around line 9386-9388: The new localization entries (for example the
    "Loading..." key in Localizable.xcstrings and other newly added keys listed) are
    missing zh-Hans translations causing fallback to source; update each of these
    keys to include a zh-Hans translation entry alongside existing locales (at
    minimum add a "zh-Hans" string for "Loading..." and for the other ranges
    referenced) so every new key has a zh-Hans value; locate the keys by their exact
    string identifiers (e.g., "Loading...") in Localizable.xcstrings and add the
    corresponding zh-Hans translations in the same object structure as the vi
    entries.

In @TablePro/Views/Connection/ConnectionFormView.swift:

  • Around line 932-934: The code computes trimmedScript but stores the original
    preConnectScript into finalAdditionalFields, so replace the stored value with
    the trimmed string; set finalAdditionalFields["preConnectScript"] =
    trimmedScript instead of preConnectScript, and apply the same change at the
    other location where preConnectScript is stored later in this file (the second
    assignment that mirrors this logic).
  • Around line 379-384: The pgpass lookup is using the wrong defaults: change the
    username fallback from "root" to "postgres" and use the selected database type's
    default port instead of always DatabaseType.postgresql.defaultPort; update the
    call to PgpassReader.resolve (the block that builds host/port/database/username)
    to compute portInt = Int(port) ?? currentDatabaseType.defaultPort (or
    DatabaseType.redshift.defaultPort when the connection type is Redshift) and set
    username to username.isEmpty ? "postgres" : username so Redshift connections use
    port 5439 and PostgreSQL uses 5432.

Nitpick comments:
In @docs/databases/postgresql.mdx:

  • Line 422: Heading "## Pre-Connect Script" uses title case; change it to
    sentence case ("## Pre-connect script") in the docs/databases/postgresql.mdx
    file by updating the heading text (the line starting with "## Pre-Connect
    Script") so only the first word is capitalized and subsequent words use
    lowercase.
  • Around line 390-392: The fenced code blocks showing configuration examples
    (e.g., the block containing "hostname:port:database:username:password" and the
    other blocks around lines 396-402) are missing language identifiers; update each
    fenced block to include the "text" language tag (```text) so the examples are
    explicitly marked as plain text per docs guidelines.
  • Line 375: Change the heading "## ~/.pgpass File Support" to sentence case by
    updating it to "## ~/.pgpass file support" in the docs where the heading string
    appears; ensure any table of contents or internal references use the same
    sentence-case heading text to keep links and anchors consistent.

In @docs/vi/databases/postgresql.mdx:

  • Around line 390-392: Update the fenced code blocks that show the PostgreSQL
    connection format so they include a language identifier; specifically change the
    triple-backtick blocks containing the text
    "hostname:port:database:username:password" (and the other similar example
    further down) to use "text" instead of "" so the examples match the
    English version and render consistently.

In @TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift:

  • Around line 52-63: The code currently calls process.waitUntilExit(), which
    blocks the thread; replace this blocking call with an asynchronous wait by using
    a detached Task or process.terminationHandler combined with await
    withCheckedContinuation to resume when the process exits (refer to
    process.waitUntilExit(), timeoutTask, Task, and process.terminationHandler), and
    ensure timeoutTask still terminates the process after 10s and is cancelled once
    the continuation resumes to avoid leaks.
  • Around line 11-12: The enum PreConnectHookRunner currently lacks an explicit
    access modifier—add an explicit access level (e.g., internal or public per your
    module API) to the enum declaration and ensure all members have explicit
    modifiers as well (for example keep private static let logger as private, and
    mark any functions/props inside PreConnectHookRunner with
    internal/private/public as appropriate) so the enum and its members conform to
    the codebase access-control guideline.

In @TablePro/Core/Utilities/Connection/PgpassReader.swift:

  • Around line 10-11: The enum PgpassReader and its members lack explicit access
    control; mark the enum as internal and explicitly annotate its stored property
    logger and each static method (the three static functions referenced in the
    review) with internal visibility (e.g., change "enum PgpassReader" to "internal
    enum PgpassReader" and add "internal" before "private static let logger" where
    appropriate or make logger "internal" if used externally, and prefix each static
    function declaration with "internal") so all enum, property, and static method
    declarations have explicit access control.
  • Around line 35-39: Replace the file-read call to use the URL-based API: build
    a URL for the home directory entry (e.g., using
    FileManager.homeDirectoryForCurrentUser.appendingPathComponent(".pgpass") or
    URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent(".pgpass")), then
    call String(contentsOf: url, encoding: .utf8) instead of
    String(contentsOfFile:encoding:); update the guard in the PgpassReader read
    logic (the variable currently named path/contents and the guard that logs "Could
    not read ~/.pgpass") to use the URL and preserve the existing error handling and
    logger.debug behavior.

In @TablePro/Resources/Localizable.xcstrings:

  • Around line 12048-12068: Consolidate the two localized keys for the
    pre-connect error into a single canonical placeholder format by keeping the "%d"
    variant ("Pre-connect script failed (exit %d): %@") and removing the stale
    "%lld" key ("Pre-connect script failed (exit %lld): %@") and its extractionState
    entry; update any call sites that still use the %lld placeholder to use the %d
    placeholder so they match the canonical key before deletion, and ensure the
    Vietnamese localization value is migrated to the retained key's localizations so
    translators are not lost.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `44f925c5-809f-4f1f-bab5-34159c2d936f`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between f24289fec760161d981670340c1fead266122677 and 3b7e2935d44779592bcf7a6c924a7f4391a44114.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `CHANGELOG.md`
* `TablePro/Core/Database/DatabaseDriver.swift`
* `TablePro/Core/Database/DatabaseManager.swift`
* `TablePro/Core/Services/Infrastructure/PreConnectHookRunner.swift`
* `TablePro/Core/Utilities/Connection/PgpassReader.swift`
* `TablePro/Models/Connection/DatabaseConnection.swift`
* `TablePro/Resources/Localizable.xcstrings`
* `TablePro/Views/Connection/ConnectionFormView.swift`
* `docs/databases/postgresql.mdx`
* `docs/vi/databases/postgresql.mdx`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

- PreConnectHookRunner: wrap Process execution in Task.detached to
  avoid blocking MainActor; use StderrCollector (Sendable, NSLock)
  to drain stderr concurrently and prevent pipe deadlock
- DatabaseManager: forward additionalFields in buildEffectiveConnection
  so usePgpass is preserved through SSH tunnels
- ConnectionFormView: cache pgpass status in @State via onChange
  handlers instead of doing file I/O in view body on every keystroke
@datlechin datlechin merged commit 5fa7be8 into main Mar 11, 2026
2 checks passed
@datlechin datlechin deleted the feat/pgpass-pre-connect-hook branch March 11, 2026 17:52
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