From bca2507ad1bcdb22d43ec098a7001d7f86af696a Mon Sep 17 00:00:00 2001 From: Matt Gode Date: Fri, 6 Apr 2018 14:04:11 -0700 Subject: [PATCH 01/12] Add implementation for multi upsert in the dosa client --- client.go | 79 +++++++++++++++++++++++++++++++++++++++++++------- client_test.go | 68 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 125 insertions(+), 22 deletions(-) diff --git a/client.go b/client.go index 2965cc5a..cdf59bdc 100644 --- a/client.go +++ b/client.go @@ -138,10 +138,12 @@ type Client interface { // to update in fieldsToUpdate (or all the fields if you use dosa.All()) Upsert(ctx context.Context, fieldsToUpdate []string, objectToUpdate DomainObject) error - // TODO: Coming in v2.1 // MultiUpsert creates or updates multiple rows. A list of fields to - // update can be specified. Use All() or nil for all fields. - // MultiUpsert(context.Context, []string, ...DomainObject) (MultiResult, error) + // update can be specified. Use All() or nil for all fields. Partial + // successes are possible, so it is critical to inspect the MultiResult response + // to check for failures. + // NOTE: This API only upserts objects of same entity type from same scope. + MultiUpsert(context.Context, []string, ...DomainObject) (MultiResult, error) // Remove removes a row by primary key. The passed-in entity should contain // the primary key field values, all other fields are ignored. @@ -151,7 +153,7 @@ type Client interface { // given RemoveRangeOp. RemoveRange(ctx context.Context, removeRangeOp *RemoveRangeOp) error - // TODO: Coming in v2.1 + // TODO: Coming in v2.2 // MultiRemove removes multiple rows by primary key. The passed-in entity should // contain the primary key field values. // MultiRemove(context.Context, ...DomainObject) (MultiResult, error) @@ -410,12 +412,69 @@ func (c *client) createOrUpsert(ctx context.Context, fieldsToUpdate []string, en return fn(ctx, re.EntityInfo(), fieldValues) } -// MultiUpsert updates several entities by primary key, The entities provided -// must contain values for all components of its primary key for the operation -// to succeed. If `fieldsToUpdate` is provided, only a subset of fields will be -// updated. -func (c *client) MultiUpsert(context.Context, []string, ...DomainObject) (MultiResult, error) { - panic("not implemented") +// MultiUpsert updates several entities of the same type by primary key, The +// entities provided must contain values for all components of its primary key +// for the operation to succeed. If `fieldsToUpdate` is provided, only a subset +// of fields will be updated. Moreover, all entities being upserted must be part +// of the same partition otherwise the request will be rejected. +func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entities ...DomainObject) (MultiResult, error) { + if !c.initialized { + return nil, &ErrNotInitialized{} + } + + if len(entities) == 0 { + return nil, fmt.Errorf("the number of entities to upsert is zero") + } + + // lookup registered entity, registry will return error if registration + // is not found + var re *RegisteredEntity + var listMultiValues []map[string]FieldValue + for _, entity := range entities { + ere, err := c.registrar.Find(entity) + if err != nil { + return nil, err + } + + if re == nil { + re = ere + } else if re != ere { + return nil, fmt.Errorf("inconsistent entity type for multi upsert: %v vs %v", re, ere) + } + + // translate entity field values to a map of primary key name/values pairs + keyFieldValues := re.KeyFieldValues(entity) + + // translate remaining entity fields values to map of column name/value pairs + fieldValues, err := re.OnlyFieldValues(entity, fieldsToUpdate) + if err != nil { + return nil, err + } + + // merge key and remaining values + for k, v := range keyFieldValues { + fieldValues[k] = v + } + + // translate entity field values to a map of primary key name/values pairs + // required to perform a read + listMultiValues = append(listMultiValues, fieldValues) + } + + results, err := c.connector.MultiUpsert(ctx, re.EntityInfo(), listMultiValues) + if err != nil { + return nil, err + } + + multiResult := MultiResult{} + // map results to entity fields + for i, entity := range entities { + if results[i] != nil { + multiResult[entity] = results[i] + } + } + + return multiResult, nil } // Remove deletes an entity by primary key, The entity provided must contain diff --git a/client_test.go b/client_test.go index cf1e12e8..f4a196df 100644 --- a/client_test.go +++ b/client_test.go @@ -726,11 +726,12 @@ func TestClient_MultiRead(t *testing.T) { // uninitialized c1 := dosaRenamed.NewClient(reg1, nullConnector) - assert.Error(t, c1.Read(ctx, fieldsToRead, cte1)) + _, err := c1.MultiRead(ctx, fieldsToRead, cte1) + assert.Error(t, err) // unregistered object c1.Initialize(ctx) - _, err := c1.MultiRead(ctx, dosaRenamed.All(), cte2) + _, err = c1.MultiRead(ctx, dosaRenamed.All(), cte2) assert.Error(t, err) assert.Contains(t, err.Error(), "ClientTestEntity2") @@ -762,19 +763,62 @@ func TestClient_MultiRead(t *testing.T) { assert.Equal(t, rs[e2].Error(), "not fonud") } -/* TODO: Coming in v2.1 -func TestClient_Unimplemented(t *testing.T) { +func TestClient_MultiUpsert(t *testing.T) { reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) + reg2, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1, cte2) + fieldsToUpsert := []string{"ID", "Email"} + e1 := &ClientTestEntity1{ID: int64(1)} + e2 := &ClientTestEntity1{ID: int64(2)} + results := []*dosaRenamed.FieldValuesOrError{ + { + Values: map[string]dosaRenamed.FieldValue{ + "id": testutil.TestInt64Ptr(int64(1)), + "name": testutil.TestStringPtr("xxx"), + "email": testutil.TestStringPtr("xxx@gmail.com"), + }, + }, + { + Error: errors.New("not fonud"), + }, + } - c := dosaRenamed.NewClient(reg1, nullConnector) - assert.Panics(t, func() { - c.MultiUpsert(ctx, dosaRenamed.All(), &ClientTestEntity1{}) - }) - assert.Panics(t, func() { - c.MultiRemove(ctx, &ClientTestEntity1{}) - }) + // uninitialized + c1 := dosaRenamed.NewClient(reg1, nullConnector) + _, err := c1.MultiUpsert(ctx, dosaRenamed.All(), cte1) + assert.Error(t, err) + + // unregistered object + c1.Initialize(ctx) + _, err = c1.MultiUpsert(ctx, dosaRenamed.All(), cte2) + assert.Error(t, err) + assert.Contains(t, err.Error(), "ClientTestEntity2") + + // multi read different types of object + c1.Initialize(ctx) + _, err = c1.MultiUpsert(ctx, dosaRenamed.All(), cte2, cte1) + assert.Error(t, err) + assert.Contains(t, err.Error(), "ClientTestEntity2") + + // happy path, mock connector + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockConn := mocks.NewMockConnector(ctrl) + mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() + mockConn.EXPECT().MultiUpsert(ctx, gomock.Any(), gomock.Any()). + Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, allValues []map[string]dosaRenamed.FieldValue) { + assert.Equal(t, allValues[0]["id"], e1.ID) + assert.Equal(t, allValues[1]["id"], e2.ID) + + }).Return(results, nil).MinTimes(1) + c3 := dosaRenamed.NewClient(reg2, mockConn) + assert.NoError(t, c3.Initialize(ctx)) + rs, err := c3.MultiUpsert(ctx, fieldsToUpsert, e1, e2) + assert.NoError(t, err) + assert.Equal(t, int64(1), e1.ID) + assert.Empty(t, e1.Name) + assert.Equal(t, "xxx@gmail.com", e1.Email) + assert.Equal(t, rs[e2].Error(), "not fonud") } -*/ func TestAdminClient_CreateScope(t *testing.T) { c := dosaRenamed.NewAdminClient(nullConnector) From 758104606cdafcc39b8315edf594474fef290b2e Mon Sep 17 00:00:00 2001 From: Matt Gode Date: Fri, 6 Apr 2018 18:34:18 -0700 Subject: [PATCH 02/12] Improve test coverage of multi upsert --- client.go | 2 +- client_test.go | 121 ++++++++++++++++++----------------- mocks/client.go | 163 +++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 207 insertions(+), 79 deletions(-) diff --git a/client.go b/client.go index cdf59bdc..b26adc84 100644 --- a/client.go +++ b/client.go @@ -153,7 +153,7 @@ type Client interface { // given RemoveRangeOp. RemoveRange(ctx context.Context, removeRangeOp *RemoveRangeOp) error - // TODO: Coming in v2.2 + // TODO: Coming in v2.7 // MultiRemove removes multiple rows by primary key. The passed-in entity should // contain the primary key field values. // MultiRemove(context.Context, ...DomainObject) (MultiResult, error) diff --git a/client_test.go b/client_test.go index f4a196df..ca40dbb2 100644 --- a/client_test.go +++ b/client_test.go @@ -400,6 +400,70 @@ func TestClient_Upsert(t *testing.T) { assert.NoError(t, c3.Upsert(ctx, fieldsToUpdate, cte1)) assert.Equal(t, cte1.Email, updatedEmail) } + +func TestClient_MultiUpsert(t *testing.T) { + reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) + reg2, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1, cte2) + fieldsToUpsert := []string{"Email"} + + e1 := &ClientTestEntity1{ID: int64(1), Email: "bar@email.com"} + e2 := &ClientTestEntity1{ID: int64(2), Email: "foo@email.com"} + + // uninitialized + c1 := dosaRenamed.NewClient(reg1, nullConnector) + _, err := c1.MultiUpsert(ctx, dosaRenamed.All(), cte1) + assert.Error(t, err) + + // empty upsert + c1.Initialize(ctx) + _, err = c1.MultiUpsert(ctx, dosaRenamed.All()) + assert.Error(t, err) + assert.Contains(t, err.Error(), "zero") + + // unregistered object + c1.Initialize(ctx) + _, err = c1.MultiUpsert(ctx, dosaRenamed.All(), cte2) + assert.Error(t, err) + assert.Contains(t, err.Error(), "ClientTestEntity2") + + // multi read different types of object + c1.Initialize(ctx) + _, err = c1.MultiUpsert(ctx, dosaRenamed.All(), cte2, cte1) + assert.Error(t, err) + assert.Contains(t, err.Error(), "ClientTestEntity2") + + // happy path, mock connector + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockConn := mocks.NewMockConnector(ctrl) + mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() + mockConn.EXPECT().MultiUpsert(ctx, gomock.Any(), gomock.Any()). + Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, allValues []map[string]dosaRenamed.FieldValue) { + assert.Equal(t, allValues[0]["id"], e1.ID) + assert.Equal(t, allValues[0]["email"], e1.Email) + assert.Equal(t, allValues[1]["id"], e2.ID) + assert.Equal(t, allValues[1]["email"], e2.Email) + + }).Return([]error{nil, nil}, nil).Times(1) + c2 := dosaRenamed.NewClient(reg2, mockConn) + assert.NoError(t, c2.Initialize(ctx)) + rs, err := c2.MultiUpsert(ctx, fieldsToUpsert, e1, e2) + assert.NoError(t, err) + assert.Empty(t, rs) + + // system error, mock connector + mockConn.EXPECT().MultiUpsert(ctx, gomock.Any(), gomock.Any()).Return([]error{nil, nil}, errors.New("connector error")) + rs, err = c2.MultiUpsert(ctx, fieldsToUpsert, e1, e2) + assert.Error(t, err) + assert.Empty(t, rs) + + // single entity error, mock connector + mockConn.EXPECT().MultiUpsert(ctx, gomock.Any(), gomock.Any()).Return([]error{errors.New("single error"), nil}, nil) + rs, err = c2.MultiUpsert(ctx, fieldsToUpsert, e1, e2) + assert.NoError(t, err) + assert.NotNil(t, rs[e1]) +} + func TestClient_CreateIfNotExists(t *testing.T) { reg1, _ := dosaRenamed.NewRegistrar("test", "team.service", cte1) reg2, _ := dosaRenamed.NewRegistrar("test", "team.service", cte1, cte2) @@ -763,63 +827,6 @@ func TestClient_MultiRead(t *testing.T) { assert.Equal(t, rs[e2].Error(), "not fonud") } -func TestClient_MultiUpsert(t *testing.T) { - reg1, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1) - reg2, _ := dosaRenamed.NewRegistrar(scope, namePrefix, cte1, cte2) - fieldsToUpsert := []string{"ID", "Email"} - e1 := &ClientTestEntity1{ID: int64(1)} - e2 := &ClientTestEntity1{ID: int64(2)} - results := []*dosaRenamed.FieldValuesOrError{ - { - Values: map[string]dosaRenamed.FieldValue{ - "id": testutil.TestInt64Ptr(int64(1)), - "name": testutil.TestStringPtr("xxx"), - "email": testutil.TestStringPtr("xxx@gmail.com"), - }, - }, - { - Error: errors.New("not fonud"), - }, - } - - // uninitialized - c1 := dosaRenamed.NewClient(reg1, nullConnector) - _, err := c1.MultiUpsert(ctx, dosaRenamed.All(), cte1) - assert.Error(t, err) - - // unregistered object - c1.Initialize(ctx) - _, err = c1.MultiUpsert(ctx, dosaRenamed.All(), cte2) - assert.Error(t, err) - assert.Contains(t, err.Error(), "ClientTestEntity2") - - // multi read different types of object - c1.Initialize(ctx) - _, err = c1.MultiUpsert(ctx, dosaRenamed.All(), cte2, cte1) - assert.Error(t, err) - assert.Contains(t, err.Error(), "ClientTestEntity2") - - // happy path, mock connector - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockConn := mocks.NewMockConnector(ctrl) - mockConn.EXPECT().CheckSchema(ctx, gomock.Any(), gomock.Any(), gomock.Any()).Return(int32(1), nil).AnyTimes() - mockConn.EXPECT().MultiUpsert(ctx, gomock.Any(), gomock.Any()). - Do(func(_ context.Context, _ *dosaRenamed.EntityInfo, allValues []map[string]dosaRenamed.FieldValue) { - assert.Equal(t, allValues[0]["id"], e1.ID) - assert.Equal(t, allValues[1]["id"], e2.ID) - - }).Return(results, nil).MinTimes(1) - c3 := dosaRenamed.NewClient(reg2, mockConn) - assert.NoError(t, c3.Initialize(ctx)) - rs, err := c3.MultiUpsert(ctx, fieldsToUpsert, e1, e2) - assert.NoError(t, err) - assert.Equal(t, int64(1), e1.ID) - assert.Empty(t, e1.Name) - assert.Equal(t, "xxx@gmail.com", e1.Email) - assert.Equal(t, rs[e2].Error(), "not fonud") -} - func TestAdminClient_CreateScope(t *testing.T) { c := dosaRenamed.NewAdminClient(nullConnector) assert.NotNil(t, c) diff --git a/mocks/client.go b/mocks/client.go index d4e1f5da..8b8e5645 100644 --- a/mocks/client.go +++ b/mocks/client.go @@ -1,25 +1,5 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - // Automatically generated by MockGen. DO NOT EDIT! -// Source: github.com/uber-go/dosa (interfaces: Client) +// Source: github.com/uber-go/dosa (interfaces: Client,AdminClient) package mocks @@ -97,6 +77,22 @@ func (_mr *_MockClientRecorder) MultiRead(arg0, arg1 interface{}, arg2 ...interf return _mr.mock.ctrl.RecordCall(_mr.mock, "MultiRead", _s...) } +func (_m *MockClient) MultiUpsert(_param0 context.Context, _param1 []string, _param2 ...dosa.DomainObject) (dosa.MultiResult, error) { + _s := []interface{}{_param0, _param1} + for _, _x := range _param2 { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "MultiUpsert", _s...) + ret0, _ := ret[0].(dosa.MultiResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) MultiUpsert(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "MultiUpsert", _s...) +} + func (_m *MockClient) Range(_param0 context.Context, _param1 *dosa.RangeOp) ([]dosa.DomainObject, string, error) { ret := _m.ctrl.Call(_m, "Range", _param0, _param1) ret0, _ := ret[0].([]dosa.DomainObject) @@ -170,3 +166,128 @@ func (_m *MockClient) WalkRange(_param0 context.Context, _param1 *dosa.RangeOp, func (_mr *_MockClientRecorder) WalkRange(arg0, arg1, arg2 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "WalkRange", arg0, arg1, arg2) } + +// Mock of AdminClient interface +type MockAdminClient struct { + ctrl *gomock.Controller + recorder *_MockAdminClientRecorder +} + +// Recorder for MockAdminClient (not exported) +type _MockAdminClientRecorder struct { + mock *MockAdminClient +} + +func NewMockAdminClient(ctrl *gomock.Controller) *MockAdminClient { + mock := &MockAdminClient{ctrl: ctrl} + mock.recorder = &_MockAdminClientRecorder{mock} + return mock +} + +func (_m *MockAdminClient) EXPECT() *_MockAdminClientRecorder { + return _m.recorder +} + +func (_m *MockAdminClient) CanUpsertSchema(_param0 context.Context, _param1 string) (*dosa.SchemaStatus, error) { + ret := _m.ctrl.Call(_m, "CanUpsertSchema", _param0, _param1) + ret0, _ := ret[0].(*dosa.SchemaStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockAdminClientRecorder) CanUpsertSchema(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "CanUpsertSchema", arg0, arg1) +} + +func (_m *MockAdminClient) CheckSchemaStatus(_param0 context.Context, _param1 string, _param2 int32) (*dosa.SchemaStatus, error) { + ret := _m.ctrl.Call(_m, "CheckSchemaStatus", _param0, _param1, _param2) + ret0, _ := ret[0].(*dosa.SchemaStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockAdminClientRecorder) CheckSchemaStatus(arg0, arg1, arg2 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "CheckSchemaStatus", arg0, arg1, arg2) +} + +func (_m *MockAdminClient) CreateScope(_param0 context.Context, _param1 string) error { + ret := _m.ctrl.Call(_m, "CreateScope", _param0, _param1) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockAdminClientRecorder) CreateScope(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateScope", arg0, arg1) +} + +func (_m *MockAdminClient) Directories(_param0 []string) dosa.AdminClient { + ret := _m.ctrl.Call(_m, "Directories", _param0) + ret0, _ := ret[0].(dosa.AdminClient) + return ret0 +} + +func (_mr *_MockAdminClientRecorder) Directories(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Directories", arg0) +} + +func (_m *MockAdminClient) DropScope(_param0 context.Context, _param1 string) error { + ret := _m.ctrl.Call(_m, "DropScope", _param0, _param1) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockAdminClientRecorder) DropScope(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "DropScope", arg0, arg1) +} + +func (_m *MockAdminClient) Excludes(_param0 []string) dosa.AdminClient { + ret := _m.ctrl.Call(_m, "Excludes", _param0) + ret0, _ := ret[0].(dosa.AdminClient) + return ret0 +} + +func (_mr *_MockAdminClientRecorder) Excludes(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Excludes", arg0) +} + +func (_m *MockAdminClient) GetSchema() ([]*dosa.EntityDefinition, error) { + ret := _m.ctrl.Call(_m, "GetSchema") + ret0, _ := ret[0].([]*dosa.EntityDefinition) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockAdminClientRecorder) GetSchema() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetSchema") +} + +func (_m *MockAdminClient) Scope(_param0 string) dosa.AdminClient { + ret := _m.ctrl.Call(_m, "Scope", _param0) + ret0, _ := ret[0].(dosa.AdminClient) + return ret0 +} + +func (_mr *_MockAdminClientRecorder) Scope(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Scope", arg0) +} + +func (_m *MockAdminClient) TruncateScope(_param0 context.Context, _param1 string) error { + ret := _m.ctrl.Call(_m, "TruncateScope", _param0, _param1) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockAdminClientRecorder) TruncateScope(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "TruncateScope", arg0, arg1) +} + +func (_m *MockAdminClient) UpsertSchema(_param0 context.Context, _param1 string) (*dosa.SchemaStatus, error) { + ret := _m.ctrl.Call(_m, "UpsertSchema", _param0, _param1) + ret0, _ := ret[0].(*dosa.SchemaStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockAdminClientRecorder) UpsertSchema(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "UpsertSchema", arg0, arg1) +} From dff6d4428cb323bf71647a2cd3010363f4e22465 Mon Sep 17 00:00:00 2001 From: Matt Gode Date: Mon, 9 Apr 2018 08:18:04 -0700 Subject: [PATCH 03/12] Update changelog to refect changes in diff --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9706b95..676d4b78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## v2.6.0 (unreleased) +- Implement MultiUpsert method in client and expose it via the Client interface (#304) ## v2.5.4 (unreleased) - Fix bug in invalidating fallback cache on upsert (#292) From b7a2c2b3481a55d2733c392644a3957b27357b24 Mon Sep 17 00:00:00 2001 From: Matt Gode Date: Mon, 9 Apr 2018 08:20:07 -0700 Subject: [PATCH 04/12] Remove nonsensical comment --- client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/client.go b/client.go index b26adc84..f3a5a205 100644 --- a/client.go +++ b/client.go @@ -456,8 +456,6 @@ func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entit fieldValues[k] = v } - // translate entity field values to a map of primary key name/values pairs - // required to perform a read listMultiValues = append(listMultiValues, fieldValues) } From 1ad1a8cb00d7b9f7be03a82fc3241dbc4cad3b4c Mon Sep 17 00:00:00 2001 From: Matt Gode Date: Mon, 9 Apr 2018 08:25:12 -0700 Subject: [PATCH 05/12] Add warning to multi upsert method --- client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client.go b/client.go index f3a5a205..bd4ebe89 100644 --- a/client.go +++ b/client.go @@ -417,6 +417,8 @@ func (c *client) createOrUpsert(ctx context.Context, fieldsToUpdate []string, en // for the operation to succeed. If `fieldsToUpdate` is provided, only a subset // of fields will be updated. Moreover, all entities being upserted must be part // of the same partition otherwise the request will be rejected. +// NOTE: This endpoint is not officially released. No guarantees about correctness +// or performance of this API will be guaranteed until v2.6 is released. func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entities ...DomainObject) (MultiResult, error) { if !c.initialized { return nil, &ErrNotInitialized{} From ad11b4a04710145bb2ba9c52bd4d5d8e9251d878 Mon Sep 17 00:00:00 2001 From: Matt Gode Date: Tue, 10 Apr 2018 11:05:39 -0700 Subject: [PATCH 06/12] Revert excess changes to client mock --- mocks/client.go | 147 +++++++----------------------------------------- 1 file changed, 21 insertions(+), 126 deletions(-) diff --git a/mocks/client.go b/mocks/client.go index 8b8e5645..350cc03c 100644 --- a/mocks/client.go +++ b/mocks/client.go @@ -1,5 +1,25 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + // Automatically generated by MockGen. DO NOT EDIT! -// Source: github.com/uber-go/dosa (interfaces: Client,AdminClient) +// Source: github.com/uber-go/dosa (interfaces: Client) package mocks @@ -166,128 +186,3 @@ func (_m *MockClient) WalkRange(_param0 context.Context, _param1 *dosa.RangeOp, func (_mr *_MockClientRecorder) WalkRange(arg0, arg1, arg2 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "WalkRange", arg0, arg1, arg2) } - -// Mock of AdminClient interface -type MockAdminClient struct { - ctrl *gomock.Controller - recorder *_MockAdminClientRecorder -} - -// Recorder for MockAdminClient (not exported) -type _MockAdminClientRecorder struct { - mock *MockAdminClient -} - -func NewMockAdminClient(ctrl *gomock.Controller) *MockAdminClient { - mock := &MockAdminClient{ctrl: ctrl} - mock.recorder = &_MockAdminClientRecorder{mock} - return mock -} - -func (_m *MockAdminClient) EXPECT() *_MockAdminClientRecorder { - return _m.recorder -} - -func (_m *MockAdminClient) CanUpsertSchema(_param0 context.Context, _param1 string) (*dosa.SchemaStatus, error) { - ret := _m.ctrl.Call(_m, "CanUpsertSchema", _param0, _param1) - ret0, _ := ret[0].(*dosa.SchemaStatus) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockAdminClientRecorder) CanUpsertSchema(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "CanUpsertSchema", arg0, arg1) -} - -func (_m *MockAdminClient) CheckSchemaStatus(_param0 context.Context, _param1 string, _param2 int32) (*dosa.SchemaStatus, error) { - ret := _m.ctrl.Call(_m, "CheckSchemaStatus", _param0, _param1, _param2) - ret0, _ := ret[0].(*dosa.SchemaStatus) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockAdminClientRecorder) CheckSchemaStatus(arg0, arg1, arg2 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "CheckSchemaStatus", arg0, arg1, arg2) -} - -func (_m *MockAdminClient) CreateScope(_param0 context.Context, _param1 string) error { - ret := _m.ctrl.Call(_m, "CreateScope", _param0, _param1) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockAdminClientRecorder) CreateScope(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateScope", arg0, arg1) -} - -func (_m *MockAdminClient) Directories(_param0 []string) dosa.AdminClient { - ret := _m.ctrl.Call(_m, "Directories", _param0) - ret0, _ := ret[0].(dosa.AdminClient) - return ret0 -} - -func (_mr *_MockAdminClientRecorder) Directories(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "Directories", arg0) -} - -func (_m *MockAdminClient) DropScope(_param0 context.Context, _param1 string) error { - ret := _m.ctrl.Call(_m, "DropScope", _param0, _param1) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockAdminClientRecorder) DropScope(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "DropScope", arg0, arg1) -} - -func (_m *MockAdminClient) Excludes(_param0 []string) dosa.AdminClient { - ret := _m.ctrl.Call(_m, "Excludes", _param0) - ret0, _ := ret[0].(dosa.AdminClient) - return ret0 -} - -func (_mr *_MockAdminClientRecorder) Excludes(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "Excludes", arg0) -} - -func (_m *MockAdminClient) GetSchema() ([]*dosa.EntityDefinition, error) { - ret := _m.ctrl.Call(_m, "GetSchema") - ret0, _ := ret[0].([]*dosa.EntityDefinition) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockAdminClientRecorder) GetSchema() *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetSchema") -} - -func (_m *MockAdminClient) Scope(_param0 string) dosa.AdminClient { - ret := _m.ctrl.Call(_m, "Scope", _param0) - ret0, _ := ret[0].(dosa.AdminClient) - return ret0 -} - -func (_mr *_MockAdminClientRecorder) Scope(arg0 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "Scope", arg0) -} - -func (_m *MockAdminClient) TruncateScope(_param0 context.Context, _param1 string) error { - ret := _m.ctrl.Call(_m, "TruncateScope", _param0, _param1) - ret0, _ := ret[0].(error) - return ret0 -} - -func (_mr *_MockAdminClientRecorder) TruncateScope(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "TruncateScope", arg0, arg1) -} - -func (_m *MockAdminClient) UpsertSchema(_param0 context.Context, _param1 string) (*dosa.SchemaStatus, error) { - ret := _m.ctrl.Call(_m, "UpsertSchema", _param0, _param1) - ret0, _ := ret[0].(*dosa.SchemaStatus) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -func (_mr *_MockAdminClientRecorder) UpsertSchema(arg0, arg1 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "UpsertSchema", arg0, arg1) -} From db6c89e95f515981fd59794d91f4121a04110ecf Mon Sep 17 00:00:00 2001 From: Tiffany Cheng Date: Thu, 26 Apr 2018 12:03:20 -0700 Subject: [PATCH 07/12] Update comments and wrap error --- client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client.go b/client.go index bd4ebe89..48f96e64 100644 --- a/client.go +++ b/client.go @@ -153,7 +153,7 @@ type Client interface { // given RemoveRangeOp. RemoveRange(ctx context.Context, removeRangeOp *RemoveRangeOp) error - // TODO: Coming in v2.7 + // TODO: Coming in v3.0 // MultiRemove removes multiple rows by primary key. The passed-in entity should // contain the primary key field values. // MultiRemove(context.Context, ...DomainObject) (MultiResult, error) @@ -418,7 +418,7 @@ func (c *client) createOrUpsert(ctx context.Context, fieldsToUpdate []string, en // of fields will be updated. Moreover, all entities being upserted must be part // of the same partition otherwise the request will be rejected. // NOTE: This endpoint is not officially released. No guarantees about correctness -// or performance of this API will be guaranteed until v2.6 is released. +// or performance of this API will be guaranteed until v3.0 is released. func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entities ...DomainObject) (MultiResult, error) { if !c.initialized { return nil, &ErrNotInitialized{} @@ -463,7 +463,7 @@ func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entit results, err := c.connector.MultiUpsert(ctx, re.EntityInfo(), listMultiValues) if err != nil { - return nil, err + return nil, errors.Wrap(err, "MultiUpsert") } multiResult := MultiResult{} From dd53450655c4eac40e870857810b3ef1e083099e Mon Sep 17 00:00:00 2001 From: tiffanycheng <8397787+tiffanycheng@users.noreply.github.com> Date: Fri, 27 Apr 2018 16:08:14 -0700 Subject: [PATCH 08/12] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91db2f90..cc069592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v3.0.0 (unreleased) +## v2.7.0 (unreleased) - Implement MultiUpsert method in client and expose it via the Client interface (#304) ## v2.6.0 (2018-04-16) From b74e439609b54d71cf16887c3e5ae7f55977bc5e Mon Sep 17 00:00:00 2001 From: tiffanycheng <8397787+tiffanycheng@users.noreply.github.com> Date: Mon, 30 Apr 2018 15:12:16 -0700 Subject: [PATCH 09/12] Update client.go change versioning number --- client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index 48f96e64..bd894dd5 100644 --- a/client.go +++ b/client.go @@ -153,7 +153,7 @@ type Client interface { // given RemoveRangeOp. RemoveRange(ctx context.Context, removeRangeOp *RemoveRangeOp) error - // TODO: Coming in v3.0 + // TODO: Coming in future versions // MultiRemove removes multiple rows by primary key. The passed-in entity should // contain the primary key field values. // MultiRemove(context.Context, ...DomainObject) (MultiResult, error) @@ -418,7 +418,7 @@ func (c *client) createOrUpsert(ctx context.Context, fieldsToUpdate []string, en // of fields will be updated. Moreover, all entities being upserted must be part // of the same partition otherwise the request will be rejected. // NOTE: This endpoint is not officially released. No guarantees about correctness -// or performance of this API will be guaranteed until v3.0 is released. +// or performance of this API will be guaranteed until v2.7 is released. func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entities ...DomainObject) (MultiResult, error) { if !c.initialized { return nil, &ErrNotInitialized{} From 536404b81d024451aec8a206071a4972fafcca8d Mon Sep 17 00:00:00 2001 From: tiffanycheng <8397787+tiffanycheng@users.noreply.github.com> Date: Tue, 1 May 2018 10:21:26 -0700 Subject: [PATCH 10/12] Update CHANGELOG.md change back to 3.0.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc069592..91db2f90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v2.7.0 (unreleased) +## v3.0.0 (unreleased) - Implement MultiUpsert method in client and expose it via the Client interface (#304) ## v2.6.0 (2018-04-16) From 255dd3b4c52e72f054e51088aff73ae830b5c86a Mon Sep 17 00:00:00 2001 From: tiffanycheng <8397787+tiffanycheng@users.noreply.github.com> Date: Tue, 1 May 2018 10:22:09 -0700 Subject: [PATCH 11/12] Update client.go Change versioning to 3.0 --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index bd894dd5..b9936e3c 100644 --- a/client.go +++ b/client.go @@ -418,7 +418,7 @@ func (c *client) createOrUpsert(ctx context.Context, fieldsToUpdate []string, en // of fields will be updated. Moreover, all entities being upserted must be part // of the same partition otherwise the request will be rejected. // NOTE: This endpoint is not officially released. No guarantees about correctness -// or performance of this API will be guaranteed until v2.7 is released. +// or performance of this API will be guaranteed until v3.0.0 is released. func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entities ...DomainObject) (MultiResult, error) { if !c.initialized { return nil, &ErrNotInitialized{} From 9018de788d3b0abb1d1d285f375d770c5d1576e7 Mon Sep 17 00:00:00 2001 From: tiffanycheng <8397787+tiffanycheng@users.noreply.github.com> Date: Fri, 4 May 2018 11:16:57 -0700 Subject: [PATCH 12/12] Update client.go Clarify comments --- client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client.go b/client.go index b9936e3c..b87cdd9b 100644 --- a/client.go +++ b/client.go @@ -416,7 +416,8 @@ func (c *client) createOrUpsert(ctx context.Context, fieldsToUpdate []string, en // entities provided must contain values for all components of its primary key // for the operation to succeed. If `fieldsToUpdate` is provided, only a subset // of fields will be updated. Moreover, all entities being upserted must be part -// of the same partition otherwise the request will be rejected. +// of the same partition otherwise the request will be rejected. This is enforced +// server side. // NOTE: This endpoint is not officially released. No guarantees about correctness // or performance of this API will be guaranteed until v3.0.0 is released. func (c *client) MultiUpsert(ctx context.Context, fieldsToUpdate []string, entities ...DomainObject) (MultiResult, error) {