diff --git a/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp b/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp index 9654045..2687375 100644 --- a/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp +++ b/example/cpp-exchange/in-mem/proof_of_concept_iface.cpp @@ -28,6 +28,12 @@ class UnorderedMapClient : public radex::IClient { return true; } + private: + void delete_key(std::string_view key) override { + _map.erase(std::string{key}); + } + + public: void put_bytes(std::string_view key, const void *bytes, radex::detail::MetaInt length) override { auto ptr = static_cast(bytes); diff --git a/include/radex/client_base.hpp b/include/radex/client_base.hpp index b76d471..b8fd1d8 100644 --- a/include/radex/client_base.hpp +++ b/include/radex/client_base.hpp @@ -201,6 +201,9 @@ class IClient { // <<< End Virtual Methods <<< private: + /// Delete the entry stored under `key` in the backing store. + virtual void delete_key(std::string_view key) = 0; + detail::ItemInfo get_item_info( std::function fetch_bytes, const data::IncomingHandle &handle); @@ -213,6 +216,9 @@ class IClient { wait_for_item_info_ptr(const data::IncomingHandle &handle, std::chrono::milliseconds timeout); + /// Delete a typed value and its associated metadata. + void delete_item(const data::OutgoingHandle &handle); + public: /// Store a scalar value under `handle`. /// @tparam T One of `int32_t`, `int64_t`, `float`, `double`. @@ -363,6 +369,11 @@ class Client : public IClient { throw_backend_unavailable(); } + private: + void delete_key(std::string_view key) override { + throw_backend_unavailable(); + } + void put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) override { throw_backend_unavailable(); } diff --git a/include/radex/dragon.hpp b/include/radex/dragon.hpp index 0e2e690..4278f98 100644 --- a/include/radex/dragon.hpp +++ b/include/radex/dragon.hpp @@ -21,6 +21,8 @@ class Client : public IClient { Client(dragon::DDict ddict); + void delete_key(std::string_view key) override; + bool dormant_timeout_warning_triggered = false; public: diff --git a/include/radex/smartredis.hpp b/include/radex/smartredis.hpp index 4c7a180..f1597f9 100644 --- a/include/radex/smartredis.hpp +++ b/include/radex/smartredis.hpp @@ -20,6 +20,8 @@ class Client : public IClient { Client(std::unique_ptr options, std::string_view logger_name); + void delete_key(std::string_view key) override; + public: /// Connect using configuration read from the environment. Client(); diff --git a/src/cpp/client.cpp b/src/cpp/client.cpp index 830281a..8010f2a 100644 --- a/src/cpp/client.cpp +++ b/src/cpp/client.cpp @@ -105,6 +105,13 @@ IClient::wait_for_item_info_ptr(const data::IncomingHandle &handle, get_item_info(fetch_bytes, handle)); } +void IClient::delete_item(const data::OutgoingHandle &handle) { + delete_key(handle.key()); + if (contains(handle.metadata_key())) { + delete_key(handle.metadata_key()); + } +} + detail::MetaData IClient::get_meta_data(const data::IncomingHandle &handle) { return detail::MetaData::from_buffer(get_bytes(handle.metadata_key())); } diff --git a/src/cpp/dragon.cpp b/src/cpp/dragon.cpp index a76e718..81a07f5 100644 --- a/src/cpp/dragon.cpp +++ b/src/cpp/dragon.cpp @@ -67,6 +67,11 @@ bool Client::contains(std::string_view key) { return ddict.contains(key_); } +void Client::delete_key(std::string_view key) { + dragon::SerializableString key_{std::string{key}}; + ddict.erase(key_); +} + void Client::put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) { // FIXME: Gross const cast needed -- check with Kent\!\! diff --git a/src/cpp/smartredis.cpp b/src/cpp/smartredis.cpp index 41a785a..519abc4 100644 --- a/src/cpp/smartredis.cpp +++ b/src/cpp/smartredis.cpp @@ -63,6 +63,10 @@ bool Client::contains(std::string_view key) { return client.key_exists(std::string{key}); } +void Client::delete_key(std::string_view key) { + client.delete_bytes(std::string{key}); +} + void Client::put_bytes(std::string_view key, const void *bytes, detail::MetaInt length) { client.put_bytes(std::string{key}, bytes, length); diff --git a/src/python/src/radex/clients/core.pxd b/src/python/src/radex/clients/core.pxd index 9ca37d9..bd83b0c 100644 --- a/src/python/src/radex/clients/core.pxd +++ b/src/python/src/radex/clients/core.pxd @@ -25,6 +25,7 @@ cdef extern from "radex/client.hpp" namespace "radex": void put_bytes(string_view, const void*, size_t) except +raise_py_error BytesBuffer get_bytes(string_view) except +raise_py_error BytesBuffer wait_for_bytes(string_view, milliseconds) except +raise_py_error + void delete_key(string_view) except + # <<< End Virtual Methods <<< void put_scalar[T](const OutgoingHandle&, T) except +raise_py_error @@ -35,3 +36,4 @@ cdef extern from "radex/client.hpp" namespace "radex": unique_ptr[ItemInfo] get_item_info_ptr(const IncomingHandle&) except +raise_py_error unique_ptr[ItemInfo] wait_for_item_info_ptr( const IncomingHandle&, milliseconds) except +raise_py_error + void delete_item(const OutgoingHandle&) except +raise_py_error diff --git a/src/python/src/radex/clients/core.pyx b/src/python/src/radex/clients/core.pyx index 4f2c606..5ef5689 100644 --- a/src/python/src/radex/clients/core.pyx +++ b/src/python/src/radex/clients/core.pyx @@ -65,6 +65,10 @@ cdef class PyClient: cdef np.number val = coerce_py_objects_to_np_numbers(value) return self._put_scalar(handle, val) + def delete_item(self, OutgoingHandle handle): + """Delete a typed value and its associated metadata.""" + self._client.delete_item(handle.unwrap()[0]) + def _put_scalar(self, OutgoingHandle handle, np.number value not None): # FIXME: Get rid of this ugly swith statment. Ideally we could used the # fused `SupportedType` type, but there seems to be a known diff --git a/tests/cpp/test_local_client.cpp b/tests/cpp/test_local_client.cpp index abc8a48..b585289 100644 --- a/tests/cpp/test_local_client.cpp +++ b/tests/cpp/test_local_client.cpp @@ -27,6 +27,12 @@ class UnorderedMapClient : public radex::IClient { return true; } + private: + void delete_key(std::string_view key) override { + _map.erase(std::string{key}); + } + + public: void put_bytes(std::string_view key, const void *bytes, radex::detail::MetaInt length) override { auto ptr = static_cast(bytes); @@ -75,6 +81,34 @@ TEMPLATE_TEST_CASE("In memory client test cases", "[in-mem]", std::int32_t, } } + SECTION("Client can delete a scalar value and its metadata") { + const radex::data::OutgoingHandle outgoing{"my-scalar"}; + const radex::data::OutgoingHandle deletion_handle{"my-scalar"}; + client.put_scalar(outgoing, TestType{}); + REQUIRE(client.contains(outgoing.key())); + REQUIRE(client.contains(outgoing.metadata_key())); + + client.delete_item(deletion_handle); + + REQUIRE_FALSE(client.contains(outgoing.key())); + REQUIRE_FALSE(client.contains(outgoing.metadata_key())); + } + + SECTION("Client can delete a tensor value and its metadata") { + const radex::data::OutgoingHandle outgoing{"my-tensor-to-delete"}; + const radex::data::OutgoingHandle deletion_handle{"my-tensor-to-delete"}; + const std::vector dims{2}; + const std::vector data{TestType{1}, TestType{2}}; + client.put_tensor(outgoing, dims, data); + REQUIRE(client.contains(outgoing.key())); + REQUIRE(client.contains(outgoing.metadata_key())); + + client.delete_item(deletion_handle); + + REQUIRE_FALSE(client.contains(outgoing.key())); + REQUIRE_FALSE(client.contains(outgoing.metadata_key())); + } + SECTION("Client can put and get a 1D tensor value") { const int size = 12; std::vector x_data(size); diff --git a/tests/python/dragon/test_delete.py b/tests/python/dragon/test_delete.py new file mode 100644 index 0000000..65b6a37 --- /dev/null +++ b/tests/python/dragon/test_delete.py @@ -0,0 +1,24 @@ +import pytest + +from radex.handles.handles import IncomingHandle, OutgoingHandle + + +def test_delete_typed_item(client, random_np_value): + key = "some-value-to-delete" + client.put_scalar(OutgoingHandle(key), random_np_value) + assert client.contains(key) + + client.delete_item(OutgoingHandle(key)) + + assert not client.contains(key) + assert not client.contains(f"meta::{key}") + + +def test_delete_raw_key(client, random_picklable): + key = "some-object-to-delete" + client.put_picklable(key, random_picklable) + assert client.contains(key) + + client.delete_item(OutgoingHandle(key)) + + assert not client.contains(key)