Migrating from go-pg
Bun is a rewrite of go-pg that works with PostgreSQL, MySQL, and SQLite. It consists of:
- Bun core that provides a query builder and models.
- pgdriver package to connect to PostgreSQL.
- migrate package to run migrations.
- dbfixture to load initial data from YAML files.
- Optional starter kit that provides modern app skeleton.
Bun's query builder tries to be compatible with go-pg's builder, but some rarely used APIs are removed (for example, WhereOrNotGroup
). In most cases, you won't need to rewrite your queries.
go-pg is still maintained and there is no urgency in rewriting go-pg apps in Bun, but new projects should prefer Bun over go-pg. And once you are familiar with the updated API, you should be able to migrate a 80-100k lines go-pg app to Bun within a single day.
New features
*pg.Query
is split into smaller structs, for example, bun.SelectQuery, bun.InsertQuery, bun.UpdateQuery, bun.DeleteQuery and so on. This is one of the reasons Bun inserts/updates data faster than go-pg.go-pg API:
err := db.ModelContext(ctx, &users).Select() err := db.ModelContext(ctx, &users).Select(&var1, &var2) res, err := db.ModelContext(ctx, &users).Insert() res, err := db.ModelContext(ctx, &user).WherePK().Update() res, err := db.ModelContext(ctx, &users).WherePK().Delete()
Bun API:
err := db.NewSelect().Model(&users).Scan(ctx) err := db.NewSelect().Model(&users).Scan(ctx, &var1, &var2) res, err := db.NewInsert().Model(&users).Exec(ctx) res, err := db.NewUpdate().Model(&users).WherePK().Exec(ctx) res, err := db.NewDelete().Model(&users).WherePK().Exec(ctx)
To create
VALUES (1, 'one')
statement, usedb.NewValues(&rows)
.Bulk
UPDATE
queries should be rewrited using CTE andVALUES
statement:db.NewUpdate(). With("_data", db.NewValues(&rows)). Model((*Model)(nil)). Table("_data"). Set("model.name = _data.name"). Where("model.id = _data.id"). Exec(ctx)
Alternatively, you can use
UpdateQuery.Bulk
helper that does the same:err := db.NewUpdate().Model(&rows).Bulk().Exec(ctx)
To create an index, use
db.NewCreateIndex()
.To drop an index, use
db.NewDropIndex()
.To truncate a table, use
db.NewTruncateTable()
.To overwrite model table name, use
q.Model((*MyModel)(nil)).ModelTableExpr("my_table_name")
.To provide initial data, use fixtures.
Go zero values and NULL
Unlike go-pg, Bun does not marshal Go zero values as SQL NULLs by default. To get the old behavior, use nullzero
tag option:
type User struct {
Name string `bun:",nullzero"`
}
For time.Time
fields you can use bun.NullTime
:
type User struct {
Name string `bun:",nullzero"`
CreatedAt time.Time `bun:",notnull,default:current_timestamp"`
UpdatedAt bun.NullTime
}
Other changes
- Replace
pg
struct tags withbun
, for example,bun:"my_column_name"
. - Replace
rel:"has-one"
withrel:"belongs-to"
andrel:"belongs-to"
withrel:"has-one"
. go-pg used wrong names for those relations. - Replace
tableName struct{} `pg:"mytable`"
withbun.BaseModel `bun:"mytable"`
. This helps with linters that mark the field as unused. - To marshal Go zero values as NULLs, use
bun:",nullzero"
field tag. By default, Bun does not marshal Go zero values asNULL
any more. - Replace
pg.ErrNoRows
withsql.ErrNoRows
. - Replace
db.WithParam
withdb.WithNamedArg
. - Replace
orm.RegisterTable
withdb.RegisterModel
. - Replace
pg.Safe
withbun.Safe
. - Replace
pg.Ident
withbun.Ident
. - Replace
pg.Array
withpgdialect.Array
. - Replace
pg:",discard_unknown_columns"
withdb.WithDiscardUnknownColumns()
option. - Replace
q.OnConflict("DO NOTHING")
withq.On("CONFLICT DO NOTHING")
. - Replace
q.OnConflict("(column) DO UPDATE")
withq.On("CONFLICT (column) DO UPDATE")
. - Replace
ForEach
withsql.Rows
anddb.ScanRow
. - Replace
WhereIn("foo IN (?)", slice)
withWhere("foo IN (?)", bun.In(slice))
. - Replace
db.RunInTransaction
withdb.RunInTx
. - Replace
db.SelectOrInsert
with an upsert:
res, err := db.NewInsert().Model(&model).On("CONFLICT DO NOTHING").Exec(ctx)
res, err := db.NewInsert().Model(&model).On("CONFLICT DO UPDATE").Exec(ctx)
- Replace
q.UpdateNotZero()
withq.OmitZero()
on an update query:
// go-pg API:
res, err := db.Model(&model).WherePK().UpdateNotZero()
// bun API:
res, err := db.NewUpdate().Model(&model).WherePK().OmitZero().Exec(ctx)
- Bun uses a database/sql pool, so use sql.DBStats instead of
pg.PoolStats.
WrapWith
is removed. UseWith
instead:
subq := db.NewSelect()
q := db.NewSelect().
With("subq", subq).
Table("subq")
Ignored columns
Unlike go-pg, Bun does not allow scanning into explicitly ignored fields. For example, the following code does not work:
type Model struct {
Foo string `bun:"-"`
}
But you can fix it by adding scanonly
option:
type Model struct {
Foo string `bun:",scanonly"`
}
pg.Listener
You have 2 options if you need pg.Listener
:
- Use pgdriver.Listener.
- Use pgx.
Porting migrations
Bun supports migrations via bun/migrate package. Because it uses timestamp-based migration names, you need to rename your migration files, for example, 1_initial.up.sql
should be renamed to 20210505110026_initial.up.sql
.
After you are done porting migrations, you need to initialize Bun tables (use starter kit):
go run cmd/bun/main.go -env=dev db init
And probably mark existing migrations as completed:
go run cmd/bun/main.go -env=dev db mark_applied
You can check the status of migrations with:
go run cmd/bun/main.go -env=dev db status
Monitoring performance
To monitor Bun performance, you can use OpenTelemetry instrumentation that comes with Bun.
OpenTelemetry is an open source project that aims to provide a unified set of APIs, libraries, agents, and instrumentation to enable observability in modern software applications. It allows developers to collect, instrument, and export telemetry data from their applications to gain insight into the performance and behavior of distributed systems.
Uptrace is a OpenTelemetry APM that supports distributed tracing, metrics, and logs. You can use it to monitor applications and troubleshoot issues.
Uptrace comes with an intuitive query builder, rich dashboards, alerting rules with notifications, and integrations for most languages and frameworks.
Uptrace can process billions of spans and metrics on a single server and allows you to monitor your applications at 10x lower cost.
In just a few minutes, you can try Uptrace by visiting the cloud demo (no login required) or running it locally with Docker. The source code is available on GitHub.