pw_kvs: Put/Get updates
- Combine the two versions of Put into one template function. Values
that convert to span are converted to byte spans. A span is created
for other objects, if they are trivially copyable.
- Update Item::Get and Item::ValueSize to access the value from the
descriptor instead of the key, avoiding the unnecessary descriptor
search.
Change-Id: I11e7ee62d19c1d67a63174208bd82a5cbf66636b
diff --git a/pw_kvs/key_value_store.cc b/pw_kvs/key_value_store.cc
index faf7944..9aed5fe 100644
--- a/pw_kvs/key_value_store.cc
+++ b/pw_kvs/key_value_store.cc
@@ -345,24 +345,10 @@
const KeyDescriptor* key_descriptor;
TRY_WITH_SIZE(FindExistingKeyDescriptor(key, &key_descriptor));
- Entry entry;
- TRY_WITH_SIZE(Entry::Read(partition_, key_descriptor->address(), &entry));
-
- StatusWithSize result = entry.ReadValue(value_buffer, offset_bytes);
- if (result.ok() && options_.verify_on_read && offset_bytes == 0u) {
- Status verify_result = entry.VerifyChecksum(
- entry_header_format_.checksum, key, value_buffer.first(result.size()));
- if (!verify_result.ok()) {
- std::memset(value_buffer.data(), 0, result.size());
- return StatusWithSize(verify_result, 0);
- }
-
- return StatusWithSize(verify_result, result.size());
- }
- return result;
+ return Get(key, *key_descriptor, value_buffer, offset_bytes);
}
-Status KeyValueStore::Put(string_view key, span<const byte> value) {
+Status KeyValueStore::PutBytes(string_view key, span<const byte> value) {
DBG("Writing key/value; key length=%zu, value length=%zu",
key.size(),
value.size());
@@ -453,26 +439,65 @@
const KeyDescriptor* key_descriptor;
TRY_WITH_SIZE(FindExistingKeyDescriptor(key, &key_descriptor));
- Entry entry;
- TRY_WITH_SIZE(Entry::Read(partition_, key_descriptor->address(), &entry));
+ return ValueSize(*key_descriptor);
+}
- return StatusWithSize(entry.value_size());
+StatusWithSize KeyValueStore::Get(string_view key,
+ const KeyDescriptor& descriptor,
+ span<std::byte> value_buffer,
+ size_t offset_bytes) const {
+ Entry entry;
+ TRY_WITH_SIZE(Entry::Read(partition_, descriptor.address(), &entry));
+
+ StatusWithSize result = entry.ReadValue(value_buffer, offset_bytes);
+ if (result.ok() && options_.verify_on_read && offset_bytes == 0u) {
+ Status verify_result = entry.VerifyChecksum(
+ entry_header_format_.checksum, key, value_buffer.first(result.size()));
+ if (!verify_result.ok()) {
+ std::memset(value_buffer.data(), 0, result.size());
+ return StatusWithSize(verify_result, 0);
+ }
+
+ return StatusWithSize(verify_result, result.size());
+ }
+ return result;
}
Status KeyValueStore::FixedSizeGet(std::string_view key,
- byte* value,
+ void* value,
+ size_t size_bytes) const {
+ TRY(CheckOperation(key));
+
+ const KeyDescriptor* descriptor;
+ TRY(FindExistingKeyDescriptor(key, &descriptor));
+
+ return FixedSizeGet(key, *descriptor, value, size_bytes);
+}
+
+Status KeyValueStore::FixedSizeGet(std::string_view key,
+ const KeyDescriptor& descriptor,
+ void* value,
size_t size_bytes) const {
// Ensure that the size of the stored value matches the size of the type.
// Otherwise, report error. This check avoids potential memory corruption.
- StatusWithSize result = ValueSize(key);
- if (!result.ok()) {
- return result.status();
- }
- if (result.size() != size_bytes) {
- DBG("Requested %zu B read, but value is %zu B", size_bytes, result.size());
+ TRY_ASSIGN(const size_t actual_size, ValueSize(descriptor));
+
+ if (actual_size != size_bytes) {
+ DBG("Requested %zu B read, but value is %zu B", size_bytes, actual_size);
return Status::INVALID_ARGUMENT;
}
- return Get(key, span(value, size_bytes)).status();
+
+ StatusWithSize result =
+ Get(key, descriptor, span(static_cast<byte*>(value), size_bytes), 0);
+
+ return result.status();
+}
+
+StatusWithSize KeyValueStore::ValueSize(const KeyDescriptor& descriptor) const {
+ Entry entry;
+ TRY_WITH_SIZE(Entry::Read(partition_, descriptor.address(), &entry));
+
+ return StatusWithSize(entry.value_size());
}
Status KeyValueStore::CheckOperation(string_view key) const {
diff --git a/pw_kvs/key_value_store_test.cc b/pw_kvs/key_value_store_test.cc
index e914c39..9d0e3d4 100644
--- a/pw_kvs/key_value_store_test.cc
+++ b/pw_kvs/key_value_store_test.cc
@@ -93,6 +93,10 @@
static_assert(ConvertsToSpan<const std::string_view&>());
static_assert(ConvertsToSpan<const std::string_view&&>());
+static_assert(ConvertsToSpan<bool[1]>());
+static_assert(ConvertsToSpan<char[35]>());
+static_assert(ConvertsToSpan<const int[35]>());
+
static_assert(ConvertsToSpan<span<int>>());
static_assert(ConvertsToSpan<span<byte>>());
static_assert(ConvertsToSpan<span<const int*>>());
@@ -284,6 +288,41 @@
EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Put("K", big_data));
}
+TEST_F(EmptyInitializedKvs, PutAndGetByValue_ConvertibleToSpan) {
+ constexpr float input[] = {1.0, -3.5};
+ ASSERT_EQ(Status::OK, kvs_.Put("key", input));
+
+ float output[2] = {};
+ ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
+ EXPECT_EQ(input[0], output[0]);
+ EXPECT_EQ(input[1], output[1]);
+}
+
+TEST_F(EmptyInitializedKvs, PutAndGetByValue_Span) {
+ float input[] = {1.0, -3.5};
+ ASSERT_EQ(Status::OK, kvs_.Put("key", span(input)));
+
+ float output[2] = {};
+ ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
+ EXPECT_EQ(input[0], output[0]);
+ EXPECT_EQ(input[1], output[1]);
+}
+
+TEST_F(EmptyInitializedKvs, PutAndGetByValue_NotConvertibleToSpan) {
+ struct TestStruct {
+ double a;
+ bool b;
+ };
+ const TestStruct input{-1234.5, true};
+
+ ASSERT_EQ(Status::OK, kvs_.Put("key", input));
+
+ TestStruct output;
+ ASSERT_EQ(Status::OK, kvs_.Get("key", &output));
+ EXPECT_EQ(input.a, output.a);
+ EXPECT_EQ(input.b, output.b);
+}
+
TEST_F(EmptyInitializedKvs, Get_Simple) {
ASSERT_EQ(Status::OK, kvs_.Put("Charles", as_bytes(span("Mingus"))));
@@ -324,6 +363,30 @@
EXPECT_EQ(0u, result.size());
}
+TEST_F(EmptyInitializedKvs, GetValue) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ uint32_t value = 0;
+ EXPECT_EQ(Status::OK, kvs_.Get("key", &value));
+ EXPECT_EQ(uint32_t(0xfeedbeef), value);
+}
+
+TEST_F(EmptyInitializedKvs, GetValue_TooSmall) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ uint8_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
+ EXPECT_EQ(0u, value);
+}
+
+TEST_F(EmptyInitializedKvs, GetValue_TooLarge) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ uint64_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, kvs_.Get("key", &value));
+ EXPECT_EQ(0u, value);
+}
+
TEST_F(EmptyInitializedKvs, Delete_GetDeletedKey_ReturnsNotFound) {
ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
@@ -435,6 +498,36 @@
}
}
+TEST_F(EmptyInitializedKvs, Iteration_GetValue) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ uint32_t value = 0;
+ EXPECT_EQ(Status::OK, entry.Get(&value));
+ EXPECT_EQ(uint32_t(0xfeedbeef), value);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooSmall) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ uint8_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
+ EXPECT_EQ(0u, value);
+ }
+}
+
+TEST_F(EmptyInitializedKvs, Iteration_GetValue_TooLarge) {
+ ASSERT_EQ(Status::OK, kvs_.Put("key", uint32_t(0xfeedbeef)));
+
+ for (KeyValueStore::Item entry : kvs_) {
+ uint64_t value = 0;
+ EXPECT_EQ(Status::INVALID_ARGUMENT, entry.Get(&value));
+ EXPECT_EQ(0u, value);
+ }
+}
+
TEST_F(EmptyInitializedKvs, Iteration_EmptyAfterDeletion) {
ASSERT_EQ(Status::OK, kvs_.Put("kEy", as_bytes(span("123"))));
ASSERT_EQ(Status::OK, kvs_.Delete("kEy"));
diff --git a/pw_kvs/public/pw_kvs/internal/span_traits.h b/pw_kvs/public/pw_kvs/internal/span_traits.h
index 4a0bd3a..18098ed 100644
--- a/pw_kvs/public/pw_kvs/internal/span_traits.h
+++ b/pw_kvs/public/pw_kvs/internal/span_traits.h
@@ -31,9 +31,10 @@
} // namespace internal
-// Traits class to detect if the type is a span.
+// Traits class to detect if the type converts to a span.
template <typename T>
-using ConvertsToSpan =
- std::bool_constant<internal::ConvertsToSpan<std::remove_reference_t<T>>(0)>;
+struct ConvertsToSpan
+ : public std::bool_constant<
+ internal::ConvertsToSpan<std::remove_reference_t<T>>(0)> {};
} // namespace pw::kvs
diff --git a/pw_kvs/public/pw_kvs/key_value_store.h b/pw_kvs/public/pw_kvs/key_value_store.h
index e605754..5e64146 100644
--- a/pw_kvs/public/pw_kvs/key_value_store.h
+++ b/pw_kvs/public/pw_kvs/key_value_store.h
@@ -90,23 +90,21 @@
size_t offset_bytes = 0) const;
// This overload of Get accepts a pointer to a trivially copyable object.
- // const T& is used instead of T* to prevent arrays from satisfying this
- // overload. To call Get with an array, pass as_writable_bytes(span(array)),
+ // If the value is an array, call Get with as_writable_bytes(span(array)),
// or pass a pointer to the array instead of the array itself.
template <typename Pointer,
typename = std::enable_if_t<std::is_pointer_v<Pointer>>>
Status Get(const std::string_view& key, const Pointer& pointer) const {
using T = std::remove_reference_t<std::remove_pointer_t<Pointer>>;
-
- static_assert(std::is_trivially_copyable<T>(), "Values must be copyable");
- static_assert(!std::is_pointer<T>(), "Values cannot be pointers");
-
- return FixedSizeGet(key, reinterpret_cast<std::byte*>(pointer), sizeof(T));
+ CheckThatObjectCanBePutOrGet<T>();
+ return FixedSizeGet(key, pointer, sizeof(T));
}
// Adds a key-value entry to the KVS. If the key was already present, its
// value is overwritten.
//
+ // The value may be a span of bytes or a trivially copyable object.
+ //
// In the current implementation, all keys in the KVS must have a unique hash.
// If Put is called with a key whose hash matches an existing key, nothing
// is added and ALREADY_EXISTS is returned.
@@ -119,15 +117,14 @@
// FAILED_PRECONDITION: the KVS is not initialized
// INVALID_ARGUMENT: key is empty or too long or value is too large
//
- Status Put(std::string_view key, span<const std::byte> value);
-
- // Adds a key-value entry to the KVS, using an object as the value.
- template <typename T,
- typename = std::enable_if_t<std::is_trivially_copyable_v<T> &&
- !std::is_pointer_v<T> &&
- !ConvertsToSpan<T>::value>>
+ template <typename T>
Status Put(const std::string_view& key, const T& value) {
- return Put(key, as_bytes(span(&value, 1)));
+ if constexpr (ConvertsToSpan<T>::value) {
+ return PutBytes(key, as_bytes(span(value)));
+ } else {
+ CheckThatObjectCanBePutOrGet<T>();
+ return PutBytes(key, as_bytes(span(&value, 1)));
+ }
}
// Removes a key-value entry from the KVS.
@@ -168,18 +165,24 @@
// The key as a null-terminated string.
const char* key() const { return key_buffer_.data(); }
+ // Gets the value referred to by this iterator. Equivalent to
+ // KeyValueStore::Get.
StatusWithSize Get(span<std::byte> value_buffer,
size_t offset_bytes = 0) const {
- return kvs_.Get(key(), value_buffer, offset_bytes);
+ return kvs_.Get(key(), *descriptor_, value_buffer, offset_bytes);
}
template <typename Pointer,
typename = std::enable_if_t<std::is_pointer_v<Pointer>>>
Status Get(const Pointer& pointer) const {
- return kvs_.Get(key(), pointer);
+ using T = std::remove_reference_t<std::remove_pointer_t<Pointer>>;
+ CheckThatObjectCanBePutOrGet<T>();
+ return kvs_.FixedSizeGet(key(), *descriptor_, pointer, sizeof(T));
}
- StatusWithSize ValueSize() const { return kvs_.ValueSize(key()); }
+ // Reads the size of the value referred to by this iterator. Equivalent to
+ // KeyValueStore::ValueSize.
+ StatusWithSize ValueSize() const { return kvs_.ValueSize(*descriptor_); }
private:
friend class iterator;
@@ -272,10 +275,33 @@
const Options& options);
private:
+ template <typename T>
+ static constexpr void CheckThatObjectCanBePutOrGet() {
+ static_assert(
+ std::is_trivially_copyable_v<T> && !std::is_pointer_v<T>,
+ "Only trivially copyable, non-pointer objects may be Put and Get by "
+ "value. Any value may be stored by converting it to a byte span with "
+ "as_bytes(span(&value, 1)) or as_writable_bytes(span(&value, 1)).");
+ }
+
+ Status PutBytes(std::string_view key, span<const std::byte> value);
+
+ StatusWithSize Get(std::string_view key,
+ const KeyDescriptor& descriptor,
+ span<std::byte> value_buffer,
+ size_t offset_bytes) const;
+
Status FixedSizeGet(std::string_view key,
- std::byte* value,
+ void* value,
size_t size_bytes) const;
+ Status FixedSizeGet(std::string_view key,
+ const KeyDescriptor& descriptor,
+ void* value,
+ size_t size_bytes) const;
+
+ StatusWithSize ValueSize(const KeyDescriptor& descriptor) const;
+
Status CheckOperation(std::string_view key) const;
Status FindKeyDescriptor(std::string_view key,