By dropping down to common database/sql
constructs, River can share connections and transactions with GORM, a well known ORM (Object Relational Mapper) in the Go ecosystem.
Sharing a database handle
The same *sql.DB
handle can be configured on GORM and a River client:
import ( "github.com/riverqueue/river" "github.com/riverqueue/river/riverdriver/riverdatabasesql" "gorm.io/driver/postgres" "gorm.io/gorm")
sqlDB, err := sql.Open("pgx", "postgres://localhost/river")if err != nil { return nil, err}
gormDB, err := gorm.Open(postgres.New(postgres.Config{ Conn: sqlDB,}), &gorm.Config{})if err != nil { return nil, err}
riverClient, err := river.NewClient(riverdatabasesql.New(sqlDB), &river.Config{ Workers: workers,})if err != nil { return nil, err}
Database/sql does not support listen
The database/sql
package doesn't support Postgres LISTEN
, so if a client using riverdatabasesql
is started for work, it does so in "poll only mode", meaning that jobs are fetched by polling periodically rather than being notified through a Postgres listen/notify channel.
For maximum throughput performance, use of riverdatabasesql
should be restricted to compatibility with packages like GORM, and that a separate client with a Pgx pool and using the more standard riverpgxv5
driver is used for working jobs.
Sharing a transaction
Transactions are shareable by starting them from GORM, then unwrapping their underlying *sql.Tx
with a type assertion and using it with a River client's InsertTx
:
tx := gormDB.Begin()if err := tx.Error; err != nil { return nil, err}
// If in a transaction, ConnPool can be type asserted as an *sql.Tx so// operations from GORM and River occur on the same transaction.sqlTx := tx.Statement.ConnPool.(*sql.Tx)
_, err = riverClient.InsertTx(ctx, sqlTx, SortArgs{ Strings: []string{ "whale", "tiger", "bear", },}, nil)if err != nil { return nil, err}
if err := tx.Commit().Error; err != nil { return nil, err}