From 2e3b5e38ae2800f8c37b62ca9b25b7ab259bde19 Mon Sep 17 00:00:00 2001 From: Gregory Newman-Smith <109068393+gregns1@users.noreply.github.com> Date: Thu, 18 Jan 2024 19:45:26 +0000 Subject: [PATCH] CBG-3212: add api to fetch a document by its CV value (#6579) * CBG-3212: add api to fetch a document by its CV value * test fix * rebased SourceAndVersion -> Version rename * Update currentRevChannels on CV revcache load and doc.updateChannels * fix spelling * Remove currentRevChannels * Move common GetRev/GetCV work into documentRevisionForRequest function * Pass revision.RevID into authorizeUserForChannels * Update db/crud.go Co-authored-by: Tor Colvin --------- Co-authored-by: Ben Brooks Co-authored-by: Tor Colvin --- db/changes_test.go | 4 +- db/crud.go | 52 +++++-- db/crud_test.go | 179 +++++++++++++++++++++++++ db/database_test.go | 2 +- db/document.go | 34 +++-- db/document_test.go | 4 +- db/hybrid_logical_vector_test.go | 9 +- db/revision_cache_interface.go | 4 +- db/revision_cache_test.go | 13 +- rest/replicatortest/replicator_test.go | 2 +- 10 files changed, 256 insertions(+), 47 deletions(-) diff --git a/db/changes_test.go b/db/changes_test.go index 544c6abcd9..1e5259907f 100644 --- a/db/changes_test.go +++ b/db/changes_test.go @@ -289,7 +289,7 @@ func TestDocDeletionFromChannelCoalescedRemoved(t *testing.T) { func TestCVPopulationOnChangeEntry(t *testing.T) { db, ctx := setupTestDB(t) defer db.Close(ctx) - collection := GetSingleDatabaseCollectionWithUser(t, db) + collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) collectionID := collection.GetCollectionID() bucketUUID := db.BucketUUID @@ -561,7 +561,7 @@ func TestCurrentVersionPopulationOnChannelCache(t *testing.T) { base.SetUpTestLogging(t, base.LevelDebug, base.KeyCRUD, base.KeyImport, base.KeyDCP, base.KeyCache, base.KeyHTTP) db, ctx := setupTestDB(t) defer db.Close(ctx) - collection := GetSingleDatabaseCollectionWithUser(t, db) + collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) collectionID := collection.GetCollectionID() bucketUUID := db.BucketUUID collection.ChannelMapper = channels.NewChannelMapper(ctx, channels.DocChannelsSyncFunction, db.Options.JavascriptTimeout) diff --git a/db/crud.go b/db/crud.go index ffaaa2eb3f..98abc375c9 100644 --- a/db/crud.go +++ b/db/crud.go @@ -314,14 +314,29 @@ func (db *DatabaseCollectionWithUser) getRev(ctx context.Context, docid, revid s // No rev ID given, so load active revision revision, err = db.revisionCache.GetActive(ctx, docid) } - if err != nil { return DocumentRevision{}, err } + return db.documentRevisionForRequest(ctx, docid, revision, &revid, nil, maxHistory, historyFrom) +} + +// documentRevisionForRequest processes the given DocumentRevision and returns a version of it for a given client request, depending on access, deleted, etc. +func (db *DatabaseCollectionWithUser) documentRevisionForRequest(ctx context.Context, docID string, revision DocumentRevision, revID *string, cv *Version, maxHistory int, historyFrom []string) (DocumentRevision, error) { + // ensure only one of cv or revID is specified + if cv != nil && revID != nil { + return DocumentRevision{}, fmt.Errorf("must have one of cv or revID in documentRevisionForRequest (had cv=%v revID=%v)", cv, revID) + } + var requestedVersion string + if revID != nil { + requestedVersion = *revID + } else if cv != nil { + requestedVersion = cv.String() + } + if revision.BodyBytes == nil { if db.ForceAPIForbiddenErrors() { - base.InfofCtx(ctx, base.KeyCRUD, "Doc: %s %s is missing", base.UD(docid), base.MD(revid)) + base.InfofCtx(ctx, base.KeyCRUD, "Doc: %s %s is missing", base.UD(docID), base.MD(requestedVersion)) return DocumentRevision{}, ErrForbidden } return DocumentRevision{}, ErrMissing @@ -340,16 +355,17 @@ func (db *DatabaseCollectionWithUser) getRev(ctx context.Context, docid, revid s _, requestedHistory = trimEncodedRevisionsToAncestor(ctx, requestedHistory, historyFrom, maxHistory) } - isAuthorized, redactedRev := db.authorizeUserForChannels(docid, revision.RevID, revision.Channels, revision.Deleted, requestedHistory) + isAuthorized, redactedRevision := db.authorizeUserForChannels(docID, revision.RevID, cv, revision.Channels, revision.Deleted, requestedHistory) if !isAuthorized { - if revid == "" { + // client just wanted active revision, not a specific one + if requestedVersion == "" { return DocumentRevision{}, ErrForbidden } if db.ForceAPIForbiddenErrors() { - base.InfofCtx(ctx, base.KeyCRUD, "Not authorized to view doc: %s %s", base.UD(docid), base.MD(revid)) + base.InfofCtx(ctx, base.KeyCRUD, "Not authorized to view doc: %s %s", base.UD(docID), base.MD(requestedVersion)) return DocumentRevision{}, ErrForbidden } - return redactedRev, nil + return redactedRevision, nil } // If the revision is a removal cache entry (no body), but the user has access to that removal, then just @@ -358,13 +374,26 @@ func (db *DatabaseCollectionWithUser) getRev(ctx context.Context, docid, revid s return DocumentRevision{}, ErrMissing } - if revision.Deleted && revid == "" { + if revision.Deleted && requestedVersion == "" { return DocumentRevision{}, ErrDeleted } return revision, nil } +func (db *DatabaseCollectionWithUser) GetCV(ctx context.Context, docid string, cv *Version, includeBody bool) (revision DocumentRevision, err error) { + if cv != nil { + revision, err = db.revisionCache.GetWithCV(ctx, docid, cv, RevCacheOmitDelta) + } else { + revision, err = db.revisionCache.GetActive(ctx, docid) + } + if err != nil { + return DocumentRevision{}, err + } + + return db.documentRevisionForRequest(ctx, docid, revision, nil, cv, 0, nil) +} + // GetDelta attempts to return the delta between fromRevId and toRevId. If the delta can't be generated, // returns nil. func (db *DatabaseCollectionWithUser) GetDelta(ctx context.Context, docID, fromRevID, toRevID string) (delta *RevisionDelta, redactedRev *DocumentRevision, err error) { @@ -396,7 +425,7 @@ func (db *DatabaseCollectionWithUser) GetDelta(ctx context.Context, docID, fromR if fromRevision.Delta != nil { if fromRevision.Delta.ToRevID == toRevID { - isAuthorized, redactedBody := db.authorizeUserForChannels(docID, toRevID, fromRevision.Delta.ToChannels, fromRevision.Delta.ToDeleted, encodeRevisions(ctx, docID, fromRevision.Delta.RevisionHistory)) + isAuthorized, redactedBody := db.authorizeUserForChannels(docID, toRevID, nil, fromRevision.Delta.ToChannels, fromRevision.Delta.ToDeleted, encodeRevisions(ctx, docID, fromRevision.Delta.RevisionHistory)) if !isAuthorized { return nil, &redactedBody, nil } @@ -419,7 +448,7 @@ func (db *DatabaseCollectionWithUser) GetDelta(ctx context.Context, docID, fromR } deleted := toRevision.Deleted - isAuthorized, redactedBody := db.authorizeUserForChannels(docID, toRevID, toRevision.Channels, deleted, toRevision.History) + isAuthorized, redactedBody := db.authorizeUserForChannels(docID, toRevID, nil, toRevision.Channels, deleted, toRevision.History) if !isAuthorized { return nil, &redactedBody, nil } @@ -478,7 +507,7 @@ func (db *DatabaseCollectionWithUser) GetDelta(ctx context.Context, docID, fromR return nil, nil, nil } -func (col *DatabaseCollectionWithUser) authorizeUserForChannels(docID, revID string, channels base.Set, isDeleted bool, history Revisions) (isAuthorized bool, redactedRev DocumentRevision) { +func (col *DatabaseCollectionWithUser) authorizeUserForChannels(docID, revID string, cv *Version, channels base.Set, isDeleted bool, history Revisions) (isAuthorized bool, redactedRev DocumentRevision) { if col.user != nil { if err := col.user.AuthorizeAnyCollectionChannel(col.ScopeName, col.Name, channels); err != nil { @@ -490,6 +519,7 @@ func (col *DatabaseCollectionWithUser) authorizeUserForChannels(docID, revID str RevID: revID, History: history, Deleted: isDeleted, + CV: cv, } if isDeleted { // Deletions are denoted by the deleted message property during 2.x replication @@ -1045,7 +1075,7 @@ func (db *DatabaseCollectionWithUser) PutExistingCurrentVersion(ctx context.Cont if existingDoc != nil { doc, unmarshalErr := db.unmarshalDocumentWithXattrs(ctx, newDoc.ID, existingDoc.Body, existingDoc.Xattrs, existingDoc.Cas, DocUnmarshalRev) if unmarshalErr != nil { - return nil, nil, "", base.HTTPErrorf(http.StatusBadRequest, "Error unmarshaling exsiting doc") + return nil, nil, "", base.HTTPErrorf(http.StatusBadRequest, "Error unmarshaling existing doc") } matchRev = doc.CurrentRev } diff --git a/db/crud_test.go b/db/crud_test.go index 7d3e97c693..5f9a902f8f 100644 --- a/db/crud_test.go +++ b/db/crud_test.go @@ -20,6 +20,7 @@ import ( sgbucket "github.com/couchbase/sg-bucket" "github.com/couchbase/sync_gateway/base" + "github.com/couchbase/sync_gateway/channels" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1957,3 +1958,181 @@ func TestPutExistingCurrentVersionWithNoExistingDoc(t *testing.T) { assert.True(t, reflect.DeepEqual(syncData.HLV.PreviousVersions, pv)) assert.Equal(t, "1-3a208ea66e84121b528f05b5457d1134", syncData.CurrentRev) } + +// TestGetCVWithDocResidentInCache: +// - Two test cases, one with doc a user will have access to, one without +// - Purpose is to have a doc that is resident in rev cache and use the GetCV function to retrieve these docs +// - Assert that the doc the user has access to is corrected fetched +// - Assert the doc the user doesn't have access to is fetched but correctly redacted +func TestGetCVWithDocResidentInCache(t *testing.T) { + const docID = "doc1" + + testCases := []struct { + name string + docChannels []string + access bool + }{ + { + name: "getCVWithUserAccess", + docChannels: []string{"A"}, + access: true, + }, + { + name: "getCVWithoutUserAccess", + docChannels: []string{"B"}, + access: false, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + db, ctx := setupTestDB(t) + defer db.Close(ctx) + collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) + collection.ChannelMapper = channels.NewChannelMapper(ctx, channels.DocChannelsSyncFunction, db.Options.JavascriptTimeout) + + // Create a user with access to channel A + authenticator := db.Authenticator(base.TestCtx(t)) + user, err := authenticator.NewUser("alice", "letmein", channels.BaseSetOf(t, "A")) + require.NoError(t, err) + require.NoError(t, authenticator.Save(user)) + collection.user, err = authenticator.GetUser("alice") + require.NoError(t, err) + + // create doc with the channels for the test case + docBody := Body{"channels": testCase.docChannels} + rev, doc, err := collection.Put(ctx, docID, docBody) + require.NoError(t, err) + + vrs := doc.HLV.Version + src := doc.HLV.SourceID + sv := &Version{Value: vrs, SourceID: src} + revision, err := collection.GetCV(ctx, docID, sv, true) + require.NoError(t, err) + if testCase.access { + assert.Equal(t, rev, revision.RevID) + assert.Equal(t, sv, revision.CV) + assert.Equal(t, docID, revision.DocID) + assert.Equal(t, []byte(`{"channels":["A"]}`), revision.BodyBytes) + } else { + assert.Equal(t, rev, revision.RevID) + assert.Equal(t, sv, revision.CV) + assert.Equal(t, docID, revision.DocID) + assert.Equal(t, []byte(RemovedRedactedDocument), revision.BodyBytes) + } + }) + } +} + +// TestGetByCVForDocNotResidentInCache: +// - Setup db with rev cache size of 1 +// - Put two docs forcing eviction of the first doc +// - Use GetCV function to fetch the first doc, forcing the rev cache to load the doc from bucket +// - Assert the doc revision fetched is correct to the first doc we created +func TestGetByCVForDocNotResidentInCache(t *testing.T) { + db, ctx := SetupTestDBWithOptions(t, DatabaseContextOptions{ + RevisionCacheOptions: &RevisionCacheOptions{ + Size: 1, + }, + }) + defer db.Close(ctx) + collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) + collection.ChannelMapper = channels.NewChannelMapper(ctx, channels.DocChannelsSyncFunction, db.Options.JavascriptTimeout) + + // Create a user with access to channel A + authenticator := db.Authenticator(base.TestCtx(t)) + user, err := authenticator.NewUser("alice", "letmein", channels.BaseSetOf(t, "A")) + require.NoError(t, err) + require.NoError(t, authenticator.Save(user)) + collection.user, err = authenticator.GetUser("alice") + require.NoError(t, err) + + const ( + doc1ID = "doc1" + doc2ID = "doc2" + ) + + revBody := Body{"channels": []string{"A"}} + rev, doc, err := collection.Put(ctx, doc1ID, revBody) + require.NoError(t, err) + + // put another doc that should evict first doc from cache + _, _, err = collection.Put(ctx, doc2ID, revBody) + require.NoError(t, err) + + // get by CV should force a load from bucket and have a cache miss + vrs := doc.HLV.Version + src := doc.HLV.SourceID + sv := &Version{Value: vrs, SourceID: src} + revision, err := collection.GetCV(ctx, doc1ID, sv, true) + require.NoError(t, err) + + // assert the fetched doc is the first doc we added and assert that we did in fact get cache miss + assert.Equal(t, int64(1), db.DbStats.Cache().RevisionCacheMisses.Value()) + assert.Equal(t, rev, revision.RevID) + assert.Equal(t, sv, revision.CV) + assert.Equal(t, doc1ID, revision.DocID) + assert.Equal(t, []byte(`{"channels":["A"]}`), revision.BodyBytes) +} + +// TestGetCVActivePathway: +// - Two test cases, one with doc a user will have access to, one without +// - Purpose is top specify nil CV to the GetCV function to force the GetActive code pathway +// - Assert doc that is created is fetched correctly when user has access to doc +// - Assert that correct error is returned when user has no access to the doc +func TestGetCVActivePathway(t *testing.T) { + const docID = "doc1" + + testCases := []struct { + name string + docChannels []string + access bool + }{ + { + name: "activeFetchWithUserAccess", + docChannels: []string{"A"}, + access: true, + }, + { + name: "activeFetchWithoutUserAccess", + docChannels: []string{"B"}, + access: false, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + db, ctx := setupTestDB(t) + defer db.Close(ctx) + collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) + collection.ChannelMapper = channels.NewChannelMapper(ctx, channels.DocChannelsSyncFunction, db.Options.JavascriptTimeout) + + // Create a user with access to channel A + authenticator := db.Authenticator(base.TestCtx(t)) + user, err := authenticator.NewUser("alice", "letmein", channels.BaseSetOf(t, "A")) + require.NoError(t, err) + require.NoError(t, authenticator.Save(user)) + collection.user, err = authenticator.GetUser("alice") + require.NoError(t, err) + + // test get active path by specifying nil cv + revBody := Body{"channels": testCase.docChannels} + rev, doc, err := collection.Put(ctx, docID, revBody) + require.NoError(t, err) + revision, err := collection.GetCV(ctx, docID, nil, true) + + if testCase.access == true { + require.NoError(t, err) + vrs := doc.HLV.Version + src := doc.HLV.SourceID + sv := &Version{Value: vrs, SourceID: src} + assert.Equal(t, rev, revision.RevID) + assert.Equal(t, sv, revision.CV) + assert.Equal(t, docID, revision.DocID) + assert.Equal(t, []byte(`{"channels":["A"]}`), revision.BodyBytes) + } else { + require.Error(t, err) + assert.ErrorContains(t, err, ErrForbidden.Error()) + assert.Equal(t, DocumentRevision{}, revision) + } + }) + } +} diff --git a/db/database_test.go b/db/database_test.go index 87629ac178..0f96c0fe0c 100644 --- a/db/database_test.go +++ b/db/database_test.go @@ -1839,7 +1839,7 @@ func TestChannelQuery(t *testing.T) { db, ctx := setupTestDB(t) defer db.Close(ctx) - collection := GetSingleDatabaseCollectionWithUser(t, db) + collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) _, err := collection.UpdateSyncFun(ctx, `function(doc, oldDoc) { channel(doc.channels); }`) diff --git a/db/document.go b/db/document.go index 04242836df..d8e43e7a6d 100644 --- a/db/document.go +++ b/db/document.go @@ -103,6 +103,17 @@ type SyncData struct { removedRevisionBodyKeys map[string]string // keys of non-winning revisions that have been removed (and so may require deletion), indexed by revID } +// determine set of current channels based on removal entries. +func (sd *SyncData) getCurrentChannels() base.Set { + ch := base.SetOf() + for channelName, channelRemoval := range sd.Channels { + if channelRemoval == nil || channelRemoval.Seq == 0 { + ch.Add(channelName) + } + } + return ch +} + func (sd *SyncData) HashRedact(salt string) SyncData { // Creating a new SyncData with the redacted info. We copy all the information which stays the same and create new @@ -183,12 +194,11 @@ type Document struct { rawUserXattr []byte // Raw user xattr as retrieved from the bucket metadataOnlyUpdate *MetadataOnlyUpdate // Contents of _mou xattr, marshalled/unmarshalled with document from xattrs - Deleted bool - DocExpiry uint32 - RevID string - DocAttachments AttachmentsMeta - inlineSyncData bool - currentRevChannels base.Set // A base.Set of the current revision's channels (determined by SyncData.Channels at UnmarshalJSON time) + Deleted bool + DocExpiry uint32 + RevID string + DocAttachments AttachmentsMeta + inlineSyncData bool } type historyOnlySyncData struct { @@ -917,7 +927,6 @@ func (doc *Document) updateChannels(ctx context.Context, newChannels base.Set) ( doc.updateChannelHistory(channel, doc.Sequence, true) } } - doc.currentRevChannels = newChannels if changed != nil { base.InfofCtx(ctx, base.KeyCRUD, "\tDoc %q / %q in channels %q", base.UD(doc.ID), doc.CurrentRev, base.UD(newChannels)) changedChannels, err = channels.SetFromArray(changed, channels.KeepStar) @@ -1027,17 +1036,6 @@ func (doc *Document) UnmarshalJSON(data []byte) error { doc.SyncData = *syncData.SyncData } - // determine current revision's channels and store in-memory (avoids doc.Channels iteration at access-check time) - if len(doc.Channels) > 0 { - ch := base.SetOf() - for channelName, channelRemoval := range doc.Channels { - if channelRemoval == nil || channelRemoval.Seq == 0 { - ch.Add(channelName) - } - } - doc.currentRevChannels = ch - } - // Unmarshal the rest of the doc body as map[string]interface{} if err := doc._body.Unmarshal(data); err != nil { return pkgerrors.WithStack(base.RedactErrorf("Failed to UnmarshalJSON() doc with id: %s. Error: %v", base.UD(doc.ID), err)) diff --git a/db/document_test.go b/db/document_test.go index 8ab2ab5b58..d1e5e70221 100644 --- a/db/document_test.go +++ b/db/document_test.go @@ -364,11 +364,11 @@ func TestRevAndVersion(t *testing.T) { SourceID: test.source, Version: test.version, } - marshalledDoc, marshalledXattr, err := document.MarshalWithXattr() + marshalledDoc, marshalledSyncXattr, _, err := document.MarshalWithXattrs() require.NoError(t, err) newDocument := NewDocument("docID") - err = newDocument.UnmarshalWithXattr(ctx, marshalledDoc, marshalledXattr, DocUnmarshalAll) + err = newDocument.UnmarshalWithXattr(ctx, marshalledDoc, marshalledSyncXattr, DocUnmarshalAll) require.NoError(t, err) require.Equal(t, test.revTreeID, newDocument.CurrentRev) require.Equal(t, expectedSequence, newSyncData.Sequence) diff --git a/db/hybrid_logical_vector_test.go b/db/hybrid_logical_vector_test.go index 102bc305fd..21cc013fd6 100644 --- a/db/hybrid_logical_vector_test.go +++ b/db/hybrid_logical_vector_test.go @@ -277,7 +277,7 @@ func TestHLVImport(t *testing.T) { db, ctx := setupTestDB(t) defer db.Close(ctx) - collection := GetSingleDatabaseCollectionWithUser(t, db) + collection, ctx := GetSingleDatabaseCollectionWithUser(ctx, t, db) localSource := collection.dbCtx.BucketUUID // 1. Test standard import of an SDK write @@ -285,7 +285,7 @@ func TestHLVImport(t *testing.T) { standardImportBody := []byte(`{"prop":"value"}`) cas, err := collection.dataStore.WriteCas(standardImportKey, 0, 0, standardImportBody, sgbucket.Raw) require.NoError(t, err, "write error") - _, err = collection.ImportDocRaw(ctx, standardImportKey, standardImportBody, nil, nil, false, cas, nil, ImportFromFeed) + _, err = collection.ImportDocRaw(ctx, standardImportKey, standardImportBody, nil, false, cas, nil, ImportFromFeed) require.NoError(t, err, "import error") importedDoc, _, err := collection.GetDocWithXattr(ctx, standardImportKey, DocUnmarshalAll) @@ -304,9 +304,8 @@ func TestHLVImport(t *testing.T) { existingBody, existingXattrs, cas, err := collection.dataStore.GetWithXattrs(ctx, existingHLVKey, []string{base.SyncXattrName}) require.NoError(t, err) - existingXattr := existingXattrs[base.SyncXattrName] - _, err = collection.ImportDocRaw(ctx, existingHLVKey, existingBody, existingXattr, nil, false, cas, nil, ImportFromFeed) + _, err = collection.ImportDocRaw(ctx, existingHLVKey, existingBody, existingXattrs, false, cas, nil, ImportFromFeed) require.NoError(t, err, "import error") importedDoc, _, err = collection.GetDocWithXattr(ctx, existingHLVKey, DocUnmarshalAll) @@ -359,7 +358,7 @@ func (h *HLVAgent) insertWithHLV(ctx context.Context, key string) (casOut uint64 h.xattrName: syncDataBytes, } - cas, err := h.datastore.WriteWithXattrs(ctx, key, 0, 0, docBody, xattrData, mutateInOpts) + cas, err := h.datastore.WriteWithXattrs(ctx, key, 0, 0, docBody, xattrData, nil, mutateInOpts) require.NoError(h.t, err) return cas } diff --git a/db/revision_cache_interface.go b/db/revision_cache_interface.go index f31a8d9f58..edf3032fac 100644 --- a/db/revision_cache_interface.go +++ b/db/revision_cache_interface.go @@ -368,7 +368,7 @@ func revCacheLoader(ctx context.Context, backingStore RevisionCacheBackingStore, return revCacheLoaderForDocument(ctx, backingStore, doc, id.RevID) } -// revCacheLoaderForCv will load a document from the bucket using the CV, comapre the fetched doc and the CV specified in the function, +// revCacheLoaderForCv will load a document from the bucket using the CV, compare the fetched doc and the CV specified in the function, // and will still return revid for purpose of populating the Rev ID lookup map on the cache func revCacheLoaderForCv(ctx context.Context, backingStore RevisionCacheBackingStore, id IDandCV) (bodyBytes []byte, history Revisions, channels base.Set, removed bool, attachments AttachmentsMeta, deleted bool, expiry *time.Time, revid string, err error) { cv := Version{ @@ -426,7 +426,7 @@ func revCacheLoaderForDocumentCV(ctx context.Context, backingStore RevisionCache if err = doc.HasCurrentVersion(cv); err != nil { return bodyBytes, history, channels, removed, attachments, deleted, doc.Expiry, revid, err } - channels = doc.currentRevChannels + channels = doc.SyncData.getCurrentChannels() revid = doc.CurrentRev return bodyBytes, history, channels, removed, attachments, deleted, doc.Expiry, revid, err diff --git a/db/revision_cache_test.go b/db/revision_cache_test.go index eb4bd765a0..1457e49530 100644 --- a/db/revision_cache_test.go +++ b/db/revision_cache_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/couchbase/sync_gateway/base" + "github.com/couchbase/sync_gateway/channels" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -50,7 +51,9 @@ func (t *testBackingStore) GetDocument(ctx context.Context, docid string, unmars Channels: base.SetOf("*"), }, } - doc.currentRevChannels = base.SetOf("*") + doc.Channels = channels.ChannelMap{ + "*": &channels.ChannelRemoval{RevID: doc.CurrentRev}, + } doc.HLV = &HybridLogicalVector{ SourceID: "test", @@ -829,7 +832,7 @@ func BenchmarkRevisionCacheRead(b *testing.B) { // TestLoaderMismatchInCV: // - Get doc that is not in cache by CV to trigger a load from bucket -// - Ensure the CV passed into teh GET operation won't match the doc in teh bucket +// - Ensure the CV passed into the GET operation won't match the doc in the bucket // - Assert we get error and the value is not loaded into the cache func TestLoaderMismatchInCV(t *testing.T) { cacheHitCounter, cacheMissCounter, getDocumentCounter, getRevisionCounter := base.SgwIntStat{}, base.SgwIntStat{}, base.SgwIntStat{}, base.SgwIntStat{} @@ -853,7 +856,7 @@ func TestLoaderMismatchInCV(t *testing.T) { // - Now perform two concurrent Gets, one by CV and one by revid on a document that doesn't exist in the cache // - This will trigger two concurrent loads from bucket in the CV code path and revid code path // - In doing so we will have two processes trying to update lookup maps at the same time and a race condition will appear -// - In doing so will cause us to potentially have two of teh same elements the cache, one with nothing referencing it +// - In doing so will cause us to potentially have two of the same elements the cache, one with nothing referencing it // - Assert after both gets are processed, that the cache only has one element in it and that both lookup maps have only one // element // - Grab the single element in the list and assert that both maps point to that element in the cache list @@ -909,10 +912,10 @@ func TestGetActive(t *testing.T) { Value: doc.Cas, } - // remove the entry form the rev cache to force teh cache to not have the active version in it + // remove the entry form the rev cache to force the cache to not have the active version in it collection.revisionCache.RemoveWithCV("doc", &expectedCV) - // call get active to get teh active version from the bucket + // call get active to get the active version from the bucket docRev, err := collection.revisionCache.GetActive(base.TestCtx(t), "doc") assert.NoError(t, err) assert.Equal(t, rev1id, docRev.RevID) diff --git a/rest/replicatortest/replicator_test.go b/rest/replicatortest/replicator_test.go index d5ebd07f9f..2637633662 100644 --- a/rest/replicatortest/replicator_test.go +++ b/rest/replicatortest/replicator_test.go @@ -8596,6 +8596,6 @@ func TestReplicatorUpdateHLVOnPut(t *testing.T) { assert.NoError(t, err) uintCAS = base.HexCasToUint64(syncData.Cas) - // TODO: assert that the SourceID and Verison pair are preserved correctly pending CBG-3211 + // TODO: assert that the SourceID and Version pair are preserved correctly pending CBG-3211 assert.Equal(t, uintCAS, syncData.HLV.CurrentVersionCAS) }