Skip to content

Commit 48b879d

Browse files
committed
CBG-4806: move conflict detection to common function to be shared between areas
1 parent 38f3742 commit 48b879d

File tree

3 files changed

+57
-26
lines changed

3 files changed

+57
-26
lines changed

db/crud.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,6 +1267,7 @@ func (db *DatabaseCollectionWithUser) PutExistingCurrentVersion(ctx context.Cont
12671267
revTreeConflictChecked := false
12681268
var parent string
12691269
var currentRevIndex int
1270+
var isConflict bool
12701271

12711272
// Conflict check here
12721273
// if doc has no HLV defined this is a new doc we haven't seen before, skip conflict check
@@ -1277,11 +1278,12 @@ func (db *DatabaseCollectionWithUser) PutExistingCurrentVersion(ctx context.Cont
12771278
return nil, nil, false, nil, addNewerVersionsErr
12781279
}
12791280
} else {
1280-
if doc.HLV.isDominating(newDocHLV) {
1281+
isConflict, err = IsInConflict(ctx, doc.HLV, newDocHLV)
1282+
if err != nil && errors.Is(err, ErrNoNewVersionsToAdd) {
12811283
base.DebugfCtx(ctx, base.KeyCRUD, "PutExistingCurrentVersion(%q): No new versions to add. existing: %#v new:%#v", base.UD(newDoc.ID), doc.HLV, newDocHLV)
12821284
return nil, nil, false, nil, base.ErrUpdateCancel // No new revisions to add
12831285
}
1284-
if newDocHLV.isDominating(doc.HLV) {
1286+
if !isConflict {
12851287
// update hlv for all newer incoming source version pairs
12861288
addNewerVersionsErr := doc.HLV.AddNewerVersions(newDocHLV)
12871289
if addNewerVersionsErr != nil {

db/hybrid_logical_vector.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
package db
1010

1111
import (
12+
"context"
1213
"encoding/base64"
14+
"errors"
1315
"fmt"
1416
"maps"
1517
"sort"
@@ -689,3 +691,50 @@ func (hlv *HybridLogicalVector) UnmarshalJSON(inputjson []byte) error {
689691
func (hlv HybridLogicalVector) GoString() string {
690692
return fmt.Sprintf("HybridLogicalVector{CurrentVersionCAS:%d, SourceID:%s, Version:%d, PreviousVersions:%#+v, MergeVersions:%#+v}", hlv.CurrentVersionCAS, hlv.SourceID, hlv.Version, hlv.PreviousVersions, hlv.MergeVersions)
691693
}
694+
695+
// ErrNoNewVersionsToAdd will be thrown when there are no new versions from incoming HLV to be added to HLV that is local
696+
var ErrNoNewVersionsToAdd = errors.New("no new versions to add to HLV")
697+
698+
// IsInConflict is used to identify if two HLV's are in conflict or not. Will return boolean to indicate if in conflict
699+
// or not and will error for the following cases:
700+
// - Local HLV dominates incoming HLV (meaning local version is a newer version that the incoming one)
701+
// - Local CV matches incoming CV, so no new versions to add
702+
func IsInConflict(ctx context.Context, localHLV, incomingHLV *HybridLogicalVector) (bool, error) {
703+
incomingCV := incomingHLV.ExtractCurrentVersionFromHLV()
704+
localCV := localHLV.ExtractCurrentVersionFromHLV()
705+
706+
// check if incoming CV and local CV are the same. This is needed here given that if both CV's are the same the
707+
// below check to check local revision is newer than incoming revision will pass given the check and will add the
708+
// incoming versions even if CV's are the same
709+
if localCV.Equal(*incomingCV) {
710+
base.DebugfCtx(ctx, base.KeyCRUD, "incoming CV %#+v is equal to local revision %#+v", incomingCV, localCV)
711+
return false, ErrNoNewVersionsToAdd
712+
}
713+
714+
// standard no conflict case. In the simple case, this happens when:
715+
// - SG writes document 1@cbs1
716+
// - CBL pulls document 1@cbs1
717+
// - SG writes document 2@cbs1
718+
if incomingHLV.DominatesSource(*localCV) {
719+
return false, nil
720+
}
721+
722+
// local revision is newer than incoming revision. Common case:
723+
// - CBL writes document 1@cbl1
724+
// - CBL pushes to SG as 1@cbl1
725+
// - CBL pulls document 1@cbl1
726+
//
727+
// NOTE: without P2P replication, this should not be the case and we would not get this revision, since CBL
728+
// would respond to a SG changes message that CBL does not need this revision
729+
if localHLV.DominatesSource(*incomingCV) {
730+
return false, ErrNoNewVersionsToAdd
731+
}
732+
// Check if conflict has been previously resolved.
733+
// - If merge versions are empty, then it has not be resolved.
734+
// - If merge versions do not match, then it has not been resolved.
735+
if len(incomingHLV.MergeVersions) != 0 && len(localHLV.MergeVersions) != 0 && maps.Equal(incomingHLV.MergeVersions, localHLV.MergeVersions) {
736+
base.DebugfCtx(ctx, base.KeyVV, "merge versions match between local HLV %#v and incoming HLV %#v, conflict previously resolved", localHLV, incomingCV)
737+
return false, nil
738+
}
739+
return true, nil
740+
}

rest/utilities_testing_blip_client.go

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"encoding/base64"
1717
"fmt"
1818
"iter"
19-
"maps"
2019
"net/http"
2120
"slices"
2221
"strconv"
@@ -325,35 +324,16 @@ func (cd *clientDoc) _hasConflict(t testing.TB, incomingHLV *db.HybridLogicalVec
325324
incomingCV := incomingHLV.ExtractCurrentVersionFromHLV()
326325
localCV := localHLV.ExtractCurrentVersionFromHLV()
327326
// safety check - ensure SG is not sending a rev that we already had - ensures changes feed messaging is working correctly to prevent
327+
// this check is also performed in the function below but we should keep this here for extra context on FailNow call
328328
if localCV.Equal(*incomingCV) {
329329
require.FailNow(t, fmt.Sprintf("incoming CV %#+v is equal to local revision %#+v - this should've been filtered via changes response before ending up as a rev. This is only true if there is a single replication occurring, two simultaneous replications (e.g. P2P) could cause this. If there are multiple replications, modify code.", incomingCV, latestRev))
330330
}
331-
// standard no conflict case. In the simple case, this happens when:
332-
// - SG writes document 1@cbs1
333-
// - CBL pulls document 1@cbs1
334-
// - SG writes document 2@cbs1
335-
if incomingHLV.DominatesSource(*localCV) {
336-
return false
337-
}
338331

339-
// local revision is newer than incoming revision. Common case:
340-
// - CBL writes document 1@cbl1
341-
// - CBL pushes to SG as 1@cbl1
342-
// - CBL pulls document 1@cbl1
343-
//
344-
// NOTE: without P2P replication, this should not be the case and we would not get this revision, since CBL
345-
// would respond to a SG changes message that CBL does not need this revision
346-
if localHLV.DominatesSource(*incomingCV) {
332+
inConflict, err := db.IsInConflict(nil, &localHLV, incomingHLV)
333+
if err != nil {
347334
require.FailNow(t, fmt.Sprintf("incoming CV %#+v has lower version than the local revision %#+v - this should've been filtered via changes response before ending up as a rev. blip tester would reply that to Sync Gateway that it doesn't need this revision", incomingCV, localHLV))
348-
return false
349-
}
350-
// Check if conflict has been previously resolved.
351-
// - If merge versions are empty, then it has not be resolved.
352-
// - If merge versions do not match, then it has not been resolved.
353-
if len(incomingHLV.MergeVersions) != 0 && len(localHLV.MergeVersions) != 0 && maps.Equal(incomingHLV.MergeVersions, localHLV.MergeVersions) {
354-
return false
355335
}
356-
return true
336+
return inConflict
357337
}
358338

359339
func (btcc *BlipTesterCollectionClient) _resolveConflict(incomingHLV *db.HybridLogicalVector, incomingBody []byte, localDoc *clientDocRev) (body []byte, hlv db.HybridLogicalVector) {

0 commit comments

Comments
 (0)