Connections
ant sits on database/sql, so the connections are the standard library's, and you can reach them whenever you need to.
Opening
ant.Open connects and fills in your context. You import the driver yourself: ant doesn't pick one for you.
import _ "github.com/jackc/pgx/v5/stdlib"
func open(ctx context.Context) (*AppDB, error) {
return ant.Open[AppDB](ctx, postgres.Dialect, dsn)
}
Close closes the pool, and should run once, on the context from Open.
Pool settings
db.DB() is the *sql.DB.
db.DB().SetMaxOpenConns(25)
db.DB().SetConnMaxLifetime(5 * time.Minute)
An existing pool
ant.Wrap puts a context on a pool you already have.
pool, err := sql.Open("pgx", dsn)
if err != nil {
return err
}
db, err := ant.Wrap[AppDB](postgres.Dialect, pool)
Close closes that pool too, as with Open.
Joining a transaction
ant.UseTx binds a context to a transaction started elsewhere, such as by another library. You commit or roll it back. Observers don't run, since they wait for a commit ant can't see.
tx, err := db.DB().BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
bound, err := ant.UseTx(db, tx)
if err != nil {
return err
}
err = bound.Users.Insert(ctx, alice)
Two context types can share one transaction, through ant.CurrentTx, so two parts of a program commit together.
type OtherDB struct {
ant.Context
Posts ant.Table[Post]
}
err := db.Tx(ctx, func(tx *AppDB) error {
joined, err := ant.UseTx(other, ant.CurrentTx(tx))
if err != nil {
return err
}
return joined.Posts.Insert(ctx, post)
})
One connection
ant.UseConn keeps everything on one connection, for a session setting or an advisory lock. Transactions stay on it too. You close it.
conn, err := db.DB().Conn(ctx)
if err != nil {
return err
}
defer conn.Close()
bound, err := ant.UseConn(db, conn)
if err != nil {
return err
}
_, err = ant.Raw[any](bound, "SET application_name = 'worker'").Exec(ctx)
Read replicas
ant.WithReplicas sends reads outside transactions to replicas in turn. Writes, locks, and transactions use the primary. A failing replica is an error for that read, not a retry elsewhere.
replica, err := sql.Open("pgx", replicaDSN)
if err != nil {
return err
}
db, err := ant.Open[AppDB](ctx, postgres.Dialect, dsn,
ant.WithReplicas(replica),
ant.WithStickyWrites(2*time.Second),
)
WithStickyWrites sends a context's reads to the primary for a while after each of its writes, so replication lag can't hide what it just wrote. To force one read:
users, err := db.Users.All(ant.OnPrimary(ctx))
For separate databases, see several databases.
See it tested: tests/features/transactions