Model and query hooks

Introduction

Hooks are user-defined functions that are called before and/or after certain operations, for example, before every processed query.

To ensure that your model implements a hook interface, use compile time checksopen in new window, for example, var _ bun.QueryHook = (*MyHook)(nil).

Disclaimer

It may sound like a good idea to use hooks for validation or caching because this way you can't forget to sanitize data or check permissions. It gives false sense of safety.

Don't do that. Code that uses hooks is harder to read, understand, and debug. It is more complex and error-prone. Instead, prefer writing simple code like thisopen in new window even if that means repeating yourself:

func InsertUser(ctx context.Context, db *bun.DB, user *User) error {
	// before insert

	if _, err := db.NewInsert().Model(user).Exec(ctx); err != nil {
		return err
	}

	// after insert

	return nil
}

Model hooks

BeforeAppendModel

To update certain fields before inserting or updating a model, use bun.BeforeAppendModelHook which is called just before constructing a query. For exampleopen in new window:

type Model struct {
    ID        int64
    CreatedAt time.Time
    UpdatedAt time.Time
}

var _ bun.BeforeAppendModelHook = (*Model)(nil)

func (m *Model) BeforeAppendModel(ctx context.Context, query bun.Query) error {
	switch query.(type) {
	case *bun.InsertQuery:
		m.CreatedAt = time.Now()
	case *bun.UpdateQuery:
		m.UpdatedAt = time.Now()
	}
	return nil
}

Before/AfterScanRow

Bun also calls BeforeScanRow and AfterScanRow hooks before and after scanning row values. For exampleopen in new window:

type Model struct{}

var _ bun.BeforeScanRowHook = (*Model)(nil)

func (m *Model) BeforeScanRow(ctx context.Context) error { return nil }

var _ bun.AfterScanRowHook = (*Model)(nil)

func (m *Model) AfterScanRow(ctx context.Context) error { return nil }

Model query hooks

You can also define model query hooks that are called before and after executing certain type of queries on a certain model. Such hooks are called once for a query and using a nil model. To access the query data, use query.GetModel().Value().

var _ bun.BeforeSelectHook = (*Model)(nil)

func (*Model) BeforeSelect(ctx context.Context, query *bun.SelectQuery) error { return nil }

var _ bun.AfterSelectHook = (*Model)(nil)

func (*Model) AfterSelect(ctx context.Context, query *bun.SelectQuery) error { return nil }

var _ bun.BeforeInsertHook = (*Model)(nil)

func (*Model) BeforeInsert(ctx context.Context, query *bun.InsertQuery) error { nil }

var _ bun.AfterInsertHook = (*Model)(nil)

func (*Model) AfterInsert(ctx context.Context, query *bun.InsertQuery) error { return nil }

var _ bun.BeforeUpdateHook = (*Model)(nil)

func (*Model) BeforeUpdate(ctx context.Context, query *bun.UpdateQuery) error { return nil }

var _ bun.AfterUpdateHook = (*Model)(nil)

func (*Model) AfterUpdate(ctx context.Context, query *bun.UpdateQuery) error { return nil }

var _ bun.BeforeDeleteHook = (*Model)(nil)

func (*Model) BeforeDelete(ctx context.Context, query *bun.DeleteQuery) error { return nil }

var _ bun.AfterDeleteHook = (*Model)(nil)

func (*Model) AfterDelete(ctx context.Context, query *bun.DeleteQuery) error { return nil }

var _ bun.BeforeCreateTableHook = (*Model)(nil)

func (*Model) BeforeCreateTable(ctx context.Context, query *CreateTableQuery) error { return nil }

var _ bun.AfterCreateTableHook = (*Model)(nil)

func (*Model) AfterCreateTable(ctx context.Context, query *CreateTableQuery) error { return nil }

var _ bun.BeforeDropTableHook = (*Model)(nil)

func (*Model) BeforeDropTable(ctx context.Context, query *DropTableQuery) error { return nil }

var _ bun.AfterDropTableHook = (*Model)(nil)

func (*Model) AfterDropTable(ctx context.Context, query *DropTableQuery) error { return nil }

Query hooks

Bun supports query hooks which are called before and after executing a query. Bun uses query hooks for logging queries and for performance monitoring.

type QueryHook struct{}

func (h *QueryHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context {
	return ctx
}

func (h *QueryHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) {
	fmt.Println(time.Since(event.StartTime), string(event.Query))
}

db.AddQueryHook(&QueryHook{})