Skip to content

feat(db): accept a read replica ID in DB operation commands#177

Merged
aprimakina merged 8 commits into
mainfrom
feat/db-connect-replica-id
Jul 16, 2026
Merged

feat(db): accept a read replica ID in DB operation commands#177
aprimakina merged 8 commits into
mainfrom
feat/db-connect-replica-id

Conversation

@aprimakina

@aprimakina aprimakina commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

The tiger db connection commands (connection-string, connect/psql, test-connection, schema) and the MCP db_execute_query / db_schema tools now accept a read replica set ID wherever a service ID is accepted. A replica ID connects to that replica's endpoint using the parent primary's credentials (replicas share them); a primary ID behaves as before.

The API resolves a replica ID via the normal service-get endpoint, returning the replica's endpoint plus a forked_from link to its parent — so credentials (and password reset) resolve against the parent.

The db connection commands (connection-string, connect/psql,
test-connection, schema) and the db_execute_query / db_schema MCP tools
now resolve a read replica set ID as well as a service ID: it's tried as
a service first, then matched against the project's read replica sets.
A replica ID connects to that replica's endpoint while credentials
resolve against the parent service (replicas share the primary's
credentials).

When --pooled is requested for a replica that has no pooler, all
surfaces warn and fall back to a direct connection. Service-management
commands still treat a replica ID as not found.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aprimakina aprimakina self-assigned this Jul 15, 2026
@aprimakina aprimakina changed the title feat(db): accept a read replica ID wherever a service ID is accepted feat(db): accept a read replica ID in DB operations commands Jul 15, 2026
aprimakina and others added 3 commits July 15, 2026 12:06
Read replica ID resolution now:
- validates a matched replica is active and has an endpoint, returning a
  clear "not active" / "no endpoint" error instead of failing late at
  connect time. A new ErrReplicaNotFound sentinel distinguishes a real
  no-match (fall back to the service error) from a matched-but-unusable
  replica (surface it).
- only scans the project's services when the service lookup failed with a
  404; other failures (auth, network, 5xx) are surfaced as-is via the new
  common.IsNotFound helper, avoiding a wasted service-list call.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… scan

The API resolves a read replica ID through the normal service-get endpoint,
returning the replica's own endpoint plus a forked_from link to its parent
(is_standby, service_id). Use that instead of scanning the project's services:

- common.ResolveConnectionTarget turns a fetched service into a
  ConnectionTarget{Connect, Credential, IsReplica}. A standby replica connects
  to its own endpoint but resolves credentials (and password reset) against the
  parent primary, fixing the "password authentication failed" error when
  connecting to a replica by ID.
- Removes the ResolveServiceOrReplica/FindReplicaByID list-scan machinery, the
  IsNotFound helper, GetReplicaConnectionDetails, ConnectToService/ConnectToTarget.
- Consolidates the pooler policy: ConnectionTarget.Details owns the
  build-and-RequirePooler rule; ReplicaPoolerWarning owns the replica guard;
  resolveConnectTarget handles both the by-ID replica and the interactive menu.
- getServiceDetails delegates to the shared common.GetService.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the redundant "in place of the service ID" and doubled "read replica"
wording from the db connection commands' help and the README, and remove the
read-only/lag aside from `db connect` help (kept in the MCP tool description).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aprimakina aprimakina force-pushed the feat/db-connect-replica-id branch from a4104cd to 57e8469 Compare July 15, 2026 11:59
@aprimakina aprimakina requested a review from Askir July 15, 2026 12:06
@aprimakina aprimakina changed the title feat(db): accept a read replica ID in DB operations commands feat(db): accept a read replica ID in DB operation commands Jul 15, 2026

@Askir Askir left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Naming confused me a lot here but I think otherwise its good.

What happens if you use update-password, save-password and tiger db create role with a read replica? For the record I think its fine if update-password and create role fail (its a read replica after all so read only) but it should probably have a good error. For save-password I genuinely am not sure if it should override the parents password or error out.

Comment thread internal/tiger/cmd/db.go Outdated
}

service, err := getServiceDetailsFunc(cmd, cfg, args)
target, err := resolveConnectionTarget(cmd, cfg, args)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We have resolveConnectionTaget and resolveConnectTarget now doing wildly different things. But reading as if someone typo'd.
Maybe we could clarify these two? One is the connection chooser and returns ConnectionDetails as the result, the other one is the lookup of the ID

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +16 to +20
type ConnectionTarget struct {
Connect api.Service
Credential api.Service
IsReplica bool
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I find this poorly named, it took me a long time while reading to understand that Credential is only the CredentialService/ the service where we resolve the credentials from rather than the actual credentials.

I think it would make sense to at least call the two ConnectionService and CredentialService. Sorry for being a bit pedantic about naming, here. I try not to do that usually but I really struggled understanding what's happening here 😅

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All good, totally makes sense c32b197

aprimakina and others added 3 commits July 16, 2026 14:56
…ommands

- Build the connection string via net/url so a role or password containing
  URL-special characters (e.g. a manually entered password) no longer produces
  an unparseable "invalid userinfo" string.
- service update-password (CLI + MCP) and db create role reject a read replica
  ID, pointing at the primary — a read-only replica can't be written to, and
  replicas share the primary's credentials. Adds common.IsReadReplica.
- db save-password redirects a replica ID to the parent primary, storing the
  password where the connect commands read it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename ConnectionTarget.Connect -> ConnectionService and Credential ->
CredentialService so the field names convey that Credential is the service
whose credentials are used, not the credentials themselves. Trim the now-
redundant doc comments the self-documenting names replace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per review feedback, the near-identical resolveConnectionTarget /
resolveConnectTarget read like a typo. Rename the args->ConnectionTarget
lookup to lookupConnectionTarget and the interactive chooser (->ConnectionDetails)
to selectConnection, and update their tests. common.ResolveConnectionTarget
keeps its name.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aprimakina

Copy link
Copy Markdown
Contributor Author

What happens if you use update-password, save-password and tiger db create role with a read replica? For the record I think its fine if update-password and create role fail (its a read replica after all so read only) but it should probably have a good error. For save-password I genuinely am not sure if it should override the parents password or error out.

That was exactly the changes I was still working on (94aadd7), update-password and db create role now reject a read replica ID up front, before any connection, with a pointer to the primary.

For save-password I went with redirect rather than error. Since a read replica shares the primary's credentials, save-password stores the password under the parent, which is exactly where db connect/test-connection read it. Erroring would've been consistent with the other two. Open to switching it to reject if you'd prefer consistency!

@aprimakina aprimakina marked this pull request as ready for review July 16, 2026 14:02
@aprimakina aprimakina force-pushed the feat/db-connect-replica-id branch from 54718de to f61c856 Compare July 16, 2026 15:08
Condense the overlong CLAUDE.md read-replica bullet to a short pointer and
fix the stale ConnectionTarget field names (ConnectionService/CredentialService).
Drop the forked_from/service-get implementation detail from spec.md, keeping the
user-facing behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aprimakina aprimakina force-pushed the feat/db-connect-replica-id branch from f61c856 to ec57555 Compare July 16, 2026 15:12
@aprimakina aprimakina merged commit 377b0e6 into main Jul 16, 2026
2 checks passed
@aprimakina aprimakina deleted the feat/db-connect-replica-id branch July 16, 2026 15:46
aprimakina added a commit that referenced this pull request Jul 16, 2026
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants