Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions db/crud.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,13 @@ func (c *DatabaseCollection) OnDemandImportForGet(ctx context.Context, docid str
return docOut, nil
}

// GetRev returns the revision for the given docID and revID, or the current active revision if revID is empty.
func (db *DatabaseCollectionWithUser) GetRev(ctx context.Context, docID, revID string, history bool, attachmentsSince []string) (DocumentRevision, error) {
// GetRev returns the revision for the given docID and revOrCV, or the current active revision if revOrCV is empty.
func (db *DatabaseCollectionWithUser) GetRev(ctx context.Context, docID, revOrCV string, history bool, attachmentsSince []string) (DocumentRevision, error) {
maxHistory := 0
if history {
maxHistory = math.MaxInt32
}
return db.getRev(ctx, docID, revID, maxHistory, nil)
return db.getRev(ctx, docID, revOrCV, maxHistory, nil)
}

// Returns the body of the current revision of a document
Expand Down Expand Up @@ -2765,6 +2765,7 @@ func (db *DatabaseCollectionWithUser) postWriteUpdateHLV(ctx context.Context, do
doc.HLV.CurrentVersionCAS = casOut
}
// backup new revision to the bucket now we have a doc assigned a CV (post macro expansion) for delta generation purposes
// we don't need to store revision body backups without delta sync in 4.0, since all clients know how to use the sendReplacementRevs feature
backupRev := db.deltaSyncEnabled() && db.deltaSyncRevMaxAgeSeconds() != 0
if db.UseXattrs() && backupRev {
var newBodyWithAtts = doc._rawBody
Expand Down
2 changes: 1 addition & 1 deletion docs/api/components/parameters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ rev:
schema:
type: string
example: 2-5145e1086bb8d1d71a531e9f6b543c58
description: The document revision to target. If this is a CV value, ensure the query parameter is URL encoded (`+`->`%2B`, `@`->`%40`, etc.)
description: The document revision to target. This can be a RevTree ID or a CV (Current Version) ID. If this is a CV value, ensure the query parameter is URL encoded (`+`->`%2B`, `@`->`%40`, etc.)
revs_from:
name: revs_from
in: query
Expand Down
2 changes: 1 addition & 1 deletion docs/api/paths/public/keyspace-docid.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ get:
Etag:
schema:
type: string
description: The document revision ID if only returning 1 revision.
description: An optimistic concurrency control (OCC) value used to prevent conflicts in a subsequent update. This value can be a RevTree ID or a CV (Current Version) ID, depending on the version type requested.
content:
application/json:
schema:
Expand Down
6 changes: 6 additions & 0 deletions rest/attachment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2135,6 +2135,9 @@ func TestAttachmentsMissing(t *testing.T) {

rt.GetDatabase().FlushRevisionCacheForTest()

// strip CV from version - we don't store revision backups for old CVs like we do for RevIDs
version2.CV = db.Version{}

body := rt.GetDocVersion(docID, version2)
require.Equal(t, "sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=", body["_attachments"].(map[string]interface{})["hello.txt"].(map[string]interface{})["digest"])
}
Expand All @@ -2158,6 +2161,9 @@ func TestAttachmentsMissingNoBody(t *testing.T) {

rt.GetDatabase().FlushRevisionCacheForTest()

// strip CV from version - we don't store revision backups for old CVs like we do for RevIDs
version2.CV = db.Version{}

body := rt.GetDocVersion(docID, version2)
require.Equal(t, "sha1-Kq5sNclPz7QV2+lfQIuc6R7oRu0=", body["_attachments"].(map[string]interface{})["hello.txt"].(map[string]interface{})["digest"])
}
Expand Down
31 changes: 23 additions & 8 deletions rest/doc_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,24 @@ import (
// HTTP handler for a GET of a document
func (h *handler) handleGetDoc() error {
docid := h.PathVar("docid")
revid := h.getQuery("rev")
rev := h.getQuery("rev") // Empty, RevTree ID, or CV
openRevs := h.getQuery("open_revs")
showExp := h.getBoolQuery("show_exp")
const showCV = true // Post-beta 4.0 _always_ returns CV - negligible impact to revtree-only clients and promotes CV as the preferred OCC value

if replicator2, _ := h.getOptBoolQuery("replicator2", false); replicator2 {
return h.handleGetDocReplicator2(docid, revid)
return h.handleGetDocReplicator2(docid, rev)
}

// Extra validation of these options, since this combination isn't valid anyway. We want to prevent users from attempting to use CV with open_revs.
if openRevs != "" && rev != "" {
return base.HTTPErrorf(http.StatusBadRequest, "cannot specify both 'rev' and 'open_revs' query parameters")
}

// We'll treat empty rev as a RevTree ID, which only affects what the ETag looks like.
// If the user specifically asked for a CV, they'll get a CV ETag - but everything else can stay as RevTree ID for compatibility.
isRevTreeID := rev == "" || base.IsRevTreeID(rev)

// Check whether the caller wants a revision history, or attachment bodies, or both:
var revsLimit = 0
var revsFrom, attachmentsSince []string
Expand Down Expand Up @@ -69,7 +78,7 @@ func (h *handler) handleGetDoc() error {

if openRevs == "" {
// Single-revision GET:
value, err := h.collection.Get1xRevBodyWithHistory(h.ctx(), docid, revid, db.Get1xRevBodyOptions{
value, err := h.collection.Get1xRevBodyWithHistory(h.ctx(), docid, rev, db.Get1xRevBodyOptions{
MaxHistory: revsLimit,
HistoryFrom: revsFrom,
AttachmentsSince: attachmentsSince,
Expand All @@ -94,8 +103,14 @@ func (h *handler) handleGetDoc() error {
}
return kNotFoundError
}
foundRev := value[db.BodyRev].(string)
h.setEtag(foundRev)

var etagValue string
if isRevTreeID {
etagValue = value[db.BodyRev].(string)
} else {
etagValue = value[db.BodyCV].(string)
}
h.setEtag(etagValue)

h.db.DbStats.Database().NumDocReadsRest.Add(1)
hasBodies := attachmentsSince != nil && value[db.BodyAttachments] != nil
Expand All @@ -110,7 +125,7 @@ func (h *handler) handleGetDoc() error {
}
base.Audit(h.ctx(), base.AuditIDDocumentRead, base.AuditFields{
base.AuditFieldDocID: docid,
base.AuditFieldDocVersion: foundRev,
base.AuditFieldDocVersion: etagValue,
})
} else {
var revids []string
Expand Down Expand Up @@ -195,12 +210,12 @@ func (h *handler) handleGetDoc() error {
return nil
}

func (h *handler) handleGetDocReplicator2(docid, revid string) error {
func (h *handler) handleGetDocReplicator2(docid, revOrCV string) error {
if !base.IsEnterpriseEdition() {
return base.HTTPErrorf(http.StatusNotImplemented, "replicator2 endpoints are only supported in EE")
}

rev, err := h.collection.GetRev(h.ctx(), docid, revid, true, nil)
rev, err := h.collection.GetRev(h.ctx(), docid, revOrCV, true, nil)
if err != nil {
return err
}
Expand Down
20 changes: 14 additions & 6 deletions rest/utilities_testing_resttester.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,20 @@ func (rt *RestTester) TriggerOnDemandImport(docID string) {

// GetDocVersion returns the doc body and version for the given docID and version. If the document is not found, t.Fail will be called.
func (rt *RestTester) GetDocVersion(docID string, version DocVersion) db.Body {
rawResponse := rt.SendAdminRequest("GET", "/{{.keyspace}}/"+docID+"?rev="+version.RevTreeID, "")
occValue := version.RevTreeID
if !version.CV.IsEmpty() {
occValue = version.CV.String()
}
rawResponse := rt.SendAdminRequest("GET", "/{{.keyspace}}/"+docID+"?rev="+url.QueryEscape(occValue), "")
RequireStatus(rt.TB(), rawResponse, http.StatusOK)
var body db.Body
require.NoError(rt.TB(), base.JSONUnmarshal(rawResponse.Body.Bytes(), &body))
return body
}

// GetDocByRev returns the doc body for the given docID and Rev. If the document is not found, t.Fail will be called.
func (rt *RestTester) GetDocByRev(docID, revTreeID string) db.Body {
rawResponse := rt.SendAdminRequest("GET", fmt.Sprintf("/%s/%s?rev=%s", rt.GetSingleKeyspace(), docID, revTreeID), "")
RequireStatus(rt.TB(), rawResponse, http.StatusOK)
var body db.Body
require.NoError(rt.TB(), base.JSONUnmarshal(rawResponse.Body.Bytes(), &body))
Expand Down Expand Up @@ -143,11 +156,6 @@ func isRespUseRevTreeIDInstead(resp *TestResponse) bool {
return resp.Code == http.StatusBadRequest && strings.Contains(resp.BodyString(), "Cannot use CV to modify a document in conflict - resolve first with RevTree ID")
}

// DeleteDocRev removes a document at a specific revision. Deprecated for DeleteDoc.
func (rt *RestTester) DeleteDocRev(docID, revID string) {
rt.DeleteDoc(docID, DocVersion{RevTreeID: revID})
}

func (rt *RestTester) GetDatabaseRoot(dbname string) DatabaseRoot {
var dbroot DatabaseRoot
resp := rt.SendAdminRequest("GET", "/"+dbname+"/", "")
Expand Down
Loading