From ff1b9aea49a362c486fb09404c1b7215344adb4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:55:23 +0000 Subject: [PATCH 01/10] Initial plan From 8fe2be6180cfbbc77fd77e6df799d2763ad59b61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:02:39 +0000 Subject: [PATCH 02/10] Add Status enum and update BucketInfo/EntryInfo structs with status field Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- .gitignore | 1 + src/reduct/bucket.cc | 25 +++++++++++++++++++++---- src/reduct/bucket.h | 7 +++++++ src/reduct/client.cc | 15 +++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 77f4c36..3b986fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .cmake +build/ diff --git a/src/reduct/bucket.cc b/src/reduct/bucket.cc index d9348a3..2c521ea 100644 --- a/src/reduct/bucket.cc +++ b/src/reduct/bucket.cc @@ -25,6 +25,19 @@ namespace reduct { using internal::IHttpClient; using internal::QueryOptionsToJsonString; +namespace { +// Helper function to parse status from JSON +IBucket::Status ParseStatus(const nlohmann::json& json) { + if (json.contains("status")) { + const auto status = json.at("status").get(); + if (status == "DELETING") { + return IBucket::Status::kDeleting; + } + } + return IBucket::Status::kReady; +} +} // namespace + class Bucket : public IBucket { public: Bucket(std::string_view url, std::string_view name, const HttpOptions& options) @@ -85,6 +98,7 @@ class Bucket : public IBucket { .oldest_record = Time() + std::chrono::microseconds(info.at("oldest_record")), .latest_record = Time() + std::chrono::microseconds(info.at("latest_record")), .is_provisioned = info.value("is_provisioned", false), + .status = ParseStatus(info), }, Error::kOk, }; @@ -111,6 +125,7 @@ class Bucket : public IBucket { .size = entry.at("size"), .oldest_record = Time() + std::chrono::microseconds(entry.at("oldest_record")), .latest_record = Time() + std::chrono::microseconds(entry.at("latest_record")), + .status = ParseStatus(entry), }; } @@ -649,17 +664,19 @@ std::ostream& operator<<(std::ostream& os, const reduct::IBucket::Settings& sett std::ostream& operator<<(std::ostream& os, const IBucket::BucketInfo& info) { os << fmt::format( "", + "oldest_record={}, latest_record={}, is_provisioned={}, status={}>", info.name, info.entry_count, info.size, info.oldest_record.time_since_epoch().count() / 1000, - info.latest_record.time_since_epoch().count() / 1000, info.is_provisioned ? "true" : "false"); + info.latest_record.time_since_epoch().count() / 1000, info.is_provisioned ? "true" : "false", + info.status == IBucket::Status::kReady ? "READY" : "DELETING"); return os; } std::ostream& operator<<(std::ostream& os, const IBucket::EntryInfo& info) { - os << fmt::format("", + os << fmt::format("", info.name, info.record_count, info.block_count, info.size, info.oldest_record.time_since_epoch().count() / 1000, - info.latest_record.time_since_epoch().count() / 1000); + info.latest_record.time_since_epoch().count() / 1000, + info.status == IBucket::Status::kReady ? "READY" : "DELETING"); return os; } } // namespace reduct diff --git a/src/reduct/bucket.h b/src/reduct/bucket.h index 71b2ca1..489f7e4 100644 --- a/src/reduct/bucket.h +++ b/src/reduct/bucket.h @@ -31,6 +31,11 @@ class IBucket { enum class QuotaType { kNone, kFifo, kHard }; + /** + * Status of bucket or entry + */ + enum class Status { kReady, kDeleting }; + /** * Bucket Settings */ @@ -56,6 +61,7 @@ class IBucket { Time oldest_record; // timestamp of the oldest record in the bucket Time latest_record; // timestamp of the latest record in the bucket bool is_provisioned; // is bucket provisioned, you can't remove it or change settings + Status status; // status of bucket (READY or DELETING) auto operator<=>(const BucketInfo&) const noexcept = default; friend std::ostream& operator<<(std::ostream& os, const BucketInfo& info); @@ -71,6 +77,7 @@ class IBucket { size_t size; // size of stored data in the bucket in bytes Time oldest_record; // timestamp of the oldest record in the entry Time latest_record; // timestamp of the latest record in the entry + Status status; // status of entry (READY or DELETING) auto operator<=>(const EntryInfo&) const noexcept = default; friend std::ostream& operator<<(std::ostream& os, const EntryInfo& info); diff --git a/src/reduct/client.cc b/src/reduct/client.cc index 82411a1..351982e 100644 --- a/src/reduct/client.cc +++ b/src/reduct/client.cc @@ -14,6 +14,19 @@ namespace reduct { +namespace { +// Helper function to parse status from JSON +IBucket::Status ParseStatus(const nlohmann::json& json) { + if (json.contains("status")) { + const auto status = json.at("status").get(); + if (status == "DELETING") { + return IBucket::Status::kDeleting; + } + } + return IBucket::Status::kReady; +} +} // namespace + /** * Hidden implement of IClient. */ @@ -89,6 +102,8 @@ class Client : public IClient { .size = bucket.at("size"), .oldest_record = Time() + std::chrono::microseconds(bucket.at("oldest_record")), .latest_record = Time() + std::chrono::microseconds(bucket.at("latest_record")), + .is_provisioned = bucket.value("is_provisioned", false), + .status = ParseStatus(bucket), }); } } catch (const std::exception& e) { From 2340bcdf885130f8562e25558790472f043384f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:05:08 +0000 Subject: [PATCH 03/10] Update tests and documentation for status field Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- README.md | 28 ++++++++++ examples/README.md | 95 ++++++++++++++++++++++++++++++++- tests/reduct/bucket_api_test.cc | 3 ++ tests/reduct/server_api_test.cc | 2 + 4 files changed, 127 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c3bc44..98abe72 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,34 @@ int main() { } ``` +For more examples, including how to handle non-blocking deletions and bucket/entry status, see the [examples](examples/README.md) directory. + +## Non-blocking Deletions (v1.18+) + +Starting from ReductStore v1.18, bucket and entry deletions are non-blocking. When you delete a bucket or entry: + +- Deletion happens in the background +- The `status` field in `BucketInfo` and `EntryInfo` indicates the state: `IBucket::Status::kReady` or `IBucket::Status::kDeleting` +- Operations on deleting buckets/entries return HTTP 409 (Conflict) + +```cpp +// Check bucket status +auto [info, err] = bucket->GetInfo(); +if (info.status == IBucket::Status::kDeleting) { + std::cout << "Bucket is being deleted" << std::endl; +} + +// List entries with status +auto [entries, list_err] = bucket->GetEntryList(); +for (const auto& entry : entries) { + if (entry.status == IBucket::Status::kDeleting) { + std::cout << "Entry " << entry.name << " is being deleted" << std::endl; + } +} +``` + +See [examples/README.md](examples/README.md) for detailed examples on handling non-blocking deletions. + ## Integration ### Build and Install reduct-cpp system-wide diff --git a/examples/README.md b/examples/README.md index c74b36b..0625add 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1 +1,94 @@ -# Usage example +# Usage Examples + +## Bucket and Entry Status + +Starting from ReductStore v1.18, buckets and entries have a `status` field that indicates their current state: +- `IBucket::Status::kReady` - The bucket or entry is ready for operations +- `IBucket::Status::kDeleting` - The bucket or entry is being deleted in the background + +### Non-blocking Deletions + +When you delete a bucket or entry, the deletion happens in the background. During this time: +- The status field will be set to `IBucket::Status::kDeleting` +- Any read, write, or delete operations will return HTTP 409 (Conflict) +- You can poll the status to wait for deletion to complete + +Example: + +```cpp +#include +#include +#include + +using reduct::IBucket; +using reduct::IClient; + +int main() { + auto client = IClient::Build("http://127.0.0.1:8383"); + + // Create a bucket + auto [bucket, err] = client->GetOrCreateBucket("my-bucket"); + if (err) { + std::cerr << "Error: " << err << std::endl; + return -1; + } + + // Check bucket status + auto [info, info_err] = bucket->GetInfo(); + if (info_err) { + std::cerr << "Error: " << info_err << std::endl; + return -1; + } + + std::cout << "Bucket status: " + << (info.status == IBucket::Status::kReady ? "READY" : "DELETING") + << std::endl; + + // Delete the bucket (non-blocking) + err = bucket->Remove(); + if (err) { + std::cerr << "Error: " << err << std::endl; + return -1; + } + + // Wait for deletion to complete + bool deleted = false; + while (!deleted) { + auto [buckets, list_err] = client->GetBucketList(); + if (list_err) { + std::cerr << "Error: " << list_err << std::endl; + return -1; + } + + deleted = true; + for (const auto& bucket_info : buckets) { + if (bucket_info.name == "my-bucket") { + if (bucket_info.status == IBucket::Status::kDeleting) { + std::cout << "Bucket is still being deleted..." << std::endl; + deleted = false; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + break; + } + } + } + } + + std::cout << "Bucket deleted successfully" << std::endl; + return 0; +} +``` + +### Handling HTTP 409 Conflicts + +If you attempt operations on a bucket or entry that is being deleted, you'll receive HTTP 409: + +```cpp +// This will return HTTP 409 if the bucket is being deleted +auto write_err = bucket->Write("entry", std::nullopt, [](auto rec) { + rec->WriteAll("data"); +}); + +if (write_err.code == 409) { + std::cerr << "Bucket is being deleted, cannot write" << std::endl; +} +``` diff --git a/tests/reduct/bucket_api_test.cc b/tests/reduct/bucket_api_test.cc index bc93145..d373af4 100644 --- a/tests/reduct/bucket_api_test.cc +++ b/tests/reduct/bucket_api_test.cc @@ -143,6 +143,7 @@ TEST_CASE("reduct::IBucket should get bucket stats", "[bucket_api]") { .oldest_record = t, .latest_record = t + std::chrono::seconds(1), .is_provisioned = false, + .status = IBucket::Status::kReady, }); } @@ -161,6 +162,7 @@ TEST_CASE("reduct::IBucket should get list of entries", "[bucket_api]") { .size = 78, .oldest_record = t + s(1), .latest_record = t + s(2), + .status = IBucket::Status::kReady, }); REQUIRE(entries[1] == IBucket::EntryInfo{ @@ -170,6 +172,7 @@ TEST_CASE("reduct::IBucket should get list of entries", "[bucket_api]") { .size = 78, .oldest_record = t + s(3), .latest_record = t + s(4), + .status = IBucket::Status::kReady, }); } diff --git a/tests/reduct/server_api_test.cc b/tests/reduct/server_api_test.cc index 94694e8..f3bfedf 100644 --- a/tests/reduct/server_api_test.cc +++ b/tests/reduct/server_api_test.cc @@ -58,12 +58,14 @@ TEST_CASE("reduct::Client should list buckets", "[server_api]") { REQUIRE(list[0].entry_count == 2); REQUIRE(list[0].oldest_record.time_since_epoch() == s(1)); REQUIRE(list[0].latest_record.time_since_epoch() == s(4)); + REQUIRE(list[0].status == IBucket::Status::kReady); REQUIRE(list[1].name == "test_bucket_2"); REQUIRE(list[1].size == 78); REQUIRE(list[1].entry_count == 1); REQUIRE(list[1].oldest_record.time_since_epoch() == s(5)); REQUIRE(list[1].latest_record.time_since_epoch() == s(6)); + REQUIRE(list[1].status == IBucket::Status::kReady); } TEST_CASE("reduct::Client should return error", "[server_api]") { From 62a8e9e044ae38c5e6c8072a67e294becfbb4015 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:07:29 +0000 Subject: [PATCH 04/10] Refactor: Move ParseStatus to shared serialisation utility Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- src/reduct/bucket.cc | 14 +------------- src/reduct/client.cc | 13 +------------ src/reduct/internal/serialisation.cc | 10 ++++++++++ src/reduct/internal/serialisation.h | 7 +++++++ 4 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/reduct/bucket.cc b/src/reduct/bucket.cc index 2c521ea..4d988f2 100644 --- a/src/reduct/bucket.cc +++ b/src/reduct/bucket.cc @@ -23,21 +23,9 @@ namespace reduct { using internal::IHttpClient; +using internal::ParseStatus; using internal::QueryOptionsToJsonString; -namespace { -// Helper function to parse status from JSON -IBucket::Status ParseStatus(const nlohmann::json& json) { - if (json.contains("status")) { - const auto status = json.at("status").get(); - if (status == "DELETING") { - return IBucket::Status::kDeleting; - } - } - return IBucket::Status::kReady; -} -} // namespace - class Bucket : public IBucket { public: Bucket(std::string_view url, std::string_view name, const HttpOptions& options) diff --git a/src/reduct/client.cc b/src/reduct/client.cc index 351982e..1f7be85 100644 --- a/src/reduct/client.cc +++ b/src/reduct/client.cc @@ -14,18 +14,7 @@ namespace reduct { -namespace { -// Helper function to parse status from JSON -IBucket::Status ParseStatus(const nlohmann::json& json) { - if (json.contains("status")) { - const auto status = json.at("status").get(); - if (status == "DELETING") { - return IBucket::Status::kDeleting; - } - } - return IBucket::Status::kReady; -} -} // namespace +using internal::ParseStatus; /** * Hidden implement of IClient. diff --git a/src/reduct/internal/serialisation.cc b/src/reduct/internal/serialisation.cc index 87de738..355995b 100644 --- a/src/reduct/internal/serialisation.cc +++ b/src/reduct/internal/serialisation.cc @@ -329,4 +329,14 @@ Result QueryLinkOptionsToJsonString(std::string_view bucket, std return {json_data, Error::kOk}; } +IBucket::Status ParseStatus(const nlohmann::json& json) { + if (json.contains("status")) { + const auto status = json.at("status").get(); + if (status == "DELETING") { + return IBucket::Status::kDeleting; + } + } + return IBucket::Status::kReady; +} + } // namespace reduct::internal diff --git a/src/reduct/internal/serialisation.h b/src/reduct/internal/serialisation.h index e94e0a5..2affa3e 100644 --- a/src/reduct/internal/serialisation.h +++ b/src/reduct/internal/serialisation.h @@ -67,6 +67,13 @@ Result QueryOptionsToJsonString(std::string_view type, s Result QueryLinkOptionsToJsonString(std::string_view bucket, std::string_view entry_name, const IBucket::QueryLinkOptions& options); +/** + * @brief Parse status from JSON + * @param json JSON object to parse status from + * @return Status enum value (defaults to kReady if not present) + */ +IBucket::Status ParseStatus(const nlohmann::json& json); + }; // namespace reduct::internal #endif // REDUCTCPP_SERIALISATION_H From c53935a2da9fea419c9995893f8bff3ff2243bb6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 15:10:35 +0000 Subject: [PATCH 05/10] Add support for non-blocking bucket/entry deletions with status field Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- _codeql_detected_source_root | 1 + 1 file changed, 1 insertion(+) create mode 120000 _codeql_detected_source_root diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000..945c9b4 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file From e43815502d043712bd30412d3d5732f7bfc17f23 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Dec 2025 17:03:51 +0000 Subject: [PATCH 06/10] Changes before error encountered Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- AGENTS.md | 57 ++++++++++++++++++++++ README.md | 28 ----------- _codeql_detected_source_root | 1 - examples/README.md | 95 +----------------------------------- src/reduct/bucket.cc | 11 +++-- 5 files changed, 64 insertions(+), 128 deletions(-) delete mode 120000 _codeql_detected_source_root diff --git a/AGENTS.md b/AGENTS.md index abc3683..9527cba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,3 +29,60 @@ - Commits: concise, imperative summaries (`Fix parsing server url`), optionally append issue refs like `(#102)`. - PRs: describe intent and behavior changes, link issues, call out API impacts, include test commands/output, and note any server or env requirements. - Ensure builds/tests pass before review; include screenshots only when modifying docs or examples. + +## Non-blocking Deletions (v1.18+) + +Starting from ReductStore v1.18, bucket and entry deletions are non-blocking. When you delete a bucket or entry: + +- Deletion happens in the background +- The `status` field in `BucketInfo` and `EntryInfo` indicates the state: `IBucket::Status::kReady` or `IBucket::Status::kDeleting` +- Operations on deleting buckets/entries return HTTP 409 (Conflict) +- Poll the status to wait for deletion to complete + +### Example Usage + +```cpp +// Check bucket status +auto [info, err] = bucket->GetInfo(); +if (info.status == IBucket::Status::kDeleting) { + std::cout << "Bucket is being deleted" << std::endl; +} + +// List entries with status +auto [entries, list_err] = bucket->GetEntryList(); +for (const auto& entry : entries) { + if (entry.status == IBucket::Status::kDeleting) { + std::cout << "Entry " << entry.name << " is being deleted" << std::endl; + } +} + +// Wait for deletion to complete +bool deleted = false; +while (!deleted) { + auto [buckets, list_err] = client->GetBucketList(); + if (list_err) break; + + deleted = true; + for (const auto& bucket_info : buckets) { + if (bucket_info.name == "my-bucket" && bucket_info.status == IBucket::Status::kDeleting) { + deleted = false; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + break; + } + } +} +``` + +### Handling HTTP 409 Conflicts + +Operations on deleting buckets/entries will return HTTP 409: + +```cpp +auto write_err = bucket->Write("entry", std::nullopt, [](auto rec) { + rec->WriteAll("data"); +}); + +if (write_err.code == 409) { + std::cerr << "Bucket is being deleted, cannot write" << std::endl; +} +``` diff --git a/README.md b/README.md index 98abe72..7c3bc44 100644 --- a/README.md +++ b/README.md @@ -87,34 +87,6 @@ int main() { } ``` -For more examples, including how to handle non-blocking deletions and bucket/entry status, see the [examples](examples/README.md) directory. - -## Non-blocking Deletions (v1.18+) - -Starting from ReductStore v1.18, bucket and entry deletions are non-blocking. When you delete a bucket or entry: - -- Deletion happens in the background -- The `status` field in `BucketInfo` and `EntryInfo` indicates the state: `IBucket::Status::kReady` or `IBucket::Status::kDeleting` -- Operations on deleting buckets/entries return HTTP 409 (Conflict) - -```cpp -// Check bucket status -auto [info, err] = bucket->GetInfo(); -if (info.status == IBucket::Status::kDeleting) { - std::cout << "Bucket is being deleted" << std::endl; -} - -// List entries with status -auto [entries, list_err] = bucket->GetEntryList(); -for (const auto& entry : entries) { - if (entry.status == IBucket::Status::kDeleting) { - std::cout << "Entry " << entry.name << " is being deleted" << std::endl; - } -} -``` - -See [examples/README.md](examples/README.md) for detailed examples on handling non-blocking deletions. - ## Integration ### Build and Install reduct-cpp system-wide diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root deleted file mode 120000 index 945c9b4..0000000 --- a/_codeql_detected_source_root +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 0625add..c74b36b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,94 +1 @@ -# Usage Examples - -## Bucket and Entry Status - -Starting from ReductStore v1.18, buckets and entries have a `status` field that indicates their current state: -- `IBucket::Status::kReady` - The bucket or entry is ready for operations -- `IBucket::Status::kDeleting` - The bucket or entry is being deleted in the background - -### Non-blocking Deletions - -When you delete a bucket or entry, the deletion happens in the background. During this time: -- The status field will be set to `IBucket::Status::kDeleting` -- Any read, write, or delete operations will return HTTP 409 (Conflict) -- You can poll the status to wait for deletion to complete - -Example: - -```cpp -#include -#include -#include - -using reduct::IBucket; -using reduct::IClient; - -int main() { - auto client = IClient::Build("http://127.0.0.1:8383"); - - // Create a bucket - auto [bucket, err] = client->GetOrCreateBucket("my-bucket"); - if (err) { - std::cerr << "Error: " << err << std::endl; - return -1; - } - - // Check bucket status - auto [info, info_err] = bucket->GetInfo(); - if (info_err) { - std::cerr << "Error: " << info_err << std::endl; - return -1; - } - - std::cout << "Bucket status: " - << (info.status == IBucket::Status::kReady ? "READY" : "DELETING") - << std::endl; - - // Delete the bucket (non-blocking) - err = bucket->Remove(); - if (err) { - std::cerr << "Error: " << err << std::endl; - return -1; - } - - // Wait for deletion to complete - bool deleted = false; - while (!deleted) { - auto [buckets, list_err] = client->GetBucketList(); - if (list_err) { - std::cerr << "Error: " << list_err << std::endl; - return -1; - } - - deleted = true; - for (const auto& bucket_info : buckets) { - if (bucket_info.name == "my-bucket") { - if (bucket_info.status == IBucket::Status::kDeleting) { - std::cout << "Bucket is still being deleted..." << std::endl; - deleted = false; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - break; - } - } - } - } - - std::cout << "Bucket deleted successfully" << std::endl; - return 0; -} -``` - -### Handling HTTP 409 Conflicts - -If you attempt operations on a bucket or entry that is being deleted, you'll receive HTTP 409: - -```cpp -// This will return HTTP 409 if the bucket is being deleted -auto write_err = bucket->Write("entry", std::nullopt, [](auto rec) { - rec->WriteAll("data"); -}); - -if (write_err.code == 409) { - std::cerr << "Bucket is being deleted, cannot write" << std::endl; -} -``` +# Usage example diff --git a/src/reduct/bucket.cc b/src/reduct/bucket.cc index 4d988f2..18fa507 100644 --- a/src/reduct/bucket.cc +++ b/src/reduct/bucket.cc @@ -660,11 +660,12 @@ std::ostream& operator<<(std::ostream& os, const IBucket::BucketInfo& info) { } std::ostream& operator<<(std::ostream& os, const IBucket::EntryInfo& info) { - os << fmt::format("", - info.name, info.record_count, info.block_count, info.size, - info.oldest_record.time_since_epoch().count() / 1000, - info.latest_record.time_since_epoch().count() / 1000, - info.status == IBucket::Status::kReady ? "READY" : "DELETING"); + os << fmt::format( + "", + info.name, info.record_count, info.block_count, info.size, + info.oldest_record.time_since_epoch().count() / 1000, + info.latest_record.time_since_epoch().count() / 1000, + info.status == IBucket::Status::kReady ? "READY" : "DELETING"); return os; } } // namespace reduct From 04abc7e81e39b853999aad2ce67d68c432c900cf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 11:39:45 +0000 Subject: [PATCH 07/10] Update AGENTS.md with version-specific testing guidelines Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- AGENTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AGENTS.md b/AGENTS.md index 9527cba..74da0da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,7 @@ - Tests live in `tests/reduct/*_test.cc`; name cases to mirror API behaviors being verified. - Integration tests expect a reachable ReductStore at `http://127.0.0.1:8383`; set `REDUCT_CPP_TOKEN_API` for auth. Fixture purges `test_*` buckets/tokens/replications before each run. - Add or extend tests with new APIs, bug fixes, and boundary conditions that touch storage state. +- **Version-specific testing**: Always test against both `reductstore/store:main` (dev version with new features) and `reductstore/store:latest` (stable version) before pushing changes. Tag tests for new features (e.g., `[1_18]` for v1.18+ features) to exclude them when testing against stable versions. ## Commit & PR Guidelines - Commits: concise, imperative summaries (`Fix parsing server url`), optionally append issue refs like `(#102)`. From b4eb44b0821f1f00fa16a5946923c34a01d11bf4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 14:09:03 +0000 Subject: [PATCH 08/10] Revert AGENTS.md feature documentation and add documentation guidelines Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- AGENTS.md | 61 ++++--------------------------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 74da0da..7577aae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,66 +24,13 @@ - Tests live in `tests/reduct/*_test.cc`; name cases to mirror API behaviors being verified. - Integration tests expect a reachable ReductStore at `http://127.0.0.1:8383`; set `REDUCT_CPP_TOKEN_API` for auth. Fixture purges `test_*` buckets/tokens/replications before each run. - Add or extend tests with new APIs, bug fixes, and boundary conditions that touch storage state. -- **Version-specific testing**: Always test against both `reductstore/store:main` (dev version with new features) and `reductstore/store:latest` (stable version) before pushing changes. Tag tests for new features (e.g., `[1_18]` for v1.18+ features) to exclude them when testing against stable versions. ## Commit & PR Guidelines - Commits: concise, imperative summaries (`Fix parsing server url`), optionally append issue refs like `(#102)`. - PRs: describe intent and behavior changes, link issues, call out API impacts, include test commands/output, and note any server or env requirements. - Ensure builds/tests pass before review; include screenshots only when modifying docs or examples. -## Non-blocking Deletions (v1.18+) - -Starting from ReductStore v1.18, bucket and entry deletions are non-blocking. When you delete a bucket or entry: - -- Deletion happens in the background -- The `status` field in `BucketInfo` and `EntryInfo` indicates the state: `IBucket::Status::kReady` or `IBucket::Status::kDeleting` -- Operations on deleting buckets/entries return HTTP 409 (Conflict) -- Poll the status to wait for deletion to complete - -### Example Usage - -```cpp -// Check bucket status -auto [info, err] = bucket->GetInfo(); -if (info.status == IBucket::Status::kDeleting) { - std::cout << "Bucket is being deleted" << std::endl; -} - -// List entries with status -auto [entries, list_err] = bucket->GetEntryList(); -for (const auto& entry : entries) { - if (entry.status == IBucket::Status::kDeleting) { - std::cout << "Entry " << entry.name << " is being deleted" << std::endl; - } -} - -// Wait for deletion to complete -bool deleted = false; -while (!deleted) { - auto [buckets, list_err] = client->GetBucketList(); - if (list_err) break; - - deleted = true; - for (const auto& bucket_info : buckets) { - if (bucket_info.name == "my-bucket" && bucket_info.status == IBucket::Status::kDeleting) { - deleted = false; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - break; - } - } -} -``` - -### Handling HTTP 409 Conflicts - -Operations on deleting buckets/entries will return HTTP 409: - -```cpp -auto write_err = bucket->Write("entry", std::nullopt, [](auto rec) { - rec->WriteAll("data"); -}); - -if (write_err.code == 409) { - std::cerr << "Bucket is being deleted, cannot write" << std::endl; -} -``` +## Documentation Guidelines +- **Do not update README.md or create examples for new features unless explicitly requested in the issue description.** +- Keep documentation changes minimal and focused on the specific requirements outlined in the issue. +- AGENTS.md is the appropriate place for development guidelines and internal documentation, not feature documentation. From c0a62bb76b950cd1d37fce8bc4282bda18121515 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 14:16:17 +0000 Subject: [PATCH 09/10] Fix test to handle 409 error for non-blocking entry deletion Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- tests/reduct/bucket_api_test.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/reduct/bucket_api_test.cc b/tests/reduct/bucket_api_test.cc index d373af4..c439662 100644 --- a/tests/reduct/bucket_api_test.cc +++ b/tests/reduct/bucket_api_test.cc @@ -194,8 +194,9 @@ TEST_CASE("reduct::IBucket should remove entry", "[bucket_api][1_6]") { Error::kOk); REQUIRE(bucket->RemoveEntry("entry-1") == Error::kOk); - REQUIRE(bucket->RemoveEntry("entry-1") == - Error{.code = 404, .message = fmt::format("Entry 'entry-1' not found in bucket '{}'", kBucketName)}); + // After removal, the entry may be in DELETING state (409) or not found (404) + auto err = bucket->RemoveEntry("entry-1"); + REQUIRE((err.code == 404 || err.code == 409)); } TEST_CASE("reduct::IBucket should rename bucket", "[bucket_api][1_12]") { From 7443a86558e736b8ed55ca694d15ae5d06033e01 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:09:58 +0000 Subject: [PATCH 10/10] Add development workflow guidelines to AGENTS.md Co-authored-by: atimin <67068+atimin@users.noreply.github.com> --- AGENTS.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 7577aae..9679c52 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,6 +30,37 @@ - PRs: describe intent and behavior changes, link issues, call out API impacts, include test commands/output, and note any server or env requirements. - Ensure builds/tests pass before review; include screenshots only when modifying docs or examples. +## Development Workflow +Before pushing changes, always complete these steps in order: + +1. **Run cpplint** - Ensure code follows style guidelines: + ```bash + pip install "cpplint<2" + find src/ \( -name "*.cc" -o -name "*.h" \) -print0 | xargs -0 cpplint + find tests/ \( -name "*.cc" -o -name "*.h" \) -print0 | xargs cpplint + ``` + +2. **Build the code** - Verify compilation succeeds: + ```bash + cmake -S . -B build -DREDUCT_CPP_USE_FETCHCONTENT=ON -DREDUCT_CPP_ENABLE_TESTS=ON + cmake --build build + ``` + +3. **Test against both server versions** - Ensure backward compatibility: + ```bash + # Test with development version (main - new features) + docker run -d -p 8383:8383 --name reductstore-test reduct/store:main + ./build/bin/reduct-tests + docker stop reductstore-test && docker rm reductstore-test + + # Test with stable version (latest) + docker run -d -p 8383:8383 --name reductstore-test reduct/store:latest + ./build/bin/reduct-tests "~[1_18]" # Exclude v1.18+ specific tests + docker stop reductstore-test && docker rm reductstore-test + ``` + +4. **Reference GitHub Actions** - See `.github/workflows/ci.yml` for the complete CI pipeline that runs cpplint, builds on multiple platforms, and tests against both `reduct/store:main` and `reduct/store:latest`. + ## Documentation Guidelines - **Do not update README.md or create examples for new features unless explicitly requested in the issue description.** - Keep documentation changes minimal and focused on the specific requirements outlined in the issue.