From 7a57e38228985a147824b9a642aeb49930a6a3af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Garcia?= Date: Tue, 22 Feb 2022 16:14:15 -0300 Subject: [PATCH] Deprecate Update() in favor of a better fn name: Patch() --- Makefile | 1 + README.md | 12 ++--- contracts.go | 5 +- examples/crud/crud.go | 8 ++-- examples/example_service/example_service.go | 2 +- .../example_service/example_service_test.go | 4 +- examples/example_service/mocks.go | 14 ++++++ ksql.go | 14 ++++++ mocks.go | 36 ++++++++++---- mocks_test.go | 47 +++++++++++++++++++ 10 files changed, 119 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 49e315e..499a99a 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ lint: setup @make --no-print-directory -C benchmarks @echo "Golint & Go Vet found no problems on your code!" +gen: mock mock: setup $(GOBIN)/mockgen -package=exampleservice -source=contracts.go -destination=examples/example_service/mocks.go diff --git a/README.md b/README.md index 1ffd4b2..83d6b71 100644 --- a/README.md +++ b/README.md @@ -166,11 +166,11 @@ it with as little functions as possible, so don't expect many additions: ```go // Provider describes the ksql public behavior // -// The Insert, Update, Delete and QueryOne functions return ksql.ErrRecordNotFound +// The Insert, Patch, Delete and QueryOne functions return ksql.ErrRecordNotFound // if no record was found or no rows were changed during the operation. type Provider interface { Insert(ctx context.Context, table Table, record interface{}) error - Update(ctx context.Context, table Table, record interface{}) error + Patch(ctx context.Context, table Table, record interface{}) error Delete(ctx context.Context, table Table, idOrRecord interface{}) error Query(ctx context.Context, records interface{}, query string, params ...interface{}) error @@ -301,12 +301,12 @@ func main() { // Updating all fields from Cristina: cris.Name = "Cris" - err = db.Update(ctx, UsersTable, cris) + err = db.Patch(ctx, UsersTable, cris) // Changing the age of Cristina but not touching any other fields: // Partial update technique 1: - err = db.Update(ctx, UsersTable, struct { + err = db.Patch(ctx, UsersTable, struct { ID int `ksql:"id"` Age int `ksql:"age"` }{ID: cris.ID, Age: 28}) @@ -315,7 +315,7 @@ func main() { } // Partial update technique 2: - err = db.Update(ctx, UsersTable, PartialUpdateUser{ + err = db.Patch(ctx, UsersTable, PartialUpdateUser{ ID: cris.ID, Age: nullable.Int(28), }) @@ -347,7 +347,7 @@ func main() { return err } - err = db.Update(ctx, UsersTable, PartialUpdateUser{ + err = db.Patch(ctx, UsersTable, PartialUpdateUser{ ID: cris2.ID, Age: nullable.Int(29), }) diff --git a/contracts.go b/contracts.go index 599b4c6..eeddd62 100644 --- a/contracts.go +++ b/contracts.go @@ -20,9 +20,12 @@ var ErrAbortIteration error = fmt.Errorf("ksql: abort iteration, should only be // if no record was found or no rows were changed during the operation. type Provider interface { Insert(ctx context.Context, table Table, record interface{}) error - Update(ctx context.Context, table Table, record interface{}) error + Patch(ctx context.Context, table Table, record interface{}) error Delete(ctx context.Context, table Table, idOrRecord interface{}) error + // Deprecated: use the Patch() method instead. + Update(ctx context.Context, table Table, record interface{}) error + Query(ctx context.Context, records interface{}, query string, params ...interface{}) error QueryOne(ctx context.Context, record interface{}, query string, params ...interface{}) error QueryChunks(ctx context.Context, parser ChunkParser) error diff --git a/examples/crud/crud.go b/examples/crud/crud.go index 1cb312c..cf018fa 100644 --- a/examples/crud/crud.go +++ b/examples/crud/crud.go @@ -111,12 +111,12 @@ func main() { // Updating all fields from Cristina: cris.Name = "Cris" - err = db.Update(ctx, UsersTable, cris) + err = db.Patch(ctx, UsersTable, cris) // Changing the age of Cristina but not touching any other fields: // Partial update technique 1: - err = db.Update(ctx, UsersTable, struct { + err = db.Patch(ctx, UsersTable, struct { ID int `ksql:"id"` Age int `ksql:"age"` }{ID: cris.ID, Age: 28}) @@ -125,7 +125,7 @@ func main() { } // Partial update technique 2: - err = db.Update(ctx, UsersTable, PartialUpdateUser{ + err = db.Patch(ctx, UsersTable, PartialUpdateUser{ ID: cris.ID, Age: nullable.Int(28), }) @@ -157,7 +157,7 @@ func main() { return err } - err = db.Update(ctx, UsersTable, PartialUpdateUser{ + err = db.Patch(ctx, UsersTable, PartialUpdateUser{ ID: cris2.ID, Age: nullable.Int(29), }) diff --git a/examples/example_service/example_service.go b/examples/example_service/example_service.go index bbee499..0aa5ae5 100644 --- a/examples/example_service/example_service.go +++ b/examples/example_service/example_service.go @@ -68,7 +68,7 @@ func (s Service) UpdateUserScore(ctx context.Context, uID int, scoreChange int) return err } - return s.db.Update(ctx, UsersTable, &UserEntity{ + return s.db.Patch(ctx, UsersTable, &UserEntity{ ID: uID, Score: nullable.Int(scoreRow.Score + scoreChange), }) diff --git a/examples/example_service/example_service_test.go b/examples/example_service/example_service_test.go index aa8c7f5..1cddac4 100644 --- a/examples/example_service/example_service_test.go +++ b/examples/example_service/example_service_test.go @@ -78,7 +78,7 @@ func TestCreateUser(t *testing.T) { } func TestUpdateUserScore(t *testing.T) { - t.Run("should call ksql.QueryOne() & Update() correctly", func(t *testing.T) { + t.Run("should call ksql.QueryOne() & Patch() correctly", func(t *testing.T) { controller := gomock.NewController(t) defer controller.Finish() @@ -102,7 +102,7 @@ func TestUpdateUserScore(t *testing.T) { "score": 42, }) }), - mockDB.EXPECT().Update(gomock.Any(), gomock.Any(), gomock.Any()). + mockDB.EXPECT().Patch(gomock.Any(), gomock.Any(), gomock.Any()). DoAndReturn(func(ctx context.Context, table ksql.Table, records ...interface{}) error { users = append(users, records...) return nil diff --git a/examples/example_service/mocks.go b/examples/example_service/mocks.go index 00138e4..308570b 100644 --- a/examples/example_service/mocks.go +++ b/examples/example_service/mocks.go @@ -83,6 +83,20 @@ func (mr *MockProviderMockRecorder) Insert(ctx, table, record interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockProvider)(nil).Insert), ctx, table, record) } +// Patch mocks base method. +func (m *MockProvider) Patch(ctx context.Context, table ksql.Table, record interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Patch", ctx, table, record) + ret0, _ := ret[0].(error) + return ret0 +} + +// Patch indicates an expected call of Patch. +func (mr *MockProviderMockRecorder) Patch(ctx, table, record interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Patch", reflect.TypeOf((*MockProvider)(nil).Patch), ctx, table, record) +} + // Query mocks base method. func (m *MockProvider) Query(ctx context.Context, records interface{}, query string, params ...interface{}) error { m.ctrl.T.Helper() diff --git a/ksql.go b/ksql.go index 1c7fed0..b40c098 100644 --- a/ksql.go +++ b/ksql.go @@ -626,10 +626,24 @@ func normalizeIDsAsMap(idNames []string, idOrMap interface{}) (idMap map[string] // Update updates the given instances on the database by id. // // Partial updates are supported, i.e. it will ignore nil pointer attributes +// +// Deprecated: Use the Patch method instead func (c DB) Update( ctx context.Context, table Table, record interface{}, +) error { + return c.Patch(ctx, table, record) +} + +// Patch applies a partial update (explained below) to the given instance on the database by id. +// +// Partial updates will ignore any nil pointer attributes from the struct, updating only +// the non nil pointers and non pointer attributes. +func (c DB) Patch( + ctx context.Context, + table Table, + record interface{}, ) error { v := reflect.ValueOf(record) t := v.Type() diff --git a/mocks.go b/mocks.go index 54cfa19..998891f 100644 --- a/mocks.go +++ b/mocks.go @@ -50,9 +50,11 @@ var _ Provider = Mock{} // type Mock struct { InsertFn func(ctx context.Context, table Table, record interface{}) error - UpdateFn func(ctx context.Context, table Table, record interface{}) error + PatchFn func(ctx context.Context, table Table, record interface{}) error DeleteFn func(ctx context.Context, table Table, idOrRecord interface{}) error + UpdateFn func(ctx context.Context, table Table, record interface{}) error + QueryFn func(ctx context.Context, records interface{}, query string, params ...interface{}) error QueryOneFn func(ctx context.Context, record interface{}, query string, params ...interface{}) error QueryChunksFn func(ctx context.Context, parser ChunkParser) error @@ -91,13 +93,17 @@ func (m Mock) SetFallbackDatabase(db Provider) Mock { if m.InsertFn == nil { m.InsertFn = db.Insert } - if m.UpdateFn == nil { - m.UpdateFn = db.Update + if m.PatchFn == nil { + m.PatchFn = db.Patch } if m.DeleteFn == nil { m.DeleteFn = db.Delete } + if m.UpdateFn == nil { + m.UpdateFn = db.Update + } + if m.QueryFn == nil { m.QueryFn = db.Query } @@ -128,14 +134,14 @@ func (m Mock) Insert(ctx context.Context, table Table, record interface{}) error return m.InsertFn(ctx, table, record) } -// Update mocks the behavior of the Update method. -// If UpdateFn is set it will just call it returning the same return values. -// If UpdateFn is unset it will panic with an appropriate error message. -func (m Mock) Update(ctx context.Context, table Table, record interface{}) error { - if m.UpdateFn == nil { - panic(fmt.Errorf("ksql.Mock.Update(ctx, %v, %v) called but the ksql.Mock.UpdateFn() is not set", table, record)) +// Patch mocks the behavior of the Patch method. +// If PatchFn is set it will just call it returning the same return values. +// If PatchFn is unset it will panic with an appropriate error message. +func (m Mock) Patch(ctx context.Context, table Table, record interface{}) error { + if m.PatchFn == nil { + panic(fmt.Errorf("ksql.Mock.Patch(ctx, %v, %v) called but the ksql.Mock.PatchFn() is not set", table, record)) } - return m.UpdateFn(ctx, table, record) + return m.PatchFn(ctx, table, record) } // Delete mocks the behavior of the Delete method. @@ -148,6 +154,16 @@ func (m Mock) Delete(ctx context.Context, table Table, idOrRecord interface{}) e return m.DeleteFn(ctx, table, idOrRecord) } +// Update mocks the behavior of the Update method. +// If UpdateFn is set it will just call it returning the same return values. +// If UpdateFn is unset it will panic with an appropriate error message. +func (m Mock) Update(ctx context.Context, table Table, record interface{}) error { + if m.UpdateFn == nil { + panic(fmt.Errorf("ksql.Mock.Update(ctx, %v, %v) called but the ksql.Mock.UpdateFn() is not set", table, record)) + } + return m.UpdateFn(ctx, table, record) +} + // Query mocks the behavior of the Query method. // If QueryFn is set it will just call it returning the same return values. // If QueryFn is unset it will panic with an appropriate error message. diff --git a/mocks_test.go b/mocks_test.go index 0488d3c..b50a5c5 100644 --- a/mocks_test.go +++ b/mocks_test.go @@ -34,6 +34,22 @@ func TestMock(t *testing.T) { tt.AssertErrContains(t, err, "ksql.Mock.Insert(", "ksql.Mock.InsertFn", "not set") }) + t.Run("Patch should panic", func(t *testing.T) { + ctx := context.Background() + mock := ksql.Mock{} + panicPayload := tt.PanicHandler(func() { + mock.Patch(ctx, UsersTable, &User{ + ID: 4242, + Name: "fake-name", + Age: 42, + }) + }) + + err, ok := panicPayload.(error) + tt.AssertEqual(t, ok, true) + tt.AssertErrContains(t, err, "ksql.Mock.Patch(", "ksql.Mock.PatchFn", "not set") + }) + t.Run("Update should panic", func(t *testing.T) { ctx := context.Background() mock := ksql.Mock{} @@ -174,6 +190,37 @@ func TestMock(t *testing.T) { }) }) + t.Run("Patch", func(t *testing.T) { + ctx := context.Background() + var capturedArgs struct { + ctx context.Context + table ksql.Table + record interface{} + } + mock := ksql.Mock{ + PatchFn: func(ctx context.Context, table ksql.Table, record interface{}) error { + capturedArgs.ctx = ctx + capturedArgs.table = table + capturedArgs.record = record + return fmt.Errorf("fake-error") + }, + } + err := mock.Patch(ctx, UsersTable, &User{ + ID: 4242, + Name: "fake-name", + Age: 42, + }) + + tt.AssertErrContains(t, err, "fake-error") + tt.AssertEqual(t, capturedArgs.ctx, ctx) + tt.AssertEqual(t, capturedArgs.table, UsersTable) + tt.AssertEqual(t, capturedArgs.record, &User{ + ID: 4242, + Name: "fake-name", + Age: 42, + }) + }) + t.Run("Update", func(t *testing.T) { ctx := context.Background() var capturedArgs struct {