Skip to content

fix(storage): pin PostgreSQL sessions to UTC timezone - #1018

Merged
Kiran01bm merged 4 commits into
mainfrom
kiran01bm/postgresconn-pin-utc
Aug 13, 2026
Merged

fix(storage): pin PostgreSQL sessions to UTC timezone#1018
Kiran01bm merged 4 commits into
mainfrom
kiran01bm/postgresconn-pin-utc

Conversation

@Kiran01bm

@Kiran01bm Kiran01bm commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Pins SchemaBot-managed PostgreSQL sessions to timezone=UTC unless the DSN sets one explicitly.

Why

Storage compares plain timestamp columns against server-side now(), which evaluates through the session TimeZone. On a server whose default TimeZone is not UTC, lease-expiry and staleness predicates would skew — and the UTC default of common container images means tests can't catch it. Pinning the session at connection setup makes the comparisons consistent across pods regardless of server configuration.

What

postgresconn injects timezone=UTC into the connection config after DSN parsing, for both DSN forms and the credential-reload path alike. An explicit timezone in the DSN wins, in either DSN form and under any GUC-name spelling.

Two related gaps are tracked as internal follow-ups, out of scope here:

  • MySQL parity: mysqlconn pins no session time_zone, so the same skew class remains live for MySQL lease/staleness predicates — follow-up injects time_zone='+00:00' unless the DSN sets one.
  • received_at is written from client-side time.Now() (zone discarded by pgx) and diffed against server now() in the backlog-age metric — follow-up writes it with the dialect's CurrentTimestamp.

Before / after

Before — session timezone follows the server default

  DSN (no timezone) ──▶ pgx session: TimeZone = server default
                                 │
                                 ▼
         now() evaluates in the server's TZ; plain-timestamp
         predicates (lease expiry, staleness) skew whenever
         the server default is not UTC


After — session pinned at connection setup

  DSN (no timezone) ──▶ postgresconn: inject timezone=UTC ──▶ session: TimeZone = UTC
  DSN (timezone=X)  ──▶ postgresconn: leave as-is         ──▶ session: TimeZone = X
                                 │
                                 ▼
         now() evaluates in UTC on every pod; timestamp
         vs now() predicates behave identically across servers

Storage compares plain timestamp columns against server-side now(),
which evaluates through the session TimeZone. On a server whose default
is not UTC, lease-expiry and staleness predicates would skew. Inject
timezone=UTC at connection setup unless the DSN sets one explicitly,
mirroring the sslmode handling.
Copilot AI lite review requested due to automatic review settings August 13, 2026 05:55

Copilot AI 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.

Pull request overview

Pins SchemaBot-managed PostgreSQL sessions to timezone=UTC (unless explicitly set in the DSN) so storage comparisons between timestamp columns and server-side now() are consistent regardless of the server’s default TimeZone.

Changes:

  • Default pgx runtime param timezone=UTC is applied during connection config parsing unless already present in the DSN.
  • Unit test added to verify both DSN forms default to UTC and that explicit DSN timezone overrides.
  • Integration test added to validate the session reports UTC even when the database default timezone is set to a non-UTC value.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
pkg/postgresconn/postgresconn.go Sets a default timezone=UTC runtime param during connectionConfig() parsing (DSN timezone still wins).
pkg/postgresconn/postgresconn_test.go Adds unit coverage for UTC default and explicit timezone override across URL + keyword DSN forms.
pkg/postgresconn/postgresconn_integration_test.go Adds integration coverage asserting Open() yields a UTC session even when DB default timezone is non-UTC.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/postgresconn/postgresconn_integration_test.go
PostgreSQL matches GUC names case-insensitively but pgx preserves DSN
key case, so an explicit ?TimeZone=... would otherwise coexist with the
injected pin in the startup packet in nondeterministic map order. PGTZ
is documented and covered as an explicit libpq fallback that skips the
pin.
A fresh unpinned session must observe the ALTER DATABASE timezone
default first; otherwise the UTC assertion on the pinned pool holds on
any UTC-default server even if Open never pinned anything.
@Kiran01bm
Kiran01bm marked this pull request as ready for review August 13, 2026 10:53
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@morgo morgo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Approving on Morgan's behalf (automated review).

Clean and PG-only (postgresconn; the MySQL path is structurally untouched). The pin is correctly scoped: it applies only when no timezone GUC is already present under case-insensitive matching — which dodges the real trap here, since pgx preserves DSN key case and a blind write would put both TimeZone=... and timezone=UTC into the startup packet in nondeterministic map order. PGTZ arriving via libpq fallback semantics counts as explicit and is honored. The motivation is load-bearing (storage's lease-expiry/staleness predicates compare plain timestamps against session-timezone-dependent now()), and the tests are exemplary — both DSN forms, both GUC spellings with a no-duplicate assertion, PGTZ honored, and an integration test that first proves a fresh unpinned session observes the non-UTC database default before asserting the pinned session reads UTC.

No nits.

@Kiran01bm
Kiran01bm merged commit 292db6a into main Aug 13, 2026
33 checks passed
@Kiran01bm
Kiran01bm deleted the kiran01bm/postgresconn-pin-utc branch August 13, 2026 21:12
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.

4 participants