Files
DiunDashboard/.planning/phases/03-postgresql-support/03-01-SUMMARY.md
Jean-Luc Makiola f611545ae5 docs(03-01): complete PostgreSQL store and migration infrastructure plan
- Add 03-01-SUMMARY.md with full execution record
- Update STATE.md: advance to plan 2, record metrics and decisions
- Update ROADMAP.md: phase 03 in progress (1/2 plans complete)
- Update REQUIREMENTS.md: mark DB-01 and DB-03 complete
2026-03-24 09:10:48 +01:00

4.7 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
03-postgresql-support 01 persistence
postgresql
store
migration
pgx
requires provides affects
PostgresStore
RunPostgresMigrations
RunSQLiteMigrations
pkg/diunwebhook/migrate.go
pkg/diunwebhook/postgres_store.go
added patterns
github.com/jackc/pgx/v5 v5.9.1
golang-migrate pgx/v5 driver
Store interface implementation
golang-migrate embedded migrations
pgx/v5 stdlib adapter
created modified
pkg/diunwebhook/postgres_store.go
pkg/diunwebhook/migrations/postgres/0001_initial_schema.up.sql
pkg/diunwebhook/migrations/postgres/0001_initial_schema.down.sql
pkg/diunwebhook/migrate.go
pkg/diunwebhook/export_test.go
go.mod
go.sum
PostgresStore uses *sql.DB via pgx/v5/stdlib adapter — no native pgx pool, consistent with SQLiteStore pattern
No mutex in PostgresStore — PostgreSQL handles concurrent writes natively (unlike SQLite)
Timestamps stored as TEXT in PostgreSQL schema — matches SQLite scan logic, avoids TIMESTAMPTZ type divergence
CreateTag uses RETURNING id instead of LastInsertId — pgx driver does not support LastInsertId
AssignTag uses ON CONFLICT (image) DO UPDATE instead of INSERT OR REPLACE — standard PostgreSQL upsert
Both migration runners compiled into same binary — no build tags needed (both drivers always present)
duration completed tasks_completed files_changed
~2.5 minutes 2026-03-24T08:09:42Z 2 7

Phase 03 Plan 01: PostgreSQL Store and Migration Infrastructure Summary

PostgresStore implementing all 9 Store interface methods using pgx/v5 stdlib adapter, plus PostgreSQL migration infrastructure with RunPostgresMigrations and renamed RunSQLiteMigrations.

What Was Built

PostgresStore (pkg/diunwebhook/postgres_store.go)

Full implementation of the Store interface for PostgreSQL:

  • NewPostgresStore constructor with connection pool: MaxOpenConns(25), MaxIdleConns(5), ConnMaxLifetime(5m)
  • All 9 methods: UpsertEvent, GetUpdates, AcknowledgeUpdate, ListTags, CreateTag, DeleteTag, AssignTag, UnassignTag, TagExists
  • PostgreSQL-native SQL: $1..$15 positional params, NOW(), RETURNING id, ON CONFLICT DO UPDATE
  • No sync.Mutex — PostgreSQL handles concurrent writes natively

PostgreSQL Migrations (pkg/diunwebhook/migrations/postgres/)

  • 0001_initial_schema.up.sql: Creates same 3 tables as SQLite (updates, tags, tag_assignments); uses SERIAL PRIMARY KEY for tags.id; timestamps remain TEXT to match scan logic
  • 0001_initial_schema.down.sql: Drops all 3 tables in dependency order

Updated migrate.go

  • RunMigrations renamed to RunSQLiteMigrations
  • RunPostgresMigrations added using pgxmigrate driver with "pgx5" database name
  • Second //go:embed migrations/postgres directive added for postgresMigrations

Decisions Made

Decision Rationale
TEXT timestamps in PostgreSQL schema Avoids scan divergence with SQLiteStore; both stores parse RFC3339 strings identically
RETURNING id in CreateTag pgx driver does not implement LastInsertId; RETURNING is the PostgreSQL-idiomatic approach
ON CONFLICT (image) DO UPDATE in AssignTag Replaces SQLite's INSERT OR REPLACE; functionally equivalent upsert in standard SQL
No mutex in PostgresStore PostgreSQL connection pool + MVCC handles concurrency; mutex would serialize unnecessarily
Both drivers compiled into binary Simpler than build tags; binary size cost acceptable for a server binary

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Updated export_test.go to use renamed function

  • Found during: Task 1 verification
  • Issue: go vet ./pkg/diunwebhook/ failed because export_test.go still referenced RunMigrations (renamed to RunSQLiteMigrations). The plan's acceptance criteria requires go vet to exit 0, which takes precedence over the instruction to defer export_test.go changes to Plan 02.
  • Fix: Updated both NewTestServer and NewTestServerWithSecret in export_test.go to call RunSQLiteMigrations
  • Files modified: pkg/diunwebhook/export_test.go
  • Commit: 95b64b4

Verification Results

  • go build ./pkg/diunwebhook/ exits 0
  • go vet ./pkg/diunwebhook/ exits 0
  • PostgreSQL migration UP contains SERIAL PRIMARY KEY, all 3 tables
  • PostgreSQL migration DOWN contains DROP TABLE IF EXISTS for all 3 tables
  • go.mod contains github.com/jackc/pgx/v5 v5.9.1
  • migrate.go exports both RunSQLiteMigrations and RunPostgresMigrations

Known Stubs

None — this plan creates implementation code, not UI stubs.

Self-Check: PASSED