# einherjar/db-postgres [![version](https://img.shields.io/badge/version-v1.0.0-5C4EE5?style=flat-square)](https://code.nochebuena.dev/einherjar/db-postgres) [![license](https://img.shields.io/badge/license-AGPL--3.0-22863A?style=flat-square)](LICENSE) [![go](https://img.shields.io/badge/Go-1.26+-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev) [![health](https://img.shields.io/badge/health-critical-D73A49?style=flat-square)]() > The runes carved in stone do not fade when the runemaster dies. They outlast the hand that carved them. `code.nochebuena.dev/einherjar/db-postgres` is the PostgreSQL database component of the Einherjar framework. It wraps `pgxpool` behind a lifecycle-aware `Component`, exposes a uniform `Executor` interface for queries and transactions, and provides a `UnitOfWork` that injects the active transaction into context — so repositories never need to know whether they are inside a transaction or not. --- ## Usage ### Setup ```go import dbpg "code.nochebuena.dev/einherjar/db-postgres" db := dbpg.New(logger, dbpg.DefaultConfig()) launcher.Append(db) // OnInit opens pool; OnStop closes it health.Register(db) // PING health check; LevelCritical ``` ### Querying ```go // GetExecutor returns the pool when called outside a UnitOfWork. rows, err := db.GetExecutor(ctx).Query(ctx, "SELECT id, name FROM users WHERE active = $1", true) defer rows.Close() var name string err := db.GetExecutor(ctx).QueryRow(ctx, "SELECT name FROM users WHERE id = $1", id).Scan(&name) ``` ### Manual transaction ```go tx, err := db.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) _, err = tx.Exec(ctx, "UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromID) if err != nil { return err } return tx.Commit(ctx) ``` `BeginTx` is available when you need explicit isolation level control: ```go tx, err := db.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.Serializable}) ``` ### Unit of work (recommended) `UnitOfWork` wraps the transaction in context so every call to `GetExecutor` inside `uow.Do` automatically returns the active transaction. Repositories require no changes. ```go uow := dbpg.NewUnitOfWork(logger, db) err := uow.Do(ctx, func(ctx context.Context) error { _, err := db.GetExecutor(ctx).Exec(ctx, "INSERT INTO orders (...) VALUES (...)", ...) return err }) ``` If the function returns an error, the transaction is rolled back. If it returns nil, the transaction is committed. ### Error handling ```go if err := db.HandleError(someErr); err != nil { // pgconn error codes mapped to xerrors: // 23505 unique_violation → ErrAlreadyExists // 23503 foreign_key_violation → ErrPreconditionFailed // 23502 not_null_violation → ErrInvalidInput // context.Canceled → ErrCancelled // context.DeadlineExceeded → ErrDeadlineExceeded } ``` `HandleError` is also available as a package-level function: `dbpg.HandleError(err)`. --- ## Environment variables | Variable | Required | Default | Description | |---|---|---|---| | `EINHERJAR_PG_HOST` | Yes | — | PostgreSQL host | | `EINHERJAR_PG_PORT` | No | `5432` | Listen port | | `EINHERJAR_PG_USER` | Yes | — | Database user | | `EINHERJAR_PG_PASSWORD` | Yes | — | Database password | | `EINHERJAR_PG_NAME` | Yes | — | Database name | | `EINHERJAR_PG_SSL_MODE` | No | `disable` | `disable`, `require`, `verify-full` | | `EINHERJAR_PG_TIMEZONE` | No | `UTC` | Session timezone | | `EINHERJAR_PG_MAX_CONNS` | No | `5` | Maximum connections in pool | | `EINHERJAR_PG_MIN_CONNS` | No | `2` | Minimum idle connections | | `EINHERJAR_PG_MAX_CONN_LIFETIME` | No | `1h` | Maximum connection lifetime | | `EINHERJAR_PG_MAX_CONN_IDLE_TIME` | No | `30m` | Maximum idle time before connection is closed | | `EINHERJAR_PG_HEALTH_CHECK_PERIOD` | No | `1m` | pgxpool background health check interval | --- ## Dependency graph ``` contracts (zero dependencies) ↑ core ↑ db-postgres (contracts, core, pgx/v5, pgerrcode) ↑ your app ``` --- ## Verification ```bash cd db-postgres/ go build ./... go vet ./... go test ./... gofmt -l . ``` --- > *The hall is built before the warriors arrive.* > *That is the only guarantee worth making.*