Skip to content

fix(connections): keep the username empty when a connection URL has none#1860

Merged
datlechin merged 1 commit into
mainfrom
fix/optional-username-no-root-default
Jul 13, 2026
Merged

fix(connections): keep the username empty when a connection URL has none#1860
datlechin merged 1 commit into
mainfrom
fix/optional-username-no-root-default

Conversation

@datlechin

Copy link
Copy Markdown
Member

Problem

Reported by a user:

why cant i have username empty like on tableplus but in your app after import from url its root? tableplus display username empty, but tablepro display root at username

Import mysql://localhost:3306/shop and the Username field ends up as root. TablePlus leaves it empty.

Root cause

The username you type is never what gets saved. ConnectionFormCoordinator rewrote a blank username to the literal "root" in two places, save() and test(), whenever the plugin reported requiresAuthentication — which is the protocol default, so it fired for MySQL, PostgreSQL, ClickHouse and nearly everything else. The field's placeholder was also the hardcoded literal Text("root"), and the model injected "root" as its init default and Codable fallback.

Everything around this was already correct, which is the tell that the fabrication was the anomaly: the URL parser returns "" for a no-user URL (with a test asserting exactly that), the URL formatter omits user@ when empty, and the deeplink exporter and pg_dump builder both already skip an empty username.

Why empty is the correct value to store

An empty username is not a workaround, it is the documented "use the default user" signal in every client library we link:

  • MariaDB Connector/C (mysql_real_connect): NULL and "" are identical. if (mysql->user && mysql->user[0]) ... else read_user_name(end) sends the macOS login name. It never transmits an empty user, so there is no anonymous-auth hazard.
  • libpq: connectOptions2() replaces an empty pguser with pg_fe_getauthname(), and does so before matching .pgpass. Our driver already omits the user= keyword when empty.
  • Redis: the driver already maps empty to nil. Redis has no username before 6.0, and sending default explicitly is known to hang some servers.
  • ClickHouse was the one real gap. See below.

Changes

  • ConnectionFormCoordinator no longer substitutes "root". Both save() and test() were re-deriving host/port/username inline, which is exactly why the bug existed twice, so the resolution now lives once on the view models: NetworkPaneViewModel.resolvedHost / .resolvedPort and AuthPaneViewModel.resolvedUsername.
  • DatabaseConnection defaults the username to "" in both its initializer and its Codable decode.
  • GeneralPaneView drops the root placeholder. On Postgres it told the user the opposite of what would actually happen.
  • PgpassReader.effectiveUsername(_:) resolves a blank username to NSUserName(), matching libpq. Previously a blank field looked up root in ~/.pgpass and reported "no match" even when a real connection would have succeeded.
  • ClickHouse: it sends Basic base64("user:pass") unconditionally, so a blank username would have gone out as :pass. New ClickHouseCredentials helper resolves blank to default, ClickHouse's documented default user. (It was previously sending root, which is wrong for ClickHouse regardless.)

Existing saved connections are untouched. Anyone who already saved a blank username has root on disk and possibly a working connection. Rewriting that on their behalf would be a worse bug than the one being fixed.

Tests

13 new tests across four files:

  • DatabaseConnectionUsernameTests — init default and decode-without-key both yield an empty username; empty round-trips through encoding.
  • ConnectionURLImportUsernameTests — a MySQL/Postgres URL with no user info imports with an empty username and exports back to a URL with no user@; a URL with user info still keeps it.
  • PgpassReaderUsernameTests — blank resolves to the OS user, explicit is preserved.
  • ClickHouseCredentialsTests — blank resolves to default, and the Basic header decodes to default:secret.

No UI automation: TableProUITests currently holds only TableProLaunchUITests.swift, so there is no connection-form harness to extend.

Verification

swiftlint lint --strict clean on all changed files. ClickHouseDriver target builds. Manual check: import mysql://localhost:3306/somedb, the Username field is blank and stays blank through Save and reopen.

@mintlify

mintlify Bot commented Jul 13, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Jul 13, 2026, 2:13 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 55231d7b4f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

host: coordinator.network.resolvedHost,
port: coordinator.network.resolvedPort,
database: coordinator.network.database,
username: PgpassReader.effectiveUsername(resolvedUsername)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the effective pgpass user when resolving passwords

This makes the form check ~/.pgpass with the OS username when the username field is blank, but the actual connection path still calls PgpassReader.resolve(... username: connection.username) in TablePro/Core/Database/DatabaseDriver.swift:547-552. For PostgreSQL connections using ~/.pgpass through an SSH/Cloudflare/Cloud SQL tunnel, the app must resolve the password itself against the original host; with a blank saved username it now reports a match in the form, then looks up an empty username during connect and passes no password, so the connection can fail despite the green status.

Useful? React with 👍 / 👎.

@datlechin datlechin merged commit 2d2da54 into main Jul 13, 2026
3 checks passed
@datlechin datlechin deleted the fix/optional-username-no-root-default branch July 13, 2026 11:11
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