Skip to content
jiangz222 edited this page Sep 18, 2021 · 18 revisions

About Hooks:

Generally, User use Hook will implement the callback method through the operated document. But many operations of Qmgo don't have documents as parameter(like Update method), which we think it's a very concise way.

So the Hook v1 of Qmgo will be different from the "general" Hook implementation:

  • The user implements the struct method of Hook, and passes it through options of a specific operation API, then Qmgo automatically do callback.

  • Only InsertOneInsertManyUpsert and ReplaceOne can do hook without options

  • If the Hook operation fails, there is no rollback operation in current version.

  • The hook before and after a certain operation takes effect independently, for example, you only need to implement BeforeInsert() error to make hook before insert work.

Insert Hook

User need to implement struct methods to use Insert Hook

BeforeInsert(ctx context.Context) error // implement it for hook before insert
AfterInsert(ctx context.Context) error  // implement it for hook after insert

InsertOne

In the InsertOne process

  • Implement the Hook method of Insert through a custom struct (User in the following example)

  • Pass in options.InsertOneOptions to make Hook work

  • If the second parameter doc implement the Hook method, options.InsertOneOptions can be ignore in InsertOne.

  • If you use the document User to implement the method and modify the document in BeforeInsert(), the modified document will be inserted into the database

type User struct {
	Name         string    `bson:"name"`
	Age          int       `bson:"age"`
}
func (u *User) BeforeInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}
func (u *User) AfterInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}

u := &User{Name: "Alice", Age: 7}

_, err := cli.InsertOne(context.Background(), u)

// following example also works
// _, err := cli.InsertOne(context.Background(), u, options.InsertOneOptions{
//  InsertHook: u,
// })

InsertMany

In the InsertMany process

  • Implement the Hook method of Insert through a custom structure (User in the following example),

  • Pass in options.InsertManyOptions to make Hook work.

  • If the second parameter docs implement the Hook method, options.InsertManyOptions can be ignore in InsertMany.

  • If you use the document User to implement method and modify the document in BeforeInsert(), the modified document will be inserted into the database

  • Because multiple documents are inserted, and Hook happens for each document, so Hook will be called back multiple times according to the number of inserted documents

type User struct {
	Name         string    `bson:"name"`
	Age          int       `bson:"age"`
}
func (u *User) BeforeInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}
func (u *User) AfterInsert(ctx context.Context) error {
  fmt.Println("before insert called")
	return nil
}

u1 := &User{Name: "Lucas", Age: 7}
u2 := &User{Name: "Alice", Age: 7}
us := []*User{u1, u2}

_, err := cli.InsertMany(ctx, us)

// following example also works
// _, err := cli.InsertMany(ctx, us, options.InsertManyOptions{
//  InsertHook: us,
// })

Update Hook

User need to implement struct methods to use Update Hook

BeforeUpdate(ctx context.Context) error // implement it for hook before update
AfterUpdate(ctx context.Context) error // implement it for hook after update

UpdateOne/UpdateAll

  • Implement the Hook method of Update through a custom struct (MyUpdateHook in the following example)

  • Pass in options.UpdateOptions to make Hook work.

  • If you use the document User to directly implement methods and modify the document in the BeforeUpdate(), will not affect the document written in the database.

type MyUpdateHook struct {
	beforeUpdateCount int
	afterUpdateCount  int
}
func (u *MyUpdateHook) BeforeUpdate(ctx context.Context) error {
	u.beforeUpdateCount++
	return nil
}
func (u *MyUpdateHook) AfterUpdate(ctx context.Context) error {
	u.afterUpdateCount++
	return nil
}

u := User{Name: "Lucas", Age: 7}
uh := &MyUpdateHook{}
_, err := cli.InsertOne(context.Background(), u)
ast.NoError(err)

err = cli.UpdateOne(ctx, bson.M{"name": "Lucas"}, bson.M{operator.Set: bson.M{"age": 27}}, options.UpdateOptions{
  UpdateHook: uh,
})

cli.UpdateAll(ctx, bson.M{"name": "Lucas"}, bson.M{operator.Set: bson.M{"age": 27}}, options.UpdateOptions{
  UpdateHook: uh,
})

ReplaceOne

  • Implement the Hook method of Update through a custom struct (User in the following example)

  • Pass in options.UpdateOptions to make Hook work.

  • If the third parameter doc implement the Hook method, options.UpdateOptions can be ignore in ReplaceOne.

  • If you use the document User to directly implement methods and modify the document in the BeforeUpdate(), the modified document will be updated into the database.

type User struct {
	Name string `bson:"name"`
	Age  int    `bson:"age"`

	beforeUpdate int
	afterUpdate  int
}

func (u *User) BeforeUpdate(ctx context.Context) error {
	u.beforeUpdate++
	return nil
}

func (u *User) AfterUpdate(ctx context.Context) error {
	u.afterUpdate++
	return nil
}

u := &User{}
err = cli.ReplaceOne(ctx, bson.M{"name": "Lucas"}, &u)

// following example also works
// err = cli.ReplaceOne(ctx, bson.M{"name": "Lucas"}, &u, options.UpdateOptions{
//		UpdateHook: u,
// })

Upsert Hook

User need to implement struct methods to use Upsert Hook

BeforeUpsert(ctx context.Context) error // implement it for hook before upsert
AfterUpsert(ctx context.Context) error // implement it for hook before upsert

In the Upsert API

  • Implement the Hook method of Upsert through a custom struct (User in the following example)

  • Pass in options.UpsertOptions to make Hook work

  • If the second parameter doc implement the Hook method, options.UpsertOptions can be ignore in Upsert.

  • If you use the document User to implement the method and modify the document in BeforeUpsert(), the modified document will be inserted into the database

type User struct {
	Name         string    `bson:"name"`
	Age          int       `bson:"age"`
}
func (u *User) BeforeUpsert(ctx context.Context) error {
	return nil
}

func (u *User) AfterUpsert(ctx context.Context) error {
	return nil
}

u := &User{Name: "Alice", Age: 7}
_, err = cli.Upsert(context.Background(), bson.M{"name": "Lucas"}, u)

// following example also works
// _, err = cli.Upsert(context.Background(), bson.M{"name": "Lucas"}, u, options.UpsertOptions{
//	UpsertHook: myHook,
//})

Remove Hook

User need to implement struct methods to use Remove Hook

BeforeRemove(ctx context.Context) error // implement it for hook before remove
AfterRemove(ctx context.Context) error // implement it for hook before remove

Remove/RemoveAll

  • A custom structure (MyRemoveHook in the following example) implements the Hook method of Remove

  • Pass in options.RemoveOptions to make Hook work.

type MyRemoveHook struct {
	beforeCount int
	afterCount  int
}

func (m *MyRemoveHook) BeforeRemove(ctx context.Context) error {
	m.beforeCount++
	return nil
}

func (m *MyRemoveHook) AfterRemove(ctx context.Context) error {
	m.afterCount++
	return nil
}

rh := &MyRemoveHook{}
err = cli.Remove(ctx, bson.M{"age": 17}, options.RemoveOptions{
  RemoveHook: rh,
})

rh = &MyRemoveHook{}
_, err = cli.RemoveAll(ctx, bson.M{"age": "7"}, options.RemoveOptions{
  RemoveHook: rh,
})

QueryHook

User need to implement struct methods to use Query Hook

BeforeQuery(ctx context.Context) error // implement it for hook before query
AfterQuery(ctx context.Context) error // implement it for hook before query

Find().One/Find().All()

  • A custom struct (MyQueryHook in the following example) implements the Hook method of Query
  • Pass in options.FindOptions to make Hook work.
type MyQueryHook struct {
	beforeCount int
	afterCount  int
}

func (q *MyQueryHook) BeforeQuery(ctx context.Context) error {
	q.beforeCount++
	return nil
}

func (q *MyQueryHook) AfterQuery(ctx context.Context) error {
	q.afterCount++
	return nil
}

qk := &MyQueryHook{}
err = cli.Find(ctx, bson.M{"age": 17}, options.FindOptions{
  QueryHook: qk,
}).One(ur)

err = cli.Find(ctx, bson.M{"age": 17}, options.FindOptions{
  QueryHook: qh,
}).All(&ur)