xref: /aosp_15_r20/external/executorch/backends/apple/coreml/runtime/kvstore/key_value_store.hpp (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
1 //
2 // key_value_store.hpp
3 //
4 // Copyright © 2024 Apple Inc. All rights reserved.
5 //
6 // Please refer to the license found in the LICENSE file in the root directory of the source tree.
7 
8 #pragma once
9 
10 #import <functional>
11 #include <optional>
12 #include <memory>
13 #include <string>
14 #include <system_error>
15 #include <type_traits>
16 
17 #include <database.hpp>
18 #include <types.hpp>
19 
20 namespace executorchcoreml {
21 namespace sqlite {
22 
23 /// A class to convert a type `T` from `sqlite::Value` and vice-versa.
24 ///
25 /// This is used by the KeyValueStore to read and write values to and from the backing sqlite table.
26 ///
27 template<typename T>
28 struct Converter {
29     static constexpr StorageType storage_type = StorageType::Null;
30 
31     template<typename FROM>
32     static sqlite::Value to_sqlite_value(FROM&& value);
33 
34     static T from_sqlite_value(const sqlite::UnOwnedValue& value);
35 };
36 
37 /// Converter for `int64_t` type.
38 template<>
39 struct Converter<int64_t> {
40     static constexpr StorageType storage_type = StorageType::Integer;
41 
to_sqlite_valueexecutorchcoreml::sqlite::Converter42     static inline Value to_sqlite_value(int value) {
43         return value;
44     }
45 
from_sqlite_valueexecutorchcoreml::sqlite::Converter46     static inline int64_t from_sqlite_value(const sqlite::UnOwnedValue& value) {
47         return std::get<int64_t>(value);
48     }
49 };
50 
51 /// Converter for `int` type.
52 template<>
53 struct Converter<int> {
54     static constexpr StorageType storage_type = StorageType::Integer;
55 
to_sqlite_valueexecutorchcoreml::sqlite::Converter56     static inline Value to_sqlite_value(int value) {
57         return static_cast<int>(value);
58     }
59 
from_sqlite_valueexecutorchcoreml::sqlite::Converter60     static  inline int from_sqlite_value(const sqlite::UnOwnedValue& value) {
61         return static_cast<int>(std::get<int64_t>(value));
62     }
63 };
64 
65 /// Converter for `size_t` type.
66 template<>
67 struct Converter<size_t> {
68     static constexpr StorageType storage_type = StorageType::Integer;
69 
to_sqlite_valueexecutorchcoreml::sqlite::Converter70     static inline Value to_sqlite_value(size_t value) {
71         return static_cast<int>(value);
72     }
73 
from_sqlite_valueexecutorchcoreml::sqlite::Converter74     static inline size_t from_sqlite_value(const sqlite::UnOwnedValue& value) {
75         return static_cast<size_t>(std::get<int64_t>(value));
76     }
77 };
78 
79 /// Converter for `double` type.
80 template<>
81 struct Converter<double> {
82     static constexpr StorageType storage_type = StorageType::Double;
83 
to_sqlite_valueexecutorchcoreml::sqlite::Converter84     static inline Value to_sqlite_value(double value) {
85         return value;
86     }
87 
from_sqlite_valueexecutorchcoreml::sqlite::Converter88     static inline int from_sqlite_value(const UnOwnedValue& value) {
89         return std::get<double>(value);
90     }
91 };
92 
93 /// Converter for `std::string` type.
94 template<>
95 struct Converter<std::string> {
96     static constexpr sqlite::StorageType storage_type = StorageType::Text;
97 
to_sqlite_valueexecutorchcoreml::sqlite::Converter98     static inline sqlite::Value to_sqlite_value(const std::string& value) {
99         return value;
100     }
101 
from_sqlite_valueexecutorchcoreml::sqlite::Converter102     static inline std::string from_sqlite_value(const UnOwnedValue& value) {
103         return std::string(std::get<sqlite::UnOwnedString>(value).data);
104     }
105 };
106 
107 /// Represents the sort order.
108 enum class SortOrder: uint8_t {
109     Ascending = 0,
110     Descending
111 };
112 
113 /// Represents a type-erased KeyValue store, the store is backed by a sqlite table.
114 class KeyValueStoreImpl {
115 public:
116     /// Constructs a type-erased KeyValue store.
117     ///
118     /// The backing table is created with the key column type set to the `get_key_storage_type` and the
119     /// value column type set to the `get_value_storage_type`.
120     ///
121     /// @param database The opened database.
122     /// @param name   The name of the store.
123     /// @param get_key_storage_type   The key storage type.
124     /// @param get_value_storage_type  The value storage type.
KeyValueStoreImpl(const std::shared_ptr<Database> & database,const std::string & name,StorageType get_key_storage_type,StorageType get_value_storage_type)125     inline KeyValueStoreImpl(const std::shared_ptr<Database>& database,
126                              const std::string& name,
127                              StorageType get_key_storage_type,
128                              StorageType get_value_storage_type) noexcept
129     :name_(name),
130     get_key_storage_type_(get_key_storage_type),
131     get_value_storage_type_(get_value_storage_type),
132     database_(std::move(database))
133     {}
134 
135     KeyValueStoreImpl(KeyValueStoreImpl const&) noexcept = delete;
136     KeyValueStoreImpl& operator=(KeyValueStoreImpl const&) noexcept = delete;
137 
138     /// Returns the name of the store.
name() const139     inline std::string_view name() const noexcept {
140         return name_;
141     }
142 
143     /// Returns the key storage type.
get_key_storage_type() const144     inline StorageType get_key_storage_type() const noexcept {
145         return get_key_storage_type_;
146     }
147 
148     /// Returns the value storage type.
get_value_storage_type() const149     inline StorageType get_value_storage_type() const noexcept {
150         return get_value_storage_type_;
151     }
152 
153     /// Returns the sqlite database.
database() const154     inline Database *database() const noexcept {
155         return database_.get();
156     }
157 
158     /// Returns the value for the specified key. If the key doesn't exists in the store or for some reason the operation failed
159     /// then `nullopt` is returned.
160     ///
161     /// @param key The key for which the value is retrieved.
162     /// @param fn   The function that will be invoked with the retrieved value.
163     /// @param error   On failure, error is populated with the failure reason.
164     /// @param update_access_statistics   If it is `true` then the access statistics (access time and count) are updated otherwise not.
165     /// @retval The associated value for the key.
166     bool get(const Value& key,
167              const std::function<void(const UnOwnedValue&)>& fn,
168              std::error_code& error,
169              bool update_access_statistics) noexcept;
170 
171     /// Returns `true` if the key exists in the store otherwise `false`.
172     ///
173     /// @param key The key.
174     /// @param error   On failure, error is populated with the failure reason.
175     /// @retval `true` if the key exists in the store otherwise `false`.
176     bool exists(const Value& key,
177                 std::error_code& error) noexcept;
178 
179     /// Sorts the keys by the access count and calls the `std::function` on each key value. The sort order
180     /// is specified by the `order` parameter. The caller can stop the iteration by returning `false`
181     /// from the lambda, to continue the iteration the caller must return `true`.
182     ///
183     /// @param fn The `std::function` that gets called for each key after its sorted.
184     /// @param order The sort order.
185     /// @param error   On failure, error is populated with the failure reason.
186     /// @retval `true` if the operation succeeded otherwise `false`.
187     bool get_keys_sorted_by_access_count(const std::function<bool(const UnOwnedValue&)>& fn,
188                                          SortOrder order,
189                                          std::error_code& error) noexcept;
190 
191     /// Sorts the keys by the access time and calls the `std::function` on each key value. The sort order
192     /// is specified by the `order` parameter. The caller can stop the iteration by returning `false`
193     /// from the lambda, to continue the iteration the caller must return `true`.
194     ///
195     /// @param fn The `std::function` that gets called for each key after its sorted.
196     /// @param order The sort order.
197     /// @param error   On failure, error is populated with the failure reason.
198     /// @retval `true` if the operation succeeded otherwise `false`.
199     bool get_keys_sorted_by_access_time(const std::function<bool(const UnOwnedValue&)>& fn,
200                                         SortOrder order,
201                                         std::error_code& error) noexcept;
202 
203     /// Stores a key and a value in the store, the old value is overwritten.
204     ///
205     /// @param key The key.
206     /// @param value The value.
207     /// @param error   On failure, error is populated with the failure reason.
208     /// @retval `true` if the operation succeeded otherwise `false`.
209     bool put(const Value& key, const Value& value, std::error_code& error) noexcept;
210 
211     /// Removes the specified key and the associated value from the store.
212     ///
213     /// @param key The key.
214     /// @param error   On failure, error is populated with the failure reason.
215     /// @retval `true` if the operation succeeded otherwise `false`.
216     bool remove(const Value& key, std::error_code& error) noexcept;
217 
218     /// Purges the store. The backing table is dropped and re-created.
219     bool purge(std::error_code& error)  noexcept;
220 
221     /// Returns the size of the store.
222     std::optional<size_t> size(std::error_code& error) noexcept;
223 
224     /// Initializes the store.
225     ///
226     /// @param error   On failure, error is populated with the failure reason.
227     /// @retval `true` if the operation succeeded otherwise `false`.
228     bool init(std::error_code& error) noexcept;
229 
230 private:
231     bool updateValueAccessCountAndTime(const Value& key,
232                                        int64_t accessCount,
233                                        std::error_code& error) noexcept;
234 
235     std::string name_;
236     StorageType get_key_storage_type_;
237     StorageType get_value_storage_type_;
238     std::shared_ptr<Database> database_;
239     std::atomic<int64_t> lastAccessTime_;
240 };
241 
242 /// Represents a KeyValue store, the store is backed by a sqlite table.
243 template <typename Key, typename Value, typename ValueConverter = Converter<Value>, typename KeyConverter = Converter<Key>>
244 class KeyValueStore final {
245 public:
246     template<typename T> using same_key = std::is_same<typename std::decay_t<T>, Key>;
247     template<typename T> using same_value = std::is_same<typename std::decay_t<T>, Value>;
248 
249     virtual ~KeyValueStore() = default;
250 
251     KeyValueStore(KeyValueStore const&) noexcept = delete;
252     KeyValueStore& operator=(KeyValueStore const&) noexcept = delete;
253 
KeyValueStore(std::unique_ptr<KeyValueStoreImpl> impl)254     inline KeyValueStore(std::unique_ptr<KeyValueStoreImpl> impl) noexcept
255     :impl_(std::move(impl))
256     {}
257 
258     /// Executes the provided lambda inside a transaction. The lambda must return `true` if the transaction is to
259     /// be committed otherwise `false`.
260     ///
261     /// The transaction is only committed if the lambda returns `true` otherwise the transaction is rolled-back.
262     ///
263     /// @param fn The lambda that will  be executed inside a transaction.
264     /// @param behavior   The transaction behavior.
265     /// @param error   On failure, error is populated with the failure reason.
266     /// @retval `true` if the transaction is committed otherwise `false`.
267     template<typename FN>
transaction(FN && fn,Database::TransactionBehavior behavior,std::error_code & error)268     bool transaction(FN&& fn, Database::TransactionBehavior behavior, std::error_code& error) noexcept {
269         return impl_->database()->transaction([&fn](){
270             return fn();
271         }, behavior, error);
272     }
273 
274 
275     /// Returns the value for the specified key. If the key doesn't exists in the store or the operation failed
276     /// then `nullopt` is returned.
277     ///
278     /// @param key The key for which the value is retrieved.
279     /// @param error   On failure, error is populated with the failure reason.
280     /// @param update_access_statistics   If it is `true` then the access statistics (access time and access count) are updated otherwise not.
281     /// @retval The associated value for the key.
282     template<typename T = Key>
get(T && key,std::error_code & error,bool update_access_statistics=true)283     inline std::optional<Value> get(T&& key, std::error_code& error, bool update_access_statistics = true) noexcept {
284         Value result;
285         std::function<void(const UnOwnedValue&)> fn = [&result](const UnOwnedValue& value) {
286             result = ValueConverter::from_sqlite_value(value);
287         };
288 
289         if (!impl_->get(KeyConverter::to_sqlite_value(std::forward<T>(key)), fn, error, update_access_statistics)) {
290             return std::nullopt;
291         }
292 
293         return result;
294     }
295 
296     /// Returns `true` if the key exists in the store otherwise `false`.
297     ///
298     /// @param key The key.
299     /// @param error   On failure, error is populated with the failure reason.
300     /// @retval `true` if the key exists in the store otherwise `false`.
301     template<typename T = Key>
exists(T && key,std::error_code & error)302     inline bool exists(T&& key, std::error_code& error) noexcept {
303         return impl_->exists(KeyConverter::to_sqlite_value(std::forward<T>(key)), error);
304     }
305 
306     /// Stores a key and its associated value in the store, the old value is overwritten.
307     ///
308     /// @param key The key.
309     /// @param value The value.
310     /// @param error   On failure, error is populated with the failure reason.
311     /// @retval `true` if the operation succeeded otherwise `false`.
312     template<typename T = Key, typename U = Value>
put(T && key,U && value,std::error_code & error) const313     inline bool put(T&& key, U&& value, std::error_code& error) const noexcept {
314         return impl_->put(KeyConverter::to_sqlite_value(std::forward<T>(key)),
315                           ValueConverter::to_sqlite_value(std::forward<U>(value)),
316                           error);
317     }
318 
319     /// Sorts the keys by the access count and calls the lambda on each key value. The sort order
320     /// is specified by the `order` parameter. The caller can stop the iteration by returning `false`
321     /// from the lambda, to continue the iteration the caller must return `true`.
322     ///
323     /// @param fn The lambda that gets called for each key after its sorted.
324     /// @param order The sort order.
325     /// @param error   On failure, error is populated with the failure reason.
326     /// @retval `true` if the operation succeeded otherwise `false`.
327     template<typename FN>
get_keys_sorted_by_access_count(FN && fn,SortOrder order,std::error_code & error)328     bool get_keys_sorted_by_access_count(FN&& fn,
329                                          SortOrder order,
330                                          std::error_code& error) noexcept {
331         std::function<bool(const UnOwnedValue&)> wrappedFn = [&fn](const UnOwnedValue& value) {
332             return fn(KeyConverter::from_sqlite_value(value));
333         };
334 
335         return impl_->get_keys_sorted_by_access_count(wrappedFn, order, error);
336     }
337 
338     /// Sorts the keys by the access time and calls the lambda on each key value. The sort order
339     /// is specified by the `order` parameter. The caller can stop the iteration by returning `false`
340     /// from the lambda, to continue the iteration the caller must return `true`.
341     ///
342     /// @param fn The lambda that gets called for each key after its sorted.
343     /// @param order The sort order.
344     /// @param error   On failure, error is populated with the failure reason.
345     /// @retval `true` if the operation succeeded otherwise `false`.
346     template<typename FN>
get_keys_sorted_by_access_time(FN && fn,SortOrder order,std::error_code & error)347     bool get_keys_sorted_by_access_time(FN&& fn,
348                                         SortOrder order,
349                                         std::error_code& error) noexcept {
350         std::function<bool(const UnOwnedValue&)> wrappedFn = [&fn](const UnOwnedValue& value) {
351             return fn(KeyConverter::from_sqlite_value(value));
352         };
353 
354         return impl_->get_keys_sorted_by_access_time(wrappedFn, order, error);
355     }
356 
357     /// Removes the specified key and its associated value from the store.
358     ///
359     /// @param key The key.
360     /// @param error   On failure, error is populated with the failure reason.
361     /// @retval `true` if the operation succeeded otherwise `false`.
362     template<typename T = Key>
remove(T && key,std::error_code & error)363     inline bool remove(T&& key, std::error_code& error) noexcept {
364         return impl_->remove(Converter<Key>::to_sqlite_value(std::forward<T>(key)), error);
365     }
366 
367     /// Returns the name of the store.
name() const368     inline std::string_view name() const noexcept {
369         return impl_->name();
370     }
371 
372     /// Returns the size of the store.
size(std::error_code & error) const373     inline std::optional<size_t> size(std::error_code& error) const noexcept {
374         return impl_->size(error);
375     }
376 
377     /// Purges the store. The backing table is dropped and re-created.
purge(std::error_code & error)378     inline bool purge(std::error_code& error) noexcept {
379         return impl_->purge(error);
380     }
381 
382     /// Creates a typed KeyValue store.
383     ///
384     /// The returned store's key type is `KeyType` and the value type is `ValueType`.  The store
385     /// uses the `KeyConverter` type to convert a value of `KeyType` to a `sqlite::Value` and
386     /// the `ValueConverter` to convert a value of `ValueType` to a `sqlite::Value`.
387     ///
388     /// @param database The sqlite database used for persisting.
389     /// @param name  The name of the store.
390     /// @param error   On failure, error is populated with the failure reason.
391     /// @retval the `unique_ptr` to the created store or `nullptr` if the creation failed.
392     static inline std::unique_ptr<KeyValueStore<Key, Value, ValueConverter, KeyConverter>>
make(const std::shared_ptr<Database> & database,const std::string & name,std::error_code & error)393     make(const std::shared_ptr<Database>& database, const std::string& name, std::error_code& error) noexcept {
394         auto impl = std::make_unique<KeyValueStoreImpl>(database,
395                                                         name,
396                                                         KeyConverter::storage_type,
397                                                         ValueConverter::storage_type);
398         if (!impl->init(error)) {
399             return nullptr;
400         }
401 
402         return std::make_unique<KeyValueStore<Key, Value, ValueConverter, KeyConverter>>(std::move(impl));
403     }
404 
405 private:
406     std::unique_ptr<KeyValueStoreImpl> impl_;
407 };
408 
409 } // namespace sqlite
410 } // namespace executorchcoreml
411