Skip to content
This repository was archived by the owner on Dec 6, 2025. It is now read-only.

Commit c443e9f

Browse files
committed
bed conflicts
1 parent 9b6bab1 commit c443e9f

File tree

5 files changed

+183
-15
lines changed

5 files changed

+183
-15
lines changed

services/tasks-svc/internal/bed/bed.go

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package bed
33
import (
44
"common"
55
"context"
6+
commonpb "gen/libs/common/v1"
67
"github.com/jackc/pgx/v5/pgconn"
78
"google.golang.org/genproto/googleapis/rpc/errdetails"
9+
"google.golang.org/protobuf/types/known/wrapperspb"
810
"hwdb"
911
"hwlocale"
1012
"hwutil"
1113
"strconv"
14+
"tasks-svc/internal/util"
1215
"tasks-svc/locale"
1316
"tasks-svc/repos/bed_repo"
1417

@@ -186,7 +189,6 @@ func (ServiceServer) GetBedsByRoom(ctx context.Context, req *pb.GetBedsByRoomReq
186189
}
187190

188191
func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (*pb.UpdateBedResponse, error) {
189-
bedRepo := bed_repo.New(hwdb.GetDB())
190192

191193
bedID, err := uuid.Parse(req.Id)
192194
if err != nil {
@@ -198,7 +200,21 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (*
198200
return nil, status.Error(codes.InvalidArgument, err.Error())
199201
}
200202

201-
consistency, err := bedRepo.UpdateBed(ctx, bed_repo.UpdateBedParams{
203+
expConsistency, ok := hwutil.ParseConsistency(req.Consistency)
204+
if !ok {
205+
return nil, common.UnparsableConsistencyError(ctx, "consistency")
206+
}
207+
208+
// Start TX
209+
tx, rollback, err := hwdb.BeginTx(hwdb.GetDB(), ctx)
210+
if err != nil {
211+
return nil, err
212+
}
213+
defer rollback()
214+
bedRepo := bed_repo.New(tx)
215+
216+
// do update
217+
result, err := bedRepo.UpdateBed(ctx, bed_repo.UpdateBedParams{
202218
ID: bedID,
203219
Name: req.Name,
204220
RoomID: roomId,
@@ -208,9 +224,52 @@ func (ServiceServer) UpdateBed(ctx context.Context, req *pb.UpdateBedRequest) (*
208224
return nil, err
209225
}
210226

227+
// conflict detection
228+
if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) {
229+
conflicts := make(map[string]*commonpb.AttributeConflict)
230+
231+
if req.Name != nil && *req.Name != result.OldName {
232+
conflicts["name"], err = util.AttributeConflict(
233+
wrapperspb.String(result.OldName),
234+
wrapperspb.String(*req.Name),
235+
)
236+
if err != nil {
237+
return nil, err
238+
}
239+
}
240+
241+
if req.RoomId != nil && *req.RoomId != result.OldRoomID.String() {
242+
conflicts["room_id"], err = util.AttributeConflict(
243+
wrapperspb.String(result.OldRoomID.String()),
244+
wrapperspb.String(*req.RoomId),
245+
)
246+
if err != nil {
247+
return nil, err
248+
}
249+
}
250+
251+
if len(conflicts) != 0 {
252+
// prevent the update
253+
if err := hwdb.Error(ctx, tx.Rollback(ctx)); err != nil {
254+
return nil, err
255+
}
256+
257+
// return conflict
258+
return &pb.UpdateBedResponse{
259+
Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts},
260+
Consistency: strconv.FormatUint(uint64(result.OldConsistency), 10),
261+
}, nil
262+
}
263+
}
264+
265+
// commit update
266+
if err := hwdb.Error(ctx, tx.Commit(ctx)); err != nil {
267+
return nil, err
268+
}
269+
211270
return &pb.UpdateBedResponse{
212-
Conflict: nil, // TODO
213-
Consistency: strconv.FormatUint(uint64(consistency), 10),
271+
Conflict: nil,
272+
Consistency: strconv.FormatUint(uint64(result.Consistency), 10),
214273
}, nil
215274
}
216275

services/tasks-svc/internal/ward/ward.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,7 @@ func (ServiceServer) UpdateWard(ctx context.Context, req *pb.UpdateWardRequest)
178178
if expConsistency != nil && *expConsistency != uint64(result.OldConsistency) {
179179
conflicts := make(map[string]*commonpb.AttributeConflict)
180180

181-
// wards are not event-sourced, we thus don't have information on what has changed since
182-
// however, wards (currently) only have one field: Name, thus it must have been changed
183-
if req.Name != nil {
181+
if req.Name != nil && *req.Name != result.OldName {
184182
conflicts["name"], err = util.AttributeConflict(
185183
wrapperspb.String(result.OldName),
186184
wrapperspb.String(*req.Name),

services/tasks-svc/repos/bed_repo.sql

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,25 @@ WHERE (room_id = sqlc.narg('room_id') OR sqlc.narg('room_id') IS NULL)
2727
ORDER BY name ASC;
2828

2929
-- name: UpdateBed :one
30+
WITH old_table AS (
31+
SELECT
32+
name as old_name,
33+
room_id as old_room_id,
34+
consistency as old_consistency
35+
FROM beds
36+
WHERE beds.id = @id
37+
)
3038
UPDATE beds
3139
SET
3240
name = coalesce(sqlc.narg('name'), name),
3341
room_id = coalesce(sqlc.narg('room_id'), room_id),
3442
consistency = consistency + 1
35-
WHERE id = @id
36-
RETURNING consistency;
43+
WHERE beds.id = @id
44+
RETURNING
45+
consistency,
46+
(SELECT old_name FROM old_table),
47+
(SELECT old_room_id FROM old_table),
48+
(SELECT old_consistency FROM old_table);
3749

3850
-- name: DeleteBed :exec
3951
DELETE FROM beds WHERE id = $1;

services/tasks-svc/repos/bed_repo/bed_repo.sql.go

Lines changed: 30 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/tasks-svc/stories/BedCRUD_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
pb "gen/services/tasks_svc/v1"
66
"github.com/stretchr/testify/assert"
7+
"google.golang.org/protobuf/types/known/wrapperspb"
78
"hwutil"
89
"strconv"
910
"testing"
@@ -177,3 +178,77 @@ func TestGetBeds(t *testing.T) {
177178

178179
}
179180
}
181+
182+
func TestUpdateBedConflict(t *testing.T) {
183+
ctx := context.Background()
184+
bedClient := bedServiceClient()
185+
186+
// prepare
187+
wardId, _ := prepareWard(t, ctx, "")
188+
roomId0, _ := prepareRoom(t, ctx, wardId, "0")
189+
190+
bedId, initialConsistency := prepareBed(t, ctx, roomId0, "")
191+
192+
name1 := "This came first"
193+
roomId1, _ := prepareRoom(t, ctx, wardId, "1")
194+
195+
// update 1
196+
update1Res, err := bedClient.UpdateBed(ctx, &pb.UpdateBedRequest{
197+
Id: bedId,
198+
Name: &name1,
199+
RoomId: &roomId1,
200+
Consistency: &initialConsistency,
201+
})
202+
assert.NoError(t, err)
203+
assert.Nil(t, update1Res.Conflict)
204+
assert.NotEqual(t, initialConsistency, update1Res.Consistency)
205+
206+
name2 := "This came second"
207+
roomId2, _ := prepareRoom(t, ctx, wardId, "2")
208+
209+
// racing update 2
210+
update2Res, err := bedClient.UpdateBed(ctx, &pb.UpdateBedRequest{
211+
Id: bedId,
212+
Name: &name2,
213+
RoomId: &roomId2,
214+
Consistency: &initialConsistency,
215+
})
216+
assert.NoError(t, err)
217+
assert.Equal(t, update1Res.Consistency, update2Res.Consistency)
218+
assert.NotNil(t, update2Res.Conflict)
219+
220+
nameRes := update2Res.Conflict.ConflictingAttributes["name"]
221+
assert.NotNil(t, nameRes)
222+
223+
nameIs := &wrapperspb.StringValue{}
224+
assert.NoError(t, nameRes.Is.UnmarshalTo(nameIs))
225+
assert.Equal(t, name1, nameIs.Value)
226+
227+
nameWant := &wrapperspb.StringValue{}
228+
assert.NoError(t, nameRes.Want.UnmarshalTo(nameWant))
229+
assert.Equal(t, name2, nameWant.Value)
230+
231+
roomRes := update2Res.Conflict.ConflictingAttributes["room_id"]
232+
assert.NotNil(t, roomRes)
233+
234+
roomIs := &wrapperspb.StringValue{}
235+
assert.NoError(t, roomRes.Is.UnmarshalTo(roomIs))
236+
assert.Equal(t, roomId1, roomIs.Value)
237+
238+
roomWant := &wrapperspb.StringValue{}
239+
assert.NoError(t, roomRes.Want.UnmarshalTo(roomWant))
240+
assert.Equal(t, roomId2, roomWant.Value)
241+
242+
// racing update 3
243+
update3Res, err := bedClient.UpdateBed(ctx, &pb.UpdateBedRequest{
244+
Id: bedId,
245+
Name: &name2,
246+
Consistency: &initialConsistency,
247+
})
248+
assert.NoError(t, err)
249+
assert.Equal(t, update1Res.Consistency, update2Res.Consistency)
250+
assert.NotNil(t, update2Res.Conflict)
251+
252+
assert.NotNil(t, update3Res.Conflict.ConflictingAttributes["name"])
253+
assert.Nil(t, update3Res.Conflict.ConflictingAttributes["room_id"])
254+
}

0 commit comments

Comments
 (0)