-
Notifications
You must be signed in to change notification settings - Fork 360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
added support for put-if-absent operations #1823
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,6 +70,12 @@ components: | |
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Error" | ||
PreconditionFailed: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note these are the docs |
||
description: Precondition Failed | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Error" | ||
ValidationError: | ||
description: Validation Error | ||
content: | ||
|
@@ -2383,6 +2389,14 @@ paths: | |
required: false | ||
schema: | ||
type: string | ||
- in: header | ||
name: If-None-Match | ||
description: Currently supports only "*" to allow uploading an object only if one doesn't exist yet | ||
example: "*" | ||
required: false | ||
schema: | ||
type: string | ||
pattern: '^\*$' # Currently, only "*" is supported | ||
responses: | ||
201: | ||
description: object metadata | ||
|
@@ -2396,6 +2410,8 @@ paths: | |
$ref: "#/components/responses/Unauthorized" | ||
404: | ||
$ref: "#/components/responses/NotFound" | ||
412: | ||
$ref: "#/components/responses/PreconditionFailed" | ||
default: | ||
$ref: "#/components/responses/ServerError" | ||
delete: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1686,6 +1686,28 @@ func (c *Controller) UploadObject(w http.ResponseWriter, r *http.Request, reposi | |
return | ||
} | ||
|
||
// before writing body, ensure preconditions - this means we essentially check for object existence twice: | ||
// once before uploading the body to save resources and time, | ||
// and then graveler will check again when passed a WriteCondition. | ||
allowOverwrite := true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is to test this specific condition outside of Graveler - why wait for the user to upload the body only to fail at CreateEntry if I already know this write is going to fail? |
||
if params.IfNoneMatch != nil { | ||
if StringValue(params.IfNoneMatch) != "*" { | ||
writeError(w, http.StatusBadRequest, "Unsupported value for If-None-Match - Only \"*\" is supported") | ||
return | ||
} | ||
// check if exists | ||
_, err := c.Catalog.GetEntry(ctx, repo.Name, branch, params.Path, catalog.GetEntryParams{ReturnExpired: true}) | ||
if err == nil { | ||
writeError(w, http.StatusPreconditionFailed, "path already exists") | ||
return | ||
} | ||
if !errors.Is(err, catalog.ErrNotFound) { | ||
writeError(w, http.StatusInternalServerError, err) | ||
return | ||
} | ||
allowOverwrite = false | ||
} | ||
|
||
// write the content | ||
file, handler, err := r.FormFile("content") | ||
if err != nil { | ||
|
@@ -1714,7 +1736,12 @@ func (c *Controller) UploadObject(w http.ResponseWriter, r *http.Request, reposi | |
Size: blob.Size, | ||
Checksum: blob.Checksum, | ||
} | ||
err = c.Catalog.CreateEntry(ctx, repo.Name, branch, entry) | ||
|
||
err = c.Catalog.CreateEntry(ctx, repo.Name, branch, entry, graveler.IfAbsent(!allowOverwrite)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool, double-checked locking actually works here! (The previous test is only an optimization, but still nice to know.) |
||
if errors.Is(err, graveler.ErrPreconditionFailed) { | ||
writeError(w, http.StatusPreconditionFailed, "path already exists") | ||
return | ||
} | ||
if handleAPIError(w, err) { | ||
return | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -654,7 +654,7 @@ func addressTypeToCatalog(t Entry_AddressType) AddressType { | |
} | ||
} | ||
|
||
func (c *Catalog) CreateEntry(ctx context.Context, repository string, branch string, entry DBEntry) error { | ||
func (c *Catalog) CreateEntry(ctx context.Context, repository string, branch string, entry DBEntry, writeConditions ...graveler.WriteConditionOption) error { | ||
repositoryID := graveler.RepositoryID(repository) | ||
branchID := graveler.BranchID(branch) | ||
ent := EntryFromCatalogEntry(entry) | ||
|
@@ -671,7 +671,7 @@ func (c *Catalog) CreateEntry(ctx context.Context, repository string, branch str | |
if err != nil { | ||
return err | ||
} | ||
return c.Store.Set(ctx, repositoryID, branchID, key, *value) | ||
return c.Store.Set(ctx, repositoryID, branchID, key, *value, writeConditions...) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style nit: no real reason to have varargs here, this API only ever gets generated slices (here on the server side). varargs now means we cannot varargs later, when we might really want it. |
||
} | ||
|
||
func (c *Catalog) CreateEntries(ctx context.Context, repository string, branch string, entries []DBEntry) error { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nopcoder I hope our generated clients don't validate. Otherwise when we add other values, an old client library won't work with an application that wants to use a non-
*
value.