By dropping down to common database/sql constructs, River can share connections and transactions with Bun, a well known ORM (Object Relational Mapper) in the Go ecosystem.
Sharing a database handle
The same *sql.DB handle can be configured on Bun and a River client:
import ( "database/sql"
"github.com/jackc/pgx/v5/pgxpool" _ "github.com/jackc/pgx/v5/stdlib" "github.com/riverqueue/river" "github.com/riverqueue/river/riverdriver/riverdatabasesql")sqlDB, err := sql.Open("pgx", "postgres://localhost/river")if err != nil { return nil, err}
// Providing a separate listener pool is optional, but gives the client// access to listen/notify and more responsive to incoming jobs.listenerPool, err := pgxpool.New(ctx, "postgres://localhost/river")if err != nil { return nil, err}defer listenerPool.Close()
bunDB := bun.NewDB(sqlDB, pgdialect.New())
riverClient, err := river.NewClient( riverdatabasesql.NewWithPgxListener(sqlDB, listenerPool), &river.Config{ Workers: workers, },)if err != nil { return nil, err}Pgx provides LISTEN/NOTIFY
The database/sql package doesn't expose Postgres LISTEN, so NewWithPgxListener uses listenerPool to receive notifications while all queries and transactions continue through sqlDB.
Use riverdatabasesql.New instead to intentionally run in poll-only mode.
Sharing a transaction
Transactions are shareable by starting them from Bun, then accessing bun.Tx's embedded *sql.Tx and using it with a River client's InsertTx:
tx, err := bunDB.BeginTx(ctx, &sql.TxOptions{})if err != nil { return nil, err}
_, err = riverClient.InsertTx(ctx, tx.Tx, SortArgs{ // tx.Tx is *sql.Tx Strings: []string{ "whale", "tiger", "bear", },}, nil)if err != nil { return nil, err}
if err := tx.Commit(); err != nil { return nil, err}