F-04: DB connection + golang-migrate runner (bookshelf-qqz.4) #7

Merged
zombor merged 4 commits from bd-bookshelf-qqz.4 into main 2026-05-20 18:33:42 +00:00
Owner

Summary

  • Adds internal/db package with Open(*sql.DB), connection pool config from db.Config, and exponential-backoff retry on ping (max 5 attempts, 30 s cap) to handle compose startup races
  • Embeds internal/db/migrations/*.sql via embed.FS and provides RunMigrations(ctx, db, logger) using golang-migrate with iofs source + mysql database driver; logs completion with duration_ms
  • Placeholder migration 0001_init creates _ping table (up) and drops it (down) — no Grimmory schema yet (that is F-12)
  • Makefile migrate-up / migrate-down targets wired to golang-migrate CLI

Test plan

  • go vet ./internal/db/... passes
  • go test -race ./internal/db/... — 12 specs pass (8 integration via testcontainers-go, 4 unit retry/mask/min)
  • go test -coverprofile=coverage.out -coverpkg=./internal/db ./internal/db/... → 100.0% on internal/db
  • Docker must be running for testcontainers-go integration tests

Closes bead bookshelf-qqz.4 on merge.

## Summary - Adds `internal/db` package with `Open(*sql.DB)`, connection pool config from `db.Config`, and exponential-backoff retry on ping (max 5 attempts, 30 s cap) to handle compose startup races - Embeds `internal/db/migrations/*.sql` via `embed.FS` and provides `RunMigrations(ctx, db, logger)` using golang-migrate with iofs source + mysql database driver; logs completion with `duration_ms` - Placeholder migration `0001_init` creates `_ping` table (up) and drops it (down) — no Grimmory schema yet (that is F-12) - `Makefile` `migrate-up` / `migrate-down` targets wired to golang-migrate CLI ## Test plan - [ ] `go vet ./internal/db/...` passes - [ ] `go test -race ./internal/db/...` — 12 specs pass (8 integration via testcontainers-go, 4 unit retry/mask/min) - [ ] `go test -coverprofile=coverage.out -coverpkg=./internal/db ./internal/db/...` → 100.0% on `internal/db` - [ ] Docker must be running for testcontainers-go integration tests Closes bead bookshelf-qqz.4 on merge.
- Open *sql.DB with pool config (MaxOpenConns, MaxIdleConns, ConnMaxLifetime)
- RetryConnect with exponential backoff, max 5 attempts, 30s cap
- Embed migrations via embed.FS; RunMigrations uses golang-migrate iofs driver
- 0001_init migration creates _ping table for smoke testing
- 100% coverage on internal/db via testcontainers-go MySQL integration tests
- Injectable sqlOpen/dbDriver/migratorSource vars for unit-test error-path coverage
- Makefile migrate-up/migrate-down targets wired to golang-migrate CLI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove 0001_init.{up,down}.sql (_ping placeholder)
- Add 0001_grimmory_baseline.{up,down}.sql from sanitized Grimmory MariaDB dump
  * 72 CREATE TABLE statements with collation, timestamp, display-width fixes
  * SET FOREIGN_KEY_CHECKS=0/1 wrapping (alphabetical order causes forward FK refs)
  * UNIQUE KEY ... USING HASH on varchar(1000) cfi columns replaced with cfi(764)
    prefix indexes — utf8mb4*1000=4000 bytes exceeds MySQL 8 InnoDB 3072-byte limit
- Update db_test.go: verify 4 representative baseline tables exist instead of _ping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zombor force-pushed bd-bookshelf-qqz.4 from bf261d25c0 to 419b8c0403
Some checks failed
/ Lint (pull_request) Successful in 1m30s
/ Test (pull_request) Failing after 2m18s
/ Coverage (pull_request) Has been skipped
2026-05-20 17:50:51 +00:00
Compare
fix(db): use BOOKSHELF_DSN env var in CI instead of testcontainers
Some checks failed
/ Lint (pull_request) Successful in 54s
/ Test (pull_request) Failing after 2m35s
/ Coverage (pull_request) Has been skipped
d3b7c937dd
The CI environment provides a MySQL service container via the BOOKSHELF_DSN
env var. When set, db_test.go now uses that DSN directly instead of trying
to spin up a Docker container via testcontainers-go (which fails because the
CI runner does not have a Docker daemon available).

Also fixes the table-existence assertion to use DATABASE() so it works with
both the CI service schema (bookshelf_test) and the testcontainers schema (testdb).
fix(ci): use mysql:3306 service hostname instead of 127.0.0.1:3307
Some checks failed
/ Test (pull_request) Failing after 2s
/ Coverage (pull_request) Has been skipped
/ Lint (pull_request) Successful in 1m0s
a87b53693d
In Forgejo Actions (act-based runner), service containers share the same
Docker network as the job container. Within that network, the service is
reachable via its name (mysql) on its container port (3306), not via the
host's loopback address (127.0.0.1:3307). The port mapping 3307:3306 is
only meaningful for GitHub-hosted runners.

Update BOOKSHELF_DSN in both the test and coverage jobs to use mysql:3306.
fix(ci): remove static host port mapping for MySQL service
Some checks failed
/ Lint (pull_request) Successful in 1m4s
/ Test (pull_request) Failing after 2m8s
/ Coverage (pull_request) Has been skipped
fe75e87631
The `ports: 3307:3306` binding was causing 'port already allocated' failures
when multiple CI jobs ran concurrently on the same runner, since only one job
can bind a given host port at a time.

Service containers in Forgejo Actions (act-based) are accessible within each
job's Docker network via the service name (mysql:3306), not via a host port
mapping. Removing the static binding eliminates the concurrency conflict while
keeping BOOKSHELF_DSN pointed at mysql:3306.
fix(ci): add multiStatements=true&charset=utf8mb4 to BOOKSHELF_DSN
Some checks failed
/ Lint (pull_request) Successful in 58s
/ Test (pull_request) Successful in 2m49s
/ Coverage (pull_request) Has been cancelled
11f03a3afb
The golang-migrate MySQL driver requires multiStatements=true in the DSN
to execute migration files containing multiple SQL statements. Without it,
MySQL rejects anything after the first semicolon as a syntax error.
Also adds charset=utf8mb4 as required by the DB package contract.
fix(ci): add -count=1 to coverage script to bypass test result cache
All checks were successful
/ Lint (pull_request) Successful in 1m15s
/ Test (pull_request) Successful in 1m50s
/ Coverage (pull_request) Successful in 1m39s
b7bd10105a
The Coverage job hits the test-result cache populated by the Test job (which
runs without -coverpkg). Cached results produce incomplete coverage profiles,
causing false < 100% failures. -count=1 forces a fresh run so the profile
always reflects the full -coverpkg=./internal/... instrumentation.
zombor force-pushed bd-bookshelf-qqz.4 from b7bd10105a
All checks were successful
/ Lint (pull_request) Successful in 1m15s
/ Test (pull_request) Successful in 1m50s
/ Coverage (pull_request) Successful in 1m39s
to e23614cbd5
Some checks failed
/ Test (pull_request) Failing after 52s
/ Coverage (pull_request) Has been skipped
/ Lint (pull_request) Successful in 1m8s
2026-05-20 18:19:48 +00:00
Compare
fix(ci): restore MySQL service container for DB integration tests
All checks were successful
/ Lint (pull_request) Successful in 1m3s
/ Test (pull_request) Successful in 1m36s
/ Coverage (pull_request) Successful in 1m19s
ea9ce85c2e
The Forgejo Actions runner has no Docker socket available to the test
process, so testcontainers-go fails with "rootless Docker not found".
PR #11 dropped the MySQL service container assuming testcontainers would
handle it, but the runner cannot spin up containers from inside the job.

Restore the mysql:8 service container with internal Docker networking
(no host port mapping — avoids the port-collision problem PR #11 cited).
Set BOOKSHELF_DSN with the full parameter set (parseTime, multiStatements,
charset) so db_test.go's BOOKSHELF_DSN branch is taken and testcontainers
is bypassed entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
zombor force-pushed bd-bookshelf-qqz.4 from ea9ce85c2e
All checks were successful
/ Lint (pull_request) Successful in 1m3s
/ Test (pull_request) Successful in 1m36s
/ Coverage (pull_request) Successful in 1m19s
to 79a16846ab
All checks were successful
/ Lint (pull_request) Successful in 1m7s
/ Test (pull_request) Successful in 1m42s
/ Coverage (pull_request) Successful in 1m20s
2026-05-20 18:30:19 +00:00
Compare
zombor merged commit 1ec2b5ac47 into main 2026-05-20 18:33:42 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
zombor/pergamum!7
No description provided.