9.1 KiB
Technology Stack
Project: DiunDashboard — PostgreSQL milestone Researched: 2026-03-23 Scope: Adding PostgreSQL support alongside existing SQLite to a Go 1.26 backend
Recommended Stack
PostgreSQL Driver
| Technology | Version | Purpose | Why |
|---|---|---|---|
github.com/jackc/pgx/v5/stdlib |
v5.9.1 | PostgreSQL database/sql driver |
The de-facto standard Go PostgreSQL driver. Pure Go. 7,328+ importers. The stdlib adapter makes it a drop-in for the existing *sql.DB code path. Native pgx interface not needed — this project uses database/sql already and has no PostgreSQL-specific features (no LISTEN/NOTIFY, no COPY). |
Confidence: HIGH — Verified via pkg.go.dev (v5.9.1, published 2026-03-22). pgx v5 is the clear community standard; lib/pq is officially in maintenance-only mode.
Do NOT use:
github.com/lib/pq— maintenance-only since 2021; pgx is the successor recommended by the postgres ecosystem.- Native pgx interface (
pgx.Connect,pgxpool.New) — overkill here; this project only needs standard queries and the existing*sql.DBpattern should be preserved for consistency.
Database Migration Tool
| Technology | Version | Purpose | Why |
|---|---|---|---|
github.com/golang-migrate/migrate/v4 |
v4.19.1 | Schema migrations for both SQLite and PostgreSQL | Supports both database/sqlite (uses modernc.org/sqlite — pure Go, no CGO) and database/pgx/v5 (uses pgx v5). Both drivers are maintained. The existing inline CREATE TABLE IF NOT EXISTS + silent ALTER TABLE approach does not scale to dual-database support; a proper migration tool is required. |
Confidence: HIGH — Verified via pkg.go.dev. The database/sqlite sub-package explicitly uses modernc.org/sqlite (pure Go), matching the project's no-CGO constraint. The database/pgx/v5 sub-package uses pgx v5.
Drivers to import:
// For SQLite migrations (pure Go, no CGO — matches existing constraint)
_ "github.com/golang-migrate/migrate/v4/database/sqlite"
// For PostgreSQL migrations (via pgx v5)
_ "github.com/golang-migrate/migrate/v4/database/pgx/v5"
// Migration source (embedded files)
_ "github.com/golang-migrate/migrate/v4/source/iofs"
Do NOT use:
pressly/goose— Its SQLite dialect documentation does not confirm pure-Go driver support; CGO status is ambiguous. golang-migrate explicitly documents use ofmodernc.org/sqlite. Goose is a fine tool but the CGO uncertainty is a disqualifier for this project.database/sqlite3variant of golang-migrate — Usesmattn/go-sqlite3which requires CGO. Usedatabase/sqlite(no3) instead.
SQLite Driver (Existing — Retain)
| Technology | Version | Purpose | Why |
|---|---|---|---|
modernc.org/sqlite |
v1.47.0 | Pure-Go SQLite driver | Already in use; must be retained for no-CGO cross-compilation. Current version in go.mod is v1.46.1 — upgrade to v1.47.0 (released 2026-03-17) for latest SQLite 3.51.3 and bug fixes. |
Confidence: HIGH — Verified via pkg.go.dev versions tab.
SQL Dialect Abstraction
The Problem
The existing codebase has four SQLite-specific SQL constructs that break on PostgreSQL:
| Location | SQLite syntax | PostgreSQL equivalent |
|---|---|---|
InitDB — tags table |
INTEGER PRIMARY KEY AUTOINCREMENT |
INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY |
UpdateEvent |
INSERT OR REPLACE INTO updates VALUES (?,...) |
INSERT INTO updates (...) ON CONFLICT (image) DO UPDATE SET ... |
DismissHandler |
UPDATE ... SET acknowledged_at = datetime('now') |
UPDATE ... SET acknowledged_at = NOW() |
TagAssignmentHandler |
INSERT OR REPLACE INTO tag_assignments |
INSERT INTO tag_assignments ... ON CONFLICT (image) DO UPDATE SET tag_id = ... |
| All handlers | ? positional placeholders |
$1, $2, ... positional placeholders |
Recommended Pattern: Storage Interface
Extract a Store interface in pkg/diunwebhook/. Implement it twice: once for SQLite (sqliteStore), once for PostgreSQL (postgresStore). Both implementations use database/sql and raw SQL, but with dialect-appropriate queries.
// pkg/diunwebhook/store.go
type Store interface {
InitSchema() error
UpdateEvent(event DiunEvent) error
GetUpdates() (map[string]UpdateEntry, error)
DismissUpdate(image string) error
GetTags() ([]Tag, error)
CreateTag(name string) (Tag, error)
DeleteTag(id int) error
AssignTag(image string, tagID int) error
UnassignTag(image string) error
}
This is a standard Go pattern: define a narrow interface, swap implementations via factory function. The sync.Mutex moves into each store implementation (SQLite store keeps SetMaxOpenConns(1) + mutex; PostgreSQL store can use a connection pool without a global mutex).
Do NOT use:
- ORM (GORM, ent, sqlc, etc.) — The query set is small and known. An ORM adds a dependency with its own dialect quirks and opaque query generation. Raw SQL with an interface is simpler, easier to test, and matches the existing project style.
database/sqlquery builder libraries (squirrel, etc.) — Same reasoning; the schema is simple enough that explicit SQL per dialect is more readable and maintainable.
Configuration
New Environment Variable
| Variable | Purpose | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string (triggers PostgreSQL mode when set) | — (unset = SQLite mode) |
DB_PATH |
SQLite file path (existing) | ./diun.db |
Selection logic: If DATABASE_URL is set, use PostgreSQL. Otherwise, use SQLite with DB_PATH. This is the simplest signal — no new DB_DRIVER variable needed.
PostgreSQL connection string format:
postgres://user:password@host:5432/dbname?sslmode=disable
Migration File Structure
migrations/
001_initial_schema.up.sql
001_initial_schema.down.sql
002_add_acknowledged_at.up.sql
002_add_acknowledged_at.down.sql
Each migration file should be valid for both SQLite and PostgreSQL — this is achievable for the current schema since:
AUTOINCREMENTcan becomeINTEGER PRIMARY KEY(SQLite auto-assigns rowid regardless of keyword; PostgreSQL usesSERIAL— requires separate dialect files or a compatibility shim).
Revised recommendation: Use separate migration directories per dialect when DDL diverges significantly:
migrations/
sqlite/
001_initial_schema.up.sql
002_add_acknowledged_at.up.sql
postgres/
001_initial_schema.up.sql
002_add_acknowledged_at.up.sql
This is more explicit than trying to share SQL across dialects. golang-migrate supports iofs (Go embed) as a source, so both directories can be embedded in the binary.
Full Dependency Changes
# Add PostgreSQL driver (via pgx v5 stdlib adapter)
go get github.com/jackc/pgx/v5@v5.9.1
# Add migration tool with SQLite (pure Go) and pgx/v5 drivers
go get github.com/golang-migrate/migrate/v4@v4.19.1
# Upgrade existing SQLite driver to current version
go get modernc.org/sqlite@v1.47.0
No other new dependencies are required. The existing database/sql usage throughout the codebase is preserved.
Alternatives Considered
| Category | Recommended | Alternative | Why Not |
|---|---|---|---|
| PostgreSQL driver | pgx/v5 stdlib | lib/pq | lib/pq is maintenance-only since 2021; pgx is the successor |
| PostgreSQL driver | pgx/v5 stdlib | Native pgx interface | Project uses database/sql; stdlib adapter preserves consistency; no need for PostgreSQL-specific features |
| Migration tool | golang-migrate | pressly/goose | Goose's SQLite CGO status unconfirmed; golang-migrate explicitly uses modernc.org/sqlite |
| Migration tool | golang-migrate | Inline CREATE TABLE IF NOT EXISTS |
Inline approach cannot handle dual-dialect schema differences or ordered version history |
| Abstraction | Store interface | GORM / ent | Schema is 3 tables; ORM adds complexity without benefit; project already uses raw SQL |
| Abstraction | Store interface | sqlc | Code generation adds a build step and CI dependency; not warranted for this scope |
| Placeholder style | Per-dialect (? vs $1) |
sqlx named params |
Named params add a new library; explicit per-dialect SQL is clearer and matches project style |
Sources
- pgx v5.9.1: https://pkg.go.dev/github.com/jackc/pgx/v5@v5.9.1 — HIGH confidence
- pgxpool: https://pkg.go.dev/github.com/jackc/pgx/v5/pgxpool — HIGH confidence
- golang-migrate v4.19.1 sqlite driver (pure Go): https://pkg.go.dev/github.com/golang-migrate/migrate/v4/database/sqlite — HIGH confidence
- golang-migrate v4 pgx/v5 driver: https://pkg.go.dev/github.com/golang-migrate/migrate/v4/database/pgx/v5 — HIGH confidence
- golang-migrate v4 sqlite3 driver (CGO — avoid): https://pkg.go.dev/github.com/golang-migrate/migrate/v4/database/sqlite3 — HIGH confidence
- modernc.org/sqlite v1.47.0: https://pkg.go.dev/modernc.org/sqlite?tab=versions — HIGH confidence
- goose v3.27.0: https://pkg.go.dev/github.com/pressly/goose/v3 — MEDIUM confidence (SQLite CGO status not confirmed in official docs)