Skip to content

Commit 8187d9a

Browse files
adamcfraserbbrks
andauthored
CBG-2055 Do not evaluate revpos during storeAttachments (#5557)
* CBG-2055 Do not evaluate revpos during storeAttachments Revpos shouldn't be used as a criteria for attachment persistence for incoming writes. In particular during storeAttachments, a variety of circumstances can result in changes to the incoming revpos, and we don't want to fail to persist or remove attachments based on revpos. In addition to removing the revpos check in storeAttachments, adds some additional handling in blip_handler for different flavors of revpos being sent by CBL to ensure it's being set to the incoming revID when the attachment doesn't exist. * Update rest/blip_api_test.go Co-authored-by: Ben Brooks <[email protected]> * Ensure version is accurate for new attachments with shared digests * Add test for legacy attachment name change * Additional test case for legacy attachment w/out name change Co-authored-by: Ben Brooks <[email protected]>
1 parent 24e39e4 commit 8187d9a

6 files changed

+305
-70
lines changed

db/attachment.go

+13-12
Original file line numberDiff line numberDiff line change
@@ -104,23 +104,18 @@ func (db *Database) storeAttachments(doc *Document, newAttachmentsMeta Attachmen
104104
if meta["stub"] != true {
105105
return nil, base.HTTPErrorf(400, "Missing data of attachment %q", name)
106106
}
107-
108-
revpos, ok := base.ToInt64(meta["revpos"])
109-
if !ok || revpos < 1 {
110-
return nil, base.HTTPErrorf(400, "Missing/invalid revpos in stub attachment %q", name)
111-
}
112107
// Try to look up the attachment in ancestor attachments
113108
if parentAttachments == nil {
114109
parentAttachments = db.retrieveAncestorAttachments(doc, parentRev, docHistory)
115110
}
116111

112+
// Note: in a non-conflict CAS retry, parentAttachments may be nil, because the attachment
113+
// data was persisted prior to the CAS failure writing the doc. In this scenario the
114+
// incoming doc attachment metadata has already been updated to stub=true to avoid attempting to
115+
// persist the attachment again, even though there is not an attachment on an ancestor.
117116
if parentAttachments != nil {
118117
if parentAttachment := parentAttachments[name]; parentAttachment != nil {
119-
parentrevpos, ok := base.ToInt64(parentAttachment.(map[string]interface{})["revpos"])
120-
121-
if ok && revpos <= parentrevpos {
122-
atts[name] = parentAttachment
123-
}
118+
atts[name] = parentAttachment
124119
}
125120
} else if meta["digest"] == nil {
126121
return nil, base.HTTPErrorf(400, "Missing digest in stub attachment %q", name)
@@ -295,12 +290,18 @@ func (db *Database) ForEachStubAttachment(body Body, minRevpos int, docID string
295290
if err != nil && !base.IsDocNotFoundError(err) {
296291
return err
297292
}
298-
if newData, err := callback(name, digest, data, meta); err != nil {
293+
newData, err := callback(name, digest, data, meta)
294+
if err != nil {
299295
return err
300-
} else if newData != nil {
296+
}
297+
if newData != nil {
301298
meta["data"] = newData
302299
delete(meta, "stub")
303300
delete(meta, "follows")
301+
} else {
302+
// Update version in the case where this is a new attachment on the doc sharing a V2 digest with
303+
// an existing attachment
304+
meta["ver"] = AttVersion2
304305
}
305306
}
306307
}

db/attachment_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -757,8 +757,8 @@ func TestStoreAttachments(t *testing.T) {
757757
revId, doc, err = db.Put("doc5", revBody)
758758
assert.Empty(t, revId, "The revId should be empty since revpos is not included in attachment")
759759
assert.Empty(t, doc, "The doc should be empty since revpos is not included in attachment")
760-
assert.Error(t, err, "It should throw 400 Missing/invalid revpos in stub attachment error")
761-
assert.Contains(t, err.Error(), "400 Missing/invalid revpos in stub attachment")
760+
assert.Error(t, err, "It should throw 400 Missing digest in stub attachment")
761+
assert.Contains(t, err.Error(), "400 Missing digest in stub attachment")
762762
}
763763

764764
// TestMigrateBodyAttachments will set up a document with an attachment in pre-2.5 metadata format, and test various upgrade scenarios.

db/blip_handler.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,9 @@ func (bh *blipHandler) handleRev(rq *blip.Message) (err error) {
951951
// Check if we have this attachment name already, if we do, continue check
952952
currentAttachment, ok := currentBucketDoc.Attachments[name]
953953
if !ok {
954+
// If we don't have this attachment already, ensure incoming revpos is greater than minRevPos, otherwise
955+
// update to ensure it's fetched and uploaded
956+
bodyAtts[name].(map[string]interface{})["revpos"], _ = ParseRevID(revID)
954957
continue
955958
}
956959

@@ -987,10 +990,9 @@ func (bh *blipHandler) handleRev(rq *blip.Message) (err error) {
987990
}
988991

989992
// Compare the revpos and attachment digest. If incoming revpos is less than or equal to minRevPos and
990-
// digest is different we need to override the revpos and set it to the current revision as the incoming
991-
// revpos must be invalid and we need to request it.
993+
// digest is different we need to override the revpos and set it to the current revision to ensure
994+
// the attachment is requested and stored
992995
if int(incomingAttachmentRevpos) <= minRevpos && currentAttachmentDigest != incomingAttachmentDigest {
993-
minRevpos, _ = ParseRevID(history[len(history)-1])
994996
bodyAtts[name].(map[string]interface{})["revpos"], _ = ParseRevID(revID)
995997
}
996998
}

rest/api_test.go

+61-50
Original file line numberDiff line numberDiff line change
@@ -8034,57 +8034,8 @@ func TestBasicAttachmentRemoval(t *testing.T) {
80348034
assert.Equal(t, attBodyExpected, attBodyActual)
80358035
}
80368036

8037-
rawDocWithAttachmentAndSyncMeta := func() []byte {
8038-
return []byte(`{
8039-
"_sync": {
8040-
"rev": "1-5fc93bd36377008f96fdae2719c174ed",
8041-
"sequence": 2,
8042-
"recent_sequences": [
8043-
2
8044-
],
8045-
"history": {
8046-
"revs": [
8047-
"1-5fc93bd36377008f96fdae2719c174ed"
8048-
],
8049-
"parents": [
8050-
-1
8051-
],
8052-
"channels": [
8053-
null
8054-
]
8055-
},
8056-
"cas": "",
8057-
"attachments": {
8058-
"hi.txt": {
8059-
"revpos": 1,
8060-
"content_type": "text/plain",
8061-
"length": 2,
8062-
"stub": true,
8063-
"digest": "sha1-witfkXg0JglCjW9RssWvTAveakI="
8064-
}
8065-
},
8066-
"time_saved": "2021-09-01T17:33:03.054227821Z"
8067-
},
8068-
"key": "value"
8069-
}`)
8070-
}
8071-
80728037
createDocWithLegacyAttachment := func(docID string, rawDoc []byte, attKey string, attBody []byte) {
8073-
// Write attachment directly to the bucket.
8074-
_, err := rt.Bucket().Add(attKey, 0, attBody)
8075-
require.NoError(t, err)
8076-
8077-
body := db.Body{}
8078-
err = body.Unmarshal(rawDoc)
8079-
require.NoError(t, err, "Error unmarshalling body")
8080-
8081-
// Write raw document to the bucket.
8082-
_, err = rt.Bucket().Add(docID, 0, rawDoc)
8083-
require.NoError(t, err)
8084-
8085-
// Migrate document metadata from document body to system xattr.
8086-
attachments := retrieveAttachmentMeta(docID)
8087-
require.Len(t, attachments, 1)
8038+
createDocWithLegacyAttachment(t, rt, docID, rawDoc, attKey, attBody)
80888039
}
80898040

80908041
t.Run("single attachment removal upon document update", func(t *testing.T) {
@@ -9393,3 +9344,63 @@ func TestAttachmentDeleteOnExpiry(t *testing.T) {
93939344
}
93949345

93959346
}
9347+
9348+
func createDocWithLegacyAttachment(t *testing.T, rt *RestTester, docID string, rawDoc []byte, attKey string, attBody []byte) {
9349+
// Write attachment directly to the bucket.
9350+
_, err := rt.Bucket().Add(attKey, 0, attBody)
9351+
require.NoError(t, err)
9352+
9353+
body := db.Body{}
9354+
err = body.Unmarshal(rawDoc)
9355+
require.NoError(t, err, "Error unmarshalling body")
9356+
9357+
// Write raw document to the bucket.
9358+
_, err = rt.Bucket().Add(docID, 0, rawDoc)
9359+
require.NoError(t, err)
9360+
9361+
// Migrate document metadata from document body to system xattr.
9362+
attachments := retrieveAttachmentMeta(t, rt, docID)
9363+
require.Len(t, attachments, 1)
9364+
}
9365+
9366+
func retrieveAttachmentMeta(t *testing.T, rt *RestTester, docID string) (attMeta map[string]interface{}) {
9367+
body := rt.getDoc(docID)
9368+
attachments, ok := body["_attachments"].(map[string]interface{})
9369+
require.True(t, ok)
9370+
return attachments
9371+
}
9372+
9373+
func rawDocWithAttachmentAndSyncMeta() []byte {
9374+
return []byte(`{
9375+
"_sync": {
9376+
"rev": "1-5fc93bd36377008f96fdae2719c174ed",
9377+
"sequence": 2,
9378+
"recent_sequences": [
9379+
2
9380+
],
9381+
"history": {
9382+
"revs": [
9383+
"1-5fc93bd36377008f96fdae2719c174ed"
9384+
],
9385+
"parents": [
9386+
-1
9387+
],
9388+
"channels": [
9389+
null
9390+
]
9391+
},
9392+
"cas": "",
9393+
"attachments": {
9394+
"hi.txt": {
9395+
"revpos": 1,
9396+
"content_type": "text/plain",
9397+
"length": 2,
9398+
"stub": true,
9399+
"digest": "sha1-witfkXg0JglCjW9RssWvTAveakI="
9400+
}
9401+
},
9402+
"time_saved": "2021-09-01T17:33:03.054227821Z"
9403+
},
9404+
"key": "value"
9405+
}`)
9406+
}

0 commit comments

Comments
 (0)