2.9 KiB
Context
The application currently uses SQLite (mattn/go-sqlite3) with:
sqlcfor type-safe query generation (configured for sqlite engine)goosefor migrations (3 migration files)- Custom retry logic for SQLite busy/locked errors
- Connection pool configuration tuned for SQLite
The codebase is in active development with no production deployments, so we can replace the database layer cleanly without migration tooling.
Goals / Non-Goals
Goals:
- Replace SQLite with PostgreSQL using the
pgxdriver - Maintain the same data model (users, sites, payments)
- Keep using
sqlcfor query generation andgoosefor migrations - Provide a development-friendly PostgreSQL setup
Non-Goals:
- Data migration from existing SQLite databases
- Supporting both databases simultaneously
- Changing the application's data model or schema
Decisions
1. Use pgx/v5 with stdlib compatibility mode
Use github.com/jackc/pgx/v5/stdlib to register the driver with database/sql. This allows us to keep the existing code structure that uses *sql.DB while gaining access to pgx features when needed.
Alternatives considered:
- Pure pgx without stdlib: Would require rewriting all database code to use pgx native types
lib/pq: Older driver, less actively maintained, fewer features
2. Fresh migrations instead of conversion
Delete all existing SQLite migrations and create a single fresh PostgreSQL migration. The schema is simple (3 tables) and there's no production data.
Alternatives considered:
- Convert migrations line-by-line: More work for no benefit since there's no data to preserve
3. PostgreSQL connection string format
Use standard PostgreSQL connection string: postgres://user:password@host:port/dbname?sslmode=disable
Configuration will accept this via the existing --db-dsn flag, with environment variable support for sensitive values.
4. Remove SQLite-specific retry logic
PostgreSQL handles concurrency differently (MVCC) and doesn't have the same busy/locked errors. Remove isRetryableError and simplify connection logic.
Alternatives considered:
- Add PostgreSQL-specific retry logic: Not needed for typical transient errors; connection pool handles reconnection
5. Use SERIAL for auto-increment primary keys
PostgreSQL uses SERIAL (or BIGSERIAL) instead of SQLite's INTEGER PRIMARY KEY AUTOINCREMENT.
6. Triggers for updated_at
Keep the same pattern of using database triggers for updated_at columns, but use PostgreSQL trigger syntax.
Risks / Trade-offs
Development environment complexity → Developers now need PostgreSQL running locally. Mitigate with docker-compose setup and clear documentation.
Connection string with credentials → Sensitive data in config. Mitigate by supporting environment variable interpolation and documenting secure practices.
sqlc regeneration → All generated code will change. Low risk since it's a clean replacement and tests will verify correctness.