Skip to content

CI: add a non-UTC TZ leg for the db adapter conformance suites #55

Description

@58bits

Follow-up from implementing @byline/db-mysql: two gaps in the current MySQL CI coverage that came out of finishing the temporal (date/datetime) convergence work, not from the original design spec.

Spec: specs/2026-07-24-db-mysql-adapter-design.md — background on the adapter this CI coverage protects.

Gap 1 — no non-UTC timezone leg

CI's MySQL service container runs under TZ=UTC (the container and runner default). Both adapters anchor date field values to UTC midnight specifically because host-local midnight would make the same stored date value materialise as a different calendar day depending on which host served it (see packages/db-postgres/src/modules/storage/normalize-row.ts's toDateOnly docblock and its MySQL counterpart in packages/db-mysql/src/modules/storage/normalize-row.ts).

Under TZ=UTC, local midnight and UTC midnight are the same instant — so a regression that silently reintroduced local-midnight date handling (e.g. dropping the explicit T00:00:00.000Z anchor and letting new Date('YYYY-MM-DD') parse against the runtime's local zone) would pass every existing CI run and only misbehave on a host running under a non-UTC offset.

Ask: add a CI leg — or parameterise the existing integration job — that runs with TZ set to a non-UTC, non-whole-hour-friendly offset (e.g. Asia/Kathmandu, UTC+5:45, or at minimum Asia/Bangkok, UTC+7, per the live-server evidence already gathered in normalize-row.ts's docblocks) so this class of regression fails CI instead of shipping.

Gap 2 — no MySQL version matrix

CI currently pins a single mysql:8.0 service container — the adapter's documented floor (packages/db-mysql/src/lib/boot-check.ts — MySQL 8.0.14+ for LATERAL joins and the lifted no-subquery-in-a-view's-FROM restriction). Nothing exercises a newer MySQL release (9.x, or mysql:latest) automatically, so a behavioural change in a newer MySQL release affecting this adapter's SQL would only surface when someone happens to test against it manually.

Ask: add a matrix leg (or a second, non-blocking job) running the same integration suite against mysql:latest (or the current newest GA) alongside the pinned mysql:8.0 floor leg, so drift between the floor and the current release is visible in CI rather than discovered live.

Where CI lives today

.github/workflows/ci.yml — the MySQL service container and db-mysql test env were added in this program (see the merged ci: added a mysql service container and db-mysql test env commit).

Activity

  1. added
    priority: nextKnown and queued for coming PRs
    area: db-adaptersDatabase adapters (db-postgres, db-mysql, conformance suite)
    on Jul 25, 2026
  2. added theissue type on Jul 25, 2026
  3. changed the title [-]CI: add a non-UTC TZ leg and a MySQL 8.0 + latest version matrix[/-] [+]CI: add a non-UTC TZ leg for the db adapter conformance suites[/+] on Jul 27, 2026
  4. 58bits commented on Jul 27, 2026

    @58bits
    MemberAuthor

    Narrowed and closing. The two gaps got different answers.

    Gap 1 — non-UTC timezone leg: closed

    This was the gap worth acting on, because it could hide a real regression rather than merely a hypothetical one. CI now re-runs both adapter conformance suites under TZ=Asia/Kathmandu (UTC+5:45 — a non-whole-hour offset, so it also catches arithmetic that assumes offsets land on the hour).

    The step was verified non-vacuous before landing, rather than assumed to work. Replacing normalize-row.ts's explicit T00:00:00.000Z anchor with a host-local T00:00:00.000:

    • under TZ=UTC (what CI ran before): 177/177 Postgres tests pass — the regression is completely invisible
    • under TZ=Asia/Kathmandu (the new leg): fails on 02 Field Types > round-trips a calendar date at UTC midnight on both adapters

    The catching test lives in the shared @byline/db-conformance field-types suite, so one step covers both adapters. Unmutated, both suites pass under the new offset (Postgres 177/177, MySQL 201/201).

    Cost was the deciding constraint, so the leg runs as an extra step inside the existing test-suite job rather than as a separate job — the service containers and the package build are already warm, so the added time is the two adapter suites themselves (~1 min) rather than another install/build/service cycle.

    Gap 2 — MySQL version matrix: descoped, deliberately

    Not doing this on every run. A matrix leg would roughly double the DB-integration surface of every pull request to guard against a class of drift that has not bitten us, and keeping CI times short is the higher priority right now.

    The pin stays at mysql:8.0 because that is the documented engine floor (8.0.14+, for LATERAL joins and the lifted no-subquery-in-a-view's-FROM restriction). Pinning the floor is what actually exercises the floor — a matrix that also ran latest would not change what the required check guarantees.

    Coverage of newer releases instead comes from:

    • local development, which already runs mysql:latest (9.x) via mysql/docker-compose.yml — so the current release is exercised continuously by anyone developing the adapter
    • a manual run when a new MySQL GA lands, or if adapter-visible behaviour is suspected

    If that proves too loose in practice — a MySQL release breaks us and local development did not catch it first — reopen for a scheduled (not per-PR) job.

    Reflected in the GA criteria at #58, where CI is now checked off and CLI adapter selection is the sole remaining blocker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: db-adaptersDatabase adapters (db-postgres, db-mysql, conformance suite)priority: nextKnown and queued for coming PRs

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions