Skip to content

Commit dda3c4a

Browse files
authored
feat(storage): Add delete object source field in ComposeObject API (googleapis#16094)
* feat(storage): Add delete object source in compose object api * add implementation of delete object source * resolve ai comments * resolve the review comments * fix the format
1 parent 1735f56 commit dda3c4a

5 files changed

Lines changed: 100 additions & 4 deletions

File tree

google/cloud/storage/internal/grpc/object_request_parser.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@ StatusOr<google::storage::v2::ComposeObjectRequest> ToProto(
371371
request.GetOption<storage::IfMetagenerationMatch>().value());
372372
}
373373
result.set_kms_key(request.GetOption<storage::KmsKeyName>().value_or(""));
374+
// Using `GetOption` with `value_or(false)` ensures that
375+
// `delete_source_objects` is only set to `true` if explicitly
376+
// requested, preserving the default behavior (no deletion) otherwise.
377+
if (request.GetOption<storage::DeleteSourceObjects>().value_or(false)) {
378+
result.set_delete_source_objects(true);
379+
}
374380
return result;
375381
}
376382

google/cloud/storage/internal/object_requests.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,9 @@ std::string ComposeObjectRequest::JsonPayload() const {
379379
source_object_list.emplace_back(std::move(source_object_json));
380380
}
381381
compose_object_payload_json["sourceObjects"] = source_object_list;
382+
if (GetOption<DeleteSourceObjects>().value_or(false)) {
383+
compose_object_payload_json["deleteSourceObjects"] = true;
384+
}
382385

383386
return compose_object_payload_json.dump();
384387
}

google/cloud/storage/internal/object_requests.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,10 +266,10 @@ std::ostream& operator<<(std::ostream& os, UpdateObjectRequest const& r);
266266
* Represents a request to the `Objects: compose` API.
267267
*/
268268
class ComposeObjectRequest
269-
: public GenericObjectRequest<ComposeObjectRequest, EncryptionKey,
270-
DestinationPredefinedAcl, KmsKeyName,
271-
IfGenerationMatch, IfMetagenerationMatch,
272-
UserProject, WithObjectMetadata> {
269+
: public GenericObjectRequest<
270+
ComposeObjectRequest, EncryptionKey, DestinationPredefinedAcl,
271+
KmsKeyName, IfGenerationMatch, IfMetagenerationMatch, UserProject,
272+
WithObjectMetadata, DeleteSourceObjects> {
273273
public:
274274
ComposeObjectRequest() = default;
275275
explicit ComposeObjectRequest(std::string bucket_name,

google/cloud/storage/tests/object_rewrite_integration_test.cc

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,82 @@ TEST_F(ObjectRewriteIntegrationTest, ComposeSimple) {
293293
EXPECT_EQ(meta->size() * 2, composed_meta->size());
294294
}
295295

296+
TEST_F(ObjectRewriteIntegrationTest, ComposeDeleteSourceObjectsFalse) {
297+
auto client = MakeIntegrationTestClient();
298+
299+
auto object_name1 = MakeRandomObjectName();
300+
StatusOr<ObjectMetadata> meta1 = client.InsertObject(
301+
bucket_name_, object_name1, LoremIpsum(), IfGenerationMatch(0));
302+
ASSERT_STATUS_OK(meta1);
303+
ScheduleForDelete(*meta1);
304+
305+
auto object_name2 = MakeRandomObjectName();
306+
StatusOr<ObjectMetadata> meta2 = client.InsertObject(
307+
bucket_name_, object_name2, LoremIpsum(), IfGenerationMatch(0));
308+
ASSERT_STATUS_OK(meta2);
309+
ScheduleForDelete(*meta2);
310+
311+
auto composed_object_name = MakeRandomObjectName();
312+
std::vector<ComposeSourceObject> source_objects = {{object_name1, {}, {}},
313+
{object_name2, {}, {}}};
314+
315+
StatusOr<ObjectMetadata> composed_meta = client.ComposeObject(
316+
bucket_name_, source_objects, composed_object_name,
317+
WithObjectMetadata(ObjectMetadata().set_content_type("plain/text")),
318+
DeleteSourceObjects(false));
319+
ASSERT_STATUS_OK(composed_meta);
320+
ScheduleForDelete(*composed_meta);
321+
322+
EXPECT_EQ(meta1->size() + meta2->size(), composed_meta->size());
323+
324+
auto check1 = client.GetObjectMetadata(bucket_name_, object_name1);
325+
EXPECT_STATUS_OK(check1) << "Source object 1 (" << object_name1
326+
<< ") should NOT have been deleted.";
327+
328+
auto check2 = client.GetObjectMetadata(bucket_name_, object_name2);
329+
EXPECT_STATUS_OK(check2) << "Source object 2 (" << object_name2
330+
<< ") should NOT have been deleted.";
331+
}
332+
333+
TEST_F(ObjectRewriteIntegrationTest, ComposeDeleteSourceObjectsTrue) {
334+
auto client = MakeIntegrationTestClient();
335+
336+
auto object_name1 = MakeRandomObjectName();
337+
StatusOr<ObjectMetadata> meta1 = client.InsertObject(
338+
bucket_name_, object_name1, LoremIpsum(), IfGenerationMatch(0));
339+
ASSERT_STATUS_OK(meta1);
340+
ScheduleForDelete(*meta1);
341+
342+
auto object_name2 = MakeRandomObjectName();
343+
StatusOr<ObjectMetadata> meta2 = client.InsertObject(
344+
bucket_name_, object_name2, LoremIpsum(), IfGenerationMatch(0));
345+
ASSERT_STATUS_OK(meta2);
346+
ScheduleForDelete(*meta2);
347+
348+
auto composed_object_name = MakeRandomObjectName();
349+
std::vector<ComposeSourceObject> source_objects = {{object_name1, {}, {}},
350+
{object_name2, {}, {}}};
351+
352+
StatusOr<ObjectMetadata> composed_meta = client.ComposeObject(
353+
bucket_name_, source_objects, composed_object_name,
354+
WithObjectMetadata(ObjectMetadata().set_content_type("plain/text")),
355+
DeleteSourceObjects(true));
356+
ASSERT_STATUS_OK(composed_meta);
357+
ScheduleForDelete(*composed_meta);
358+
359+
EXPECT_EQ(meta1->size() + meta2->size(), composed_meta->size());
360+
361+
auto check1 = client.GetObjectMetadata(bucket_name_, object_name1);
362+
EXPECT_FALSE(check1.ok());
363+
EXPECT_EQ(StatusCode::kNotFound, check1.status().code())
364+
<< "Source object 1 (" << object_name1 << ") should have been deleted.";
365+
366+
auto check2 = client.GetObjectMetadata(bucket_name_, object_name2);
367+
EXPECT_FALSE(check2.ok());
368+
EXPECT_EQ(StatusCode::kNotFound, check2.status().code())
369+
<< "Source object 2 (" << object_name2 << ") should have been deleted.";
370+
}
371+
296372
TEST_F(ObjectRewriteIntegrationTest, ComposedUsingEncryptedObject) {
297373
// TODO(#14385) - the emulator does not support this feature for gRPC.
298374
if (UsingEmulator() && UsingGrpc()) GTEST_SKIP();

google/cloud/storage/well_known_parameters.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,17 @@ struct ReturnPartialSuccess
605605
}
606606
};
607607

608+
/**
609+
* Controls if the source objects should be deleted after a successful compose.
610+
*/
611+
struct DeleteSourceObjects
612+
: public internal::WellKnownParameter<DeleteSourceObjects, bool> {
613+
using WellKnownParameter<DeleteSourceObjects, bool>::WellKnownParameter;
614+
static char const* well_known_parameter_name() {
615+
return "deleteSourceObjects";
616+
}
617+
};
618+
608619
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
609620
} // namespace storage
610621
} // namespace cloud

0 commit comments

Comments
 (0)