186 lines
9.1 KiB
Markdown
186 lines
9.1 KiB
Markdown
# 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.DB` pattern 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:**
|
|
|
|
```go
|
|
// 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 of `modernc.org/sqlite`. Goose is a fine tool but the CGO uncertainty is a disqualifier for this project.
|
|
- `database/sqlite3` variant of golang-migrate — Uses `mattn/go-sqlite3` which requires CGO. Use `database/sqlite` (no `3`) 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.
|
|
|
|
```go
|
|
// 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/sql` query 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:
|
|
- `AUTOINCREMENT` can become `INTEGER PRIMARY KEY` (SQLite auto-assigns rowid regardless of keyword; PostgreSQL uses `SERIAL` — 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
|
|
|
|
```bash
|
|
# 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)
|