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