Skip to content

feat(services/azblob): support if_match writes and honor preconditions on block commit - #7990

Open
shanielh wants to merge 1 commit into
apache:mainfrom
shanielh:feat/azblob-conditional-writes
Open

feat(services/azblob): support if_match writes and honor preconditions on block commit#7990
shanielh wants to merge 1 commit into
apache:mainfrom
shanielh:feat/azblob-conditional-writes

Conversation

@shanielh

Copy link
Copy Markdown

Which issue does this PR close?

Part of #7889.

This is the Azure Blob half of #7890, split out so it can land independently. The GCS half of #7890 is heading for an RFC discussion and is not included here; #7889 stays open for it.

Rationale for this change

Azure Blob's Put Blob API supports If-Match with an arbitrary ETag, but OpenDAL only wired up If-None-Match, so write_with_if_match was reported as unsupported even though the underlying API can do it. This blocks optimistic-concurrency writes ("only overwrite if the object hasn't changed since I read it") on azblob.

While wiring that up, a related gap surfaced on the chunked write path. Conditional args were only applied to the single-shot Put Blob request. A caller that writes more than once before close() — via writer_with(..), chunk(..), or just two write() calls — commits through Put Block + Put Block List instead, and azblob_complete_put_block_list_request carried no conditional headers at all. if_match, if_none_match and if_not_exists were silently dropped there, so a conditional write degraded to an unconditional overwrite with no error to signal that the precondition had been discarded.

The if_none_match / if_not_exists part of that is a pre-existing bug independent of if_match: both capabilities are already declared for azblob and both work single-shot, so if_not_exists(true) through a chunked writer overwrote rather than failing. It is fixed here rather than separately because it is the same dropped-precondition bug in the same function, and fixing only if_match would leave the identical hole open two lines away. Happy to split it out if reviewers prefer.

What changes are included in this PR?

  • if_match on Put Blob: wire the If-Match header into azblob_put_blob_request mirroring the existing if_none_match handling, and declare write_with_if_match.
  • Preconditions on block commit: apply if_not_exists, if_none_match and if_match to azblob_complete_put_block_list_request. Put Block does not evaluate preconditions, so they belong on Put Block List, which does. This matches azblob_complete_copy_block_list_request, which already honors if_not_exists.
  • Behavior tests: three new shared tests (test_writer_write_with_if_not_exists, test_writer_write_with_if_none_match, test_writer_write_with_if_match) that force the multi-part commit path by writing twice before close(). They are gated on write_can_multi plus the relevant conditional capability, so they exercise any service declaring those.
  • No error-mapping changes: azblob's parse_error already maps 412/409/304 to ErrorKind::ConditionNotMatch.
  • No changes to S3, GCS, or Capability docs.

Verified against Azurite:

  • On the pre-change code, test_writer_write_with_if_none_match and test_writer_write_with_if_not_exists fail (assertion failed: res.is_err() — the conditional write succeeded where it should have returned ConditionNotMatch). test_writer_write_with_if_match early-returns there since the capability isn't declared yet.
  • With this change, the full azblob behavior suite passes: 122 passed, 0 failed. That also covers the existing test_write_with_if_match, which now runs on azblob for the first time.

Also run: cargo fmt --all -- --check, cargo clippy --workspace --all-targets --all-features -- -D warnings, and cargo test -p opendal-service-azblob.

Are there any user-facing changes?

Yes, both additive:

  • Azure Blob users can now pass if_match to write_with / writer_with, matching what was already available for if_none_match.
  • Conditional writes now behave consistently between single-shot and chunked writes on azblob. Code that previously passed if_not_exists(true) or if_none_match(..) through a chunked writer and saw the write succeed will now correctly get ErrorKind::ConditionNotMatch when the precondition does not hold. This is a bug fix, but it is a behavior change for anyone who was unknowingly relying on the precondition being dropped.

No breaking changes to public APIs.

AI Usage Statement

This PR was developed with Claude Code (Anthropic), model Claude Opus 5: codebase investigation, implementation, and verification. Behavior tests were run against a local Azurite instance in Docker, both before and after the change; no live Azure account was used.

…s on block commit

Azure Blob's Put Blob API supports If-Match with an arbitrary ETag, so wire
it in alongside the existing If-None-Match support and declare
write_with_if_match.

Conditional args were only applied to the single-shot Put Blob request.
Writing more than once before close() commits through Put Block + Put Block
List instead, and that commit request carried no conditional headers, so
if_match, if_none_match and if_not_exists were silently dropped and the
write degraded to an unconditional overwrite. Put Block does not evaluate
preconditions, so they are applied to Put Block List, which does.

Add behavior tests that force the block path by writing twice before
close(). They fail against Azurite on the previous code for if_none_match
and if_not_exists, and pass with this change.
@shanielh
shanielh requested a review from Xuanwo as a code owner July 30, 2026 13:55
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. releases-note/feat The PR implements a new feature or has a title that begins with "feat" labels Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

releases-note/feat The PR implements a new feature or has a title that begins with "feat" size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant