Skip to content

Commit 693e4b5

Browse files
authored
CBG-5017 Resync: allow legacy rev documents to be resynced (#7890)
1 parent c6d3114 commit 693e4b5

File tree

5 files changed

+160
-38
lines changed

5 files changed

+160
-38
lines changed

db/background_mgr_resync_dcp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (r *ResyncManagerDCP) Run(ctx context.Context, options map[string]any, pers
149149
collectionCtx := databaseCollection.AddCollectionContext(ctx)
150150
_, unusedSequences, err := (&DatabaseCollectionWithUser{
151151
DatabaseCollection: databaseCollection,
152-
}).resyncDocument(collectionCtx, docID, key, regenerateSequences, []uint64{})
152+
}).ResyncDocument(collectionCtx, docID, key, regenerateSequences, []uint64{})
153153

154154
databaseCollection.releaseSequences(collectionCtx, unusedSequences)
155155

db/database.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,7 +1822,9 @@ func (db *DatabaseCollectionWithUser) getResyncedDocument(ctx context.Context, d
18221822
return doc, shouldUpdate, updatedExpiry, doc.Sequence, updatedUnusedSequences, nil
18231823
}
18241824

1825-
func (db *DatabaseCollectionWithUser) resyncDocument(ctx context.Context, docid, key string, regenerateSequences bool, unusedSequences []uint64) (updatedHighSeq uint64, updatedUnusedSequences []uint64, err error) {
1825+
// ResyncDocument will re-run the sync function on the document and write an updated version to the bucket. If
1826+
// the sync function doesn't change any channels or access grants, no write will be performed.
1827+
func (db *DatabaseCollectionWithUser) ResyncDocument(ctx context.Context, docid, key string, regenerateSequences bool, unusedSequences []uint64) (updatedHighSeq uint64, updatedUnusedSequences []uint64, err error) {
18261828
var updatedDoc *Document
18271829
var shouldUpdate bool
18281830
var updatedExpiry *uint32
@@ -1855,12 +1857,11 @@ func (db *DatabaseCollectionWithUser) resyncDocument(ctx context.Context, docid,
18551857
doc.MetadataOnlyUpdate = computeMetadataOnlyUpdate(doc.Cas, doc.RevSeqNo, doc.MetadataOnlyUpdate)
18561858
}
18571859

1858-
_, rawSyncXattr, rawVvXattr, rawMouXattr, rawGlobalXattr, err := updatedDoc.MarshalWithXattrs()
1860+
_, rawSyncXattr, _, rawMouXattr, rawGlobalXattr, err := updatedDoc.MarshalWithXattrs()
18591861
updatedDoc := sgbucket.UpdatedDoc{
18601862
Doc: nil, // Resync does not require document body update
18611863
Xattrs: map[string][]byte{
18621864
base.SyncXattrName: rawSyncXattr,
1863-
base.VvXattrName: rawVvXattr,
18641865
},
18651866
Expiry: updatedExpiry,
18661867
}

db/database_test.go

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3868,61 +3868,103 @@ func Test_invalidateAllPrincipalsCache(t *testing.T) {
38683868
}
38693869

38703870
func Test_resyncDocument(t *testing.T) {
3871-
if !base.TestUseXattrs() {
3872-
t.Skip("Walrus doesn't support xattr")
3873-
}
38743871
db, ctx := setupTestDB(t)
38753872
defer db.Close(ctx)
38763873

38773874
db.Options.EnableXattr = true
38783875
collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db)
38793876

3880-
syncFn := `
3877+
testCases := []struct {
3878+
name string
3879+
useHLV bool
3880+
}{
3881+
{
3882+
name: "pre 4.0",
3883+
useHLV: false,
3884+
},
3885+
{
3886+
name: "has hlv",
3887+
useHLV: true,
3888+
},
3889+
}
3890+
3891+
for _, tc := range testCases {
3892+
t.Run(tc.name, func(t *testing.T) {
3893+
startingSyncFnCount := int(db.DbStats.Database().SyncFunctionCount.Value())
3894+
syncFn := `
38813895
function sync(doc, oldDoc){
38823896
channel("channel." + "ABC");
38833897
}
38843898
`
3885-
_, err := collection.UpdateSyncFun(ctx, syncFn)
3886-
require.NoError(t, err)
3899+
_, err := collection.UpdateSyncFun(ctx, syncFn)
3900+
require.NoError(t, err)
38873901

3888-
docID := uuid.NewString()
3902+
docID := uuid.NewString()
38893903

3890-
updateBody := make(map[string]any)
3891-
updateBody["val"] = "value"
3892-
_, doc, err := collection.Put(ctx, docID, updateBody)
3893-
require.NoError(t, err)
3894-
assert.NotNil(t, doc)
3904+
updateBody := make(map[string]any)
3905+
updateBody["val"] = "value"
3906+
if tc.useHLV {
3907+
_, _, err := collection.Put(ctx, docID, updateBody)
3908+
require.NoError(t, err)
3909+
} else {
3910+
collection.CreateDocNoHLV(t, ctx, docID, updateBody)
3911+
}
38953912

3896-
syncFn = `
3913+
syncFn = `
38973914
function sync(doc, oldDoc){
38983915
channel("channel." + "ABC12332423234");
38993916
}
39003917
`
3901-
_, err = collection.UpdateSyncFun(ctx, syncFn)
3902-
require.NoError(t, err)
3903-
3904-
_, _, err = collection.resyncDocument(ctx, docID, realDocID(docID), false, []uint64{10})
3905-
require.NoError(t, err)
3906-
err = collection.WaitForPendingChanges(ctx)
3907-
require.NoError(t, err)
3918+
_, err = collection.UpdateSyncFun(ctx, syncFn)
3919+
require.NoError(t, err)
39083920

3909-
syncData, err := collection.GetDocSyncData(ctx, docID)
3910-
assert.NoError(t, err)
3921+
preResyncDoc, err := collection.GetDocument(ctx, docID, DocUnmarshalAll)
3922+
require.NoError(t, err)
3923+
if !tc.useHLV {
3924+
require.Nil(t, preResyncDoc.HLV)
3925+
}
3926+
_, _, err = collection.ResyncDocument(ctx, docID, realDocID(docID), false, []uint64{10})
3927+
require.NoError(t, err)
3928+
err = collection.WaitForPendingChanges(ctx)
3929+
require.NoError(t, err)
39113930

3912-
assert.Len(t, syncData.ChannelSet, 2)
3913-
assert.Len(t, syncData.Channels, 2)
3914-
found := false
3931+
postResyncDoc, _, err := collection.getDocWithXattrs(ctx, docID, collection.syncGlobalSyncMouRevSeqNoAndUserXattrKeys(), DocUnmarshalAll)
3932+
assert.NoError(t, err)
39153933

3916-
for _, chSet := range syncData.ChannelSet {
3917-
if chSet.Name == "channel.ABC12332423234" {
3918-
found = true
3919-
break
3920-
}
3921-
}
3934+
assert.Len(t, postResyncDoc.ChannelSet, 2)
3935+
assert.Len(t, postResyncDoc.Channels, 2)
3936+
found := false
39223937

3923-
assert.True(t, found)
3924-
assert.Equal(t, 2, int(db.DbStats.Database().SyncFunctionCount.Value()))
3938+
for _, chSet := range postResyncDoc.ChannelSet {
3939+
if chSet.Name == "channel.ABC12332423234" {
3940+
found = true
3941+
break
3942+
}
3943+
}
3944+
assert.True(t, found)
39253945

3946+
require.NoError(t, err)
3947+
if tc.useHLV {
3948+
require.NotNil(t, postResyncDoc.HLV)
3949+
require.Equal(t, Version{
3950+
SourceID: db.EncodedSourceID,
3951+
Value: preResyncDoc.Cas,
3952+
}, Version{
3953+
SourceID: postResyncDoc.HLV.SourceID,
3954+
Value: postResyncDoc.HLV.Version,
3955+
})
3956+
} else {
3957+
require.Nil(t, postResyncDoc.HLV)
3958+
}
3959+
require.NotNil(t, postResyncDoc.MetadataOnlyUpdate)
3960+
require.Equal(t, MetadataOnlyUpdate{
3961+
HexCAS: base.CasToString(postResyncDoc.Cas),
3962+
PreviousHexCAS: base.CasToString(preResyncDoc.Cas),
3963+
PreviousRevSeqNo: preResyncDoc.RevSeqNo,
3964+
}, *postResyncDoc.MetadataOnlyUpdate)
3965+
assert.Equal(t, startingSyncFnCount+2, int(db.DbStats.Database().SyncFunctionCount.Value()))
3966+
})
3967+
}
39263968
}
39273969

39283970
func Test_getUpdatedDocument(t *testing.T) {

rest/legacy_rev_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2025-Present Couchbase, Inc.
2+
//
3+
// Use of this software is governed by the Business Source License included
4+
// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified
5+
// in that file, in accordance with the Business Source License, use of this
6+
// software will be governed by the Apache License, Version 2.0, included in
7+
// the file licenses/APL2.txt.
8+
9+
package rest
10+
11+
import (
12+
"fmt"
13+
"net/http"
14+
"testing"
15+
16+
"github.com/couchbase/sync_gateway/base"
17+
"github.com/couchbase/sync_gateway/db"
18+
"github.com/stretchr/testify/require"
19+
)
20+
21+
// TestResyncLegacyRev makes sure that running resync on a legacy rev will send future blip messages as a legacy rev and not as an HLV
22+
func TestResyncLegacyRev(t *testing.T) {
23+
rt := NewRestTesterPersistentConfig(t)
24+
defer rt.Close()
25+
26+
const (
27+
alice = "alice"
28+
channelName = "A"
29+
)
30+
collection, ctx := rt.GetSingleTestDatabaseCollectionWithUser()
31+
// default collection will use channel Name and named collection will use collection.Name
32+
rt.CreateUser(alice, []string{channelName, collection.Name})
33+
34+
docID := db.SafeDocumentName(t, t.Name())
35+
doc := rt.CreateDocNoHLV(docID, db.Body{"channels": channelName})
36+
37+
btcRunner := NewBlipTesterClientRunner(t)
38+
btcRunner.Run(func(t *testing.T) {
39+
btc := btcRunner.NewBlipTesterClientOptsWithRT(rt, &BlipTesterClientOpts{Username: alice})
40+
defer btc.Close()
41+
42+
btcRunner.StartOneshotPull(btc.id)
43+
44+
msg := btcRunner.WaitForPullRevMessage(btc.id, docID, DocVersion{RevTreeID: doc.GetRevTreeID()})
45+
require.Equal(t, msg.Properties[db.RevMessageRev], doc.GetRevTreeID())
46+
})
47+
previousChannel := collection.Name
48+
if base.IsDefaultCollection(collection.ScopeName, collection.Name) {
49+
previousChannel = "A"
50+
}
51+
52+
_, err := collection.UpdateSyncFun(ctx, fmt.Sprintf(`function() {channel("B", "%s")}`, previousChannel))
53+
require.NoError(t, err)
54+
55+
// use ResyncDocument and TakeDbOffline/Online instead of /ks/_config/sync && /db/_resync to work under rosmar which
56+
// doesn't yet support DCP resync or updating config on an existing bucket.
57+
regenerateSequences := false
58+
var unusedSequences []uint64
59+
_, _, err = collection.ResyncDocument(ctx, docID, docID, regenerateSequences, unusedSequences)
60+
require.NoError(t, err)
61+
62+
rt.TakeDbOffline()
63+
rt.TakeDbOnline()
64+
65+
resp := rt.SendAdminRequest(http.MethodGet, "/{{.keyspace}}/_raw/"+docID, "")
66+
RequireStatus(t, resp, http.StatusOK)
67+
68+
btcRunner = NewBlipTesterClientRunner(t)
69+
btcRunner.Run(func(t *testing.T) {
70+
btc := btcRunner.NewBlipTesterClientOptsWithRT(rt, &BlipTesterClientOpts{Username: alice})
71+
defer btc.Close()
72+
73+
btcRunner.StartOneshotPull(btc.id)
74+
75+
msg := btcRunner.WaitForPullRevMessage(btc.id, docID, DocVersion{RevTreeID: doc.GetRevTreeID()})
76+
// make sure second rev message after resync is still legacy rev format
77+
require.Equal(t, msg.Properties[db.RevMessageRev], doc.GetRevTreeID())
78+
})
79+
}

rest/utilities_testing_blip_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1982,7 +1982,7 @@ func (btcc *BlipTesterCollectionClient) WaitForPullRevMessage(docID string, vers
19821982
return
19831983
}
19841984
var lookupVersion DocVersion
1985-
if btcc.UseHLV() {
1985+
if btcc.UseHLV() && !version.CV.IsEmpty() {
19861986
lookupVersion = DocVersion{CV: version.CV}
19871987
} else {
19881988
lookupVersion = DocVersion{RevTreeID: version.RevTreeID}

0 commit comments

Comments
 (0)