GetDelta attempts to return the delta between fromRevId and toRevId. If the delta can't be generated, returns nil. Delta generation is synchronized per fromRev via a shared revision cache value lock to avoid multiple clients generating the same delta simultaneously.
(ctx context.Context, docID, fromRev, toRev string)
| 450 | // GetDelta attempts to return the delta between fromRevId and toRevId. If the delta can't be generated, returns nil. |
| 451 | // Delta generation is synchronized per fromRev via a shared revision cache value lock to avoid multiple clients generating the same delta simultaneously. |
| 452 | func (db *DatabaseCollectionWithUser) GetDelta(ctx context.Context, docID, fromRev, toRev string) (delta *RevisionDelta, redactedRev *DocumentRevision, err error) { |
| 453 | if docID == "" || fromRev == "" || toRev == "" { |
| 454 | return nil, nil, nil |
| 455 | } |
| 456 | |
| 457 | var initialFromRevision DocumentRevision |
| 458 | var fromRevVrs Version |
| 459 | fromRevIsCV := !base.IsRevTreeID(fromRev) |
| 460 | if fromRevIsCV { |
| 461 | fromRevVrs, err = ParseVersion(fromRev) |
| 462 | if err != nil { |
| 463 | return nil, nil, err |
| 464 | } |
| 465 | // It is possible delta source will not be resident in the cache and we may want to lookup to the bucket for a backup revision |
| 466 | initialFromRevision, err = db.revisionCache.GetWithCV(ctx, docID, &fromRevVrs, RevCacheIncludeDelta, true) |
| 467 | if err != nil { |
| 468 | return nil, nil, err |
| 469 | } |
| 470 | } else { |
| 471 | initialFromRevision, err = db.revisionCache.GetWithRev(ctx, docID, fromRev, RevCacheIncludeDelta) |
| 472 | if err != nil { |
| 473 | return nil, nil, err |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | // If the fromRevision is a removal cache entry (no body), but the user has access to that removal, then just |
| 478 | // return 404 missing to indicate that the body of the revision is no longer available. |
| 479 | // Delta can't be generated if we don't have the fromRevision body. |
| 480 | if initialFromRevision.Removed { |
| 481 | return nil, nil, ErrMissing |
| 482 | } |
| 483 | |
| 484 | // If the fromRevision was a tombstone, then return error to tell delta sync to send full body replication. |
| 485 | if initialFromRevision.Deleted { |
| 486 | return nil, nil, base.ErrDeltaSourceIsTombstone |
| 487 | } |
| 488 | |
| 489 | // If delta is found, check whether it is a delta for the toRevID we want. |
| 490 | if initialFromRevision.Delta != nil && (initialFromRevision.Delta.ToCV == toRev || initialFromRevision.Delta.ToRevID == toRev) { |
| 491 | isAuthorized, redactedBody := db.authorizeUserForChannels(docID, toRev, initialFromRevision.CV, initialFromRevision.Delta.ToChannels, initialFromRevision.Delta.ToDeleted, encodeRevisions(ctx, docID, initialFromRevision.Delta.RevisionHistory)) |
| 492 | if !isAuthorized { |
| 493 | return nil, &redactedBody, nil |
| 494 | } |
| 495 | db.dbStats().DeltaSync().DeltaCacheHit.Add(1) |
| 496 | return initialFromRevision.Delta, nil, nil |
| 497 | } |
| 498 | |
| 499 | if initialFromRevision.BodyBytes != nil { |
| 500 | // Acquire a delta lock to generate delta (ensuring only one toRev unmarshalling/diff for this fromRev and allow racing clients to share the result) |
| 501 | initialFromRevision.RevCacheValueDeltaLock.Lock() |
| 502 | defer initialFromRevision.RevCacheValueDeltaLock.Unlock() |
| 503 | |
| 504 | // fromRevisionForDiff is a version of the fromRevision that is guarded by the delta lock that we will use to generate the delta (or check again for a newly cached delta) |
| 505 | var fromRevisionForDiff DocumentRevision |
| 506 | if fromRevIsCV { |
| 507 | fromRevisionForDiff, err = db.revisionCache.GetWithCV(ctx, docID, &fromRevVrs, RevCacheIncludeDelta, true) |
| 508 | if err != nil { |
| 509 | return nil, nil, err |