• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

couchbase / sync_gateway / 487

10 Jul 2025 01:51PM UTC coverage: 65.002% (+0.5%) from 64.538%
487

push

jenkins

torcolvin
Merge remote-tracking branch 'origin/release/anemone' into CBG-4704

2521 of 3694 new or added lines in 50 files covered. (68.25%)

218 existing lines in 26 files now uncovered.

39896 of 61377 relevant lines covered (65.0%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.79
/db/revision_cache_interface.go
1
/*
2
Copyright 2019-Present Couchbase, Inc.
3

4
Use of this software is governed by the Business Source License included in
5
the file licenses/BSL-Couchbase.txt.  As of the Change Date specified in that
6
file, in accordance with the Business Source License, use of this software will
7
be governed by the Apache License, Version 2.0, included in the file
8
licenses/APL2.txt.
9
*/
10

11
package db
12

13
import (
14
        "context"
15
        "time"
16

17
        "github.com/couchbase/sync_gateway/base"
18
)
19

20
const (
21
        // DefaultRevisionCacheSize is the number of recently-accessed doc revisions to cache in RAM
22
        DefaultRevisionCacheSize uint32 = 5000
23

24
        // DefaultRevisionCacheShardCount is the default number of shards to use for the revision cache
25
        DefaultRevisionCacheShardCount uint16 = 16
26
)
27

28
// RevisionCache is an interface that can be used to fetch a DocumentRevision for a Doc ID and Rev ID pair.
29
type RevisionCache interface {
30

31
        // GetWithRev returns the given revision, and stores if not already cached.
32
        // When includeDelta=true, the returned DocumentRevision will include delta - requires additional locking during retrieval.
33
        GetWithRev(ctx context.Context, docID, revID string, collectionID uint32, includeDelta bool) (DocumentRevision, error)
34

35
        // GetWithCV returns the given revision by CV, and stores if not already cached.
36
        // When includeBody=true, the returned DocumentRevision will include a mutable shallow copy of the marshaled body.
37
        // When includeDelta=true, the returned DocumentRevision will include delta - requires additional locking during retrieval.
38
        GetWithCV(ctx context.Context, docID string, cv *Version, collectionID uint32, includeDelta bool) (DocumentRevision, error)
39

40
        // GetActive returns the current revision for the given doc ID, and stores if not already cached.
41
        GetActive(ctx context.Context, docID string, collectionID uint32) (docRev DocumentRevision, err error)
42

43
        // Peek returns the given revision if present in the cache
44
        Peek(ctx context.Context, docID, revID string, collectionID uint32) (docRev DocumentRevision, found bool)
45

46
        // Put will store the given docRev in the cache
47
        Put(ctx context.Context, docRev DocumentRevision, collectionID uint32)
48

49
        // Upsert will remove existing value and re-create new one
50
        Upsert(ctx context.Context, docRev DocumentRevision, collectionID uint32)
51

52
        // RemoveWithRev evicts a revision from the cache using its revID.
53
        RemoveWithRev(ctx context.Context, docID, revID string, collectionID uint32)
54

55
        // RemoveWithCV evicts a revision from the cache using its current version.
56
        RemoveWithCV(ctx context.Context, docID string, cv *Version, collectionID uint32)
57

58
        // UpdateDelta stores the given toDelta value in the given rev if cached
59
        UpdateDelta(ctx context.Context, docID, revID string, collectionID uint32, toDelta RevisionDelta)
60

61
        // UpdateDeltaCV stores the given toDelta value in the given rev if cached but will look up in cache by cv
62
        UpdateDeltaCV(ctx context.Context, docID string, cv *Version, collectionID uint32, toDelta RevisionDelta)
63
}
64

65
const (
66
        RevCacheIncludeDelta = true
67
        RevCacheOmitDelta    = false
68
)
69

70
// Force compile-time check of all RevisionCache types for interface
71
var _ RevisionCache = &LRURevisionCache{}
72
var _ RevisionCache = &ShardedLRURevisionCache{}
73
var _ RevisionCache = &BypassRevisionCache{}
74

75
// NewRevisionCache returns a RevisionCache implementation for the given config options.
76
func NewRevisionCache(cacheOptions *RevisionCacheOptions, backingStores map[uint32]RevisionCacheBackingStore, cacheStats *base.CacheStats) RevisionCache {
1✔
77

1✔
78
        // If cacheOptions is not passed in, use defaults
1✔
79
        if cacheOptions == nil {
2✔
80
                cacheOptions = DefaultRevisionCacheOptions()
1✔
81
        }
1✔
82

83
        if cacheOptions.MaxItemCount == 0 {
2✔
84
                bypassStat := cacheStats.RevisionCacheBypass
1✔
85
                return NewBypassRevisionCache(backingStores, bypassStat)
1✔
86
        }
1✔
87

88
        cacheHitStat := cacheStats.RevisionCacheHits
1✔
89
        cacheMissStat := cacheStats.RevisionCacheMisses
1✔
90
        cacheNumItemsStat := cacheStats.RevisionCacheNumItems
1✔
91
        cacheMemoryStat := cacheStats.RevisionCacheTotalMemory
1✔
92
        if cacheNumItemsStat.Value() != 0 {
2✔
93
                cacheNumItemsStat.Set(0)
1✔
94
        }
1✔
95
        if cacheMemoryStat.Value() != 0 {
2✔
96
                cacheMemoryStat.Set(0)
1✔
97
        }
1✔
98

99
        if cacheOptions.ShardCount > 1 {
2✔
100
                return NewShardedLRURevisionCache(cacheOptions, backingStores, cacheHitStat, cacheMissStat, cacheNumItemsStat, cacheMemoryStat)
1✔
101
        }
1✔
102

103
        return NewLRURevisionCache(cacheOptions, backingStores, cacheHitStat, cacheMissStat, cacheNumItemsStat, cacheMemoryStat)
1✔
104
}
105

106
type RevisionCacheOptions struct {
107
        MaxItemCount uint32
108
        MaxBytes     int64
109
        ShardCount   uint16
110
}
111

112
func DefaultRevisionCacheOptions() *RevisionCacheOptions {
1✔
113
        return &RevisionCacheOptions{
1✔
114
                MaxItemCount: DefaultRevisionCacheSize,
1✔
115
                ShardCount:   DefaultRevisionCacheShardCount,
1✔
116
        }
1✔
117
}
1✔
118

119
// RevisionCacheBackingStore is the interface required to be passed into a RevisionCache constructor to provide a backing store for loading documents.
120
type RevisionCacheBackingStore interface {
121
        GetDocument(ctx context.Context, docid string, unmarshalLevel DocumentUnmarshalLevel) (doc *Document, err error)
122
        getRevision(ctx context.Context, doc *Document, revid string) ([]byte, AttachmentsMeta, error)
123
        getCurrentVersion(ctx context.Context, doc *Document, cv Version) ([]byte, AttachmentsMeta, error)
124
}
125

126
// collectionRevisionCache is a view of a revision cache for a collection.
127
type collectionRevisionCache struct {
128
        revCache     *RevisionCache
129
        collectionID uint32
130
}
131

132
// NewCollectionRevisionCache returns a view of a revision cache for a collection.
133
func newCollectionRevisionCache(revCache *RevisionCache, collectionID uint32) collectionRevisionCache {
1✔
134
        return collectionRevisionCache{
1✔
135
                revCache:     revCache,
1✔
136
                collectionID: collectionID,
1✔
137
        }
1✔
138
}
1✔
139

140
// Get is for per collection access to Get method
141
func (c *collectionRevisionCache) GetWithRev(ctx context.Context, docID, revID string, includeDelta bool) (DocumentRevision, error) {
1✔
142
        return (*c.revCache).GetWithRev(ctx, docID, revID, c.collectionID, includeDelta)
1✔
143
}
1✔
144

145
// Get is for per collection access to Get method
146
func (c *collectionRevisionCache) GetWithCV(ctx context.Context, docID string, cv *Version, includeDelta bool) (DocumentRevision, error) {
1✔
147
        return (*c.revCache).GetWithCV(ctx, docID, cv, c.collectionID, includeDelta)
1✔
148
}
1✔
149

150
// GetActive is for per collection access to GetActive method
151
func (c *collectionRevisionCache) GetActive(ctx context.Context, docID string) (DocumentRevision, error) {
1✔
152
        return (*c.revCache).GetActive(ctx, docID, c.collectionID)
1✔
153
}
1✔
154

155
// Peek is for per collection access to Peek method
156
func (c *collectionRevisionCache) Peek(ctx context.Context, docID, revID string) (DocumentRevision, bool) {
1✔
157
        return (*c.revCache).Peek(ctx, docID, revID, c.collectionID)
1✔
158
}
1✔
159

160
// Put is for per collection access to Put method
161
func (c *collectionRevisionCache) Put(ctx context.Context, docRev DocumentRevision) {
1✔
162
        (*c.revCache).Put(ctx, docRev, c.collectionID)
1✔
163
}
1✔
164

165
// Upsert is for per collection access to Upsert method
166
func (c *collectionRevisionCache) Upsert(ctx context.Context, docRev DocumentRevision) {
×
167
        (*c.revCache).Upsert(ctx, docRev, c.collectionID)
×
168
}
×
169

170
// RemoveWithRev is for per collection access to Remove method
171
func (c *collectionRevisionCache) RemoveWithRev(ctx context.Context, docID, revID string) {
1✔
172
        (*c.revCache).RemoveWithRev(ctx, docID, revID, c.collectionID)
1✔
173
}
1✔
174

175
// RemoveWithCV is for per collection access to Remove method
176
func (c *collectionRevisionCache) RemoveWithCV(ctx context.Context, docID string, cv *Version) {
1✔
177
        (*c.revCache).RemoveWithCV(ctx, docID, cv, c.collectionID)
1✔
178
}
1✔
179

180
// UpdateDelta is for per collection access to UpdateDelta method
181
func (c *collectionRevisionCache) UpdateDelta(ctx context.Context, docID, revID string, toDelta RevisionDelta) {
×
182
        (*c.revCache).UpdateDelta(ctx, docID, revID, c.collectionID, toDelta)
×
183
}
×
184

185
// UpdateDeltaCV is for per collection access to UpdateDeltaCV method
NEW
186
func (c *collectionRevisionCache) UpdateDeltaCV(ctx context.Context, docID string, cv *Version, toDelta RevisionDelta) {
×
NEW
187
        (*c.revCache).UpdateDeltaCV(ctx, docID, cv, c.collectionID, toDelta)
×
NEW
188
}
×
189

190
// DocumentRevision stored and returned by the rev cache
191
type DocumentRevision struct {
192
        DocID string
193
        RevID string
194
        // BodyBytes contains the raw document, with no special properties.
195
        BodyBytes   []byte
196
        History     Revisions
197
        Channels    base.Set
198
        Expiry      *time.Time
199
        Attachments AttachmentsMeta
200
        Delta       *RevisionDelta
201
        Deleted     bool
202
        Removed     bool  // True if the revision is a removal.
203
        MemoryBytes int64 // storage of the doc rev bytes measurement, includes size of delta when present too
204
        CV          *Version
205
        hlvHistory  string
206
}
207

208
// MutableBody returns a deep copy of the given document revision as a plain body (without any special properties)
209
// Callers are free to modify any of this body without affecting the document revision.
210
func (rev *DocumentRevision) MutableBody() (b Body, err error) {
1✔
211
        if err := b.Unmarshal(rev.BodyBytes); err != nil {
1✔
212
                return nil, err
×
213
        }
×
214

215
        return b, nil
1✔
216
}
217

218
// Body returns an unmarshalled body that is kept in the document revision to produce shallow copies.
219
// If an unmarshalled copy is not available in the document revision, it makes a copy from the raw body
220
// bytes and stores it in document revision itself before returning the body.
221
func (rev *DocumentRevision) Body() (b Body, err error) {
1✔
222

1✔
223
        if err := b.Unmarshal(rev.BodyBytes); err != nil {
1✔
224
                return nil, err
×
225
        }
×
226

227
        return b, nil
1✔
228
}
229

230
// Inject1xBodyProperties will inject special properties (_rev etc) into document body avoiding unnecessary marshal work
231
func (rev *DocumentRevision) Inject1xBodyProperties(ctx context.Context, db *DatabaseCollectionWithUser, requestedHistory Revisions, attachmentsSince []string, showExp bool) ([]byte, error) {
1✔
232

1✔
233
        kvPairs := []base.KVPair{
1✔
234
                {Key: BodyId, Val: rev.DocID},
1✔
235
                {Key: BodyRev, Val: rev.RevID},
1✔
236
        }
1✔
237

1✔
238
        if requestedHistory != nil {
2✔
239
                kvPairs = append(kvPairs, base.KVPair{Key: BodyRevisions, Val: requestedHistory})
1✔
240
        }
1✔
241

242
        if showExp && rev.Expiry != nil && !rev.Expiry.IsZero() {
2✔
243
                kvPairs = append(kvPairs, base.KVPair{Key: BodyExpiry, Val: rev.Expiry.Format(time.RFC3339)})
1✔
244
        }
1✔
245

246
        if rev.Deleted {
2✔
247
                kvPairs = append(kvPairs, base.KVPair{Key: BodyDeleted, Val: rev.Deleted})
1✔
248
        }
1✔
249

250
        if attachmentsSince != nil {
2✔
251
                if len(rev.Attachments) > 0 {
2✔
252
                        minRevpos := 1
1✔
253
                        if len(attachmentsSince) > 0 {
2✔
254
                                ancestor := rev.History.findAncestor(attachmentsSince)
1✔
255
                                if ancestor != "" {
2✔
256
                                        minRevpos, _ = ParseRevID(ctx, ancestor)
1✔
257
                                        minRevpos++
1✔
258
                                }
1✔
259
                        }
260
                        bodyAtts, err := db.loadAttachmentsData(rev.Attachments, minRevpos, rev.DocID)
1✔
261
                        if err != nil {
1✔
262
                                return nil, err
×
263
                        }
×
264
                        DeleteAttachmentVersion(bodyAtts)
1✔
265
                        kvPairs = append(kvPairs, base.KVPair{Key: BodyAttachments, Val: bodyAtts})
1✔
266
                }
267
        } else if rev.Attachments != nil {
2✔
268
                // Stamp attachment metadata back into the body
1✔
269
                DeleteAttachmentVersion(rev.Attachments)
1✔
270
                kvPairs = append(kvPairs, base.KVPair{Key: BodyAttachments, Val: rev.Attachments})
1✔
271
        }
1✔
272

273
        newBytes, err := base.InjectJSONProperties(rev.BodyBytes, kvPairs...)
1✔
274
        if err != nil {
2✔
275
                return nil, err
1✔
276
        }
1✔
277
        return newBytes, nil
1✔
278
}
279

280
// Mutable1xBody returns a copy of the given document revision as a 1.x style body (with special properties)
281
// Callers are free to modify this body without affecting the document revision.
282
func (rev *DocumentRevision) Mutable1xBody(ctx context.Context, db *DatabaseCollectionWithUser, requestedHistory Revisions, attachmentsSince []string, showExp bool, showCV bool) (b Body, err error) {
1✔
283
        b, err = rev.Body()
1✔
284
        if err != nil {
1✔
285
                return nil, err
×
286
        }
×
287
        if b == nil {
2✔
288
                return nil, base.RedactErrorf("null doc body for docID: %s revID: %s", base.UD(rev.DocID), base.UD(rev.RevID))
1✔
289
        }
1✔
290

291
        b[BodyId] = rev.DocID
1✔
292
        b[BodyRev] = rev.RevID
1✔
293

1✔
294
        // Add revision metadata:
1✔
295
        if requestedHistory != nil {
2✔
296
                b[BodyRevisions] = requestedHistory
1✔
297
        }
1✔
298

299
        if showExp && rev.Expiry != nil && !rev.Expiry.IsZero() {
2✔
300
                b[BodyExpiry] = rev.Expiry.Format(time.RFC3339)
1✔
301
        }
1✔
302

303
        if showCV && rev.CV != nil {
2✔
304
                b["_cv"] = rev.CV.String()
1✔
305
        }
1✔
306

307
        if rev.Deleted {
2✔
308
                b[BodyDeleted] = true
1✔
309
        }
1✔
310

311
        // Add attachment data if requested:
312
        if attachmentsSince != nil {
2✔
313
                if len(rev.Attachments) > 0 {
2✔
314
                        minRevpos := 1
1✔
315
                        if len(attachmentsSince) > 0 {
2✔
316
                                ancestor := rev.History.findAncestor(attachmentsSince)
1✔
317
                                if ancestor != "" {
2✔
318
                                        minRevpos, _ = ParseRevID(ctx, ancestor)
1✔
319
                                        minRevpos++
1✔
320
                                }
1✔
321
                        }
322
                        bodyAtts, err := db.loadAttachmentsData(rev.Attachments, minRevpos, rev.DocID)
1✔
323
                        if err != nil {
1✔
324
                                return nil, err
×
325
                        }
×
326
                        DeleteAttachmentVersion(bodyAtts)
1✔
327
                        b[BodyAttachments] = bodyAtts
1✔
328
                }
329
        } else if rev.Attachments != nil {
2✔
330
                // Stamp attachment metadata back into the body
1✔
331
                DeleteAttachmentVersion(rev.Attachments)
1✔
332
                b[BodyAttachments] = rev.Attachments
1✔
333
        }
1✔
334

335
        return b, nil
1✔
336
}
337

338
// As1xBytes returns a byte slice representing the 1.x style body, containing special properties (i.e. _id, _rev, _attachments, etc.)
339
func (rev *DocumentRevision) As1xBytes(ctx context.Context, db *DatabaseCollectionWithUser, requestedHistory Revisions, attachmentsSince []string, showExp bool) (b []byte, err error) {
1✔
340
        // inject the special properties
1✔
341
        body1x, err := rev.Inject1xBodyProperties(ctx, db, requestedHistory, attachmentsSince, showExp)
1✔
342
        if err != nil {
1✔
343
                return nil, err
×
344
        }
×
345

346
        return body1x, nil
1✔
347
}
348

349
type IDAndRev struct {
350
        DocID        string
351
        RevID        string
352
        CollectionID uint32
353
}
354

355
type IDandCV struct {
356
        DocID        string
357
        Version      uint64
358
        Source       string
359
        CollectionID uint32
360
}
361

362
// RevisionDelta stores data about a delta between a revision and ToRevID.
363
type RevisionDelta struct {
364
        ToRevID               string                  // Target revID for the delta
365
        ToCV                  string                  // Target CV for the delta
366
        DeltaBytes            []byte                  // The actual delta
367
        AttachmentStorageMeta []AttachmentStorageMeta // Storage metadata of all attachments present on ToRevID
368
        ToChannels            base.Set                // Full list of channels for the to revision
369
        RevisionHistory       []string                // Revision history from parent of ToRevID to source revID, in descending order
370
        HlvHistory            string                  // HLV History in CBL format
371
        ToDeleted             bool                    // Flag if ToRevID is a tombstone
372
        totalDeltaBytes       int64                   // totalDeltaBytes is the total bytes for channels, revisions and body on the delta itself
373
}
374

375
func newRevCacheDelta(deltaBytes []byte, fromRevID string, toRevision DocumentRevision, deleted bool, toRevAttStorageMeta []AttachmentStorageMeta) RevisionDelta {
1✔
376
        revDelta := RevisionDelta{
1✔
377
                ToRevID:               toRevision.RevID,
1✔
378
                ToCV:                  toRevision.CV.String(),
1✔
379
                DeltaBytes:            deltaBytes,
1✔
380
                AttachmentStorageMeta: toRevAttStorageMeta,
1✔
381
                ToChannels:            toRevision.Channels,
1✔
382
                RevisionHistory:       toRevision.History.parseAncestorRevisions(fromRevID),
1✔
383
                HlvHistory:            toRevision.hlvHistory,
1✔
384
                ToDeleted:             deleted,
1✔
385
        }
1✔
386
        revDelta.CalculateDeltaBytes()
1✔
387
        return revDelta
1✔
388
}
1✔
389

390
// This is the RevisionCacheLoaderFunc callback for the context's RevisionCache.
391
// Its job is to load a revision from the bucket when there's a cache miss.
392
func revCacheLoader(ctx context.Context, backingStore RevisionCacheBackingStore, id IDAndRev) (bodyBytes []byte, history Revisions, channels base.Set, removed bool, attachments AttachmentsMeta, deleted bool, expiry *time.Time, hlv *HybridLogicalVector, err error) {
1✔
393
        var doc *Document
1✔
394
        if doc, err = backingStore.GetDocument(ctx, id.DocID, DocUnmarshalSync); doc == nil {
2✔
395
                return bodyBytes, history, channels, removed, attachments, deleted, expiry, hlv, err
1✔
396
        }
1✔
397
        return revCacheLoaderForDocument(ctx, backingStore, doc, id.RevID)
1✔
398
}
399

400
// revCacheLoaderForCv will load a document from the bucket using the CV, compare the fetched doc and the CV specified in the function,
401
// and will still return revid for purpose of populating the Rev ID lookup map on the cache
402
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, hlv *HybridLogicalVector, err error) {
1✔
403
        cv := Version{
1✔
404
                Value:    id.Version,
1✔
405
                SourceID: id.Source,
1✔
406
        }
1✔
407
        var doc *Document
1✔
408
        if doc, err = backingStore.GetDocument(ctx, id.DocID, DocUnmarshalSync); doc == nil {
2✔
409
                return bodyBytes, history, channels, removed, attachments, deleted, expiry, revid, hlv, err
1✔
410
        }
1✔
411

412
        return revCacheLoaderForDocumentCV(ctx, backingStore, doc, cv)
1✔
413
}
414

415
// Common revCacheLoader functionality used either during a cache miss (from revCacheLoader), or directly when retrieving current rev from cache
416

417
func revCacheLoaderForDocument(ctx context.Context, backingStore RevisionCacheBackingStore, doc *Document, revid string) (bodyBytes []byte, history Revisions, channels base.Set, removed bool, attachments AttachmentsMeta, deleted bool, expiry *time.Time, hlv *HybridLogicalVector, err error) {
1✔
418
        if bodyBytes, attachments, err = backingStore.getRevision(ctx, doc, revid); err != nil {
2✔
419
                // If we can't find the revision (either as active or conflicted body from the document, or as old revision body backup), check whether
1✔
420
                // the revision was a channel removal. If so, we want to store as removal in the revision cache
1✔
421
                removalBodyBytes, removalHistory, activeChannels, isRemoval, isDelete, isRemovalErr := doc.IsChannelRemoval(ctx, revid)
1✔
422
                if isRemovalErr != nil {
1✔
NEW
423
                        return bodyBytes, history, channels, isRemoval, nil, isDelete, nil, hlv, isRemovalErr
×
424
                }
×
425

426
                if isRemoval {
2✔
427
                        return removalBodyBytes, removalHistory, activeChannels, isRemoval, nil, isDelete, nil, hlv, nil
1✔
428
                } else {
2✔
429
                        // If this wasn't a removal, return the original error from getRevision
1✔
430
                        return bodyBytes, history, channels, removed, nil, isDelete, nil, hlv, err
1✔
431
                }
1✔
432
        }
433
        deleted = doc.History[revid].Deleted
1✔
434

1✔
435
        validatedHistory, getHistoryErr := doc.History.getHistory(revid)
1✔
436
        if getHistoryErr != nil {
1✔
NEW
437
                return bodyBytes, history, channels, removed, nil, deleted, nil, hlv, getHistoryErr
×
438
        }
×
439
        history = encodeRevisions(ctx, doc.ID, validatedHistory)
1✔
440
        channels = doc.History[revid].Channels
1✔
441
        if doc.HLV != nil {
2✔
442
                hlv = doc.HLV
1✔
443
        }
1✔
444

445
        return bodyBytes, history, channels, removed, attachments, deleted, doc.Expiry, hlv, err
1✔
446
}
447

448
// revCacheLoaderForDocumentCV used either during cache miss (from revCacheLoaderForCv), or used directly when getting current active CV from cache
449
// nolint:staticcheck
450
func revCacheLoaderForDocumentCV(ctx context.Context, backingStore RevisionCacheBackingStore, doc *Document, cv Version) (bodyBytes []byte, history Revisions, channels base.Set, removed bool, attachments AttachmentsMeta, deleted bool, expiry *time.Time, revid string, hlv *HybridLogicalVector, err error) {
1✔
451
        if bodyBytes, attachments, err = backingStore.getCurrentVersion(ctx, doc, cv); err != nil {
2✔
452
                // TODO: CBG-3814 - pending support of channel removal for CV
1✔
453
                base.ErrorfCtx(ctx, "pending CBG-3814 support of channel removal for CV: %v", err)
1✔
454
        }
1✔
455

456
        deleted = doc.Deleted
1✔
457
        channels = doc.SyncData.getCurrentChannels()
1✔
458
        revid = doc.CurrentRev
1✔
459
        hlv = doc.HLV
1✔
460
        validatedHistory, getHistoryErr := doc.History.getHistory(revid)
1✔
461
        if getHistoryErr != nil {
1✔
NEW
462
                return bodyBytes, history, channels, removed, attachments, deleted, doc.Expiry, revid, hlv, err
×
NEW
463
        }
×
464
        history = encodeRevisions(ctx, doc.ID, validatedHistory)
1✔
465

1✔
466
        return bodyBytes, history, channels, removed, attachments, deleted, doc.Expiry, revid, hlv, err
1✔
467
}
468

469
func (c *DatabaseCollection) getCurrentVersion(ctx context.Context, doc *Document, cv Version) (bodyBytes []byte, attachments AttachmentsMeta, err error) {
1✔
470
        if err = doc.HasCurrentVersion(ctx, cv); err != nil {
2✔
471
                bodyBytes, err = c.getOldRevisionJSON(ctx, doc.ID, base.Crc32cHashString([]byte(cv.String())))
1✔
472
                if err != nil || bodyBytes == nil {
2✔
473
                        return nil, nil, err
1✔
474
                }
1✔
475
        } else {
1✔
476
                bodyBytes, err = doc.BodyBytes(ctx)
1✔
477
                if err != nil {
1✔
NEW
478
                        base.WarnfCtx(ctx, "Marshal error when retrieving active current version body: %v", err)
×
NEW
479
                        return nil, nil, err
×
NEW
480
                }
×
481
        }
482

483
        attachments = doc.Attachments
1✔
484

1✔
485
        // handle backup revision inline attachments, or pre-2.5 meta
1✔
486
        if inlineAtts, cleanBodyBytes, _, err := extractInlineAttachments(bodyBytes); err != nil {
1✔
NEW
487
                return nil, nil, err
×
488
        } else if len(inlineAtts) > 0 {
1✔
NEW
489
                // we found some inline attachments, so merge them with attachments, and update the bodies
×
NEW
490
                attachments = mergeAttachments(inlineAtts, attachments)
×
NEW
491
                bodyBytes = cleanBodyBytes
×
NEW
492
        }
×
493
        return bodyBytes, attachments, err
1✔
494
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc