diff --git a/core/services/gcs/src/backend.rs b/core/services/gcs/src/backend.rs index c038fb5de58c..3648e7967bc2 100644 --- a/core/services/gcs/src/backend.rs +++ b/core/services/gcs/src/backend.rs @@ -357,6 +357,9 @@ impl Builder for GcsBuilder { write_with_content_type: true, write_with_content_encoding: true, write_with_user_metadata: true, + // Honored on single-shot writes (JSON upload + ifGenerationMatch=0). + // Multipart XML uploads cannot enforce preconditions and return + // Unsupported when if_not_exists is set — see GcsWriter::initiate_part. write_with_if_not_exists: true, // The min multipart size of Gcs is 5 MiB. diff --git a/core/services/gcs/src/writer.rs b/core/services/gcs/src/writer.rs index 9e50e3b9e0c5..b594d1836ef3 100644 --- a/core/services/gcs/src/writer.rs +++ b/core/services/gcs/src/writer.rs @@ -72,6 +72,21 @@ impl oio::MultipartWrite for GcsWriter { } async fn initiate_part(&self) -> Result { + // GCS XML API multipart uploads cannot carry request preconditions + // (`ifGenerationMatch` / similar). Single-shot `write_once` honors + // `if_not_exists` via the JSON upload API; the multipart path must not + // silently overwrite an existing object when the caller asked for a + // conditional create. + // + // ref: https://cloud.google.com/storage/docs/request-preconditions + // ref: https://github.com/apache/opendal/issues/8040 + if self.op.if_not_exists() { + return Err(Error::new( + ErrorKind::Unsupported, + "gcs multipart upload cannot honor if_not_exists; use write() for conditional creates", + )); + } + let resp = self .core .gcs_initiate_multipart_upload(&self.ctx, &self.path, &self.op) diff --git a/core/tests/behavior/async_write.rs b/core/tests/behavior/async_write.rs index 5a17dc69cf82..4076cee687be 100644 --- a/core/tests/behavior/async_write.rs +++ b/core/tests/behavior/async_write.rs @@ -821,14 +821,6 @@ pub async fn test_writer_write_with_if_not_exists(op: Operator) -> Result<()> { return Ok(()); } - // GCS XML API multipart uploads do not support preconditions, so the multipart - // writer path cannot honor if_not_exists. Tracked in - // https://github.com/apache/opendal/issues/8040 - #[cfg(feature = "services-gcs")] - if op.info().scheme() == services::GCS_SCHEME { - return Ok(()); - } - let path = TEST_FIXTURE.new_file_path(); let content = gen_fixed_bytes(cap.write_multi_min_size.unwrap_or(1)); @@ -846,6 +838,18 @@ pub async fn test_writer_write_with_if_not_exists(op: Operator) -> Result<()> { Ok(()) } .await; + + // GCS XML multipart uploads cannot enforce if_not_exists, so the writer fails + // closed with Unsupported instead of ConditionNotMatch / silent overwrite. + // Single-shot write_with_if_not_exists still works via the JSON API. + // + // ref: https://github.com/apache/opendal/issues/8040 + #[cfg(feature = "services-gcs")] + if op.info().scheme() == services::GCS_SCHEME { + assert_eq!(res.unwrap_err().kind(), ErrorKind::Unsupported); + return Ok(()); + } + assert_eq!(res.unwrap_err().kind(), ErrorKind::ConditionNotMatch); Ok(())