hfutatzhanghb commented on code in PR #7818: URL: https://github.com/apache/opendal/pull/7818#discussion_r3467300511
########## core/core/src/docs/rfcs/7818_rename_if_not_exists.md: ########## @@ -0,0 +1,450 @@ +- Proposal Name: `rename_if_not_exists` +- Start Date: 2026-06-24 +- RFC PR: [apache/opendal#7818](https://github.com/apache/opendal/pull/7818) + +# Summary + +Extend rename with an `if_not_exists` option: + +```rust +op.rename_with("staging/file", "published/file") + .if_not_exists(true) + .await?; +``` + +The existing `rename` API keeps its overwrite semantics. When +`if_not_exists` is enabled, rename succeeds only if the destination does not +exist. A destination conflict returns `ConditionNotMatch` without modifying the +source or destination. + +# Motivation + +OpenDAL defines `rename` as an overwrite operation. Some applications also need +an atomic publish primitive: move a completed staging file into place only when +no other writer has already published that destination. + +A caller cannot implement this safely with `stat` followed by `rename`. Another +writer can create the destination after `stat` reports that it is absent but +before rename runs. A service configuration flag is also unsuitable because it +would make the meaning of the same `rename` call depend on backend construction +rather than an explicit call-site condition. + +OpenDAL already models the equivalent destination condition for write and copy +through options: + +```rust +op.write_with("path", content) + .if_not_exists(true) + .await?; + +op.copy_with("source", "target") + .if_not_exists(true) + .await?; +``` + +Rename should follow the same public API and error model. + +# Guide-level explanation + +Use `rename` when the destination may be replaced: + +```rust +use opendal::{Operator, Result}; + +async fn replace(op: Operator) -> Result<()> { + op.rename("staging/file", "published/file").await?; + Ok(()) +} +``` + +Use `rename_with(...).if_not_exists(true)` when an existing destination must be +preserved: + +```rust +use opendal::{ErrorKind, Operator, Result}; + +async fn publish(op: Operator) -> Result<()> { + match op + .rename_with("staging/file", "published/file") + .if_not_exists(true) + .await + { + Ok(()) => Ok(()), + Err(err) if err.kind() == ErrorKind::ConditionNotMatch => Err(err), + Err(err) => Err(err), + } +} +``` + +The conditional operation has the following outcomes: + +- If the destination does not exist, the source is renamed to the destination. +- If the destination exists, the operation returns `ConditionNotMatch` and + leaves both paths unchanged. +- If the service cannot enforce the destination condition atomically, the + operation returns `Unsupported`. Review Comment: @erickguan Thanks! This aligns with how copy and write handle the same situation. The options struct is always constructable — there's no compile-time gate. At runtime, the `correctness_check` layer inspects the capability flag and returns `Unsupported` when `if_not_exists` is set but the service doesn't advertise it: - write: [correctness_check.rs#L136-L143](https://github.com/apache/opendal/blob/main/core/core/src/layers/correctness_check.rs#L136-L143) - copy: [correctness_check.rs#L210-L217](https://github.com/apache/opendal/blob/main/core/core/src/layers/correctness_check.rs#L210-L217) Rename follows the identical pattern. I've added a note in the RFC explicitly calling out this alignment. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
